neyuki 0.6.3

A small Lua-like scripting language implemented in Rust
-- A pretend music player: it shows its "track" in the OS media controls (the
-- volume flyout on Windows, the MPRIS applet on Linux, Control Center on
-- macOS) and follows the play/pause, next and previous keys, wherever they
-- are pressed. Nothing is actually played.
--
--   neyuki examples/media.nyk [artwork.png]

local ui = require("@neyuki/ui")
local os = require("@neyuki/os")
local math = require("@neyuki/math")

local tracks = {
    { title = "Morning Light", artist = "The Placeholders", album = "Demo Tapes", duration = 184 },
    { title = "Second Wind", artist = "The Placeholders", album = "Demo Tapes", duration = 212 },
    { title = "Long Way Home", artist = "Sample Rate", album = "Loops", duration = 245 },
}
local current = 1
local position = 0
local playing = true
local log = {}

local artworkPath = os.args()[1]
local artwork = nil
if (artworkPath != nil) then artwork = ui.loadImage(artworkPath) end

local win = ui.open({ title = "Neyuki Player", width = 480, height = 260 })

function publish()
    local track = tracks[current]
    local ok, err = ui.mediaSession({
        title = track.title,
        artist = track.artist,
        album = track.album,
        artwork = artwork,
        duration = track.duration,
        position = position,
        playing = playing,
    })
    if (not ok) then note("no media controls: " .. tostring(err)) end
end

function skip(by: number)
    current = (current - 1 + by) % #tracks + 1
    position = 0
    publish()
end

function note(line: string)
    table.insert(log, line)
    if (#log > 5) then table.remove(log, 1) end
end

publish()

ui.run(function(events: {Event}, dt: number)
    for _, event in events do
        if (event.kind == "keydown" and event.key == "escape") then
            win:close()
            return
        elseif (event.kind == "mediakey") then
            note("mediakey " .. event.key)
            if (event.key == "playpause") then playing = not playing
            elseif (event.key == "play") then playing = true
            elseif (event.key == "pause" or event.key == "stop") then playing = false
            elseif (event.key == "next") then skip(1)
            elseif (event.key == "previous") then skip(-1)
            end
            publish()
        elseif (event.kind == "mediasession" and event.action == "seek") then
            local track = tracks[current]
            local target = event.position
            if (target == nil) then target = position + event.offset end
            position = math.clamp(target, 0, track.duration)
            note("seek to " .. tostring(math.floor(position)))
            publish()
        end
    end

    local track = tracks[current]
    if (playing) then
        position += dt
        if (position >= track.duration) then skip(1) end
    end

    win:clear("#1e1e2e")
    win:text(24, 24, track.title, { size = 26, color = "#cdd6f4" })
    win:text(24, 60, track.artist .. " - " .. track.album, { size = 16, color = "#a6adc8" })
    local w, _ = win:size()
    local barWidth = w - 48
    win:fillRect(24, 100, barWidth, 6, "#313244", { radius = 3 })
    win:fillRect(24, 100, barWidth * position / track.duration, 6, "#89b4fa", { radius = 3 })
    local status = "paused"
    if (playing) then status = "playing" end
    win:text(24, 116, status .. "  " .. tostring(math.floor(position)) .. " / " .. tostring(track.duration) .. " s",
        { size = 14, color = "#a6adc8" })
    for i, line in log do
        win:text(24, 140 + i * 18, line, { size = 13, color = "#6c7086" })
    end
    win:present()
end)