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.
This commit is contained in:
Deluan
2026-05-22 22:22:23 -03:00
parent 8897ec918e
commit 0265ff3ad1
+29 -13
View File
@@ -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 (
<ThemeProvider theme={createMuiTheme(theme)}>
<ReactJkMusicPlayer
@@ -444,7 +461,6 @@ const Player = () => {
onAudioListsChange={onAudioListsChange}
onAudioVolumeChange={onAudioVolumeChange}
onAudioProgress={onAudioProgress}
onAudioSeeked={onAudioSeeked}
onAudioPlay={onAudioPlay}
onAudioPlayTrackChange={onAudioPlayTrackChange}
onAudioPause={onAudioPause}