diff --git a/src/core/mpris2.cpp b/src/core/mpris2.cpp index 4246156c..fa368b3b 100644 --- a/src/core/mpris2.cpp +++ b/src/core/mpris2.cpp @@ -312,7 +312,7 @@ QString Mpris2::LoopStatus() const { return "None"; } - switch (app_->playlist_manager()->sequence()->repeat_mode()) { + switch (app_->playlist_manager()->active() ? app_->playlist_manager()->active()->RepeatMode() : app_->playlist_manager()->sequence()->repeat_mode()) { case PlaylistSequence::RepeatMode::Album: case PlaylistSequence::RepeatMode::Playlist: return "Playlist"; case PlaylistSequence::RepeatMode::Track: return "Track"; @@ -351,7 +351,8 @@ void Mpris2::SetRate(double rate) { bool Mpris2::Shuffle() const { - return app_->playlist_manager()->sequence()->shuffle_mode() != PlaylistSequence::ShuffleMode::Off; + const PlaylistSequence::ShuffleMode shuffle_mode = app_->playlist_manager()->active() ? app_->playlist_manager()->active()->RepeatMode() : app_->playlist_manager()->sequence()->repeat_mode(); + return shuffle_mode != PlaylistSequence::ShuffleMode::Off; } diff --git a/src/core/player.cpp b/src/core/player.cpp index 248d5bc0..9a960e8f 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -386,7 +386,7 @@ void Player::NextItem(const EngineBase::TrackChangeFlags change, const Playlist: // If we received too many errors in auto change, with repeat enabled, we stop if (change & EngineBase::TrackChangeType::Auto) { - const PlaylistSequence::RepeatMode repeat_mode = active_playlist->sequence()->repeat_mode(); + const PlaylistSequence::RepeatMode repeat_mode = active_playlist->RepeatMode(); if (repeat_mode != PlaylistSequence::RepeatMode::Off) { if ((repeat_mode == PlaylistSequence::RepeatMode::Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->filter()->rowCount())) { // We received too many "Error" state changes: probably looping over a playlist which contains only unavailable elements: stop now. @@ -561,6 +561,7 @@ void Player::Stop(const bool stop_after) { engine_->Stop(stop_after); app_->playlist_manager()->active()->set_current_row(-1); + app_->playlist_manager()->active()->reset_played_indexes(); current_item_.reset(); pause_time_ = QDateTime(); play_offset_nanosec_ = 0; diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index ef5c6907..d8bfc0d9 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -114,6 +114,8 @@ const int Playlist::kUndoItemLimit = 500; const qint64 Playlist::kMinScrobblePointNsecs = 31LL * kNsecPerSec; const qint64 Playlist::kMaxScrobblePointNsecs = 240LL * kNsecPerSec; +const int Playlist::kMaxPlayedIndexes = 100; + Playlist::Playlist(SharedPtr backend, SharedPtr task_manager, SharedPtr collection_backend, const int id, const QString &special_type, const bool favorite, QObject *parent) : QAbstractListModel(parent), is_loading_(false), @@ -127,7 +129,6 @@ Playlist::Playlist(SharedPtr backend, SharedPtr ta favorite_(favorite), current_is_paused_(false), current_virtual_index_(-1), - is_shuffled_(false), playlist_sequence_(nullptr), ignore_sorting_(false), undo_stack_(new QUndoStack(this)), @@ -489,7 +490,6 @@ int Playlist::last_played_row() const { } void Playlist::ShuffleModeChanged(const PlaylistSequence::ShuffleMode mode) { - is_shuffled_ = (mode != PlaylistSequence::ShuffleMode::Off); ReshuffleIndices(); } @@ -501,9 +501,8 @@ bool Playlist::FilterContainsVirtualIndex(const int i) const { int Playlist::NextVirtualIndex(int i, const bool ignore_repeat_track) const { - PlaylistSequence::RepeatMode repeat_mode = playlist_sequence_->repeat_mode(); - PlaylistSequence::ShuffleMode shuffle_mode = playlist_sequence_->shuffle_mode(); - bool album_only = repeat_mode == PlaylistSequence::RepeatMode::Album || shuffle_mode == PlaylistSequence::ShuffleMode::InsideAlbum; + const PlaylistSequence::RepeatMode repeat_mode = RepeatMode(); + const bool album_only = repeat_mode == PlaylistSequence::RepeatMode::Album || ShuffleMode() == PlaylistSequence::ShuffleMode::InsideAlbum; // This one's easy - if we have to repeat the current track then just return i if (repeat_mode == PlaylistSequence::RepeatMode::Track && !ignore_repeat_track) { @@ -546,9 +545,8 @@ int Playlist::NextVirtualIndex(int i, const bool ignore_repeat_track) const { int Playlist::PreviousVirtualIndex(int i, const bool ignore_repeat_track) const { - PlaylistSequence::RepeatMode repeat_mode = playlist_sequence_->repeat_mode(); - PlaylistSequence::ShuffleMode shuffle_mode = playlist_sequence_->shuffle_mode(); - bool album_only = repeat_mode == PlaylistSequence::RepeatMode::Album || shuffle_mode == PlaylistSequence::ShuffleMode::InsideAlbum; + const PlaylistSequence::RepeatMode repeat_mode = RepeatMode(); + const bool album_only = repeat_mode == PlaylistSequence::RepeatMode::Album || ShuffleMode() == PlaylistSequence::ShuffleMode::InsideAlbum; // This one's easy - if we have to repeat the current track then just return i if (repeat_mode == PlaylistSequence::RepeatMode::Track && !ignore_repeat_track) { @@ -582,7 +580,7 @@ int Playlist::PreviousVirtualIndex(int i, const bool ignore_repeat_track) const } -int Playlist::next_row(const bool ignore_repeat_track) const { +int Playlist::next_row(const bool ignore_repeat_track) { // Any queued items take priority if (!queue_->is_empty()) { @@ -593,7 +591,7 @@ int Playlist::next_row(const bool ignore_repeat_track) const { if (next_virtual_index >= virtual_items_.count()) { // We've gone off the end of the playlist. - switch (playlist_sequence_->repeat_mode()) { + switch (RepeatMode()) { case PlaylistSequence::RepeatMode::Off: case PlaylistSequence::RepeatMode::Intro: return -1; @@ -602,6 +600,7 @@ int Playlist::next_row(const bool ignore_repeat_track) const { break; default: + ReshuffleIndices(); next_virtual_index = NextVirtualIndex(-1, ignore_repeat_track); break; } @@ -614,14 +613,18 @@ int Playlist::next_row(const bool ignore_repeat_track) const { } -int Playlist::previous_row(const bool ignore_repeat_track) const { +int Playlist::previous_row(const bool ignore_repeat_track) { + + while (!played_indexes_.isEmpty()) { + const QPersistentModelIndex idx = played_indexes_.takeLast(); + if (idx.isValid() && idx != current_item_index_) return idx.row(); + } int prev_virtual_index = PreviousVirtualIndex(current_virtual_index_, ignore_repeat_track); - if (prev_virtual_index < 0) { // We've gone off the beginning of the playlist. - switch (playlist_sequence_->repeat_mode()) { + switch (RepeatMode()) { case PlaylistSequence::RepeatMode::Off: return -1; case PlaylistSequence::RepeatMode::Track: @@ -643,8 +646,8 @@ int Playlist::previous_row(const bool ignore_repeat_track) const { void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const bool is_stopping, const bool force_inform) { - QModelIndex old_current_item_index = current_item_index_; - QModelIndex new_current_item_index; + QPersistentModelIndex old_current_item_index = current_item_index_; + QPersistentModelIndex new_current_item_index; if (i != -1) new_current_item_index = QPersistentModelIndex(index(i, 0, QModelIndex())); if (new_current_item_index != current_item_index_) ClearStreamMetadata(); @@ -678,7 +681,7 @@ void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const b if (i == -1) { current_virtual_index_ = -1; } - else if (is_shuffled_ && current_virtual_index_ == -1) { + else if (ShuffleMode() != PlaylistSequence::ShuffleMode::Off && current_virtual_index_ == -1) { // This is the first thing we're playing so we want to make sure the array is shuffled ReshuffleIndices(); @@ -687,7 +690,7 @@ void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const b virtual_items_.prepend(i); current_virtual_index_ = 0; } - else if (is_shuffled_) { + else if (ShuffleMode() != PlaylistSequence::ShuffleMode::Off) { current_virtual_index_ = static_cast(virtual_items_.indexOf(i)); } else { @@ -727,6 +730,10 @@ void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const b if (current_item_index_.isValid()) { last_played_item_index_ = current_item_index_; + played_indexes_.append(current_item_index_); + if (played_indexes_.count() > kMaxPlayedIndexes) { + played_indexes_.remove(0, played_indexes_.count() - kMaxPlayedIndexes); + } ScheduleSave(); } @@ -913,6 +920,8 @@ void Playlist::MoveItemWithoutUndo(const int source, const int dest) { void Playlist::MoveItemsWithoutUndo(const QList &source_rows, int pos) { emit layoutAboutToBeChanged(); + + PlaylistItemPtrList old_items = items_; PlaylistItemPtrList moved_items; moved_items.reserve(source_rows.count()); @@ -954,7 +963,22 @@ void Playlist::MoveItemsWithoutUndo(const QList &source_rows, int pos) { changePersistentIndex(pidx, index(pidx.row() + d, pidx.column(), QModelIndex())); } } - current_virtual_index_ = static_cast(virtual_items_.indexOf(current_row())); + + // Update virtual items + if (ShuffleMode() != PlaylistSequence::ShuffleMode::Off) { + const QList old_virtual_items = virtual_items_; + for (int i = 0; i < virtual_items_.count(); ++i) { + virtual_items_[i] = items_.indexOf(old_items[old_virtual_items[i]]); + } + } + + // Update current virtual index + if (current_item_index_.isValid()) { + current_virtual_index_ = static_cast(virtual_items_.indexOf(current_item_index_.row())); + } + else { + current_virtual_index_ = -1; + } emit layoutChanged(); @@ -966,6 +990,7 @@ void Playlist::MoveItemsWithoutUndo(int start, const QList &dest_rows) { emit layoutAboutToBeChanged(); + PlaylistItemPtrList old_items = items_; PlaylistItemPtrList moved_items; moved_items.reserve(dest_rows.count()); @@ -1010,7 +1035,22 @@ void Playlist::MoveItemsWithoutUndo(int start, const QList &dest_rows) { changePersistentIndex(pidx, index(pidx.row() + d, pidx.column(), QModelIndex())); } } - current_virtual_index_ = static_cast(virtual_items_.indexOf(current_row())); + + // Update virtual items + if (ShuffleMode() != PlaylistSequence::ShuffleMode::Off) { + const QList old_virtual_items = virtual_items_; + for (int i = 0; i < virtual_items_.count(); ++i) { + virtual_items_[i] = items_.indexOf(old_items[old_virtual_items[i]]); + } + } + + // Update current virtual index + if (current_item_index_.isValid()) { + current_virtual_index_ = static_cast(virtual_items_.indexOf(current_item_index_.row())); + } + else { + current_virtual_index_ = -1; + } emit layoutChanged(); @@ -1085,14 +1125,13 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const in queue_->InsertFirst(indexes); } - ScheduleSave(); - if (auto_sort_) { sort(sort_column_, sort_order_); } - else { - ReshuffleIndices(); - } + + ReshuffleIndices(); + + ScheduleSave(); } @@ -1417,8 +1456,6 @@ void Playlist::sort(int column, Qt::SortOrder order) { undo_stack_->push(new PlaylistUndoCommands::SortItems(this, column, order, new_items)); - ReshuffleIndices(); - } void Playlist::ReOrderWithoutUndo(const PlaylistItemPtrList &new_items) { @@ -1438,6 +1475,22 @@ void Playlist::ReOrderWithoutUndo(const PlaylistItemPtrList &new_items) { changePersistentIndex(idx, index(new_rows[item], idx.column(), idx.parent())); } + // Update virtual items + if (ShuffleMode() != PlaylistSequence::ShuffleMode::Off) { + const QList old_virtual_items = virtual_items_; + for (int i = 0; i < virtual_items_.count(); ++i) { + virtual_items_[i] = items_.indexOf(old_items[old_virtual_items[i]]); + } + } + + // Update current virtual index + if (current_item_index_.isValid()) { + current_virtual_index_ = static_cast(virtual_items_.indexOf(current_item_index_.row())); + } + else { + current_virtual_index_ = -1; + } + emit layoutChanged(); emit PlaylistChanged(); @@ -1652,9 +1705,9 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co if (row < 0 || row >= items_.size() || row + count > items_.size()) { return PlaylistItemPtrList(); } - beginRemoveRows(QModelIndex(), row, row + count - 1); // Remove items + beginRemoveRows(QModelIndex(), row, row + count - 1); PlaylistItemPtrList ret; ret.reserve(count); for (int i = 0; i < count; ++i) { @@ -1669,20 +1722,26 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co } } - endRemoveRows(); - - QList::iterator it = virtual_items_.begin(); - while (it != virtual_items_.end()) { - if (*it >= items_.count()) { - it = virtual_items_.erase(it); // clazy:exclude=strict-iterators + // Update virtual items + for (int i = row; i < items_.count() + count; ++i) { + Q_ASSERT(virtual_items_.count(i) == 1); + if (i >= row + count) { + virtual_items_[virtual_items_.indexOf(i)] = i - count; } else { - ++it; + virtual_items_.remove(virtual_items_.indexOf(i)); } } - // Reset current_virtual_index_ - if (current_row() == -1) { + endRemoveRows(); + + Q_ASSERT(items_.count() == virtual_items_.count()); + + // Update current virtual index + if (current_item_index_.isValid()) { + current_virtual_index_ = static_cast(virtual_items_.indexOf(current_item_index_.row())); + } + else { if (row - 1 > 0 && row - 1 < items_.size()) { current_virtual_index_ = static_cast(virtual_items_.indexOf(row - 1)); } @@ -1690,9 +1749,6 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co current_virtual_index_ = -1; } } - else { - current_virtual_index_ = static_cast(virtual_items_.indexOf(current_row())); - } ScheduleSave(); @@ -1744,8 +1800,7 @@ void Playlist::ClearStreamMetadata() { bool Playlist::stop_after_current() const { - PlaylistSequence::RepeatMode repeat_mode = playlist_sequence_->repeat_mode(); - if (repeat_mode == PlaylistSequence::RepeatMode::OneByOne) { + if (RepeatMode() == PlaylistSequence::RepeatMode::OneByOne) { return true; } @@ -1910,45 +1965,27 @@ bool AlbumShuffleComparator(const QMap &album_key_positions, const void Playlist::ReshuffleIndices() { - if (!playlist_sequence_) { - return; - } - - if (playlist_sequence_->shuffle_mode() == PlaylistSequence::ShuffleMode::Off) { - // No shuffling - sort the virtual item list normally. - std::sort(virtual_items_.begin(), virtual_items_.end()); - if (current_row() != -1) { - current_virtual_index_ = static_cast(virtual_items_.indexOf(current_row())); - } - return; - } - - // If the user is already playing a song, advance the begin iterator to only shuffle items that haven't been played yet. - QList::iterator begin = virtual_items_.begin(); - QList::iterator end = virtual_items_.end(); - if (current_virtual_index_ != -1) { - std::advance(begin, current_virtual_index_ + 1); - } - - std::random_device rd; - std::mt19937 g(rd()); - - switch (playlist_sequence_->shuffle_mode()) { - case PlaylistSequence::ShuffleMode::Off: - // Handled above. + const PlaylistSequence::ShuffleMode shuffle_mode = ShuffleMode(); + switch (shuffle_mode) { + case PlaylistSequence::ShuffleMode::Off:{ + // No shuffling - sort the virtual item list normally. + std::sort(virtual_items_.begin(), virtual_items_.end()); break; + } case PlaylistSequence::ShuffleMode::All: - case PlaylistSequence::ShuffleMode::InsideAlbum: - std::shuffle(begin, end, g); + case PlaylistSequence::ShuffleMode::InsideAlbum:{ + std::random_device rd; + std::shuffle(virtual_items_.begin(), virtual_items_.end(), std::mt19937(rd())); break; + } - case PlaylistSequence::ShuffleMode::Albums: { + case PlaylistSequence::ShuffleMode::Albums:{ QMap album_keys; // real index -> key QSet album_key_set; // unique keys // Find all the unique albums in the playlist - for (QList::iterator it = begin; it != end; ++it) { + for (QList::const_iterator it = virtual_items_.begin(); it != virtual_items_.end(); ++it) { const int index = *it; const QString key = items_[index]->Metadata().AlbumKey(); album_keys[index] = key; @@ -1957,7 +1994,8 @@ void Playlist::ReshuffleIndices() { // Shuffle them QStringList shuffled_album_keys = album_key_set.values(); - std::shuffle(shuffled_album_keys.begin(), shuffled_album_keys.end(), g); + std::random_device rd; + std::shuffle(shuffled_album_keys.begin(), shuffled_album_keys.end(), std::mt19937(rd())); // If the user is currently playing a song, force its album to be first // Or if the song was not playing but it was selected, force its album to be first. @@ -1976,12 +2014,20 @@ void Playlist::ReshuffleIndices() { } // Sort the virtual items - std::stable_sort(begin, end, std::bind(AlbumShuffleComparator, album_key_positions, album_keys, std::placeholders::_1, std::placeholders::_2)); + std::stable_sort(virtual_items_.begin(), virtual_items_.end(), std::bind(AlbumShuffleComparator, album_key_positions, album_keys, std::placeholders::_1, std::placeholders::_2)); break; } } + // Update current virtual index + if (current_item_index_.isValid()) { + current_virtual_index_ = static_cast(virtual_items_.indexOf(current_item_index_.row())); + } + else { + current_virtual_index_ = -1; + } + } void Playlist::set_sequence(PlaylistSequence *v) { @@ -1989,7 +2035,7 @@ void Playlist::set_sequence(PlaylistSequence *v) { playlist_sequence_ = v; QObject::connect(v, &PlaylistSequence::ShuffleModeChanged, this, &Playlist::ShuffleModeChanged); - ShuffleModeChanged(v->shuffle_mode()); + ShuffleModeChanged(ShuffleMode()); } @@ -2305,7 +2351,7 @@ void Playlist::TurnOffDynamicPlaylist() { dynamic_playlist_.reset(); if (playlist_sequence_) { - ShuffleModeChanged(playlist_sequence_->shuffle_mode()); + ShuffleModeChanged(ShuffleMode()); } emit DynamicModeChanged(false); diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h index cd48deee..c84cbc90 100644 --- a/src/playlist/playlist.h +++ b/src/playlist/playlist.h @@ -182,8 +182,9 @@ class Playlist : public QAbstractListModel { int current_row() const; int last_played_row() const; void reset_last_played() { last_played_item_index_ = QPersistentModelIndex(); } - int next_row(const bool ignore_repeat_track = false) const; - int previous_row(const bool ignore_repeat_track = false) const; + void reset_played_indexes() { played_indexes_.clear(); } + int next_row(const bool ignore_repeat_track = false); + int previous_row(const bool ignore_repeat_track = false); const QModelIndex current_index() const; @@ -211,6 +212,9 @@ class Playlist : public QAbstractListModel { void set_sequence(PlaylistSequence *v); PlaylistSequence *sequence() const { return playlist_sequence_; } + PlaylistSequence::ShuffleMode ShuffleMode() const { return playlist_sequence_ && !is_dynamic() ? playlist_sequence_->shuffle_mode() : PlaylistSequence::ShuffleMode::Off; } + PlaylistSequence::RepeatMode RepeatMode() const { return playlist_sequence_ && !is_dynamic() ? playlist_sequence_->repeat_mode() : PlaylistSequence::RepeatMode::Off; } + QUndoStack *undo_stack() const { return undo_stack_; } bool scrobbled() const { return scrobbled_; } @@ -363,6 +367,8 @@ class Playlist : public QAbstractListModel { void Save(); private: + static const int kMaxPlayedIndexes; + bool is_loading_; PlaylistFilter *filter_; Queue *queue_; @@ -382,6 +388,8 @@ class Playlist : public QAbstractListModel { // Contains the indices into items_ in the order that they will be played. QList virtual_items_; + QList played_indexes_; + // A map of collection ID to playlist item - for fast lookups when collection items change. QMultiMap collection_items_by_id_; @@ -391,8 +399,6 @@ class Playlist : public QAbstractListModel { bool current_is_paused_; int current_virtual_index_; - bool is_shuffled_; - PlaylistSequence *playlist_sequence_; // Hack to stop QTreeView::setModel sorting the playlist diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp index 55df870e..8b3d704c 100644 --- a/src/playlist/playlistmanager.cpp +++ b/src/playlist/playlistmanager.cpp @@ -381,8 +381,6 @@ void PlaylistManager::SetActivePlaylist(const int id) { emit ActiveChanged(active()); - sequence_->set_dynamic(active()->is_dynamic()); - } void PlaylistManager::SetActiveToCurrent() { diff --git a/src/playlist/playlistsequence.cpp b/src/playlist/playlistsequence.cpp index bcb7bb98..496b6f4f 100644 --- a/src/playlist/playlistsequence.cpp +++ b/src/playlist/playlistsequence.cpp @@ -47,8 +47,7 @@ PlaylistSequence::PlaylistSequence(QWidget *parent, SettingsProvider *settings) shuffle_menu_(new QMenu(this)), loading_(false), repeat_mode_(RepeatMode::Off), - shuffle_mode_(ShuffleMode::Off), - dynamic_(false) { + shuffle_mode_(ShuffleMode::Off) { ui_->setupUi(this); @@ -205,11 +204,11 @@ void PlaylistSequence::SetShuffleMode(const ShuffleMode mode) { } PlaylistSequence::ShuffleMode PlaylistSequence::shuffle_mode() const { - return dynamic_ ? ShuffleMode::Off : shuffle_mode_; + return shuffle_mode_; } PlaylistSequence::RepeatMode PlaylistSequence::repeat_mode() const { - return dynamic_ ? RepeatMode::Off : repeat_mode_; + return repeat_mode_; } // Called from global shortcut diff --git a/src/playlist/playlistsequence.h b/src/playlist/playlistsequence.h index ce13908c..acd7e26c 100644 --- a/src/playlist/playlistsequence.h +++ b/src/playlist/playlistsequence.h @@ -68,8 +68,6 @@ class PlaylistSequence : public QWidget { QMenu *repeat_menu() const { return repeat_menu_; } QMenu *shuffle_menu() const { return shuffle_menu_; } - void set_dynamic(const bool dynamic) { dynamic_ = dynamic; } - public slots: void SetRepeatMode(const PlaylistSequence::RepeatMode mode); void SetShuffleMode(const PlaylistSequence::ShuffleMode mode);