]> Some of my projects - aniplayer.git/commitdiff
Implement track switching
authorAPTX <marek321@gmail.com>
Sun, 26 Mar 2017 12:24:43 +0000 (14:24 +0200)
committerAPTX <marek321@gmail.com>
Sun, 26 Mar 2017 12:24:43 +0000 (14:24 +0200)
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.

18 files changed:
backendplugins/backend_mpv/backendmpv.cpp
backendplugins/backend_mpv/backendmpv.h
backendplugins/backend_null/backendnull.cpp
backendplugins/backend_null/backendnull.h
core/chaptermodel.h
core/core.pro
core/include/aniplayer/backendpluginbase.h
core/include/aniplayer/backendpluginbase.h.X24088 [new file with mode: 0644]
core/include/aniplayer/playerplugininterface.h
core/include/aniplayer/playerplugininterface.h.Q75208 [new file with mode: 0644]
core/include/aniplayer/playerplugininterface.h.X13004 [new file with mode: 0644]
core/include/aniplayer/playerplugininterface.h.e74088 [new file with mode: 0644]
core/include/aniplayer/playerplugininterface.h.i13004 [new file with mode: 0644]
core/player.cpp
core/player.h
core/trackmodel.cpp [new file with mode: 0644]
core/trackmodel.h [new file with mode: 0644]
uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml

index 0c6c28ce2bce9e733a69d695a4a09f43a14cba92..ce5d4ffce9e94876f83a2389f50cbdfb6f55bfd9 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <QOpenGLContext>
 #include <QOpenGLFramebufferObject>
+#include <QString>
 #include <QUrl>
 
 #include <mpv/client.h>
@@ -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 <int type> struct MpvProperty;
 
 template <> struct MpvProperty<MPV_FORMAT_NONE> {
@@ -171,11 +208,30 @@ template <> struct MpvProperty<MPV_FORMAT_NODE> {
   }
 };
 
+template <> struct MpvProperty<MPV_FORMAT_STRING> {
+  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<char **>(property->data);
+  }
+};
+
 template <int TYPE>
 decltype(auto) readProperty(struct mpv_event_property *property) {
   return MpvProperty<TYPE>::read(property);
 }
 
+int MpvInstance::readTrackIndex(struct mpv_event_property *property) {
+  const auto str = readProperty<MPV_FORMAT_STRING>(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<PlayerPluginInterface::Volume>(
                   readProperty<MPV_FORMAT_DOUBLE>(property) / 100.0));
         }
+      } else if (strcmp(property->name, "track-list") == 0) {
+        const auto node = readProperty<MPV_FORMAT_NODE>(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<MPV_FORMAT_NODE>(property);
         const auto variant = mpv::qt::node_to_variant(node);
@@ -339,7 +436,7 @@ void VideoRendererMpv::render(QOpenGLFramebufferObject *fbo) {
 }
 
 #ifdef Q_OS_WIN
-#include <windows.h>
+#include <Windows.h>
 #endif
 
 void *VideoRendererMpv::getProcAddress(void *, const char *name) {
index 98627c59b1545d9b2cc4fc5022be07adf78ec5c8..ab4f0e925d5ba7b846107d37163e8a37f67fa404 100644 (file)
@@ -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;
index 08efa5196a4dffb894ec19441919d975a124b3ce..f1656d1e93c632048d00268dd06389a1f6cf1a23 100644 (file)
@@ -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)
+{
+
+}
index f16cefbf5f9bb6a3ea705dddf83c454f3929b3f5..d345fea9f24de647a321a43c07e88934bd13b4b2 100644 (file)
@@ -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:
index 95d31fcd4a567e71bdfcff99fa75432ad041d193..c2f7f44bf3e6a11c667054bb2b11ce1696c5a9d6 100644 (file)
@@ -1,9 +1,10 @@
 #ifndef CHAPTERMODEL_H
 #define CHAPTERMODEL_H
 
-#include "aniplayer/playerplugininterface.h"
 #include <QAbstractListModel>
 
+#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);
index e971c2c2a9862fcbbe2dc5baaedbd171fc462d60..d5f851590f9d6590e17f9004bcee008ca890ef1d 100644 (file)
@@ -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)
index d7aeaf376d74b2e2a2a3d1deecd8b1cccb5afab5..56d67282efecfd14556ab791beb825c9ec2d3259 100644 (file)
@@ -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 (file)
index 0000000..56d6728
--- /dev/null
@@ -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 <QObject>
+Q_DECLARE_INTERFACE(BackendPluginBase, ANIPLAYER_BACKEND_DPLUGIN_INTERFACE_IID)
+
+#endif // BACKENDPLUGINBASE_H
index fe7c12ecdddccedbfaa79ba8156956e73921251f..1f7b70a6c3354202a76596a093368a67c7e85d6b 100644 (file)
@@ -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<Track>;
+  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 (file)
index 0000000..9ff567d
--- /dev/null
@@ -0,0 +1,82 @@
+#ifndef PLAYERPLUGININTERFACE_H
+#define PLAYERPLUGININTERFACE_H
+
+#include <QtGlobal>
+#include <QUrl>
+
+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<Track>;
+  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<Chapter>;
+  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 (file)
index 0000000..1f7b70a
--- /dev/null
@@ -0,0 +1,87 @@
+#ifndef PLAYERPLUGININTERFACE_H
+#define PLAYERPLUGININTERFACE_H
+
+#include <QtGlobal>
+#include <QUrl>
+
+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<Track>;
+  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<Chapter>;
+  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 (file)
index 0000000..48aa5e6
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef PLAYERPLUGININTERFACE_H
+#define PLAYERPLUGININTERFACE_H
+
+#include <QtGlobal>
+#include <QUrl>
+
+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<Track>;
+  virtual void backendTracksChanged(const TrackList &tracks) = 0;
+
+  struct Chapter {
+    QString title;
+    TimeStamp startTime;
+  };
+  using ChapterList = QList<Chapter>;
+  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 (file)
index 0000000..1f7b70a
--- /dev/null
@@ -0,0 +1,87 @@
+#ifndef PLAYERPLUGININTERFACE_H
+#define PLAYERPLUGININTERFACE_H
+
+#include <QtGlobal>
+#include <QUrl>
+
+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<Track>;
+  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<Chapter>;
+  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
index a0d28bf419179ce0eef37ff1bc89cea23012099b..6aa70854d86bc988053c6c8e9693c2df34b4b312 100644 (file)
@@ -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>("TimeStamp");
-  qRegisterMetaType<StreamIndex>("StreamIndex");
+  qRegisterMetaType<TrackIndex>("StreamIndex");
   qRegisterMetaType<Volume>("Volume");
   qmlRegisterType<Player>("org.aptx.aniplayer", 1, 0, "Player");
 }
index 20b264396ff804ae82ee82ab9ab9ff71fc5f1a84..ae61346bfa40d47268f2455868436110b27e098d 100644 (file)
@@ -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<QString>;
   using VideoStreams = QList<QString>;
   using SubtitleStreams = QList<QString>;
@@ -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 (file)
index 0000000..2ba46eb
--- /dev/null
@@ -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<int, QByteArray> TrackModel::roleNames() const {
+  static QHash<int, QByteArray> 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 (file)
index 0000000..03b7a83
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef TRACKMODEL_H
+#define TRACKMODEL_H
+
+#include <QAbstractListModel>
+
+#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<int, QByteArray> 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
index 498f1dc3351261b7e0bcc8d1c9efab4c651bf34a..600e8525182d72066c6478fabe35748838cdf8ba 100644 (file)
@@ -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)
+    }
 }