import { GTK_ALIGN_CENTER, GTK_ALIGN_FILL } from '../constants.js' const mpris = await Service.import("mpris") const players = mpris.bind("players") const FALLBACK_ICON = "audio-x-generic-symbolic" const PLAY_ICON = "media-playback-start-symbolic" const PAUSE_ICON = "media-playback-pause-symbolic" const PREV_ICON = "media-skip-backward-symbolic" const NEXT_ICON = "media-skip-forward-symbolic" /** @param {number} length */ function lengthStr(length) { const min = Math.floor(length / 60) const sec = Math.floor(length % 60) const sec0 = sec < 10 ? "0" : "" return `${min}:${sec0}${sec}` } /** @param {import('types/service/mpris').MprisPlayer} player */ function Player(player) { const artists = player.bind("track_artists").transform(a => a.join(", ")) const playPause = Widget.Button({ className: "play-pause", onClicked: () => player.playPause(), visible: player.bind("can_play"), child: Widget.Icon({ icon: player.bind("play_back_status").transform(s => { switch (s) { case "Playing": return PAUSE_ICON case "Paused": case "Stopped": return PLAY_ICON } }), }), }) const prev = Widget.Button({ onClicked: () => player.previous(), visible: player.bind("can_go_prev"), child: Widget.Icon(PREV_ICON), }) const next = Widget.Button({ onClicked: () => player.next(), visible: player.bind("can_go_next"), child: Widget.Icon(NEXT_ICON), }) const positionSlider = Widget.Slider({ className: "position", drawValue: false, onChange: ({ value }) => player.position = value * player.length, visible: player.bind("length").as(l => l > 0), setup: self => { function update() { const value = player.position / player.length self.value = value > 0 ? value : 0 } self.hook(player, update) self.hook(player, update, "position") self.poll(1000, update) }, }) const positionLabel = Widget.Label({ className: "position", hpack: "start", setup: self => { const update = (_, time) => { self.label = lengthStr(time || player.position) self.visible = player.length > 0 } self.hook(player, update, "position") self.poll(1000, update) }, }) const lengthLabel = Widget.Label({ className: "length", hpack: "end", visible: player.bind("length").transform(l => l > 0), label: player.bind("length").transform(lengthStr), }) return Widget.Overlay({ className: "player", child: Widget.Box({ className: "bg-img", vpack: "start", css: player.bind("cover_path").transform(p => `background-image: url('${p}');`), }), overlays: [ Widget.Box({ className: "bg-cover" }), Widget.Box({ className: "overlay", vertical: true, halign: GTK_ALIGN_CENTER, children: [ Widget.Box({ className: "info", vertical: true, children: [ Widget.Label({ className: "title", label: player.bind("track_title") }), Widget.Label({ className: "album", label: player.bind("track_album") }), Widget.Label({ className: "artist", label: artists }), ], }), Widget.Box({ vexpand: true, }), Widget.CenterBox({ className: "controls", startWidget: positionLabel, centerWidget: Widget.Box({ halign: GTK_ALIGN_CENTER, spacing: 20, children: [ prev, playPause, next, ], }), endWidget: lengthLabel, }), positionSlider, ], }) ], }) } function MediaContent() { const currentIdx = Variable(0); const currentPlayer = Utils.merge([currentIdx.bind(), players], (currentIdx, players) => { const idx = Math.min(currentIdx, players.length - 1); return players[idx]; }) return Widget.Box({ children: currentPlayer.as(p => [Player(p)]), }) } export function Media(monitor = 0) { return Widget.Window({ monitor, visible: false, exclusivity: "normal", className: "media", margins: [10, 10, 0, 10], name: `media${monitor}`, anchor: ["left", "top"], child: MediaContent(), }) }