MPRIS: media players with track metadata, playback state and position.
local player = mantle.mpris:map(function(mpris)
return mpris and mpris.players[1]
end)
button {
on_click = function()
local current = player:get()
if current then
mantle.mpris:control(current.id, "play_pause")
end
end,
children = {
text {
max_width = 240,
elide = "End",
content = player:map(function(current)
return current and (current.play_state .. ": " .. current.title) or ""
end),
},
},
}
mantle.mpris:get() returns MprisState, nil before the first push. A field marked ? may be absent.
Field Type Description
playersPlayerState[]Every controllable MPRIS player except playerctld, longest-running first, so players[1] stays put; empty when none runs.
Field Type Description
album_art_pathstringCover art as an existing absolute path, or empty; a remote artUrl is not fetched.
artiststringArtists joined with ", "; empty when unset.
desktop_entrystringThe player’s .desktop basename, e.g. "firefox", for app matching; empty when unset.
idstringBus-name suffix after org.mpris.MediaPlayer2., e.g. "spotify"; every action takes it.
identitystringDisplay name, e.g. "Spotify"; empty if unanswered.
lengthintegerTrack length in microseconds, or -1 when unknown, as for a live stream.
play_statestring"Playing", "Paused" or "Stopped"; keeps the last value when a read fails, empty if none.
positionintegerPlayback offset in microseconds as of position_updated_at, not polled while playing: add elapsed time. -1 when unknown.
position_updated_atintegerCLOCK_MONOTONIC microseconds when position was read. No Lua clock shares this epoch (not mantle.system.monotonic); only compare it with itself.
titlestringTrack title; empty when unset, normal between tracks.
urlstringxesam:url as sent, e.g. a file:// path or an https:// page; empty when unset.
Call each as mantle.mpris:<action>(arguments...); ? marks an argument you may omit.
Action Arguments Description
controlid: string, cmd: PlayerCommandSends a playback command to players[].id.
seekid: string, position_us: integerSeeks to an absolute position in microseconds, clamped to [0, length] (only >= 0 when length is -1).
seek_relativeid: string, offset_us: integerSeeks by a signed offset in microseconds, unclamped; past the end may skip to the next track.
One of "play", "pause", "play_pause", "next", "previous".
Contract Behavior
Discovery Session bus ListNames once, then NameOwnerChanged for org.mpris.MediaPlayer2.*. Skips playerctld and any player reporting CanControl = false
Pushes On a PlaybackStatus or Metadata change and on Seeked. A status change re-reads Position 100 ms later. Nothing polls
Seek seek calls SetPosition with the cached mpris:trackid. A player without one gets a relative Seek from a live Position read
Task Answer
Show a live progress bar Stamp each new position_updated_at with mantle.system.monotonic in on_change, then add the seconds since: Media player
Trap Fix
position stands still while playingIt is the offset at position_updated_at, not polled. Extrapolate, as above
position_updated_at compared with mantle.system.monotonic gives nonsenseDifferent clocks and units: CLOCK_MONOTONIC microseconds against seconds since system started. Only compare it with itself
See also: Media player recipe.
Source: supervisor/src/capabilities/mpris/