import { timeout } from "ags/time"; import { execAsync } from "ags/process"; import { Astal, Gtk } from "ags/gtk4"; import { Clipboard } from "../../scripts/clipboard"; import { player } from "../bar/Media"; import { createBinding, With } from "ags"; import AstalMpris from "gi://AstalMpris"; import AstalIO from "gi://AstalIO"; import Gio from "gi://Gio?version=2.0"; import Pango from "gi://Pango?version=1.0"; export const BigMedia = () => { let dragTimer: (AstalIO.Time|undefined); return pl.available)}> {(player: AstalMpris.Player) => player.available && { const artSub = createBinding(player, "artUrl").subscribe(() => { const firstChild = self.get_first_child(); const albumArt = getAlbumArt(player); if(!albumArt) { if(firstChild instanceof Gtk.Picture) self.remove(firstChild); return; } if(firstChild instanceof Gtk.Picture) { firstChild.set_filename(albumArt); return; } self.prepend( as Gtk.Picture ); }); const destroyId = self.connect("destroy", () => { self.disconnect(destroyId); artSub(); }); }}> title ?? "No Title") } ellipsize={Pango.EllipsizeMode.END} maxWidthChars={25} /> artist ?? "No Artist") } ellipsize={Pango.EllipsizeMode.END} maxWidthChars={28} /> { if(type === undefined || type === null) return; if(!dragTimer) { dragTimer = timeout(200, () => player.position = Math.floor(value)); return; } dragTimer.cancel(); dragTimer = timeout(200, () => player.position = Math.floor(value)); }} /> { const sec = Math.floor(pos % 60); return pos > 0 && player.length > 0 ? `${Math.floor(pos / 60)}:${sec < 10 ? "0" : ""}${sec}` : "0:00"; })} /> { execAsync(`playerctl --player=${ player.busName.replace(/^org\.mpris\.MediaPlayer2\./i, "") } metadata xesam:url`).then(link => { Clipboard.getDefault().copyAsync(link); }).catch((e: Error) => { console.error(`Media: couldn't copy media link. Stderr: \n${e.message}\n${e.stack}`); }); }} /> status !== AstalMpris.Shuffle.UNSUPPORTED)} iconName={ createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ? "media-playlist-shuffle-symbolic" : "media-playlist-consecutive-symbolic")} tooltipText={ createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ? "Shuffle" : "No shuffle")} onClicked={player.shuffle} /> player.canGoPrevious && player.previous()} /> status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play")} iconName={createBinding(player, "playbackStatus").as(status => status === AstalMpris.PlaybackStatus.PLAYING ? "media-playback-pause-symbolic" : "media-playback-start-symbolic")} onClicked={player.play_pause} /> player.canGoNext && player.next()} /> { if(status === AstalMpris.Loop.TRACK) return "media-playlist-repeat-song-symbolic"; if(status === AstalMpris.Loop.PLAYLIST) return "media-playlist-repeat-symbolic"; return "loop-arrow-symbolic"; })} visible={createBinding(player, "loopStatus").as(status => status !== AstalMpris.Loop.UNSUPPORTED)} tooltipText={createBinding(player, "loopStatus").as(status => { if(status === AstalMpris.Loop.TRACK) return "Loop song"; if(status === AstalMpris.Loop.PLAYLIST) return "Loop playlist"; return "No loop"; })} onClicked={player.loop} /> { /* bananananananana */ const sec = Math.floor(len % 60); return (len > 0 && Number.isFinite(len)) ? `${Math.floor(len / 60)}:${sec < 10 ? "0" : ""}${sec}` : "0:00"; })} /> } as Gtk.Box; } /** * This function handles album art/cover of playing media. If a file is provided * by the player, it adds the "file://" uri as a prefix, so you can use it in css. * * @param player the player you want to pull album art from * @returns Binding to player.artUrl containing the album art uri, or an undefined binding ig none was found. * */ function getAlbumArt(player: AstalMpris.Player): string|undefined { const artUrl = player.artUrl; if(!artUrl) return undefined; if(artUrl.startsWith("/")) return "file://" + artUrl; return artUrl; }