fix reportPlayback event chain (#2131)

- properly send stopped event on song change
- properly send both starting and playing evento n song change instead of only starting
This commit is contained in:
jeffvli
2026-06-06 18:41:59 -07:00
parent 40a1d1438d
commit 46b94a83f1
5 changed files with 96 additions and 20 deletions
@@ -67,8 +67,9 @@ Jellyfin progress APIs still use playback position (ticks), not listen time:
- pause / unpause
Other events:
- When the song changes: sends 'start' when the new track is playing;
clears submission flag and listen accumulator for the new track.
- When the song changes: sends 'stop' for the previous track; sends 'start'
when the new track is playing; clears submission flag and listen accumulator
for the new track.
- When the song is restarted (near 0 after 10s+): clears submission flag
and listen accumulator.
@@ -129,6 +130,7 @@ export const useScrobble = () => {
const previousSongRef = useRef<QueueSong | undefined>(undefined);
const previousTimestampRef = useRef<number>(0);
const stopPositionRef = useRef<number>(0);
const lastProgressEventRef = useRef<number>(0);
const lastSeekEventRef = useRef<number>(0);
const songChangeTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
@@ -316,7 +318,10 @@ export const useScrobble = () => {
) => {
const currentSong = properties.song;
const previousSong = previousSongRef.current;
const previousPositionSec = stopPositionRef.current;
const mediaType = currentSong?._itemType.includes('song') ? 'song' : 'podcast';
const previousMediaType = previousSong?._itemType.includes('song') ? 'song' : 'podcast';
const useTicksForPrevious = previousSong?._serverType === ServerType.JELLYFIN;
// Handle notifications
if (scrobbleSettings?.notify && currentSong?.id) {
@@ -352,6 +357,7 @@ export const useScrobble = () => {
if (!isScrobbleEnabled || isPrivateModeEnabled) {
previousSongRef.current = currentSong;
previousTimestampRef.current = 0;
stopPositionRef.current = 0;
listenedMsRef.current = 0;
lastListenSampleTimeRef.current = null;
flushScrobbleDebug();
@@ -395,10 +401,42 @@ export const useScrobble = () => {
},
);
}
// Send stop scrobble for the track that was playing before the change
if (previousSong?.id) {
sendScrobble.mutate(
{
apiClientProps: { serverId: previousSong._serverId || '' },
query: {
albumId: previousSong.albumId,
event: 'stop',
id: previousSong.id,
mediaType: previousMediaType,
playbackRate: playbackRate,
position: getPositionValue(
previousPositionSec,
useTicksForPrevious,
),
submission: false,
},
},
{
onSuccess: () => {
logFn.debug(logMsg[LogCategory.SCROBBLE].scrobbledStop, {
category: LogCategory.SCROBBLE,
meta: {
id: previousSong.id,
},
});
},
},
);
}
}, 2000);
previousSongRef.current = currentSong;
previousTimestampRef.current = 0;
stopPositionRef.current = 0;
flushScrobbleDebug();
},
[
@@ -591,6 +629,7 @@ export const useScrobble = () => {
isCurrentSongScrobbledRef.current = false;
lastProgressEventRef.current = 0;
previousTimestampRef.current = 0;
stopPositionRef.current = 0;
listenedMsRef.current = 0;
lastListenSampleTimeRef.current = null;
@@ -625,6 +664,17 @@ export const useScrobble = () => {
// Update previous timestamp on progress for use in status change handler
const handleProgressUpdate = useCallback(
(properties: { timestamp: number }, prev: { timestamp: number }) => {
// Preserve last playback position when the playhead resets to the start
// (song change can fire after progress already reports 0 for the new track).
if (
properties.timestamp < SCROBBLE_TRACK_BEGIN_SEC &&
prev.timestamp >= SCROBBLE_TRACK_BEGIN_SEC
) {
stopPositionRef.current = prev.timestamp;
} else {
stopPositionRef.current = properties.timestamp;
}
previousTimestampRef.current = properties.timestamp;
handleScrobbleFromProgress(properties, prev);
flushScrobbleDebug();