The media provider wraps the platform’s session APIs (MPRIS on Linux, GSMTC
on Windows) into one model. Read side under scope media, verbs under
media.control.
The model
| Field | Type | Meaning |
|---|---|---|
hasPlayer | bool | any session exists |
title / artist | string | current track |
artUrl | string | album art — https://, or a data: URI for local art |
playing | bool | play/pause state |
dur / pos | number | length / position, seconds |
on_media(host, m) — scope media
The event stream. Fires on every state change and position poll (~2/s while playing), always with the current model.
function M.on_media(host, m)
host.set_presence("nowplaying", m.hasPlayer, {
title = m.title, artist = m.artist, artUrl = m.artUrl,
playing = m.playing,
posFrac = m.dur > 0 and m.pos / m.dur or -1,
})
end
perch.media.read() — scope media
local m = perch.media.read()
Pull the last full model. One use case: seeding on_start so a package
starting mid-song doesn’t wait for the next event.
perch.media.control(verb) — scope media.control
perch.media.control("playPause")
perch.media.control("seek:132") -- absolute seconds, integer
| Verb | Effect |
|---|---|
playPause | toggle |
next / previous | track skip |
seek:<seconds> | absolute seek (non-negative integer) |
openPlayer | raise the playing app |
poke | ask the source to re-emit state |
The verb list is a hard whitelist — anything else is refused and logged. Wiring a seek bar end-to-end:
-- displays.lua
{ type = "bar", bind = "posFrac", seek = true, knob = true, action = "seek:{frac}" }
-- logic.lua (frac arrives 0..1; the verb wants seconds)
function M.on_command(host, widget, action)
if action:sub(1, 5) == "seek:" then
perch.media.control("seek:" .. math.floor(tonumber(action:sub(6)) * dur))
end
end
The visualizer
You don’t get raw audio — the viz component’s level feed is produced by
the core capture pipeline (running only while something plays) and arrives
through the builtin media payload. For your own widgets, drive a viz
with any 0–1 level array you compute.