1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 10:27:35 +00:00

SoundPlayer: Implement playlist shuffle mode

The shuffling algorithm uses a naïve bloom filter to provide random
uniformity, avoiding items that were recently played.  With 32 bits,
double hashing, and an error rate of ~10%, this bloom filter should
be able to hold around ~16 keys, which should be sufficient to give the
illusion of fairness to the shuffling algorithm.

This avoids having to shuffle the playlist itself (user might have
spent quite a bit of time to sort them, so it's not a good idea to mess
with it), or having to create a proxy model that shuffles (that could
potentially use quite a bit of memory).
This commit is contained in:
Leandro Pereira 2021-09-30 07:41:00 -07:00 committed by Andreas Kling
parent 0812965f50
commit 314b8a374b
7 changed files with 106 additions and 11 deletions

View file

@ -8,6 +8,7 @@
#include "Playlist.h"
#include <AK/LexicalPath.h>
#include <AK/Random.h>
#include <LibAudio/Loader.h>
#include <LibGUI/MessageBox.h>
@ -33,7 +34,7 @@ void Playlist::try_fill_missing_info(Vector<M3UEntry>& entries, StringView path)
Vector<M3UEntry*> to_delete;
for (auto& entry : entries) {
if (!LexicalPath(entry.path).is_absolute())
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()) {
@ -69,8 +70,32 @@ StringView Playlist::next()
return {};
m_next_index_to_play = 0;
}
auto next = m_model->items().at(m_next_index_to_play).path;
m_next_index_to_play++;
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;
}