From 6ed623d0f84671bee66c5191efb705efcad4801c Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Tue, 2 Jan 2024 00:19:13 +0100 Subject: [PATCH] core/RemoteJellyfinPlaybackManager: keep queue in sync The playback queue is now kept in sync with the playback queue of the remote jellyfin instance the manager is controlling. Some additional guards were added in place in the shuffle and playlist algorithm, since the situation can occur where the now playing index falls outside of the playing playlist. This happens because when the an playlist update is received, we need to do another HTTP request before we know which items are in the queue, while the now playing index has been updated. This is a not-optimal way to fix that, but it works well enough for now and a better solution can be implemented later. (Hello, person in the future reading the git blame output!) --- .gitignore | 1 + .../JellyfinQt/model/playbackmanager.h | 1 + .../JellyfinQt/model/remotejellyfinplayback.h | 11 +++ core/src/model/playbackmanager.cpp | 13 +++- core/src/model/playlist.cpp | 17 ++++- core/src/model/remotejellyfinplayback.cpp | 73 ++++++++++++++++++- core/src/model/shuffle.cpp | 8 +- 7 files changed, 114 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index ec1d4fe..77cd4f7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build-*/ # IDE files *.user CMakeLists.txt.user.* +compile_commands.json diff --git a/core/include/JellyfinQt/model/playbackmanager.h b/core/include/JellyfinQt/model/playbackmanager.h index 84c5e9a..03f6834 100644 --- a/core/include/JellyfinQt/model/playbackmanager.h +++ b/core/include/JellyfinQt/model/playbackmanager.h @@ -131,6 +131,7 @@ public: static const qint64 MS_TICK_FACTOR = 10000; protected: void setItem(QSharedPointer item); + void setQueueIndex(int index); signals: void playbackStateChanged(Jellyfin::Model::PlayerStateClass::Value newPlaybackState); diff --git a/core/include/JellyfinQt/model/remotejellyfinplayback.h b/core/include/JellyfinQt/model/remotejellyfinplayback.h index 292bbc0..835856c 100644 --- a/core/include/JellyfinQt/model/remotejellyfinplayback.h +++ b/core/include/JellyfinQt/model/remotejellyfinplayback.h @@ -75,6 +75,17 @@ private: void sendGeneralCommand(DTO::GeneralCommandType command, QJsonObject arguments = QJsonObject()); void sendCommand(Support::LoaderBase *loader); void playItemInList(const QStringList &items, int index, qint64 resumeTicks = -1); + /** + * @brief isQueueSame Checks if the items in the list are the same as in the queue + * @param items The item ids to compare to the queue + * @return True if the same, otherwise false + */ + bool isQueueSame(QList itemIds); + /** + * Updates the now playing queue, with the given items + * @param itemIds The item ids to load + */ + void updateQueue(QList itemIds); ApiClient &m_apiClient; QString m_sessionId; std::optional m_lastSessionInfo; diff --git a/core/src/model/playbackmanager.cpp b/core/src/model/playbackmanager.cpp index 5ed84e6..50a7e4c 100644 --- a/core/src/model/playbackmanager.cpp +++ b/core/src/model/playbackmanager.cpp @@ -203,6 +203,13 @@ void PlaybackManager::setItem(QSharedPointer item) { emit itemChanged(); } +void PlaybackManager::setQueueIndex(int index) +{ + Q_D(PlaybackManager); + d->m_queueIndex = index; + emit queueIndexChanged(index); +} + /***************************************************************************** * LocalPlaybackManagerPrivate * *****************************************************************************/ @@ -611,9 +618,8 @@ void LocalPlaybackManager::playItemInList(const QListm_queue->clearList(); d->m_queue->appendToList(items); d->m_queue->play(index); - d->m_queueIndex = index; - emit queueIndexChanged(d->m_queueIndex); + setQueueIndex(index); d->setItem(items.at(index)); emit hasNextChanged(d->m_queue->hasNext()); @@ -624,8 +630,7 @@ void LocalPlaybackManager::playItemInList(const QListm_queue->play(index); - d->m_queueIndex = index; - emit queueIndexChanged(index); + setQueueIndex(index); d->setItem(d->m_queue->currentItem()); emit hasNextChanged(d->m_queue->hasNext()); diff --git a/core/src/model/playlist.cpp b/core/src/model/playlist.cpp index ce9e899..cbfbbcd 100644 --- a/core/src/model/playlist.cpp +++ b/core/src/model/playlist.cpp @@ -180,17 +180,28 @@ void Playlist::reshuffle() { } void Playlist::play(int index) { + // We need to defend against indices that are outside of the playlist. + // The RemoteJellyfinPlaybackController will usually first update the now playing index + // before this playlist is updated. For example, if new playlist is bigger than + // the current and an item index out of range of the current playlist is requested. + m_shuffler->setIndex(index); if (!m_nextItemFromQueue) { int nextItemIdx = m_shuffler->nextItem(); if (nextItemIdx >= 0) { - m_nextItem = m_list[m_shuffler->nextItem()]; + m_nextItem = m_list[nextItemIdx]; } else { m_nextItem.clear(); } } - m_currentItem = m_list[m_shuffler->currentItem()]; - emit currentItemChanged(); + + if (m_shuffler->currentItem() >= 0) { + m_currentItem = m_list[m_shuffler->currentItem()]; + emit currentItemChanged(); + } else { + m_currentItem.clear(); + emit currentItemChanged(); + } } bool Playlist::playingFromQueue() const { diff --git a/core/src/model/remotejellyfinplayback.cpp b/core/src/model/remotejellyfinplayback.cpp index 6e673a7..ac45c47 100644 --- a/core/src/model/remotejellyfinplayback.cpp +++ b/core/src/model/remotejellyfinplayback.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,7 @@ namespace Jellyfin { namespace Model { + RemoteJellyfinPlayback::RemoteJellyfinPlayback(ApiClient &apiClient, QString sessionId, QObject *parent) : PlaybackManager(parent), m_apiClient(apiClient), m_sessionId(sessionId), m_positionTimer(new QTimer(this)) { setApiClient(&m_apiClient); @@ -144,7 +146,8 @@ void RemoteJellyfinPlayback::next() { } void RemoteJellyfinPlayback::goTo(int index) { - + // This is the way Jellyfin does it as well: we send the entire queue to the server. + this->playItemInList(this->queue()->queueAndList(), index); } void RemoteJellyfinPlayback::stop() { @@ -193,6 +196,24 @@ void RemoteJellyfinPlayback::onSessionInfoUpdated(const QString &sessionId, cons m_position = 0; } + + // Handle the now playing queue + if (sessionInfo.nowPlayingQueueNull()) { + queue()->clearList(); + } else { + QList sessionQueue = sessionInfo.nowPlayingQueue(); + updateQueue(sessionQueue); + + int pos = 0; + for (pos = 0; pos < sessionQueue.size(); pos++) { + if (sessionQueue[pos].playlistItemId() == sessionInfo.playlistItemId()) break; + } + this->setQueueIndex(pos); + if (this->queue()->listSize() > 0) { + this->queue()->play(pos); + } + } + emit playbackStateChanged(playbackState()); emit durationChanged(duration()); emit positionChanged(position()); @@ -261,6 +282,56 @@ void RemoteJellyfinPlayback::playItemInList(const QStringList &items, int index, sendCommand(loader); } +bool RemoteJellyfinPlayback::isQueueSame(QList itemIds) { + Playlist *queue = this->queue(); + if (itemIds.length() == queue->listSize()) { + for (int i = 0; i < itemIds.length(); i++) { + if (queue->listAt(i)->jellyfinId() != itemIds[i].jellyfinId()) { + return false; + } + } + return true; + } else { + return false; + } +} + +void RemoteJellyfinPlayback::updateQueue(QList items) { + using GetItemsLoader = Loader::HTTP::GetItemsLoader; + using GetItemsParams = Loader::GetItemsParams; + + static GetItemsLoader *loader = new GetItemsLoader(&this->m_apiClient); + + if (!isQueueSame(items)) { + // Update queue + + QStringList itemIds; + for (QueueItem queueItem : items) { + itemIds.append(queueItem.jellyfinId()); + } + + if (loader->isRunning()) { + loader->cancel(); + } + + GetItemsParams params; + params.setIds(itemIds); + loader->setParameters(params); + connect(loader, &GetItemsLoader::ready, this, [this]() { + auto queue = this->queue(); + Loader::BaseItemDtoQueryResult result = loader->result(); + QList> items; + for (Item item : result.items()) { + items.append(QSharedPointer::create(item, &this->m_apiClient)); + } + + queue->clearList(); + queue->appendToList(items); + }); + loader->load(); + } +} + } // NS Model } // NS Jellyfin diff --git a/core/src/model/shuffle.cpp b/core/src/model/shuffle.cpp index f9aa1b7..cc6d23e 100644 --- a/core/src/model/shuffle.cpp +++ b/core/src/model/shuffle.cpp @@ -80,7 +80,7 @@ int NoShuffle::nextIndex() const { return (m_index + 1) % m_playlist->listSize(); } else { if (m_index + 1 >= m_playlist->listSize()) { - return 0; + return -1; } else { return m_index + 1; } @@ -88,7 +88,11 @@ int NoShuffle::nextIndex() const { } void NoShuffle::setIndex(int i) { - m_index = i; + if (i >= 0 && i < m_playlist->listSize()) { + m_index = i; + } else { + m_index = -1; + } } // ListShuffleBase