import Gtk from "gi://Gtk?version=3.0"
import GLib from "gi://GLib"
import Pango from "gi://Pango"
import AstalMpris from "gi://AstalMpris"
import { Variable, bind } from "astal"
const mpris = AstalMpris.get_default()
const PREV = "media-skip-backward-symbolic"
const PLAY = "media-playback-start-symbolic"
const PAUSE = "media-playback-pause-symbolic"
const NEXT = "media-skip-forward-symbolic"
const NO_ART = "audio-x-generic-symbolic"
function fmtTime(seconds: number) {
if (!seconds || seconds < 0) return "0:00"
const min = Math.floor(seconds / 60)
const sec = Math.floor(seconds % 60)
return `${min}:${sec < 10 ? "0" : ""}${sec}`
}
function playerIcon(entry: string) {
const name = entry ? `${entry.toLowerCase()}-symbolic` : ""
const theme = Gtk.IconTheme.get_default()!
return name !== "" && theme.has_icon(name) ? name : NO_ART
}
function PlayerCard(player: AstalMpris.Player) {
// position only changes on the bus when seeking, so poll every second;
// the ticker stops itself when the player disconnects so we never
// touch a disposed GObject
const position = Variable(-1)
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
if (!mpris.players.includes(player)) return GLib.SOURCE_REMOVE
position.set(player.available ? player.position : -1)
return GLib.SOURCE_CONTINUE
})
return (
c !== "" ? `background-image: url('${encodeURI(`file://${c}`)}')` : "",
)}
>
{/* kept allocated (opacity, not visible) so the padded box
stays 48px and the icon sits dead center with no art */}
(c !== "" ? 0 : 1))}
/>
)
}
export function MediaSection() {
const players = bind(mpris, "players")
return (
ps.length > 0)}>
{players.as((ps) => ps.map((p) => PlayerCard(p)))}
)
}
// the players property only notifies on connect/disconnect, so the bar
// button tracks the first player directly to follow track changes
let player: AstalMpris.Player | null = null
let hookIds: Array = []
const hasPlayer = Variable(false)
const title = Variable("")
const icon = Variable(NO_ART)
function sync() {
const current = mpris.players[0] ?? null
if (current !== player) {
hookIds.forEach((id) => player?.disconnect(id))
hookIds = []
player = current
if (player) {
hookIds = [
player.connect("notify::title", sync),
player.connect("notify::entry", sync),
]
}
}
hasPlayer.set(player !== null)
title.set(player?.title || "")
icon.set(player ? playerIcon(player.entry) : "")
}
mpris.connect("notify::players", sync)
sync()
export function MediaButton() {
return (
)
}