fix(web): songs-like play overlay + scrubber smoothing
test-web / test (push) Failing after 45s

PlaylistCard: per-artist system variants (songs_like_artist) all share
one variant tag but exist as one playlist per seed artist; routing
their play handler through systemShuffle(variant) hit the wrong
playlist (or 404). Detect via seed_artist_id != null and fall through
to getPlaylist(playlist.id) for those; still tag the queue with the
variant so source attribution stays correct.

smoothPosition.svelte.ts: new useSmoothPosition() hook mirrors
Android's rememberSmoothPositionMs. player.position only updates
~4 Hz (HTML audio timeupdate), so the seek thumb stepped visibly;
$effect resets on each canonical tick / seek / track-change and a
rAF loop extrapolates at playback rate between ticks.

Wired into both PlayerBar.svelte (mini + expanded seek rows) and
now-playing/+page.svelte. Seek input handler still reads the raw
range value (not smoothed.value) so user drags stay authoritative.
This commit is contained in:
2026-06-01 22:39:24 -04:00
parent d4c6bb3f2d
commit 74bae74f9f
4 changed files with 86 additions and 10 deletions
+7 -2
View File
@@ -14,11 +14,16 @@
} from '$lib/player/store.svelte';
import { formatDuration } from '$lib/media/duration';
import { FALLBACK_COVER, coverUrl } from '$lib/media/covers';
import { useSmoothPosition } from '$lib/player/smoothPosition.svelte';
import LikeButton from '$lib/components/LikeButton.svelte';
import { pageTitle } from '$lib/branding';
const current = $derived(player.current);
// Per-frame interpolated playhead so the scrubber thumb glides
// smoothly between server ticks. Shares the helper with PlayerBar.
const smoothed = useSmoothPosition();
const skipPrevDisabled = $derived(player.index === 0 && player.position < 3);
const skipNextDisabled = $derived(
player.index === player.queue.length - 1 && player.repeat === 'off'
@@ -123,13 +128,13 @@
min="0"
max={player.duration || 0}
step="0.1"
value={player.position}
value={smoothed.value}
oninput={onSeekInput}
aria-label="Seek"
class="w-full accent-accent"
/>
<div class="mt-1 flex justify-between text-xs text-text-secondary tabular-nums">
<span>{formatDuration(player.position)}</span>
<span>{formatDuration(smoothed.value)}</span>
<span>{formatDuration(player.duration)}</span>
</div>
</div>