From ea93f7fc4d17f147727a385a093b48d0a014745b Mon Sep 17 00:00:00 2001 From: Maxim Isaev Date: Sun, 26 Apr 2026 02:36:38 +0300 Subject: [PATCH] fix(player): keep seekbar waveform on current track during gapless preload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Byte-preload for the next track called refreshWaveformForTrack(next), which writes global waveformBins and replaced the playing track’s waveform when seeking near the end. Drop that call and ignore stale RPC results if currentTrack changed while the fetch was in flight. --- src/store/playerStore.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/store/playerStore.ts b/src/store/playerStore.ts index 6f429c9d..61b2d9b9 100644 --- a/src/store/playerStore.ts +++ b/src/store/playerStore.ts @@ -678,6 +678,8 @@ async function refreshWaveformForTrack(trackId: string) { if (!trackId) return; try { const row = await invoke('analysis_get_waveform_for_track', { trackId }); + // Never apply bins for a non-current track (e.g. gapless byte-preload fetches the neighbour). + if (usePlayerStore.getState().currentTrack?.id !== trackId) return; const bins = row ? coerceWaveformBins(row.bins) : null; if (!bins || bins.length === 0) { usePlayerStore.setState({ @@ -945,8 +947,8 @@ function handleAudioProgress(current_time: number, duration: number) { // Byte pre-download — runs early so bytes are cached by chain time. if ((shouldBytePreload || shouldBytePreloadForGaplessBackup) && nextTrack.id !== bytePreloadingId) { bytePreloadingId = nextTrack.id; - // Keep analysis context warm for the upcoming track before any source swap. - void refreshWaveformForTrack(nextTrack.id); + // Loudness cache only — do not call refreshWaveformForTrack(next): it writes global + // waveformBins and would replace the current track's seekbar while still playing it. void refreshLoudnessForTrack(nextTrack.id, { syncPlayingEngine: false }); if (import.meta.env.DEV) { console.info('[psysonic][preload-request]', {