1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2025-09-06 02:32:44 +00:00

core: remote playback send commands and update state

This commit is contained in:
Chris Josten 2023-01-04 21:32:27 +01:00
parent 77cb5d5957
commit 7a7ddc7717
18 changed files with 371 additions and 91 deletions

View file

@ -5,6 +5,7 @@
#include "JellyfinQt/loader/http/session.h"
#include "JellyfinQt/loader/requesttypes.h"
#include <JellyfinQt/model/playbackmanager.h>
#include <JellyfinQt/model/remotejellyfinplayback.h>
namespace Jellyfin {
@ -39,13 +40,16 @@ QString LocalSession::userName() const {
}
PlaybackManager *LocalSession::createPlaybackManager() const {
return new LocalPlaybackManager();
LocalPlaybackManager *playbackManager = new LocalPlaybackManager();
playbackManager->setApiClient(&m_apiClient);
return playbackManager;
}
// ControllableJellyfinSession
ControllableJellyfinSession::ControllableJellyfinSession(const QSharedPointer<DTO::SessionInfo> info, QObject *parent)
ControllableJellyfinSession::ControllableJellyfinSession(const QSharedPointer<DTO::SessionInfo> info, ApiClient &apiClient, QObject *parent)
: ControllableSession(parent),
m_data(info) {}
m_data(info),
m_apiClient(apiClient){}
QString ControllableJellyfinSession::id() const {
return m_data->jellyfinId();
@ -68,8 +72,7 @@ QString ControllableJellyfinSession::userName() const {
}
PlaybackManager * ControllableJellyfinSession::createPlaybackManager() const {
// TODO: implement
return nullptr;
return new RemoteJellyfinPlayback(m_apiClient, m_data->jellyfinId());
}
RemoteSessionScanner::RemoteSessionScanner(QObject *parent)
@ -114,7 +117,7 @@ void RemoteJellyfinSessionScanner::startScanning() {
// Skip this device
if (it->jellyfinId() == d->apiClient->deviceId()) continue;
emit sessionFound(new ControllableJellyfinSession(QSharedPointer<DTO::SessionInfo>::create(*it)));
emit sessionFound(new ControllableJellyfinSession(QSharedPointer<DTO::SessionInfo>::create(*it), *d->apiClient));
}
});
d->loader->load();

View file

@ -41,8 +41,8 @@ Item::Item(ApiClient *apiClient, QObject *parent)
}
Item::Item(const DTO::BaseItemDto &data, ApiClient *apiClient, QObject *parent)
: DTO::BaseItemDto(data),
QObject(parent),
: QObject(parent),
DTO::BaseItemDto(data),
m_apiClient(nullptr) {
setApiClient(apiClient);
}

View file

@ -59,7 +59,7 @@ public:
PlayerState m_state;
Model::Playlist *m_queue = nullptr;
Model::Playlist *m_queue;
int m_queueIndex = 0;
bool m_resumePlayback = false;
@ -153,6 +153,15 @@ int PlaybackManager::queueIndex() const {
return d->m_queueIndex;
}
void PlaybackManager::swap(PlaybackManager &other) {
other.queue()->clearList();
other.queue()->appendToList(this->queue()->queueAndList());
other.playItemInList(this->queue()->queueAndList(), this->queue()->currentItemIndexInList() >= 0
? this->queue()->currentItemIndexInList()
: 0);
other.seek(position());
}
void PlaybackManager::playItemId(const QString &id) {}
bool PlaybackManager::resumePlayback() const {
@ -188,6 +197,12 @@ void PlaybackManager::setSubtitleIndex(int newSubtitleIndex) {
emit subtitleIndexChanged(newSubtitleIndex);
}
void PlaybackManager::setItem(QSharedPointer<Item> item) {
Q_D(PlaybackManager);
d->m_item = item;
emit itemChanged();
}
/*****************************************************************************
* LocalPlaybackManagerPrivate *
*****************************************************************************/
@ -491,10 +506,6 @@ LocalPlaybackManager::LocalPlaybackManager(QObject *parent)
});
}
void LocalPlaybackManager::swap(PlaybackManager &other) {
Q_UNIMPLEMENTED();
}
Player* LocalPlaybackManager::player() const {
const Q_D(LocalPlaybackManager);
return d->m_mediaPlayer;

View file

@ -104,6 +104,14 @@ void Playlist::next() {
emit currentItemChanged();
}
QList<QSharedPointer<Item>> Playlist::queueAndList() const {
QList<QSharedPointer<Item>> result;
result.reserve(totalSize());
result.append(m_queue.toList());
result.append(m_list.toList());
return result;
}
QSharedPointer<const Item> Playlist::listAt(int index) const {
if (m_shuffler->canShuffleInAdvance()) {
return m_list.at(m_shuffler->itemAt(index));

View file

@ -20,90 +20,127 @@
#include <JellyfinQt/model/remotejellyfinplayback.h>
#include <JellyfinQt/apiclient.h>
#include <JellyfinQt/dto/sessioninfo.h>
#include <JellyfinQt/loader/http/session.h>
#include <JellyfinQt/loader/requesttypes.h>
#include <JellyfinQt/model/item.h>
#include <JellyfinQt/model/playlist.h>
#include <JellyfinQt/eventbus.h>
#include <JellyfinQt/websocket.h>
#include <QDebug>
namespace Jellyfin {
namespace Model {
RemoteJellyfinPlayback::RemoteJellyfinPlayback(ApiClient &apiClient, QObject *parent)
: PlaybackManager(parent), m_apiClient(apiClient) {
RemoteJellyfinPlayback::RemoteJellyfinPlayback(ApiClient &apiClient, QString sessionId, QObject *parent)
: PlaybackManager(parent), m_apiClient(apiClient), m_sessionId(sessionId), m_positionTimer(new QTimer(this)) {
setApiClient(&m_apiClient);
m_apiClient.websocket()->subscribeToSessionInfo();
connect(m_apiClient.eventbus(), &EventBus::sessionInfoUpdated, this, &RemoteJellyfinPlayback::onSessionInfoUpdated);
// Arm the timer
m_positionTimer->setInterval(1000);
connect(m_positionTimer, &QTimer::timeout, this, &RemoteJellyfinPlayback::onPositionTimerFired);
}
void RemoteJellyfinPlayback::swap(PlaybackManager &other) {
RemoteJellyfinPlayback::~RemoteJellyfinPlayback() {
m_apiClient.websocket()->unsubscribeToSessionInfo();
}
PlayerState RemoteJellyfinPlayback::playbackState() const {
return m_lastSessionInfo.has_value()
? m_lastSessionInfo.value().playState()->isPaused()
? PlayerState::Paused
: PlayerState::Playing
: PlayerState::Stopped;
}
MediaStatus RemoteJellyfinPlayback::mediaStatus() const {
return MediaStatus::Loaded;
}
bool RemoteJellyfinPlayback::hasNext() const {
return true;
}
bool RemoteJellyfinPlayback::hasPrevious() const {
return true;
}
PlaybackManagerError RemoteJellyfinPlayback::error() const {
return PlaybackManagerError::NoError;
}
const QString &RemoteJellyfinPlayback::errorString() const {
return m_sessionId;
}
qint64 RemoteJellyfinPlayback::position() const {
return m_position;
}
qint64 RemoteJellyfinPlayback::duration() const {
if (!m_lastSessionInfo.has_value()
|| m_lastSessionInfo.value().nowPlayingItem().isNull()) {
return 0;
}
return m_lastSessionInfo.value().nowPlayingItem()->runTimeTicks().value_or(0) / 10000;
}
bool RemoteJellyfinPlayback::seekable() const {
if (!m_lastSessionInfo.has_value()
|| m_lastSessionInfo.value().playState().isNull()) {
return false;
}
return m_lastSessionInfo.value().playState()->canSeek();
}
bool RemoteJellyfinPlayback::hasAudio() const {
return false;
}
bool RemoteJellyfinPlayback::hasVideo() const {
return false;
}
void RemoteJellyfinPlayback::playItem(QSharedPointer<Item> item) {
return playItemInList({item}, 0);
}
void RemoteJellyfinPlayback::playItemInList(const QList<QSharedPointer<Item> > &items, int index) {
// Map items to their ID
QStringList itemIds;
itemIds.reserve(items.size());
for(auto it = items.begin(); it < items.end(); it++) {
itemIds.append((*it)->jellyfinId());
}
if (this->resumePlayback()) {
this->playItemInList(itemIds, index, items.at(index)->userData()->playbackPositionTicks());
} else {
this->playItemInList(itemIds, index);
}
}
void RemoteJellyfinPlayback::pause() {
sendPlaystateCommand(DTO::PlaystateCommand::Pause);
}
void RemoteJellyfinPlayback::play() {
sendPlaystateCommand(DTO::PlaystateCommand::Unpause);
}
void RemoteJellyfinPlayback::playItemId(const QString &id) {
playItemInList({id}, 0);
}
void RemoteJellyfinPlayback::previous() {
sendPlaystateCommand(DTO::PlaystateCommand::PreviousTrack);
}
void RemoteJellyfinPlayback::next() {
sendPlaystateCommand(DTO::PlaystateCommand::NextTrack);
}
void RemoteJellyfinPlayback::goTo(int index) {
@ -111,21 +148,107 @@ void RemoteJellyfinPlayback::goTo(int index) {
}
void RemoteJellyfinPlayback::stop() {
sendPlaystateCommand(DTO::PlaystateCommand::Stop);
}
void RemoteJellyfinPlayback::seek(qint64 pos) {
sendPlaystateCommand(DTO::PlaystateCommand::Seek, pos * PlaybackManager::MS_TICK_FACTOR);
}
void RemoteJellyfinPlayback::onPositionTimerFired() {
m_position += m_positionTimer->interval();
emit positionChanged(position());
}
void RemoteJellyfinPlayback::onSessionInfoUpdated(const QString &sessionId, const SessionInfo &sessionInfo) {
if (sessionId != m_sessionId) return;
qDebug() << "Session info updated for " << sessionId;
m_lastSessionInfo = sessionInfo;
if (m_lastSessionInfo->nowPlayingItem().isNull()) {
setItem(QSharedPointer<Model::Item>::create());
} else {
Jellyfin::BaseItemDto itemData = *m_lastSessionInfo->nowPlayingItem().data();
setItem(QSharedPointer<Model::Item>::create(itemData, &m_apiClient));
}
// Update current position and run timer if needed
if (m_lastSessionInfo.has_value()
&& !m_lastSessionInfo.value().playState().isNull()) {
m_position = m_lastSessionInfo.value().playState()->positionTicks().value_or(0) / PlaybackManager::MS_TICK_FACTOR;
if (!m_positionTimer->isActive() && !m_lastSessionInfo.value().playState()->isPaused()) {
m_positionTimer->start();
} else if (m_positionTimer->isActive() && m_lastSessionInfo.value().playState()->isPaused()) {
m_positionTimer->stop();
}
} else if (m_positionTimer->isActive()){
m_positionTimer->stop();
m_position = 0;
}
emit playbackStateChanged(playbackState());
emit durationChanged(duration());
emit positionChanged(position());
}
void RemoteJellyfinPlayback::sendPlaystateCommand(DTO::PlaystateCommand command, qint64 seekTicks) {
using Params = Loader::SendPlaystateCommandParams;
using CommandLoader = Loader::HTTP::SendPlaystateCommandLoader;
Params params;
params.setCommand(command);
params.setSessionId(m_sessionId);
if (seekTicks >= 0) {
params.setSeekPositionTicks(seekTicks);
}
auto loader = new CommandLoader(&m_apiClient);
loader->setParameters(params);
sendCommand(loader);
}
void RemoteJellyfinPlayback::sendGeneralCommand(DTO::GeneralCommandType command, QJsonObject arguments) {
Loader::SendFullGeneralCommandParams params;
using Params = Loader::SendFullGeneralCommandParams;
using CommandLoader = Loader::HTTP::SendFullGeneralCommandLoader;
Params params;
QSharedPointer<DTO::GeneralCommand> fullCommand = QSharedPointer<DTO::GeneralCommand>::create(command, m_apiClient.userId());
fullCommand->setArguments(arguments);
// FIXME: send command
params.setBody(fullCommand);
params.setSessionId(m_sessionId);
auto loader = new CommandLoader(&m_apiClient);
loader->setParameters(params);
sendCommand(loader);
}
void RemoteJellyfinPlayback::sendCommand(Support::LoaderBase *loader) {
connect(loader, &Support::LoaderBase::ready, this, [loader](){
loader->deleteLater();
});
connect(loader, &Support::LoaderBase::error, this, [loader](QString message){
loader->deleteLater();
});
loader->load();
}
void RemoteJellyfinPlayback::playItemInList(const QStringList &items, int index, qint64 resumeTicks) {
using Params = Loader::PlayParams;
using CommandLoader = Loader::HTTP::PlayLoader;
Params params;
params.setSessionId(m_sessionId);
if (resumeTicks >= 0) {
params.setStartPositionTicks(resumeTicks);
}
params.setPlayCommand(DTO::PlayCommand::PlayNow);
params.setItemIds(items);
//params.setStartIndex(index);
CommandLoader *loader = new CommandLoader(&m_apiClient);
loader->setParameters(params);
sendCommand(loader);
}
} // NS Model
} // NS Jellyfin

View file

@ -46,37 +46,37 @@ PlayerAdaptor::~PlayerAdaptor() {
bool PlayerAdaptor::canControl() const
{
// get the value of property CanControl
return true;
return m_mediaControl->playbackManager() != nullptr;
}
bool PlayerAdaptor::canGoNext() const
{
// get the value of property CanGoNext
return canPlay() && m_mediaControl->playbackManager()->hasNext();
return canControl() && canPlay() && m_mediaControl->playbackManager()->hasNext();
}
bool PlayerAdaptor::canGoPrevious() const
{
// get the value of property CanGoPrevious
return canPlay() && m_mediaControl->playbackManager()->hasPrevious();
return canControl() && canPlay() && m_mediaControl->playbackManager()->hasPrevious();
}
bool PlayerAdaptor::canPause() const
{
// get the value of property CanPause
return canPlay();
return canControl() && canPlay();
}
bool PlayerAdaptor::canPlay() const
{
// get the value of property CanPlay
return m_mediaControl->playbackManager()->queue()->rowCount(QModelIndex()) > 0;
return canControl() && m_mediaControl->playbackManager()->queue()->rowCount(QModelIndex()) > 0;
}
bool PlayerAdaptor::canSeek() const
{
// get the value of property CanSeek
return m_mediaControl->playbackManager()->seekable();
return canControl() && m_mediaControl->playbackManager()->seekable();
}
QString PlayerAdaptor::loopStatus() const
@ -134,7 +134,10 @@ QVariantMap PlayerAdaptor::metadata() const
}
map[QStringLiteral("xesam:contentCreated")] = item->dateCreated();
map[QStringLiteral("xesam:genre")] = item->genres();
map[QStringLiteral("xesam:lastUsed")] = item->userData()->lastPlayedDate();
if (!item->userData().isNull()) {
map[QStringLiteral("xesam:lastUsed")] = item->userData()->lastPlayedDate();
}
QJsonObject providers = item->providerIds();
if (providers.contains(QStringLiteral("MusicBrainzTrack"))) {

View file

@ -75,25 +75,6 @@ PlaybackManager::PlaybackManager(QObject *parent)
: QObject(parent) {
QScopedPointer<PlaybackManagerPrivate> foo(new PlaybackManagerPrivate(this));
d_ptr.swap(foo);
Q_D(PlaybackManager);
// Set up connections.
connect(d->m_impl.data(), &Model::PlaybackManager::positionChanged, this, &PlaybackManager::positionChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::durationChanged, this, &PlaybackManager::durationChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::hasNextChanged, this, &PlaybackManager::hasNextChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::hasPreviousChanged, this, &PlaybackManager::hasPreviousChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::seekableChanged, this, &PlaybackManager::seekableChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::queueIndexChanged, this, &PlaybackManager::queueIndexChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::itemChanged, this, &PlaybackManager::mediaPlayerItemChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::playbackStateChanged, this, &PlaybackManager::playbackStateChanged);
if (auto localImp = qobject_cast<Model::LocalPlaybackManager*>(d->m_impl.data())) {
connect(localImp, &Model::LocalPlaybackManager::streamUrlChanged, this, [this](const QUrl& newUrl){
emit this->streamUrlChanged(newUrl.toString());
});
connect(localImp, &Model::LocalPlaybackManager::playMethodChanged, this, &PlaybackManager::playMethodChanged);
}
connect(d->m_impl.data(), &Model::PlaybackManager::mediaStatusChanged, this, &PlaybackManager::mediaStatusChanged);
}
PlaybackManager::~PlaybackManager() {
@ -175,12 +156,61 @@ void PlaybackManager::setControllingSession(QSharedPointer<Model::ControllableSe
qCDebug(playbackManager()) << "Now controlling session " << session->name();
session->setParent(this);
if (!d->m_impl.isNull()) {
disconnect(d->m_impl.data(), &Model::PlaybackManager::positionChanged, this, &PlaybackManager::positionChanged);
disconnect(d->m_impl.data(), &Model::PlaybackManager::durationChanged, this, &PlaybackManager::durationChanged);
disconnect(d->m_impl.data(), &Model::PlaybackManager::hasNextChanged, this, &PlaybackManager::hasNextChanged);
disconnect(d->m_impl.data(), &Model::PlaybackManager::hasPreviousChanged, this, &PlaybackManager::hasPreviousChanged);
disconnect(d->m_impl.data(), &Model::PlaybackManager::seekableChanged, this, &PlaybackManager::seekableChanged);
disconnect(d->m_impl.data(), &Model::PlaybackManager::queueIndexChanged, this, &PlaybackManager::queueIndexChanged);
disconnect(d->m_impl.data(), &Model::PlaybackManager::itemChanged, this, &PlaybackManager::mediaPlayerItemChanged);
disconnect(d->m_impl.data(), &Model::PlaybackManager::playbackStateChanged, this, &PlaybackManager::playbackStateChanged);
if (auto localImp = qobject_cast<Model::LocalPlaybackManager*>(d->m_impl.data())) {
disconnect(localImp, &Model::LocalPlaybackManager::playMethodChanged, this, &PlaybackManager::playMethodChanged);
}
disconnect(d->m_impl.data(), &Model::PlaybackManager::mediaStatusChanged, this, &PlaybackManager::mediaStatusChanged);
}
Model::PlaybackManager *other = session->createPlaybackManager();
if (!d->m_impl.isNull()) {
bool thisIsLocal = qobject_cast<Model::LocalPlaybackManager *>(d->m_impl.data()) != nullptr;
//bool otherIsLocal = qobject_cast<Model::LocalPlaybackManager *>(other) != nullptr;
// Stop playing locally when switching to another session
if (thisIsLocal) {
d->m_impl->stop();
}
}
d->m_displayQueue->setPlaylistModel(other->queue());
d->m_impl.reset(other);
d->m_session.swap(session);
// TODO: swap out playback manager
emit controllingSessionChanged();
emit controllingSessionIdChanged();
emit controllingSessionNameChanged();
emit controllingSessionLocalChanged();
if (other != nullptr) {
connect(d->m_impl.data(), &Model::PlaybackManager::positionChanged, this, &PlaybackManager::positionChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::durationChanged, this, &PlaybackManager::durationChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::hasNextChanged, this, &PlaybackManager::hasNextChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::hasPreviousChanged, this, &PlaybackManager::hasPreviousChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::seekableChanged, this, &PlaybackManager::seekableChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::queueIndexChanged, this, &PlaybackManager::queueIndexChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::itemChanged, this, &PlaybackManager::mediaPlayerItemChanged);
connect(d->m_impl.data(), &Model::PlaybackManager::playbackStateChanged, this, &PlaybackManager::playbackStateChanged);
if (auto localImp = qobject_cast<Model::LocalPlaybackManager*>(d->m_impl.data())) {
connect(localImp, &Model::LocalPlaybackManager::streamUrlChanged, this, [this](const QUrl& newUrl){
emit this->streamUrlChanged(newUrl.toString());
});
connect(localImp, &Model::LocalPlaybackManager::playMethodChanged, this, &PlaybackManager::playMethodChanged);
}
connect(d->m_impl.data(), &Model::PlaybackManager::mediaStatusChanged, this, &PlaybackManager::mediaStatusChanged);
}
}
QString PlaybackManager::controllingSessionId() const {

View file

@ -26,20 +26,8 @@ namespace ViewModel {
Playlist::Playlist(Model::Playlist *data, QObject *parent)
: QAbstractListModel(parent),
m_data(data) {
connect(data, &Model::Playlist::beforeListCleared, this, &Playlist::onBeforePlaylistCleared);
connect(data, &Model::Playlist::listCleared, this, &Playlist::onPlaylistCleared);
connect(data, &Model::Playlist::beforeItemsAddedToList, this, &Playlist::onBeforeItemsAddedToList);
connect(data, &Model::Playlist::beforeItemsAddedToQueue, this, &Playlist::onBeforeItemsAddedToQueue);
connect(data, &Model::Playlist::itemsAddedToList, this, &Playlist::onItemsAddedToList);
connect(data, &Model::Playlist::itemsAddedToQueue, this, &Playlist::onItemsAddedToQueue);
connect(data, &Model::Playlist::beforeItemsRemovedFromList, this, &Playlist::onBeforeItemsRemovedFromList);
connect(data, &Model::Playlist::beforeItemsRemovedFromQueue, this, &Playlist::onBeforeItemsRemovedFromQueue);
connect(data, &Model::Playlist::itemsRemovedFromList, this, &Playlist::onItemsRemovedFromList);
connect(data, &Model::Playlist::itemsRemovedFromQueue, this, &Playlist::onItemsRemovedFromQueue);
connect(data, &Model::Playlist::listReshuffled, this, &Playlist::onReshuffled);
connect(data, &Model::Playlist::currentItemChanged, this, &Playlist::onPlayingItemChanged);
m_data(nullptr) {
setPlaylistModel(data);
}
int Playlist::rowCount(const QModelIndex &parent) const {
@ -55,6 +43,41 @@ QHash<int, QByteArray> Playlist::roleNames() const {
{RoleNames::section, "section"},
{RoleNames::playing, "playing"},
};
}
void Playlist::setPlaylistModel(Model::Playlist *data) {
if (m_data != nullptr) {
disconnect(data, &Model::Playlist::beforeListCleared, this, &Playlist::onBeforePlaylistCleared);
disconnect(data, &Model::Playlist::listCleared, this, &Playlist::onPlaylistCleared);
disconnect(data, &Model::Playlist::beforeItemsAddedToList, this, &Playlist::onBeforeItemsAddedToList);
disconnect(data, &Model::Playlist::beforeItemsAddedToQueue, this, &Playlist::onBeforeItemsAddedToQueue);
disconnect(data, &Model::Playlist::itemsAddedToList, this, &Playlist::onItemsAddedToList);
disconnect(data, &Model::Playlist::itemsAddedToQueue, this, &Playlist::onItemsAddedToQueue);
disconnect(data, &Model::Playlist::beforeItemsRemovedFromList, this, &Playlist::onBeforeItemsRemovedFromList);
disconnect(data, &Model::Playlist::beforeItemsRemovedFromQueue, this, &Playlist::onBeforeItemsRemovedFromQueue);
disconnect(data, &Model::Playlist::itemsRemovedFromList, this, &Playlist::onItemsRemovedFromList);
disconnect(data, &Model::Playlist::itemsRemovedFromQueue, this, &Playlist::onItemsRemovedFromQueue);
disconnect(data, &Model::Playlist::listReshuffled, this, &Playlist::onReshuffled);
disconnect(data, &Model::Playlist::currentItemChanged, this, &Playlist::onPlayingItemChanged);
}
beginResetModel();
m_data = data;
endResetModel();
if (m_data != nullptr) {
connect(data, &Model::Playlist::beforeListCleared, this, &Playlist::onBeforePlaylistCleared);
connect(data, &Model::Playlist::listCleared, this, &Playlist::onPlaylistCleared);
connect(data, &Model::Playlist::beforeItemsAddedToList, this, &Playlist::onBeforeItemsAddedToList);
connect(data, &Model::Playlist::beforeItemsAddedToQueue, this, &Playlist::onBeforeItemsAddedToQueue);
connect(data, &Model::Playlist::itemsAddedToList, this, &Playlist::onItemsAddedToList);
connect(data, &Model::Playlist::itemsAddedToQueue, this, &Playlist::onItemsAddedToQueue);
connect(data, &Model::Playlist::beforeItemsRemovedFromList, this, &Playlist::onBeforeItemsRemovedFromList);
connect(data, &Model::Playlist::beforeItemsRemovedFromQueue, this, &Playlist::onBeforeItemsRemovedFromQueue);
connect(data, &Model::Playlist::itemsRemovedFromList, this, &Playlist::onItemsRemovedFromList);
connect(data, &Model::Playlist::itemsRemovedFromQueue, this, &Playlist::onItemsRemovedFromQueue);
connect(data, &Model::Playlist::listReshuffled, this, &Playlist::onReshuffled);
connect(data, &Model::Playlist::currentItemChanged, this, &Playlist::onPlayingItemChanged);
}
};
QVariant Playlist::data(const QModelIndex &index, int role) const {

View file

@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include <JellyfinQt/dto/generalcommand.h>
#include <JellyfinQt/dto/generalcommandtype.h>
#include <JellyfinQt/dto/playstaterequest.h>
#include <JellyfinQt/dto/sessioninfo.h>
#include <JellyfinQt/dto/useritemdatadto.h>
Q_LOGGING_CATEGORY(jellyfinWebSocket, "jellyfin.websocket");
@ -61,6 +62,21 @@ void WebSocket::open() {
qCDebug(jellyfinWebSocket) << "Opening WebSocket connection to " << m_webSocket.requestUrl() << ", connect attempt " << m_reconnectAttempt;
}
void WebSocket::subscribeToSessionInfo() {
if (m_sessionInfoSubscribeCount++ == 0) {
// First argument: initial delay in milliseconds
// Second argument: periodic update interval in milliseconds
// Reference: https://github.com/jellyfin/jellyfin/blob/f3c57e6a0ae015dc51cf548a0380d1bed33959c2/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs#L99
sendMessage(MessageType::SessionsStart, QJsonValue(QStringLiteral("0,5000")));
}
}
void WebSocket::unsubscribeToSessionInfo() {
if (--m_sessionInfoSubscribeCount == 0) {
sendMessage(MessageType::SessionsStop);
}
}
void WebSocket::onConnected() {
connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &WebSocket::textMessageReceived);
m_reconnectAttempt = 0;
@ -76,7 +92,6 @@ void WebSocket::onDisconnected() {
}
void WebSocket::textMessageReceived(const QString &message) {
qCDebug(jellyfinWebSocket) << "message received: " << message;
QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8());
if (doc.isNull() || !doc.isObject()) {
qCWarning(jellyfinWebSocket()) << "Malformed message received over WebSocket: parse error or root not an object.";
@ -90,6 +105,7 @@ void WebSocket::textMessageReceived(const QString &message) {
// Convert the type so we can use it in our enums.
QString messageType = messageRoot["MessageType"].toString();
qCDebug(jellyfinWebSocket) << "Message received: " << messageType;
QJsonValue data = messageRoot["Data"];
if (messageType == QStringLiteral("ForceKeepAlive")) {
setupKeepAlive(data.toInt());
@ -136,8 +152,17 @@ void WebSocket::textMessageReceived(const QString &message) {
qCWarning(jellyfinWebSocket) << "Unparseable UserData list received: " << e->what();
}
}
} else if (messageType == QStringLiteral("Sessions")) {
try {
QList<DTO::SessionInfo> sessionInfoList = Support::fromJsonValue<QList<DTO::SessionInfo>>(data);
for (auto it = sessionInfoList.cbegin(); it != sessionInfoList.cend(); it++) {
emit m_apiClient->eventbus()->sessionInfoUpdated(it->jellyfinId(), *it);
}
} catch(QException *e) {
qCWarning(jellyfinWebSocket) << "Unparseable SessionInfo list received: " << e->what();
}
} else {
qCDebug(jellyfinWebSocket) << messageType;
qCDebug(jellyfinWebSocket) << "Unhandled message: " << messageType;
}
}