mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2025-09-04 01:42:44 +00:00
Moved playback logic to C++-side (and refractoring)
This commit is contained in:
parent
895731ae38
commit
f7bca333c8
35 changed files with 1063 additions and 449 deletions
|
@ -46,6 +46,7 @@ class JsonSerializable : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE JsonSerializable(QObject *parent);
|
||||
virtual ~JsonSerializable();
|
||||
|
||||
/**
|
||||
* @brief Sets this objects properties based on obj.
|
||||
|
@ -56,7 +57,7 @@ public:
|
|||
private:
|
||||
QVariant jsonToVariant(QMetaProperty prop, const QJsonValue &val, const QJsonObject &root);
|
||||
QJsonValue variantToJson(const QVariant var) const;
|
||||
QVariant deserializeQobject(const QJsonObject &obj, const QMetaProperty &prop);
|
||||
QVariant deserializeQObject(const QJsonObject &obj, const QMetaProperty &prop);
|
||||
|
||||
/**
|
||||
* @brief Sets the first letter of the string to lower case (to make it camelCase).
|
||||
|
@ -72,6 +73,8 @@ private:
|
|||
static QString toPascalCase(QString st);
|
||||
|
||||
static const QRegularExpression m_listExpression;
|
||||
static const QRegularExpression m_hashExpression;
|
||||
static int findTypeIdForProperty(QString type);
|
||||
/**
|
||||
* @brief Qt is doing weird. I'll keep track of the metatypes myself.
|
||||
*/
|
||||
|
@ -107,17 +110,20 @@ public:
|
|||
Q_PROPERTY(Status status READ status NOTIFY statusChanged STORED false)
|
||||
Q_PROPERTY(QNetworkReply::NetworkError error READ error NOTIFY errorChanged STORED false)
|
||||
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged STORED false)
|
||||
Q_PROPERTY(QStringList extraFields MEMBER m_extraFields WRITE setExtraFields NOTIFY extraFieldsChanged STORED false)
|
||||
|
||||
Status status() const { return m_status; }
|
||||
QNetworkReply::NetworkError error() const { return m_error; }
|
||||
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 errorChanged(QNetworkReply::NetworkError newError);
|
||||
void errorStringChanged(QString newErrorString);
|
||||
void extraFieldsChanged(const QStringList &newExtraFields);
|
||||
/**
|
||||
* @brief Convenience signal for status == RemoteData.Ready.
|
||||
*/
|
||||
|
@ -159,6 +165,7 @@ private:
|
|||
Status m_status = Uninitialised;
|
||||
QNetworkReply::NetworkError m_error = QNetworkReply::NoError;
|
||||
QString m_errorString;
|
||||
QStringList m_extraFields;
|
||||
};
|
||||
|
||||
} // NS DTO
|
||||
|
|
|
@ -93,11 +93,13 @@ public:
|
|||
Q_PROPERTY(int productionYear READ productionYear WRITE setProductionYear NOTIFY productionYearChanged)
|
||||
Q_PROPERTY(int indexNumber READ indexNumber WRITE setIndexNumber NOTIFY indexNumberChanged)
|
||||
Q_PROPERTY(int indexNumberEnd READ indexNumberEnd WRITE setIndexNumberEnd NOTIFY indexNumberEndChanged)
|
||||
Q_PROPERTY(int parentIndexNumber READ parentIndexNumber WRITE setParentIndexNumber NOTIFY parentIndexNumberChanged)
|
||||
Q_PROPERTY(bool isFolder READ isFolder WRITE setIsFolder NOTIFY isFolderChanged)
|
||||
Q_PROPERTY(QString parentID MEMBER m_parentId READ parentId NOTIFY parentIdChanged)
|
||||
Q_PROPERTY(QString type MEMBER m_type NOTIFY typeChanged)
|
||||
Q_PROPERTY(QString parentBackdropItemId MEMBER m_parentBackdropItemId NOTIFY parentBackdropItemIdChanged)
|
||||
Q_PROPERTY(QStringList parentBackdropImageTags MEMBER m_parentBackdropImageTags NOTIFY parentBackdropImageTagsChanged)
|
||||
Q_PROPERTY(UserData *userData MEMBER m_userData NOTIFY userDataChanged)
|
||||
Q_PROPERTY(UserData *userData READ userData WRITE setUserData NOTIFY userDataChanged)
|
||||
Q_PROPERTY(int recursiveItemCount READ recursiveItemCount WRITE setRecursiveItemCount NOTIFY recursiveItemCountChanged)
|
||||
Q_PROPERTY(int childCount READ childCount WRITE setChildCount NOTIFY childCountChanged)
|
||||
Q_PROPERTY(QString albumArtist MEMBER m_albumArtist NOTIFY albumArtistChanged)
|
||||
|
@ -147,8 +149,19 @@ public:
|
|||
void setIndexNumber(int newIndexNumber) { m_indexNumber = std::optional<int>(newIndexNumber); emit indexNumberChanged(newIndexNumber); }
|
||||
int indexNumberEnd() const { return m_indexNumberEnd.value_or(-1); }
|
||||
void setIndexNumberEnd(int newIndexNumberEnd) { m_indexNumberEnd = std::optional<int>(newIndexNumberEnd); emit indexNumberEndChanged(newIndexNumberEnd); }
|
||||
int parentIndexNumber() const { return m_parentIndexNumber.value_or(-1); }
|
||||
void setParentIndexNumber(int newParentIndexNumber) { m_parentIndexNumber= std::optional<int>(newParentIndexNumber); emit parentIndexNumberChanged(newParentIndexNumber); }
|
||||
bool isFolder() const { return m_isFolder.value_or(false); }
|
||||
void setIsFolder(bool newIsFolder) { m_isFolder = newIsFolder; emit isFolderChanged(newIsFolder); }
|
||||
QString parentId() const { return m_parentId; }
|
||||
UserData *userData() const { return m_userData; }
|
||||
void setUserData(UserData *newUserData) {
|
||||
if (m_userData != nullptr) {
|
||||
m_userData->deleteLater();
|
||||
}
|
||||
m_userData = newUserData;
|
||||
emit userDataChanged(newUserData);
|
||||
}
|
||||
int recursiveItemCount() const { return m_recursiveItemCount.value_or(-1); }
|
||||
void setRecursiveItemCount(int newRecursiveItemCount) { m_recursiveItemCount = newRecursiveItemCount; emit recursiveItemCountChanged(newRecursiveItemCount); }
|
||||
int childCount() const { return m_childCount.value_or(-1); }
|
||||
|
@ -193,6 +206,7 @@ signals:
|
|||
void indexNumberChanged(int newIndexNumber);
|
||||
void indexNumberEndChanged(int newIndexNumberEnd);
|
||||
void isFolderChanged(bool newIsFolder);
|
||||
void parentIdChanged(const QString &newParentId);
|
||||
void typeChanged(const QString &newType);
|
||||
void parentBackdropItemIdChanged();
|
||||
void parentBackdropImageTagsChanged();
|
||||
|
@ -213,7 +227,7 @@ signals:
|
|||
void heightChanged(int newHeight);
|
||||
|
||||
public slots:
|
||||
void onUserDataChanged(const QString &itemId, QSharedPointer<UserData> userData);
|
||||
void onUserDataChanged(const QString &itemId, UserData *userData);
|
||||
protected:
|
||||
// Overrides
|
||||
QString getDataUrl() const override;
|
||||
|
@ -251,7 +265,9 @@ protected:
|
|||
std::optional<int> m_productionYear = std::nullopt;
|
||||
std::optional<int> m_indexNumber = std::nullopt;
|
||||
std::optional<int> m_indexNumberEnd = std::nullopt;
|
||||
std::optional<int> m_parentIndexNumber = std::nullopt;
|
||||
std::optional<bool> m_isFolder = std::nullopt;
|
||||
QString m_parentId;
|
||||
QString m_type;
|
||||
QString m_parentBackdropItemId;
|
||||
QStringList m_parentBackdropImageTags;
|
||||
|
|
|
@ -35,7 +35,6 @@ public:
|
|||
Q_INVOKABLE explicit MediaStream(QObject *parent = nullptr);
|
||||
MediaStream(const MediaStream &other);
|
||||
bool operator==(const MediaStream &other);
|
||||
virtual ~MediaStream() { qDebug() << "MediaStream destroyed"; }
|
||||
|
||||
enum MediaStreamType {
|
||||
Undefined,
|
||||
|
|
|
@ -33,26 +33,38 @@ class User : public RemoteData {
|
|||
public:
|
||||
Q_INVOKABLE User(QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QString userId MEMBER m_userId WRITE setUserId NOTIFY userIdChanged)
|
||||
Q_PROPERTY(QString jellyfinId MEMBER m_jellyfinId WRITE setJellyfinId NOTIFY jellyfinIdChanged)
|
||||
Q_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QString primaryImageTag MEMBER m_primaryImageTag NOTIFY primaryImageTagChanged)
|
||||
Q_PROPERTY(bool hasPassword MEMBER m_hasPassword NOTIFY hasPasswordChanged)
|
||||
Q_PROPERTY(bool hasConfiguredPassword MEMBER m_hasConfiguredPassword NOTIFY hasConfiguredPasswordChanged)
|
||||
Q_PROPERTY(bool hasConfiguredEasyPassword MEMBER m_hasConfiguredEasyPassword NOTIFY hasConfiguredEasyPasswordChanged)
|
||||
|
||||
void setUserId(const QString &newUserId) {
|
||||
this->m_userId = newUserId;
|
||||
emit userIdChanged(newUserId);
|
||||
reload();
|
||||
void setJellyfinId(const QString &newJellyfinId) {
|
||||
if (m_jellyfinId != newJellyfinId) {
|
||||
this->m_jellyfinId = newJellyfinId;
|
||||
emit jellyfinIdChanged(newJellyfinId);
|
||||
reload();
|
||||
}
|
||||
}
|
||||
signals:
|
||||
void userIdChanged(const QString &newUserId);
|
||||
void nameChanged(const QString &newName);
|
||||
void jellyfinIdChanged(const QString &newJellyfinId);
|
||||
void primaryImageTagChanged(const QString &newPrimaryImageTag);
|
||||
void hasPasswordChanged(bool newHasPasswordChanged);
|
||||
void hasConfiguredPasswordChanged(bool newHasConfiguredPasswordChanged);
|
||||
void hasConfiguredEasyPasswordChanged(bool newHasConfiguredEasyPasswordChanged);
|
||||
|
||||
protected:
|
||||
QString getDataUrl() const override;
|
||||
bool canReload() const override;
|
||||
private:
|
||||
QString m_userId;
|
||||
QString m_name;
|
||||
QString m_jellyfinId;
|
||||
QString m_primaryImageTag;
|
||||
bool m_hasPassword;
|
||||
bool m_hasConfiguredPassword;
|
||||
bool m_hasConfiguredEasyPassword;
|
||||
};
|
||||
|
||||
} // NS DTO
|
||||
|
|
|
@ -67,7 +67,7 @@ signals:
|
|||
void playedChanged(bool newPlayed);
|
||||
public slots:
|
||||
void updateOnServer();
|
||||
void onUpdated(QSharedPointer<UserData> other);
|
||||
void onUpdated(UserData *other);
|
||||
private:
|
||||
std::optional<double> m_playedPercentage = std::nullopt;
|
||||
qint64 m_playbackPositionTicks = 0;
|
||||
|
|
|
@ -162,8 +162,9 @@ signals:
|
|||
* @param userData The new user data
|
||||
*
|
||||
* Note: only Jellyfin::UserData should connect to this signal, they will update themselves!
|
||||
* Note: the userData is only valid during this callback, afterwards it is deleted!
|
||||
*/
|
||||
void userDataChanged(const QString &itemId, QSharedPointer<UserData> userData);
|
||||
void userDataChanged(const QString &itemId, UserData *userData);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
|
@ -192,7 +193,7 @@ public slots:
|
|||
|
||||
protected slots:
|
||||
void defaultNetworkErrorHandler(QNetworkReply::NetworkError error);
|
||||
void onUserDataChanged(const QString &itemId, QSharedPointer<UserData> newData);
|
||||
void onUserDataChanged(const QString &itemId, UserData *newData);
|
||||
|
||||
protected:
|
||||
/**
|
||||
|
|
|
@ -27,6 +27,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QtQml>
|
||||
#include <QQmlParserStatus>
|
||||
#include <QVariant>
|
||||
|
||||
#include "apiclient.h"
|
||||
|
@ -35,9 +36,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
namespace Jellyfin {
|
||||
|
||||
namespace DTO {
|
||||
class Item;
|
||||
class JsonSerializable;
|
||||
class User;
|
||||
}
|
||||
class SortOptions : public QObject{
|
||||
class SortOptions : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SortOptions (QObject *parent = nullptr) : QObject(parent) {}
|
||||
|
@ -61,6 +64,134 @@ public:
|
|||
Q_ENUM(SortBy)
|
||||
};
|
||||
|
||||
/**
|
||||
* Q_OBJECT does not support template classes. This base class declares the
|
||||
* Q_OBJECT related properties and signals.
|
||||
*/
|
||||
class BaseApiModel : public QAbstractListModel, public QQmlParserStatus {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BaseApiModel(QString path, bool hasRecordResponse, bool addUserId, QObject *parent = nullptr);
|
||||
enum ModelStatus {
|
||||
Uninitialised,
|
||||
Loading,
|
||||
Ready,
|
||||
Error,
|
||||
LoadingMore
|
||||
};
|
||||
Q_ENUM(ModelStatus)
|
||||
|
||||
enum SortOrder {
|
||||
Unspecified,
|
||||
Ascending,
|
||||
Descending
|
||||
};
|
||||
Q_ENUM(SortOrder)
|
||||
Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient NOTIFY apiClientChanged)
|
||||
Q_PROPERTY(ModelStatus status READ status NOTIFY statusChanged)
|
||||
|
||||
// Query properties
|
||||
Q_PROPERTY(int limit MEMBER m_limit NOTIFY limitChanged)
|
||||
Q_PROPERTY(QList<QString> sortBy MEMBER m_sortBy NOTIFY sortByChanged)
|
||||
Q_PROPERTY(QList<QString> fields MEMBER m_fields NOTIFY fieldsChanged)
|
||||
Q_PROPERTY(SortOrder sortOrder MEMBER m_sortOrder NOTIFY sortOrderChanged)
|
||||
|
||||
ModelStatus status() const { return m_status; }
|
||||
void setApiClient(ApiClient *newApiClient);
|
||||
void setLimit(int newLimit);
|
||||
|
||||
// From AbstractListModel, gets implemented in ApiModel<T>
|
||||
virtual int rowCount(const QModelIndex &index) const override = 0;
|
||||
virtual QHash<int, QByteArray> roleNames() const override = 0;
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override = 0;
|
||||
virtual bool canFetchMore(const QModelIndex &parent) const override = 0;
|
||||
virtual void fetchMore(const QModelIndex &parent) override = 0;
|
||||
|
||||
|
||||
signals:
|
||||
void ready();
|
||||
void apiClientChanged(ApiClient *newApiClient);
|
||||
void statusChanged(ModelStatus newStatus);
|
||||
void limitChanged(int newLimit);
|
||||
void sortByChanged(QList<QString> newSortOrder);
|
||||
void sortOrderChanged(SortOrder newSortOrder);
|
||||
void fieldsChanged(QList<QString> newFields);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief (Re)loads the data into this model. This might make a network request.
|
||||
*/
|
||||
void reload();
|
||||
|
||||
protected:
|
||||
enum LoadType {
|
||||
RELOAD,
|
||||
LOAD_MORE
|
||||
};
|
||||
|
||||
ApiClient *m_apiClient = nullptr;
|
||||
bool m_isBeingParsed = false;
|
||||
// Per-model specific settings.
|
||||
QString m_path;
|
||||
bool m_hasRecordResponse;
|
||||
bool m_addUserId;
|
||||
bool padding; bool padding2;
|
||||
|
||||
// Query/record controlling properties
|
||||
int m_limit = -1;
|
||||
int m_startIndex = 0;
|
||||
int m_totalRecordCount = 0;
|
||||
const int DEFAULT_LIMIT = 100;
|
||||
|
||||
// Query properties
|
||||
QList<QString> m_fields = {};
|
||||
QList<QString> m_sortBy = {};
|
||||
SortOrder m_sortOrder = Unspecified;
|
||||
|
||||
// State properties.
|
||||
ModelStatus m_status = Uninitialised;
|
||||
|
||||
void setStatus(ModelStatus newStatus) {
|
||||
if (this->m_status != newStatus) {
|
||||
this->m_status = newStatus;
|
||||
emit this->statusChanged(newStatus);
|
||||
if (m_status == Ready) {
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void load(LoadType loadType);
|
||||
virtual void setModelData(QJsonArray &data) = 0;
|
||||
virtual void appendModelData(QJsonArray &data) = 0;
|
||||
/**
|
||||
* @brief Adds parameters to the query
|
||||
* @param query The query to add parameters to
|
||||
*
|
||||
* This method is intended to be overrided by subclasses. It gets called
|
||||
* before a request is made to the server and can be used to enable
|
||||
* query types specific for a certain model to be available.
|
||||
*
|
||||
* Make sure to call the method in the superclass as well!
|
||||
*/
|
||||
virtual void addQueryParameters(QUrlQuery &query);
|
||||
|
||||
/**
|
||||
* @brief Replaces placeholders in an URL.
|
||||
* @param path The path in which placeholders should be replaced.
|
||||
*
|
||||
* This method is intended to be overrided by subclasses. It gets called
|
||||
* before a request is made to the server and can be used to enable
|
||||
* query types specific for a certain model to be available.
|
||||
*
|
||||
* Make sure to call the method in the superclass as well!
|
||||
*/
|
||||
virtual void replacePathPlaceholders(QString &path);
|
||||
|
||||
virtual void classBegin() override;
|
||||
virtual void componentComplete() override;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Abstract model for displaying a REST JSON collection. Role names will be based on the fields encountered in the
|
||||
|
@ -86,25 +217,9 @@ public:
|
|||
* The model will have roleNames for "name" and "id".
|
||||
*
|
||||
*/
|
||||
class ApiModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
template <typename T>
|
||||
class ApiModel : public BaseApiModel {
|
||||
public:
|
||||
enum ModelStatus {
|
||||
Uninitialised,
|
||||
Loading,
|
||||
Ready,
|
||||
Error,
|
||||
LoadingMore
|
||||
};
|
||||
Q_ENUM(ModelStatus)
|
||||
|
||||
enum SortOrder {
|
||||
Unspecified,
|
||||
Ascending,
|
||||
Descending
|
||||
};
|
||||
Q_ENUM(SortOrder)
|
||||
|
||||
/**
|
||||
* @brief Creates a new basemodel
|
||||
* @param path The path (relative to the baseUrl of JellyfinApiClient) to make the call to.
|
||||
|
@ -134,22 +249,6 @@ public:
|
|||
* responseHasRecords should be true
|
||||
*/
|
||||
explicit ApiModel(QString path, bool responseHasRecords, bool passUserId = false, QObject *parent = nullptr);
|
||||
Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient NOTIFY apiClientChanged)
|
||||
Q_PROPERTY(ModelStatus status READ status NOTIFY statusChanged)
|
||||
|
||||
// Query properties
|
||||
Q_PROPERTY(int limit MEMBER m_limit NOTIFY limitChanged)
|
||||
Q_PROPERTY(QString parentId MEMBER m_parentId NOTIFY parentIdChanged)
|
||||
Q_PROPERTY(QList<QString> sortBy MEMBER m_sortBy NOTIFY sortByChanged)
|
||||
Q_PROPERTY(QList<QString> fields MEMBER m_fields NOTIFY fieldsChanged)
|
||||
Q_PROPERTY(QString seasonId MEMBER m_seasonId NOTIFY seasonIdChanged)
|
||||
Q_PROPERTY(QList<QString> imageTypes MEMBER m_imageTypes NOTIFY imageTypesChanged)
|
||||
Q_PROPERTY(QList<QString> includeItemTypes MEMBER m_includeItemTypes NOTIFY includeItemTypesChanged)
|
||||
Q_PROPERTY(bool recursive MEMBER m_recursive)
|
||||
Q_PROPERTY(SortOrder sortOrder MEMBER m_sortOrder NOTIFY sortOrderChanged)
|
||||
|
||||
// Path properties
|
||||
Q_PROPERTY(QString show MEMBER m_show NOTIFY showChanged)
|
||||
|
||||
// Standard QAbstractItemModel overrides
|
||||
int rowCount(const QModelIndex &index) const override {
|
||||
|
@ -161,12 +260,18 @@ public:
|
|||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
|
||||
ModelStatus status() const { return m_status; }
|
||||
|
||||
// Helper methods
|
||||
template<typename QEnum>
|
||||
QString enumToString (const QEnum anEnum) { return QVariant::fromValue(anEnum).toString(); }
|
||||
|
||||
// QList-like API
|
||||
T* at(int index) { return m_array.at(index); }
|
||||
int size() { return rowCount(QModelIndex()); }
|
||||
void insert(int index, T* object);
|
||||
void append(T* object) { insert(size(), object); }
|
||||
void removeAt(int index);
|
||||
void removeOne(T* object);
|
||||
|
||||
template<typename QEnum>
|
||||
QString enumListToString (const QList<QEnum> enumList) {
|
||||
QString result;
|
||||
|
@ -176,74 +281,20 @@ public:
|
|||
return result;
|
||||
}
|
||||
|
||||
signals:
|
||||
void apiClientChanged(ApiClient *newApiClient);
|
||||
void statusChanged(ModelStatus newStatus);
|
||||
void limitChanged(int newLimit);
|
||||
void parentIdChanged(QString newParentId);
|
||||
void sortByChanged(QList<QString> newSortOrder);
|
||||
void sortOrderChanged(SortOrder newSortOrder);
|
||||
void showChanged(QString newShow);
|
||||
void seasonIdChanged(QString newSeasonId);
|
||||
void fieldsChanged(QList<QString> newFields);
|
||||
void imageTypesChanged(QList<QString> newImageTypes);
|
||||
void includeItemTypesChanged(const QList<QString> &newIncludeItemTypes);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief (Re)loads the data into this model. This might make a network request.
|
||||
*/
|
||||
void reload();
|
||||
protected:
|
||||
|
||||
enum LoadType {
|
||||
RELOAD,
|
||||
LOAD_MORE
|
||||
};
|
||||
|
||||
void load(LoadType loadType);
|
||||
/**
|
||||
* @brief Adds parameters to the query
|
||||
* @param query The query to add parameters to
|
||||
*
|
||||
* This method is intended to be overrided by subclasses. It gets called
|
||||
* before a request is made to the server and can be used to enable
|
||||
* query types specific for a certain model to be available.
|
||||
*/
|
||||
virtual void addQueryParameters(QUrlQuery &query);
|
||||
ApiClient *m_apiClient = nullptr;
|
||||
ModelStatus m_status = Uninitialised;
|
||||
|
||||
QString m_path;
|
||||
QJsonArray m_array;
|
||||
bool m_hasRecordResponse;
|
||||
|
||||
// Path properties
|
||||
QString m_show;
|
||||
|
||||
// Query/record controlling properties
|
||||
int m_limit = -1;
|
||||
int m_startIndex = 0;
|
||||
int m_totalRecordCount = 0;
|
||||
const int DEFAULT_LIMIT = 100;
|
||||
|
||||
// Query properties
|
||||
bool m_addUserId = false;
|
||||
QString m_parentId;
|
||||
QString m_seasonId;
|
||||
QList<QString> m_fields = {};
|
||||
QList<QString> m_imageTypes = {};
|
||||
QList<QString> m_sortBy = {};
|
||||
QList<QString> m_includeItemTypes = {};
|
||||
SortOrder m_sortOrder = Unspecified;
|
||||
bool m_recursive = false;
|
||||
|
||||
// AbstractItemModel bookkeeping
|
||||
QHash<int, QByteArray> m_roles;
|
||||
|
||||
void setStatus(ModelStatus newStatus) {
|
||||
this->m_status = newStatus;
|
||||
emit this->statusChanged(newStatus);
|
||||
}
|
||||
// Helper methods.
|
||||
T *deserializeResult(QJsonValueRef source);
|
||||
virtual void addQueryParameters(QUrlQuery &query) override;
|
||||
virtual void replacePathPlaceholders(QString &path) override;
|
||||
|
||||
virtual void setModelData(QJsonArray &data) override;
|
||||
virtual void appendModelData(QJsonArray &data) override;
|
||||
|
||||
// Model-specific properties.
|
||||
QList<T*> m_array;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@ -256,7 +307,7 @@ private:
|
|||
/**
|
||||
* @brief List of the public users on the server.
|
||||
*/
|
||||
class PublicUserModel : public ApiModel {
|
||||
class PublicUserModel : public ApiModel<User> {
|
||||
public:
|
||||
explicit PublicUserModel (QObject *parent = nullptr);
|
||||
};
|
||||
|
@ -266,15 +317,54 @@ public:
|
|||
*
|
||||
* Listens for updates in the library and updates the model accordingly.
|
||||
*/
|
||||
class ItemModel : public ApiModel {
|
||||
class ItemModel : public ApiModel<Item> {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ItemModel (QString path, bool responseHasRecords, bool replaceUser, QObject *parent = nullptr);
|
||||
// Query parameters
|
||||
Q_PROPERTY(QString parentId MEMBER m_parentId WRITE setParentId NOTIFY parentIdChanged)
|
||||
Q_PROPERTY(QString seasonId MEMBER m_seasonId NOTIFY seasonIdChanged)
|
||||
Q_PROPERTY(QList<QString> imageTypes MEMBER m_imageTypes NOTIFY imageTypesChanged)
|
||||
Q_PROPERTY(QList<QString> includeItemTypes MEMBER m_includeItemTypes NOTIFY includeItemTypesChanged)
|
||||
Q_PROPERTY(bool recursive MEMBER m_recursive)
|
||||
QList<QString> m_includeItemTypes = {};
|
||||
|
||||
// Path properties
|
||||
Q_PROPERTY(QString show MEMBER m_show NOTIFY showChanged)
|
||||
|
||||
void setParentId(const QString &parentId) {
|
||||
m_parentId = parentId;
|
||||
emit parentIdChanged(m_parentId);
|
||||
}
|
||||
signals:
|
||||
// Query property signals
|
||||
void parentIdChanged(QString newParentId);
|
||||
void seasonIdChanged(QString newSeasonId);
|
||||
void imageTypesChanged(QList<QString> newImageTypes);
|
||||
void includeItemTypesChanged(const QList<QString> &newIncludeItemTypes);
|
||||
|
||||
// Path property signals
|
||||
void showChanged(QString newShow);
|
||||
public slots:
|
||||
void onUserDataChanged(const QString &itemId, QSharedPointer<DTO::UserData> userData);
|
||||
void onUserDataChanged(const QString &itemId, DTO::UserData *userData);
|
||||
protected:
|
||||
virtual void addQueryParameters(QUrlQuery &query) override;
|
||||
virtual void replacePathPlaceholders(QString &path) override;
|
||||
private:
|
||||
// Path properties
|
||||
QString m_show;
|
||||
|
||||
// Query parameters
|
||||
QString m_parentId;
|
||||
QString m_seasonId;
|
||||
QList<QString> m_imageTypes = {};
|
||||
bool m_recursive = false;
|
||||
};
|
||||
|
||||
class UserViewModel : public ApiModel {
|
||||
//template<>
|
||||
//void ApiModel<Item>::apiClientChanged();
|
||||
|
||||
class UserViewModel : public ItemModel {
|
||||
public:
|
||||
explicit UserViewModel (QObject *parent = nullptr);
|
||||
};
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace Jellyfin {
|
|||
|
||||
namespace JsonHelper {
|
||||
void convertToCamelCase(QJsonValueRef val);
|
||||
void convertToCamelCase(QJsonValue &val);
|
||||
QString convertToCamelCaseHelper(const QString &str);
|
||||
};
|
||||
|
||||
|
|
|
@ -20,29 +20,38 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
#ifndef JELLYFIN_MEDIA_SOURCE_H
|
||||
#define JELLYFIN_MEDIA_SOURCE_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QFuture>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QUrlQuery>
|
||||
#include <QVariant>
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include <QtMultimedia/QMediaPlayer>
|
||||
#include <QtMultimedia/QMediaPlaylist>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#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 and so on.
|
||||
* 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
|
||||
|
@ -54,28 +63,49 @@ public:
|
|||
DirectPlay
|
||||
};
|
||||
Q_ENUM(PlayMethod)
|
||||
using FetchCallback = std::function<void(QUrl &&, PlayMethod)>;
|
||||
|
||||
explicit PlaybackManager(QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient)
|
||||
Q_PROPERTY(Item *item READ item WRITE setItem NOTIFY itemChanged)
|
||||
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(QObject* mediaPlayer READ mediaPlayer WRITE setMediaPlayer NOTIFY mediaPlayerChanged)
|
||||
Q_PROPERTY(PlayMethod playMethod READ playMethod NOTIFY playMethodChanged)
|
||||
|
||||
Item *item() const { return m_item; }
|
||||
void setItem(Item *newItem);
|
||||
// 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)
|
||||
|
||||
QObject *mediaPlayer() const {
|
||||
return m_qmlMediaPlayer;
|
||||
}
|
||||
void setMediaPlayer(QObject *qmlMediaPlayer);
|
||||
// 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);
|
||||
|
@ -86,14 +116,29 @@ signals:
|
|||
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:
|
||||
void updatePlaybackInfo();
|
||||
/**
|
||||
* @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.
|
||||
|
@ -104,10 +149,16 @@ public slots:
|
|||
* @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;
|
||||
|
@ -122,10 +173,19 @@ private:
|
|||
qint64 m_stopPosition = 0;
|
||||
QMediaPlayer::State m_oldState = QMediaPlayer::StoppedState;
|
||||
PlayMethod m_playMethod = Transcode;
|
||||
QObject *m_qmlMediaPlayer = nullptr;
|
||||
QMediaPlayer * m_mediaPlayer = nullptr;
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -136,8 +196,13 @@ private:
|
|||
/**
|
||||
* @brief Retrieves the URL of the stream to open.
|
||||
*/
|
||||
void fetchStreamUrl();
|
||||
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;
|
||||
|
@ -149,6 +214,7 @@ private:
|
|||
*/
|
||||
void postPlaybackInfo(PlaybackInfoType type);
|
||||
|
||||
|
||||
void classBegin() override {
|
||||
m_qmlIsParsingComponent = true;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue