Skip to content

MPRIS player control in the odio API

The MPRIS backend auto-discovers every MPRIS-compatible player (Spotify, VLC, Firefox, MPD, Kodi, Bluetooth devices) and exposes unified playback controls. Players appear and disappear in real time — no configuration needed.

GET /players

Returns all active players with their current state: playback status, track metadata, volume, shuffle, loop mode, position, and whether the player exposes a tracklist:

[
{
"bus_name": "org.mpris.MediaPlayer2.mpd",
"identity": "Music Player Daemon",
"playback_status": "Stopped",
"loop_status": "None",
"volume": 1,
"position_updated_at": "2026-09-22T18:59:08.21841652+02:00",
"rate": 1,
"metadata": {
"mpris:artUrl": "https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/bc/56/4c/bc564c0b-b7ce-1250-5bab-91301e96ed02/3663729372383_cover.jpg/600x600bb.jpg",
"mpris:trackid": "/org/mpris/MediaPlayer2/Track/3",
"xesam:album": "La Grosse Radio Reggae - Dub Dancehall Roots - From Paris - www.LaGrosseRadio.com",
"xesam:title": "Ryon - Ma France A Nous"
},
"capabilities": {
"can_play": true,
"can_pause": true,
"can_go_next": false,
"can_go_previous": true,
"can_seek": false,
"can_control": true
},
"tracklist_supported": true
}
]

bus_name is the {player} used in every route below. Metadata values are all strings, mpris:length and position are in microseconds. loop_status, shuffle, volume, position and rate are omitted when the player doesn’t report them (or when they hold their zero value).

POST /players/{player}/play
POST /players/{player}/pause
POST /players/{player}/play_pause
POST /players/{player}/stop
POST /players/{player}/next
POST /players/{player}/previous
POST /players/{player}/seek
{ "offset": 1000000 }
POST /players/{player}/position
{ "track_id": "...", "position": 0 }
POST /players/{player}/volume
{ "volume": 0.5 }
POST /players/{player}/loop
{ "loop": "None|Track|Playlist" }
POST /players/{player}/shuffle
{ "shuffle": true }

Since odio-api v0.16.0, players implementing the MPRIS TrackList interface expose their queue. /players reports tracklist_supported per player, and the routes below answer 404 on players that don’t implement the interface.

TrackList is optional in the MPRIS spec, and most players skip it — a player advertises it through the HasTrackList root property, which is what tracklist_supported mirrors.

PlayerTracklist
MPD via mpd2mpris 0.13.0+The MPD queue, editable when the server allows queue edits
VLCIts playlist, always editable
Bluetooth devices via BlueZ mpris-proxyOnly when the connected phone or laptop exposes a browsable now-playing list over AVRCP
spotifydNot implemented, HasTrackList is hardcoded to false
Shairport SyncNot implemented, the property isn’t exposed at all
GET /players/{player}/tracklist
{
"can_edit_tracks": true,
"tracks": [
{ "track_id": "/org/mpris/MediaPlayer2/Track/42", "metadata": { "xesam:title": "..." } }
]
}

The list is served from the player cache, kept live by the TrackListReplaced, TrackAdded, TrackRemoved and TrackMetadataChanged D-Bus signals.

POST /players/{player}/tracklist/goto/{trackid}
POST /players/{player}/tracklist/remove/{trackid}
POST /players/{player}/tracklist/add
{ "uri": "file:///media/USB/album/01.flac", "after_track": "", "set_as_current": false }

{trackid} is either the last segment of the track’s object path (42) or the full %2F-encoded path. uri must be absolute and its scheme must be one the player declares in SupportedUriSchemes, otherwise the request is rejected with 400. An empty after_track appends, NoTrack prepends.

add and remove require can_edit_tracks and answer 403 otherwise. goto is not an edit operation per the spec, so it works on read-only tracklists too.

EventTrigger
player.updatedPlayback state, volume, or metadata change
player.addedNew player appeared
player.removedPlayer closed
player.positionPeriodic position tick
player.tracklist.updatedQueue replaced, or a track added, removed, or its metadata changed

The backend listens on D-Bus for org.mpris.MediaPlayer2 interfaces. Player state is cached and invalidated via D-Bus signals, with a heartbeat for accurate position tracking.