diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9723b9c4..36860133 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,9 @@ add_executable(alligator datamanager.cpp audiomanager.cpp powermanagementinterface.cpp + mpris2/mpris2.cpp + mpris2/mediaplayer2.cpp + mpris2/mediaplayer2player.cpp resources.qrc ) diff --git a/src/audiomanager.cpp b/src/audiomanager.cpp index 0a15b222..dff85f29 100644 --- a/src/audiomanager.cpp +++ b/src/audiomanager.cpp @@ -35,7 +35,7 @@ AudioManager::AudioManager(QObject *parent) : QObject(parent), d(std::make_uniqu { connect(&d->m_player, &QMediaPlayer::mutedChanged, this, &AudioManager::playerMutedChanged); connect(&d->m_player, &QMediaPlayer::volumeChanged, this, &AudioManager::playerVolumeChanged); - //connect(&d->m_player, &QMediaPlayer::mediaChanged, this, &AudioManager::sourceChanged); + connect(&d->m_player, &QMediaPlayer::mediaChanged, this, &AudioManager::sourceChanged); connect(&d->m_player, &QMediaPlayer::mediaStatusChanged, this, &AudioManager::statusChanged); connect(&d->m_player, &QMediaPlayer::mediaStatusChanged, this, &AudioManager::mediaStatusChanged); connect(&d->m_player, &QMediaPlayer::stateChanged, this, &AudioManager::playbackStateChanged); @@ -50,8 +50,6 @@ AudioManager::AudioManager(QObject *parent) : QObject(parent), d(std::make_uniqu // Check if an entry was playing when the program was shut down and restore it if (SettingsManager::self()->lastPlayingEntry() != QStringLiteral("none")) setEntry(DataManager::instance().getEntry(SettingsManager::self()->lastPlayingEntry())); - //SettingsManager.lastPlayingEntry !== "none" ? DataManager.getEntry(SettingsManager.lastPlayingEntry) : undefined - } AudioManager::~AudioManager() @@ -82,12 +80,10 @@ qreal AudioManager::volume() const return userVolume * 100.0; } -/* QUrl AudioManager::source() const { return d->m_player.media().request().url(); } -*/ QMediaPlayer::Error AudioManager::error() const { @@ -113,6 +109,11 @@ bool AudioManager::seekable() const return d->m_player.isSeekable(); } +bool AudioManager::canPlay() const +{ + return (d->m_entry != nullptr); +} + QMediaPlayer::State AudioManager::playbackState() const { return d->m_player.state(); @@ -161,6 +162,7 @@ void AudioManager::setEntry(Entry* entry) d->m_player.pause(); d->lockPositionSaving = false; Q_EMIT entryChanged(entry); + Q_EMIT playerCanPlayChanged(); } } @@ -234,6 +236,17 @@ void AudioManager::seek(qint64 position) d->m_player.setPosition(position); } +void AudioManager::next() +{ + qDebug() << "Skip to next track"; + // TODO: to be implemented +} + +void AudioManager::previous() +{ + qDebug() << "Back to previous track"; + // TODO: to be implemented +} void AudioManager::mediaStatusChanged() { qDebug() << "AudioManager::mediaStatusChanged" << d->m_player.mediaStatus(); diff --git a/src/audiomanager.h b/src/audiomanager.h index 4d5138de..6a16a8aa 100644 --- a/src/audiomanager.h +++ b/src/audiomanager.h @@ -97,7 +97,7 @@ public: [[nodiscard]] qreal volume() const; - //[[nodiscard]] QUrl source() const; + [[nodiscard]] QUrl source() const; [[nodiscard]] QMediaPlayer::MediaStatus status() const; @@ -113,6 +113,8 @@ public: [[nodiscard]] bool seekable() const; + [[nodiscard]] bool canPlay() const; + Q_SIGNALS: void playerOpenChanged(bool state); @@ -123,7 +125,7 @@ Q_SIGNALS: void volumeChanged(); - //void sourceChanged(); + void sourceChanged(); void statusChanged(QMediaPlayer::MediaStatus status); @@ -145,6 +147,8 @@ Q_SIGNALS: void stopped(); + void playerCanPlayChanged(); + public Q_SLOTS: void setEntry(Entry* entry); @@ -155,7 +159,7 @@ public Q_SLOTS: void setVolume(qreal volume); - //void setSource(const QUrl &source); + //void setSource(const QUrl &source); //source should only be set by audiomanager itself void setPosition(qint64 position); @@ -169,6 +173,10 @@ public Q_SLOTS: void seek(qint64 position); + void previous(); //TODO: implement canPrevious and canNext member functions and re-use them in MPRIS and playercontrol + + void next(); + private Q_SLOTS: void mediaStatusChanged(); diff --git a/src/datamanager.cpp b/src/datamanager.cpp index 92450b02..5e30a56c 100644 --- a/src/datamanager.cpp +++ b/src/datamanager.cpp @@ -285,6 +285,11 @@ int DataManager::queueCount() const return m_queuemap.count(); } +QStringList DataManager::getQueue() const +{ + return m_queuemap; +} + bool DataManager::entryInQueue(const QString &feedurl, const QString &id) const { Q_UNUSED(feedurl); diff --git a/src/datamanager.h b/src/datamanager.h index a2916cc9..e2228392 100644 --- a/src/datamanager.h +++ b/src/datamanager.h @@ -40,6 +40,7 @@ public: Entry* getQueueEntry(int const &index) const; int queueCount() const; + QStringList getQueue() const; Q_INVOKABLE bool entryInQueue(const QString &feedurl, const QString &id) const; Q_INVOKABLE void addtoQueue(const QString &feedurl, const QString &id); Q_INVOKABLE void moveQueueItem(const int &from, const int &to); diff --git a/src/mpris2/mediaplayer2.cpp b/src/mpris2/mediaplayer2.cpp new file mode 100644 index 00000000..0758a7f1 --- /dev/null +++ b/src/mpris2/mediaplayer2.cpp @@ -0,0 +1,71 @@ +/** + * SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan + * SPDX-FileCopyrightText: 2014 (c) Ashish Madeti + * SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "mediaplayer2.h" + +#include + +#include + +MediaPlayer2::MediaPlayer2(QObject* parent) + : QDBusAbstractAdaptor(parent) +{ +} + +MediaPlayer2::~MediaPlayer2() += default; + +bool MediaPlayer2::CanQuit() const +{ + return true; +} + +bool MediaPlayer2::CanRaise() const +{ + return true; +} +bool MediaPlayer2::HasTrackList() const +{ + return false; +} + +void MediaPlayer2::Quit() +{ + QCoreApplication::quit(); +} + +void MediaPlayer2::Raise() +{ + emit raisePlayer(); +} + +QString MediaPlayer2::Identity() const +{ + return KAboutData::applicationData().displayName(); +} + +QString MediaPlayer2::DesktopEntry() const +{ + return KAboutData::applicationData().desktopFileName(); +} + +QStringList MediaPlayer2::SupportedUriSchemes() const +{ + return QStringList() << QStringLiteral("file"); +} + +QStringList MediaPlayer2::SupportedMimeTypes() const +{ +// KService::Ptr app = KService::serviceByDesktopName(KCmdLineArgs::aboutData()->appName()); + +// if (app) { +// return app->mimeTypes(); +// } + + return QStringList(); +} diff --git a/src/mpris2/mediaplayer2.h b/src/mpris2/mediaplayer2.h new file mode 100644 index 00000000..f96292fe --- /dev/null +++ b/src/mpris2/mediaplayer2.h @@ -0,0 +1,51 @@ +/** + * SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan + * SPDX-FileCopyrightText: 2014 (c) Ashish Madeti + * SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + + +class MediaPlayer2 : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2") // Docs: https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html + + Q_PROPERTY(bool CanQuit READ CanQuit CONSTANT) + Q_PROPERTY(bool CanRaise READ CanRaise CONSTANT) + Q_PROPERTY(bool HasTrackList READ HasTrackList CONSTANT) + + Q_PROPERTY(QString Identity READ Identity CONSTANT) + Q_PROPERTY(QString DesktopEntry READ DesktopEntry CONSTANT) + + Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes CONSTANT) + Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes CONSTANT) + +public: + explicit MediaPlayer2(QObject* parent = nullptr); + ~MediaPlayer2() override; + + [[nodiscard]] bool CanQuit() const; + [[nodiscard]] bool CanRaise() const; + [[nodiscard]] bool HasTrackList() const; + + [[nodiscard]] QString Identity() const; + [[nodiscard]] QString DesktopEntry() const; + + [[nodiscard]] QStringList SupportedUriSchemes() const; + [[nodiscard]] QStringList SupportedMimeTypes() const; + +public Q_SLOTS: + void Quit(); + void Raise(); + +Q_SIGNALS: + void raisePlayer(); + +}; diff --git a/src/mpris2/mediaplayer2player.cpp b/src/mpris2/mediaplayer2player.cpp new file mode 100644 index 00000000..daf259a8 --- /dev/null +++ b/src/mpris2/mediaplayer2player.cpp @@ -0,0 +1,456 @@ +/** + * SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan + * SPDX-FileCopyrightText: 2014 (c) Ashish Madeti + * SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "mediaplayer2player.h" +#include "mpris2.h" + +#include "fetcher.h" +#include "datamanager.h" +#include "audiomanager.h" + +#if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND +#include +#endif + +#include +#include +#include +#include + + +static const double MAX_RATE = 1.0; +static const double MIN_RATE = 1.0; + +MediaPlayer2Player::MediaPlayer2Player(AudioManager *audioPlayer, bool showProgressOnTaskBar, QObject* parent) + : QDBusAbstractAdaptor(parent), m_audioPlayer(audioPlayer), + mProgressIndicatorSignal(QDBusMessage::createSignal(QStringLiteral("/org/kde/alligator"), + QStringLiteral("com.canonical.Unity.LauncherEntry"), + QStringLiteral("Update"))), + mShowProgressOnTaskBar(showProgressOnTaskBar) +{ + connect(m_audioPlayer, &AudioManager::sourceChanged, + this, &MediaPlayer2Player::playerSourceChanged, Qt::QueuedConnection); + connect(m_audioPlayer, &AudioManager::playerCanPlayChanged, + this, &MediaPlayer2Player::playControlEnabledChanged); + connect(m_audioPlayer, &AudioManager::sourceChanged, + this, &MediaPlayer2Player::skipBackwardControlEnabledChanged); + connect(m_audioPlayer, &AudioManager::sourceChanged, + this, &MediaPlayer2Player::skipForwardControlEnabledChanged); + connect(m_audioPlayer, &AudioManager::playbackStateChanged, + this, &MediaPlayer2Player::playerPlaybackStateChanged); + connect(m_audioPlayer, &AudioManager::seekableChanged, + this, &MediaPlayer2Player::playerIsSeekableChanged); + connect(m_audioPlayer, &AudioManager::positionChanged, + this, &MediaPlayer2Player::audioPositionChanged); + connect(m_audioPlayer, &AudioManager::seek, + this, &MediaPlayer2Player::playerSeeked); + connect(m_audioPlayer, &AudioManager::durationChanged, + this, &MediaPlayer2Player::audioDurationChanged); + connect(m_audioPlayer, &AudioManager::volumeChanged, + this, &MediaPlayer2Player::playerVolumeChanged); + + m_volume = m_audioPlayer->volume() / 100; + m_canPlay = m_audioPlayer->canPlay(); + signalPropertiesChange(QStringLiteral("Volume"), Volume()); + + m_mediaPlayerPresent = 1; +} + +MediaPlayer2Player::~MediaPlayer2Player() += default; + +QString MediaPlayer2Player::PlaybackStatus() const +{ + QString result; + + if (m_audioPlayer->playbackState() == QMediaPlayer::StoppedState) { + result = QStringLiteral("Stopped"); + } else if (m_audioPlayer->playbackState() == QMediaPlayer::PlayingState) { + result = QStringLiteral("Playing"); + } else { + result = QStringLiteral("Paused"); + } + + if (mShowProgressOnTaskBar) { + QVariantMap parameters; + + if (m_audioPlayer->playbackState() == QMediaPlayer::StoppedState || m_audioPlayer->duration() == 0) { + parameters.insert(QStringLiteral("progress-visible"), false); + parameters.insert(QStringLiteral("progress"), 0); + } else { + parameters.insert(QStringLiteral("progress-visible"), true); + parameters.insert(QStringLiteral("progress"), qRound(static_cast(m_position / m_audioPlayer->duration())) / 1000.0); + } + + mProgressIndicatorSignal.setArguments({QStringLiteral("application://org.kde.alligator.desktop"), parameters}); + + QDBusConnection::sessionBus().send(mProgressIndicatorSignal); + } + + return result; +} + +bool MediaPlayer2Player::CanGoNext() const +{ + return m_canGoNext; +} + +void MediaPlayer2Player::Next() +{ + emit next(); + + if (m_audioPlayer) { + m_audioPlayer->next(); + } +} + +bool MediaPlayer2Player::CanGoPrevious() const +{ + return m_canGoPrevious; +} + + +void MediaPlayer2Player::Previous() +{ + // not implemented +} + +bool MediaPlayer2Player::CanPause() const +{ + return m_canPlay; +} + +void MediaPlayer2Player::Pause() +{ + if (m_audioPlayer) { + m_audioPlayer->pause(); + } +} + +void MediaPlayer2Player::PlayPause() +{ + emit playPause(); + + if (m_audioPlayer) { + if (m_audioPlayer->playbackState() == QMediaPlayer::State::PausedState) + m_audioPlayer->play(); + else if (m_audioPlayer->playbackState() == QMediaPlayer::State::PlayingState) + m_audioPlayer->pause(); + } +} + +void MediaPlayer2Player::Stop() +{ + emit stop(); + + // we actually don't really want to stop, because that would reset the player + // position, so we pause instead + if (m_audioPlayer) { + m_audioPlayer->pause(); + } +} + +bool MediaPlayer2Player::CanPlay() const +{ + return m_canPlay; +} + +void MediaPlayer2Player::Play() +{ + if (m_audioPlayer) { + m_audioPlayer->play(); + } +} + +double MediaPlayer2Player::Volume() const +{ + return m_volume; +} + +void MediaPlayer2Player::setVolume(double volume) +{ + m_volume= qBound(0.0, volume, 1.0); + emit volumeChanged(m_volume); + + m_audioPlayer->setVolume(100 * m_volume); + + signalPropertiesChange(QStringLiteral("Volume"), Volume()); +} + +QVariantMap MediaPlayer2Player::Metadata() const +{ + return m_metadata; +} + +qlonglong MediaPlayer2Player::Position() const +{ + return m_position; +} + +void MediaPlayer2Player::setPropertyPosition(int newPositionInMs) +{ + m_position = qlonglong(newPositionInMs) * 1000; + + /* only send new progress when it has advanced more than 1 % + * to limit DBus traffic + */ + const auto incrementalProgress = static_cast(newPositionInMs - mPreviousProgressPosition) / m_audioPlayer->duration(); + if (mShowProgressOnTaskBar && (incrementalProgress > 0.01 || incrementalProgress < 0)) + { + mPreviousProgressPosition = newPositionInMs; + QVariantMap parameters; + parameters.insert(QStringLiteral("progress-visible"), true); + parameters.insert(QStringLiteral("progress"), static_cast(newPositionInMs) / m_audioPlayer->duration()); + + mProgressIndicatorSignal.setArguments({QStringLiteral("application://org.kde.alligator.desktop"), parameters}); + + QDBusConnection::sessionBus().send(mProgressIndicatorSignal); + } +} + +double MediaPlayer2Player::Rate() const +{ + return m_rate; +} + +void MediaPlayer2Player::setRate(double newRate) +{ + if (newRate <= 0.0001 && newRate >= -0.0001) { + Pause(); + } else { + m_rate = qBound(MinimumRate(), newRate, MaximumRate()); + emit rateChanged(m_rate); + + signalPropertiesChange(QStringLiteral("Rate"), Rate()); + } +} + +double MediaPlayer2Player::MinimumRate() const +{ + return MIN_RATE; +} + +double MediaPlayer2Player::MaximumRate() const +{ + return MAX_RATE; +} + +bool MediaPlayer2Player::CanSeek() const +{ + return m_playerIsSeekableChanged; +} + +bool MediaPlayer2Player::CanControl() const +{ + return true; +} + +void MediaPlayer2Player::Seek(qlonglong Offset) +{ + if (mediaPlayerPresent()) { + auto offset = (m_position + Offset) / 1000; + m_audioPlayer->seek(int(offset)); + } +} + +void MediaPlayer2Player::SetPosition(const QDBusObjectPath &trackId, qlonglong pos) +{ + if (trackId.path() == m_currentTrackId) { + m_audioPlayer->seek(int(pos / 1000)); + } +} + +void MediaPlayer2Player::OpenUri(const QString &uri) +{ + Q_UNUSED(uri); +} + +void MediaPlayer2Player::playerSourceChanged() +{ + // TODO: do we need to implement this?? + //setCurrentTrack(m_audioPlayer->playListPosition()); +} + +void MediaPlayer2Player::playControlEnabledChanged() +{ + m_canPlay = m_audioPlayer->canPlay(); + + signalPropertiesChange(QStringLiteral("CanPause"), CanPause()); + signalPropertiesChange(QStringLiteral("CanPlay"), CanPlay()); + + emit canPauseChanged(); + emit canPlayChanged(); +} + +void MediaPlayer2Player::skipBackwardControlEnabledChanged() +{ + m_canGoPrevious = (DataManager::instance().getQueue().indexOf(m_audioPlayer->entry()->id()) > 0); + + signalPropertiesChange(QStringLiteral("CanGoPrevious"), CanGoPrevious()); + emit canGoPreviousChanged(); +} + +void MediaPlayer2Player::skipForwardControlEnabledChanged() +{ + m_canGoNext = (DataManager::instance().getQueue().indexOf(m_audioPlayer->entry()->id()) < DataManager::instance().getQueue().count()); + + signalPropertiesChange(QStringLiteral("CanGoNext"), CanGoNext()); + emit canGoNextChanged(); +} + +void MediaPlayer2Player::playerPlaybackStateChanged() +{ + signalPropertiesChange(QStringLiteral("PlaybackStatus"), PlaybackStatus()); + emit playbackStatusChanged(); + + playerIsSeekableChanged(); +} + +void MediaPlayer2Player::playerIsSeekableChanged() +{ + m_playerIsSeekableChanged = m_audioPlayer->seekable(); + + signalPropertiesChange(QStringLiteral("CanSeek"), CanSeek()); + emit canSeekChanged(); +} + +void MediaPlayer2Player::audioPositionChanged() +{ + setPropertyPosition(static_cast(m_audioPlayer->position())); +} + +void MediaPlayer2Player::playerSeeked(qint64 position) +{ + Q_EMIT Seeked(position * 1000); +} + +void MediaPlayer2Player::audioDurationChanged() +{ + m_metadata = getMetadataOfCurrentTrack(); + signalPropertiesChange(QStringLiteral("Metadata"), Metadata()); + + skipBackwardControlEnabledChanged(); + skipForwardControlEnabledChanged(); + playerPlaybackStateChanged(); + playerIsSeekableChanged(); + setPropertyPosition(static_cast(m_audioPlayer->position())); +} + +void MediaPlayer2Player::playerVolumeChanged() +{ + setVolume(m_audioPlayer->volume() / 100.0); +} + +int MediaPlayer2Player::currentTrack() const +{ + return DataManager::instance().getQueue().indexOf(m_audioPlayer->entry()->id()); +} + +void MediaPlayer2Player::setCurrentTrack(int newTrackPosition) +{ + m_currentTrack = m_audioPlayer->entry()->title(); + m_currentTrackId = QDBusObjectPath(QLatin1String("/org/kde/alligator/playlist/") + QString::number(newTrackPosition)).path(); + + emit currentTrackChanged(); + + m_metadata = getMetadataOfCurrentTrack(); + signalPropertiesChange(QStringLiteral("Metadata"), Metadata()); +} + +QVariantMap MediaPlayer2Player::getMetadataOfCurrentTrack() +{ + auto result = QVariantMap(); + + if (m_currentTrackId.isEmpty()) { + return {}; + } + + Entry* entry = m_audioPlayer->entry(); + + result[QStringLiteral("mpris:trackid")] = QVariant::fromValue(QDBusObjectPath(m_currentTrackId)); + result[QStringLiteral("mpris:length")] = qlonglong(m_audioPlayer->duration()) * 1000; + //convert milli-seconds into micro-seconds + if (!entry->title().isEmpty()) { + result[QStringLiteral("xesam:title")] = entry->title(); + } + result[QStringLiteral("xesam:url")] = entry->enclosure()->path(); + if (!entry->feed()->name().isEmpty()) { + result[QStringLiteral("xesam:album")] = entry->feed()->name(); + } + if (entry->authors().count() > 0) { + QStringList authors; + for (auto &author : entry->authors()) authors.append(author->name()); + result[QStringLiteral("xesam:artist")] = authors; + } + if (!entry->image().isEmpty()) { + result[QStringLiteral("mpris:artUrl")] = Fetcher::instance().imagePath(entry->image()); + } + + return result; +} + +int MediaPlayer2Player::mediaPlayerPresent() const +{ + return m_mediaPlayerPresent; +} + +bool MediaPlayer2Player::showProgressOnTaskBar() const +{ + return mShowProgressOnTaskBar; +} + +void MediaPlayer2Player::setShowProgressOnTaskBar(bool value) +{ + mShowProgressOnTaskBar = value; + + QVariantMap parameters; + + if (!mShowProgressOnTaskBar || m_audioPlayer->playbackState() == QMediaPlayer::StoppedState || m_audioPlayer->duration() == 0) { + parameters.insert(QStringLiteral("progress-visible"), false); + parameters.insert(QStringLiteral("progress"), 0); + } else { + parameters.insert(QStringLiteral("progress-visible"), true); + parameters.insert(QStringLiteral("progress"), qRound(static_cast(m_position / m_audioPlayer->duration())) / 1000.0); + } + + mProgressIndicatorSignal.setArguments({QStringLiteral("application://org.kde.alligator.desktop"), parameters}); + + QDBusConnection::sessionBus().send(mProgressIndicatorSignal); +} + +void MediaPlayer2Player::setMediaPlayerPresent(int status) +{ + if (m_mediaPlayerPresent != status) { + m_mediaPlayerPresent = status; + emit mediaPlayerPresentChanged(); + + signalPropertiesChange(QStringLiteral("CanGoNext"), CanGoNext()); + signalPropertiesChange(QStringLiteral("CanGoPrevious"), CanGoPrevious()); + signalPropertiesChange(QStringLiteral("CanPause"), CanPause()); + signalPropertiesChange(QStringLiteral("CanPlay"), CanPlay()); + emit canGoNextChanged(); + emit canGoPreviousChanged(); + emit canPauseChanged(); + emit canPlayChanged(); + } +} + +void MediaPlayer2Player::signalPropertiesChange(const QString &property, const QVariant &value) +{ + QVariantMap properties; + properties[property] = value; + const int ifaceIndex = metaObject()->indexOfClassInfo("D-Bus Interface"); + QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/org/mpris/MediaPlayer2"), + QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged")); + + msg << QLatin1String(metaObject()->classInfo(ifaceIndex).value()); + msg << properties; + msg << QStringList(); + + QDBusConnection::sessionBus().send(msg); +} diff --git a/src/mpris2/mediaplayer2player.h b/src/mpris2/mediaplayer2player.h new file mode 100644 index 00000000..050d4d46 --- /dev/null +++ b/src/mpris2/mediaplayer2player.h @@ -0,0 +1,142 @@ +/** + * SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan + * SPDX-FileCopyrightText: 2014 (c) Ashish Madeti + * SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include +#include + +class AudioManager; + +class MediaPlayer2Player : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player") // Docs: https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html + + Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus NOTIFY playbackStatusChanged) + Q_PROPERTY(double Rate READ Rate WRITE setRate NOTIFY rateChanged) + Q_PROPERTY(QVariantMap Metadata READ Metadata NOTIFY playbackStatusChanged) + Q_PROPERTY(double Volume READ Volume WRITE setVolume NOTIFY volumeChanged) + Q_PROPERTY(qlonglong Position READ Position WRITE setPropertyPosition NOTIFY playbackStatusChanged) + Q_PROPERTY(double MinimumRate READ MinimumRate CONSTANT) + Q_PROPERTY(double MaximumRate READ MaximumRate CONSTANT) + Q_PROPERTY(bool CanGoNext READ CanGoNext NOTIFY canGoNextChanged) + Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious NOTIFY canGoPreviousChanged) + Q_PROPERTY(bool CanPlay READ CanPlay NOTIFY canPlayChanged) + Q_PROPERTY(bool CanPause READ CanPause NOTIFY canPauseChanged) + Q_PROPERTY(bool CanControl READ CanControl NOTIFY canControlChanged) + Q_PROPERTY(bool CanSeek READ CanSeek NOTIFY canSeekChanged) + Q_PROPERTY(int currentTrack READ currentTrack WRITE setCurrentTrack NOTIFY currentTrackChanged) + Q_PROPERTY(int mediaPlayerPresent READ mediaPlayerPresent WRITE setMediaPlayerPresent NOTIFY mediaPlayerPresentChanged) + +public: + explicit MediaPlayer2Player(AudioManager *audioPlayer, + bool showProgressOnTaskBar, + QObject* parent = nullptr); + ~MediaPlayer2Player() override; + + QString PlaybackStatus() const; + double Rate() const; + QVariantMap Metadata() const; + double Volume() const; + qlonglong Position() const; + double MinimumRate() const; + double MaximumRate() const; + bool CanGoNext() const; + bool CanGoPrevious() const; + bool CanPlay() const; + bool CanPause() const; + bool CanSeek() const; + bool CanControl() const; + int currentTrack() const; + int mediaPlayerPresent() const; + + bool showProgressOnTaskBar() const; + void setShowProgressOnTaskBar(bool value); + +Q_SIGNALS: + void Seeked(qlonglong Position); + + void rateChanged(double newRate); + void volumeChanged(double newVol); + void playbackStatusChanged(); + void canGoNextChanged(); + void canGoPreviousChanged(); + void canPlayChanged(); + void canPauseChanged(); + void canControlChanged(); + void canSeekChanged(); + void currentTrackChanged(); + void mediaPlayerPresentChanged(); + void next(); + void previous(); + void playPause(); + void stop(); + +public Q_SLOTS: + + void Next(); + void Previous(); + void Pause(); + void PlayPause(); + void Stop(); + void Play(); + void Seek(qlonglong Offset); + void SetPosition(const QDBusObjectPath &trackId, qlonglong pos); + void OpenUri(const QString &uri); + +private Q_SLOTS: + + void playerSourceChanged(); + + void playControlEnabledChanged(); + + void skipBackwardControlEnabledChanged(); + + void skipForwardControlEnabledChanged(); + + void playerPlaybackStateChanged(); + + void playerIsSeekableChanged(); + + void audioPositionChanged(); + + void playerSeeked(qint64 position); + + void audioDurationChanged(); + + void playerVolumeChanged(); + +private: + void signalPropertiesChange(const QString &property, const QVariant &value); + + void setMediaPlayerPresent(int status); + void setRate(double newRate); + void setVolume(double volume); + void setPropertyPosition(int newPositionInMs); + void setCurrentTrack(int newTrackPosition); + + QVariantMap getMetadataOfCurrentTrack(); + + QVariantMap m_metadata; + QString m_currentTrack; + QString m_currentTrackId; + double m_rate = 1.0; + double m_volume = 0.0; + int m_mediaPlayerPresent = 0; + bool m_canPlay = false; + bool m_canGoNext = false; + bool m_canGoPrevious = false; + qlonglong m_position = 0; + bool m_playerIsSeekableChanged = false; + AudioManager *m_audioPlayer = nullptr; + mutable QDBusMessage mProgressIndicatorSignal; + int mPreviousProgressPosition = 0; + bool mShowProgressOnTaskBar = true; +}; diff --git a/src/mpris2/mpris2.cpp b/src/mpris2/mpris2.cpp new file mode 100644 index 00000000..a591edf5 --- /dev/null +++ b/src/mpris2/mpris2.cpp @@ -0,0 +1,111 @@ +/** + * SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan + * SPDX-FileCopyrightText: 2014 (c) Ashish Madeti + * SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "mpris2.h" +#include "mediaplayer2.h" +#include "mediaplayer2player.h" +#include "audiomanager.h" + +#include + + +#if defined Q_OS_WIN +#include +#else +#include +#endif + +Mpris2::Mpris2(QObject* parent) + : QObject(parent) +{ +} + +void Mpris2::initDBusService() +{ + QString mspris2Name(QStringLiteral("org.mpris.MediaPlayer2.") + m_playerName); + + bool success = QDBusConnection::sessionBus().registerService(mspris2Name); + + // If the above failed, it's likely because we're not the first instance + // or the name is already taken. In that event the MPRIS2 spec wants the + // following: + if (!success) { +#if defined Q_OS_WIN + success = QDBusConnection::sessionBus().registerService(mspris2Name + QLatin1String(".instance") + QString::number(GetCurrentProcessId())); +#else + success = QDBusConnection::sessionBus().registerService(mspris2Name + QLatin1String(".instance") + QString::number(getpid())); +#endif + } + + if (success) { + m_mp2 = std::make_unique(this); + m_mp2p = std::make_unique(m_audioPlayer, mShowProgressOnTaskBar, this); + + QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/mpris/MediaPlayer2"), this, QDBusConnection::ExportAdaptors); + + connect(m_mp2.get(), &MediaPlayer2::raisePlayer, this, &Mpris2::raisePlayer); + } +} + +Mpris2::~Mpris2() += default; + +QString Mpris2::playerName() const +{ + return m_playerName; +} + +AudioManager *Mpris2::audioPlayer() const +{ + return m_audioPlayer; +} + +bool Mpris2::showProgressOnTaskBar() const +{ + return mShowProgressOnTaskBar; +} + +void Mpris2::setPlayerName(const QString &playerName) +{ + if (m_playerName == playerName) { + return; + } + + m_playerName = playerName; + + if (m_audioPlayer && !m_playerName.isEmpty()) { + if (!m_mp2) { + initDBusService(); + } + } + + emit playerNameChanged(); +} + +void Mpris2::setAudioPlayer(AudioManager *audioPlayer) +{ + if (m_audioPlayer == audioPlayer) + return; + + m_audioPlayer = audioPlayer; + + if (m_audioPlayer && !m_playerName.isEmpty()) { + if (!m_mp2) { + initDBusService(); + } + } + + emit audioPlayerChanged(); +} + +void Mpris2::setShowProgressOnTaskBar(bool value) +{ + m_mp2p->setShowProgressOnTaskBar(value); + mShowProgressOnTaskBar = value; + Q_EMIT showProgressOnTaskBarChanged(); +} diff --git a/src/mpris2/mpris2.h b/src/mpris2/mpris2.h new file mode 100644 index 00000000..e32958c0 --- /dev/null +++ b/src/mpris2/mpris2.h @@ -0,0 +1,74 @@ +/** + * SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan + * SPDX-FileCopyrightText: 2014 (c) Ashish Madeti + * SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include +#include + +class MediaPlayer2Player; +class MediaPlayer2; +class AudioManager; + +class Mpris2 : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString playerName + READ playerName + WRITE setPlayerName + NOTIFY playerNameChanged) + + Q_PROPERTY(AudioManager* audioPlayer + READ audioPlayer + WRITE setAudioPlayer + NOTIFY audioPlayerChanged) + + Q_PROPERTY(bool showProgressOnTaskBar + READ showProgressOnTaskBar + WRITE setShowProgressOnTaskBar + NOTIFY showProgressOnTaskBarChanged) + +public: + explicit Mpris2(QObject* parent = nullptr); + ~Mpris2() override; + + [[nodiscard]] QString playerName() const; + + [[nodiscard]] AudioManager* audioPlayer() const; + + [[nodiscard]] bool showProgressOnTaskBar() const; + +public Q_SLOTS: + + void setPlayerName(const QString &playerName); + + void setAudioPlayer(AudioManager* audioPlayer); + + void setShowProgressOnTaskBar(bool value); + +Q_SIGNALS: + void raisePlayer(); + + void playerNameChanged(); + + void audioPlayerChanged(); + + void showProgressOnTaskBarChanged(); + +private: + + void initDBusService(); + + std::unique_ptr m_mp2; + std::unique_ptr m_mp2p; + QString m_playerName; + AudioManager* m_audioPlayer = nullptr; + bool mShowProgressOnTaskBar = true; +}; diff --git a/src/qml/PlayerControls.qml b/src/qml/PlayerControls.qml index 5014a08c..2d87742c 100644 --- a/src/qml/PlayerControls.qml +++ b/src/qml/PlayerControls.qml @@ -199,7 +199,7 @@ Kirigami.Page { icon.width: parent.buttonsize flat: true Layout.alignment: Qt.AlignHCenter - onClicked: console.log("TODO") + onClicked: audio.next() } } }