1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-11-22 01:05:17 +00:00

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!)
This commit is contained in:
Chris Josten 2024-01-02 00:19:13 +01:00
parent 61a7eaf52e
commit 6ed623d0f8
7 changed files with 114 additions and 10 deletions

1
.gitignore vendored
View file

@ -9,3 +9,4 @@ build-*/
# IDE files # IDE files
*.user *.user
CMakeLists.txt.user.* CMakeLists.txt.user.*
compile_commands.json

View file

@ -131,6 +131,7 @@ public:
static const qint64 MS_TICK_FACTOR = 10000; static const qint64 MS_TICK_FACTOR = 10000;
protected: protected:
void setItem(QSharedPointer<Item> item); void setItem(QSharedPointer<Item> item);
void setQueueIndex(int index);
signals: signals:
void playbackStateChanged(Jellyfin::Model::PlayerStateClass::Value newPlaybackState); void playbackStateChanged(Jellyfin::Model::PlayerStateClass::Value newPlaybackState);

View file

@ -75,6 +75,17 @@ private:
void sendGeneralCommand(DTO::GeneralCommandType command, QJsonObject arguments = QJsonObject()); void sendGeneralCommand(DTO::GeneralCommandType command, QJsonObject arguments = QJsonObject());
void sendCommand(Support::LoaderBase *loader); void sendCommand(Support::LoaderBase *loader);
void playItemInList(const QStringList &items, int index, qint64 resumeTicks = -1); 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<QueueItem> itemIds);
/**
* Updates the now playing queue, with the given items
* @param itemIds The item ids to load
*/
void updateQueue(QList<QueueItem> itemIds);
ApiClient &m_apiClient; ApiClient &m_apiClient;
QString m_sessionId; QString m_sessionId;
std::optional<DTO::SessionInfo> m_lastSessionInfo; std::optional<DTO::SessionInfo> m_lastSessionInfo;

View file

@ -203,6 +203,13 @@ void PlaybackManager::setItem(QSharedPointer<Item> item) {
emit itemChanged(); emit itemChanged();
} }
void PlaybackManager::setQueueIndex(int index)
{
Q_D(PlaybackManager);
d->m_queueIndex = index;
emit queueIndexChanged(index);
}
/***************************************************************************** /*****************************************************************************
* LocalPlaybackManagerPrivate * * LocalPlaybackManagerPrivate *
*****************************************************************************/ *****************************************************************************/
@ -611,9 +618,8 @@ void LocalPlaybackManager::playItemInList(const QList<QSharedPointer<Model::Item
d->m_queue->clearList(); d->m_queue->clearList();
d->m_queue->appendToList(items); d->m_queue->appendToList(items);
d->m_queue->play(index); d->m_queue->play(index);
d->m_queueIndex = index;
emit queueIndexChanged(d->m_queueIndex); setQueueIndex(index);
d->setItem(items.at(index)); d->setItem(items.at(index));
emit hasNextChanged(d->m_queue->hasNext()); emit hasNextChanged(d->m_queue->hasNext());
@ -624,8 +630,7 @@ void LocalPlaybackManager::playItemInList(const QList<QSharedPointer<Model::Item
void LocalPlaybackManager::goTo(int index) { void LocalPlaybackManager::goTo(int index) {
Q_D(LocalPlaybackManager); Q_D(LocalPlaybackManager);
d->m_queue->play(index); d->m_queue->play(index);
d->m_queueIndex = index; setQueueIndex(index);
emit queueIndexChanged(index);
d->setItem(d->m_queue->currentItem()); d->setItem(d->m_queue->currentItem());
emit hasNextChanged(d->m_queue->hasNext()); emit hasNextChanged(d->m_queue->hasNext());

View file

@ -180,17 +180,28 @@ void Playlist::reshuffle() {
} }
void Playlist::play(int index) { 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); m_shuffler->setIndex(index);
if (!m_nextItemFromQueue) { if (!m_nextItemFromQueue) {
int nextItemIdx = m_shuffler->nextItem(); int nextItemIdx = m_shuffler->nextItem();
if (nextItemIdx >= 0) { if (nextItemIdx >= 0) {
m_nextItem = m_list[m_shuffler->nextItem()]; m_nextItem = m_list[nextItemIdx];
} else { } else {
m_nextItem.clear(); 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 { bool Playlist::playingFromQueue() const {

View file

@ -21,6 +21,7 @@
#include <JellyfinQt/apiclient.h> #include <JellyfinQt/apiclient.h>
#include <JellyfinQt/dto/sessioninfo.h> #include <JellyfinQt/dto/sessioninfo.h>
#include <JellyfinQt/loader/http/items.h>
#include <JellyfinQt/loader/http/session.h> #include <JellyfinQt/loader/http/session.h>
#include <JellyfinQt/loader/requesttypes.h> #include <JellyfinQt/loader/requesttypes.h>
#include <JellyfinQt/model/item.h> #include <JellyfinQt/model/item.h>
@ -33,6 +34,7 @@
namespace Jellyfin { namespace Jellyfin {
namespace Model { namespace Model {
RemoteJellyfinPlayback::RemoteJellyfinPlayback(ApiClient &apiClient, QString sessionId, QObject *parent) RemoteJellyfinPlayback::RemoteJellyfinPlayback(ApiClient &apiClient, QString sessionId, QObject *parent)
: PlaybackManager(parent), m_apiClient(apiClient), m_sessionId(sessionId), m_positionTimer(new QTimer(this)) { : PlaybackManager(parent), m_apiClient(apiClient), m_sessionId(sessionId), m_positionTimer(new QTimer(this)) {
setApiClient(&m_apiClient); setApiClient(&m_apiClient);
@ -144,7 +146,8 @@ void RemoteJellyfinPlayback::next() {
} }
void RemoteJellyfinPlayback::goTo(int index) { 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() { void RemoteJellyfinPlayback::stop() {
@ -193,6 +196,24 @@ void RemoteJellyfinPlayback::onSessionInfoUpdated(const QString &sessionId, cons
m_position = 0; m_position = 0;
} }
// Handle the now playing queue
if (sessionInfo.nowPlayingQueueNull()) {
queue()->clearList();
} else {
QList<QueueItem> 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 playbackStateChanged(playbackState());
emit durationChanged(duration()); emit durationChanged(duration());
emit positionChanged(position()); emit positionChanged(position());
@ -261,6 +282,56 @@ void RemoteJellyfinPlayback::playItemInList(const QStringList &items, int index,
sendCommand(loader); sendCommand(loader);
} }
bool RemoteJellyfinPlayback::isQueueSame(QList<QueueItem> 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<QueueItem> 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<QSharedPointer<Item>> items;
for (Item item : result.items()) {
items.append(QSharedPointer<Item>::create(item, &this->m_apiClient));
}
queue->clearList();
queue->appendToList(items);
});
loader->load();
}
}
} // NS Model } // NS Model
} // NS Jellyfin } // NS Jellyfin

View file

@ -80,7 +80,7 @@ int NoShuffle::nextIndex() const {
return (m_index + 1) % m_playlist->listSize(); return (m_index + 1) % m_playlist->listSize();
} else { } else {
if (m_index + 1 >= m_playlist->listSize()) { if (m_index + 1 >= m_playlist->listSize()) {
return 0; return -1;
} else { } else {
return m_index + 1; return m_index + 1;
} }
@ -88,7 +88,11 @@ int NoShuffle::nextIndex() const {
} }
void NoShuffle::setIndex(int i) { void NoShuffle::setIndex(int i) {
m_index = i; if (i >= 0 && i < m_playlist->listSize()) {
m_index = i;
} else {
m_index = -1;
}
} }
// ListShuffleBase // ListShuffleBase