156 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { bind } from "astal";
 | 
						|
import { Gtk } from "astal/gtk4";
 | 
						|
import AstalMpris from "gi://AstalMpris";
 | 
						|
import Pango from "gi://Pango?version=1.0";
 | 
						|
const ALIGN = Gtk.Align;
 | 
						|
 | 
						|
const mpris = AstalMpris.get_default();
 | 
						|
mpris.connect("player-added", p => {
 | 
						|
    print("Player added:", p);
 | 
						|
});
 | 
						|
 | 
						|
const PlayerModule = () => {
 | 
						|
    return (
 | 
						|
        <box vertical cssClasses={ [ 'players-box' ] }>
 | 
						|
            <label label={"Music Players"} halign={ALIGN.CENTER} cssClasses={[ 'title-2' ]}></label>
 | 
						|
            <Gtk.Separator marginTop={3} marginBottom={5}></Gtk.Separator>
 | 
						|
            <box cssClasses={["players"]}>
 | 
						|
                {bind(mpris, "players").as(players => {
 | 
						|
                    return players.map(player => {
 | 
						|
                        return <PlayerItem player={player}></PlayerItem>;
 | 
						|
                    });
 | 
						|
                })}
 | 
						|
            </box>
 | 
						|
            <label label={"No playback active"} visible={bind(mpris, "players").as( players => players.length === 0 )}></label>
 | 
						|
        </box>
 | 
						|
    );
 | 
						|
};
 | 
						|
 | 
						|
// TODO: Update widths
 | 
						|
const pbStatus = AstalMpris.PlaybackStatus;
 | 
						|
const PlayerItem = ({ player }: { player: AstalMpris.Player }) => {
 | 
						|
    return (
 | 
						|
        <box cssClasses={["player"]}>
 | 
						|
            <image
 | 
						|
                cssClasses={["cover-art"]}
 | 
						|
                file={bind(player, "coverArt")}
 | 
						|
                hexpand
 | 
						|
                vexpand
 | 
						|
            ></image>
 | 
						|
            <box vertical>
 | 
						|
                <label
 | 
						|
                    label={bind(player, "title").as(
 | 
						|
                        title => title ?? "Unknown title",
 | 
						|
                    )}
 | 
						|
                    cssClasses={["title"]}
 | 
						|
                    halign={ALIGN.START}
 | 
						|
                    valign={ALIGN.START}
 | 
						|
                    maxWidthChars={30}
 | 
						|
                    ellipsize={Pango.EllipsizeMode.END}
 | 
						|
                ></label>
 | 
						|
                <label
 | 
						|
                    label={bind(player, "artist").as(
 | 
						|
                        artist => artist ?? "Unknown artist",
 | 
						|
                    )}
 | 
						|
                    halign={ALIGN.START}
 | 
						|
                    valign={ALIGN.START}
 | 
						|
                    maxWidthChars={30}
 | 
						|
                    ellipsize={Pango.EllipsizeMode.END}
 | 
						|
                ></label>
 | 
						|
                <slider
 | 
						|
                    visible={bind(player, "length").as(l => l > 0)}
 | 
						|
                    value={bind(player, "position")}
 | 
						|
                    min={0}
 | 
						|
                    max={bind(player, "length")}
 | 
						|
                    onChangeValue={v =>
 | 
						|
                        player.set_position(v.get_value())
 | 
						|
                    }
 | 
						|
                ></slider>
 | 
						|
                <centerbox
 | 
						|
                    cssClasses={["actions"]}
 | 
						|
                    startWidget={
 | 
						|
                        <label
 | 
						|
                            label={bind(player, "position").as(v =>
 | 
						|
                                secondsToFriendlyTime(v),
 | 
						|
                            )}
 | 
						|
                            hexpand
 | 
						|
                            cssClasses={["position"]}
 | 
						|
                        ></label>
 | 
						|
                    }
 | 
						|
                    centerWidget={
 | 
						|
                        <box>
 | 
						|
                            <button
 | 
						|
                                visible={bind(player, "canGoPrevious")}
 | 
						|
                                child={
 | 
						|
                                    <image
 | 
						|
                                        iconName={
 | 
						|
                                            "media-skip-backward-symbolic"
 | 
						|
                                        }
 | 
						|
                                    ></image>
 | 
						|
                                }
 | 
						|
                                onClicked={() => player.previous()}
 | 
						|
                            ></button>
 | 
						|
                            <button
 | 
						|
                                visible={bind(player, "canControl")}
 | 
						|
                                child={
 | 
						|
                                    <image
 | 
						|
                                        iconName={bind(
 | 
						|
                                            player,
 | 
						|
                                            "playbackStatus",
 | 
						|
                                        ).as(status => {
 | 
						|
                                            if (status === pbStatus.PLAYING) {
 | 
						|
                                                return "media-playback-pause-symbolic";
 | 
						|
                                            } else {
 | 
						|
                                                return "media-playback-start-symbolic";
 | 
						|
                                            }
 | 
						|
                                        })}
 | 
						|
                                    ></image>
 | 
						|
                                }
 | 
						|
                                onClicked={() => player.play_pause()}
 | 
						|
                            ></button>
 | 
						|
                            <button
 | 
						|
                                visible={bind(player, "canGoNext")}
 | 
						|
                                child={
 | 
						|
                                    <image
 | 
						|
                                        iconName={"media-skip-forward-symbolic"}
 | 
						|
                                    ></image>
 | 
						|
                                }
 | 
						|
                                onClicked={() => player.next()}
 | 
						|
                            ></button>
 | 
						|
                        </box>
 | 
						|
                    }
 | 
						|
                    endWidget={
 | 
						|
                        <label
 | 
						|
                            cssClasses={["length"]}
 | 
						|
                            hexpand
 | 
						|
                            label={bind(player, "length").as(v =>
 | 
						|
                                secondsToFriendlyTime(v),
 | 
						|
                            )}
 | 
						|
                        ></label>
 | 
						|
                    }
 | 
						|
                ></centerbox>
 | 
						|
            </box>
 | 
						|
        </box>
 | 
						|
    );
 | 
						|
};
 | 
						|
 | 
						|
const secondsToFriendlyTime = (time: number) => {
 | 
						|
    const m = Math.floor(time / 60);
 | 
						|
    const minutes = Math.floor(m % 60);
 | 
						|
    const hours = Math.floor(m / 60 % 24);
 | 
						|
    const seconds = Math.floor(time % 60);
 | 
						|
    if (hours > 0) {
 | 
						|
        return `${hours}:${expandTime(minutes)}:${expandTime(seconds)}`;
 | 
						|
    } else {
 | 
						|
        return `${minutes}:${expandTime(seconds)}`;
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
const expandTime = (time: number): string => {
 | 
						|
    return time < 10 ? `0${time}` : "" + time;
 | 
						|
};
 | 
						|
 | 
						|
export default {
 | 
						|
    PlayerModule,
 | 
						|
};
 |