kde-extraapps/kmix/backends/mixer_mpris2.cpp
Ivailo Monev 2e7f21de69 kmix: remove unused configuration checks
since Katie requires compiler with C++11 support the shared pointer
checks could be ommited

Signed-off-by: Ivailo Monev <xakepa10@laimg.moc>
2020-02-19 16:13:43 +00:00

696 lines
22 KiB
C++

/**
* KMix -- MPRIS2 backend
*
* Copyright (C) 2011 Christian Esken <esken@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "mixer_mpris2.h"
#include "core/mixer.h"
#include "core/ControlManager.h"
#include "core/GlobalConfig.h"
#include <QStringList>
#include <QDBusReply>
#include <QString>
#include <qvariant.h>
#include <KDebug>
#include <KLocale>
// Set the QDBUS_DEBUG env variable for debugging Qt DBUS calls.
Mixer_Backend* MPRIS2_getMixer(Mixer *mixer, int device )
{
return new Mixer_MPRIS2(mixer, device );
}
Mixer_MPRIS2::Mixer_MPRIS2(Mixer *mixer, int device) : Mixer_Backend(mixer, device )
{
}
int Mixer_MPRIS2::open()
{
if ( m_devnum != 0 )
return Mixer::ERR_OPEN;
registerCard(i18n("Playback Streams"));
_id = "Playback Streams";
_mixer->setDynamic();
return addAllRunningPlayersAndInitHotplug();
}
int Mixer_MPRIS2::close()
{
m_isOpen = false;
closeCommon();
qDeleteAll(controls);
controls.clear();
return 0;
}
int Mixer_MPRIS2::mediaPlay(QString id)
{
return mediaControl(id, "PlayPause");
}
int Mixer_MPRIS2::mediaPrev(QString id)
{
return mediaControl(id, "Previous");
}
int Mixer_MPRIS2::mediaNext(QString id)
{
return mediaControl(id, "Next");
}
/**
* Sends a media control command to the given application.
* @param applicationId The MPRIS applicationId
* @returns Always 0. Hint: Currently nobody uses the return code
*/
int Mixer_MPRIS2::mediaControl(QString applicationId, QString commandName)
{
MPrisControl* mad = controls.value(applicationId);
if ( mad == 0 )
return 0; // Might have disconnected recently => simply ignore command
kDebug() << "Send " << commandName << " to id=" << applicationId;
QDBusPendingReply<> repl2 =
mad->playerIfc->asyncCall(commandName);
QDBusPendingCallWatcher* watchMediaControlReply = new QDBusPendingCallWatcher(repl2, mad);
connect(watchMediaControlReply, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherMediaControl(QDBusPendingCallWatcher *)));
return 0; // Presume everything went well. Can't do more for ASYNC calls
}
void Mixer_MPRIS2::watcherMediaControl(QDBusPendingCallWatcher* watcher)
{
MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl == 0)
{
return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged)
}
// Actually the code below in this method is more or less just debugging
const QDBusMessage& msg = watcher->reply();
QString id = mprisCtl->getId();
QString busDestination = mprisCtl->getBusDestination();
kDebug() << "Media control for id=" << id << ", path=" << msg.path() << ", interface=" << msg.interface() << ", busDestination" << busDestination;
}
/**
* readVolumeFromHW() should be used only for hotplug (and even that should go away). Everything should operate via
* the slot volumeChanged in the future.
*/
int Mixer_MPRIS2::readVolumeFromHW( const QString& /*id*/, std::shared_ptr<MixDevice> /*md*/)
{
// Everything is done by notifications => no code neccessary
return Mixer::OK_UNCHANGED;
}
/**
* A slot that processes data from the MPrisControl that emit the signal.
*
* @param The emitting MPrisControl
* @param newVolume The new volume
*/
void Mixer_MPRIS2::playbackStateChanged(MPrisControl* mad, MediaController::PlayState playState)
{
std::shared_ptr<MixDevice> md = m_mixDevices.get(mad->getId());
md->getMediaController()->setPlayState(playState);
QMetaObject::invokeMethod(this, "announceGUI", Qt::QueuedConnection);
// ControlManager::instance().announce(_mixer->id(), ControlChangeType::GUI, QString("MixerMPRIS2.playbackStateChanged"));
}
/**
* A slot that processes data from the MPrisControl that emit the signal.
*
* @param The emitting MPrisControl
* @param newVolume The new volume
*/
void Mixer_MPRIS2::volumeChanged(MPrisControl* mad, double newVolume)
{
std::shared_ptr<MixDevice> md = m_mixDevices.get(mad->getId());
int volInt = newVolume *100;
if (GlobalConfig::instance().data.debugVolume)
kDebug() << "changed" << volInt;
volumeChangedInternal(md, volInt);
}
void Mixer_MPRIS2::volumeChangedInternal(std::shared_ptr<MixDevice> md, int volumePercentage)
{
if ( md->isVirtuallyMuted() && volumePercentage == 0)
{
// Special code path for virtual mute switches. Don't write back the volume if it is muted in the KMix GUI
return;
}
Volume& vol = md->playbackVolume();
vol.setVolume( Volume::LEFT, volumePercentage);
md->setMuted(volumePercentage == 0);
QMetaObject::invokeMethod(this, "announceVolume", Qt::QueuedConnection);
// ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, QString("MixerMPRIS2.volumeChanged"));
}
// The following is an example message for an incoming volume change:
/*
signal sender=:1.125 -> dest=(null destination) serial=503 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
string "org.mpris.MediaPlayer2.Player"
array [
dict entry(
string "Volume"
variant double 0.81
)
]
array [
]
*/
/**
* @overload
*
* @param id
* @param md
* @return
*/
int Mixer_MPRIS2::writeVolumeToHW( const QString& id, std::shared_ptr<MixDevice> md )
{
Volume& vol = md->playbackVolume();
double volFloat = 0;
if ( ! md->isMuted() )
{
int volInt = vol.getVolume(Volume::LEFT);
volFloat = volInt/100.0;
}
QList<QVariant> arg;
arg.append(QString("org.mpris.MediaPlayer2.Player"));
arg.append(QString("Volume"));
arg << QVariant::fromValue(QDBusVariant(volFloat));
MPrisControl* mad = controls.value(id);
QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player"));
QVariant v2 = QVariant(QString("Volume"));
QVariant v3 = QVariant::fromValue(QDBusVariant(volFloat));
// QVariant v3 = QVariant(volFloat);
// I don't care too much for the reply, as I won't receive a result. Thus fire-and-forget here.
mad->propertyIfc->asyncCall("Set", v1, v2, v3);
return 0;
}
void Mixer_MPRIS2::setEnumIdHW(const QString&, unsigned int)
{
// no enums in MPRIS
}
unsigned int Mixer_MPRIS2::enumIdHW(const QString&)
{
// no enums in MPRIS
return 0;
}
bool Mixer_MPRIS2::moveStream( const QString&, const QString& )
{
// not supported in MPRIS
return false;
}
/**
* Adds all currently running players and then starts listening
* for changes (new players, and disappearing players).<br>
*
* @return int
**/
int Mixer_MPRIS2::addAllRunningPlayersAndInitHotplug()
{
QDBusConnection dbusConn = QDBusConnection::sessionBus();
if (! dbusConn.isConnected() )
{
kError(67100) << "Cannot connect to the D-Bus session bus.\n"
<< "To start it, run:\n"
<<"\teval `dbus-launch --auto-syntax`\n";
return Mixer::ERR_OPEN;
}
// Start listening for new Mediaplayers
bool connected = dbusConn.connect("", QString("/org/freedesktop/DBus"), "org.freedesktop.DBus", "NameOwnerChanged", this, SLOT(newMediaPlayer(QString,QString,QString)) );
if (!connected)
{
kWarning() << "MPRIS2 hotplug init failure. New Media Players will not be detected.";
}
/* Here is a small concurrency issue.
* If new players appear between registeredServiceNames() below and the connect() above these players *might* show up doubled in KMix.
* There is no simple solution (reversing could have the problem of not-adding), so we live for now with it.
*/
/*
* Bug 311189: Introspecting via "dbusConn.interface()->registeredServiceNames()" does not work too well.
* Comment: I am not so sure that registeredServiceNames() is really an issue. It is more likely
* in a later step, when talking to the probed apps. Still, I now do a hand crafted 3-line version of
* registeredServiceNames() via "ListNames", so I can later more easily change to async.
*/
QDBusInterface dbusIfc("org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", dbusConn);
QDBusPendingReply<QStringList> repl = dbusIfc.asyncCall("ListNames");
repl.waitForFinished();
if (! repl.isValid() )
{
kError() << "Invalid reply while listing Media Players. MPRIS2 players will not be available." << repl.error();
return 1;
}
foreach (QString busDestination , repl.value() )
{
if ( busDestination.startsWith("org.mpris.MediaPlayer2") )
{
addMprisControlAsync(busDestination);
kDebug() << "MPRIS2: Attached media player on busDestination=" << busDestination;
}
}
return 0;
}
QString Mixer_MPRIS2::busDestinationToControlId(const QString& busDestination)
{
const QString prefix = "org.mpris.MediaPlayer2.";
if (! busDestination.startsWith(prefix))
{
kWarning() << "Ignoring unsupported control, busDestination=" << busDestination;
return QString();
}
return busDestination.mid(prefix.length());
}
/**
* Asynchronously add the MPRIS control designated by the DBUS busDestination.
* to the internal apps list.
*
* @param conn An open connection to the DBUS Session Bus
* @param busDestination The DBUS busDestination, e.g. "org.mpris.MediaPlayer2.amarok"
*/
void Mixer_MPRIS2::addMprisControlAsync(QString busDestination)
{
// -1- Create a MPrisControl. Its fields will be filled partially here, partially via ASYNC DUBUS replies
QString id = busDestinationToControlId(busDestination);
kDebug() << "Get control of busDestination=" << busDestination << "id=" << id;
QDBusConnection conn = QDBusConnection::sessionBus();
QDBusInterface *qdbiProps = new QDBusInterface(QString(busDestination), QString("/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties", conn, this);
QDBusInterface *qdbiPlayer = new QDBusInterface(QString(busDestination), QString("/org/mpris/MediaPlayer2"), "org.mpris.MediaPlayer2.Player", conn, this);
// -2- Add the control to our official control list
MPrisControl* mad = new MPrisControl(id, busDestination);
mad->propertyIfc = qdbiProps;
mad->playerIfc = qdbiPlayer;
controls.insert(id, mad);
/*
* WTF: - asyncCall("Get", arg) : returns an error message (see below)
* - asyncCallWithArgumentList("Get", arg) : returns an error message (see below)
* - callWithArgumentList(QDBus::Block, "Get", arg) : works
* - syncCall("Get", v1, v2) : works
*
* kmix(13543) Mixer_MPRIS2::addMPrisControl: (marok), msg2= QDBusMessage(type=Error, service=":1.44", error name="org.freedesktop.DBus.Error.UnknownMethod", error message="No such method 'Get' in interface 'org.freedesktop.DBus.Properties' at object path '/org/mpris/MediaPlayer2' (signature 'av')", signature="s", contents=("No such method 'Get' in interface 'org.freedesktop.DBus.Properties' at object path '/org/mpris/MediaPlayer2' (signature 'av')") ) , isValid= false , isFinished= true , isError= true
*
* This behavior is total counter-intuitive :-(((
*/
// Create ASYNC DBUS queries for the new control. This effectively starts a chain of async DBUS commands.
QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2"));
QVariant v2 = QVariant(QString("Identity"));
QDBusPendingReply<QVariant > repl2 = mad->propertyIfc->asyncCall("Get", v1, v2);
QDBusPendingCallWatcher* watchIdentity = new QDBusPendingCallWatcher(repl2, mad);
connect(watchIdentity, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherPlugControlId(QDBusPendingCallWatcher *)));
}
MixDevice::ChannelType Mixer_MPRIS2::getChannelTypeFromPlayerId(const QString& id)
{
// TODO This hardcoded application list is a quick hack. It should be generalized.
MixDevice::ChannelType ct = MixDevice::APPLICATION_STREAM;
if (id.startsWith("amarok"))
{
ct = MixDevice::APPLICATION_AMAROK;
}
else if (id.startsWith("banshee"))
{
ct = MixDevice::APPLICATION_BANSHEE;
}
else if (id.startsWith("vlc"))
{
ct = MixDevice::APPLICATION_VLC;
}
else if (id.startsWith("xmms"))
{
ct = MixDevice::APPLICATION_XMM2;
}
else if (id.startsWith("tomahawk"))
{
ct = MixDevice::APPLICATION_TOMAHAWK;
}
else if (id.startsWith("clementine"))
{
ct = MixDevice::APPLICATION_CLEMENTINE;
}
return ct;
}
void Mixer_MPRIS2::watcherInitialVolume(QDBusPendingCallWatcher* watcher)
{
MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl == 0)
return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged)
const QDBusMessage& msg = watcher->reply();
QList<QVariant> repl = msg.arguments();
if ( ! repl.isEmpty() )
{
QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(repl.at(0));
QVariant result2 = dbusVariant.variant();
double volume = result2.toDouble();
volumeChanged(mprisCtl, volume);
}
watcher->deleteLater();
}
void Mixer_MPRIS2::watcherInitialPlayState(QDBusPendingCallWatcher* watcher)
{
MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl == 0)
return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged)
const QDBusMessage& msg = watcher->reply();
QList<QVariant> repl = msg.arguments();
if ( ! repl.isEmpty() )
{
QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(repl.at(0));
QVariant result2 = dbusVariant.variant();
QString playbackStateString = result2.toString();
MediaController::PlayState playState = Mixer_MPRIS2::mprisPlayStateString2PlayState(playbackStateString);
playbackStateChanged(mprisCtl, playState);
}
watcher->deleteLater();
}
/**
* Convenience method for the watcher*() methods.
* Returns the MPrisControl that is parent of the given watcher, if the reply is valid. In this case you can
* use the result and call watcher->deleteLater() after processing the result.
*
* Otherwise 0 is returned, and watcher->deleteLater() is called. <b>Important</b> You must call watcher->deleteLater()
* yourself for the other (normal/good) case.
*
* @param watcher
* @return
*/
MPrisControl* Mixer_MPRIS2::watcherHelperGetMPrisControl(QDBusPendingCallWatcher* watcher)
{
const QDBusMessage& msg = watcher->reply();
if ( msg.type() == QDBusMessage::ReplyMessage )
{
QObject* obj = watcher->parent();
MPrisControl* mad = qobject_cast<MPrisControl*>(obj);
if (mad != 0)
{
return mad;
}
kWarning() << "Ignoring unexpected Control Id. object=" << obj;
}
else if ( msg.type() == QDBusMessage::ErrorMessage )
{
kError() << "ERROR in Media control operation, path=" << msg.path() << ", msg=" << msg;
}
watcher->deleteLater();
return 0;
}
void Mixer_MPRIS2::watcherPlugControlId(QDBusPendingCallWatcher* watcher)
{
MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl == 0)
{
return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged)
}
const QDBusMessage& msg = watcher->reply();
QString id = mprisCtl->getId();
QString busDestination = mprisCtl->getBusDestination();
QString readableName = id; // Start with ID, but replace with reply (if exists)
kDebug() << "Plugging id=" << id << ", busDestination" << busDestination << ", name= " << readableName;
QList<QVariant> repl = msg.arguments();
if ( ! repl.isEmpty() )
{
// We have to do some very ugly casting from QVariant to QDBusVariant to QVariant. This API totally sucks.
QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(repl.at(0));
QVariant result2 = dbusVariant.variant();
readableName = result2.toString();
// kDebug() << "REPLY " << result2.type() << ": " << readableName;
MixDevice::ChannelType ct = getChannelTypeFromPlayerId(id);
MixDevice* mdNew = new MixDevice(_mixer, id, readableName, ct);
// MPRIS2 doesn't support an actual mute switch. Mute is defined as volume = 0.0
// Thus we won't add the playback switch
Volume* vol = new Volume( 100, 0, false, false);
vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); // MPRIS is only one control ("Mono")
MediaController* mediaContoller = mdNew->getMediaController();
mediaContoller->addMediaPlayControl();
mediaContoller->addMediaNextControl();
mediaContoller->addMediaPrevControl();
mdNew->setApplicationStream(true);
mdNew->addPlaybackVolume(*vol);
m_mixDevices.append( mdNew->addToPool() );
delete vol; // vol is only temporary. mdNew has its own volume object. => delete
QDBusConnection sessionBus = QDBusConnection::sessionBus();
sessionBus.connect(busDestination, QString("/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties", "PropertiesChanged", mprisCtl, SLOT(onPropertyChange(QString,QVariantMap,QStringList)) );
connect(mprisCtl, SIGNAL(volumeChanged(MPrisControl*,double)), this, SLOT(volumeChanged(MPrisControl*,double)) );
connect(mprisCtl, SIGNAL(playbackStateChanged(MPrisControl*,MediaController::PlayState)), SLOT (playbackStateChanged(MPrisControl*,MediaController::PlayState)) );
sessionBus.connect(busDestination, QString("/Player"), "org.freedesktop.MediaPlayer", "TrackChange", mprisCtl, SLOT(trackChangedIncoming(QVariantMap)) );
// The following line is evil: mad->playerIfc->property("Volume") is in fact a synchronous call, and
// sync calls are strictly forbidden, see bug 317926
//volumeChanged(mad, mad->playerIfc->property("Volume").toDouble());
// --- Query initial state --------------------------------------------------------------------------------
QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player"));
QVariant v2 = QVariant(QString("Volume"));
QDBusPendingReply<QVariant > repl2 = mprisCtl->propertyIfc->asyncCall("Get", v1, v2);
QDBusPendingCallWatcher* watcherOutgoing = new QDBusPendingCallWatcher(repl2, mprisCtl);
connect(watcherOutgoing, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherInitialVolume(QDBusPendingCallWatcher *)));
v2 = QVariant(QString("PlaybackStatus"));
repl2 = mprisCtl->propertyIfc->asyncCall("Get", v1, v2);
watcherOutgoing = new QDBusPendingCallWatcher(repl2, mprisCtl);
connect(watcherOutgoing, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(watcherInitialPlayState(QDBusPendingCallWatcher *)));
// Push notifyToReconfigureControls to stack, so it will not be executed synchronously
announceControlListAsync(id);
}
watcher->deleteLater();
}
// -----------------------------------------------------------------------------------------------------------
// ASYNC announce slots, including convenience wrappers
// -----------------------------------------------------------------------------------------------------------
/**
* Convenience wrapper to do the ASYNC call to #announceControlList()
* @param
*/
void Mixer_MPRIS2::announceControlListAsync(QString /*streamId*/)
{
// currently we do not use the streamId
QMetaObject::invokeMethod(this, "announceControlList", Qt::QueuedConnection);
}
void Mixer_MPRIS2::announceControlList()
{
ControlManager::instance().announce(_mixer->id(), ControlChangeType::ControlList, getDriverName());
}
void Mixer_MPRIS2::announceGUI()
{
ControlManager::instance().announce(_mixer->id(), ControlChangeType::GUI, getDriverName());
}
void Mixer_MPRIS2::announceVolume()
{
ControlManager::instance().announce(_mixer->id(), ControlChangeType::Volume, getDriverName());
}
// -----------------------------------------------------------------------------------------------------------
/**
* Handles the hotplug of new MPRIS2 enabled Media Players
*/
void Mixer_MPRIS2::newMediaPlayer(QString name, QString oldOwner, QString newOwner)
{
if ( name.startsWith("org.mpris.MediaPlayer2") )
{
if ( oldOwner.isEmpty() && !newOwner.isEmpty())
{
kDebug() << "Mediaplayer registers: " << name;
addMprisControlAsync(name);
}
else if ( !oldOwner.isEmpty() && newOwner.isEmpty())
{
QString id = busDestinationToControlId(name);
kDebug() << "Mediaplayer unregisters: " << name << " , id=" << id;
// -1- Remove Mediaplayer connection
if (controls.contains(id))
{
const MPrisControl *control = controls.value(id);
QObject::disconnect(control,0,0,0);
controls.remove(id);
}
// -2- Remove MixDevice from internal list
std::shared_ptr<MixDevice> md = m_mixDevices.get(id);
if (md)
{
// We know about the player that is unregistering => remove internally
md->close();
m_mixDevices.removeById(id);
announceControlListAsync(id);
kDebug() << "MixDevice 4 useCount=" << md.use_count();
}
}
else
{
kWarning() << "Mediaplayer has registered under a new name. This is currently not supported by KMix";
}
}
}
/**
* This slot is a simple proxy that enriches the DBUS signal with our data, which especially contains the id of the MixDevice.
*/
void MPrisControl::trackChangedIncoming(QVariantMap /*msg*/)
{
kDebug() << "Track changed";
}
MediaController::PlayState Mixer_MPRIS2::mprisPlayStateString2PlayState(const QString& playbackStatus)
{
MediaController::PlayState playState = MediaController::PlayUnknown;
if (playbackStatus == "Playing")
{
playState = MediaController::PlayPlaying;
}
else if (playbackStatus == "Stopped")
{
playState = MediaController::PlayStopped;
}
else if (playbackStatus == "Paused")
{
playState = MediaController::PlayPaused;
}
return playState;
}
/**
* This slot is a simple proxy that enriches the DBUS signal with our data, which especially contains the id of the MixDevice.
*/
void MPrisControl::onPropertyChange(QString /*ifc*/,QVariantMap msg ,QStringList /*sl*/)
{
QMap<QString, QVariant>::iterator v = msg.find("Volume");
if (v != msg.end() )
{
double volDouble = v.value().toDouble();
kDebug(67100) << "volumeChanged incoming: vol=" << volDouble;
emit volumeChanged( this, volDouble);
}
v = msg.find("PlaybackStatus");
if (v != msg.end() )
{
QString playbackStatus = v.value().toString();
MediaController::PlayState playState = Mixer_MPRIS2::mprisPlayStateString2PlayState(playbackStatus);
kDebug() << "PlaybackStatus is now " << playbackStatus;
emit playbackStateChanged(this, playState);
}
}
Mixer_MPRIS2::~Mixer_MPRIS2()
{
close();
}
MPrisControl::MPrisControl(QString id, QString busDestination)
: propertyIfc(0)
, playerIfc(0)
{
volume = 0;
this->id = id;
this->busDestination = busDestination;
retrievedElems = MPrisControl::NONE;
}
MPrisControl::~MPrisControl()
{
delete propertyIfc;
delete playerIfc;
}
QString Mixer_MPRIS2::getDriverName()
{
return "MPRIS2";
}
QString MPRIS2_getDriverName()
{
return "MPRIS2";
}
#include "moc_mixer_mpris2.cpp"