From: APTX Date: Sun, 26 Mar 2017 12:24:43 +0000 (+0200) Subject: Implement track switching X-Git-Url: https://gitweb.tyo.aptx.org/?a=commitdiff_plain;h=d224d5f7adbcad572743620af1955c8e8f171d31;p=aniplayer.git Implement track switching Some functions initially added for this were renamed as they used "stream" instead of "track" in the name. The default UI uses combo boxes to select the tracks. These work terribly in fullscreen mode and will have to be changed. --- diff --git a/backendplugins/backend_mpv/backendmpv.cpp b/backendplugins/backend_mpv/backendmpv.cpp index 0c6c28c..ce5d4ff 100644 --- a/backendplugins/backend_mpv/backendmpv.cpp +++ b/backendplugins/backend_mpv/backendmpv.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -58,6 +59,18 @@ MpvInstance::MpvInstance(PlayerPluginInterface *playerInterface, qCDebug(mpvBackend) << "register" << VOLUME << mpv_observe_property(m_handle, 0, VOLUME, MPV_FORMAT_DOUBLE); + qCDebug(mpvBackend) << "register track-list" + << mpv_observe_property(m_handle, 0, "track-list", + MPV_FORMAT_NODE); + qCDebug(mpvBackend) << "register vid" + << mpv_observe_property(m_handle, 0, "vid", + MPV_FORMAT_STRING); + qCDebug(mpvBackend) << "register aid" + << mpv_observe_property(m_handle, 0, "aid", + MPV_FORMAT_STRING); + qCDebug(mpvBackend) << "register sid" + << mpv_observe_property(m_handle, 0, "sid", + MPV_FORMAT_STRING); qCDebug(mpvBackend) << "register chapter-list" << mpv_observe_property(m_handle, 0, "chapter-list", MPV_FORMAT_NODE); @@ -127,6 +140,30 @@ void MpvInstance::setVolume(Volume volume) { } } +void MpvInstance::setCurrentVideoStream(TrackIndex track) { + qint64 tmp = track; + if (track < 0) + mpv_set_property_string(m_handle, "vid", "no"); + else + mpv_set_property(m_handle, "vid", MPV_FORMAT_INT64, &tmp); +} + +void MpvInstance::setCurrentAudioStream(TrackIndex track) { + qint64 tmp = track; + if (track < 0) + mpv_set_property_string(m_handle, "aid", "no"); + else + mpv_set_property(m_handle, "aid", MPV_FORMAT_INT64, &tmp); +} + +void MpvInstance::setCurrentSubtitleStream(TrackIndex track) { + qint64 tmp = track; + if (track < 0) + mpv_set_property_string(m_handle, "sid", "no"); + else + mpv_set_property(m_handle, "sid", MPV_FORMAT_INT64, &tmp); +} + template struct MpvProperty; template <> struct MpvProperty { @@ -171,11 +208,30 @@ template <> struct MpvProperty { } }; +template <> struct MpvProperty { + static char *read(struct mpv_event_property *property) { + Q_ASSERT(property->format == MPV_FORMAT_STRING); + if (!property->data) + qWarning("Property data data is null"); + return *static_cast(property->data); + } +}; + template decltype(auto) readProperty(struct mpv_event_property *property) { return MpvProperty::read(property); } +int MpvInstance::readTrackIndex(struct mpv_event_property *property) { + const auto str = readProperty(property); + qCDebug(mpvBackend).nospace() << "Trying to read id from >" << str << "<"; + + const auto qstr = QString::fromUtf8(str); + bool ok = false; + const int id = qstr.toInt(&ok); + return ok ? id : NoTrack; +} + void MpvInstance::processMpvEvents() { struct mpv_event *event = nullptr; do { @@ -219,6 +275,47 @@ void MpvInstance::processMpvEvents() { static_cast( readProperty(property) / 100.0)); } + } else if (strcmp(property->name, "track-list") == 0) { + const auto node = readProperty(property); + const auto variant = mpv::qt::node_to_variant(node); + qCDebug(mpvBackend) << "TRACKS" << variant; + + PlayerPluginInterface::TrackList videoTracks; + PlayerPluginInterface::TrackList audioTracks; + PlayerPluginInterface::TrackList subtitleTracks; + for (const auto &v : variant.toList()) { + const auto &map = v.toMap(); + const auto &type = map["type"].toString(); + auto &tracks = [&]() -> PlayerPluginInterface::TrackList & { + if (type == "video") { + return videoTracks; + } + if (type == "audio") { + return audioTracks; + } + return subtitleTracks; + }(); + const auto track = PlayerPluginInterface::Track{ + map["title"].toString(), map["lang"].toString(), + map["id"].toInt()}; + tracks << track; + } + m_player->backendVideoTracksChanged(videoTracks); + m_player->backendAudioTracksChanged(audioTracks); + m_player->backendSubtitleTracksChanged(subtitleTracks); + + } else if (strcmp(property->name, "vid") == 0) { + const int trackIndex = readTrackIndex(property); + m_player->backendCurrentVideoTrackChanged(trackIndex); + + } else if (strcmp(property->name, "aid") == 0) { + const int trackIndex = readTrackIndex(property); + m_player->backendCurrentAudioTrackChanged(trackIndex); + + } else if (strcmp(property->name, "sid") == 0) { + const int trackIndex = readTrackIndex(property); + m_player->backendCurrentSubtitleTrackChanged(trackIndex); + } else if (strcmp(property->name, "chapter-list") == 0) { const auto node = readProperty(property); const auto variant = mpv::qt::node_to_variant(node); @@ -339,7 +436,7 @@ void VideoRendererMpv::render(QOpenGLFramebufferObject *fbo) { } #ifdef Q_OS_WIN -#include +#include #endif void *VideoRendererMpv::getProcAddress(void *, const char *name) { diff --git a/backendplugins/backend_mpv/backendmpv.h b/backendplugins/backend_mpv/backendmpv.h index 98627c5..ab4f0e9 100644 --- a/backendplugins/backend_mpv/backendmpv.h +++ b/backendplugins/backend_mpv/backendmpv.h @@ -9,6 +9,7 @@ struct mpv_handle; struct mpv_opengl_cb_context; +struct mpv_event_property; class BACKEND_MPVSHARED_EXPORT BackendMpv : public QObject, public BackendPluginBase { @@ -26,6 +27,10 @@ public: class BACKEND_MPVSHARED_EXPORT MpvInstance : public QObject, public BackendInstance { Q_OBJECT + + static constexpr const TrackIndex DefaultTrack = -2; + static constexpr const TrackIndex NoTrack = -1; + public: MpvInstance(PlayerPluginInterface *, QObject *parent = nullptr); virtual ~MpvInstance() override; @@ -41,7 +46,13 @@ public: void setVolume(Volume) override; + void setCurrentVideoStream(TrackIndex) override; + void setCurrentAudioStream(TrackIndex) override; + void setCurrentSubtitleStream(TrackIndex) override; + private: + int readTrackIndex(struct mpv_event_property *); + PlayerPluginInterface *m_player = nullptr; mpv_handle *m_handle = nullptr; Volume m_volumeToSet = -1; diff --git a/backendplugins/backend_null/backendnull.cpp b/backendplugins/backend_null/backendnull.cpp index 08efa51..f1656d1 100644 --- a/backendplugins/backend_null/backendnull.cpp +++ b/backendplugins/backend_null/backendnull.cpp @@ -31,3 +31,18 @@ VideoRendererBase *NullInstance::createRenderer(VideoUpdateInterface *) { void NullInstance::seek(TimeStamp) {} void NullInstance::setVolume(Volume) {} + +void NullInstance::setCurrentVideoStream(BackendInstance::TrackIndex) +{ + +} + +void NullInstance::setCurrentAudioStream(BackendInstance::TrackIndex) +{ + +} + +void NullInstance::setCurrentSubtitleStream(BackendInstance::TrackIndex) +{ + +} diff --git a/backendplugins/backend_null/backendnull.h b/backendplugins/backend_null/backendnull.h index f16cefb..d345fea 100644 --- a/backendplugins/backend_null/backendnull.h +++ b/backendplugins/backend_null/backendnull.h @@ -36,6 +36,11 @@ public: void setVolume(Volume) override; + void setCurrentVideoStream(TrackIndex) override; + void setCurrentAudioStream(TrackIndex) override; + void setCurrentSubtitleStream(TrackIndex) override; + + VideoRendererBase *createRenderer(VideoUpdateInterface *) override; private: diff --git a/core/chaptermodel.h b/core/chaptermodel.h index 95d31fc..c2f7f44 100644 --- a/core/chaptermodel.h +++ b/core/chaptermodel.h @@ -1,9 +1,10 @@ #ifndef CHAPTERMODEL_H #define CHAPTERMODEL_H -#include "aniplayer/playerplugininterface.h" #include +#include "aniplayer/playerplugininterface.h" + class ChapterModel : public QAbstractListModel { public: enum ChapterRoles { @@ -12,7 +13,7 @@ public: EndTimeRole }; - ChapterModel(QObject *parent = nullptr); + explicit ChapterModel(QObject *parent = nullptr); void setChapters(const PlayerPluginInterface::ChapterList &); void setDuration(PlayerPluginInterface::TimeStamp duration); diff --git a/core/core.pro b/core/core.pro index e971c2c..d5f8515 100644 --- a/core/core.pro +++ b/core/core.pro @@ -12,6 +12,7 @@ SOURCES += main.cpp \ videoelement.cpp \ instancemanager.cpp \ settings.cpp \ + trackmodel.cpp \ chaptermodel.cpp HEADERS += \ @@ -24,6 +25,7 @@ HEADERS += \ videoelement.h \ instancemanager.h \ settings.h \ + trackmodel.h \ chaptermodel.h include(qtsingleapplication/qtsingleapplication.pri) diff --git a/core/include/aniplayer/backendpluginbase.h b/core/include/aniplayer/backendpluginbase.h index d7aeaf3..56d6728 100644 --- a/core/include/aniplayer/backendpluginbase.h +++ b/core/include/aniplayer/backendpluginbase.h @@ -7,6 +7,7 @@ class QUrl; class BackendInstance { public: + using TrackIndex = int; // In seconds using TimeStamp = double; // Volume valid range is 0.0-1.0 @@ -24,6 +25,10 @@ public: virtual void seek(TimeStamp) = 0; virtual void setVolume(Volume) = 0; + + virtual void setCurrentVideoStream(TrackIndex) = 0; + virtual void setCurrentAudioStream(TrackIndex) = 0; + virtual void setCurrentSubtitleStream(TrackIndex) = 0; }; class BackendPluginBase { diff --git a/core/include/aniplayer/backendpluginbase.h.X24088 b/core/include/aniplayer/backendpluginbase.h.X24088 new file mode 100644 index 0000000..56d6728 --- /dev/null +++ b/core/include/aniplayer/backendpluginbase.h.X24088 @@ -0,0 +1,47 @@ +#ifndef BACKENDPLUGINBASE_H +#define BACKENDPLUGINBASE_H + +#include "playerplugininterface.h" + +class QUrl; + +class BackendInstance { +public: + using TrackIndex = int; + // In seconds + using TimeStamp = double; + // Volume valid range is 0.0-1.0 + using Volume = double; + virtual ~BackendInstance() = default; + + virtual VideoRendererBase *createRenderer(VideoUpdateInterface *) = 0; + + virtual bool open(const QUrl &resource) = 0; + + virtual void play() = 0; + virtual void pause() = 0; + virtual void stop() = 0; + + virtual void seek(TimeStamp) = 0; + + virtual void setVolume(Volume) = 0; + + virtual void setCurrentVideoStream(TrackIndex) = 0; + virtual void setCurrentAudioStream(TrackIndex) = 0; + virtual void setCurrentSubtitleStream(TrackIndex) = 0; +}; + +class BackendPluginBase { +public: + virtual ~BackendPluginBase() = default; + + virtual BackendInstance *createInstance(PlayerPluginInterface *) = 0; +}; + +#define ANIPLAYER_BACKEND_DPLUGIN_INTERFACE_IID \ + "org.aptx.aniplayer.BackendPluginInterface" + +#include +Q_DECLARE_INTERFACE(BackendPluginBase, ANIPLAYER_BACKEND_DPLUGIN_INTERFACE_IID) + +#endif // BACKENDPLUGINBASE_H diff --git a/core/include/aniplayer/playerplugininterface.h b/core/include/aniplayer/playerplugininterface.h index fe7c12e..1f7b70a 100644 --- a/core/include/aniplayer/playerplugininterface.h +++ b/core/include/aniplayer/playerplugininterface.h @@ -15,6 +15,7 @@ public: class PlayerPluginInterface { public: + using TrackIndex = int; using TimeStamp = double; using Volume = double; @@ -45,7 +46,19 @@ public: virtual void playbackVolumeChanged(Volume) = 0; virtual void playbackMaxVolumeChanged(Volume) = 0; - virtual void streamsChanged() = 0; + struct Track { + QString title; + QString language; + int id; + }; + using TrackList = QList; + virtual void backendVideoTracksChanged(const TrackList &) = 0; + virtual void backendAudioTracksChanged(const TrackList &) = 0; + virtual void backendSubtitleTracksChanged(const TrackList &) = 0; + + virtual void backendCurrentVideoTrackChanged(TrackIndex) = 0; + virtual void backendCurrentAudioTrackChanged(TrackIndex) = 0; + virtual void backendCurrentSubtitleTrackChanged(TrackIndex) = 0; struct Chapter { QString title; diff --git a/core/include/aniplayer/playerplugininterface.h.Q75208 b/core/include/aniplayer/playerplugininterface.h.Q75208 new file mode 100644 index 0000000..9ff567d --- /dev/null +++ b/core/include/aniplayer/playerplugininterface.h.Q75208 @@ -0,0 +1,82 @@ +#ifndef PLAYERPLUGININTERFACE_H +#define PLAYERPLUGININTERFACE_H + +#include +#include + +class QOpenGLFramebufferObject; + +class VideoUpdateInterface { +public: + virtual ~VideoUpdateInterface() = default; + + virtual void videoUpdated() = 0; +}; + +class PlayerPluginInterface { +public: + using TimeStamp = double; + using Volume = double; + + enum class PlayState { Stopped, Paused, Playing }; + /* + * .-----. + * | | Error + * v | + * Stopped -'<--+<-------. + * | ^ | + * | Load | Error | + * v | | + * Paused<------+ | File End + * | ^ | + * | Play | Pause | + * v | | + * Playing------+--------' + */ + + virtual ~PlayerPluginInterface() = default; + + virtual void backendReadyToPlay() = 0; + + virtual void backendSourceChanged(QUrl source) = 0; + virtual void playStateChanged(PlayState) = 0; + virtual void playbackDurationChanged(TimeStamp) = 0; + virtual void playbackPositionChanged(TimeStamp) = 0; + virtual void playbackVolumeChanged(Volume) = 0; + virtual void playbackMaxVolumeChanged(Volume) = 0; + + struct Track { + QString title; + QString language; + int id; + }; + using TrackList = QList; + virtual void backendVideoTracksChanged(const TrackList &) = 0; + virtual void backendAudioTracksChanged(const TrackList &) = 0; + virtual void backendSubtitleTracksChanged(const TrackList &) = 0; + + struct Chapter { + QString title; + TimeStamp startTime; + }; + using ChapterList = QList; + virtual void backendChaptersChanged(const ChapterList &chapters) = 0; +}; + +class PlayerRendererInterface { +public: + virtual ~PlayerRendererInterface() = default; + virtual void rendererSinkSet(VideoUpdateInterface *) = 0; + virtual void rendererReady() = 0; +}; + +class VideoRendererBase { +public: + VideoRendererBase() = default; + VideoRendererBase(const VideoRendererBase &) = delete; + VideoRendererBase &operator=(const VideoRendererBase &) = delete; + virtual ~VideoRendererBase() = default; + virtual void render(QOpenGLFramebufferObject *) = 0; +}; + +#endif // PLAYERPLUGININTERFACE_H diff --git a/core/include/aniplayer/playerplugininterface.h.X13004 b/core/include/aniplayer/playerplugininterface.h.X13004 new file mode 100644 index 0000000..1f7b70a --- /dev/null +++ b/core/include/aniplayer/playerplugininterface.h.X13004 @@ -0,0 +1,87 @@ +#ifndef PLAYERPLUGININTERFACE_H +#define PLAYERPLUGININTERFACE_H + +#include +#include + +class QOpenGLFramebufferObject; + +class VideoUpdateInterface { +public: + virtual ~VideoUpdateInterface() = default; + + virtual void videoUpdated() = 0; +}; + +class PlayerPluginInterface { +public: + using TrackIndex = int; + using TimeStamp = double; + using Volume = double; + + enum class PlayState { Stopped, Paused, Playing }; + /* + * .-----. + * | | Error + * v | + * Stopped -'<--+<-------. + * | ^ | + * | Load | Error | + * v | | + * Paused<------+ | File End + * | ^ | + * | Play | Pause | + * v | | + * Playing------+--------' + */ + + virtual ~PlayerPluginInterface() = default; + + virtual void backendReadyToPlay() = 0; + + virtual void backendSourceChanged(QUrl source) = 0; + virtual void playStateChanged(PlayState) = 0; + virtual void playbackDurationChanged(TimeStamp) = 0; + virtual void playbackPositionChanged(TimeStamp) = 0; + virtual void playbackVolumeChanged(Volume) = 0; + virtual void playbackMaxVolumeChanged(Volume) = 0; + + struct Track { + QString title; + QString language; + int id; + }; + using TrackList = QList; + virtual void backendVideoTracksChanged(const TrackList &) = 0; + virtual void backendAudioTracksChanged(const TrackList &) = 0; + virtual void backendSubtitleTracksChanged(const TrackList &) = 0; + + virtual void backendCurrentVideoTrackChanged(TrackIndex) = 0; + virtual void backendCurrentAudioTrackChanged(TrackIndex) = 0; + virtual void backendCurrentSubtitleTrackChanged(TrackIndex) = 0; + + struct Chapter { + QString title; + TimeStamp startTime; + }; + using ChapterList = QList; + virtual void backendChaptersChanged(const ChapterList &chapters) = 0; +}; + +class PlayerRendererInterface { +public: + virtual ~PlayerRendererInterface() = default; + virtual void rendererSinkSet(VideoUpdateInterface *) = 0; + virtual void rendererReady() = 0; +}; + +class VideoRendererBase { +public: + VideoRendererBase() = default; + VideoRendererBase(const VideoRendererBase &) = delete; + VideoRendererBase &operator=(const VideoRendererBase &) = delete; + virtual ~VideoRendererBase() = default; + virtual void render(QOpenGLFramebufferObject *) = 0; +}; + +#endif // PLAYERPLUGININTERFACE_H diff --git a/core/include/aniplayer/playerplugininterface.h.e74088 b/core/include/aniplayer/playerplugininterface.h.e74088 new file mode 100644 index 0000000..48aa5e6 --- /dev/null +++ b/core/include/aniplayer/playerplugininterface.h.e74088 @@ -0,0 +1,79 @@ +#ifndef PLAYERPLUGININTERFACE_H +#define PLAYERPLUGININTERFACE_H + +#include +#include + +class QOpenGLFramebufferObject; + +class VideoUpdateInterface { +public: + virtual ~VideoUpdateInterface() = default; + + virtual void videoUpdated() = 0; +}; + +class PlayerPluginInterface { +public: + using TimeStamp = double; + using Volume = double; + + enum class PlayState { Stopped, Paused, Playing }; + /* + * .-----. + * | | Error + * v | + * Stopped -'<--+<-------. + * | ^ | + * | Load | Error | + * v | | + * Paused<------+ | File End + * | ^ | + * | Play | Pause | + * v | | + * Playing------+--------' + */ + + virtual ~PlayerPluginInterface() = default; + + virtual void backendReadyToPlay() = 0; + + virtual void backendSourceChanged(QUrl source) = 0; + virtual void playStateChanged(PlayState) = 0; + virtual void playbackDurationChanged(TimeStamp) = 0; + virtual void playbackPositionChanged(TimeStamp) = 0; + virtual void playbackVolumeChanged(Volume) = 0; + virtual void playbackMaxVolumeChanged(Volume) = 0; + + struct Track { + QString title; + QString language; + }; + using TrackList = QList; + virtual void backendTracksChanged(const TrackList &tracks) = 0; + + struct Chapter { + QString title; + TimeStamp startTime; + }; + using ChapterList = QList; + virtual void backendChaptersChanged(const ChapterList &chapters) = 0; +}; + +class PlayerRendererInterface { +public: + virtual ~PlayerRendererInterface() = default; + virtual void rendererSinkSet(VideoUpdateInterface *) = 0; + virtual void rendererReady() = 0; +}; + +class VideoRendererBase { +public: + VideoRendererBase() = default; + VideoRendererBase(const VideoRendererBase &) = delete; + VideoRendererBase &operator=(const VideoRendererBase &) = delete; + virtual ~VideoRendererBase() = default; + virtual void render(QOpenGLFramebufferObject *) = 0; +}; + +#endif // PLAYERPLUGININTERFACE_H diff --git a/core/include/aniplayer/playerplugininterface.h.i13004 b/core/include/aniplayer/playerplugininterface.h.i13004 new file mode 100644 index 0000000..1f7b70a --- /dev/null +++ b/core/include/aniplayer/playerplugininterface.h.i13004 @@ -0,0 +1,87 @@ +#ifndef PLAYERPLUGININTERFACE_H +#define PLAYERPLUGININTERFACE_H + +#include +#include + +class QOpenGLFramebufferObject; + +class VideoUpdateInterface { +public: + virtual ~VideoUpdateInterface() = default; + + virtual void videoUpdated() = 0; +}; + +class PlayerPluginInterface { +public: + using TrackIndex = int; + using TimeStamp = double; + using Volume = double; + + enum class PlayState { Stopped, Paused, Playing }; + /* + * .-----. + * | | Error + * v | + * Stopped -'<--+<-------. + * | ^ | + * | Load | Error | + * v | | + * Paused<------+ | File End + * | ^ | + * | Play | Pause | + * v | | + * Playing------+--------' + */ + + virtual ~PlayerPluginInterface() = default; + + virtual void backendReadyToPlay() = 0; + + virtual void backendSourceChanged(QUrl source) = 0; + virtual void playStateChanged(PlayState) = 0; + virtual void playbackDurationChanged(TimeStamp) = 0; + virtual void playbackPositionChanged(TimeStamp) = 0; + virtual void playbackVolumeChanged(Volume) = 0; + virtual void playbackMaxVolumeChanged(Volume) = 0; + + struct Track { + QString title; + QString language; + int id; + }; + using TrackList = QList; + virtual void backendVideoTracksChanged(const TrackList &) = 0; + virtual void backendAudioTracksChanged(const TrackList &) = 0; + virtual void backendSubtitleTracksChanged(const TrackList &) = 0; + + virtual void backendCurrentVideoTrackChanged(TrackIndex) = 0; + virtual void backendCurrentAudioTrackChanged(TrackIndex) = 0; + virtual void backendCurrentSubtitleTrackChanged(TrackIndex) = 0; + + struct Chapter { + QString title; + TimeStamp startTime; + }; + using ChapterList = QList; + virtual void backendChaptersChanged(const ChapterList &chapters) = 0; +}; + +class PlayerRendererInterface { +public: + virtual ~PlayerRendererInterface() = default; + virtual void rendererSinkSet(VideoUpdateInterface *) = 0; + virtual void rendererReady() = 0; +}; + +class VideoRendererBase { +public: + VideoRendererBase() = default; + VideoRendererBase(const VideoRendererBase &) = delete; + VideoRendererBase &operator=(const VideoRendererBase &) = delete; + virtual ~VideoRendererBase() = default; + virtual void render(QOpenGLFramebufferObject *) = 0; +}; + +#endif // PLAYERPLUGININTERFACE_H diff --git a/core/player.cpp b/core/player.cpp index a0d28bf..6aa7085 100644 --- a/core/player.cpp +++ b/core/player.cpp @@ -16,6 +16,22 @@ Player::Player(BackendPluginBase *backendPlugin, QObject *parent) m_backend = backendPlugin->createInstance(this); Q_CHECK_PTR(m_backend); + m_videoTrackModel = new TrackModel{this}; + m_videoTrackModel->type = "video"; + Q_CHECK_PTR(m_videoTrackModel); + connect(m_videoTrackModel, SIGNAL(trackChangeRequested(int)), this, + SLOT(setCurrentVideoTrack(int))); + m_audioTrackModel = new TrackModel{this}; + m_audioTrackModel->type = "audio"; + Q_CHECK_PTR(m_audioTrackModel); + connect(m_audioTrackModel, SIGNAL(trackChangeRequested(int)), this, + SLOT(setCurrentAudioTrack(int))); + m_subtitleTrackModel = new TrackModel{this}; + m_subtitleTrackModel->type = "sub"; + Q_CHECK_PTR(m_subtitleTrackModel); + connect(m_subtitleTrackModel, SIGNAL(trackChangeRequested(int)), this, + SLOT(setCurrentSubtitleTrack(int))); + m_chapterModel = new ChapterModel{this}; Q_CHECK_PTR(m_chapterModel); } @@ -42,16 +58,16 @@ Player::AudioStreams Player::availableAudioStreams() const { return m_availableAudioStreams; } -Player::StreamIndex Player::currentAudioStream() const { - return m_currentAudioStream; +Player::TrackIndex Player::currentAudioTrack() const { + return m_currentAudioTrack; } -Player::StreamIndex Player::currentVideoStream() const { - return m_currentVideoStream; +Player::TrackIndex Player::currentVideoTrack() const { + return m_currentVideoTrack; } -Player::StreamIndex Player::currentSubtitleStream() const { - return m_currentSubtitleStream; +Player::TrackIndex Player::currentSubtitleTrack() const { + return m_currentSubtitleTrack; } Player::VideoStreams Player::availableVideoStreams() const { @@ -66,6 +82,18 @@ double Player::duration() const { return m_duration; } double Player::position() const { return m_position; } +QAbstractItemModel *Player::videoTrackModel() const { + return m_videoTrackModel; +} + +QAbstractItemModel *Player::audioTrackModel() const { + return m_audioTrackModel; +} + +QAbstractItemModel *Player::subtitleTrackModel() const { + return m_subtitleTrackModel; +} + QAbstractItemModel *Player::chapterModel() const { return m_chapterModel; } void Player::load(const QUrl &resource) { @@ -123,29 +151,25 @@ void Player::setMuted(bool muted) { void Player::toggleMuted() { setMuted(!muted()); } -void Player::setCurrentAudioStream(Player::StreamIndex currentAudioStream) { - if (m_currentAudioStream == currentAudioStream) +void Player::setCurrentVideoTrack(Player::TrackIndex currentVideoStream) { + if (m_currentVideoTrack == currentVideoStream) return; - m_currentAudioStream = currentAudioStream; - emit currentAudioStreamChanged(currentAudioStream); + m_backend->setCurrentVideoStream(currentVideoStream); } -void Player::setCurrentVideoStream(Player::StreamIndex currentVideoStream) { - if (m_currentVideoStream == currentVideoStream) +void Player::setCurrentAudioTrack(Player::TrackIndex currentAudioStream) { + if (m_currentAudioTrack == currentAudioStream) return; - m_currentVideoStream = currentVideoStream; - emit currentVideoStreamChanged(currentVideoStream); + m_backend->setCurrentAudioStream(currentAudioStream); } -void Player::setCurrentSubtitleStream( - Player::StreamIndex currentSubtitleStream) { - if (m_currentSubtitleStream == currentSubtitleStream) +void Player::setCurrentSubtitleTrack(Player::TrackIndex currentSubtitleStream) { + if (m_currentSubtitleTrack == currentSubtitleStream) return; - m_currentSubtitleStream = currentSubtitleStream; - emit currentSubtitleStreamChanged(currentSubtitleStream); + m_backend->setCurrentSubtitleStream(currentSubtitleStream); } void Player::backendReadyToPlay() { @@ -223,16 +247,50 @@ void Player::playbackMaxVolumeChanged(Player::Volume volume) { emit maxVolumeChanged(volume); } -void Player::streamsChanged() {} +void Player::backendVideoTracksChanged( + const PlayerPluginInterface::TrackList &tracks) { + m_videoTrackModel->setTracks(tracks); +} + +void Player::backendAudioTracksChanged( + const PlayerPluginInterface::TrackList &tracks) { + m_audioTrackModel->setTracks(tracks); +} + +void Player::backendSubtitleTracksChanged( + const PlayerPluginInterface::TrackList &tracks) { + m_subtitleTrackModel->setTracks(tracks); +} void Player::backendChaptersChanged( const PlayerPluginInterface::ChapterList &chapters) { m_chapterModel->setChapters(chapters); } +void Player::backendCurrentVideoTrackChanged(Player::TrackIndex track) { + if (m_currentVideoTrack == track) + return; + m_currentVideoTrack = track; + emit currentVideoTrackChanged(track); +} + +void Player::backendCurrentAudioTrackChanged(Player::TrackIndex track) { + if (m_currentAudioTrack == track) + return; + m_currentAudioTrack = track; + emit currentAudioTrackChanged(track); +} + +void Player::backendCurrentSubtitleTrackChanged(Player::TrackIndex track) { + if (m_currentSubtitleTrack == track) + return; + m_currentSubtitleTrack = track; + emit currentSubtitleTrackChanged(track); +} + void Player::reqisterQmlTypes() { qRegisterMetaType("TimeStamp"); - qRegisterMetaType("StreamIndex"); + qRegisterMetaType("StreamIndex"); qRegisterMetaType("Volume"); qmlRegisterType("org.aptx.aniplayer", 1, 0, "Player"); } diff --git a/core/player.h b/core/player.h index 20b2643..ae61346 100644 --- a/core/player.h +++ b/core/player.h @@ -8,6 +8,7 @@ #include "aniplayer/backendpluginbase.h" #include "chaptermodel.h" +#include "trackmodel.h" class Player : public QObject, public PlayerPluginInterface, @@ -26,13 +27,12 @@ class Player : public QObject, Q_PROPERTY(double maxVolume READ maxVolume NOTIFY maxVolumeChanged) Q_PROPERTY(bool muted READ muted WRITE setMuted NOTIFY mutedChanged) - Q_PROPERTY(Player::StreamIndex currentAudioStream READ currentAudioStream - WRITE setCurrentAudioStream NOTIFY currentAudioStreamChanged) - Q_PROPERTY(Player::StreamIndex currentVideoStream READ currentVideoStream - WRITE setCurrentVideoStream NOTIFY currentVideoStreamChanged) - Q_PROPERTY( - Player::StreamIndex currentSubtitleStream READ currentSubtitleStream WRITE - setCurrentSubtitleStream NOTIFY currentSubtitleStreamChanged) + Q_PROPERTY(int currentVideoTrack READ currentVideoTrack WRITE + setCurrentVideoTrack NOTIFY currentVideoTrackChanged) + Q_PROPERTY(int currentAudioTrack READ currentAudioTrack WRITE + setCurrentAudioTrack NOTIFY currentAudioTrackChanged) + Q_PROPERTY(int currentSubtitleTrack READ currentSubtitleTrack WRITE + setCurrentSubtitleTrack NOTIFY currentSubtitleTrackChanged) Q_PROPERTY(Player::AudioStreams availableAudioStreams READ availableAudioStreams NOTIFY availableAudioStreamsChanged) @@ -41,11 +41,17 @@ class Player : public QObject, Q_PROPERTY( Player::SubtitleStreams availableSubtitleStreams READ availableSubtitleStreams NOTIFY availableSubtitleStreamsChanged) + Q_PROPERTY(QAbstractItemModel *videoTrackModel READ videoTrackModel NOTIFY + videoTrackModelChanged) + Q_PROPERTY(QAbstractItemModel *audioTrackModel READ audioTrackModel NOTIFY + audioTrackModelChanged) + Q_PROPERTY(QAbstractItemModel *subtitleTrackModel READ subtitleTrackModel + NOTIFY subtitleTrackModelChanged) Q_PROPERTY(QAbstractItemModel *chapterModel READ chapterModel NOTIFY chapterModelChanged) public: - using StreamIndex = int; + using TrackIndex = int; using AudioStreams = QList; using VideoStreams = QList; using SubtitleStreams = QList; @@ -76,9 +82,9 @@ public: Volume maxVolume() const; bool muted() const; - StreamIndex currentAudioStream() const; - StreamIndex currentVideoStream() const; - StreamIndex currentSubtitleStream() const; + TrackIndex currentAudioTrack() const; + TrackIndex currentVideoTrack() const; + TrackIndex currentSubtitleTrack() const; AudioStreams availableAudioStreams() const; VideoStreams availableVideoStreams() const; @@ -87,6 +93,9 @@ public: double duration() const; double position() const; + QAbstractItemModel *videoTrackModel() const; + QAbstractItemModel *audioTrackModel() const; + QAbstractItemModel *subtitleTrackModel() const; QAbstractItemModel *chapterModel() const; signals: @@ -100,9 +109,9 @@ signals: void availableSubtitleStreamsChanged(SubtitleStreams availableSubtitleStreams); - void currentAudioStreamChanged(int currentAudioStream); - void currentVideoStreamChanged(StreamIndex currentVideoStream); - void currentSubtitleStreamChanged(StreamIndex currentSubtitleStream); + void currentVideoTrackChanged(int currentVideoTrack); + void currentAudioTrackChanged(int currentAudioTrack); + void currentSubtitleTrackChanged(int currentSubtitleTrack); void currentSourceChanged(QUrl currentSource); void nextSourceChanged(QUrl nextSource); @@ -110,6 +119,9 @@ signals: void durationChanged(double duration); void positionChanged(double position); + void videoTrackModelChanged(QAbstractItemModel *videoTrackModel); + void audioTrackModelChanged(QAbstractItemModel *audioTrackModel); + void subtitleTrackModelChanged(QAbstractItemModel *subtitleTrackModel); void chapterModelChanged(QAbstractItemModel *chapterModel); public slots: @@ -135,9 +147,9 @@ public slots: void toggleMuted(); // Streams - void setCurrentAudioStream(StreamIndex currentAudioStream); - void setCurrentVideoStream(StreamIndex currentVideoStream); - void setCurrentSubtitleStream(StreamIndex currentSubtitleStream); + void setCurrentAudioTrack(int currentAudioTrack); + void setCurrentVideoTrack(int currentVideoTrack); + void setCurrentSubtitleTrack(int currentSubtitleTrack); protected: void backendReadyToPlay() override; @@ -149,9 +161,15 @@ protected: void playbackPositionChanged(TimeStamp) override; void playbackVolumeChanged(Volume) override; void playbackMaxVolumeChanged(Volume) override; - void streamsChanged() override; + void backendVideoTracksChanged(const TrackList &) override; + void backendAudioTracksChanged(const TrackList &) override; + void backendSubtitleTracksChanged(const TrackList &) override; void backendChaptersChanged(const ChapterList &chapters) override; + void backendCurrentVideoTrackChanged(TrackIndex) override; + void backendCurrentAudioTrackChanged(TrackIndex) override; + void backendCurrentSubtitleTrackChanged(TrackIndex) override; + public: static void reqisterQmlTypes(); @@ -166,14 +184,17 @@ private: Volume m_volume = MAX_VOLUME; Volume m_maxVolume = MAX_VOLUME; AudioStreams m_availableAudioStreams; - StreamIndex m_currentAudioStream = StreamIndex{}; - StreamIndex m_currentVideoStream = StreamIndex{}; - StreamIndex m_currentSubtitleStream = StreamIndex{}; + TrackIndex m_currentAudioTrack = TrackIndex{}; + TrackIndex m_currentVideoTrack = TrackIndex{}; + TrackIndex m_currentSubtitleTrack = TrackIndex{}; VideoStreams m_availableVideoStreams; SubtitleStreams m_availableSubtitleStreams; Player::TimeStamp m_duration = 0; Player::TimeStamp m_position = 0; VideoUpdateInterface *m_renderer = nullptr; + TrackModel *m_videoTrackModel; + TrackModel *m_audioTrackModel; + TrackModel *m_subtitleTrackModel; ChapterModel *m_chapterModel; bool m_muted = false; bool m_backendInstanceReady = false; @@ -181,7 +202,7 @@ private: }; Q_DECLARE_METATYPE(Player::PlayState) -Q_DECLARE_METATYPE(Player::StreamIndex) +Q_DECLARE_METATYPE(Player::TrackIndex) Q_DECLARE_METATYPE(Player::TimeStamp) // Q_DECLARE_METATYPE(Player::Volume) diff --git a/core/trackmodel.cpp b/core/trackmodel.cpp new file mode 100644 index 0000000..2ba46eb --- /dev/null +++ b/core/trackmodel.cpp @@ -0,0 +1,65 @@ +#include "trackmodel.h" + +TrackModel::TrackModel(QObject *parent) : QAbstractListModel{parent} {} + +void TrackModel::setTracks(const PlayerPluginInterface::TrackList &data) { + beginResetModel(); + m_data = data; + endResetModel(); +} + +QHash TrackModel::roleNames() const { + static QHash roles{ + {Qt::DisplayRole, "text"}, + {TitleRole, "title"}, + {LanguageRole, "language"}, + {IdRole, "id"}, + }; + return roles; +} + +int TrackModel::rowCount(const QModelIndex &parent) const { + if (parent.isValid()) + return 0; + return m_data.size() + 1; +} + +QVariant TrackModel::data(const QModelIndex &index, int role) const { + static PlayerPluginInterface::Track noneTrackData{"None", "", -1}; + + auto &trackData = + (index.row() == 0) ? noneTrackData : m_data[index.row() - 1]; + + switch (role) { + case Qt::DisplayRole: + return QString{"[%4] %1: %2 (%3)"} + .arg(trackData.id) + .arg(trackData.title) + .arg(trackData.language) + .arg(type); + case TitleRole: + return trackData.title; + case LanguageRole: + return trackData.language; + case IdRole: + return trackData.id; + } + return {}; +} + +void TrackModel::requestTrackChangeForRow(int row) +{ + if (row < 0 || row >= rowCount()) + return; + emit trackChangeRequested(data(index(row, 0), IdRole).toInt()); +} + +int TrackModel::rowForTrackIndex(int trackIndex) +{ + if (trackIndex == -1) + return 0; + for (int i = 0; i < m_data.size(); ++i) + if (m_data[i].id == trackIndex) + return i + 1; + return 0; +} diff --git a/core/trackmodel.h b/core/trackmodel.h new file mode 100644 index 0000000..03b7a83 --- /dev/null +++ b/core/trackmodel.h @@ -0,0 +1,34 @@ +#ifndef TRACKMODEL_H +#define TRACKMODEL_H + +#include + +#include "aniplayer/playerplugininterface.h" + +class TrackModel : public QAbstractListModel { + Q_OBJECT +public: + enum TrackRoles { TitleRole = Qt::UserRole + 1, LanguageRole, IdRole }; + + explicit TrackModel(QObject *parent = nullptr); + + void setTracks(const PlayerPluginInterface::TrackList &); + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex{}) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QString type; + +public slots: + void requestTrackChangeForRow(int row); + int rowForTrackIndex(int trackIndex); + +signals: + void trackChangeRequested(int track); + +private: + PlayerPluginInterface::TrackList m_data; +}; + +#endif // TRACKMODEL_H diff --git a/uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml b/uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml index 498f1dc..600e852 100644 --- a/uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml +++ b/uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml @@ -153,5 +153,26 @@ Row { volume: controlledPlayer ? controlledPlayer.volume : 1 onVolumeChangeRequested: controlledPlayer.setVolume(volume) } + ComboBox { + id: videoTracks + model: player.videoTrackModel + textRole: "text" + currentIndex: player.videoTrackModel.rowForTrackIndex(player.currentVideoTrack) + onActivated: player.videoTrackModel.requestTrackChangeForRow(index) + } + ComboBox { + id: audioTracks + model: player.audioTrackModel + textRole: "text" + currentIndex: player.audioTrackModel.rowForTrackIndex(player.currentAudioTrack) + onActivated: player.audioTrackModel.requestTrackChangeForRow(index) + } + ComboBox { + id: subtitleTracks + model: player.subtitleTrackModel + textRole: "text" + currentIndex: player.subtitleTrackModel.rowForTrackIndex(player.currentSubtitleTrack) + onActivated: player.subtitleTrackModel.requestTrackChangeForRow(index) + } }