From 827667870e22337b89cf41d2eec25e330f61849c Mon Sep 17 00:00:00 2001 From: Bart De Vries Date: Thu, 25 Jan 2024 21:26:01 +0100 Subject: [PATCH] WIP --- .gitlab-ci.yml | 2 +- .kde-ci.yml | 1 + src/kmediasession/CMakeLists.txt | 34 ++ src/kmediasession/config-kmediasession.h.in | 1 + src/kmediasession/kmediasession.cpp | 11 + src/kmediasession/kmediasession.h | 1 + .../mediabackends/mpvmediabackend.cpp | 435 ++++++++++++++++++ .../mediabackends/mpvmediabackend.h | 52 +++ .../mediabackends/mpvmediabackend_p.h | 55 +++ 9 files changed, 591 insertions(+), 1 deletion(-) create mode 100644 src/kmediasession/mediabackends/mpvmediabackend.cpp create mode 100644 src/kmediasession/mediabackends/mpvmediabackend.h create mode 100644 src/kmediasession/mediabackends/mpvmediabackend_p.h diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fda31009..74a1c4be 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,7 +9,7 @@ include: - /gitlab-templates/linux-qt6.yml - /gitlab-templates/freebsd-qt6.yml - /gitlab-templates/android-qt6.yml - - /gitlab-templates/windows-qt6.yml +# - /gitlab-templates/windows-qt6.yml - /gitlab-templates/flatpak.yml - /gitlab-templates/craft-android-qt6-apks.yml - /gitlab-templates/craft-windows-mingw64-qt6.yml diff --git a/.kde-ci.yml b/.kde-ci.yml index e0a075a8..12d45fc7 100644 --- a/.kde-ci.yml +++ b/.kde-ci.yml @@ -13,6 +13,7 @@ Dependencies: 'frameworks/threadweaver': '@latest-kf6' 'frameworks/kcolorscheme': '@latest-kf6' 'libraries/kirigami-addons': '@latest-kf6' + 'libraries/mpvqt': '@latest' 'third-party/qtkeychain': '@latest' - 'on': ['Linux', 'FreeBSD', 'Windows'] diff --git a/src/kmediasession/CMakeLists.txt b/src/kmediasession/CMakeLists.txt index c6ad7272..6d2c8900 100644 --- a/src/kmediasession/CMakeLists.txt +++ b/src/kmediasession/CMakeLists.txt @@ -15,11 +15,22 @@ if (NOT ANDROID) find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS Widgets) endif() +option(BUILD_MPV_BACKEND "Build Mpv audio backend" ON) +if (BUILD_MPV_BACKEND) +find_package(MpvQt) +set_package_properties(MpvQt PROPERTIES + DESCRIPTION "Allows to play audio and video using MpvQt as backend" + TYPE RECOMMENDED) +endif() + +option(BUILD_LIBVLC_BACKEND "Build libVLC audio backend" ON) +if (BUILD_LIBVLC_BACKEND) find_package(LIBVLC) set_package_properties(LIBVLC PROPERTIES DESCRIPTION "Allows to play audio using libVLC as backend" URL "https://www.videolan.org/vlc/libvlc.html" TYPE RECOMMENDED) +endif() option(BUILD_GSTREAMER_BACKEND "Build gstreamer audio backend" ON) if (BUILD_GSTREAMER_BACKEND) @@ -94,6 +105,25 @@ else() endif() endif() +if (MpvQt_FOUND) + ecm_qt_declare_logging_category(KMediaSession + HEADER "mpvmediabackendlogging.h" + IDENTIFIER "MpvMediaBackendLog" + CATEGORY_NAME "org.kde.kmediasession.mpvmediabackend" + DEFAULT_SEVERITY Info + ) + ecm_qt_declare_logging_category(KMediaSession + HEADER "mpvsignalslogging.h" + IDENTIFIER "MpvSignalsLog" + CATEGORY_NAME "org.kde.kmediasession.mpvsignals" + DEFAULT_SEVERITY Info + ) + target_sources(KMediaSession PRIVATE + mediabackends/mpvmediabackend.cpp + ) + set(HAVE_MPVQT ON) +endif() + if (LIBVLC_FOUND) ecm_qt_declare_logging_category(KMediaSession HEADER "vlcmediabackendlogging.h" @@ -145,6 +175,10 @@ target_link_libraries(KMediaSession PRIVATE KF6::I18n ) +if (MpvQt_FOUND) + target_link_libraries(KMediaSession PRIVATE MpvQt::MpvQt) +endif() + if (LIBVLC_FOUND) target_link_libraries(KMediaSession PRIVATE LIBVLC::LIBVLC) endif() diff --git a/src/kmediasession/config-kmediasession.h.in b/src/kmediasession/config-kmediasession.h.in index 1ab6cd0e..87c262cc 100644 --- a/src/kmediasession/config-kmediasession.h.in +++ b/src/kmediasession/config-kmediasession.h.in @@ -8,3 +8,4 @@ #cmakedefine HAVE_LIBVLC #cmakedefine HAVE_GST +#cmakedefine HAVE_MPVQT diff --git a/src/kmediasession/kmediasession.cpp b/src/kmediasession/kmediasession.cpp index 01e91bc3..de55982c 100644 --- a/src/kmediasession/kmediasession.cpp +++ b/src/kmediasession/kmediasession.cpp @@ -24,6 +24,9 @@ #ifdef HAVE_GST #include "mediabackends/gstmediabackend.h" #endif +#ifdef HAVE_MPVQT +#include "mediabackends/mpvmediabackend.h" +#endif class KMediaSessionPrivate { @@ -37,6 +40,9 @@ private: #endif #ifdef HAVE_GST {KMediaSession::MediaBackends::Gst, QStringLiteral("GStreamer")}, +#endif +#ifdef HAVE_MPVQT + {KMediaSession::MediaBackends::Mpv, QStringLiteral("Mpv")}, #endif }; @@ -309,6 +315,11 @@ void KMediaSession::setCurrentBackend(KMediaSession::MediaBackends backend) case KMediaSession::MediaBackends::Gst: d->m_player = new GstMediaBackend(this); break; +#endif +#ifdef HAVE_MPVQT + case KMediaSession::MediaBackends::Mpv: + d->m_player = new MpvMediaBackend(this); + break; #endif }; diff --git a/src/kmediasession/kmediasession.h b/src/kmediasession/kmediasession.h index 5681125b..73812646 100644 --- a/src/kmediasession/kmediasession.h +++ b/src/kmediasession/kmediasession.h @@ -58,6 +58,7 @@ public: Qt = 0, Vlc = 1, Gst = 2, + Mpv = 3, }; Q_ENUM(MediaBackends) diff --git a/src/kmediasession/mediabackends/mpvmediabackend.cpp b/src/kmediasession/mediabackends/mpvmediabackend.cpp new file mode 100644 index 00000000..6ba5afcd --- /dev/null +++ b/src/kmediasession/mediabackends/mpvmediabackend.cpp @@ -0,0 +1,435 @@ +/** + * SPDX-FileCopyrightText: 2024 Bart De Vries + * + * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + */ + +#include "mpvmediabackend.h" +#include "mpvmediabackend_p.h" +#include "mpvmediabackendlogging.h" +#include "mpvsignalslogging.h" + +#include +#include + +#include +#include +#include +#include +#include + +#define LOG100 4.60517018599 + +MpvMediaBackend::MpvMediaBackend(QObject *parent) + : AbstractMediaBackend(parent) + , d(std::make_unique()) +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::MpvMediaBackend"; + d->m_kmediaSession = static_cast(parent); + d->m_parent = this; +} + +MpvMediaBackend::~MpvMediaBackend() +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::~MpvMediaBackend"; +} + +KMediaSession::MediaBackends MpvMediaBackend::backend() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::backend()"; + return KMediaSession::MediaBackends::Mpv; +} + +bool MpvMediaBackend::muted() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::muted()"; + + return d->m_isMuted; +} + +qreal MpvMediaBackend::volume() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::volume()"; + + return d->m_volume; +} + +QUrl MpvMediaBackend::source() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::source()"; + + return d->m_source; +} + +KMediaSession::Error MpvMediaBackend::error() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::error()"; + + return d->m_error; +} + +qint64 MpvMediaBackend::duration() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::duration()"; + + return d->m_duration; +} + +qint64 MpvMediaBackend::position() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::position()"; + + d->m_position = d->getProperty(QStringLiteral("time-pos")).toDouble() * 1000; + return d->m_position; +} + +qreal MpvMediaBackend::playbackRate() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::playbackRate()"; + + return d->m_playbackRate; +} + +bool MpvMediaBackend::seekable() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::seekable()"; + + return d->m_isSeekable; +} + +KMediaSession::PlaybackState MpvMediaBackend::playbackState() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::playbackState()"; + + bool paused = d->getProperty(QStringLiteral("pause")).toBool(); + if (!d->m_source.isEmpty()) { + if (paused) { + if (d->m_playerState != KMediaSession::PlaybackState::StoppedState) { + d->m_playerState = KMediaSession::PlaybackState::PausedState; + } + if (d->m_timer->isActive()) { + d->m_timer->stop(); + } + } else { + d->m_playerState = KMediaSession::PlaybackState::PlayingState; + if (!d->m_timer->isActive()) { + d->m_timer->start(d->m_notifyInterval); + } + } + } else { + d->m_playerState = KMediaSession::PlaybackState::StoppedState; + if (d->m_timer->isActive()) { + d->m_timer->stop(); + } + } + return d->m_playerState; +} + +KMediaSession::MediaStatus MpvMediaBackend::mediaStatus() const +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::mediaStatus()"; + + return d->m_mediaStatus; +} + +void MpvMediaBackend::setMuted(bool muted) +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::setMuted(" << muted << ")"; + + Q_EMIT d->setProperty(QStringLiteral("ao-mute"), muted); +} + +void MpvMediaBackend::setVolume(qreal volume) +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::setVolume(" << volume << ")"; + + if (volume >= 99.0) { + Q_EMIT d->setProperty(QStringLiteral("ao-volume"), 100); + } else { + Q_EMIT d->setProperty(QStringLiteral("ao-volume"), (-std::log(1 - volume / 100.0) / LOG100) * 100.0); + } +} + +void MpvMediaBackend::setSource(const QUrl &source) +{ + if (source.isEmpty()) { + return; + } + + if (playbackState() != KMediaSession::PlaybackState::StoppedState) { + stop(); + } + + d->m_duration = 0; + d->m_isSeekable = false; + d->m_playbackRate = 1.0; + d->m_position = 0; + d->m_playerState = KMediaSession::PlaybackState::StoppedState; + d->m_mediaStatus = KMediaSession::MediaStatus::LoadingMedia; + + d->command(QStringList{QStringLiteral("loadfile"), source.toString()}); + Q_EMIT d->setProperty(QStringLiteral("pause"), true); + + d->m_source = source; + + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::sourceChanged(" << source << ")"; + QTimer::singleShot(0, this, [this, source]() { + Q_EMIT sourceChanged(source); + }); +} + +void MpvMediaBackend::setPosition(qint64 position) +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::setPosition(" << position << ")"; + + Q_EMIT d->setProperty(QStringLiteral("time-pos"), position / 1000.0); + + d->m_position = position; + Q_EMIT positionChanged(d->m_position); +} + +void MpvMediaBackend::setPlaybackRate(qreal rate) +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::setPlaybackRate(" << rate << ")"; + + Q_EMIT d->setProperty(QStringLiteral("speed"), rate); +} + +void MpvMediaBackend::play() +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::play()"; + + if (d->m_playerState == KMediaSession::PlaybackState::PlayingState) { + return; + } + + d->m_playerState = KMediaSession::PlaybackState::PlayingState; + Q_EMIT playbackStateChanged(d->m_playerState); + + Q_EMIT d->setProperty(QStringLiteral("pause"), false); +} + +void MpvMediaBackend::pause() +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::pause()"; + + if (d->m_playerState == KMediaSession::PlaybackState::PausedState) { + return; + } + + d->m_playerState = KMediaSession::PlaybackState::PausedState; + Q_EMIT playbackStateChanged(d->m_playerState); + + Q_EMIT d->setProperty(QStringLiteral("pause"), true); +} + +void MpvMediaBackend::stop() +{ + qCDebug(MpvMediaBackendLog) << "MpvMediaBackend::stop()"; + + if (d->m_playerState == KMediaSession::PlaybackState::StoppedState) { + return; + } + + Q_EMIT d->setProperty(QStringLiteral("pause"), true); + + d->m_playerState = KMediaSession::PlaybackState::StoppedState; + Q_EMIT playbackStateChanged(d->m_playerState); + + setPosition(0); + Q_EMIT positionChanged(d->m_position); +} + +MpvMediaBackendPrivate::MpvMediaBackendPrivate(QQuickItem *parent) + : MpvAbstractItem(parent) +{ + // enable console output + Q_EMIT setProperty(QStringLiteral("terminal"), QStringLiteral("no")); + + // don't load user scripts or configs + Q_EMIT setProperty(QStringLiteral("config"), QStringLiteral("no")); + Q_EMIT setProperty(QStringLiteral("load-scripts"), QStringLiteral("no")); + + // force vo to libmpv (which it should be set to anyway) --> is this needed? + // setProperty(QStringLiteral("vo"), QStringLiteral("libmpv")); + Q_EMIT setProperty(QStringLiteral("video"), QStringLiteral("no")); + Q_EMIT setProperty(QStringLiteral("audio-display"), QStringLiteral("no")); + + // use safe hardware acceleration + Q_EMIT setProperty(QStringLiteral("hwdec"), QStringLiteral("auto-safe")); + + // disable OSD and fonts + Q_EMIT setProperty(QStringLiteral("osd-level"), QStringLiteral("0")); + Q_EMIT setProperty(QStringLiteral("embeddedfonts"), QStringLiteral("no")); + + // disable input + Q_EMIT setProperty(QStringLiteral("input-builtin-bindings"), QStringLiteral("no")); + Q_EMIT setProperty(QStringLiteral("input-default-bindings"), QStringLiteral("no")); + Q_EMIT setProperty(QStringLiteral("input-vo-keyboard"), QStringLiteral("no")); + + Q_EMIT observeProperty(QStringLiteral("duration"), MPV_FORMAT_DOUBLE); + // Q_EMIT observeProperty(QStringLiteral("time-pos"), MPV_FORMAT_DOUBLE); // fires too frequently; using dedicated timer + Q_EMIT observeProperty(QStringLiteral("speed"), MPV_FORMAT_DOUBLE); + Q_EMIT observeProperty(QStringLiteral("pause"), MPV_FORMAT_FLAG); + Q_EMIT observeProperty(QStringLiteral("metadata"), MPV_FORMAT_NODE_MAP); + Q_EMIT observeProperty(QStringLiteral("seekable"), MPV_FORMAT_FLAG); + + connect(mpvController(), &MpvController::propertyChanged, this, &MpvMediaBackendPrivate::onPropertyChanged, Qt::QueuedConnection); + connect(mpvController(), &MpvController::fileLoaded, this, &MpvMediaBackendPrivate::onFileLoaded, Qt::QueuedConnection); + connect(mpvController(), &MpvController::endFile, this, &MpvMediaBackendPrivate::onEndFile, Qt::QueuedConnection); + + // create timer to get player state updates + // this is done because some properties don't send signals (e.g. ao-volume), + // or fire them to rapidly (e.g. time-pos) + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &MpvMediaBackendPrivate::timerUpdate); +} + +void MpvMediaBackendPrivate::onFileLoaded() +{ + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onFileLoaded"; + + m_isSeekable = true; + m_playerState = KMediaSession::PlaybackState::PausedState; + m_mediaStatus = KMediaSession::MediaStatus::BufferedMedia; + + Q_EMIT m_parent->mediaStatusChanged(m_mediaStatus); + Q_EMIT m_parent->playbackStateChanged(m_playerState); + Q_EMIT m_parent->seekableChanged(m_isSeekable); + + parseMetaData(); +} + +void MpvMediaBackendPrivate::onEndFile(const QString &reason) +{ + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onEndFile"; + + if (m_timer->isActive()) { + m_timer->stop(); + } + + m_isSeekable = false; + m_playerState = KMediaSession::PlaybackState::StoppedState; + m_mediaStatus = KMediaSession::MediaStatus::EndOfMedia; + + if (reason == QStringLiteral("error")) { + m_mediaStatus = KMediaSession::MediaStatus::InvalidMedia; + m_error = KMediaSession::Error::ResourceError; + Q_EMIT m_parent->errorChanged(m_error); + } + + Q_EMIT m_parent->mediaStatusChanged(KMediaSession::MediaStatus::NoMedia); + Q_EMIT m_parent->mediaStatusChanged(m_mediaStatus); + Q_EMIT m_parent->playbackStateChanged(m_playerState); + Q_EMIT m_parent->seekableChanged(m_isSeekable); +} + +void MpvMediaBackendPrivate::parseMetaData() +{ + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::parseMetaData"; + qCDebug(MpvSignalsLog) << getProperty(QStringLiteral("metadata")).toMap(); + + QMap metaData = getProperty(QStringLiteral("metadata")).toMap(); + if (metaData.contains(QStringLiteral("title"))) { + m_kmediaSession->metaData()->setTitle(metaData[QStringLiteral("title")].toString()); + } + if (metaData.contains(QStringLiteral("artist"))) { + m_kmediaSession->metaData()->setArtist(metaData[QStringLiteral("artist")].toString()); + } + if (metaData.contains(QStringLiteral("album"))) { + m_kmediaSession->metaData()->setAlbum(metaData[QStringLiteral("album")].toString()); + } + + // TODO: get image; perhaps from track-list? + // qDebug() << getProperty(QStringLiteral("track-list")); +} + +void MpvMediaBackendPrivate::onPropertyChanged(const QString &property, const QVariant &value) +{ + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onPropertyChanged triggered on property" << property << ", value" << value; + + // abort here if the signalled property value is invalid + if (!value.isValid()) { + return; + } + + if (property == QStringLiteral("time-pos")) { + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onPropertyChanged" + << "time-pos" << value; + m_position = value.toDouble() * 1000; + Q_EMIT m_parent->positionChanged(m_position); + } else if (property == QStringLiteral("duration")) { + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onPropertyChanged" + << "duration" << value; + m_duration = value.toDouble() * 1000; + Q_EMIT m_parent->durationChanged(m_duration); + } else if (property == QStringLiteral("speed")) { + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onPropertyChanged" + << "speed" << value; + m_playbackRate = value.toDouble(); + Q_EMIT m_parent->playbackRateChanged(m_playbackRate); + } else if (property == QStringLiteral("seekable")) { + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onPropertyChanged" + << "seekable" << value; + m_isSeekable = value.toBool(); + Q_EMIT m_parent->seekableChanged(m_isSeekable); + } else if (property == QStringLiteral("metadata")) { + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onPropertyChanged" + << "metadata" << value; + parseMetaData(); + } else if (property == QStringLiteral("pause")) { + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::onPropertyChanged" + << "pause" << value; + // pause is expected to be false if there's no currently loaded media, so skip it + if (!m_source.isEmpty()) { + bool paused = value.toBool(); + if (paused) { + if (m_playerState != KMediaSession::PlaybackState::StoppedState) { + m_playerState = KMediaSession::PlaybackState::PausedState; + } + if (m_timer->isActive()) { + m_timer->stop(); + } + } else { + m_playerState = KMediaSession::PlaybackState::PlayingState; + if (!m_timer->isActive()) { + m_timer->start(m_notifyInterval); + } + } + } else { + m_playerState = KMediaSession::PlaybackState::StoppedState; + if (m_timer->isActive()) { + m_timer->stop(); + } + } + Q_EMIT m_parent->playbackStateChanged(m_playerState); + } +} + +void MpvMediaBackendPrivate::timerUpdate() +{ + qCDebug(MpvSignalsLog) << "MpvMediaBackendPrivate::timerUpdate"; + QTimer::singleShot(0, this, [this]() { + // Update position + // Always update position; this timer only runs if we're playing so + // the position should have changed + m_position = getProperty(QStringLiteral("time-pos")).toDouble() * 1000; + Q_EMIT m_parent->positionChanged(m_position); + + // Update volume + m_newVolume = std::max(0.0, getProperty(QStringLiteral("ao-volume")).toDouble()); + m_newVolume = (1 - std::exp(-m_newVolume / 100.0 * LOG100)) * 100.0; // convert from linear to log scale + m_newVolume = (m_newVolume) > 99 ? 100 : m_newVolume; + if (abs(m_newVolume - m_volume) > 0.5) { + m_volume = m_newVolume; + Q_EMIT m_parent->volumeChanged(m_volume); + } + + // Update mute state + m_newIsMuted = getProperty(QStringLiteral("ao-mute")).toBool(); + if (m_newIsMuted != m_isMuted) { + m_isMuted = m_newIsMuted; + Q_EMIT m_parent->mutedChanged(m_isMuted); + } + }); +} diff --git a/src/kmediasession/mediabackends/mpvmediabackend.h b/src/kmediasession/mediabackends/mpvmediabackend.h new file mode 100644 index 00000000..ea08ddb6 --- /dev/null +++ b/src/kmediasession/mediabackends/mpvmediabackend.h @@ -0,0 +1,52 @@ +/** + * SPDX-FileCopyrightText: 2024 Bart De Vries + * + * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + */ + +#pragma once + +#include +#include + +#include "abstractmediabackend.h" +#include "kmediasession.h" + +class MpvMediaBackendPrivate; + +class MpvMediaBackend : public AbstractMediaBackend +{ + Q_OBJECT + +public: + explicit MpvMediaBackend(QObject *parent); + ~MpvMediaBackend(); + + KMediaSession::MediaBackends backend() const override; + + bool muted() const override; + qreal volume() const override; + QUrl source() const override; + KMediaSession::MediaStatus mediaStatus() const override; + KMediaSession::PlaybackState playbackState() const override; + qreal playbackRate() const override; + KMediaSession::Error error() const override; + qint64 duration() const override; + qint64 position() const override; + bool seekable() const override; + +public Q_SLOTS: + void setMuted(bool muted) override; + void setVolume(qreal volume) override; + void setSource(const QUrl &source) override; + void setPosition(qint64 position) override; + void setPlaybackRate(qreal rate) override; + + void play() override; + void pause() override; + void stop() override; + +private: + friend class MpvMediaBackendPrivate; + std::unique_ptr d; +}; diff --git a/src/kmediasession/mediabackends/mpvmediabackend_p.h b/src/kmediasession/mediabackends/mpvmediabackend_p.h new file mode 100644 index 00000000..80df9918 --- /dev/null +++ b/src/kmediasession/mediabackends/mpvmediabackend_p.h @@ -0,0 +1,55 @@ +/** + * SPDX-FileCopyrightText: 2024 Bart De Vries + * + * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + */ + +#pragma once + +#include "mpvmediabackend.h" + +#include + +#include +#include +#include +#include +#include + +#include "kmediasession.h" + +class MpvMediaBackendPrivate : public MpvAbstractItem +{ +public: + explicit MpvMediaBackendPrivate(QQuickItem *parent = nullptr); + ~MpvMediaBackendPrivate() = default; + +private: + friend class MpvMediaBackend; + + void onFileLoaded(); + void onEndFile(const QString &reason); + void parseMetaData(); + void onPropertyChanged(const QString &property, const QVariant &value); + void timerUpdate(); + + KMediaSession *m_kmediaSession = nullptr; + MpvMediaBackend *m_parent = nullptr; + + const qint64 m_notifyInterval = 500; // interval for position updates (in ms) + + QTimer *m_timer = nullptr; + QUrl m_source; + qreal m_volume = 100.0; + qint64 m_position = 0; + qint64 m_duration = 0; + bool m_isMuted = false; + bool m_isSeekable = false; + qreal m_playbackRate = 1.0; + KMediaSession::PlaybackState m_playerState = KMediaSession::StoppedState; + KMediaSession::MediaStatus m_mediaStatus = KMediaSession::NoMedia; + KMediaSession::Error m_error = KMediaSession::NoError; + + qreal m_newVolume = 100.0; + bool m_newIsMuted = false; +};