Files
minstrel/web/src/lib/player/mediaSession.svelte.ts
T
bvandeusen 4189ef9899 feat(web): add MediaSession glue for OS media controls
useMediaSession() wires navigator.mediaSession metadata + action
handlers (play/pause/previous/next/seekto) + positionState to the
player store via $effect. Exports nothing stateful — callers just
invoke it once at root-layout mount.
2026-04-24 21:03:01 -04:00

57 lines
1.4 KiB
TypeScript

import {
player,
togglePlay,
skipNext,
skipPrev,
seekTo
} from './store.svelte';
export function useMediaSession(): void {
if (typeof navigator === 'undefined' || !('mediaSession' in navigator)) return;
const ms = navigator.mediaSession;
$effect(() => {
const t = player.current;
if (!t) {
ms.metadata = null;
return;
}
ms.metadata = new MediaMetadata({
title: t.title,
artist: t.artist_name,
album: t.album_title,
artwork: [{
src: `/api/albums/${t.album_id}/cover`,
sizes: '512x512',
type: 'image/jpeg'
}]
});
});
ms.setActionHandler('play', () => togglePlay());
ms.setActionHandler('pause', () => togglePlay());
ms.setActionHandler('previoustrack', () => skipPrev());
ms.setActionHandler('nexttrack', () => skipNext());
ms.setActionHandler('seekto', (e) => {
if (typeof e.seekTime === 'number') seekTo(e.seekTime);
});
$effect(() => {
if (player.duration > 0 && 'setPositionState' in ms) {
try {
ms.setPositionState({
duration: player.duration,
position: Math.min(player.position, player.duration),
playbackRate: 1
});
} catch {
// setPositionState can throw on NaN/Infinity briefly between tracks.
}
}
});
$effect(() => {
ms.playbackState = player.isPlaying ? 'playing' : 'paused';
});
}