import { player } from './store.svelte'; /** * Reactive smoothed playhead position in seconds. Mirrors Android's * `rememberSmoothPositionMs`: the underlying `player.position` updates * every ~250 ms (HTML audio `timeupdate` fires ~4 Hz), so reading it * directly snaps the scrubber forward in discrete steps. This helper * extrapolates per-frame between server ticks so the bar glides at * playback rate. * * The `$effect` re-runs whenever the canonical position / isPlaying / * duration changes, which both handles natural ticks (re-sync to the * new authoritative value) and seeks (instant jump on user action). * The rAF loop advances at wall-clock seconds since the segment * started, so as long as the canonical position keeps up with real * time the resync on each tick is sub-frame and invisible. * * Returns a `{ value }` accessor instead of a bare number so callers * can read it reactively inside components (Svelte's rune wiring * follows the property access). */ export function useSmoothPosition(): { readonly value: number } { let displayed = $state(player.position); $effect(() => { // Reset whenever the canonical position changes (server tick, // seek, track change) so we anchor to the truth. displayed = player.position; if (!player.isPlaying || player.duration <= 0) return; const startReal = performance.now(); const startPos = player.position; const duration = player.duration; let rafId = 0; const tick = () => { const elapsed = (performance.now() - startReal) / 1000; displayed = Math.min(startPos + elapsed, duration); rafId = requestAnimationFrame(tick); }; rafId = requestAnimationFrame(tick); return () => cancelAnimationFrame(rafId); }); return { get value() { return displayed; } }; }