feat(web): mount <audio> at root layout and wire it to the player store
Single hidden <audio> survives navigation. Effects drive it from the store (src, volume, play/pause) and DOM events report back (timeupdate, loadedmetadata, playing, pause, waiting, ended, error). useMediaSession is called once here so OS media controls light up as soon as the user signs in.
This commit is contained in:
@@ -6,8 +6,17 @@
|
||||
import { queryClient } from '$lib/query/client';
|
||||
import { user } from '$lib/auth/store.svelte';
|
||||
import Shell from '$lib/components/Shell.svelte';
|
||||
import {
|
||||
registerAudioEl,
|
||||
reportTimeUpdate,
|
||||
reportDuration,
|
||||
reportStateFromAudio,
|
||||
player
|
||||
} from '$lib/player/store.svelte';
|
||||
import { useMediaSession } from '$lib/player/mediaSession.svelte';
|
||||
|
||||
let { children } = $props<{ children: import('svelte').Snippet }>();
|
||||
let audioEl: HTMLAudioElement | undefined = $state();
|
||||
|
||||
// Reactive guard: runs every time page.url or user.value changes.
|
||||
$effect(() => {
|
||||
@@ -20,8 +29,52 @@
|
||||
goto('/', { replaceState: true });
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (audioEl) registerAudioEl(audioEl);
|
||||
return () => registerAudioEl(null);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!audioEl) return;
|
||||
const src = player.current?.stream_url;
|
||||
if (src) {
|
||||
const absolute = new URL(src, window.location.origin).href;
|
||||
if (audioEl.src !== absolute) audioEl.src = src;
|
||||
} else {
|
||||
audioEl.removeAttribute('src');
|
||||
audioEl.load();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (audioEl) audioEl.volume = player.volume;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!audioEl) return;
|
||||
if (player.isPlaying) {
|
||||
audioEl.play().catch(() => { /* interrupted / blocked — store gets the event */ });
|
||||
} else {
|
||||
audioEl.pause();
|
||||
}
|
||||
});
|
||||
|
||||
useMediaSession();
|
||||
</script>
|
||||
|
||||
<audio
|
||||
bind:this={audioEl}
|
||||
preload="metadata"
|
||||
ontimeupdate={() => audioEl && reportTimeUpdate(audioEl.currentTime)}
|
||||
onloadedmetadata={() => audioEl && reportDuration(audioEl.duration)}
|
||||
onplaying={() => reportStateFromAudio('playing')}
|
||||
onpause={() => reportStateFromAudio('paused')}
|
||||
onwaiting={() => reportStateFromAudio('waiting')}
|
||||
onended={() => reportStateFromAudio('ended')}
|
||||
onerror={() => reportStateFromAudio('error', audioEl?.error?.message)}
|
||||
></audio>
|
||||
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{#if user.value !== null && page.url.pathname !== '/login'}
|
||||
<Shell>{@render children()}</Shell>
|
||||
|
||||
Reference in New Issue
Block a user