harbour-sailfin/core/src/model/playbackreporter.cpp

201 lines
7.2 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/model/playbackreporter.h>
#include <optional>
#include <QDebug>
#include <JellyfinQt/apiclient.h>
#include <JellyfinQt/dto/playbackprogressinfo.h>
#include <JellyfinQt/model/item.h>
#include <JellyfinQt/model/playlist.h>
#include <JellyfinQt/model/playbackmanager.h>
#include <JellyfinQt/model/player.h>
namespace Jellyfin {
namespace Model {
Q_LOGGING_CATEGORY(playbackReporter, "jellyfin.model.playbackReporter");
class PlaybackReporterPrivate : public QObject {
Q_DECLARE_PUBLIC(PlaybackReporter);
public:
explicit PlaybackReporterPrivate(PlaybackReporter *parent)
: QObject(parent) {
q_ptr = parent;
}
PlaybackReporter *q_ptr;
LocalPlaybackManager *m_playbackManager = nullptr;
/// Timer used to update the play progress on the Jellyfin server
QTimer m_updateTimer;
enum PlaybackInfoType { Started, Stopped, Progress };
qint64 m_oldPosition = 0;
quint64 m_stopPosition = 0;
PlayerState m_oldState;
static const int MS_TICK_FACTOR = 10000;
public slots:
void positionChanged(qint64 position);
void playerStateChanged(Jellyfin::Model::PlayerStateClass::Value newState);
/**
* @brief updatePlaybackInfo Updates the Jellyfin server with the current playback progress etc.
*/
void updatePlaybackInfo();
void postPlaybackInfo(Jellyfin::Model::PlaybackReporterPrivate::PlaybackInfoType type);
};
// PlaybackReporter
PlaybackReporter::PlaybackReporter(QObject *parent)
: QObject(parent),
d_ptr(new PlaybackReporterPrivate(this)){
Q_D(PlaybackReporter);
d->m_updateTimer.setInterval(10000); // 10 seconds
d->m_updateTimer.setSingleShot(false);
connect(&d->m_updateTimer, &QTimer::timeout, d, &PlaybackReporterPrivate::updatePlaybackInfo);
}
void PlaybackReporter::setPlaybackManager(LocalPlaybackManager *playbackManager) {
Q_D(PlaybackReporter);
if (d->m_playbackManager != nullptr) {
// Disconnect
disconnect(d->m_playbackManager, &PlaybackManager::playbackStateChanged, d, &PlaybackReporterPrivate::playerStateChanged);
disconnect(d->m_playbackManager->player(), &Player::seeked, d, &PlaybackReporterPrivate::updatePlaybackInfo);
disconnect(d->m_playbackManager->player(), &Player::positionChanged, d, &PlaybackReporterPrivate::positionChanged);
}
d->m_playbackManager = playbackManager;
if (d->m_playbackManager != nullptr) {
this->setParent(d->m_playbackManager);
connect(d->m_playbackManager, &PlaybackManager::playbackStateChanged, d, &PlaybackReporterPrivate::playerStateChanged);
connect(d->m_playbackManager->player(), &Player::seeked, d, &PlaybackReporterPrivate::updatePlaybackInfo);
connect(d->m_playbackManager->player(), &Player::positionChanged, d, &PlaybackReporterPrivate::positionChanged);
} else {
this->setParent(nullptr);
}
}
// Private
void PlaybackReporterPrivate::positionChanged(qint64 newPosition) {
if (newPosition == 0 && m_oldPosition != 0) {
// Save the old position when stop gets called. The QMediaPlayer will try to set
// position to 0 when stopped, but we don't want to report that to Jellyfin. We
// want the old position.
m_stopPosition = m_oldPosition;
}
m_oldPosition = newPosition;
}
void PlaybackReporterPrivate::playerStateChanged(PlayerState newState) {
if (m_oldState == newState) return;
if (m_oldState == PlayerState::Stopped) {
// We're transitioning from stopped to either playing or paused.
// Set up the recurring timer
m_updateTimer.start();
postPlaybackInfo(Started);
} else if (newState == PlayerState::Stopped) {
// We've stopped playing the media. Post a stop signal.
m_updateTimer.stop();
postPlaybackInfo(Stopped);
} else {
postPlaybackInfo(Progress);
}
m_oldState = newState;
}
void PlaybackReporterPrivate::updatePlaybackInfo() {
postPlaybackInfo(Progress);
}
void PlaybackReporterPrivate::postPlaybackInfo(PlaybackInfoType type) {
if (m_playbackManager == nullptr) {
qCWarning(playbackReporter) << "PlaybackManager not set. Not posting playback info";
return;
} else if (m_playbackManager->apiClient() == nullptr) {
qCWarning(playbackReporter) << "Set PlaybackManager does not have a apiClient set. Not posting playback info";
return;
} else if (m_playbackManager->currentItem().isNull()) {
qCWarning(playbackReporter) << "Item is null. Not posting playback info";
return;
}
DTO::PlaybackProgressInfo progress(
m_playbackManager->player()->seekable(),
m_playbackManager->currentItem(),
m_playbackManager->currentItem()->jellyfinId(),
m_playbackManager->player()->state() == PlayerState::Paused,
false, // is muted?
m_playbackManager->playMethod(),
DTO::RepeatMode::RepeatNone);
progress.setSessionId(m_playbackManager->sessionId());
switch(type) {
case Started: // FALLTHROUGH
case Progress: {
progress.setAudioStreamIndex(m_playbackManager->audioIndex());
progress.setSubtitleStreamIndex(m_playbackManager->subtitleIndex());
progress.setPositionTicks(m_playbackManager->player()->position() * MS_TICK_FACTOR);
Playlist *playlist = m_playbackManager->queue();
QList<DTO::QueueItem> queue;
for (int i = 0; i < playlist->listSize(); i++) {
DTO::QueueItem queueItem(playlist->listAt(i)->jellyfinId());
queue.append(queueItem);
}
progress.setNowPlayingQueue(queue);
break;
}
case Stopped:
progress.setPositionTicks(m_stopPosition * MS_TICK_FACTOR);
break;
}
QString path;
switch (type) {
case Started:
path = "/Sessions/Playing";
break;
case Progress:
path = "/Sessions/Playing/Progress";
break;
case Stopped:
path = "/Sessions/Playing/Stopped";
break;
}
// client is never null, checked at the start of this function.
ApiClient *client = m_playbackManager->apiClient();
QNetworkReply *rep = client->post(path, QJsonDocument(progress.toJson()));
connect(rep, &QNetworkReply::finished, this, [rep](){
rep->deleteLater();
});
client->setDefaultErrorHandler(rep);
}
} // NS Model
} // NS Jellyfin