Serenity Operating System
at master 111 lines 3.5 kB view raw
1/* 2 * Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com> 3 * Copyright (c) 2021, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "Playlist.h" 9 10#include <AK/LexicalPath.h> 11#include <AK/Random.h> 12#include <LibAudio/Loader.h> 13#include <LibCore/DeprecatedFile.h> 14#include <LibGUI/MessageBox.h> 15 16bool Playlist::load(StringView path) 17{ 18 auto parser = M3UParser::from_file(path); 19 auto items = parser->parse(true); 20 21 if (items->size() <= 0) 22 return false; 23 24 try_fill_missing_info(*items, path); 25 for (auto& item : *items) 26 m_model->items().append(item); 27 m_model->invalidate(); 28 29 return true; 30} 31 32void Playlist::try_fill_missing_info(Vector<M3UEntry>& entries, StringView path) 33{ 34 LexicalPath playlist_path(path); 35 Vector<M3UEntry*> to_delete; 36 37 for (auto& entry : entries) { 38 if (!LexicalPath { entry.path }.is_absolute()) 39 entry.path = DeprecatedString::formatted("{}/{}", playlist_path.dirname(), entry.path); 40 41 if (!entry.extended_info->file_size_in_bytes.has_value()) { 42 auto size = Core::DeprecatedFile::size(entry.path); 43 if (size.is_error()) 44 continue; 45 entry.extended_info->file_size_in_bytes = size.value(); 46 } else if (!Core::DeprecatedFile::exists(entry.path)) { 47 to_delete.append(&entry); 48 continue; 49 } 50 51 if (!entry.extended_info->track_display_title.has_value()) 52 entry.extended_info->track_display_title = LexicalPath::title(entry.path); 53 54 if (!entry.extended_info->track_length_in_seconds.has_value()) { 55 // TODO: Implement embedded metadata extractor for other audio formats 56 if (auto reader = Audio::Loader::create(entry.path); !reader.is_error()) 57 entry.extended_info->track_length_in_seconds = reader.value()->total_samples() / reader.value()->sample_rate(); 58 } 59 60 // TODO: Implement a metadata parser for the uncomfortably numerous popular embedded metadata formats 61 } 62 63 for (auto& entry : to_delete) 64 entries.remove_first_matching([&](M3UEntry& e) { return &e == entry; }); 65} 66 67StringView Playlist::next() 68{ 69 if (m_next_index_to_play >= size()) { 70 if (!looping()) 71 return {}; 72 m_next_index_to_play = 0; 73 } 74 75 auto next = m_model->items().at(m_next_index_to_play).path; 76 if (!shuffling()) { 77 m_next_index_to_play++; 78 return next; 79 } 80 81 // Try a few times getting an item to play that has not been 82 // recently played. But do not try too hard, as we don't want 83 // to wait forever. 84 int shuffle_try; 85 int const max_times_to_try = min(4, size()); 86 for (shuffle_try = 0; shuffle_try < max_times_to_try; shuffle_try++) { 87 if (!m_previously_played_paths.maybe_contains(next)) 88 break; 89 90 m_next_index_to_play = get_random_uniform(size()); 91 next = m_model->items().at(m_next_index_to_play).path; 92 } 93 if (shuffle_try == max_times_to_try) { 94 // If we tried too much, maybe it's time to try resetting 95 // the bloom filter and start over. 96 m_previously_played_paths.reset(); 97 } 98 99 m_previously_played_paths.add(next); 100 return next; 101} 102 103StringView Playlist::previous() 104{ 105 m_next_index_to_play--; 106 if (m_next_index_to_play < 0) { 107 m_next_index_to_play = 0; 108 return {}; 109 } 110 return m_model->items().at(m_next_index_to_play).path; 111}