From f5e48445dde89fbcdfcc63c7539cdebb3813d4c1 Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 22 May 2026 22:22:23 -0300 Subject: [PATCH] fix(ui): report playback when restarting current track via prev The navidrome-music-player library rewinds the current track by directly mutating audio.currentTime when the Previous button is pressed with restartCurrentOnPrev (and other programmatic seek paths like singleLoop reset and mediaSession seek). It does not invoke its onAudioSeeked callback for these, so the play tracker never learned about the new position until the next ~30s heartbeat. Replace the React onAudioSeeked prop with a native HTML5 'seeked' event listener on the audio element, which fires for every seek (programmatic or via slider release). The handler is debounced by 250ms so the burst of seeks emitted while dragging the progress bar coalesces into a single reportPlayback call at the final position. --- ui/src/audioplayer/Player.jsx | 42 ++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/ui/src/audioplayer/Player.jsx b/ui/src/audioplayer/Player.jsx index e2070deea..c3d795b26 100644 --- a/ui/src/audioplayer/Player.jsx +++ b/ui/src/audioplayer/Player.jsx @@ -272,18 +272,6 @@ const Player = () => { } }, []) - const onAudioSeeked = useCallback( - (info) => { - if (!info.isRadio && currentTrackId) { - const posMs = Math.floor(info.currentTime * 1000) - lastPositionMsRef.current = posMs - const state = audioInstance?.paused ? 'paused' : 'playing' - subsonic.reportPlayback(currentTrackId, posMs, state) - } - }, - [currentTrackId, audioInstance], - ) - const onAudioVolumeChange = useCallback( // sqrt to compensate for the logarithmic volume (volume) => dispatch(setVolume(Math.sqrt(volume))), @@ -436,6 +424,35 @@ const Player = () => { } }, [isMobilePlayer, audioInstance]) + // Report every seek (including programmatic ones the library does not surface + // via onAudioSeeked, e.g. restartCurrentOnPrev). Debounce coalesces drag + // bursts into one report at the final position. + useEffect(() => { + if (!audioInstance) return + let timer = null + const flush = () => { + timer = null + if ( + !currentTrackIdRef.current || + playerStateRef.current?.current?.isRadio + ) { + return + } + const posMs = Math.floor((audioInstance.currentTime || 0) * 1000) + const state = audioInstance.paused ? 'paused' : 'playing' + subsonic.reportPlayback(currentTrackIdRef.current, posMs, state) + } + const handleSeeked = () => { + if (timer) clearTimeout(timer) + timer = setTimeout(flush, 250) + } + audioInstance.addEventListener('seeked', handleSeeked) + return () => { + if (timer) clearTimeout(timer) + audioInstance.removeEventListener('seeked', handleSeeked) + } + }, [audioInstance]) + return ( { onAudioListsChange={onAudioListsChange} onAudioVolumeChange={onAudioVolumeChange} onAudioProgress={onAudioProgress} - onAudioSeeked={onAudioSeeked} onAudioPlay={onAudioPlay} onAudioPlayTrackChange={onAudioPlayTrackChange} onAudioPause={onAudioPause}