1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-05-01 12:16:27 +00:00

Add track selection back

This commit is contained in:
Chris Josten 2021-08-23 01:46:57 +02:00
parent 5a24bdee59
commit 1aae311b9b
11 changed files with 306 additions and 57 deletions

View file

@ -17,6 +17,7 @@ set(JellyfinQt_SOURCES
src/viewmodel/item.cpp
src/viewmodel/itemmodel.cpp
src/viewmodel/loader.cpp
src/viewmodel/mediastream.cpp
src/viewmodel/modelstatus.cpp
src/viewmodel/playbackmanager.cpp
src/viewmodel/playlist.cpp
@ -47,6 +48,7 @@ set(JellyfinQt_HEADERS
include/JellyfinQt/viewmodel/item.h
include/JellyfinQt/viewmodel/itemmodel.h
include/JellyfinQt/viewmodel/loader.h
include/JellyfinQt/viewmodel/mediastream.h
include/JellyfinQt/viewmodel/modelstatus.h
include/JellyfinQt/viewmodel/propertyhelper.h
include/JellyfinQt/viewmodel/playbackmanager.h

View file

@ -43,6 +43,7 @@
#include "../loader/http/getitem.h"
#include "../loader/requesttypes.h"
#include "../model/item.h"
#include "mediastream.h"
#include "loader.h"
namespace Jellyfin {
@ -109,8 +110,11 @@ public:
Q_PROPERTY(QString seriesId READ seriesId NOTIFY seriesIdChanged)
Q_PROPERTY(QString seasonId READ seasonId NOTIFY seasonIdChanged)
Q_PROPERTY(QString seasonName READ seasonName NOTIFY seasonNameChanged)
/*Q_PROPERTY(QList<MediaStream *> __list__mediaStreams MEMBER __list__m_mediaStreams NOTIFY mediaStreamsChanged)
Q_PROPERTY(QVariantList mediaStreams READ mediaStreams NOTIFY mediaStreamsChanged STORED false)*/
/*Q_PROPERTY(QList<MediaStream *> __list__mediaStreams MEMBER __list__m_mediaStreams NOTIFY mediaStreamsChanged)*/
Q_PROPERTY(QList<QObject *> mediaStreams READ mediaStreams NOTIFY mediaStreamsChanged)
Q_PROPERTY(QList<QObject *> audioStreams READ audioStreams NOTIFY audioStreamsChanged)
Q_PROPERTY(QList<QObject *> videoStreams READ videoStreams NOTIFY videoStreamsChanged)
Q_PROPERTY(QList<QObject *> subtitleStreams READ subtitleStreams NOTIFY subtitleStreamsChanged)
Q_PROPERTY(QStringList artists READ artists NOTIFY artistsChanged)
// Why is this a QJsonObject? Well, because I couldn't be bothered to implement the deserialisations of
// a QHash at the moment.
@ -152,6 +156,10 @@ public:
QString seasonId() const { return m_data->seasonId(); }
QString seasonName() const { return m_data->seasonName(); }
QObjectList mediaStreams() const { return m_allMediaStreams; }
QObjectList audioStreams() const { return m_audioStreams; }
QObjectList videoStreams() const { return m_videoStreams; }
QObjectList subtitleStreams() const { return m_subtitleStreams; }
QStringList artists() const { return m_data->artists(); }
QJsonObject imageTags() const { return m_data->imageTags(); }
QStringList backdropImageTags() const { return m_data->backdropImageTags(); }
@ -208,7 +216,10 @@ signals:
void seriesIdChanged(const QString &newSeriesId);
void seasonIdChanged(const QString &newSeasonId);
void seasonNameChanged(const QString &newSeasonName);
void mediaStreamsChanged(/*const QList<MediaStream *> &newMediaStreams*/);
void mediaStreamsChanged(QVariantList &newMediaStreams);
void audioStreamsChanged(QVariantList &newAudioStreams);
void videoStreamsChanged(QVariantList &newVideoStreams);
void subtitleStreamsChanged(QVariantList &newSubtitleStreams);
void artistsChanged(const QStringList &newArtists);
void imageTagsChanged();
void backdropImageTagsChanged();
@ -219,9 +230,14 @@ signals:
protected:
void setUserData(DTO::UserItemDataDto &newData);
void setUserData(QSharedPointer<DTO::UserItemDataDto> newData);
void updateMediaStreams();
QSharedPointer<Model::Item> m_data;
UserData *m_userData = nullptr;
QObjectList m_allMediaStreams;
QObjectList m_audioStreams;
QObjectList m_videoStreams;
QObjectList m_subtitleStreams;
private slots:
void onUserDataChanged(const DTO::UserItemDataDto &userData);
};

View file

@ -34,7 +34,17 @@
#include "propertyhelper.h"
// Jellyfin Forward Read/Write Property
#define FWDPROP(type, propName, propSetName) JF_FWD_RW_PROP(type, propName, propSetName, this->m_parameters)
#define FWDPROP(type, propName, propSetName) \
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) { \
this->m_parameters.set##propSetName( newValue ); \
emit propName##Changed(); \
autoReloadIfNeeded(); \
} \
Q_SIGNALS: \
void propName##Changed();
namespace Jellyfin {
@ -161,6 +171,7 @@ public:
FWDPROP(bool, isPlaceHolder, IsPlaceHolder)
FWDPROP(bool, isPlayed, IsPlayed)
FWDPROP(bool, isUnaired, IsUnaired)
FWDPROP(int, limit, Limit)
FWDPROP(QList<Jellyfin::DTO::LocationTypeClass::Value>, locationTypes, LocationTypes)
FWDPROP(qint32, maxHeight, MaxHeight)
FWDPROP(QString, maxOfficialRating, MaxOfficialRating)

View file

@ -185,17 +185,22 @@ private:
* @brief Updates the data when finished.
*/
void onLoaderReady() {
R newData = m_loader->result();
if (m_dataViewModel->data()->sameAs(newData)) {
// Replace the data the model holds
m_dataViewModel->data()->replaceData(newData);
} else {
// Replace the model
using PointerType = typename decltype(m_dataViewModel->data())::Type;
m_dataViewModel = new T(this, QSharedPointer<PointerType>::create(newData, m_apiClient));
try {
R newData = m_loader->result();
if (m_dataViewModel->data()->sameAs(newData)) {
// Replace the data the model holds
m_dataViewModel->data()->replaceData(newData);
} else {
// Replace the model
using PointerType = typename decltype(m_dataViewModel->data())::Type;
m_dataViewModel = new T(this, QSharedPointer<PointerType>::create(newData, m_apiClient));
}
setStatus(Ready);
emitDataChanged();
} catch(QException &e) {
setErrorString(e.what());
setStatus(Error);
}
setStatus(Ready);
emitDataChanged();
}
void onLoaderError(QString message) {

View file

@ -0,0 +1,153 @@
/* * Sailfin: a Jellyfin client written using Qt
* Copyright (C) 2021 Chris Josten and the Sailfin Contributors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef JELLYFIN_VIEWMODEL_MEDIASTREAM_H
#define JELLYFIN_VIEWMODEL_MEDIASTREAM_H
#include <QObject>
#include <QScopedPointer>
#include "../dto/mediastream.h"
namespace Jellyfin {
namespace ViewModel {
class MediaStream : public QObject {
Q_OBJECT
public:
explicit MediaStream(QSharedPointer<DTO::MediaStream> data, QObject *parent = nullptr);
Q_PROPERTY(QString codec READ codec NOTIFY codecChanged)
Q_PROPERTY(QString codecTag READ codecTag NOTIFY codecTagChanged)
Q_PROPERTY(QString language READ language NOTIFY languageChanged)
Q_PROPERTY(QString colorRange READ colorRange NOTIFY colorRangeChanged)
Q_PROPERTY(QString colorSpace READ colorSpace NOTIFY colorSpaceChanged)
Q_PROPERTY(QString colorTransfer READ colorTransfer NOTIFY colorTransferChanged)
Q_PROPERTY(QString colorPrimaries READ colorPrimaries NOTIFY colorPrimariesChanged)
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(QString localizedUndefined READ localizedUndefined NOTIFY localizedUndefinedChanged);
Q_PROPERTY(QString localizedDefault READ localizedDefault NOTIFY localizedDefaultChanged);
Q_PROPERTY(QString localizedForced READ localizedForced NOTIFY localizedForcedChanged);
Q_PROPERTY(QString displayTitle READ displayTitle NOTIFY displayTitleChanged);
Q_PROPERTY(QString nalLengthSize READ nalLengthSize NOTIFY nalLengthSizeChanged);
Q_PROPERTY(bool interlaced READ interlaced NOTIFY interlacedChanged)
Q_PROPERTY(bool avc READ avc NOTIFY avcChanged)
Q_PROPERTY(QString channelLayout READ channelLayout NOTIFY channelLayoutChanged)
Q_PROPERTY(qint32 bitRate READ bitRate NOTIFY bitRateChanged)
Q_PROPERTY(qint32 bitDepth READ bitDepth NOTIFY bitDepthChanged)
Q_PROPERTY(qint32 refFrames READ refFrames NOTIFY refFramesChanged)
Q_PROPERTY(qint32 packetLength READ packetLength NOTIFY packetLengthChanged)
Q_PROPERTY(qint32 channels READ channels NOTIFY channelsChanged)
Q_PROPERTY(qint32 sampleRate READ sampleRate NOTIFY sampleRateChanged)
Q_PROPERTY(bool isDefault READ isDefault NOTIFY isDefaultChanged)
Q_PROPERTY(bool forced READ forced NOTIFY forcedChanged)
Q_PROPERTY(qint32 width READ width NOTIFY widthChanged)
Q_PROPERTY(qint32 height READ height NOTIFY heightChanged)
Q_PROPERTY(float averageFrameRate READ averageFrameRate NOTIFY averageFrameRateChanged)
Q_PROPERTY(float realFrameRate READ realFrameRate NOTIFY realFrameRateChanged)
Q_PROPERTY(QString profile READ profile NOTIFY profileChanged)
Q_PROPERTY(Jellyfin::DTO::MediaStreamTypeClass::Value type READ type NOTIFY typeChanged)
Q_PROPERTY(QString aspectRatio READ aspectRatio NOTIFY aspectRatioChanged)
Q_PROPERTY(qint32 index READ index NOTIFY indexChanged)
QString codec() const { return m_data->codec(); }
QString codecTag() const { return m_data->codecTag(); }
QString language() const { return m_data->language(); }
QString colorRange() const { return m_data->colorRange(); }
QString colorSpace() const { return m_data->colorSpace(); }
QString colorTransfer() const { return m_data->colorTransfer(); }
QString colorPrimaries() const { return m_data->colorPrimaries(); }
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(); }
QString localizedUndefined() const { return m_data->localizedUndefined(); }
QString localizedDefault() const { return m_data->localizedDefault(); }
QString localizedForced() const { return m_data->localizedForced(); }
QString displayTitle() const { return m_data->displayTitle(); }
QString nalLengthSize() const { return m_data->nalLengthSize(); }
bool interlaced() const { return m_data->isInterlaced(); }
bool avc() const { return m_data->isAVC().value_or(false); }
QString channelLayout() const { return m_data->channelLayout(); }
qint32 bitRate() const { return m_data->bitRate().value_or(-1); }
qint32 bitDepth() const { return m_data->bitDepth().value_or(-1); }
qint32 refFrames() const { return m_data->refFrames().value_or(-1); }
qint32 packetLength() const { return m_data->packetLength().value_or(-1); }
qint32 channels() const { return m_data->channels().value_or(-1); }
qint32 sampleRate() const { return m_data->sampleRate().value_or(-1); }
bool isDefault() const { return m_data->isDefault(); }
bool forced() const { return m_data->isForced(); }
qint32 width() const { return m_data->width().value_or(-1); }
qint32 height() const { return m_data->height().value_or(-1); }
float averageFrameRate() const { return m_data->averageFrameRate().value_or(-1.0); }
float realFrameRate() const { return m_data->realFrameRate().value_or(-1.0); }
QString profile() const { return m_data->profile(); }
DTO::MediaStreamType type() const { return m_data->type(); }
QString aspectRatio() const { return m_data->aspectRatio(); }
qint32 index() const { return m_data->index(); }
signals:
void codecChanged(const QString &newCodec);
void codecTagChanged(const QString &newCodecTag);
void languageChanged(const QString &newLanguage);
void colorRangeChanged(const QString &newColorRange);
void colorSpaceChanged(const QString &newColorSpace);
void colorTransferChanged(const QString &newColorTransfer);
void colorPrimariesChanged(const QString &newColorPrimaries);
void commentChanged(const QString &newComment);
void timeBaseChanged(const QString &newTimeBase);
void titleChanged(const QString &newTitle);
void videoRangeChanged(const QString &newVideoRanged);
void localizedUndefinedChanged(const QString &newLocalizedUndefined);
void localizedDefaultChanged(const QString &newLocalizedDefault);
void localizedForcedChanged(const QString &newLocalizedForced);
void displayTitleChanged(const QString &newDisplayTitle);
void nalLengthSizeChanged(const QString &newNalLengthSize);
void interlacedChanged(bool newInterlaced);
void avcChanged(bool newAVC);
void channelLayoutChanged(const QString &newChannelLayout);
void bitRateChanged(qint32 newBitRate);
void bitDepthChanged(qint32 newBitDepth);
void refFramesChanged(qint32 newRefFrames);
void packetLengthChanged(qint32 newPacketLength);
void channelsChanged(qint32 newChannels);
void sampleRateChanged(qint32 newSampleRate);
void isDefaultChanged(bool newIsDefault);
void forcedChanged(bool newForced);
void heightChanged(qint32 newHeight);
void widthChanged(qint32 newWidth);
void averageFrameRateChanged(float newAverageFrameRate);
void realFrameRateChanged(float newRealFrameRate);
void profileChanged(const QString &newProfile);
void typeChanged(const Jellyfin::DTO::MediaStreamTypeClass::Value newType);
void aspectRatioChanged(const QString &newAspectRatio);
void indexChanged(qint32 newIndex);
private:
QSharedPointer<DTO::MediaStream> m_data;
};
} // ViewModel
} // Jellyfin
#endif // JELLYFIN_VIEWMODEL_MEDIASTREAM_H

View file

@ -28,6 +28,7 @@ Item::Item(QObject *parent, QSharedPointer<Model::Item> data)
m_userData(new UserData(this)){
connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged);
m_userData->setData(data->userData());
updateMediaStreams();
}
void Item::setData(QSharedPointer<Model::Item> newData) {
@ -41,9 +42,39 @@ void Item::setData(QSharedPointer<Model::Item> newData) {
connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged);
setUserData(m_data->userData());
}
emit userDataChanged(m_userData);
}
void Item::updateMediaStreams() {
m_allMediaStreams.clear();
m_audioStreams.clear();
m_videoStreams.clear();
m_subtitleStreams.clear();
const QList<DTO::MediaStream> streams = m_data->mediaStreams();
for (auto it = streams.cbegin(); it != streams.cend(); it++) {
MediaStream *stream = new MediaStream(QSharedPointer<DTO::MediaStream>::create(*it), this);
m_allMediaStreams.append(stream);
switch(stream->type()) {
case DTO::MediaStreamType::Audio:
m_audioStreams.append(stream);
break;
case DTO::MediaStreamType::Video:
m_videoStreams.append(stream);
break;
case DTO::MediaStreamType::Subtitle:
m_subtitleStreams.append(stream);
break;
default:
break;
}
}
qDebug() << m_audioStreams.size() << " audio streams, " << m_videoStreams.size() << " video streams, "
<< m_subtitleStreams.size() << " subtitle streams, " << m_allMediaStreams.size() << " streams total";
}
void Item::setUserData(DTO::UserItemDataDto &newData) {
setUserData(QSharedPointer<DTO::UserItemDataDto>::create(newData));
}

View file

@ -0,0 +1,30 @@
/*
* Sailfin: a Jellyfin client written using Qt
* Copyright (C) 2021 Chris Josten and the Sailfin Contributors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "JellyfinQt/viewmodel/mediastream.h"
namespace Jellyfin {
namespace ViewModel {
MediaStream::MediaStream(QSharedPointer<DTO::MediaStream> data, QObject *parent)
: QObject(parent),
m_data(data) {}
}
}

View file

@ -286,6 +286,8 @@ void PlaybackManager::requestItemUrl(QSharedPointer<Model::Item> item) {
params.setEnableDirectPlay(true);
params.setEnableDirectStream(true);
params.setEnableTranscoding(true);
params.setAudioStreamIndex(this->m_audioIndex);
params.setSubtitleStreamIndex(this->m_subtitleIndex);
loader->setParameters(params);
connect(loader, &ItemUrlLoader::ready, [this, loader, item] {

View file

@ -19,13 +19,18 @@ 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
import nl.netsoj.chris.Jellyfin 1.0 as J
Column {
property var tracks
property var audioTracks
property var videoTracks
property var subtitleTracks
readonly property int videoTrack: videoSelector.currentItem ? videoSelector.currentItem._index : 0
readonly property int audioTrack: audioSelector.currentItem ? audioSelector.currentItem._index : 0
readonly property int subtitleTrack: subitleSelector.currentItem._index
ListModel {
id: videoModel
}
@ -40,13 +45,13 @@ Column {
ComboBox {
id: videoSelector
label: qsTr("Video track")
enabled: videoModel.count > 1
enabled: videoTracks.length > 1
menu: ContextMenu {
Repeater {
model: videoModel
model: videoTracks
MenuItem {
readonly property int _index: model.index
text: model.displayTitle
readonly property int _index: modelData.index
text: modelData.displayTitle
}
}
}
@ -55,13 +60,13 @@ Column {
ComboBox {
id: audioSelector
label: qsTr("Audio track")
enabled: audioModel.count > 1
enabled: audioTracks.length > 1
menu: ContextMenu {
Repeater {
model: audioModel
model: audioTracks
MenuItem {
readonly property int _index: model.index
text: model.displayTitle
readonly property int _index: modelData.index
text: modelData.displayTitle
}
}
}
@ -70,7 +75,7 @@ Column {
ComboBox {
id: subitleSelector
label: qsTr("Subtitle track")
enabled: subtitleModel.count > 0
enabled: subtitleTracks.length> 0
menu: ContextMenu {
MenuItem {
readonly property int _index: -1
@ -78,39 +83,12 @@ Column {
text: qsTr("Off")
}
Repeater {
model: subtitleModel
model: subtitleTracks
MenuItem {
readonly property int _index: model.index
text: model.displayTitle
readonly property int _index: modelData.index
text: modelData.displayTitle
}
}
}
}
onTracksChanged: {
audioModel.clear()
subtitleModel.clear()
if (typeof tracks === "undefined") {
console.log("tracks undefined")
return
}
console.log(tracks)
for(var i = 0; i < tracks.length; i++) {
var track = tracks[i];
switch(track.type) {
case MediaStream.Video:
videoModel.append(track)
break;
case MediaStream.Audio:
audioModel.append(track)
break;
case MediaStream.Subtitle:
subtitleModel.append(track)
break;
default:
console.log("Ignored " + track.displayTitle + "(" + track.type + ")")
break;
}
}
}
}

View file

@ -26,8 +26,9 @@ Rectangle {
id: videoError
//FIXME: Once QTBUG-10822 is resolved, change to J.PlaybackManager
property var player
property bool showError: false
color: pal.palette.overlayBackgroundColor
opacity: player.error === MediaPlayer.NoError ? 0.0 : 1.0
opacity: showError ? 1.0 : 0.0
Behavior on opacity { FadeAnimator {} }
SilicaItem {
@ -88,6 +89,20 @@ Rectangle {
text: qsTr("Retry")
onClicked: player.play()
}
Button {
text: qsTr("Hide")
onClicked: showError = false
}
}
}
Connections {
target: player
onErrorChanged: {
if (player.error !== MediaPlayer.NoError) {
showError = true
}
}
}

View file

@ -76,7 +76,13 @@ BaseDetailPage {
VideoTrackSelector {
id: trackSelector
width: parent.width
tracks: itemData.mediaStreams
audioTracks: itemData.audioStreams
videoTracks: itemData.videoStreams
subtitleTracks: itemData.subtitleStreams
}
Label {
text: "Video %1, audio %2, subtitle %3".arg(trackSelector.videoTrack).arg(trackSelector.audioTrack).arg(trackSelector.subtitleTrack)
}
}
}