diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index 9ea00a2c..d1e22e3d 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -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();
+
+
{#if user.value !== null && page.url.pathname !== '/login'}
{@render children()}