Commit 9f9cf498 authored by Urs Fleisch's avatar Urs Fleisch

MPRIS D-Bus interface to control audio player from panel.

parent 0e3c3f54
......@@ -6,6 +6,7 @@ Fri Dec 23 08:33:40 CET 2016 Urs Fleisch <ufleisch@users.sourceforge.net>
+ If the first command line argument is "--portable", the
configuration is stored in a file kid3.ini in the program folder.
+ Image data can be copied to clipboard.
+ MPRIS2 D-Bus interface for the audio player.
* Improved:
+ "Import CSV" can import to different files if no matching
......
......@@ -32,6 +32,7 @@ set(model_SRCS
model/frameeditorobject.cpp
model/frameobjectmodel.cpp
model/iusercommandprocessor.cpp
model/mprisinterface.cpp
)
set(model_MOC_HDRS
......@@ -54,6 +55,7 @@ set(model_MOC_HDRS
model/genremodel.h
model/frameeditorobject.h
model/frameobjectmodel.h
model/mprisinterface.h
)
if (HAVE_QTDBUS)
......
......@@ -38,13 +38,17 @@
#include <QMediaPlayer>
#include <QMediaPlaylist>
#endif
#include "kid3application.h"
#include "taggedfile.h"
#include "fileproxymodel.h"
/**
* Constructor.
*
* @param parent parent object
* @param app parent application
*/
AudioPlayer::AudioPlayer(QObject* parent) : QObject(parent)
AudioPlayer::AudioPlayer(Kid3Application* app) : QObject(app),
m_app(app)
#ifdef HAVE_PHONON
, m_fileNr(-1)
#endif
......@@ -63,6 +67,10 @@ AudioPlayer::AudioPlayer(QObject* parent) : QObject(parent)
this, SLOT(currentSourceChanged()));
connect(m_mediaObject, SIGNAL(tick(qint64)),
this, SIGNAL(positionChanged(qint64)));
connect(m_mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
this, SLOT(onStateChanged()));
connect(m_audioOutput, SIGNAL(volumeChanged(qreal)),
this, SLOT(onVolumeChanged(qreal)));
#else
m_mediaPlayer = new QMediaPlayer(this);
m_mediaPlaylist = new QMediaPlaylist(m_mediaPlayer);
......@@ -71,6 +79,10 @@ AudioPlayer::AudioPlayer(QObject* parent) : QObject(parent)
this, SLOT(currentIndexChanged(int)));
connect(m_mediaPlayer, SIGNAL(positionChanged(qint64)),
this, SIGNAL(positionChanged(qint64)));
connect(m_mediaPlayer, SIGNAL(stateChanged(QMediaPlayer::State)),
this, SLOT(onStateChanged()));
connect(m_mediaPlayer, SIGNAL(volumeChanged(int)),
this, SIGNAL(volumeChanged(int)));
#endif
}
......@@ -109,10 +121,24 @@ void AudioPlayer::setFiles(const QStringList& files, int fileNr)
m_mediaPlaylist->setCurrentIndex(0);
}
#endif
emit fileCountChanged(getFileCount());
}
/**
* Get name of current file.
* Get number of files in play list.
* @return number of files.
*/
int AudioPlayer::getFileCount() const
{
#ifdef HAVE_PHONON
return m_files.size();
#else
return m_mediaPlaylist->mediaCount();
#endif
}
/**
* Get path of current file.
* @return file name.
*/
QString AudioPlayer::getFileName() const
......@@ -127,6 +153,33 @@ QString AudioPlayer::getFileName() const
#endif
}
/**
* Get tagged file for current file.
* @return tagged file, 0 if not available.
*/
TaggedFile* AudioPlayer::getTaggedFile() const
{
FileProxyModel* model = m_app->getFileProxyModel();
QModelIndex index = model->index(getFileName());
if (index.isValid()) {
return FileProxyModel::getTaggedFileOfIndex(index);
}
return 0;
}
/**
* Get index of current file in playlist.
* @return index of current file.
*/
int AudioPlayer::getCurrentIndex() const
{
#ifdef HAVE_PHONON
return m_fileNr;
#else
return m_mediaPlaylist->currentIndex();
#endif
}
/**
* Get the current playback position in milliseconds.
* @return time in milliseconds.
......@@ -147,9 +200,85 @@ quint64 AudioPlayer::getCurrentPosition() const
void AudioPlayer::setCurrentPosition(quint64 position)
{
#ifdef HAVE_PHONON
return m_mediaObject->seek(position);
m_mediaObject->seek(position);
#else
m_mediaPlayer->setPosition(position);
#endif
emit currentPositionChanged(position);
}
/**
* Get playing state.
* @return state.
*/
AudioPlayer::State AudioPlayer::getState() const
{
#ifdef HAVE_PHONON
switch (m_mediaObject->state()) {
case Phonon::PlayingState:
return PlayingState;
case Phonon::PausedState:
return PausedState;
default:
return StoppedState;
}
#else
switch (m_mediaPlayer->state()) {
case QMediaPlayer::StoppedState:
return StoppedState;
case QMediaPlayer::PlayingState:
return PlayingState;
case QMediaPlayer::PausedState:
return PausedState;
}
#endif
return StoppedState;
}
/**
* Signal stateChanged() when the playing state is changed.
*/
void AudioPlayer::onStateChanged()
{
emit stateChanged(getState());
}
/**
* Get duration of current track in milliseconds.
* @return duration.
*/
qint64 AudioPlayer::getDuration() const
{
#ifdef HAVE_PHONON
return m_mediaObject->totalTime();
#else
return m_mediaPlayer->duration();
#endif
}
/**
* Get volume.
* @return volume level between 0 and 100.
*/
int AudioPlayer::getVolume() const
{
#ifdef HAVE_PHONON
return m_audioOutput->volume() * 100;
#else
return m_mediaPlayer->volume();
#endif
}
/**
* Set volume.
* @param volume level between 0 and 100
*/
void AudioPlayer::setVolume(int volume)
{
#ifdef HAVE_PHONON
m_audioOutput->setVolume(static_cast<qreal>(volume) / 100);
#else
return m_mediaPlayer->setPosition(position);
m_mediaPlayer->setVolume(volume);
#endif
}
......@@ -223,6 +352,30 @@ void AudioPlayer::playOrPause()
#endif
}
/**
* Resume playback.
*/
void AudioPlayer::play()
{
#ifdef HAVE_PHONON
m_mediaObject->play();
#else
m_mediaPlayer->play();
#endif
}
/**
* Pause playback.
*/
void AudioPlayer::pause()
{
#ifdef HAVE_PHONON
m_mediaObject->pause();
#else
m_mediaPlayer->pause();
#endif
}
/**
* Stop playback.
*/
......@@ -265,6 +418,15 @@ void AudioPlayer::aboutToFinish()
}
}
}
/**
* Signal volumeChanged() when the volume is changed.
* @param volume volume
*/
void AudioPlayer::onVolumeChanged(qreal volume)
{
emit volumeChanged(volume * 100);
}
#else
/**
* Update display and button state when the current source is changed.
......
......@@ -45,6 +45,9 @@ class QMediaPlayer;
class QMediaPlaylist;
#endif
class Kid3Application;
class TaggedFile;
/**
* Audio player toolbar.
*/
......@@ -52,12 +55,19 @@ class KID3_CORE_EXPORT AudioPlayer : public QObject {
Q_OBJECT
public:
/** Playing state. */
enum State {
StoppedState, /**< Stopped */
PlayingState, /**< Playing */
PausedState /**< Paused */
};
/**
* Constructor.
*
* @param parent parent object
* @param app parent application
*/
explicit AudioPlayer(QObject* parent);
explicit AudioPlayer(Kid3Application* app);
/**
* Destructor.
......@@ -73,11 +83,29 @@ public:
void setFiles(const QStringList& files, int fileNr = 0);
/**
* Get name of current file.
* Get number of files in play list.
* @return number of files.
*/
int getFileCount() const;
/**
* Get path of current file.
* @return file name.
*/
QString getFileName() const;
/**
* Get tagged file for current file.
* @return tagged file, 0 if not available.
*/
TaggedFile* getTaggedFile() const;
/**
* Get index of current file in playlist.
* @return index of current file.
*/
int getCurrentIndex() const;
/**
* Get the current playback position in milliseconds.
* @return time in milliseconds.
......@@ -90,6 +118,30 @@ public:
*/
void setCurrentPosition(quint64 position);
/**
* Get playing state.
* @return state.
*/
State getState() const;
/**
* Get duration of current track in milliseconds.
* @return duration.
*/
qint64 getDuration() const;
/**
* Get volume.
* @return volume level between 0 and 100.
*/
int getVolume() const;
/**
* Set volume.
* @param volume level between 0 and 100
*/
void setVolume(int volume);
#ifdef HAVE_PHONON
/**
* Play a track from the files.
......@@ -136,7 +188,31 @@ signals:
* Emitted when the current track position changed.
* @param position time in milliseconds
*/
void positionChanged(qint64 position);
void positionChanged(qint64 position);
/**
* Emitted when the current position is changed using setCurrentPosition().
* @param position time in milliseconds
*/
void currentPositionChanged(qint64 position);
/**
* Emitted when the playing state is changed.
* @param state playing state
*/
void stateChanged(AudioPlayer::State state);
/**
* Emitted when the volume is changed.
* @param volume level between 0 and 100
*/
void volumeChanged(int volume);
/**
* Emitted when the file count changed.
* @param count number of files in play list
*/
void fileCountChanged(int count);
public slots:
/**
......@@ -144,6 +220,16 @@ public slots:
*/
void playOrPause();
/**
* Resume playback.
*/
void play();
/**
* Pause playback.
*/
void pause();
/**
* Stop playback.
*/
......@@ -170,6 +256,12 @@ private slots:
* Queue next track when the current track is about to finish.
*/
void aboutToFinish();
/**
* Signal volumeChanged() when the volume is changed.
* @param volume volume
*/
void onVolumeChanged(qreal volume);
#else
/**
* Update display and button state when the current source is changed.
......@@ -177,8 +269,13 @@ private slots:
*/
void currentIndexChanged(int position);
#endif
/**
* Signal stateChanged() when the playing state is changed.
*/
void onStateChanged();
private:
Kid3Application* m_app;
#ifdef HAVE_PHONON
/**
* Select a track from the files and optionally start playing it.
......
......@@ -86,6 +86,9 @@
#include "iusercommandprocessor.h"
#if defined HAVE_PHONON || QT_VERSION >= 0x050000
#include "audioplayer.h"
#ifdef HAVE_QTDBUS
#include "mprisinterface.h"
#endif
#endif
#include "importplugins.h"
......@@ -516,7 +519,14 @@ AudioPlayer* Kid3Application::getAudioPlayer()
{
if (!m_player) {
m_player = new AudioPlayer(this);
#ifdef HAVE_QTDBUS
new MprisInterface(m_player);
new MprisPlayerInterface(m_player);
#endif
}
#ifdef HAVE_QTDBUS
activateMprisInterface();
#endif
return m_player;
}
......@@ -526,10 +536,69 @@ AudioPlayer* Kid3Application::getAudioPlayer()
void Kid3Application::deleteAudioPlayer() {
if (m_player) {
m_player->stop();
#ifdef HAVE_QTDBUS
deactivateMprisInterface();
#endif
delete m_player;
m_player = 0;
}
}
#ifdef HAVE_QTDBUS
/**
* Activate the MPRIS D-Bus Interface if not already active.
*/
void Kid3Application::activateMprisInterface()
{
if (!m_mprisServiceName.isEmpty() || !m_player)
return;
if (QDBusConnection::sessionBus().isConnected()) {
m_mprisServiceName = QLatin1String("org.mpris.MediaPlayer2.kid3");
bool ok = QDBusConnection::sessionBus().registerService(m_mprisServiceName);
if (!ok) {
// If another instance of Kid3 is already running register a service
// with ".instancePID" appended, see
// https://specifications.freedesktop.org/mpris-spec/latest/
m_mprisServiceName += QLatin1String(".instance");
m_mprisServiceName += QString::number(::getpid());
ok = QDBusConnection::sessionBus().registerService(m_mprisServiceName);
}
if (ok) {
if (!QDBusConnection::sessionBus().registerObject(
QLatin1String("/org/mpris/MediaPlayer2"), m_player)) {
qWarning("Registering D-Bus MPRIS object failed");
}
} else {
m_mprisServiceName.clear();
qWarning("Registering D-Bus MPRIS service failed");
}
} else {
qWarning("Cannot connect to the D-BUS session bus.");
}
}
/**
* Deactivate the MPRIS D-Bus Interface if it is active.
*/
void Kid3Application::deactivateMprisInterface()
{
if (m_mprisServiceName.isEmpty())
return;
if (QDBusConnection::sessionBus().isConnected()) {
QDBusConnection::sessionBus().unregisterObject(
QLatin1String("/org/mpris/MediaPlayer2"));
if (QDBusConnection::sessionBus().unregisterService(m_mprisServiceName)) {
m_mprisServiceName.clear();
} else {
qWarning("Unregistering D-Bus MPRIS service failed");
}
} else {
qWarning("Cannot connect to the D-BUS session bus.");
}
}
#endif
#endif
/**
......
......@@ -1060,6 +1060,18 @@ public slots:
* Show play tool bar.
*/
void showAudioPlayer();
#ifdef HAVE_QTDBUS
/**
* Activate the MPRIS D-Bus Interface if not already active.
*/
void activateMprisInterface();
/**
* Deactivate the MPRIS D-Bus Interface if it is active.
*/
void deactivateMprisInterface();
#endif
#endif
/**
......@@ -1322,6 +1334,9 @@ private:
#if defined HAVE_PHONON || QT_VERSION >= 0x050000
/** Audio player */
AudioPlayer* m_player;
#ifdef HAVE_QTDBUS
QString m_mprisServiceName;
#endif
#endif
FileFilter* m_expressionFileFilter;
/** Information about selected tagged files */
......
/**
* \file mprisinterface.cpp
* MPRIS D-Bus interface for audio player.
*
* \b Project: Kid3
* \author Urs Fleisch
* \date 09-Dec-2016
*
* Copyright (C) 2016 Urs Fleisch
*
* This file is part of Kid3.
*
* Kid3 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Kid3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mprisinterface.h"
#if (defined HAVE_PHONON || QT_VERSION >= 0x050000) && defined HAVE_QTDBUS
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDBusObjectPath>
#include <QUrl>
#include <QDir>
#include <QTemporaryFile>
#include <QApplication>
#include "audioplayer.h"
#include "taggedfile.h"
#include "trackdata.h"
#include "pictureframe.h"
MprisInterface::MprisInterface(AudioPlayer* player)
: QDBusAbstractAdaptor(player), m_audioPlayer(player)
{
}
MprisInterface::~MprisInterface()
{
}
QString MprisInterface::identity() const
{
return QLatin1String("Kid3");
}
QString MprisInterface::desktopEntry() const
{
// Organization domain is only set in the KDE application.
return QApplication::organizationDomain().isEmpty()
? QLatin1String("kid3-qt") : QLatin1String("kid3");
}
QStringList MprisInterface::supportedUriSchemes() const
{
return QStringList() << QLatin1String("file");
}
QStringList MprisInterface::supportedMimeTypes() const
{
return QStringList()
<< QLatin1String("audio/mpeg")
<< QLatin1String("audio/ogg")
<< QLatin1String("application/ogg")
<< QLatin1String("audio/x-flac")
<< QLatin1String("audio/x-flac+ogg")
<< QLatin1String("audio/x-vorbis+ogg")
<< QLatin1String("audio/x-speex+ogg")
<< QLatin1String("audio/x-oggflac")
<< QLatin1String("audio/x-musepack")
<< QLatin1String("audio/aac")
<< QLatin1String("audio/mp4")
<< QLatin1String("audio/x-speex")
<< QLatin1String("audio/x-tta")
<< QLatin1String("audio/x-wavpack")
<< QLatin1String("audio/x-aiff")
<< QLatin1String("audio/x-it")