Compare commits
1
Commits
kf6
...
work/mpris
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c42e2f3b42 |
No files matched your search
@@ -1,2 +1,3 @@
|
||||
CMakeLists.txt.user
|
||||
flatpak/flatpak-builder-tools/
|
||||
build/
|
||||
@@ -0,0 +1,71 @@
|
||||
/***************************************************************************
|
||||
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
|
||||
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
|
||||
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
***************************************************************************/
|
||||
|
||||
#include "mediaplayer2.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <KCoreAddons/KAboutData>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/***************************************************************************
|
||||
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
|
||||
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
|
||||
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
***************************************************************************/
|
||||
|
||||
//#pragma once
|
||||
|
||||
#include "elisaLib_export.h"
|
||||
|
||||
#include <QDBusAbstractAdaptor>
|
||||
#include <QStringList>
|
||||
|
||||
|
||||
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();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,493 @@
|
||||
/***************************************************************************
|
||||
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
|
||||
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
|
||||
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
***************************************************************************/
|
||||
|
||||
#include "mediaplayer2player.h"
|
||||
#include "mpris2.h"
|
||||
|
||||
#include "asyncytmusic.h"
|
||||
#include "ytmusic.h"
|
||||
#include "playlistmodel.h"
|
||||
|
||||
#if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND
|
||||
#include <KFileMetaData/EmbeddedImageData>
|
||||
#endif
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QStringList>
|
||||
#include <QDBusMessage>
|
||||
#include <QDBusConnection>
|
||||
|
||||
|
||||
static const double MAX_RATE = 1.0;
|
||||
static const double MIN_RATE = 1.0;
|
||||
|
||||
MediaPlayer2Player::MediaPlayer2Player(MediaPlayListProxyModel *playListControler, ManageAudioPlayer *manageAudioPlayer,
|
||||
ManageMediaPlayerControl *manageMediaPlayerControl, ManageHeaderBar *manageHeaderBar,
|
||||
AudioWrapper *audioPlayer, bool showProgressOnTaskBar, QObject* parent)
|
||||
: QDBusAbstractAdaptor(parent), m_playListControler(playListControler), m_manageAudioPlayer(manageAudioPlayer),
|
||||
m_manageMediaPlayerControl(manageMediaPlayerControl), m_manageHeaderBar(manageHeaderBar), m_audioPlayer(audioPlayer),
|
||||
mProgressIndicatorSignal(QDBusMessage::createSignal(QStringLiteral("/org/kde/elisa"),
|
||||
QStringLiteral("com.canonical.Unity.LauncherEntry"),
|
||||
QStringLiteral("Update"))),
|
||||
mShowProgressOnTaskBar(showProgressOnTaskBar)
|
||||
{
|
||||
if (!m_playListControler) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_manageAudioPlayer, &ManageAudioPlayer::playerSourceChanged,
|
||||
this, &MediaPlayer2Player::playerSourceChanged, Qt::QueuedConnection);
|
||||
connect(m_manageMediaPlayerControl, &ManageMediaPlayerControl::playControlEnabledChanged,
|
||||
this, &MediaPlayer2Player::playControlEnabledChanged);
|
||||
connect(m_manageMediaPlayerControl, &ManageMediaPlayerControl::skipBackwardControlEnabledChanged,
|
||||
this, &MediaPlayer2Player::skipBackwardControlEnabledChanged);
|
||||
connect(m_manageMediaPlayerControl, &ManageMediaPlayerControl::skipForwardControlEnabledChanged,
|
||||
this, &MediaPlayer2Player::skipForwardControlEnabledChanged);
|
||||
connect(m_manageAudioPlayer, &ManageAudioPlayer::playerPlaybackStateChanged,
|
||||
this, &MediaPlayer2Player::playerPlaybackStateChanged);
|
||||
connect(m_manageAudioPlayer, &ManageAudioPlayer::playerIsSeekableChanged,
|
||||
this, &MediaPlayer2Player::playerIsSeekableChanged);
|
||||
connect(m_manageAudioPlayer, &ManageAudioPlayer::playerPositionChanged,
|
||||
this, &MediaPlayer2Player::audioPositionChanged);
|
||||
connect(m_manageAudioPlayer, &ManageAudioPlayer::seek,
|
||||
this, &MediaPlayer2Player::playerSeeked);
|
||||
connect(m_manageAudioPlayer, &ManageAudioPlayer::audioDurationChanged,
|
||||
this, &MediaPlayer2Player::audioDurationChanged);
|
||||
connect(m_audioPlayer, &AudioWrapper::volumeChanged,
|
||||
this, &MediaPlayer2Player::playerVolumeChanged);
|
||||
|
||||
m_volume = m_audioPlayer->volume() / 100;
|
||||
m_canPlay = m_manageMediaPlayerControl->playControlEnabled();
|
||||
signalPropertiesChange(QStringLiteral("Volume"), Volume());
|
||||
|
||||
m_mediaPlayerPresent = 1;
|
||||
}
|
||||
|
||||
MediaPlayer2Player::~MediaPlayer2Player()
|
||||
= default;
|
||||
|
||||
QString MediaPlayer2Player::PlaybackStatus() const
|
||||
{
|
||||
QString result;
|
||||
|
||||
if (!m_playListControler) {
|
||||
result = QStringLiteral("Stopped");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (m_manageAudioPlayer->playerPlaybackState() == QMediaPlayer::StoppedState) {
|
||||
result = QStringLiteral("Stopped");
|
||||
} else if (m_manageAudioPlayer->playerPlaybackState() == QMediaPlayer::PlayingState) {
|
||||
result = QStringLiteral("Playing");
|
||||
} else {
|
||||
result = QStringLiteral("Paused");
|
||||
}
|
||||
|
||||
if (mShowProgressOnTaskBar) {
|
||||
QVariantMap parameters;
|
||||
|
||||
if (m_manageAudioPlayer->playerPlaybackState() == 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<double>(m_position / m_audioPlayer->duration())) / 1000.0);
|
||||
}
|
||||
|
||||
mProgressIndicatorSignal.setArguments({QStringLiteral("application://org.kde.elisa.desktop"), parameters});
|
||||
|
||||
QDBusConnection::sessionBus().send(mProgressIndicatorSignal);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MediaPlayer2Player::CanGoNext() const
|
||||
{
|
||||
return m_canGoNext;
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::Next()
|
||||
{
|
||||
emit next();
|
||||
|
||||
if (m_playListControler) {
|
||||
m_playListControler->skipNextTrack();
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaPlayer2Player::CanGoPrevious() const
|
||||
{
|
||||
return m_canGoPrevious;
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::Previous()
|
||||
{
|
||||
emit previous();
|
||||
|
||||
if (m_playListControler) {
|
||||
m_playListControler->skipPreviousTrack(m_audioPlayer->position());
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaPlayer2Player::CanPause() const
|
||||
{
|
||||
return m_canPlay;
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::Pause()
|
||||
{
|
||||
if (m_playListControler) {
|
||||
m_manageAudioPlayer->ensurePause();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::PlayPause()
|
||||
{
|
||||
emit playPause();
|
||||
|
||||
if (m_playListControler) {
|
||||
m_manageAudioPlayer->playPause();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::Stop()
|
||||
{
|
||||
emit stop();
|
||||
|
||||
if (m_playListControler) {
|
||||
m_manageAudioPlayer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaPlayer2Player::CanPlay() const
|
||||
{
|
||||
return m_canPlay;
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::Play()
|
||||
{
|
||||
if (m_playListControler) {
|
||||
m_manageAudioPlayer->ensurePlay();
|
||||
}
|
||||
}
|
||||
|
||||
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 sent new progress when it has advanced more than 1 %
|
||||
* to limit DBus traffic
|
||||
*/
|
||||
const auto incrementalProgress = static_cast<double>(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<double>(newPositionInMs) / m_audioPlayer->duration());
|
||||
|
||||
mProgressIndicatorSignal.setArguments({QStringLiteral("application://org.kde.elisa.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_manageAudioPlayer->playerSeek(int(offset));
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::SetPosition(const QDBusObjectPath &trackId, qlonglong pos)
|
||||
{
|
||||
if (trackId.path() == m_currentTrackId) {
|
||||
m_manageAudioPlayer->playerSeek(int(pos / 1000));
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::OpenUri(const QString &uri)
|
||||
{
|
||||
Q_UNUSED(uri);
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::playerSourceChanged()
|
||||
{
|
||||
if (!m_playListControler) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentTrack(m_manageAudioPlayer->playListPosition());
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::playControlEnabledChanged()
|
||||
{
|
||||
if (!m_playListControler) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_canPlay = m_manageMediaPlayerControl->playControlEnabled();
|
||||
|
||||
signalPropertiesChange(QStringLiteral("CanPause"), CanPause());
|
||||
signalPropertiesChange(QStringLiteral("CanPlay"), CanPlay());
|
||||
|
||||
emit canPauseChanged();
|
||||
emit canPlayChanged();
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::skipBackwardControlEnabledChanged()
|
||||
{
|
||||
if (!m_playListControler) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_canGoPrevious = m_manageMediaPlayerControl->skipBackwardControlEnabled();
|
||||
|
||||
signalPropertiesChange(QStringLiteral("CanGoPrevious"), CanGoPrevious());
|
||||
emit canGoPreviousChanged();
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::skipForwardControlEnabledChanged()
|
||||
{
|
||||
if (!m_playListControler) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_canGoNext = m_manageMediaPlayerControl->skipForwardControlEnabled();
|
||||
|
||||
signalPropertiesChange(QStringLiteral("CanGoNext"), CanGoNext());
|
||||
emit canGoNextChanged();
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::playerPlaybackStateChanged()
|
||||
{
|
||||
signalPropertiesChange(QStringLiteral("PlaybackStatus"), PlaybackStatus());
|
||||
emit playbackStatusChanged();
|
||||
|
||||
playerIsSeekableChanged();
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::playerIsSeekableChanged()
|
||||
{
|
||||
m_playerIsSeekableChanged = m_manageAudioPlayer->playerIsSeekable();
|
||||
|
||||
signalPropertiesChange(QStringLiteral("CanSeek"), CanSeek());
|
||||
emit canSeekChanged();
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::audioPositionChanged()
|
||||
{
|
||||
setPropertyPosition(static_cast<int>(m_manageAudioPlayer->playerPosition()));
|
||||
}
|
||||
|
||||
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<int>(m_manageAudioPlayer->playerPosition()));
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::playerVolumeChanged()
|
||||
{
|
||||
setVolume(m_audioPlayer->volume() / 100.0);
|
||||
}
|
||||
|
||||
int MediaPlayer2Player::currentTrack() const
|
||||
{
|
||||
return m_manageAudioPlayer->playListPosition();
|
||||
}
|
||||
|
||||
void MediaPlayer2Player::setCurrentTrack(int newTrackPosition)
|
||||
{
|
||||
m_currentTrack = m_manageAudioPlayer->playerSource().toString();
|
||||
m_currentTrackId = QDBusObjectPath(QLatin1String("/org/kde/elisa/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 {};
|
||||
}
|
||||
|
||||
result[QStringLiteral("mpris:trackid")] = QVariant::fromValue<QDBusObjectPath>(QDBusObjectPath(m_currentTrackId));
|
||||
result[QStringLiteral("mpris:length")] = qlonglong(m_manageAudioPlayer->audioDuration()) * 1000;
|
||||
//convert milli-seconds into micro-seconds
|
||||
if (!m_manageHeaderBar->title().isNull() && !m_manageHeaderBar->title().toString().isEmpty()) {
|
||||
result[QStringLiteral("xesam:title")] = m_manageHeaderBar->title();
|
||||
}
|
||||
result[QStringLiteral("xesam:url")] = m_manageAudioPlayer->playerSource().toString();
|
||||
if (!m_manageHeaderBar->album().isNull() && !m_manageHeaderBar->album().toString().isEmpty()) {
|
||||
result[QStringLiteral("xesam:album")] = m_manageHeaderBar->album();
|
||||
}
|
||||
if (!m_manageHeaderBar->artist().isNull() && !m_manageHeaderBar->artist().toString().isEmpty()) {
|
||||
result[QStringLiteral("xesam:artist")] = QStringList{m_manageHeaderBar->artist().toString()};
|
||||
}
|
||||
if (!m_manageHeaderBar->image().isEmpty() && !m_manageHeaderBar->image().toString().isEmpty()) {
|
||||
if (m_manageHeaderBar->image().scheme() == QStringLiteral("image")) {
|
||||
// adding a special case for image:// URLs that are only valid because Elisa installs a special handler for them
|
||||
// converting those URL to data URLs with embedded image data
|
||||
#if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND
|
||||
KFileMetaData::EmbeddedImageData embeddedImage;
|
||||
|
||||
auto imageData = embeddedImage.imageData(m_manageHeaderBar->image().toString().mid(14));
|
||||
|
||||
if (imageData.contains(KFileMetaData::EmbeddedImageData::FrontCover)) {
|
||||
result[QStringLiteral("mpris:artUrl")] = QString{QStringLiteral("data:image/png;base64,") + QLatin1String{imageData[KFileMetaData::EmbeddedImageData::FrontCover].toBase64()}};
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
result[QStringLiteral("mpris:artUrl")] = m_manageHeaderBar->image().toString();
|
||||
}
|
||||
}
|
||||
|
||||
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_manageAudioPlayer->playerPlaybackState() == 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<double>(m_position / m_audioPlayer->duration())) / 1000.0);
|
||||
}
|
||||
|
||||
mProgressIndicatorSignal.setArguments({QStringLiteral("application://org.kde.elisa.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);
|
||||
}
|
||||
|
||||
#include "moc_mediaplayer2player.cpp"
|
||||
@@ -0,0 +1,159 @@
|
||||
/***************************************************************************
|
||||
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
|
||||
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
|
||||
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef MEDIAPLAYER2PLAYER_H
|
||||
#define MEDIAPLAYER2PLAYER_H
|
||||
|
||||
#include "elisaLib_export.h"
|
||||
|
||||
#include <QDBusAbstractAdaptor>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QDBusMessage>
|
||||
|
||||
class MediaPlayListProxyModel;
|
||||
class ManageAudioPlayer;
|
||||
class ManageMediaPlayerControl;
|
||||
class ManageHeaderBar;
|
||||
class AudioWrapper;
|
||||
|
||||
class ELISALIB_EXPORT 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(MediaPlayListProxyModel *playListControler,
|
||||
ManageAudioPlayer *manageAudioPlayer,
|
||||
ManageMediaPlayerControl* manageMediaPlayerControl,
|
||||
ManageHeaderBar * manageHeaderBar,
|
||||
AudioWrapper *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;
|
||||
MediaPlayListProxyModel *m_playListControler = nullptr;
|
||||
bool m_playerIsSeekableChanged = false;
|
||||
ManageAudioPlayer* m_manageAudioPlayer = nullptr;
|
||||
ManageMediaPlayerControl* m_manageMediaPlayerControl = nullptr;
|
||||
ManageHeaderBar * m_manageHeaderBar = nullptr;
|
||||
AudioWrapper *m_audioPlayer = nullptr;
|
||||
mutable QDBusMessage mProgressIndicatorSignal;
|
||||
int mPreviousProgressPosition = 0;
|
||||
bool mShowProgressOnTaskBar = true;
|
||||
};
|
||||
|
||||
#endif // MEDIAPLAYER2PLAYER_H
|
||||
@@ -0,0 +1,200 @@
|
||||
/***************************************************************************
|
||||
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
|
||||
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
|
||||
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
***************************************************************************/
|
||||
|
||||
#include "mpris2.h"
|
||||
#include "mediaplayer2.h"
|
||||
#include "mediaplayer2player.h"
|
||||
#include "mediaplaylistproxymodel.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
|
||||
|
||||
#if defined Q_OS_WIN
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#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<MediaPlayer2>(this);
|
||||
m_mp2p = std::make_unique<MediaPlayer2Player>(m_playListModel, m_manageAudioPlayer, m_manageMediaPlayerControl, m_manageHeaderBar, 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;
|
||||
}
|
||||
|
||||
MediaPlayListProxyModel *Mpris2::playListModel() const
|
||||
{
|
||||
return m_playListModel;
|
||||
}
|
||||
|
||||
ManageAudioPlayer *Mpris2::audioPlayerManager() const
|
||||
{
|
||||
return m_manageAudioPlayer;
|
||||
}
|
||||
|
||||
ManageMediaPlayerControl *Mpris2::manageMediaPlayerControl() const
|
||||
{
|
||||
return m_manageMediaPlayerControl;
|
||||
}
|
||||
|
||||
ManageHeaderBar *Mpris2::headerBarManager() const
|
||||
{
|
||||
return m_manageHeaderBar;
|
||||
}
|
||||
|
||||
AudioWrapper *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_playListModel && m_manageAudioPlayer && m_manageMediaPlayerControl && m_manageHeaderBar && m_audioPlayer && !m_playerName.isEmpty()) {
|
||||
if (!m_mp2) {
|
||||
initDBusService();
|
||||
}
|
||||
}
|
||||
|
||||
emit playerNameChanged();
|
||||
}
|
||||
|
||||
void Mpris2::setPlayListModel(MediaPlayListProxyModel *playListModel)
|
||||
{
|
||||
if (m_playListModel == playListModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_playListModel = playListModel;
|
||||
|
||||
if (m_playListModel && m_manageAudioPlayer && m_manageMediaPlayerControl && m_manageHeaderBar && m_audioPlayer && !m_playerName.isEmpty()) {
|
||||
if (!m_mp2) {
|
||||
initDBusService();
|
||||
}
|
||||
}
|
||||
|
||||
emit playListModelChanged();
|
||||
}
|
||||
|
||||
void Mpris2::setAudioPlayerManager(ManageAudioPlayer *manageAudioPlayer)
|
||||
{
|
||||
if (m_manageAudioPlayer == manageAudioPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_manageAudioPlayer = manageAudioPlayer;
|
||||
|
||||
if (m_playListModel && m_manageAudioPlayer && m_manageMediaPlayerControl && m_manageHeaderBar && m_audioPlayer && !m_playerName.isEmpty()) {
|
||||
if (!m_mp2) {
|
||||
initDBusService();
|
||||
}
|
||||
}
|
||||
|
||||
emit audioPlayerManagerChanged();
|
||||
}
|
||||
|
||||
void Mpris2::setManageMediaPlayerControl(ManageMediaPlayerControl *manageMediaPlayerControl)
|
||||
{
|
||||
if (m_manageMediaPlayerControl == manageMediaPlayerControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_manageMediaPlayerControl = manageMediaPlayerControl;
|
||||
|
||||
if (m_playListModel && m_manageAudioPlayer && m_manageMediaPlayerControl && m_manageHeaderBar && m_audioPlayer && !m_playerName.isEmpty()) {
|
||||
if (!m_mp2) {
|
||||
initDBusService();
|
||||
}
|
||||
}
|
||||
|
||||
emit manageMediaPlayerControlChanged();
|
||||
}
|
||||
|
||||
void Mpris2::setHeaderBarManager(ManageHeaderBar *manageHeaderBar)
|
||||
{
|
||||
if (m_manageHeaderBar == manageHeaderBar)
|
||||
return;
|
||||
|
||||
m_manageHeaderBar = manageHeaderBar;
|
||||
|
||||
if (m_playListModel && m_manageAudioPlayer && m_manageMediaPlayerControl && m_manageHeaderBar && m_audioPlayer && !m_playerName.isEmpty()) {
|
||||
if (!m_mp2) {
|
||||
initDBusService();
|
||||
}
|
||||
}
|
||||
|
||||
emit headerBarManagerChanged();
|
||||
}
|
||||
|
||||
void Mpris2::setAudioPlayer(AudioWrapper *audioPlayer)
|
||||
{
|
||||
if (m_audioPlayer == audioPlayer)
|
||||
return;
|
||||
|
||||
m_audioPlayer = audioPlayer;
|
||||
|
||||
if (m_playListModel && m_manageAudioPlayer && m_manageMediaPlayerControl && m_manageHeaderBar && 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();
|
||||
}
|
||||
|
||||
#include "moc_mpris2.cpp"
|
||||
@@ -0,0 +1,131 @@
|
||||
/***************************************************************************
|
||||
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
|
||||
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
|
||||
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef MEDIACENTER_MPRIS2_H
|
||||
#define MEDIACENTER_MPRIS2_H
|
||||
|
||||
#include "elisaLib_export.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <memory>
|
||||
|
||||
class MediaPlayer2Player;
|
||||
class MediaPlayer2;
|
||||
class MediaPlayListProxyModel;
|
||||
class ManageAudioPlayer;
|
||||
class ManageMediaPlayerControl;
|
||||
class ManageHeaderBar;
|
||||
class AudioWrapper;
|
||||
|
||||
class ELISALIB_EXPORT Mpris2 : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString playerName
|
||||
READ playerName
|
||||
WRITE setPlayerName
|
||||
NOTIFY playerNameChanged)
|
||||
|
||||
Q_PROPERTY(MediaPlayListProxyModel* playListModel
|
||||
READ playListModel
|
||||
WRITE setPlayListModel
|
||||
NOTIFY playListModelChanged)
|
||||
|
||||
Q_PROPERTY(ManageAudioPlayer* audioPlayerManager
|
||||
READ audioPlayerManager
|
||||
WRITE setAudioPlayerManager
|
||||
NOTIFY audioPlayerManagerChanged)
|
||||
|
||||
Q_PROPERTY(ManageMediaPlayerControl* manageMediaPlayerControl
|
||||
READ manageMediaPlayerControl
|
||||
WRITE setManageMediaPlayerControl
|
||||
NOTIFY manageMediaPlayerControlChanged)
|
||||
|
||||
Q_PROPERTY(ManageHeaderBar* headerBarManager
|
||||
READ headerBarManager
|
||||
WRITE setHeaderBarManager
|
||||
NOTIFY headerBarManagerChanged)
|
||||
|
||||
Q_PROPERTY(AudioWrapper* 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]] MediaPlayListProxyModel* playListModel() const;
|
||||
|
||||
[[nodiscard]] ManageAudioPlayer* audioPlayerManager() const;
|
||||
|
||||
[[nodiscard]] ManageMediaPlayerControl* manageMediaPlayerControl() const;
|
||||
|
||||
[[nodiscard]] ManageHeaderBar* headerBarManager() const;
|
||||
|
||||
[[nodiscard]] AudioWrapper* audioPlayer() const;
|
||||
|
||||
[[nodiscard]] bool showProgressOnTaskBar() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
void setPlayerName(const QString &playerName);
|
||||
|
||||
void setPlayListModel(MediaPlayListProxyModel* playListModel);
|
||||
|
||||
void setAudioPlayerManager(ManageAudioPlayer* audioPlayerManager);
|
||||
|
||||
void setManageMediaPlayerControl(ManageMediaPlayerControl* manageMediaPlayerControl);
|
||||
|
||||
void setHeaderBarManager(ManageHeaderBar* headerBarManager);
|
||||
|
||||
void setAudioPlayer(AudioWrapper* audioPlayer);
|
||||
|
||||
void setShowProgressOnTaskBar(bool value);
|
||||
|
||||
Q_SIGNALS:
|
||||
void raisePlayer();
|
||||
|
||||
void playerNameChanged();
|
||||
|
||||
void playListModelChanged();
|
||||
|
||||
void audioPlayerManagerChanged();
|
||||
|
||||
void manageMediaPlayerControlChanged();
|
||||
|
||||
void headerBarManagerChanged();
|
||||
|
||||
void audioPlayerChanged();
|
||||
|
||||
void showProgressOnTaskBarChanged();
|
||||
|
||||
private:
|
||||
|
||||
void initDBusService();
|
||||
|
||||
std::unique_ptr<MediaPlayer2> m_mp2;
|
||||
std::unique_ptr<MediaPlayer2Player> m_mp2p;
|
||||
QString m_playerName;
|
||||
MediaPlayListProxyModel* m_playListModel = nullptr;
|
||||
ManageAudioPlayer* m_manageAudioPlayer = nullptr;
|
||||
ManageMediaPlayerControl* m_manageMediaPlayerControl = nullptr;
|
||||
ManageHeaderBar* m_manageHeaderBar = nullptr;
|
||||
AudioWrapper* m_audioPlayer = nullptr;
|
||||
bool mShowProgressOnTaskBar = true;
|
||||
};
|
||||
|
||||
#endif //MEDIACENTER_MPRIS2_H
|
||||
Reference in new issue
Block a user