1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-05-11 16:42:42 +00:00
harbour-sailfin/core/src/viewmodel/playbackmanager.cpp
Chris Josten 9266f65c2f Guess device icons
Device icons for the local device is now determined by looking at what
the value of the deviceType property of the ApiClient is. This property
was newly introduced, so that applications using JellyfinQt can set
their own device type.

For other devices, a guess is made based on the client name. This guess
has been derived from what Jellyfin Web does.
2024-01-02 15:14:23 +01:00

450 lines
15 KiB
C++

/*
* Sailfin: a Jellyfin client written using Qt
* Copyright (C) 2021-2022 Chris Josten and the Sailfin Contributors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "JellyfinQt/viewmodel/playbackmanager.h"
#include "JellyfinQt/apimodel.h"
#include <JellyfinQt/dto/playstatecommand.h>
#include <JellyfinQt/dto/playstaterequest.h>
#include <JellyfinQt/dto/useritemdatadto.h>
#include <JellyfinQt/model/controllablesession.h>
#include <JellyfinQt/model/playbackmanager.h>
#include <JellyfinQt/viewmodel/settings.h>
#include <QSharedPointer>
#include <utility>
namespace Jellyfin {
class ItemModel;
namespace DTO {
using UserData = UserItemDataDto;
}
namespace ViewModel {
Q_LOGGING_CATEGORY(playbackManager, "jellyfin.viewmodel.playbackmanager")
class PlaybackManagerPrivate {
Q_DECLARE_PUBLIC(PlaybackManager);
public:
explicit PlaybackManagerPrivate(PlaybackManager *q);
PlaybackManager *q_ptr = nullptr;
ApiClient *m_apiClient = nullptr;
QSharedPointer<Model::ControllableSession> m_session;
QScopedPointer<Model::PlaybackManager> m_impl;
/// The currently played item that will be shown in the GUI
ViewModel::Item *m_displayItem = nullptr;
/// The currently played queue that will be shown in the GUI
ViewModel::Playlist *m_displayQueue = nullptr;
bool m_handlePlaystateCommands;
};
PlaybackManagerPrivate::PlaybackManagerPrivate(PlaybackManager *q)
: q_ptr(q),
m_session(nullptr),
m_impl(new Model::LocalPlaybackManager(q)),
m_displayItem(new ViewModel::Item(q)),
m_displayQueue(new ViewModel::Playlist(m_impl->queue())) {
}
// PlaybackManager
PlaybackManager::PlaybackManager(QObject *parent)
: QObject(parent) {
QScopedPointer<PlaybackManagerPrivate> foo(new PlaybackManagerPrivate(this));
d_ptr.swap(foo);
}
PlaybackManager::~PlaybackManager() {
}
void PlaybackManager::setApiClient(ApiClient *apiClient) {
Q_D(PlaybackManager);
if (d->m_apiClient != nullptr) {
disconnect(d->m_apiClient->eventbus(), &EventBus::playstateCommandReceived, this, &PlaybackManager::handlePlaystateRequest);
}
if (!d->m_displayItem->data().isNull()) {
d->m_displayItem->data()->setApiClient(apiClient);
}
d->m_apiClient = apiClient;
d->m_impl->setApiClient(apiClient);
if (d->m_apiClient != nullptr) {
// Set the session to a new LocalSession in case it hasn't been set yet.
if (d->m_session.isNull()) {
setControllingSession(QSharedPointer<Model::LocalSession>::create(*apiClient, this));
}
connect(d->m_apiClient->eventbus(), &EventBus::playstateCommandReceived, this, &PlaybackManager::handlePlaystateRequest);
}
}
bool PlaybackManager::resumePlayback() const {
const Q_D(PlaybackManager);
return d->m_impl->resumePlayback();
}
void PlaybackManager::setResumePlayback(bool newResumePlayback) {
Q_D(PlaybackManager);
return d->m_impl->setResumePlayback(newResumePlayback);
}
int PlaybackManager::audioIndex() const {
const Q_D(PlaybackManager);
return d->m_impl->audioIndex();
}
void PlaybackManager::setAudioIndex(int newAudioIndex){
Q_D(PlaybackManager);
d->m_impl->setAudioIndex(newAudioIndex);
}
int PlaybackManager::subtitleIndex() const {
const Q_D(PlaybackManager);
return d->m_impl->subtitleIndex();
}
void PlaybackManager::setSubtitleIndex(int newSubtitleIndex){
Q_D(PlaybackManager);
d->m_impl->setSubtitleIndex(newSubtitleIndex);
}
ViewModel::Item *PlaybackManager::item() const {
const Q_D(PlaybackManager);
return d->m_displayItem;
}
QSharedPointer<Model::Item> PlaybackManager::dataItem() const {
const Q_D(PlaybackManager);
return d->m_displayItem->data();
}
ApiClient * PlaybackManager::apiClient() const {
const Q_D(PlaybackManager);
return d->m_apiClient;
}
QSharedPointer<Model::ControllableSession> PlaybackManager::controllingSession() const {
const Q_D(PlaybackManager);
return d->m_session;
}
void PlaybackManager::setControllingSession(QSharedPointer<Model::ControllableSession> session) {
Q_D(PlaybackManager);
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();
if (other != nullptr) {
d->m_impl->swap(*other);
}
}
}
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);
emit positionChanged(d->m_impl->position());
connect(d->m_impl.data(), &Model::PlaybackManager::durationChanged, this, &PlaybackManager::durationChanged);
emit durationChanged(d->m_impl->duration());
connect(d->m_impl.data(), &Model::PlaybackManager::hasNextChanged, this, &PlaybackManager::hasNextChanged);
emit hasNextChanged(d->m_impl->hasNext());
connect(d->m_impl.data(), &Model::PlaybackManager::hasPreviousChanged, this, &PlaybackManager::hasPreviousChanged);
emit hasPreviousChanged(d->m_impl->hasPrevious());
connect(d->m_impl.data(), &Model::PlaybackManager::seekableChanged, this, &PlaybackManager::seekableChanged);
emit seekableChanged(d->m_impl->seekable());
connect(d->m_impl.data(), &Model::PlaybackManager::queueIndexChanged, this, &PlaybackManager::queueIndexChanged);
emit queueIndexChanged(d->m_impl->queueIndex());
connect(d->m_impl.data(), &Model::PlaybackManager::itemChanged, this, &PlaybackManager::mediaPlayerItemChanged);
emit itemChanged();
connect(d->m_impl.data(), &Model::PlaybackManager::playbackStateChanged, this, &PlaybackManager::playbackStateChanged);
emit playbackStateChanged(d->m_impl->playbackState());
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);
}
emit streamUrlChanged(streamUrl());
connect(d->m_impl.data(), &Model::PlaybackManager::mediaStatusChanged, this, &PlaybackManager::mediaStatusChanged);
emit mediaStatusChanged(d->m_impl->mediaStatus());
}
}
QString PlaybackManager::controllingSessionId() const {
const Q_D(PlaybackManager);
return d->m_session->id();
}
QString PlaybackManager::controllingSessionName() const {
const Q_D(PlaybackManager);
return d->m_session->name();
}
bool PlaybackManager::controllingSessionLocal() const {
const Q_D(PlaybackManager);
return qobject_cast<Model::LocalPlaybackManager *>(d->m_impl.data()) != nullptr;
}
QString PlaybackManager::streamUrl() const {
const Q_D(PlaybackManager);
if (Model::LocalPlaybackManager *lpm = qobject_cast<Model::LocalPlaybackManager *>(d->m_impl.data())) {
return lpm->streamUrl().toString();
} else {
return QStringLiteral("<not playing back locally>");
}
}
PlayMethod PlaybackManager::playMethod() const {
const Q_D(PlaybackManager);
if (Model::LocalPlaybackManager *lpm = qobject_cast<Model::LocalPlaybackManager *>(d->m_impl.data())) {
return lpm->playMethod();
} else {
return PlayMethod::EnumNotSet;
}
}
Model::MediaStatus PlaybackManager::mediaStatus() const {
const Q_D(PlaybackManager);
return d->m_impl->mediaStatus();
}
qint64 PlaybackManager::position() const {
const Q_D(PlaybackManager);
return d->m_impl->position();
}
qint64 PlaybackManager::duration() const {
const Q_D(PlaybackManager);
return d->m_impl->duration();
}
ViewModel::Playlist *PlaybackManager::queue() const {
const Q_D(PlaybackManager);
return d->m_displayQueue;
}
int PlaybackManager::queueIndex() const {
const Q_D(PlaybackManager);
return d->m_impl->queueIndex();
}
bool PlaybackManager::hasNext() const {
const Q_D(PlaybackManager);
return d->m_impl->hasNext();
}
bool PlaybackManager::hasPrevious() const {
const Q_D(PlaybackManager);
return d->m_impl->hasPrevious();
}
QObject* PlaybackManager::mediaObject() const {
const Q_D(PlaybackManager);
if (auto localPb = qobject_cast<Model::LocalPlaybackManager*>(d->m_impl.data())) {
return localPb->player()->videoOutputSource();
} else {
return nullptr;
}
}
Model::PlayerState PlaybackManager::playbackState() const {
const Q_D(PlaybackManager);
return d->m_impl->playbackState();
}
bool PlaybackManager::hasVideo() const {
const Q_D(PlaybackManager);
return d->m_impl->hasVideo();
}
bool PlaybackManager::seekable() const {
const Q_D(PlaybackManager);
return d->m_impl->seekable();
}
bool PlaybackManager::handlePlaystateCommands() const {
const Q_D(PlaybackManager);
return d->m_handlePlaystateCommands;
}
void PlaybackManager::setHandlePlaystateCommands(bool newHandlePlaystateCommands) {
Q_D(PlaybackManager);
d->m_handlePlaystateCommands = newHandlePlaystateCommands;
emit handlePlaystateCommandsChanged(newHandlePlaystateCommands);
}
QMediaPlayer::Error PlaybackManager::error() const {
return QMediaPlayer::NoError;
}
QString PlaybackManager::errorString() const {
const Q_D(PlaybackManager);
return d->m_impl->errorString();
}
void PlaybackManager::mediaPlayerItemChanged() {
Q_D(PlaybackManager);
d->m_displayItem->setData(d->m_impl->currentItem());
emit itemChanged();
}
void PlaybackManager::playItem(Item *item) {
playItem(item->data());
}
void PlaybackManager::playItem(QSharedPointer<Model::Item> item) {
Q_D(PlaybackManager);
d->m_impl->playItem(item);
}
void PlaybackManager::playItemId(const QString &id) {
Q_D(PlaybackManager);
d->m_impl->playItemId(id);
}
void PlaybackManager::playItemInList(ItemModel *playlist, int index) {
Q_D(PlaybackManager);
d->m_impl->playItemInList(playlist->toList(), index);
}
void PlaybackManager::skipToItemIndex(int index) {
Q_D(PlaybackManager);
d->m_impl->goTo(index);
}
void PlaybackManager::play() {
Q_D(PlaybackManager);
d->m_impl->play();
}
void PlaybackManager::pause() {
Q_D(PlaybackManager);
return d->m_impl->pause();
}
void PlaybackManager::seek(qint64 pos) {
Q_D(PlaybackManager);
d->m_impl->seek(pos);
emit seeked(pos);
}
void PlaybackManager::stop() {
Q_D(PlaybackManager);
d->m_impl->stop();
}
void PlaybackManager::next() {
Q_D(PlaybackManager);
d->m_impl->next();
}
void PlaybackManager::previous() {
Q_D(PlaybackManager);
d->m_impl->previous();
}
void PlaybackManager::handlePlaystateRequest(const DTO::PlaystateRequest &request) {
//if (!m_handlePlaystateCommands) return;
switch(request.command()) {
case DTO::PlaystateCommand::Pause:
pause();
break;
case DTO::PlaystateCommand::PlayPause:
if (playbackState() != Model::PlayerState::Playing) {
play();
} else {
pause();
}
break;
case DTO::PlaystateCommand::Unpause:
play();
break;
case DTO::PlaystateCommand::Stop:
stop();
break;
case DTO::PlaystateCommand::NextTrack:
next();
break;
case DTO::PlaystateCommand::PreviousTrack:
previous();
break;
case DTO::PlaystateCommand::Seek:
if (request.seekPositionTicksNull()) {
qCWarning(playbackManager) << "Received seek command without position argument";
} else {
seek(request.seekPositionTicks().value() / MS_TICK_FACTOR);
}
break;
default:
qCDebug(playbackManager) << "Unhandled PlaystateCommand: " << request.command();
break;
}
}
void PlaybackManager::componentComplete() {
Q_D(PlaybackManager);
if (d->m_apiClient == nullptr) qCWarning(playbackManager) << "No ApiClient set for PlaybackManager";
m_qmlIsParsingComponent = false;
}
} // NS ViewModel
} // NS Jellyfin