From 4794d2fbb0c00e2b5f40aed647060b4c5edc1cc8 Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Fri, 6 Feb 2026 03:54:42 -0800 Subject: [PATCH] player scrubber --- src/lib/components/GlobalPlayer.svelte | 70 +++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/lib/components/GlobalPlayer.svelte b/src/lib/components/GlobalPlayer.svelte index 14c40d5..de75c73 100644 --- a/src/lib/components/GlobalPlayer.svelte +++ b/src/lib/components/GlobalPlayer.svelte @@ -62,6 +62,11 @@ let currentTime = $state(0); let duration = $state(0); + // Seek scrubber value that both mobile + desktop sliders bind to. + // We keep it in sync with `currentTime` unless the user is actively dragging. + let scrubValue = $state(0); + let isScrubbing = $state(false); + // local UI derived from viewport; not persisted let isMobile = $state(false); @@ -105,7 +110,7 @@ // Media Session bindings const media = createMediaSessionBindings({ play: () => void audioEl?.play(), - pause: () => audioEl?.pause(), + pause: () => void audioEl?.pause(), next: () => { next(); void syncAndAutoplay(); @@ -124,6 +129,21 @@ }, }); + function onScrubInput(e: Event) { + if (!audioEl) return; + const next = Number((e.currentTarget as HTMLInputElement).value); + if (!Number.isFinite(next)) return; + + isScrubbing = true; + scrubValue = next; + audioEl.currentTime = Math.max(0, next); + } + + function onScrubCommit() { + // Release "user is dragging" mode so timeupdate can drive the UI again. + isScrubbing = false; + } + function syncAudioToCurrentTrack() { if (!audioEl) return; @@ -240,6 +260,12 @@ function onAudioTimeUpdate() { if (!audioEl) return; currentTime = audioEl.currentTime || 0; + + // Keep the shared scrubber value synced to playback time unless the user is dragging. + if (!isScrubbing) { + scrubValue = currentTime; + } + media.updatePositionState({ duration, position: currentTime, @@ -249,6 +275,12 @@ function onAudioLoadedMetadata() { if (!audioEl) return; duration = Number.isFinite(audioEl.duration) ? audioEl.duration : 0; + + // On metadata load (track change), ensure scrubber aligns with currentTime. + if (!isScrubbing) { + scrubValue = currentTime; + } + media.updatePositionState({ duration, position: currentTime, @@ -519,6 +551,24 @@
{formatTime(currentTime)} / {formatTime(duration)}
+ +