mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2025-09-04 01:42:44 +00:00
WIP: logic rewrite
WIP: adding loaders
This commit is contained in:
parent
b9b08ab384
commit
2360b261f7
1769 changed files with 124903 additions and 1963 deletions
197
core/include/JellyfinQt/viewmodel/item.h
Normal file
197
core/include/JellyfinQt/viewmodel/item.h
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Sailfin: a Jellyfin client written using Qt
|
||||
* Copyright (C) 2021 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_VIEWMODEL_ITEM_H
|
||||
#define JELLYFIN_VIEWMODEL_ITEM_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
/*#include "dto.h"
|
||||
#include "mediastream.h"
|
||||
#include "namedguidpair.h"
|
||||
#include "userdata.h"*/
|
||||
|
||||
#include "../loader/requesttypes.h"
|
||||
#include "../model/item.h"
|
||||
#include "loader.h"
|
||||
|
||||
namespace Jellyfin {
|
||||
|
||||
namespace ViewModel {
|
||||
|
||||
class Item : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE explicit Item(QSharedPointer<Model::Item> data = QSharedPointer<Model::Item>(),
|
||||
QObject *parent = nullptr);
|
||||
|
||||
// Please keep the order of the properties the same as in the file linked above.
|
||||
Q_PROPERTY(QUuid jellyfinId READ jellyfinId NOTIFY jellyfinIdChanged)
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QString originalTitle READ originalTitle NOTIFY originalTitleChanged)
|
||||
Q_PROPERTY(QString serverId READ serverId NOTIFY serverIdChanged)
|
||||
Q_PROPERTY(QString etag READ etag NOTIFY etagChanged)
|
||||
Q_PROPERTY(QString sourceType READ sourceType NOTIFY sourceTypeChanged)
|
||||
Q_PROPERTY(QString playlistItemId READ playlistItemId NOTIFY playlistItemIdChanged)
|
||||
Q_PROPERTY(QDateTime dateCreated READ dateCreated NOTIFY dateCreatedChanged)
|
||||
Q_PROPERTY(QDateTime dateLastMediaAdded READ dateLastMediaAdded NOTIFY dateLastMediaAddedChanged)
|
||||
Q_PROPERTY(QString extraType READ extraType NOTIFY extraTypeChanged)
|
||||
Q_PROPERTY(int airsBeforeSeasonNumber READ airsBeforeSeasonNumber NOTIFY airsBeforeSeasonNumberChanged)
|
||||
Q_PROPERTY(int airsAfterSeasonNumber READ airsAfterSeasonNumber NOTIFY airsAfterSeasonNumberChanged)
|
||||
Q_PROPERTY(int airsBeforeEpisodeNumber READ airsBeforeEpisodeNumber NOTIFY airsBeforeEpisodeNumberChanged)
|
||||
/*Q_PROPERTY(bool canDelete READ canDelete NOTIFY canDeleteChanged)
|
||||
Q_PROPERTY(bool canDownload READ canDownload NOTIFY canDownloadChanged)
|
||||
Q_PROPERTY(bool hasSubtitles READ hasSubtitles NOTIFY hasSubtitlesChanged)
|
||||
Q_PROPERTY(QString preferredMetadataLanguage READ preferredMetadataLanguage NOTIFY preferredMetadataLanguageChanged)
|
||||
Q_PROPERTY(QString preferredMetadataCountryCode READ preferredMetadataCountryCode NOTIFY preferredMetadataCountryCodeChanged)
|
||||
Q_PROPERTY(bool supportsSync READ supportsSync NOTIFY supportsSyncChanged)
|
||||
Q_PROPERTY(QString container READ container NOTIFY containerChanged)
|
||||
Q_PROPERTY(QString sortName READ sortName NOTIFY sortNameChanged)
|
||||
Q_PROPERTY(QString forcedSortName READ forcedSortName NOTIFY forcedSortNameChanged)
|
||||
//SKIP: Video3DFormat
|
||||
Q_PROPERTY(QDateTime premiereData READ premiereDate NOTIFY premiereDateChanged)
|
||||
//SKIP: ExternalUrls
|
||||
//SKIP: MediaSources
|
||||
Q_PROPERTY(float criticRating READ criticRating NOTIFY criticRatingChanged)
|
||||
Q_PROPERTY(QStringList productionLocations READ productionLocations NOTIFY productionLocationsChanged)
|
||||
|
||||
// Handpicked, important ones
|
||||
Q_PROPERTY(qint64 runTimeTicks READ runTimeTicks NOTIFY runTimeTicksChanged)
|
||||
Q_PROPERTY(QString overview READ overview NOTIFY overviewChanged)
|
||||
Q_PROPERTY(int productionYear READ productionYear NOTIFY productionYearChanged)
|
||||
Q_PROPERTY(int indexNumber READ indexNumber NOTIFY indexNumberChanged)
|
||||
Q_PROPERTY(int indexNumberEnd READ indexNumberEnd NOTIFY indexNumberEndChanged)
|
||||
Q_PROPERTY(bool isFolder READ isFolder NOTIFY isFolderChanged)
|
||||
Q_PROPERTY(QString type READ type NOTIFY typeChanged)
|
||||
Q_PROPERTY(QString parentBackdropItemId READ parentBackdropItemId NOTIFY parentBackdropItemIdChanged)
|
||||
Q_PROPERTY(QStringList parentBackdropImageTags READ parentBackdropImageTags NOTIFY parentBackdropImageTagsChanged)
|
||||
Q_PROPERTY(UserData *userData READ userData NOTIFY userDataChanged)
|
||||
Q_PROPERTY(int recursiveItemCount READ recursiveItemCount NOTIFY recursiveItemCountChanged)
|
||||
Q_PROPERTY(int childCount READ childCount NOTIFY childCountChanged)
|
||||
Q_PROPERTY(QString albumArtist READ albumArtist NOTIFY albumArtistChanged)
|
||||
Q_PROPERTY(QVariantList albumArtists READ albumArtists NOTIFY albumArtistsChanged)
|
||||
Q_PROPERTY(QString seriesName READ seriesName NOTIFY seriesNameChanged)
|
||||
Q_PROPERTY(QString seasonName READ seasonName NOTIFY seasonNameChanged)
|
||||
Q_PROPERTY(QList<MediaStream *> __list__mediaStreams MEMBER __list__m_mediaStreams NOTIFY mediaStreamsChanged)
|
||||
Q_PROPERTY(QVariantList mediaStreams READ mediaStreams NOTIFY mediaStreamsChanged STORED false)
|
||||
Q_PROPERTY(QStringList artists READ artists NOTIFY artistsChanged)
|
||||
// Why is this a QJsonObject? Well, because I couldn't be bothered to implement the deserialisations of
|
||||
// a QHash at the moment.
|
||||
Q_PROPERTY(QJsonObject imageTags READ imageTags NOTIFY imageTagsChanged)
|
||||
Q_PROPERTY(QStringList backdropImageTags READ backdropImageTags NOTIFY backdropImageTagsChanged)
|
||||
Q_PROPERTY(QJsonObject imageBlurHashes READ imageBlurHashes NOTIFY imageBlurHashesChanged)
|
||||
Q_PROPERTY(QString mediaType READ mediaType READ mediaType NOTIFY mediaTypeChanged)
|
||||
Q_PROPERTY(int width READ width NOTIFY widthChanged)
|
||||
Q_PROPERTY(int height READ height NOTIFY heightChanged)*/
|
||||
|
||||
QUuid jellyfinId() const { return m_data->jellyfinId(); }
|
||||
QString name() const { return m_data->name(); }
|
||||
QString originalTitle() const { return m_data->originalTitle(); }
|
||||
QString serverId() const { return m_data->serverId(); }
|
||||
QString etag() const { return m_data->etag(); }
|
||||
QString sourceType() const { return m_data->sourceType(); }
|
||||
QString playlistItemId() const { return m_data->playlistItemId(); }
|
||||
QDateTime dateCreated() const { return m_data->dateCreated(); }
|
||||
QDateTime dateLastMediaAdded() const { return m_data->dateLastMediaAdded(); }
|
||||
QString extraType() const { return m_data->extraType(); }
|
||||
int airsBeforeSeasonNumber() const { return m_data->airsBeforeSeasonNumber().value_or(0); }
|
||||
int airsAfterSeasonNumber() const { return m_data->airsAfterSeasonNumber().value_or(999); }
|
||||
int airsBeforeEpisodeNumber() const { return m_data->airsBeforeEpisodeNumber().value_or(0); }
|
||||
|
||||
QSharedPointer<Model::Item> data() const { return m_data; }
|
||||
void setData(QSharedPointer<Model::Item> newData);
|
||||
signals:
|
||||
void jellyfinIdChanged(const QUuid &newId);
|
||||
void nameChanged(const QString &newName);
|
||||
void originalTitleChanged(const QString &newOriginalTitle);
|
||||
void serverIdChanged(const QString &newServerId);
|
||||
void etagChanged(const QString &newEtag);
|
||||
void sourceTypeChanged(const QString &sourceType);
|
||||
void playlistItemIdChanged(const QString &playlistItemIdChanged);
|
||||
void dateCreatedChanged(QDateTime newDateCreatedChanged);
|
||||
void dateLastMediaAddedChanged(QDateTime newDateLastMediaAdded);
|
||||
void extraTypeChanged(const QString &newExtraType);
|
||||
void airsBeforeSeasonNumberChanged(int newAirsBeforeSeasonNumber);
|
||||
void airsAfterSeasonNumberChanged(int newAirsAfterSeasonNumber);
|
||||
void airsBeforeEpisodeNumberChanged(int newAirsAfterEpisodeNumber);
|
||||
bool canDeleteChanged(bool newCanDelete);
|
||||
void canDownloadChanged(bool newCanDownload);
|
||||
void hasSubtitlesChanged(bool newHasSubtitles);
|
||||
void preferredMetadataLanguageChanged(const QString &newPreferredMetadataLanguage);
|
||||
void preferredMetadataCountryCodeChanged(const QString &newPreferredMetadataCountryCode);
|
||||
void supportsSyncChanged(bool newSupportsSync);
|
||||
void containerChanged(const QString &newContainer);
|
||||
void sortNameChanged(const QString &newSortName);
|
||||
void forcedSortNameChanged(const QString &newForcedSortName);
|
||||
void premiereDateChanged(QDateTime newPremiereDate);
|
||||
void criticRatingChanged(float newCriticRating);
|
||||
void productionLocationsChanged(QStringList newProductionLocations);
|
||||
|
||||
// Handpicked, important ones
|
||||
void runTimeTicksChanged(qint64 newRunTimeTicks);
|
||||
void overviewChanged(const QString &newOverview);
|
||||
void productionYearChanged(int newProductionYear);
|
||||
void indexNumberChanged(int newIndexNumber);
|
||||
void indexNumberEndChanged(int newIndexNumberEnd);
|
||||
void isFolderChanged(bool newIsFolder);
|
||||
void typeChanged(const QString &newType);
|
||||
void parentBackdropItemIdChanged();
|
||||
void parentBackdropImageTagsChanged();
|
||||
//void userDataChanged(UserData *newUserData);
|
||||
void recursiveItemCountChanged(int newRecursiveItemCount);
|
||||
void childCountChanged(int newChildCount);
|
||||
void albumArtistChanged(const QString &newAlbumArtist);
|
||||
//void albumArtistsChanged(NameGuidPair *newAlbumArtists);
|
||||
void seriesNameChanged(const QString &newSeriesName);
|
||||
void seasonNameChanged(const QString &newSeasonName);
|
||||
void mediaStreamsChanged(/*const QList<MediaStream *> &newMediaStreams*/);
|
||||
void artistsChanged(const QStringList &newArtists);
|
||||
void imageTagsChanged();
|
||||
void backdropImageTagsChanged();
|
||||
void imageBlurHashesChanged();
|
||||
void mediaTypeChanged(const QString &newMediaType);
|
||||
void widthChanged(int newWidth);
|
||||
void heightChanged(int newHeight);
|
||||
protected:
|
||||
QSharedPointer<Model::Item> m_data;
|
||||
};
|
||||
|
||||
class ItemLoader : Loader<Item, DTO::BaseItemDto, Jellyfin::Loader::GetItemsByUserIdParams> {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemLoader(QObject *parent = nullptr);
|
||||
};
|
||||
|
||||
} // NS ViewModel
|
||||
} // NS Jellyfin
|
||||
|
||||
#endif // JELLYFIN_VIEWMODEL_ITEM_H
|
211
core/include/JellyfinQt/viewmodel/loader.h
Normal file
211
core/include/JellyfinQt/viewmodel/loader.h
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Sailfin: a Jellyfin client written using Qt
|
||||
* Copyright (C) 2021 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_DTO_DTO
|
||||
#define JELLYFIN_DTO_DTO
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include "../support/loader.h"
|
||||
|
||||
namespace Jellyfin {
|
||||
namespace ViewModel {
|
||||
|
||||
/**
|
||||
* @brief An "interface" for a remote data source
|
||||
*
|
||||
* This class is basically a base class for JSON data that can be fetched from over the network.
|
||||
* Subclasses should reimplement reload and call setStatus to update the QML part of the code
|
||||
* appropiatly.
|
||||
*/
|
||||
class LoaderBase : public QObject, public QQmlParserStatus {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QQmlParserStatus)
|
||||
public:
|
||||
enum Status {
|
||||
/// The data is unitialized and not loading either.
|
||||
Uninitialised,
|
||||
/// The data is being loaded over the network
|
||||
Loading,
|
||||
/// The data is ready, the properties in this object are up to date.
|
||||
Ready,
|
||||
/// An error has occurred while loading the data. See error() for more details.
|
||||
Error
|
||||
};
|
||||
Q_ENUM(Status)
|
||||
|
||||
explicit LoaderBase(QObject *parent = nullptr) : QObject(parent) {}
|
||||
LoaderBase(ApiClient *apiClient, QObject *parent = nullptr)
|
||||
: QObject(parent), m_apiClient(apiClient) {}
|
||||
|
||||
Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient WRITE setApiClient NOTIFY apiClientChanged STORED false)
|
||||
Q_PROPERTY(Status status READ status NOTIFY statusChanged STORED false)
|
||||
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged STORED false)
|
||||
Q_PROPERTY(bool autoReload MEMBER m_autoReload NOTIFY autoReloadChanged)
|
||||
|
||||
Status status() const { return m_status; }
|
||||
QString errorString() const { return m_errorString; }
|
||||
|
||||
void setApiClient(ApiClient *newApiClient);
|
||||
void setExtraFields(const QStringList &extraFields);
|
||||
signals:
|
||||
void statusChanged(Status newStatus);
|
||||
void apiClientChanged(ApiClient *newApiClient);
|
||||
void errorStringChanged(QString newErrorString);
|
||||
void autoReloadChanged(bool newAutoReload);
|
||||
void viewModelChanged();
|
||||
|
||||
/**
|
||||
* @brief Convenience signal for status == RemoteData.Ready.
|
||||
*/
|
||||
void ready();
|
||||
public slots:
|
||||
|
||||
/**
|
||||
* @brief Overload this method to reimplement the fetching mechanism to
|
||||
* populate the RemoteData with data from the server.
|
||||
*
|
||||
* The default implementation makes a GET request to getDataUrl() and parses the resulting JSON,
|
||||
* which should be enough for most cases. Consider overriding getDataUrl() and
|
||||
* canRelaod() if possible. Manual overrides need to make sure that
|
||||
* they're calling setStatus(Status), setError(QNetworkReply::NetworkError) and
|
||||
* setErrorString() to let the QML side know what this thing is up to.
|
||||
*/
|
||||
virtual void reload() {};
|
||||
protected:
|
||||
/**
|
||||
* @brief Subclasses should implement this to determine if they can
|
||||
* load data from the server.
|
||||
*
|
||||
* Usage cases include checking if the
|
||||
* required properties, such as the item id are set.
|
||||
*/
|
||||
virtual bool canReload() const;
|
||||
|
||||
void setStatus(Status newStatus);
|
||||
void setError(QNetworkReply::NetworkError error);
|
||||
void setErrorString(const QString &newErrorString);
|
||||
|
||||
void classBegin() override {
|
||||
m_isParsing = true;
|
||||
}
|
||||
|
||||
void componentComplete() override {
|
||||
m_isParsing = false;
|
||||
if (canReload()) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
ApiClient *m_apiClient = nullptr;
|
||||
protected:
|
||||
// Returns true if this class is instantiated within QML and is still being parsed.
|
||||
bool isQmlParsing() const { return m_isParsing; }
|
||||
void emitDataChanged();
|
||||
private:
|
||||
Status m_status = Uninitialised;
|
||||
QString m_errorString;
|
||||
bool m_autoReload = true;
|
||||
bool m_isParsing = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class representing data from a remote source, meant to be instantiated from QML.
|
||||
*
|
||||
* Subclasses should do the following:
|
||||
* - initialize m_loader with an appropiate loader
|
||||
* - create properties that map to the parameters.
|
||||
* - override canReload()
|
||||
*/
|
||||
template <class T, class R, class P>
|
||||
class Loader : public LoaderBase {
|
||||
using RFutureWatcher = QFutureWatcher<std::optional<R>>;
|
||||
public:
|
||||
Loader(QObject *parent = nullptr)
|
||||
: LoaderBase(parent),
|
||||
m_futureWatcher(new RFutureWatcher) {
|
||||
m_dataViewModel = new T(this);
|
||||
connect(m_futureWatcher, &RFutureWatcher::finished, this, &Loader<T, R, P>::updateData());
|
||||
}
|
||||
Loader(ApiClient *apiClient, QObject *parent = nullptr)
|
||||
: LoaderBase(apiClient, parent),
|
||||
m_futureWatcher(new QFutureWatcher<std::optional<R>>) {
|
||||
m_dataViewModel = new T(this);
|
||||
}
|
||||
|
||||
T *dataViewModel() const { return m_dataViewModel; }
|
||||
|
||||
void reload() override {
|
||||
setStatus(Loading);
|
||||
QFuture<std::optional<R>> future = QtConcurrent::run(this, &Loader<T, R, P>::invokeLoader, m_parameters);
|
||||
m_futureWatcher->setFuture(future);
|
||||
}
|
||||
protected:
|
||||
T* m_dataViewModel;
|
||||
P m_parameters;
|
||||
/**
|
||||
* @brief Subclasses should initialize this to a loader that actually loads stuff.
|
||||
*/
|
||||
Support::Loader<R, P> m_loader;
|
||||
private:
|
||||
QFutureWatcher<std::optional<R>> *m_futureWatcher;
|
||||
|
||||
/**
|
||||
* @brief Callback for QtConcurrent::run()
|
||||
* @param self Pointer to this class
|
||||
* @param parameters Parameters to forward to the loader
|
||||
* @return empty optional if an error occured, otherwise the result.
|
||||
*/
|
||||
std::optional<R> invokeLoader(P parameters) {
|
||||
try {
|
||||
return this->m_loader.load(parameters);
|
||||
} catch (Support::LoadException e) {
|
||||
this->setErrorString(QString(e.what()));
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Updates the data when finished.
|
||||
*/
|
||||
void updateData() {
|
||||
std::optional<R> newData = m_futureWatcher->result();
|
||||
if (newData.has_value()) {
|
||||
if (newData.sameAs(*m_dataViewModel->data())) {
|
||||
// Replace the data the model holds
|
||||
m_dataViewModel->data()->replaceData(*newData);
|
||||
} else {
|
||||
// Replace the model
|
||||
m_dataViewModel->setData(QSharedPointer<T>::create(*newData, m_apiClient));
|
||||
}
|
||||
setStatus(Ready);
|
||||
emitDataChanged();
|
||||
} else {
|
||||
setStatus(Error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void registerRemoteTypes(const char *uri);
|
||||
|
||||
} // NS ViewModel
|
||||
} // NS Jellyfin
|
||||
|
||||
#endif // JELLYFIN_DTO_DTO_H
|
231
core/include/JellyfinQt/viewmodel/playbackmanager.h
Normal file
231
core/include/JellyfinQt/viewmodel/playbackmanager.h
Normal file
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Sailfin: a Jellyfin client written using Qt
|
||||
* Copyright (C) 2021 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_VIEWMODEL_PLAYBACKMANAGER_H
|
||||
#define JELLYFIN_VIEWMODEL_PLAYBACKMANAGER_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QFuture>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QUrlQuery>
|
||||
#include <QVariant>
|
||||
|
||||
#include <QtMultimedia/QMediaPlayer>
|
||||
#include <QtMultimedia/QMediaPlaylist>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "../dto/baseitemdto.h"
|
||||
#include "../support/jsonconv.h"
|
||||
#include "../viewmodel/item.h"
|
||||
|
||||
#include "../apiclient.h"
|
||||
|
||||
|
||||
namespace Jellyfin {
|
||||
|
||||
// Forward declaration of Jellyfin::ApiClient found in jellyfinapiclient.h
|
||||
class ApiClient;
|
||||
class ItemModel;
|
||||
class RemoteItem;
|
||||
|
||||
namespace ViewModel {
|
||||
|
||||
/**
|
||||
* @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<void(QUrl &&, PlayMethod)>;
|
||||
|
||||
explicit PlaybackManager(QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient WRITE setApiClient)
|
||||
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(Model::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)
|
||||
|
||||
Model::Item *item() const { return m_item.data(); }
|
||||
void setApiClient(ApiClient *apiClient);
|
||||
|
||||
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(BaseItemDto *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;
|
||||
QSharedPointer<Model::Item> m_item;
|
||||
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(ViewModel::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 Model::Item *item, bool autoOpen, const FetchCallback &callback);
|
||||
void fetchAndSetStreamUrl(const Model::Item *item);
|
||||
void setStreamUrl(const QString &streamUrl);
|
||||
void setPlaybackState(QMediaPlayer::State newState);
|
||||
|
||||
Model::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;
|
||||
};
|
||||
|
||||
} // NS ViewModel
|
||||
} // NS Jellyfin
|
||||
|
||||
#endif // JELLYFIN_VIEWMODEL_PLAYBACKMANAGER_H
|
Loading…
Add table
Add a link
Reference in a new issue