From 0c72906f88a614dec41bc787032092da6e435cb1 Mon Sep 17 00:00:00 2001 From: Henk Kalkwater <chris+dev@netsoj.nl> Date: Thu, 13 Mar 2025 02:51:19 +0100 Subject: [PATCH] [3/3] update openapi spec: update code interacting with generated code Adjusted C++ code to handle with new and renamed objects, as well as properties with different types. As a result of changing types, the QML side had to be updated as well. I hope I found everything by manually testing. Additionally, the Qt Quick application has been updated to test the remote sessions more easily and to make it launch again. --- core/include/JellyfinQt/apimodel.h | 6 +- core/include/JellyfinQt/eventbus.h | 6 +- .../JellyfinQt/model/controllablesession.h | 6 +- .../JellyfinQt/model/remotejellyfinplayback.h | 6 +- core/include/JellyfinQt/viewmodel/item.h | 20 +- core/include/JellyfinQt/viewmodel/itemmodel.h | 42 ++-- .../JellyfinQt/viewmodel/mediastream.h | 6 +- core/src/apiclient.cpp | 23 +- core/src/apimodel.cpp | 5 +- core/src/jellyfin.cpp | 11 +- core/src/model/controllablesession.cpp | 7 +- core/src/model/deviceprofile.cpp | 235 +++++++++--------- core/src/model/item.cpp | 6 +- core/src/model/playbackmanager.cpp | 11 +- core/src/model/playbackreporter.cpp | 4 +- core/src/model/remotejellyfinplayback.cpp | 7 +- core/src/viewmodel/item.cpp | 2 +- core/src/viewmodel/itemmodel.cpp | 2 +- core/src/viewmodel/userdata.cpp | 6 +- core/src/websocket.cpp | 4 +- qtquick/qml/ApiClient.qml | 1 + qtquick/qml/main.qml | 2 +- qtquick/qml/pages/MainPage.qml | 19 +- sailfish/qml/Utils.js | 63 ++--- .../qml/components/ItemChildrenShowcase.qml | 2 +- sailfish/qml/components/PlaybackBar.qml | 4 +- .../qml/components/music/SongDelegate.qml | 3 +- sailfish/qml/cover/CollectionPage.qml | 2 +- sailfish/qml/cover/PosterCover.qml | 2 +- sailfish/qml/harbour-sailfin.qml | 2 +- sailfish/qml/pages/AboutPage.qml | 2 +- sailfish/qml/pages/MainPage.qml | 4 +- .../qml/pages/itemdetails/CollectionPage.qml | 18 +- .../qml/pages/itemdetails/MusicAlbumPage.qml | 2 +- .../qml/pages/itemdetails/MusicArtistPage.qml | 26 +- .../pages/itemdetails/MusicLibraryPage.qml | 20 +- sailfish/translations/harbour-sailfin-de.ts | 32 +-- sailfish/translations/harbour-sailfin-ru.ts | 32 +-- sailfish/translations/harbour-sailfin.ts | 32 +-- 39 files changed, 366 insertions(+), 317 deletions(-) diff --git a/core/include/JellyfinQt/apimodel.h b/core/include/JellyfinQt/apimodel.h index a0bff30..9b85df2 100644 --- a/core/include/JellyfinQt/apimodel.h +++ b/core/include/JellyfinQt/apimodel.h @@ -248,8 +248,8 @@ extern template int extractTotalRecordCount(const QList<DTO::BaseItemDto> &resul extern template void setRequestLimit(Loader::GetLatestMediaParams ¶ms, int limit); extern template bool setRequestStartIndex(Loader::GetLatestMediaParams ¶ms, int offset); -extern template void setRequestLimit(Loader::GetItemsByUserIdParams ¶ms, int limit); -extern template bool setRequestStartIndex(Loader::GetItemsByUserIdParams ¶ms, int offset); +extern template void setRequestLimit(Loader::GetItemsParams ¶ms, int limit); +extern template bool setRequestStartIndex(Loader::GetItemsParams ¶ms, int offset); extern template void setRequestLimit(Loader::GetResumeItemsParams ¶ms, int limit); extern template bool setRequestStartIndex(Loader::GetResumeItemsParams ¶ms, int offset); @@ -332,7 +332,6 @@ protected: R result = m_loader->result(); QList<D> records = extractRecords<D, R>(result); int totalRecordCount = extractTotalRecordCount<R>(result); - qDebug() << "Total record count: " << totalRecordCount << ", records in request: " << records.size(); // If totalRecordCount < 0, it is not supported for this endpoint if (totalRecordCount < 0) { totalRecordCount = records.size(); @@ -549,7 +548,6 @@ protected: void loadingFinished() override { Q_ASSERT(m_loader != nullptr); std::pair<QList<T*>, int> result = m_loader->result(); - qDebug() << "Results loaded: index: " << result.second << ", count: " << result.first.size(); if (result.second == -1) { clear(); } else if (result.second == m_array.size()) { diff --git a/core/include/JellyfinQt/eventbus.h b/core/include/JellyfinQt/eventbus.h index 3b94ac3..755ac32 100644 --- a/core/include/JellyfinQt/eventbus.h +++ b/core/include/JellyfinQt/eventbus.h @@ -26,7 +26,7 @@ namespace Jellyfin { namespace DTO { class UserItemDataDto; class PlaystateRequest; - class SessionInfo; + class SessionInfoDto; } /** @@ -43,14 +43,14 @@ signals: * @param itemId The id of the item which was updated. * @param userData The new userData */ - void itemUserDataUpdated(const QString &itemId, const DTO::UserItemDataDto &userData); + void itemUserDataUpdated(const QString &itemId, const Jellyfin::DTO::UserItemDataDto &userData); /** * @brief The information about a session has been updated * @param sessionId The id of the session * @param sessionInfo The associated information */ - void sessionInfoUpdated(const QString &sessionId, const DTO::SessionInfo &sessionInfo); + void sessionInfoUpdated(const QString &sessionId, const Jellyfin::DTO::SessionInfoDto &sessionInfo); /** * @brief The server has requested to display an message to the user diff --git a/core/include/JellyfinQt/model/controllablesession.h b/core/include/JellyfinQt/model/controllablesession.h index a41c40a..5d3aa6a 100644 --- a/core/include/JellyfinQt/model/controllablesession.h +++ b/core/include/JellyfinQt/model/controllablesession.h @@ -5,7 +5,7 @@ #include <QScopedPointer> #include <QSharedPointer> -#include "JellyfinQt/dto/sessioninfo.h" +#include "JellyfinQt/dto/sessioninfodto.h" namespace Jellyfin { @@ -105,7 +105,7 @@ private: class ControllableJellyfinSession : public ControllableSession { Q_OBJECT public: - ControllableJellyfinSession(QSharedPointer<DTO::SessionInfo> info, ApiClient &apiClient, QObject *parent = nullptr); + ControllableJellyfinSession(QSharedPointer<DTO::SessionInfoDto> info, ApiClient &apiClient, QObject *parent = nullptr); QString id() const override; QString name() const override; QString appName() const override; @@ -113,7 +113,7 @@ public: QString userName() const override; PlaybackManager *createPlaybackManager() const override; private: - QSharedPointer<DTO::SessionInfo> m_data; + QSharedPointer<DTO::SessionInfoDto> m_data; ApiClient &m_apiClient; }; diff --git a/core/include/JellyfinQt/model/remotejellyfinplayback.h b/core/include/JellyfinQt/model/remotejellyfinplayback.h index 835856c..15068ce 100644 --- a/core/include/JellyfinQt/model/remotejellyfinplayback.h +++ b/core/include/JellyfinQt/model/remotejellyfinplayback.h @@ -22,7 +22,7 @@ #include <JellyfinQt/dto/generalcommandtype.h> #include <JellyfinQt/dto/playcommand.h> #include <JellyfinQt/dto/playstatecommand.h> -#include <JellyfinQt/dto/sessioninfo.h> +#include <JellyfinQt/dto/sessioninfodto.h> #include <JellyfinQt/model/playbackmanager.h> #include <JellyfinQt/support/loader.h> @@ -69,7 +69,7 @@ public slots: void seek(qint64 pos) override; private slots: void onPositionTimerFired(); - void onSessionInfoUpdated(const QString &sessionId, const DTO::SessionInfo &sessionInfo); + void onSessionInfoUpdated(const QString &sessionId, const DTO::SessionInfoDto &sessionInfo); private: void sendPlaystateCommand(DTO::PlaystateCommand command, qint64 seekTicks = -1); void sendGeneralCommand(DTO::GeneralCommandType command, QJsonObject arguments = QJsonObject()); @@ -88,7 +88,7 @@ private: void updateQueue(QList<QueueItem> itemIds); ApiClient &m_apiClient; QString m_sessionId; - std::optional<DTO::SessionInfo> m_lastSessionInfo; + std::optional<DTO::SessionInfoDto> m_lastSessionInfo; QTimer *m_positionTimer; qint64 m_position = 0; }; diff --git a/core/include/JellyfinQt/viewmodel/item.h b/core/include/JellyfinQt/viewmodel/item.h index 2cff478..1ae23bc 100644 --- a/core/include/JellyfinQt/viewmodel/item.h +++ b/core/include/JellyfinQt/viewmodel/item.h @@ -116,7 +116,7 @@ public: 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(Jellyfin::DTO::ExtraTypeClass::Value 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) @@ -143,7 +143,7 @@ public: 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(Jellyfin::DTO::BaseItemKindClass::Value type READ type NOTIFY typeChanged) Q_PROPERTY(QString parentBackdropItemId READ parentBackdropItemId NOTIFY parentBackdropItemIdChanged) Q_PROPERTY(QStringList parentBackdropImageTags READ parentBackdropImageTags NOTIFY parentBackdropImageTagsChanged) Q_PROPERTY(Jellyfin::ViewModel::UserData *userData READ userData NOTIFY userDataChanged) @@ -163,7 +163,7 @@ public: Q_PROPERTY(double primaryImageAspectRatio READ primaryImageAspectRatio NOTIFY primaryImageAspectRatioChanged) Q_PROPERTY(QStringList artists READ artists NOTIFY artistsChanged) Q_PROPERTY(QList<QObject *> artistItems READ artistItems NOTIFY artistItemsChanged); - Q_PROPERTY(QString collectionType READ collectionType NOTIFY collectionTypeChanged) + Q_PROPERTY(Jellyfin::DTO::CollectionTypeClass::Value collectionType READ collectionType NOTIFY collectionTypeChanged) // 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) @@ -178,7 +178,7 @@ public: Q_PROPERTY(int albumCount READ albumCount NOTIFY albumCountChanged) Q_PROPERTY(int artistCount READ artistCount NOTIFY artistCountChanged) Q_PROPERTY(int musicVideoCount READ musicVideoCount NOTIFY musicVideoCountChanged) - Q_PROPERTY(QString mediaType READ mediaType NOTIFY mediaTypeChanged) + Q_PROPERTY(Jellyfin::DTO::MediaTypeClass::Value mediaType READ mediaType NOTIFY mediaTypeChanged) Q_PROPERTY(QDateTime endDate READ endDate NOTIFY endDateChanged) Q_PROPERTY(QDateTime startDate READ startDate NOTIFY startDateChanged) Q_PROPERTY(int width READ width NOTIFY widthChanged) @@ -194,7 +194,7 @@ public: 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(); } + ExtraType 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); } @@ -204,7 +204,7 @@ public: int indexNumber() const { return m_data->indexNumber().value_or(-1); } int indexNumberEnd() const { return m_data->indexNumberEnd().value_or(-1); } bool isFolder() const { return m_data->isFolder().value_or(false); } - QString type() const { return m_data->type(); } + BaseItemKind type() const { return m_data->type(); } QString parentBackdropItemId() const { return m_data->parentBackdropItemId(); } QStringList parentBackdropImageTags() const { return m_data->parentBackdropImageTags(); } UserData *userData() const { return m_userData; } @@ -223,11 +223,11 @@ public: double primaryImageAspectRatio() const { return m_data->primaryImageAspectRatio().value_or(1.0); } QStringList artists() const { return m_data->artists(); } QList<QObject *> artistItems() const{ return this->m_artistItems; } - QString collectionType() const { return this->m_data->collectionType(); } + CollectionType collectionType() const { return this->m_data->collectionType(); } QJsonObject imageTags() const { return m_data->imageTags(); } QStringList backdropImageTags() const { return m_data->backdropImageTags(); } QJsonObject imageBlurHashes() const { return m_data->imageBlurHashes(); } - QString mediaType() const { return m_data->mediaType(); } + MediaType mediaType() const { return m_data->mediaType(); } QDateTime endDate() const { return m_data->endDate(); } QDateTime startDate() const { return m_data->startDate(); } Item *currentProgram() const { return m_currentProgram; } @@ -281,7 +281,7 @@ signals: void indexNumberChanged(int newIndexNumber); void indexNumberEndChanged(int newIndexNumberEnd); void isFolderChanged(bool newIsFolder); - void typeChanged(const QString &newType); + void typeChanged(const BaseItemKind &newType); void parentBackdropItemIdChanged(); void parentBackdropImageTagsChanged(); void userDataChanged(UserData *newUserData); @@ -313,7 +313,7 @@ signals: void albumCountChanged(int newAlbumCount); void artistCountChanged(int newArtistCount); void musicVideoCountChanged(int newMusicVideoCount); - void mediaTypeChanged(const QString &newMediaType); + void mediaTypeChanged(const MediaType &newMediaType); void endDateChanged(); void startDateChanged(); void widthChanged(int newWidth); diff --git a/core/include/JellyfinQt/viewmodel/itemmodel.h b/core/include/JellyfinQt/viewmodel/itemmodel.h index 5c9e9c3..30792c6 100644 --- a/core/include/JellyfinQt/viewmodel/itemmodel.h +++ b/core/include/JellyfinQt/viewmodel/itemmodel.h @@ -38,7 +38,7 @@ public: \ Q_PROPERTY(type propName READ propName WRITE set##propSetName NOTIFY propName##Changed) \ type propName() const { return this->m_parameters.propName(); } \ - void set##propSetName(type newValue) { \ + void set##propSetName(const type &newValue) { \ this->m_parameters.set##propSetName( newValue ); \ emit propName##Changed(); \ autoReloadIfNeeded(); \ @@ -92,7 +92,7 @@ public: this->connect(this, &BaseModelLoader::apiClientChanged, this, &AbstractUserParameterLoader<T, D, R, P>::apiClientChanged); } protected: - virtual bool canReload() const override { + bool canReload() const override { return BaseModelLoader::canReload() && !this->m_parameters.userId().isNull(); } private: @@ -125,7 +125,7 @@ public: FWDPROP(bool, includeExternalContent, IncludeExternalContent) FWDPROP(bool, includeHidden, IncludeHidden) - FWDPROP(QStringList, presetViews, PresetViews) + FWDLISTPROP(Jellyfin::DTO::CollectionType, presetViews, PresetViews) }; using LatestMediaBase = AbstractUserParameterLoader<Model::Item, DTO::BaseItemDto, QList<DTO::BaseItemDto>, Jellyfin::Loader::GetLatestMediaParams>; @@ -141,12 +141,12 @@ public: FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDPROP(bool, groupItems, GroupItems) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) - FWDPROP(QStringList, includeItemTypes, IncludeItemTypes) + FWDLISTPROP(Jellyfin::DTO::BaseItemKindClass::Value, includeItemTypes, IncludeItemTypes) FWDPROP(bool, isPlayed, IsPlayed) FWDPROP(QString, parentId, ParentId) }; -using UserItemsLoaderBase = AbstractUserParameterLoader<Model::Item, DTO::BaseItemDto, DTO::BaseItemDtoQueryResult, Jellyfin::Loader::GetItemsByUserIdParams>; +using UserItemsLoaderBase = AbstractUserParameterLoader<Model::Item, DTO::BaseItemDto, DTO::BaseItemDtoQueryResult, Jellyfin::Loader::GetItemsParams>; class UserItemsLoader : public UserItemsLoaderBase { Q_OBJECT public: @@ -166,7 +166,7 @@ public: FWDPROP(bool, enableUserData, EnableUserData) FWDPROP(QStringList, excludeArtistIds, ExcludeArtistIds) FWDPROP(QStringList, excludeItemIds, ExcludeItemIds) - FWDPROP(QStringList, excludeItemTypes, ExcludeItemTypes) + FWDLISTPROP(Jellyfin::DTO::BaseItemKindClass::Value, excludeItemTypes, ExcludeItemTypes) FWDPROP(QList<Jellyfin::DTO::LocationTypeClass::Value>, excludeLocationTypes, ExcludeLocationTypes) FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDLISTPROP(Jellyfin::DTO::ItemFilterClass::Value, filters, Filters) @@ -186,7 +186,7 @@ public: FWDPROP(QStringList, ids, Ids) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, imageTypes, ImageTypes) - FWDPROP(QStringList, includeItemTypes, IncludeItemTypes) + FWDLISTPROP(Jellyfin::DTO::BaseItemKindClass::Value, includeItemTypes, IncludeItemTypes) FWDPROP(bool, is3D, Is3D) FWDPROP(bool, is4K, Is4K) FWDPROP(bool, isFavorite, IsFavorite) @@ -201,13 +201,13 @@ public: FWDPROP(QString, maxOfficialRating, MaxOfficialRating) FWDPROP(QDateTime, maxPremiereDate, MaxPremiereDate) FWDPROP(qint32, maxWidth, MaxWidth) - FWDPROP(QStringList, mediaTypes, MediaTypes) + FWDLISTPROP(Jellyfin::DTO::MediaTypeClass::Value, mediaTypes, MediaTypes) FWDPROP(qint32, minHeight, MinHeight) FWDPROP(QString, minOfficialRating, MinOfficialRating) FWDPROP(QDateTime, minPremiereDate, MinPremiereDate) FWDPROP(qint32, minWidth, MinWidth) - FWDPROP(QString, sortBy, SortBy) - FWDPROP(QString, sortOrder, SortOrder) + FWDLISTPROP(Jellyfin::DTO::ItemSortByClass::Value, sortBy, SortBy) + FWDLISTPROP(Jellyfin::DTO::SortOrderClass::Value, sortOrder, SortOrder) FWDPROP(QStringList, tags, Tags) FWDPROP(QList<qint32>, years, Years) @@ -227,11 +227,11 @@ public: FWDPROP(bool, enableImages, EnableImages) FWDPROP(bool, enableTotalRecordCount, EnableTotalRecordCount) FWDPROP(bool, enableUserData, EnableUserData) - FWDPROP(QStringList, excludeItemTypes, ExcludeItemTypes) + FWDLISTPROP(Jellyfin::DTO::BaseItemKindClass::Value, excludeItemTypes, ExcludeItemTypes) FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) - FWDPROP(QStringList, includeItemTypes, IncludeItemTypes) - FWDPROP(QStringList, mediaTypes, MediaTypes) + FWDLISTPROP(Jellyfin::DTO::BaseItemKindClass::Value, includeItemTypes, IncludeItemTypes) + FWDLISTPROP(Jellyfin::DTO::MediaTypeClass::Value, mediaTypes, MediaTypes) FWDPROP(QString, parentId, ParentId) FWDPROP(QString, searchTerm, SearchTerm) }; @@ -269,7 +269,7 @@ public: FWDPROP(bool, isMissing, IsMissing) FWDPROP(qint32, season, Season) FWDPROP(QString, seasonId, SeasonId) - FWDPROP(QString, sortBy, SortBy) + FWDPROP(Jellyfin::DTO::ItemSortByClass::Value, sortBy, SortBy) FWDPROP(QString, startItemId, StartItemId) }; @@ -281,7 +281,7 @@ public: FWDPROP(bool, disableFirstEpisode, DisableFirstEpisode) FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, enableImageTypes, EnableImageTypes); - FWDPROP(bool, enableImges, EnableImges) + FWDPROP(bool, enableImages, EnableImages) FWDPROP(bool, enableTotalRecordCount, EnableTotalRecordCount) FWDPROP(bool, enableUserData, EnableUserData) FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) @@ -300,16 +300,16 @@ public: FWDPROP(bool, enableImages, EnableImages) FWDPROP(bool, enableTotalRecordCount, EnableTotalRecordCount) FWDPROP(bool, enableUserData, EnableUserData) - FWDPROP(QStringList, excludeItemTypes, ExcludeItemTypes) + FWDLISTPROP(Jellyfin::DTO::BaseItemKindClass::Value, excludeItemTypes, ExcludeItemTypes) FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDLISTPROP(Jellyfin::DTO::ItemFilterClass::Value, filters, Filters) FWDPROP(QStringList, genreIds, GenreIds) FWDPROP(QStringList, genres, Genres) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) - FWDPROP(QStringList, includeItemTypes, IncludeItemTypes) + FWDLISTPROP(Jellyfin::DTO::BaseItemKindClass::Value, includeItemTypes, IncludeItemTypes) FWDPROP(bool, isFavorite, IsFavorite) FWDPROP(int, limit, Limit) - FWDPROP(QStringList, mediaTypes, MediaTypes) + FWDLISTPROP(Jellyfin::DTO::MediaTypeClass::Value, mediaTypes, MediaTypes) FWDPROP(double, minCommunityRating, MinCommunityRating) FWDPROP(QString, nameLessThan, NameLessThan) FWDPROP(QString, nameStartsWith, NameStartsWith) @@ -347,7 +347,7 @@ public: FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, enableImageTypes, EnableImageTypes) FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDPROP(bool, enableUserData, EnableUserData) - FWDPROP(QStringList, sortBy, SortBy) + FWDLISTPROP(Jellyfin::DTO::ItemSortByClass::Value, sortBy, SortBy) FWDPROP(Jellyfin::DTO::SortOrderClass::Value, sortOrder, SortOrder) FWDPROP(bool, enableFavoriteSorting, EnableFavoriteSorting) FWDPROP(bool, addCurrentProgram, AddCurrentProgram) @@ -405,7 +405,7 @@ public: explicit ItemModel (QObject *parent = nullptr); - virtual QHash<int, QByteArray> roleNames() const override { + QHash<int, QByteArray> roleNames() const override { return { JFRN(jellyfinId), JFRN(name), @@ -450,7 +450,7 @@ public: QSharedPointer<Model::Item> itemAt(int index); private slots: void onInsertItems(const QModelIndex &parent, int start, int end); - void onUserDataUpdated(const DTO::UserItemDataDto &newUserData); + void onUserDataUpdated(const Jellyfin::DTO::UserItemDataDto &newUserData); }; #undef JFRN diff --git a/core/include/JellyfinQt/viewmodel/mediastream.h b/core/include/JellyfinQt/viewmodel/mediastream.h index 0728fdf..6a4b706 100644 --- a/core/include/JellyfinQt/viewmodel/mediastream.h +++ b/core/include/JellyfinQt/viewmodel/mediastream.h @@ -41,7 +41,7 @@ public: Q_PROPERTY(QString comment READ comment NOTIFY commentChanged); Q_PROPERTY(QString timeBase READ timeBase NOTIFY timeBaseChanged); Q_PROPERTY(QString title READ title NOTIFY titleChanged); - Q_PROPERTY(QString videoRange READ videoRange NOTIFY videoRangeChanged); + Q_PROPERTY(Jellyfin::DTO::VideoRangeClass::Value videoRange READ videoRange NOTIFY videoRangeChanged); Q_PROPERTY(QString localizedUndefined READ localizedUndefined NOTIFY localizedUndefinedChanged); Q_PROPERTY(QString localizedDefault READ localizedDefault NOTIFY localizedDefaultChanged); Q_PROPERTY(QString localizedForced READ localizedForced NOTIFY localizedForcedChanged); @@ -78,7 +78,7 @@ public: QString comment() const { return m_data->comment(); } QString timeBase() const { return m_data->timeBase(); } QString title() const { return m_data->title(); } - QString videoRange() const { return m_data->videoRange(); } + DTO::VideoRange videoRange() const { return m_data->videoRange(); } QString localizedUndefined() const { return m_data->localizedUndefined(); } QString localizedDefault() const { return m_data->localizedDefault(); } QString localizedForced() const { return m_data->localizedForced(); } @@ -116,7 +116,7 @@ signals: void commentChanged(const QString &newComment); void timeBaseChanged(const QString &newTimeBase); void titleChanged(const QString &newTitle); - void videoRangeChanged(const QString &newVideoRanged); + void videoRangeChanged(const DTO::VideoRange &newVideoRanged); void localizedUndefinedChanged(const QString &newLocalizedUndefined); void localizedDefaultChanged(const QString &newLocalizedDefault); void localizedForcedChanged(const QString &newLocalizedForced); diff --git a/core/src/apiclient.cpp b/core/src/apiclient.cpp index 6ccb740..d510559 100644 --- a/core/src/apiclient.cpp +++ b/core/src/apiclient.cpp @@ -430,15 +430,15 @@ void ApiClient::authenticate(QString username, QString password, bool storeCrede } void ApiClient::submitQuickConnectCode(const QString &code) { - using QQAuthorizeLoader = Loader::HTTP::AuthorizeLoader; - Loader::AuthorizeParams params; + using QQAuthorizeLoader = Loader::HTTP::AuthorizeQuickConnectLoader; + Loader::AuthorizeQuickConnectParams params; params.setCode(code); QQAuthorizeLoader *loader = new QQAuthorizeLoader(this); loader->setParameters(params); loader->load(); - loader->connect(loader, &QQAuthorizeLoader::error, this, [this, loader](QString message) { + loader->connect(loader, &QQAuthorizeLoader::error, this, [this, loader](const QString &message) { qDebug() << "QQ error: " << message; emit this->quickConnectRejected(); loader->deleteLater(); @@ -475,17 +475,18 @@ void ApiClient::generateDeviceProfile() { Q_D(ApiClient); QSharedPointer<DTO::DeviceProfile> deviceProfile = QSharedPointer<DTO::DeviceProfile>::create(Model::DeviceProfile::generateProfile()); deviceProfile->setJellyfinId(d->deviceId); - deviceProfile->setFriendlyName(QSysInfo::prettyProductName()); + deviceProfile->setName(QSysInfo::prettyProductName()); deviceProfile->setMaxStreamingBitrate(d->settings->maxStreamingBitRate()); d->deviceProfile = deviceProfile; - QSharedPointer<DTO::ClientCapabilitiesDto> clientCapabilities = QSharedPointer<DTO::ClientCapabilitiesDto>::create(true, // supports mediaControl - false, // supports content uploading - true, // supports persistent identifier - false, // supports sync - deviceProfile); - clientCapabilities->setPlayableMediaTypes({"Audio", "Video", "Photo"}); - clientCapabilities->setSupportedCommands(d->supportedCommands); + QSharedPointer<DTO::ClientCapabilitiesDto> clientCapabilities = QSharedPointer<DTO::ClientCapabilitiesDto>::create( + QList({ MediaType::Audio, MediaType::Video, MediaType::Photo }), + d->supportedCommands, + true, // supports mediaControl + true, // supports persistent identifier + deviceProfile + ); + clientCapabilities->setAppStoreUrl("https://chris.netsoj.nl/projects/harbour-sailfin"); clientCapabilities->setIconUrl("https://chris.netsoj.nl/static/img/logo.png"); diff --git a/core/src/apimodel.cpp b/core/src/apimodel.cpp index 20113d9..36b43d9 100644 --- a/core/src/apimodel.cpp +++ b/core/src/apimodel.cpp @@ -64,7 +64,6 @@ void BaseModelLoader::setApiClient(ApiClient *newApiClient) { void BaseModelLoader::setLimit(int newLimit) { m_explicitLimitSet = newLimit >= 0; - qCDebug(jellyfinApiModel) << "Limit explicitly set to " << newLimit; this->m_limit = newLimit; emit limitChanged(newLimit); } @@ -144,12 +143,12 @@ bool setRequestStartIndex(Loader::GetLatestMediaParams ¶ms, int offset) { } template<> -void setRequestLimit(Loader::GetItemsByUserIdParams ¶ms, int limit) { +void setRequestLimit(Loader::GetItemsParams ¶ms, int limit) { params.setLimit(limit); } template<> -bool setRequestStartIndex(Loader::GetItemsByUserIdParams ¶ms, int index) { +bool setRequestStartIndex(Loader::GetItemsParams ¶ms, int index) { params.setStartIndex(index); return true; } diff --git a/core/src/jellyfin.cpp b/core/src/jellyfin.cpp index 8efcca3..c15d13f 100644 --- a/core/src/jellyfin.cpp +++ b/core/src/jellyfin.cpp @@ -18,7 +18,9 @@ */ #include "JellyfinQt/jellyfin.h" +#include "JellyfinQt/dto/collectiontype.h" #include "JellyfinQt/model/item.h" +#include "JellyfinQt/dto/itemsortby.h" #include "JellyfinQt/dto/itemfields.h" #include "JellyfinQt/dto/mediastream.h" #include "JellyfinQt/dto/nameguidpair.h" @@ -92,11 +94,16 @@ void JellyfinPlugin::registerTypes(const char *uri) { qmlRegisterType<ViewModel::LiveTvChannelsLoader>(uri, 1, 0, "LiveTvChannelsLoader"); // Enumerations + qmlRegisterUncreatableType<Jellyfin::DTO::CollectionTypeClass>(uri, 1, 0, "CollectionType", "Is an enum"); qmlRegisterUncreatableType<Jellyfin::DTO::GeneralCommandTypeClass>(uri, 1, 0, "GeneralCommandType", "Is an enum"); - qmlRegisterUncreatableType<Jellyfin::ViewModel::ModelStatusClass>(uri, 1, 0, "ModelStatus", "Is an enum"); - qmlRegisterUncreatableType<Jellyfin::DTO::PlayMethodClass>(uri, 1, 0, "PlayMethod", "Is an enum"); qmlRegisterUncreatableType<Jellyfin::DTO::ItemFieldsClass>(uri, 1, 0, "ItemFields", "Is an enum"); qmlRegisterUncreatableType<Jellyfin::DTO::ImageTypeClass>(uri, 1, 0, "ImageType", "Is an enum"); + qmlRegisterUncreatableType<Jellyfin::DTO::MediaTypeClass>(uri, 1, 0, "MediaType", "Is an enum"); + qmlRegisterUncreatableType<Jellyfin::DTO::BaseItemKindClass>(uri, 1, 0, "ItemType", "Is an enum"); + qmlRegisterUncreatableType<Jellyfin::DTO::PlayMethodClass>(uri, 1, 0, "PlayMethod", "Is an enum"); + qmlRegisterUncreatableType<Jellyfin::DTO::ItemSortByClass>(uri, 1, 0, "SortBy", "Is an enum"); + qmlRegisterUncreatableType<Jellyfin::DTO::SortOrderClass>(uri, 1, 0, "SortOrder", "Is an enum"); + qmlRegisterUncreatableType<Jellyfin::ViewModel::ModelStatusClass>(uri, 1, 0, "ModelStatus", "Is an enum"); qmlRegisterUncreatableType<Jellyfin::ViewModel::NowPlayingSection>(uri, 1, 0, "NowPlayingSection", "Is an enum"); qmlRegisterUncreatableType<Jellyfin::Model::PlayerStateClass>(uri, 1, 0, "PlayerState", "Is an enum"); qmlRegisterUncreatableType<Jellyfin::Model::MediaStatusClass>(uri, 1, 0, "MediaStatus", "Is an enum"); diff --git a/core/src/model/controllablesession.cpp b/core/src/model/controllablesession.cpp index a1b3b70..c499575 100644 --- a/core/src/model/controllablesession.cpp +++ b/core/src/model/controllablesession.cpp @@ -46,7 +46,7 @@ PlaybackManager *LocalSession::createPlaybackManager() const { } // ControllableJellyfinSession -ControllableJellyfinSession::ControllableJellyfinSession(const QSharedPointer<DTO::SessionInfo> info, ApiClient &apiClient, QObject *parent) +ControllableJellyfinSession::ControllableJellyfinSession(const QSharedPointer<DTO::SessionInfoDto> info, ApiClient &apiClient, QObject *parent) : ControllableSession(parent), m_data(info), m_apiClient(apiClient){} @@ -147,14 +147,15 @@ void RemoteJellyfinSessionScanner::startScanning() { d->loader->setParameters(params); connect(d->loader, &Loader::HTTP::GetSessionsLoader::ready, this, [this, d, localSession]() { if (d->loader == nullptr) return; - QList<DTO::SessionInfo> sessions = d->loader->result(); + QList<DTO::SessionInfoDto> sessions = d->loader->result(); + qDebug() << "Found " << sessions.count() << " sessions"; for(auto it = sessions.begin(); it != sessions.end(); it++) { // Skip this device if (it->deviceId() == localSession->id()) continue; - emit sessionFound(new ControllableJellyfinSession(QSharedPointer<DTO::SessionInfo>::create(*it), *d->apiClient)); + emit sessionFound(new ControllableJellyfinSession(QSharedPointer<DTO::SessionInfoDto>::create(*it), *d->apiClient)); } }); d->loader->load(); diff --git a/core/src/model/deviceprofile.cpp b/core/src/model/deviceprofile.cpp index ba5363a..8c6fdf1 100644 --- a/core/src/model/deviceprofile.cpp +++ b/core/src/model/deviceprofile.cpp @@ -53,8 +53,6 @@ int DeviceProfile::maxStreamingBitrate() { } DTO::DeviceProfile DeviceProfile::generateProfile() { - using JsonPair = QPair<QString, QJsonValue>; - QStringList audioCodes = { "aac", "flac", @@ -94,130 +92,138 @@ DTO::DeviceProfile DeviceProfile::generateProfile() { // AAC - DTO::CodecProfile codecProfile1(DTO::CodecType::VideoAudio); + DTO::CodecProfile codecProfile1( + DTO::CodecType::VideoAudio, + { + createCondition(CondVal::IsSecondaryAudio, + Condition::Equals, + "false", + false) + }, + {} + ); codecProfile1.setCodec("aac"); - QList<DTO::ProfileCondition> codecProfile1Conditions; - codecProfile1Conditions.append(createCondition(CondVal::IsSecondaryAudio, - Condition::Equals, - "false", - false)); - codecProfile1.setConditions(codecProfile1Conditions); - - DTO::CodecProfile codecProfile2(DTO::CodecType::Video); - codecProfile2.setCodec("h264"); - codecProfile2.setConditions({ - createCondition(CondVal::IsAnamorphic, + DTO::CodecProfile codecProfile2( + DTO::CodecType::Video, + { + createCondition(CondVal::IsAnamorphic, Condition::NotEquals, "true", false), - createCondition(CondVal::VideoProfile, - Condition::EqualsAny, - "baseline|constrained baseline", false), //"high|main|baseline|constrained baseline" - createCondition(CondVal::VideoLevel, - Condition::LessThanEqual, - "51", false), - createCondition(CondVal::IsInterlaced, - Condition::NotEquals, - "true", false) - }); + createCondition(CondVal::VideoProfile, + Condition::EqualsAny, + "baseline|constrained baseline", false), //"high|main|baseline|constrained baseline" + createCondition(CondVal::VideoLevel, + Condition::LessThanEqual, + "51", false), + createCondition(CondVal::IsInterlaced, + Condition::NotEquals, + "true", false) + }, + {} + ); + codecProfile2.setCodec("h264"); QList<DTO::CodecProfile> codecProfiles = { codecProfile1, codecProfile2 }; // Hard coded nr 1: - DTO::TranscodingProfile transcoding1(DTO::DlnaProfileType::Audio, - false, // estimeateContentLength - false, // enable MPEG2 TS M2 mode - DTO::TranscodeSeekInfo::Auto, - false, // copyTimestamps - DTO::EncodingContext::Streaming, - false, // enable subtitles in manifest - 0, // minSegments - 0, // minSegmentLength - true // set break on nonkeyframes - ); - transcoding1.setAudioCodec("aac"); - transcoding1.setContainer("ts"); + DTO::TranscodingProfile transcoding1( + QStringLiteral("ts"), + DTO::DlnaProfileType::Audio, + QStringLiteral("h264"), + QStringLiteral("aac"), + DTO::MediaStreamProtocol::Hls, + false, // estimeateContentLength + false, // enable MPEG2 TS M2 mode + DTO::TranscodeSeekInfo::Auto, + false, // copyTimestamps + DTO::EncodingContext::Streaming, + false, // enable subtitles in manifest + 0, // minSegments + 0, // minSegmentLength + true, // set break on nonkeyframes, + {}, // conditions + true // Enable audio VBR encoding + ); transcoding1.setMaxAudioChannels("2"); - transcoding1.setProtocol("hls"); // Hard code nr 2 - DTO::TranscodingProfile transcoding2(DTO::DlnaProfileType::Video, - false, // estimate content length - false, // enable MPEG2 ts M2 mode - DTO::TranscodeSeekInfo::Auto, - false, // copy timestamps - DTO::EncodingContext::Streaming, - false, // enable subtitles in manifest - 0, // minSegments - 0, // minSegmentLength - true // set break on non-keyframes - ); - transcoding2.setAudioCodec("mp3,aac"); - transcoding2.setContainer("ts"); + DTO::TranscodingProfile transcoding2( + QStringLiteral("ts"), + DTO::DlnaProfileType::Video, + QStringLiteral("h264"), + QStringLiteral("mp3,aac"), + DTO::MediaStreamProtocol::Hls, + false, // estimate content length + false, // enable MPEG2 ts M2 mode + DTO::TranscodeSeekInfo::Auto, + false, // copy timestamps + DTO::EncodingContext::Streaming, + false, // enable subtitles in manifest + 0, // minSegments + 0, // minSegmentLength + true, // set break on non-keyframes + {}, // conditions + true// enableAudioVbrEncoding + ); transcoding2.setMaxAudioChannels("2"); - transcoding2.setProtocol("hls"); - transcoding2.setVideoCodec("h264"); // Fallback - DTO::TranscodingProfile transcoding3(DTO::DlnaProfileType::Video, - false, // estimate content length - false, // enable MPEG2 ts M2 mode - DTO::TranscodeSeekInfo::Auto, - false, // copy timestamps - DTO::EncodingContext::Static, - false, // enable subtitles in manifest - 0, // minSegments - 0, // minSegmentLength - true // set break on non-keyframes - ); - transcoding3.setContainer("mp4"); - transcoding3.setAudioCodec(videoAudioCodecs.join(',')); - transcoding3.setVideoCodec("h264"); - transcoding3.setProtocol("http"); + DTO::TranscodingProfile transcoding3( + QStringLiteral("mp4"), + DTO::DlnaProfileType::Video, + QStringLiteral("h264"), + videoAudioCodecs.join(","), + DTO::MediaStreamProtocol::Http, + false, // estimate content length + false, // enable MPEG2 ts M2 mode + DTO::TranscodeSeekInfo::Auto, + false, // copy timestamps + DTO::EncodingContext::Static, + false, // enable subtitles in manifest + 0, // minSegments + 0, // minSegmentLength + true, // set break on non-keyframes + {}, + true + ); QList<DTO::TranscodingProfile> transcodingProfiles = { transcoding1, transcoding2, transcoding3 }; if (supportsHls() && !hlsVideoAudioCodecs.isEmpty()) { - DTO::TranscodingProfile transcoding4(DTO::DlnaProfileType::Video, - false, // estimate content length - false, // enable MPEG2 ts M2 mode - DTO::TranscodeSeekInfo::Auto, - false, // copy timestamps - DTO::EncodingContext::Streaming, - false, // enable subtitles in manifest - 1, // minSegments - 0, // minSegmentLength - true // set break on non-keyframes - ); - transcoding4.setContainer("ts"); - transcoding4.setAudioCodec(hlsVideoAudioCodecs.join(',')); - transcoding4.setVideoCodec(hlsVideoCodecs.join(',')); - transcoding4.setProtocol("hls"); + DTO::TranscodingProfile transcoding4( + QStringLiteral("ts"), + DTO::DlnaProfileType::Video, + hlsVideoAudioCodecs.join(","), + hlsVideoAudioCodecs.join(","), + DTO::MediaStreamProtocol::Hls, + false, // estimate content length + false, // enable MPEG2 ts M2 mode + DTO::TranscodeSeekInfo::Auto, + false, // copy timestamps + DTO::EncodingContext::Streaming, + false, // enable subtitles in manifest + 1, // minSegments + 0, // minSegmentLength + true, // set break on non-keyframes + {}, + true + ); transcoding4.setMaxAudioChannels("2"); transcodingProfiles.append(transcoding4); } - // Response profiles (or whatever it actually does?) - DTO::ResponseProfile responseProfile1(DTO::DlnaProfileType::Video); - responseProfile1.setContainer("m4v"); - responseProfile1.setMimeType("video/mp4"); - QList<DTO::ResponseProfile> responseProfiles = { - responseProfile1 - }; - // Direct play profiles // Video - DTO::DirectPlayProfile directPlayProfile1(DTO::DlnaProfileType::Video); - directPlayProfile1.setContainer("mp4,m4v"); + DTO::DirectPlayProfile directPlayProfile1("mp4,m4v", DTO::DlnaProfileType::Video); directPlayProfile1.setVideoCodec(mp4VideoCodecs.join(',')); directPlayProfile1.setAudioCodec(videoAudioCodecs.join(',')); - DTO::DirectPlayProfile directPlayProfile2(DTO::DlnaProfileType::Video); - directPlayProfile2.setContainer("mkv"); + DTO::DirectPlayProfile directPlayProfile2("mkv", DTO::DlnaProfileType::Video); directPlayProfile2.setVideoCodec(mp4VideoCodecs.join(',')); directPlayProfile2.setAudioCodec(videoAudioCodecs.join(',')); @@ -227,43 +233,36 @@ DTO::DeviceProfile DeviceProfile::generateProfile() { // Audio for (auto it = audioCodes.begin(); it != audioCodes.end(); it++) { if (*it == "mp2") { - DTO::DirectPlayProfile profile(DTO::DlnaProfileType::Audio); - profile.setContainer("mp2,mp3"); + DTO::DirectPlayProfile profile("mp2,mp3", DTO::DlnaProfileType::Audio); profile.setAudioCodec("mp2"); directPlayProfiles.append(profile); } else if(*it == "mp3") { - DTO::DirectPlayProfile profile(DTO::DlnaProfileType::Audio); - profile.setContainer("mp3"); + DTO::DirectPlayProfile profile("mp3", DTO::DlnaProfileType::Audio); profile.setAudioCodec("mp3"); directPlayProfiles.append(profile); } else if (*it == "webma") { - DTO::DirectPlayProfile profile(DTO::DlnaProfileType::Audio); - profile.setContainer("webma,webm"); + DTO::DirectPlayProfile profile("webma,webm", DTO::DlnaProfileType::Audio); directPlayProfiles.append(profile); } else { - DTO::DirectPlayProfile profile(DTO::DlnaProfileType::Audio); - profile.setContainer(*it); + DTO::DirectPlayProfile profile(*it, DTO::DlnaProfileType::Audio); directPlayProfiles.append(profile); } } + QList<DTO::ContainerProfile> containerProfiles = { }; + + QList<DTO::SubtitleProfile> subtitleProfiles = { + DTO::SubtitleProfile(DTO::SubtitleDeliveryMethodClass::Hls), + DTO::SubtitleProfile(DTO::SubtitleDeliveryMethodClass::Encode) + }; + DTO::DeviceProfile profile( - QSharedPointer<DTO::DeviceIdentification>::create(), - false, // enableAlbumArtInDidl - false, // enableSingleAlbumArtLimit - false, // enableSingleSubtitleLimit - std::numeric_limits<qint32>().max(), // max album art width - std::numeric_limits<qint32>().max(), // max album art height - 0, // timeline offset seconds - false, // request plain video items - false, // request plain folders - false, // enableMSMediaReceiverRegistrar, - false //ignoreTranscodeByteRangeRequests + directPlayProfiles, + transcodingProfiles, + containerProfiles, + codecProfiles, + subtitleProfiles ); - profile.setCodecProfiles(codecProfiles); - profile.setDirectPlayProfiles(directPlayProfiles); - profile.setResponseProfiles(responseProfiles); - profile.setTranscodingProfiles(transcodingProfiles); profile.setMaxStreamingBitrate(std::make_optional<qint32>(maxStreamingBitrate())); return profile; } diff --git a/core/src/model/item.cpp b/core/src/model/item.cpp index 4687e68..496dcc7 100644 --- a/core/src/model/item.cpp +++ b/core/src/model/item.cpp @@ -26,12 +26,16 @@ namespace Model { Item::Item(ApiClient *apiClient, QObject *parent) : Item(DTO::BaseItemDto( QString(), // id + ExtraType::Unknown, Video3DFormat::EnumNotSet, PlayAccess::Full, - QSharedPointer<UserItemDataDto>::create(0, 0, false, false), + BaseItemKind::EnumNotSet, + QSharedPointer<UserItemDataDto>::create(0, 0, false, false, QString(), QString()), + CollectionType::EnumNotSet, VideoType::EnumNotSet, LocationType::Virtual, IsoType::EnumNotSet, + MediaType::EnumNotSet, ImageOrientation::EnumNotSet, ChannelType::EnumNotSet, ProgramAudio::EnumNotSet, diff --git a/core/src/model/playbackmanager.cpp b/core/src/model/playbackmanager.cpp index 5bd254c..2cb65b7 100644 --- a/core/src/model/playbackmanager.cpp +++ b/core/src/model/playbackmanager.cpp @@ -232,7 +232,7 @@ public: void requestItemUrl(QSharedPointer<Model::Item> item); // slots - void handlePlaybackInfoResponse(QString itemId, QString mediaType, DTO::PlaybackInfoResponse &response); + void handlePlaybackInfoResponse(QString itemId, MediaType mediaType, DTO::PlaybackInfoResponse &response); /// Called when we have fetched the playback URL and playSession void onItemUrlReceived(const QString &itemId, const QUrl &url, const QString &playSession, // Fully specify class to please MOC @@ -352,7 +352,7 @@ void LocalPlaybackManagerPrivate::setItem(QSharedPointer<Model::Item> newItem) { } } -void LocalPlaybackManagerPrivate::handlePlaybackInfoResponse(QString itemId, QString mediaType, DTO::PlaybackInfoResponse &response) { +void LocalPlaybackManagerPrivate::handlePlaybackInfoResponse(QString itemId, MediaType mediaType, DTO::PlaybackInfoResponse &response) { Q_Q(LocalPlaybackManager); //TODO: move the item URL fetching logic out of this function, into MediaSourceInfo? QList<DTO::MediaSourceInfo> mediaSources = response.mediaSources(); @@ -394,15 +394,16 @@ void LocalPlaybackManagerPrivate::handlePlaybackInfoResponse(QString itemId, QSt resultingUrl = QUrl::fromLocalFile(source.path()); playMethod = PlayMethod::DirectPlay; } else if (source.supportsDirectStream() && !transcodePreferred) { - if (mediaType == "Video") { - mediaType.append('s'); + QString mediaTypeUrl = Support::toString(mediaType); + if (mediaType == MediaType::Video) { + mediaTypeUrl.append('s'); } QUrlQuery query; query.addQueryItem("mediaSourceId", source.jellyfinId()); query.addQueryItem("deviceId", m_apiClient->deviceId()); query.addQueryItem("api_key", m_apiClient->token()); query.addQueryItem("Static", "True"); - resultingUrl = QUrl(m_apiClient->baseUrl() + "/" + mediaType + "/" + itemId + resultingUrl = QUrl(m_apiClient->baseUrl() + "/" + mediaTypeUrl + "/" + itemId + "/stream." + source.container() + "?" + query.toString(QUrl::EncodeReserved)); playMethod = PlayMethod::DirectStream; } else if (source.supportsTranscoding() && !source.transcodingUrlNull() && transcodingAllowed) { diff --git a/core/src/model/playbackreporter.cpp b/core/src/model/playbackreporter.cpp index 8d26b10..1b27fb3 100644 --- a/core/src/model/playbackreporter.cpp +++ b/core/src/model/playbackreporter.cpp @@ -149,7 +149,9 @@ void PlaybackReporterPrivate::postPlaybackInfo(PlaybackInfoType type) { m_playbackManager->player()->state() == PlayerState::Paused, false, // is muted? m_playbackManager->playMethod(), - DTO::RepeatMode::RepeatNone); + DTO::RepeatMode::RepeatNone, + DTO::PlaybackOrder::Default + ); progress.setSessionId(m_playbackManager->sessionId()); diff --git a/core/src/model/remotejellyfinplayback.cpp b/core/src/model/remotejellyfinplayback.cpp index 213cc12..76d6e6b 100644 --- a/core/src/model/remotejellyfinplayback.cpp +++ b/core/src/model/remotejellyfinplayback.cpp @@ -20,7 +20,7 @@ #include <JellyfinQt/model/remotejellyfinplayback.h> #include <JellyfinQt/apiclient.h> -#include <JellyfinQt/dto/sessioninfo.h> +#include <JellyfinQt/dto/sessioninfodto.h> #include <JellyfinQt/loader/http/items.h> #include <JellyfinQt/loader/http/session.h> #include <JellyfinQt/loader/requesttypes.h> @@ -174,7 +174,7 @@ void RemoteJellyfinPlayback::onPositionTimerFired() { emit positionChanged(position()); } -void RemoteJellyfinPlayback::onSessionInfoUpdated(const QString &sessionId, const SessionInfo &sessionInfo) { +void RemoteJellyfinPlayback::onSessionInfoUpdated(const QString &sessionId, const SessionInfoDto &sessionInfo) { if (sessionId != m_sessionId) return; m_lastSessionInfo = sessionInfo; @@ -245,8 +245,7 @@ void RemoteJellyfinPlayback::sendGeneralCommand(DTO::GeneralCommandType command, using CommandLoader = Loader::HTTP::SendFullGeneralCommandLoader; Params params; - QSharedPointer<DTO::GeneralCommand> fullCommand = QSharedPointer<DTO::GeneralCommand>::create(command, m_apiClient.userId()); - fullCommand->setArguments(arguments); + QSharedPointer<DTO::GeneralCommand> fullCommand = QSharedPointer<DTO::GeneralCommand>::create(command, m_apiClient.userId(), arguments); params.setBody(fullCommand); params.setSessionId(m_sessionId); diff --git a/core/src/viewmodel/item.cpp b/core/src/viewmodel/item.cpp index 85a452f..cfa54fa 100644 --- a/core/src/viewmodel/item.cpp +++ b/core/src/viewmodel/item.cpp @@ -101,7 +101,7 @@ void Item::setUserData(DTO::UserItemDataDto &newData) { void Item::setUserData(QSharedPointer<DTO::UserItemDataDto> newData) { if (m_data.isNull() || m_data->userData().isNull()) { - m_userData->setData(QSharedPointer<DTO::UserData>::create(0, 0, false, false)); + m_userData->setData(QSharedPointer<DTO::UserData>::create(0, 0, false, false, QString(), QString())); } else { m_userData->setData(newData); } diff --git a/core/src/viewmodel/itemmodel.cpp b/core/src/viewmodel/itemmodel.cpp index 10c648b..24c2902 100644 --- a/core/src/viewmodel/itemmodel.cpp +++ b/core/src/viewmodel/itemmodel.cpp @@ -48,7 +48,7 @@ LatestMediaLoader::LatestMediaLoader(QObject *parent) : LatestMediaBase(new Jellyfin::Loader::HTTP::GetLatestMediaLoader(), parent){ } UserItemsLoader::UserItemsLoader(QObject *parent) - : UserItemsLoaderBase(new Jellyfin::Loader::HTTP::GetItemsByUserIdLoader(), parent) {} + : UserItemsLoaderBase(new Jellyfin::Loader::HTTP::GetItemsLoader(), parent) {} ResumeItemsLoader::ResumeItemsLoader(QObject *parent) : ResumeItemsLoaderBase(new Jellyfin::Loader::HTTP::GetResumeItemsLoader(), parent) {} diff --git a/core/src/viewmodel/userdata.cpp b/core/src/viewmodel/userdata.cpp index a4dda54..184b428 100644 --- a/core/src/viewmodel/userdata.cpp +++ b/core/src/viewmodel/userdata.cpp @@ -22,7 +22,7 @@ namespace Jellyfin { namespace ViewModel { UserData::UserData(QObject *parent) - : UserData(QSharedPointer<DTO::UserItemDataDto>::create(0, 0, false, false), parent) { + : UserData(QSharedPointer<DTO::UserItemDataDto>::create(0, 0, false, false, QString(), QString()), parent) { } @@ -30,13 +30,13 @@ UserData::UserData(QSharedPointer<DTO::UserItemDataDto> data, QObject *parent) : QObject(parent), m_data(data) { if (m_data.isNull()) { - m_data = QSharedPointer<DTO::UserItemDataDto>::create(0, 0, false, false); + m_data = QSharedPointer<DTO::UserItemDataDto>::create(0, 0, false, false, QString(), QString()); } } void UserData::setData(QSharedPointer<DTO::UserItemDataDto> data) { if (data.isNull()) { - m_data = QSharedPointer<DTO::UserItemDataDto>::create(0, 0, false, false); + m_data = QSharedPointer<DTO::UserItemDataDto>::create(0, 0, false, false, QString(), QString()); } else { m_data = data; } diff --git a/core/src/websocket.cpp b/core/src/websocket.cpp index bc7a99f..16e3eac 100644 --- a/core/src/websocket.cpp +++ b/core/src/websocket.cpp @@ -22,7 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include <JellyfinQt/dto/generalcommand.h> #include <JellyfinQt/dto/generalcommandtype.h> #include <JellyfinQt/dto/playstaterequest.h> -#include <JellyfinQt/dto/sessioninfo.h> +#include <JellyfinQt/dto/sessioninfodto.h> #include <JellyfinQt/dto/useritemdatadto.h> Q_LOGGING_CATEGORY(jellyfinWebSocket, "jellyfin.websocket"); @@ -157,7 +157,7 @@ void WebSocket::textMessageReceived(const QString &message) { } } else if (messageType == QStringLiteral("Sessions")) { try { - QList<DTO::SessionInfo> sessionInfoList = Support::fromJsonValue<QList<DTO::SessionInfo>>(data); + QList<DTO::SessionInfoDto> sessionInfoList = Support::fromJsonValue<QList<DTO::SessionInfoDto>>(data); for (auto it = sessionInfoList.cbegin(); it != sessionInfoList.cend(); it++) { emit m_apiClient->eventbus()->sessionInfoUpdated(it->jellyfinId(), *it); } diff --git a/qtquick/qml/ApiClient.qml b/qtquick/qml/ApiClient.qml index 643e0d3..0d3edbb 100644 --- a/qtquick/qml/ApiClient.qml +++ b/qtquick/qml/ApiClient.qml @@ -3,5 +3,6 @@ import QtQuick 2.12 import nl.netsoj.chris.Jellyfin 1.0 as J J.ApiClient { + appName: "Sailfin QtQuick" supportedCommands: [J.GeneralCommandType.Play, J.GeneralCommandType.DisplayContent, J.GeneralCommandType.DisplayMessage] } diff --git a/qtquick/qml/main.qml b/qtquick/qml/main.qml index cec66b3..7104305 100644 --- a/qtquick/qml/main.qml +++ b/qtquick/qml/main.qml @@ -110,7 +110,7 @@ ApplicationWindow { enabled: playbackManager.hasPrevious } Button { - readonly property bool _playing: playbackManager.playbackState === PlayerState.Playing + readonly property bool _playing: playbackManager.playbackState === J.PlayerState.Playing anchors.verticalCenter: parent.verticalCenter text: _playing ? "Pause" : "Play" onClicked: _playing ? playbackManager.pause() : playbackManager.play() diff --git a/qtquick/qml/pages/MainPage.qml b/qtquick/qml/pages/MainPage.qml index b58e33b..49d01e8 100644 --- a/qtquick/qml/pages/MainPage.qml +++ b/qtquick/qml/pages/MainPage.qml @@ -34,10 +34,21 @@ Page { Column { id: content width: parent.width - CheckBox { - checked: ApiClient.settings.allowTranscoding - text: "allow transcoding" - onCheckedChanged: ApiClient.settings.allowTranscoding = checked + Row { + CheckBox { + checked: ApiClient.settings.allowTranscoding + text: "allow transcoding" + onCheckedChanged: ApiClient.settings.allowTranscoding = checked + } + ComboBox { + model: J.RemoteDeviceList { + id: deviceList + apiClient: ApiClient + scanning: _modelsLoaded == true + } + textRole: "deviceName" + width: 400 + } } Repeater { model: mediaLibraryModel diff --git a/sailfish/qml/Utils.js b/sailfish/qml/Utils.js index 3b702fa..268b003 100644 --- a/sailfish/qml/Utils.js +++ b/sailfish/qml/Utils.js @@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ .pragma library +.import nl.netsoj.chris.Jellyfin 1.0 as J /** * Converts miliseconds to a h:mm:ss format @@ -89,54 +90,60 @@ function itemModelImageUrl(baseUrl, itemId, tag, type, options) { } function usePortraitCover(itemType) { - return ["Series", "Movie", "tvshows", "movies"].indexOf(itemType) >= 0 + var portraitTypes = [J.ItemType.Series, J.ItemType.Movie, J.CollectionType.Tvshows, J.CollectionType.Movies] + console.log("UsePortraitCover itemType: ", itemType) + return portraitTypes.indexOf(itemType) >= 0 } /** * Returns the page url for a certain item type. */ function getPageUrl(mediaType, itemType, isFolder) { - switch (itemType.toLowerCase()) { - case "series": + switch (itemType) { + case J.ItemType.Series: return Qt.resolvedUrl("pages/itemdetails/SeriesPage.qml") - case "movie": + case J.ItemType.Movie: return Qt.resolvedUrl("pages/itemdetails/FilmPage.qml") - case "collection": - case "photoalbum": + case J.ItemType.Folder: + case J.ItemType.PhotoAlbum: return Qt.resolvedUrl("pages/itemdetails/CollectionPage.qml") - case "season": + case J.ItemType.Season: return Qt.resolvedUrl("pages/itemdetails/SeasonPage.qml") - case "episode": + case J.ItemType.Episode: return Qt.resolvedUrl("pages/itemdetails/EpisodePage.qml") - case "musicartist": + case J.ItemType.MusicArtist: return Qt.resolvedUrl("pages/itemdetails/MusicArtistPage.qml") - case "musicalbum": - case "playlist": + case J.ItemType.MusicAlbum: + case J.ItemType.Playlist: return Qt.resolvedUrl("pages/itemdetails/MusicAlbumPage.qml") - case "photo": + case J.ItemType.Photo: return Qt.resolvedUrl("pages/itemdetails/PhotoPage.qml") - case "tvchannel": + case J.ItemType.TvChannel: return Qt.resolvedUrl("pages/itemdetails/LiveTvChannelPage.qml") - case "collectionfolder": + case J.ItemType.CollectionFolder: // TODO: support for other collection folders - switch(mediaType.toLowerCase()) { - case "music": + switch(mediaType) { + case J.CollectionType.Music: return Qt.resolvedUrl("pages/itemdetails/MusicLibraryPage.qml") } // FALLTRHOUGH default: - switch (mediaType ? mediaType.toLowerCase() : isFolder ? "folder" : "") { - case "livetv": - return Qt.resolvedUrl("pages/itemdetails/LiveTvChannelsPage.qml") - case "folder": - return Qt.resolvedUrl("pages/itemdetails/CollectionPage.qml") - case "video": - return Qt.resolvedUrl("pages/itemdetails/VideoPage.qml") - case "photo": - return Qt.resolvedUrl("pages/itemdetails/PhotoPage.qml") - default: - if (isFolder) return Qt.resolvedUrl("pages/itemdetails/CollectionPage.qml") - return Qt.resolvedUrl("pages/itemdetails/UnsupportedPage.qml") + if (isFolder) { + switch (mediaType) { + case J.CollectionType.Livetv: + return Qt.resolvedUrl("pages/itemdetails/LiveTvChannelsPage.qml") + default: + return Qt.resolvedUrl("pages/itemdetails/CollectionPage.qml") + } + } else { + switch (mediaType) { + case J.MediaType.Photo: + return Qt.resolvedUrl("pages/itemdetails/PhotoPage.qml") + case J.MediaType.Video: + return Qt.resolvedUrl("pages/itemdetails/VideoPage.qml") + default: + return Qt.resolvedUrl("pages/itemdetails/UnsupportedPage.qml") + } } } } diff --git a/sailfish/qml/components/ItemChildrenShowcase.qml b/sailfish/qml/components/ItemChildrenShowcase.qml index 93084e5..cc91a68 100644 --- a/sailfish/qml/components/ItemChildrenShowcase.qml +++ b/sailfish/qml/components/ItemChildrenShowcase.qml @@ -10,7 +10,7 @@ MoreSection { busy: itemModel.loader.status === J.ModelStatus.Loading || extraBusy property bool extraBusy: false property alias loader: itemModel.loader - property string collectionType + property int collectionType property bool collapseWhenEmpty: true J.ItemModel { diff --git a/sailfish/qml/components/PlaybackBar.qml b/sailfish/qml/components/PlaybackBar.qml index dcfc2e1..c4f1f32 100644 --- a/sailfish/qml/components/PlaybackBar.qml +++ b/sailfish/qml/components/PlaybackBar.qml @@ -158,7 +158,7 @@ PanelBackground { } switch(manager.item.mediaType) { - case "Audio": + case J.MediaType.Audio: var links = []; var items = manager.item.artistItems; console.log(items) @@ -181,7 +181,7 @@ PanelBackground { color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor linkColor: Theme.secondaryColor onLinkActivated: { - appWindow.navigateToItem(link, "Audio", "MusicArtist", true) + appWindow.navigateToItem(link, J.MediaType.Audio, J.ItemType.MusicArtist, true) } textFormat: Text.StyledText Icon { diff --git a/sailfish/qml/components/music/SongDelegate.qml b/sailfish/qml/components/music/SongDelegate.qml index 3d85e61..73ce4be 100644 --- a/sailfish/qml/components/music/SongDelegate.qml +++ b/sailfish/qml/components/music/SongDelegate.qml @@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ import QtQuick 2.6 import Sailfish.Silica 1.0 +import nl.netsoj.chris.Jellyfin 1.0 as J import "../.." @@ -106,7 +107,7 @@ ListItem { } function goToArtist(id) { - appWindow.navigateToItem(id, "audio", "MusicArtist", true) + appWindow.navigateToItem(id, J.MediaType.Audio, J.ItemType.MusicArtist, true) } Component { diff --git a/sailfish/qml/cover/CollectionPage.qml b/sailfish/qml/cover/CollectionPage.qml index b81178b..d90591d 100644 --- a/sailfish/qml/cover/CollectionPage.qml +++ b/sailfish/qml/cover/CollectionPage.qml @@ -41,7 +41,7 @@ CoverBackground { apiClient: appWindow.apiClient limit: cover.rowCount * 2 - 2 imageTypes: [J.ImageType.Primary] - sortBy: "IsFavoriteOrLiked,Random" + sortBy: [J.SortBy.IsFavoriteOrLiked, J.SortBy.Random] recursive: true parentId: itemId autoReload: false diff --git a/sailfish/qml/cover/PosterCover.qml b/sailfish/qml/cover/PosterCover.qml index 2bd769e..5972df0 100644 --- a/sailfish/qml/cover/PosterCover.qml +++ b/sailfish/qml/cover/PosterCover.qml @@ -38,7 +38,7 @@ CoverBackground { Shim { // Movies usually show their name on the poster, // so showing it here as well is a bit double - visible: mData.type !== "Movie" + visible: mData.type !== J.ItemType.Movie anchors { left: parent.left right: parent.right diff --git a/sailfish/qml/harbour-sailfin.qml b/sailfish/qml/harbour-sailfin.qml index 7651dcf..483f995 100644 --- a/sailfish/qml/harbour-sailfin.qml +++ b/sailfish/qml/harbour-sailfin.qml @@ -156,7 +156,7 @@ ApplicationWindow { } function navigateToItem(jellyfinId, mediaType, type, isFolder) { - if (mediaType === "Audio" && !isFolder) { + if (mediaType === MediaType.Audio && !isFolder) { playbackManager.playItemId(jellyfinId) } else { var url = Utils.getPageUrl(mediaType, type, isFolder) diff --git a/sailfish/qml/pages/AboutPage.qml b/sailfish/qml/pages/AboutPage.qml index 5099792..e8b0356 100644 --- a/sailfish/qml/pages/AboutPage.qml +++ b/sailfish/qml/pages/AboutPage.qml @@ -56,7 +56,7 @@ Page { "You can <a href=\"github\">view its source code on GitHub</a>. " + "Parts of the code of Sailfin are from other libraries. <a href='3rdparty'>View their licenses here</a>.</p>") .arg(apiClient.version) - .arg(2024) + .arg(2025) textFormat: Text.StyledText color: Theme.secondaryHighlightColor linkColor: Theme.primaryColor diff --git a/sailfish/qml/pages/MainPage.qml b/sailfish/qml/pages/MainPage.qml index 34f38a6..e619e09 100644 --- a/sailfish/qml/pages/MainPage.qml +++ b/sailfish/qml/pages/MainPage.qml @@ -109,13 +109,13 @@ Page { ItemChildrenShowcase { text: model.name onHeaderClicked: appWindow.navigateToItem(model.jellyfinId, model.collectionType, model.type, model.isFolder); - collectionType: model.collectionType || "" + collectionType: model.collectionType || 0 loader: J.LatestMediaLoader { apiClient: appWindow.apiClient parentId: jellyfinId } Binding on loader { - when: model.collectionType == "livetv" + when: model.collectionType == J.CollectionType.Livetv value: J.LiveTvChannelsLoader{ apiClient: appWindow.apiClient } diff --git a/sailfish/qml/pages/itemdetails/CollectionPage.qml b/sailfish/qml/pages/itemdetails/CollectionPage.qml index afb9104..8014477 100644 --- a/sailfish/qml/pages/itemdetails/CollectionPage.qml +++ b/sailfish/qml/pages/itemdetails/CollectionPage.qml @@ -39,7 +39,7 @@ BaseDetailPage { id: collectionLoader apiClient: appWindow.apiClient parentId: itemData.jellyfinId - sortBy: "SortName" + sortBy: [ J.SortBy.SortName ] autoReload: itemData.jellyfinId.length > 0 && (pageRoot.status == PageStatus.Active || _collectionModelLoaded) } } @@ -107,7 +107,7 @@ BaseDetailPage { id: itemImage anchors.fill: parent source: Utils.itemModelImageUrl(apiClient.baseUrl, model.jellyfinId, model.imageTags.Primary, "Primary", {"maxWidth": width}) - blurhash: model.imageBlurHashes.Primary[model.imageTags.Primary] + blurhash: model.imageBlurHashes.Primary !== undefined ? model.imageBlurHashes.Primary[model.imageTags.Primary] : undefined fallbackColor: Utils.colorFromString(model.name) fillMode: Image.PreserveAspectCrop clip: true @@ -152,8 +152,14 @@ BaseDetailPage { Component { id: sortPageComponent + Page { id: sortPage + readonly property var sortMap: { + "SortName": [J.SortBy.SortName], + "PlayCount": [J.SortBy.PlayCount], + "DateCreated": [J.SortBy.DateCreated] + }; ListModel { id: sortOptions @@ -183,19 +189,19 @@ BaseDetailPage { MenuItem { //: Sort order text: qsTr("Ascending") - onClicked: apply(model.value, "Ascending") + onClicked: apply(model.value, J.SortOrder.Ascending) } MenuItem { //: Sort order text: qsTr("Descending") - onClicked: apply(model.value, "Descending") + onClicked: apply(model.value, J.SortOrder.Descending) } } onClicked: openMenu() function apply(field, order) { - collectionModel.loader.sortBy = field; - collectionModel.loader.sortOrder = order; + collectionModel.loader.sortBy = sortMap[field]; + collectionModel.loader.sortOrder = [order]; collectionModel.loader.reload() pageStack.pop() } diff --git a/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml b/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml index cf8f618..4cecad9 100644 --- a/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml +++ b/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml @@ -51,7 +51,7 @@ BaseDetailPage { id: collectionModel loader: J.UserItemsLoader { apiClient: appWindow.apiClient - sortBy: itemData.type === "MusicAlbum" ? "ParentIndexNumber,IndexNumber,SortName" : "" + sortBy: itemData.type === J.ItemType.MusicAlbum ? [J.SortBy.ParentIndexNumber, J.SortBy.IndexNumber,J.SortBy.SortName] : [] fields: [J.ItemFields.ItemCounts, J.ItemFields.PrimaryImageAspectRatio] parentId: itemData.jellyfinId autoReload: itemData.jellyfinId.length > 0 diff --git a/sailfish/qml/pages/itemdetails/MusicArtistPage.qml b/sailfish/qml/pages/itemdetails/MusicArtistPage.qml index 48ce3e1..017630a 100644 --- a/sailfish/qml/pages/itemdetails/MusicArtistPage.qml +++ b/sailfish/qml/pages/itemdetails/MusicArtistPage.qml @@ -33,11 +33,11 @@ BaseDetailPage { id: albumsModel loader: J.UserItemsLoader { apiClient: appWindow.apiClient - sortBy: "PremiereDate,ProductionYear,SortName" - sortOrder: "Descending" + sortBy: [J.SortBy.PremiereDate, J.SortBy.ProductionYear, J.SortBy.SortName] + sortOrder: [J.SortOrder.Descending] fields: [J.ItemFields.ItemCounts, J.ItemFields.PrimaryImageAspectRatio] - includeItemTypes: ["MusicAlbum"] - albumArtistIds: itemData.jellyfinId + includeItemTypes: [J.ItemType.MusicAlbum] + albumArtistIds: [itemData.jellyfinId] recursive: true autoReload: itemData.jellyfinId.length > 0 limit: _maxItems @@ -48,10 +48,10 @@ BaseDetailPage { id: fullAlbumsModelComponent J.UserItemsLoader { apiClient: appWindow.apiClient - sortBy: "PremiereDate,ProductionYear,SortName" - sortOrder: "Descending" + sortBy: [J.SortBy.PremiereDate, J.SortBy.ProductionYear, J.SortBy.SortName] + sortOrder: [J.SortOrder.Descending] fields: [J.ItemFields.ItemCounts, J.ItemFields.PrimaryImageAspectRatio] - includeItemTypes: ["MusicAlbum"] + includeItemTypes: [J.ItemType.MusicAlbum] albumArtistIds: itemData.jellyfinId recursive: true autoReload: false @@ -62,10 +62,10 @@ BaseDetailPage { id: appearsOnModel loader: J.UserItemsLoader { apiClient: appWindow.apiClient - sortBy: "PremiereDate,ProductionYear,SortName" - sortOrder: "Descending" + sortBy: [J.SortBy.PremiereDate, J.SortBy.ProductionYear, J.SortBy.SortName] + sortOrder: [J.SortOrder.Descending] fields: [J.ItemFields.ItemCounts, J.ItemFields.PrimaryImageAspectRatio] - includeItemTypes: ["MusicAlbum"] + includeItemTypes: [J.ItemType.MusicAlbum] contributingArtistIds: itemData.jellyfinId recursive: true autoReload: itemData.jellyfinId.length > 0 @@ -76,10 +76,10 @@ BaseDetailPage { id: fullAppearsOnModelComponent J.UserItemsLoader { apiClient: appWindow.apiClient - sortBy: "PremiereDate,ProductionYear,SortName" - sortOrder: "Descending" + sortBy: [J.SortBy.PremiereDate, J.SortBy.ProductionYear, J.SortBy.SortName] + sortOrder: [J.SortOrder.Descending] fields: [J.ItemFields.ItemCounts, J.ItemFields.PrimaryImageAspectRatio] - includeItemTypes: ["MusicAlbum"] + includeItemTypes: [J.ItemType.MusicAlbum] contributingArtistIds: itemData.jellyfinId recursive: true autoReload: false diff --git a/sailfish/qml/pages/itemdetails/MusicLibraryPage.qml b/sailfish/qml/pages/itemdetails/MusicLibraryPage.qml index 965f488..7dbf95e 100644 --- a/sailfish/qml/pages/itemdetails/MusicLibraryPage.qml +++ b/sailfish/qml/pages/itemdetails/MusicLibraryPage.qml @@ -56,7 +56,7 @@ BaseDetailPage { J.LatestMediaLoader { apiClient: appWindow.apiClient parentId: itemData.jellyfinId - includeItemTypes: "Audio" + includeItemTypes: [J.ItemType.Audio] autoReload: false } } @@ -74,9 +74,9 @@ BaseDetailPage { J.UserItemsLoader { apiClient: appWindow.apiClient parentId: itemData.jellyfinId - includeItemTypes: "MusicAlbum" + includeItemTypes: [J.ItemType.MusicAlbum] recursive: true - sortBy: "SortName" + sortBy: [J.SortBy.SortName] autoReload: false } } @@ -85,9 +85,9 @@ BaseDetailPage { J.UserItemsLoader { apiClient: appWindow.apiClient parentId: itemData.jellyfinId - includeItemTypes: "Playlist" + includeItemTypes: [J.ItemType.Playlist] recursive: true - sortBy: "SortName" + sortBy: [J.SortBy.SortName] autoReload: false } } @@ -109,7 +109,7 @@ BaseDetailPage { apiClient: appWindow.apiClient parentId: itemData.jellyfinId autoReload: _firstTimeLoaded && itemData.jellyfinId.length > 0 - includeItemTypes: "Audio" + includeItemTypes: [J.ItemType.Audio] limit: 12 } onHeaderClicked: pageStack.push(Qt.resolvedUrl("CollectionPage.qml"), { @@ -128,9 +128,9 @@ BaseDetailPage { loader: J.UserItemsLoader { apiClient: appWindow.apiClient parentId: itemData.jellyfinId - includeItemTypes: "MusicAlbum" + includeItemTypes: [J.ItemType.MusicAlbum] autoReload: _firstTimeLoaded && itemData.jellyfinId.length > 0 - sortBy: "Random" + sortBy: [J.SortBy.Random] recursive: true limit: 12 } @@ -148,9 +148,9 @@ BaseDetailPage { loader: J.UserItemsLoader { apiClient: appWindow.apiClient parentId: itemData.jellyfinId - includeItemTypes: "Playlist" + includeItemTypes: [J.ItemType.Playlist] autoReload: _firstTimeLoaded && itemData.jellyfinId.length > 0 - sortBy: "Random" + sortBy: [J.SortBy.Random] recursive: true limit: 12 } diff --git a/sailfish/translations/harbour-sailfin-de.ts b/sailfish/translations/harbour-sailfin-de.ts index a20e489..62255ba 100644 --- a/sailfish/translations/harbour-sailfin-de.ts +++ b/sailfish/translations/harbour-sailfin-de.ts @@ -110,7 +110,7 @@ </message> <message> <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="90"/> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="169"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="175"/> <source>Sort by</source> <extracomment>Menu item for selecting the sort order of a collection</extracomment> <translation type="unfinished"></translation> @@ -126,28 +126,28 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="160"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="166"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="161"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="167"/> <source>Play count</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="162"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="168"/> <source>Date added</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="185"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="191"/> <source>Ascending</source> <extracomment>Sort order</extracomment> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="190"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="196"/> <source>Descending</source> <extracomment>Sort order</extracomment> <translation type="unfinished"></translation> @@ -268,14 +268,17 @@ <context> <name>LiveTvChannelPage</name> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="8"/> <source>%1 | %2 - %3</source> <translation type="unfinished"></translation> </message> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="14"/> <source>Program info</source> <translation type="unfinished"></translation> </message> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="19"/> <source>No program info available</source> <translation type="unfinished"></translation> </message> @@ -283,6 +286,7 @@ <context> <name>LiveTvChannelsPage</name> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelsPage.qml" line="80"/> <source>No program information available</source> <extracomment>Shown in the channel list when the name of the current program is unknown</extracomment> <translation type="unfinished"></translation> @@ -362,12 +366,12 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/MainPage.qml" line="136"/> + <location filename="../qml/pages/MainPage.qml" line="142"/> <source>Network error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/MainPage.qml" line="139"/> + <location filename="../qml/pages/MainPage.qml" line="145"/> <source>Pull down to retry again</source> <translation type="unfinished"></translation> </message> @@ -394,7 +398,7 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/MusicAlbumPage.qml" line="78"/> + <location filename="../qml/pages/itemdetails/MusicAlbumPage.qml" line="77"/> <source>Disc %1</source> <translation type="unfinished"></translation> </message> @@ -546,7 +550,7 @@ Page title for the list of all artists within the music library</extracomment> <context> <name>QObject</name> <message> - <location filename="../src/harbour-sailfin.cpp" line="53"/> + <location filename="../src/harbour-sailfin.cpp" line="54"/> <source>Sailfin</source> <extracomment>Application display name</extracomment> <translation type="unfinished"></translation> @@ -585,7 +589,7 @@ Placeholder text for textfield for entering the Quick Connect codeyy</extracomme <context> <name>SeasonPage</name> <message> - <location filename="../qml/pages/itemdetails/SeasonPage.qml" line="143"/> + <location filename="../qml/pages/itemdetails/SeasonPage.qml" line="138"/> <source>No overview available</source> <extracomment>No overview/summary text of an episode available</extracomment> <translation type="unfinished"></translation> @@ -674,13 +678,13 @@ Placeholder text for textfield for entering the Quick Connect codeyy</extracomme <context> <name>SongDelegate</name> <message> - <location filename="../qml/components/music/SongDelegate.qml" line="119"/> + <location filename="../qml/components/music/SongDelegate.qml" line="120"/> <source>Go to %1</source> <extracomment>Context menu item for navigating to the artist of the selected track</extracomment> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/components/music/SongDelegate.qml" line="122"/> + <location filename="../qml/components/music/SongDelegate.qml" line="123"/> <source>Go to artists</source> <extracomment>Context menu item for navigating to one of the artists of the selected track (opens submenu)</extracomment> <translation type="unfinished"></translation> @@ -801,7 +805,7 @@ This is still an alpha version :)</source> <context> <name>VideoPage</name> <message> - <location filename="../qml/pages/itemdetails/VideoPage.qml" line="57"/> + <location filename="../qml/pages/itemdetails/VideoPage.qml" line="58"/> <source>Run time: %2</source> <translation type="unfinished"></translation> </message> diff --git a/sailfish/translations/harbour-sailfin-ru.ts b/sailfish/translations/harbour-sailfin-ru.ts index a20e489..62255ba 100644 --- a/sailfish/translations/harbour-sailfin-ru.ts +++ b/sailfish/translations/harbour-sailfin-ru.ts @@ -110,7 +110,7 @@ </message> <message> <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="90"/> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="169"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="175"/> <source>Sort by</source> <extracomment>Menu item for selecting the sort order of a collection</extracomment> <translation type="unfinished"></translation> @@ -126,28 +126,28 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="160"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="166"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="161"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="167"/> <source>Play count</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="162"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="168"/> <source>Date added</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="185"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="191"/> <source>Ascending</source> <extracomment>Sort order</extracomment> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="190"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="196"/> <source>Descending</source> <extracomment>Sort order</extracomment> <translation type="unfinished"></translation> @@ -268,14 +268,17 @@ <context> <name>LiveTvChannelPage</name> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="8"/> <source>%1 | %2 - %3</source> <translation type="unfinished"></translation> </message> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="14"/> <source>Program info</source> <translation type="unfinished"></translation> </message> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="19"/> <source>No program info available</source> <translation type="unfinished"></translation> </message> @@ -283,6 +286,7 @@ <context> <name>LiveTvChannelsPage</name> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelsPage.qml" line="80"/> <source>No program information available</source> <extracomment>Shown in the channel list when the name of the current program is unknown</extracomment> <translation type="unfinished"></translation> @@ -362,12 +366,12 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/MainPage.qml" line="136"/> + <location filename="../qml/pages/MainPage.qml" line="142"/> <source>Network error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/MainPage.qml" line="139"/> + <location filename="../qml/pages/MainPage.qml" line="145"/> <source>Pull down to retry again</source> <translation type="unfinished"></translation> </message> @@ -394,7 +398,7 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/MusicAlbumPage.qml" line="78"/> + <location filename="../qml/pages/itemdetails/MusicAlbumPage.qml" line="77"/> <source>Disc %1</source> <translation type="unfinished"></translation> </message> @@ -546,7 +550,7 @@ Page title for the list of all artists within the music library</extracomment> <context> <name>QObject</name> <message> - <location filename="../src/harbour-sailfin.cpp" line="53"/> + <location filename="../src/harbour-sailfin.cpp" line="54"/> <source>Sailfin</source> <extracomment>Application display name</extracomment> <translation type="unfinished"></translation> @@ -585,7 +589,7 @@ Placeholder text for textfield for entering the Quick Connect codeyy</extracomme <context> <name>SeasonPage</name> <message> - <location filename="../qml/pages/itemdetails/SeasonPage.qml" line="143"/> + <location filename="../qml/pages/itemdetails/SeasonPage.qml" line="138"/> <source>No overview available</source> <extracomment>No overview/summary text of an episode available</extracomment> <translation type="unfinished"></translation> @@ -674,13 +678,13 @@ Placeholder text for textfield for entering the Quick Connect codeyy</extracomme <context> <name>SongDelegate</name> <message> - <location filename="../qml/components/music/SongDelegate.qml" line="119"/> + <location filename="../qml/components/music/SongDelegate.qml" line="120"/> <source>Go to %1</source> <extracomment>Context menu item for navigating to the artist of the selected track</extracomment> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/components/music/SongDelegate.qml" line="122"/> + <location filename="../qml/components/music/SongDelegate.qml" line="123"/> <source>Go to artists</source> <extracomment>Context menu item for navigating to one of the artists of the selected track (opens submenu)</extracomment> <translation type="unfinished"></translation> @@ -801,7 +805,7 @@ This is still an alpha version :)</source> <context> <name>VideoPage</name> <message> - <location filename="../qml/pages/itemdetails/VideoPage.qml" line="57"/> + <location filename="../qml/pages/itemdetails/VideoPage.qml" line="58"/> <source>Run time: %2</source> <translation type="unfinished"></translation> </message> diff --git a/sailfish/translations/harbour-sailfin.ts b/sailfish/translations/harbour-sailfin.ts index a20e489..62255ba 100644 --- a/sailfish/translations/harbour-sailfin.ts +++ b/sailfish/translations/harbour-sailfin.ts @@ -110,7 +110,7 @@ </message> <message> <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="90"/> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="169"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="175"/> <source>Sort by</source> <extracomment>Menu item for selecting the sort order of a collection</extracomment> <translation type="unfinished"></translation> @@ -126,28 +126,28 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="160"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="166"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="161"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="167"/> <source>Play count</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="162"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="168"/> <source>Date added</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="185"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="191"/> <source>Ascending</source> <extracomment>Sort order</extracomment> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="190"/> + <location filename="../qml/pages/itemdetails/CollectionPage.qml" line="196"/> <source>Descending</source> <extracomment>Sort order</extracomment> <translation type="unfinished"></translation> @@ -268,14 +268,17 @@ <context> <name>LiveTvChannelPage</name> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="8"/> <source>%1 | %2 - %3</source> <translation type="unfinished"></translation> </message> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="14"/> <source>Program info</source> <translation type="unfinished"></translation> </message> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelPage.qml" line="19"/> <source>No program info available</source> <translation type="unfinished"></translation> </message> @@ -283,6 +286,7 @@ <context> <name>LiveTvChannelsPage</name> <message> + <location filename="../qml/pages/itemdetails/LiveTvChannelsPage.qml" line="80"/> <source>No program information available</source> <extracomment>Shown in the channel list when the name of the current program is unknown</extracomment> <translation type="unfinished"></translation> @@ -362,12 +366,12 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/MainPage.qml" line="136"/> + <location filename="../qml/pages/MainPage.qml" line="142"/> <source>Network error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/MainPage.qml" line="139"/> + <location filename="../qml/pages/MainPage.qml" line="145"/> <source>Pull down to retry again</source> <translation type="unfinished"></translation> </message> @@ -394,7 +398,7 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/pages/itemdetails/MusicAlbumPage.qml" line="78"/> + <location filename="../qml/pages/itemdetails/MusicAlbumPage.qml" line="77"/> <source>Disc %1</source> <translation type="unfinished"></translation> </message> @@ -546,7 +550,7 @@ Page title for the list of all artists within the music library</extracomment> <context> <name>QObject</name> <message> - <location filename="../src/harbour-sailfin.cpp" line="53"/> + <location filename="../src/harbour-sailfin.cpp" line="54"/> <source>Sailfin</source> <extracomment>Application display name</extracomment> <translation type="unfinished"></translation> @@ -585,7 +589,7 @@ Placeholder text for textfield for entering the Quick Connect codeyy</extracomme <context> <name>SeasonPage</name> <message> - <location filename="../qml/pages/itemdetails/SeasonPage.qml" line="143"/> + <location filename="../qml/pages/itemdetails/SeasonPage.qml" line="138"/> <source>No overview available</source> <extracomment>No overview/summary text of an episode available</extracomment> <translation type="unfinished"></translation> @@ -674,13 +678,13 @@ Placeholder text for textfield for entering the Quick Connect codeyy</extracomme <context> <name>SongDelegate</name> <message> - <location filename="../qml/components/music/SongDelegate.qml" line="119"/> + <location filename="../qml/components/music/SongDelegate.qml" line="120"/> <source>Go to %1</source> <extracomment>Context menu item for navigating to the artist of the selected track</extracomment> <translation type="unfinished"></translation> </message> <message> - <location filename="../qml/components/music/SongDelegate.qml" line="122"/> + <location filename="../qml/components/music/SongDelegate.qml" line="123"/> <source>Go to artists</source> <extracomment>Context menu item for navigating to one of the artists of the selected track (opens submenu)</extracomment> <translation type="unfinished"></translation> @@ -801,7 +805,7 @@ This is still an alpha version :)</source> <context> <name>VideoPage</name> <message> - <location filename="../qml/pages/itemdetails/VideoPage.qml" line="57"/> + <location filename="../qml/pages/itemdetails/VideoPage.qml" line="58"/> <source>Run time: %2</source> <translation type="unfinished"></translation> </message>