From 512ef7bcab7f82a7de3752a48d63a3854b0bd4e9 Mon Sep 17 00:00:00 2001 From: APTX Date: Sun, 25 Nov 2018 19:00:34 +0900 Subject: [PATCH] Add playlist support Adds an initial directory playlist. DirectoryPlaylist contains all files in the same directory as the current file. --- core/CMakeLists.txt | 4 + core/directoryplaylist.cpp | 89 +++++++++++++++++++ core/directoryplaylist.h | 22 +++++ core/player.cpp | 29 ++++++ core/player.h | 9 ++ core/playlist.cpp | 67 ++++++++++++++ core/playlist.h | 49 ++++++++++ .../qml/PlayerControls.qml | 22 +++++ 8 files changed, 291 insertions(+) create mode 100644 core/directoryplaylist.cpp create mode 100644 core/directoryplaylist.h create mode 100644 core/playlist.cpp create mode 100644 core/playlist.h diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 7326b90..cbbaafd 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -41,6 +41,8 @@ set(core_SOURCES trackmodel.cpp chaptermodel.cpp annotationmodel.cpp + playlist.cpp + directoryplaylist.cpp ) set(core_HEADERS @@ -52,6 +54,8 @@ set(core_HEADERS trackmodel.h chaptermodel.h annotationmodel.h + playlist.h + directoryplaylist.h ) set(core_PUBLIC_HEADERS diff --git a/core/directoryplaylist.cpp b/core/directoryplaylist.cpp new file mode 100644 index 0000000..731c4ab --- /dev/null +++ b/core/directoryplaylist.cpp @@ -0,0 +1,89 @@ +#include "directoryplaylist.h" + +#include +#include + +#include +#include + +Q_LOGGING_CATEGORY(directoryPlaylistCategory, "playlist.directory") + +DirectoryPlaylist::DirectoryPlaylist(QObject* parent) : Playlist{parent} {} + +PlaylistEntry DirectoryPlaylist::doGetCurrent() const { return m_current; } + +bool DirectoryPlaylist::doNext() { + const auto fileInfo = QFileInfo{m_current.path}; + + if (!fileInfo.exists()) { + qCDebug(directoryPlaylistCategory) + << "Current entry does not found on filesystem"; + return false; + } + + const auto dir = fileInfo.dir(); + const auto entries = dir.entryInfoList(QDir::Files, + QDir::Name | QDir::IgnoreCase); + auto it = std::find(std::begin(entries), std::end(entries), fileInfo); + + if (it == std::end(entries)) { + qCDebug(directoryPlaylistCategory) << "Could not find entry in directory"; + return false; + } + + ++it; + + if (it == std::end(entries)) { + qCDebug(directoryPlaylistCategory) << "Current entry is the last entry"; + return false; + } + + m_current.path = it->absoluteFilePath(); + return true; +} + +bool DirectoryPlaylist::doPrevious() { + const auto fileInfo = QFileInfo{m_current.path}; + + if (!fileInfo.exists()) { + qCDebug(directoryPlaylistCategory) + << "Current entry does not found on filesystem"; + return false; + } + + const auto dir = fileInfo.dir(); + const auto entries = dir.entryInfoList(QDir::Files, + QDir::Name | QDir::IgnoreCase); + auto it = std::find(std::rbegin(entries), std::rend(entries), fileInfo); + + if (it == std::rend(entries)) { + qCDebug(directoryPlaylistCategory) << "Could not find entry in directory"; + return false; + } + + ++it; + + if (it == std::rend(entries)) { + qCDebug(directoryPlaylistCategory) << "Current entry is the first entry"; + return false; + } + + m_current.path = it->absoluteFilePath(); + return true; +} + +bool DirectoryPlaylist::doUpdateCurrentEntry(const PlaylistEntry ¤t) { + const auto fileInfo = QFileInfo{current.path}; + + if (!fileInfo.exists()) { + qCDebug(directoryPlaylistCategory) + << "Could not find updated entry on filesystem"; + return false; + } + + qCDebug(directoryPlaylistCategory) + << "Updating current entry. New directory:" << fileInfo.absolutePath() + << "New entry:" << fileInfo.fileName(); + m_current = current; + return true; +} diff --git a/core/directoryplaylist.h b/core/directoryplaylist.h new file mode 100644 index 0000000..191c642 --- /dev/null +++ b/core/directoryplaylist.h @@ -0,0 +1,22 @@ +#ifndef DIRECTORYPLAYLIST_H +#define DIRECTORYPLAYLIST_H + +#include "playlist.h" + +class DirectoryPlaylist : public Playlist +{ +public: + DirectoryPlaylist(QObject* parent = nullptr); + + // Playlist interface +protected: + PlaylistEntry doGetCurrent() const override; + bool doNext() override; + bool doPrevious() override; + bool doUpdateCurrentEntry(const PlaylistEntry ¤t) override; + +private: + PlaylistEntry m_current; +}; + +#endif // DIRECTORYPLAYLIST_H diff --git a/core/player.cpp b/core/player.cpp index 171eeeb..6b7a555 100644 --- a/core/player.cpp +++ b/core/player.cpp @@ -4,6 +4,8 @@ #include #include +#include "directoryplaylist.h" + #ifdef Q_OS_WIN #include #endif @@ -43,6 +45,11 @@ Player::Player(BackendPluginBase *backendPlugin, QObject *parent) m_annotationModel = new AnnotationModel{this}; Q_CHECK_PTR(m_annotationModel); + + m_playlist = new DirectoryPlaylist{this}; + Q_CHECK_PTR(m_playlist); + connect(m_playlist, SIGNAL(currentChanged(PlaylistEntry)), this, + SLOT(playPlaylistEntry(PlaylistEntry))); } Player::~Player() { @@ -128,6 +135,7 @@ void Player::load(const QUrl &resource) { m_backend->open(resource); else setNextSource(resource); + updatePlaylistState(resource); } void Player::loadAndPlay(const QUrl &resource) { @@ -162,6 +170,14 @@ void Player::togglePlay() { PlayState::Playing == state() ? pause() : play(); } void Player::seek(Player::TimeStamp position) { m_backend->seek(position); } +void Player::next() { + m_playlist->next(); +} + +void Player::previous() { + m_playlist->previous(); +} + void Player::setVolume(Volume volume) { volume = qBound(Volume{}, volume, m_maxVolume); m_backend->setVolume(volume); @@ -348,6 +364,14 @@ void Player::reqisterQmlTypes() { void Player::onNewFrame() { emit frameChanged(m_lastFrame); } +void Player::playPlaylistEntry(const PlaylistEntry &entry) { + loadAndPlay(entry.url()); +} + +void Player::playNextPlaylistEntryOnEndOfFile() { + m_playlist->next(); +} + bool Player::canLoadVideoNow() const { return m_backendInstanceReady && m_renderer && m_rendererReady; } @@ -357,3 +381,8 @@ void Player::loadNextFile() { loadAndPlay(m_nextSource); setNextSource(QUrl{}); } + +void Player::updatePlaylistState(const QUrl &resource) { + m_playlist->updateCurrentEntry({resource.toLocalFile()}); +} + diff --git a/core/player.h b/core/player.h index 4c2b4cb..0414360 100644 --- a/core/player.h +++ b/core/player.h @@ -12,6 +12,7 @@ #include "chaptermodel.h" #include "trackmodel.h" #include "annotationmodel.h" +#include "playlist.h" class Player : public QObject, public PlayerPluginInterface, @@ -153,6 +154,10 @@ public slots: void seek(Player::TimeStamp position); + // Playlist + void next(); + void previous(); + // Volume void setVolume(Volume volume); void volumeUp(int byPercentagePoints = 5); @@ -193,10 +198,13 @@ public: private slots: void onNewFrame(); + void playPlaylistEntry(const PlaylistEntry &entry); + void playNextPlaylistEntryOnEndOfFile(); private: bool canLoadVideoNow() const; void loadNextFile(); + void updatePlaylistState(const QUrl &resource); BackendInstance *m_backend = nullptr; QUrl m_currentSource; @@ -218,6 +226,7 @@ private: TrackModel *m_subtitleTrackModel; ChapterModel *m_chapterModel; AnnotationModel *m_annotationModel; + Playlist *m_playlist; QImage m_lastFrame; bool m_saveToImage = true; bool m_muted = false; diff --git a/core/playlist.cpp b/core/playlist.cpp new file mode 100644 index 0000000..c810040 --- /dev/null +++ b/core/playlist.cpp @@ -0,0 +1,67 @@ +#include "playlist.h" + +#include +#include + +Q_LOGGING_CATEGORY(playlistCategory, "playlist") + +Playlist::Playlist(QObject *parent) : QObject{parent} {} + +PlaylistEntry Playlist::current() const { + return doGetCurrent(); +} + +void Playlist::updateCurrentEntry(const PlaylistEntry &entry) { + doUpdateCurrentEntry(entry); +} + +void Playlist::next() { + const auto current = doGetCurrent(); + + if (!doNext()) { + qCDebug(playlistCategory()) << "No next entry available"; + return; + } + + const auto nextCurrent = doGetCurrent(); + + qCDebug(playlistCategory()) << "Got next entry:" << nextCurrent; + + if (current != nextCurrent) { + emit currentChanged(nextCurrent); + } +} + +void Playlist::previous() { + const auto current = doGetCurrent(); + + if (!doPrevious()) { + qCDebug(playlistCategory()) << "No previous entry available"; + return; + } + + const auto nextCurrent = doGetCurrent(); + + qCDebug(playlistCategory()) << "Got previous entry:" << nextCurrent; + + if (current != nextCurrent) { + emit currentChanged(nextCurrent); + } +} + +PlaylistEntry Playlist::doGetCurrent() const { + return {}; +} + +bool Playlist::doNext() { return false; } + +bool Playlist::doPrevious() { return false; } + +bool Playlist::doUpdateCurrentEntry(const PlaylistEntry ¤t) { + return false; +} + +QDebug &operator<<(QDebug &stream, const PlaylistEntry &entry) { + stream.nospace() << "PlaylistEntry{path = " << entry.path << "}"; + return stream; +} diff --git a/core/playlist.h b/core/playlist.h new file mode 100644 index 0000000..64acd9a --- /dev/null +++ b/core/playlist.h @@ -0,0 +1,49 @@ +#ifndef PLAYLIST_H +#define PLAYLIST_H + +#include +#include + +class PlaylistEntry { +public: + QString path; + + QUrl url() const { return QUrl::fromLocalFile(path); } + + friend bool operator==(const PlaylistEntry &a, const PlaylistEntry &b) { + return a.path == b.path; + } + + friend bool operator!=(const PlaylistEntry &a, const PlaylistEntry &b) { + return a.path != b.path; + } +}; + +QDebug &operator<<(QDebug &stream, const PlaylistEntry &entry); + +class Playlist : public QObject { + Q_OBJECT + Q_PROPERTY(PlaylistEntry current READ current NOTIFY currentChanged) + +public: + Playlist(QObject *parent = nullptr); + + PlaylistEntry current() const; + void updateCurrentEntry(const PlaylistEntry &entry); + +public slots: + void next(); + void previous(); + +signals: + void currentChanged(const PlaylistEntry ¤t); + +protected: + virtual PlaylistEntry doGetCurrent() const; + virtual bool doNext(); + virtual bool doPrevious(); + virtual bool doUpdateCurrentEntry(const PlaylistEntry ¤t); + +}; + +#endif // PLAYLIST_H diff --git a/uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml b/uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml index 3bca431..950aa99 100644 --- a/uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml +++ b/uiplugins/ui_desktop_qml_default/qml/PlayerControls.qml @@ -61,6 +61,18 @@ Flow { onTriggered: controlledPlayer.position -= 5 shortcut: "left" } + Action { + id: nextPlaylistEntry + enabled: controlledPlayer + onTriggered: controlledPlayer.next() + shortcut: "ctrl+right" + } + Action { + id: previousPlaylistEntry + enabled: controlledPlayer + onTriggered: controlledPlayer.previous() + shortcut: "ctrl+left" + } OpenButton { @@ -88,6 +100,16 @@ Flow { } } } + BasicButton { + id: previousPlaylistEntryButton + text: "|<<" + onClicked: previousPlaylistEntry.trigger(); + } + BasicButton { + id: nextPlaylistEntryButton + text: ">>|" + onClicked: nextPlaylistEntry.trigger(); + } BasicButton { id: fullscreenButton text: "FS" -- 2.52.0