@jackyalciné

Got an app idea or need some coding done? I can help! Learn more about how you & I can work together.

MPRIS and KDE Playing Nice

Learning about how KDE (and OpenDesktop-compliant) environments interact with media players.

:book: thoughts :bookmark: kde, mpris, code, pulseaudio :clock7: :clock3: about 3 minutes

I use KDE and I have a collection of music playing as well as video playing applications on my machine (sometimes running at the same time, but not playing). For example, I might have Amarok open when I’m offline to play my offline collection of music and listen to podcasts as well; but switch to Spotify when I get reconnected to play the tracks I have in my Spotify collection1. The only issue here is that my media control keys are currently hardwired to work with Spotify (since I spend quite a bit of time online). That’s changed and I’ve been spending more time offline in order to encourage me to go online when necessary. Thus, came the idea of creating a sort of multiplexer for all of the org.mpris.MediaPlayer2 services there is.

Existing Solutions

As far as I know, KMix has early adoption support for MPRIS capabilities. I’d figure it to be tricky since I wouldn’t assume Pulse or ALSA to provide information about the running media process. However, it seems that a bit of information is provided by Pulse. I ran the following:

$ pacmd
Welcome to PulseAudio! Use "help" for usage information.
>>> list-clients
  ....
    index: 20
        driver: <protocol-native.c>
        owner module: 9
        properties:
                application.name = "libphonon"
                native-protocol.peer = "UNIX socket client"
                native-protocol.version = "28"
                media.role = "music"
                phonon.streamid = "{12299553-cacf-43a8-a9e0-c603c8e82997}"
                application.process.id = "29439"
                application.process.user = "jacky"
                application.process.host = "neuromancer"
                application.process.binary = "amarok"
                application.language = "en_US.UTF-8"
                window.x11.display = ":0"
                application.process.machine_id = "f8b2b7ce8af8a524e8eb855c0000000b"
                application.process.session_id = "c1"
                application.icon_name = "amarok"
  ....

It actually returns the running process ID, running language(!), and user of the media process so that earlier thought of lack of information is thrown out of the window. This is probably how KMix is able to show icon and name information for streams.

Investigation

MPRIS is heavily rooted in the use of D-Bus, so I decided to do a little search in D-Bus services running on the session bus when I had Spotify, VLC and Amarok running concurrently.

$ qdbus | grep MediaPlayer2
 org.mpris.MediaPlayer2.amarok
 org.mpris.MediaPlayer2.spotify
 org.mpris.MediaPlayer2.vlc
 org.mpris.MediaPlayer2.vlc-8056

So far, so good. This means I can find any process running using MPRIS using the following C++ sample2:

  QDBusConnection sessionBus;
  QDBusConnectionInterface interface;
  QStringList knownServices, mprisServices;

  sessionBus    = QDBusConnection::sessionBus();
  interface     = sessionBus.interface();
  knownServices = interface->registeredServiceNames();
  mprisServices = knownServices.filter ( "MediaPlayer2" );

All that would be enough just to make a QDBusMessage method call, but we want a bit more than that. We can assume that the MPRIS object is stashed under /org/mpris/MediaPlayer2. However, there’s one slight issue that I’ve noticed; a lack of a signal for listening to state changes in a MediaPlayer2 object.

$ qdbus org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 | grep signal
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString, QVariantMap, QStringList)
signal void org.mpris.MediaPlayer2.TrackList.TrackAdded(QVariantMap, QDBusObjectPath)
signal void org.mpris.MediaPlayer2.TrackList.TrackListReplaced(QList<QDBusObjectPath>, QDBusObjectPath)
signal void org.mpris.MediaPlayer2.TrackList.TrackMetadataChanged(QDBusObjectPath, QVariantMap)
signal void org.mpris.MediaPlayer2.TrackList.TrackRemoved(QDBusObjectPath)

That was going to be the easiest part; just listening to that to handle the switching of playing processes; but hey. However, that isn’t the only solution. I could poll the status of known players by querying the ‘PlaybackStatus’. The only issue now is how often should this polling occur? Every ten seconds sounds sensible, but every fifteen is less chronic and probably would be more appropriate.

Code

Code? We do that still? Oh nah. At the moment, I haven’t any code. But I plan to work on this in the near future. Good chance I’ll tweet when I start.

  1. Since the time of writing, I’ve changed it back to work with Amarok (since I’ve picked up podcast listening). 

  2. Make ‘interface’ into a pointer; freaking Markdown highlighting in Vim.