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