Commit b1097f4e authored by Michael Pyne's avatar Michael Pyne
Browse files

playlist: Reimplement random and album random sequencing.

parent 6aef3be8
......@@ -64,6 +64,7 @@
#include <time.h>
#include <cmath>
#include <algorithm>
#include <random>
#include "directoryloader.h"
#include "playlistitem.h"
......@@ -122,10 +123,24 @@ Playlist::Playlist(
: QTreeWidget(collection->playlistStack())
, m_collection(collection)
, m_playlistName(name)
, m_refillDebounce(new QTimer(this))
, m_fetcher(new WebImageFetcher(this))
{
setup(extraCols);
// The timer soaks up repeated events that may cause the random play list
// to be regenerated repeatedly.
m_refillDebounce->setInterval(100);
m_refillDebounce->setSingleShot(true);
connect(m_refillDebounce, &QTimer::timeout,
this, &Playlist::refillRandomList);
// Any of the random-related actions being triggered will cause the parent
// group to emit the triggered signal.
QActionGroup *randomGroup = action("disableRandomPlay")->actionGroup();
connect(randomGroup, &QActionGroup::triggered,
m_refillDebounce, qOverload<>(&QTimer::start));
// Some subclasses need to do even more handling but will remember to
// call setupPlaylist
if(!delaySetup) {
......@@ -174,6 +189,7 @@ Playlist::~Playlist()
// make clear that it's intentional that those subclassed versions don't
// get called (because we can't call them)
m_randomSequence.clear();
Playlist::clearItems(Playlist::items());
if(!m_shuttingDown)
......@@ -197,26 +213,82 @@ void Playlist::playFirst()
{
QTreeWidgetItemIterator listIt(const_cast<Playlist *>(this), QTreeWidgetItemIterator::NotHidden);
beginPlayingItem(static_cast<PlaylistItem *>(*listIt));
refillRandomList();
}
void Playlist::playNextAlbum()
{
#if 0
#else
playNext();
#endif
const auto &item = playingItem();
if(!item || !action("albumRandomPlay")->isChecked()) {
playNext();
return;
}
const auto currentAlbum = item->file().tag()->album();
const auto nextAlbumTrack = std::find_if(m_randomSequence.begin(), m_randomSequence.end(),
[currentAlbum](const PlaylistItem *item) {
return item->file().tag()->album() != currentAlbum;
});
if(nextAlbumTrack == m_randomSequence.end()) {
// We were on the last album, playNext will handle looping if we should loop
m_randomSequence.clear();
playNext();
}
else {
m_randomSequence.erase(m_randomSequence.begin(), nextAlbumTrack);
beginPlayingItem(*nextAlbumTrack);
}
}
void Playlist::playNext()
{
auto nowPlaying = playingItem();
QTreeWidgetItemIterator listIt(nowPlaying, QTreeWidgetItemIterator::NotHidden);
PlaylistItem *next = nullptr;
if(*listIt) {
++listIt;
auto nowPlaying = playingItem();
bool doLoop = action("loopPlaylist")->isChecked();
// Treat an item from a different playlist as if we were being asked to
// play from a stop
if(nowPlaying && nowPlaying->playlist() != this) {
nowPlaying = nullptr;
}
if(action("disableRandomPlay")->isChecked()) {
QTreeWidgetItemIterator listIt = nowPlaying
? QTreeWidgetItemIterator(nowPlaying, QTreeWidgetItemIterator::NotHidden)
: QTreeWidgetItemIterator(this, QTreeWidgetItemIterator::NotHidden);
if(*listIt && nowPlaying) {
++listIt;
}
next = static_cast<PlaylistItem *>(*listIt);
if(!next && doLoop) {
playFirst();
return;
}
}
else {
// The two random play modes are identical here, the difference is in how the
// randomized sequence is generated by refillRandomList
if(m_randomSequence.isEmpty() && (doLoop || !nowPlaying)) {
refillRandomList();
// Don't play the same track twice in a row even if it can
// "randomly" happen
if(m_randomSequence.front() == nowPlaying) {
std::swap(m_randomSequence.front(), m_randomSequence.back());
}
}
if(!m_randomSequence.isEmpty()) {
next = m_randomSequence.takeFirst();
}
}
// Will stop playback if next is still null
beginPlayingItem(next);
}
......@@ -298,7 +370,7 @@ void Playlist::saveAs()
void Playlist::updateDeletedItem(PlaylistItem *item)
{
m_members.remove(item->file().absFilePath());
m_randomSequence.removeAll(item);
m_history.removeAll(item);
}
......@@ -509,7 +581,8 @@ void Playlist::slotBeginPlayback()
PlaylistItem *item = static_cast<PlaylistItem *>(*visible);
if(item) {
beginPlayingItem(item);
refillRandomList();
playNext();
}
else {
action("stop")->trigger();
......@@ -586,7 +659,7 @@ void Playlist::slotAddCover(bool retrieveLocal)
// Called when image fetcher has added a new cover.
void Playlist::slotCoverChanged(int coverId)
{
qCDebug(JUK_LOG) << "Refreshing information for newly changed covers.\n";
qCDebug(JUK_LOG) << "Refreshing information for newly changed covers.";
refreshAlbums(selectedItems(), coverId);
}
......@@ -626,6 +699,36 @@ void Playlist::slotReload()
loadFile(m_fileName, fileInfo);
}
void Playlist::refillRandomList()
{
qCDebug(JUK_LOG) << "Refilling random items.";
if(action("disableRandomPlay")->isChecked()) {
m_randomSequence.clear();
return;
}
PlaylistItemList randomItems = visibleItems();
// See https://www.pcg-random.org/posts/cpp-seeding-surprises.html
std::random_device rdev;
uint64_t rseed = (uint64_t(rdev()) << 32) | rdev();
std::linear_congruential_engine<
uint64_t, 6364136223846793005U, 1442695040888963407U, 0U
> knuth_lcg(rseed);
std::shuffle(randomItems.begin(), randomItems.end(), knuth_lcg);
if(action("albumRandomPlay")->isChecked()) {
std::sort(randomItems.begin(), randomItems.end(),
[](PlaylistItem *a, PlaylistItem *b) {
return a->file().tag()->album() < b->file().tag()->album();
});
}
std::swap(m_randomSequence, randomItems);
}
void Playlist::slotWeightDirty(int column)
{
if(column < 0) {
......@@ -738,6 +841,7 @@ void Playlist::removeFromDisk(const PlaylistItemList &items)
void Playlist::synchronizeItemsTo(const PlaylistItemList &itemList)
{
// direct call to ::items to avoid infinite loop, bug 402355
m_randomSequence.clear();
clearItems(Playlist::items());
createItems(itemList);
}
......@@ -1667,11 +1771,12 @@ void Playlist::cleanupAfterAllFileLoadsCompleted()
// and let user adjust from there.
if(manualResize()) {
auto manualResizeAction = action<KToggleAction>("resizeColumnsManually");
auto wasChecked = manualResizeAction->isChecked();
manualResizeAction->toggle();
manualResizeAction->setChecked(false);
calculateColumnWeights();
slotUpdateColumnWidths();
manualResizeAction->toggle();
manualResizeAction->setChecked(wasChecked);
}
playlistItemsChanged();
......@@ -2089,6 +2194,7 @@ void Playlist::slotPlayCurrent()
QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected);
PlaylistItem *next = static_cast<PlaylistItem *>(*it);
beginPlayingItem(next);
refillRandomList();
}
void Playlist::slotUpdateTime()
......
......@@ -34,9 +34,10 @@
class KActionMenu;
class QAction;
class QFileInfo;
class QMimeData;
class QAction;
class QTimer;
class WebImageFetcher;
class PlaylistItem;
......@@ -405,6 +406,12 @@ public slots:
*/
virtual void slotReload();
/**
* Ensures the random sequence of playlist items is built. Ignored if we're
* not in random playback mode so it is safe to call in any mode.
*/
void refillRandomList();
/**
* Tells the listview that the next time that it paints that the weighted
* column widths must be recalculated. If this is called without a column
......@@ -685,6 +692,10 @@ private:
bool m_widthsDirty = true;
bool m_applySharedSettings = true;
/// Used for random play and album random play
PlaylistItemList m_randomSequence;
QTimer *m_refillDebounce;
PlaylistSearch* m_search;
bool m_searchEnabled = true;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment