Perch
Lua APIperch.media

perch.media

The now-playing model and transport verbs — scopes media and media.control.

2 min readUpdated Jul 21, 2026

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

FieldTypeMeaning
hasPlayerboolany session exists
title / artiststringcurrent track
artUrlstringalbum art — https://, or a data: URI for local art
playingboolplay/pause state
dur / posnumberlength / 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
VerbEffect
playPausetoggle
next / previoustrack skip
seek:<seconds>absolute seek (non-negative integer)
openPlayerraise the playing app
pokeask 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.

esc
Type to search
navigate open