/* Sailfin: a Jellyfin client written using Qt Copyright (C) 2021 Chris Josten 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_MEDIA_SOURCE_H #define JELLYFIN_MEDIA_SOURCE_H #include #include #include #include #include #include #include #include #include #include #include #include "JellyfinQt/DTO/item.h" #include "apiclient.h" namespace Jellyfin { // Forward declaration of Jellyfin::ApiClient found in jellyfinapiclient.h class ApiClient; class ItemModel; using namespace DTO; /** * @brief The PlaybackManager class manages the playback of Jellyfin items. It fetches streams based on Jellyfin items, posts * the current playback state to the Jellyfin Server, contains the actual media player and so on. * * The PlaybackManager actually keeps two mediaPlayers, m_mediaPlayer1 and m_mediaPlayer2. When one is playing, the other is * preloading the next item in the queue. The current media player is pointed to by m_mediaPlayer. */ class PlaybackManager : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) public: enum PlayMethod { Transcode, Stream, DirectPlay }; Q_ENUM(PlayMethod) using FetchCallback = std::function; explicit PlaybackManager(QObject *parent = nullptr); Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient) Q_PROPERTY(QString streamUrl READ streamUrl NOTIFY streamUrlChanged) Q_PROPERTY(bool autoOpen MEMBER m_autoOpen NOTIFY autoOpenChanged) Q_PROPERTY(int audioIndex MEMBER m_audioIndex NOTIFY audioIndexChanged) Q_PROPERTY(int subtitleIndex MEMBER m_subtitleIndex NOTIFY subtitleIndexChanged) Q_PROPERTY(bool resumePlayback MEMBER m_resumePlayback NOTIFY resumePlaybackChanged) Q_PROPERTY(PlayMethod playMethod READ playMethod NOTIFY playMethodChanged) // Current Item and queue informatoion Q_PROPERTY(Item *item READ item NOTIFY itemChanged) Q_PROPERTY(QAbstractItemModel *queue READ queue NOTIFY queueChanged) Q_PROPERTY(int queueIndex READ queueIndex NOTIFY queueIndexChanged) // Current media player related property getters Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) Q_PROPERTY(QMediaPlayer::Error error READ error NOTIFY errorChanged) Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged) Q_PROPERTY(bool hasVideo READ hasVideo NOTIFY hasVideoChanged) Q_PROPERTY(QObject* mediaObject READ mediaObject NOTIFY mediaObjectChanged) Q_PROPERTY(QMediaPlayer::MediaStatus mediaStatus READ mediaStatus NOTIFY mediaStatusChanged) Q_PROPERTY(QMediaPlayer::State playbackState READ playbackState NOTIFY playbackStateChanged) Q_PROPERTY(qint64 position READ position NOTIFY positionChanged) Item *item() const { return m_item; } QString streamUrl() const { return m_streamUrl; } PlayMethod playMethod() const { return m_playMethod; } QObject *mediaObject() const { return m_mediaPlayer; } qint64 position() const { return m_mediaPlayer->position(); } qint64 duration() const { return m_mediaPlayer->duration(); } ItemModel *queue() const { return m_queue; } int queueIndex() const { return m_queueIndex; } // Current media player related property getters QMediaPlayer::State playbackState() const { return m_playbackState; } QMediaPlayer::MediaStatus mediaStatus() const { return m_mediaPlayer->mediaStatus(); } bool hasVideo() const { return m_mediaPlayer->isVideoAvailable(); } QMediaPlayer::Error error () const { return m_mediaPlayer->error(); } QString errorString() const { return m_mediaPlayer->errorString(); } signals: void itemChanged(Item *newItemId); void streamUrlChanged(const QString &newStreamUrl); void autoOpenChanged(bool autoOpen); void audioIndexChanged(int audioIndex); void subtitleIndexChanged(int subtitleIndex); void mediaPlayerChanged(QObject *newMediaPlayer); void resumePlaybackChanged(bool newResumePlayback); void playMethodChanged(PlayMethod newPlayMethod); // Current media player related property signals void mediaObjectChanged(QObject *newMediaObject); void positionChanged(qint64 newPosition); void durationChanged(qint64 newDuration); void queueChanged(ItemModel *newQue); void queueIndexChanged(int newIndex); void playbackStateChanged(QMediaPlayer::State newState); void mediaStatusChanged(QMediaPlayer::MediaStatus newMediaStatus); void hasVideoChanged(bool newHasVideo); void errorChanged(QMediaPlayer::Error newError); void errorStringChanged(const QString &newErrorString); public slots: /** * @brief playItem Plays the item with the given id. This will construct the Jellyfin::Item internally * and delete it later. * @param itemId The id of the item to play. */ void playItem(const QString &itemId); void playItemInList(ItemModel *itemList, int index); void play() { m_mediaPlayer->play(); } void pause() { m_mediaPlayer->pause(); } void seek(qint64 pos) { m_mediaPlayer->setPosition(pos); } void stop() { m_mediaPlayer->stop(); } /** * @brief previous Play the previous track in the current playlist. */ void previous(); /** * @brief next Play the next track in the current playlist. */ void next(); private slots: void mediaPlayerStateChanged(QMediaPlayer::State newState); void mediaPlayerPositionChanged(qint64 position); void mediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus newStatus); void mediaPlayerError(QMediaPlayer::Error error); /** * @brief updatePlaybackInfo Updates the Jellyfin server with the current playback progress etc. */ void updatePlaybackInfo(); private: QTimer m_updateTimer; ApiClient *m_apiClient = nullptr; Item *m_item = nullptr; QString m_streamUrl; QString m_playSessionId; int m_audioIndex = 0; int m_subtitleIndex = -1; qint64 m_resumePosition = 0; qint64 m_oldPosition = 0; qint64 m_stopPosition = 0; QMediaPlayer::State m_oldState = QMediaPlayer::StoppedState; PlayMethod m_playMethod = Transcode; QMediaPlayer::State m_playbackState = QMediaPlayer::StoppedState; // Pointer to the current media player. QMediaPlayer *m_mediaPlayer = nullptr; QMediaPlayer *m_mediaPlayer1; QMediaPlayer *m_mediaPlayer2; ItemModel *m_queue = nullptr; int m_queueIndex = 0; bool m_resumePlayback = true; void setItem(Item *newItem); void swapMediaPlayer(); bool m_qmlIsParsingComponent = false; /** * @brief Whether to automatically open the livestream of the item; */ bool m_autoOpen = false; /** * @brief Retrieves the URL of the stream to open. */ void fetchStreamUrl(const Item *item, bool autoOpen, const FetchCallback &callback); void fetchAndSetStreamUrl(const Item *item); void setStreamUrl(const QString &streamUrl); void setPlaybackState(QMediaPlayer::State newState); Item *nextItem(); void setQueue(ItemModel *itemModel); // Factor to multiply with when converting from milliseconds to ticks. const static int MS_TICK_FACTOR = 10000; enum PlaybackInfoType { Started, Stopped, Progress }; /** * @brief Posts the playback information */ void postPlaybackInfo(PlaybackInfoType type); void classBegin() override { m_qmlIsParsingComponent = true; } void componentComplete() override; }; } #endif // JELLYFIN_MEDIA_SOURCE_H