1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-11-23 01:35:18 +00:00
harbour-sailfin/core/include/JellyfinQt/model/playbackmanager.h
Chris Josten 6ed623d0f8 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!)
2024-01-02 00:19:13 +01:00

243 lines
8.6 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
*/
#ifndef JELLYFIN_MODEL_PLAYBACKMANAGER_H
#define JELLYFIN_MODEL_PLAYBACKMANAGER_H
#include <QLoggingCategory>
#include <QObject>
#include <QSharedPointer>
#include <JellyfinQt/dto/playmethod.h>
#include <JellyfinQt/model/player.h>
namespace Jellyfin {
namespace Model {
class Item;
class Playlist;
Q_DECLARE_LOGGING_CATEGORY(playbackManager);
class PlaybackManagerErrorClass {
Q_GADGET
public:
enum Value {
NoError,
PlaybackInfoError,
RemoteClientNotReachable,
PlayerGeneralError
};
Q_ENUM(Value);
};
using PlaybackManagerError = PlaybackManagerErrorClass::Value;
class PlaybackManagerPrivate;
/**
* @brief Base class for a playback manager.
*
* Besides some glue code for the properties,
* most of the actual playback logic is implemented in the two subclasses: {@link LocalPlaybackManager}
* and {@link RemotePlaybackManager}
*/
class PlaybackManager : public QObject {
Q_OBJECT
Q_DECLARE_PRIVATE(PlaybackManager);
Q_PROPERTY(bool resumePlayback READ resumePlayback WRITE setResumePlayback NOTIFY resumePlaybackChanged)
Q_PROPERTY(int audioIndex READ audioIndex WRITE setAudioIndex NOTIFY audioIndexChanged)
Q_PROPERTY(int subtitleIndex READ subtitleIndex WRITE setSubtitleIndex NOTIFY subtitleIndexChanged)
/**
* @brief The position in ticks in the currently playing item
*/
Q_PROPERTY(qint64 position READ position NOTIFY positionChanged)
/**
* @brief The duration in ticks of the currently playing item
*/
Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged)
/**
* @brief Whether the playbackmanager is currently able to seek
*/
Q_PROPERTY(bool seekable READ seekable NOTIFY seekableChanged)
/**
* @brief Whether the currently playing item has audio
*/
Q_PROPERTY(bool hasAudio READ hasAudio NOTIFY hasAudioChanged)
/**
* @brief Whether the currently playing item has video
*/
Q_PROPERTY(bool hasVideo READ hasVideo NOTIFY hasVideoChanged)
Q_PROPERTY(Jellyfin::Model::PlayerStateClass::Value playbackState READ playbackState NOTIFY playbackStateChanged)
Q_PROPERTY(Jellyfin::Model::MediaStatusClass::Value mediaStatus READ mediaStatus NOTIFY mediaStatusChanged)
Q_PROPERTY(int queueIndex READ queueIndex NOTIFY queueIndexChanged)
public:
explicit PlaybackManager(QObject *parent = nullptr);
virtual ~PlaybackManager();
void swap(PlaybackManager& other);
ApiClient * apiClient() const;
void setApiClient(ApiClient *apiClient);
// Getters
QSharedPointer<Item> currentItem() const;
Playlist *queue() const;
int queueIndex() const;
bool resumePlayback() const;
void setResumePlayback(bool newResumePlayback);
int audioIndex() const;
void setAudioIndex(int newAudioIndex);
int subtitleIndex() const;
void setSubtitleIndex(int newSubtitleIndex);
virtual PlayerState playbackState() const = 0;
virtual MediaStatus mediaStatus() const = 0;
virtual bool hasNext() const = 0;
virtual bool hasPrevious() const = 0;
virtual PlaybackManagerError error() const = 0;
virtual const QString &errorString() const = 0;
virtual qint64 position() const = 0;
virtual qint64 duration() const = 0;
virtual bool seekable() const = 0;
virtual bool hasAudio() const = 0;
virtual bool hasVideo() const = 0;
/**
* @brief Start playing the given item
*/
virtual void playItem(QSharedPointer<Model::Item> item) = 0;
/**
* @brief Set the playlist to the given playlist and start playing the item at the given index
* @param items The list of items to play
* @param index Index of the item to play
*/
virtual void playItemInList(const QList<QSharedPointer<Model::Item>> &items, int index) = 0;
static const qint64 MS_TICK_FACTOR = 10000;
protected:
void setItem(QSharedPointer<Item> item);
void setQueueIndex(int index);
signals:
void playbackStateChanged(Jellyfin::Model::PlayerStateClass::Value newPlaybackState);
void mediaStatusChanged(Jellyfin::Model::MediaStatusClass::Value newMediaStatus);
void queueChanged(Jellyfin::Model::Playlist *newPlaylist);
void hasNextChanged(bool newHasNext);
void hasPreviousChanged(bool newHasPrevious);
void itemChanged();
void queueIndexChanged(int index);
void errorChanged(Jellyfin::Model::PlaybackManagerErrorClass::Value newError);
void errorStringChanged(const QString &newErrorString);
void positionChanged(qint64 newPosition);
void durationChanged(qint64 newDuration);
void seekableChanged(bool newSeekable);
void hasAudioChanged();
void hasVideoChanged();
void resumePlaybackChanged(bool newPlaybackChanged);
void audioIndexChanged(int newAudioIndex);
void subtitleIndexChanged(int newSubtitleIndex);
public slots:
virtual void pause() = 0;
virtual void play() = 0;
virtual void playItemId(const QString &id) = 0;
virtual void previous() = 0;
virtual void next() = 0;
/**
* @brief Play the item at the index in the current playlist
* @param index the item to go to;
*/
virtual void goTo(int index) = 0;
virtual void stop() = 0;
virtual void seek(qint64 pos) = 0;
protected:
explicit PlaybackManager(PlaybackManagerPrivate *d, QObject *parent = nullptr);
QScopedPointer<PlaybackManagerPrivate> d_ptr;
};
class LocalPlaybackManagerPrivate;
/**
* @brief Controls playback whithin this app.
*
* This class mostly consists of bookkeeping between the actual media player implementation (which is
* abstracted into yet another class) and the ViewModel.
*
* It does things like:
* * Fetching the actual media URL of an item and deciding which playback method to use
* * Managing the current playlist state and instructing the media player which item to play next
* * Keeping track of the playback state that the user would expect from a media player,
* instead of what the media player implementation reports.
*
*/
class LocalPlaybackManager : public PlaybackManager {
Q_DECLARE_PRIVATE(LocalPlaybackManager);
Q_OBJECT
Q_PROPERTY(Jellyfin::Model::Player *player READ player NOTIFY playerChanged)
Q_PROPERTY(Jellyfin::DTO::PlayMethodClass::Value playMethod READ playMethod NOTIFY playMethodChanged)
Q_PROPERTY(QUrl streamUrl READ streamUrl NOTIFY streamUrlChanged)
public:
explicit LocalPlaybackManager(QObject *parent = nullptr);
Player *player() const;
QString sessionId() const;
DTO::PlayMethod playMethod() const;
const QUrl &streamUrl() const;
PlayerState playbackState() const override;
MediaStatus mediaStatus() const override;
PlaybackManagerError error() const override;
const QString& errorString() const override;
qint64 position() const override;
qint64 duration() const override;
bool seekable() const override;
bool hasAudio() const override;
bool hasVideo() const override;
bool hasNext() const override;
bool hasPrevious() const override;
void playItemInList(const QList<QSharedPointer<Model::Item>> &items, int index) override;
public slots:
void pause() override;
void play() override;
void playItem(QSharedPointer<Model::Item> item) override;
void playItemId(const QString &itemId) override;
void next() override;
void previous() override;
void stop() override;
void seek(qint64 pos) override;
void goTo(int index) override;
signals:
void playerChanged(Jellyfin::Model::Player *newPlayer);
void playMethodChanged(Jellyfin::DTO::PlayMethodClass::Value newPlayMethod);
void streamUrlChanged(const QUrl &newStreamUrl);
};
/**
* @brief Controls playback for remote devices, such as other Jellyfin clients, over the network.
*/
class RemoteJellyfinPlaybackManager {
};
} // NS Model
} // NS Jellyfin
#endif // JELLYFIN_MODEL_PLAYBACKMANAGER_H