adjust jellyfin scrobble conditions, remove status change event (#1274)

This commit is contained in:
jeffvli
2025-12-04 20:43:10 -08:00
parent 7ac47377f1
commit 566d938b6c
+125 -117
View File
@@ -69,6 +69,7 @@ export const useScrobble = () => {
const previousSongRef = useRef<QueueSong | undefined>(undefined); const previousSongRef = useRef<QueueSong | undefined>(undefined);
const previousTimestampRef = useRef<number>(0); const previousTimestampRef = useRef<number>(0);
const lastProgressEventRef = useRef<number>(0); const lastProgressEventRef = useRef<number>(0);
const lastSeekEventRef = useRef<number>(0);
const songChangeTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined); const songChangeTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const notifyTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined); const notifyTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
@@ -153,8 +154,10 @@ export const useScrobble = () => {
} }
} }
const preventScrobbleFromProgress = currentSong._serverType === ServerType.JELLYFIN;
// Check if we should submit scrobble based on conditions // Check if we should submit scrobble based on conditions
if (!isCurrentSongScrobbled) { if (!isCurrentSongScrobbled && !preventScrobbleFromProgress) {
const shouldSubmitScrobble = checkScrobbleConditions({ const shouldSubmitScrobble = checkScrobbleConditions({
scrobbleAtDurationMs: (scrobbleSettings?.scrobbleAtDuration ?? 0) * 1000, scrobbleAtDurationMs: (scrobbleSettings?.scrobbleAtDuration ?? 0) * 1000,
scrobbleAtPercentage: scrobbleSettings?.scrobbleAtPercentage, scrobbleAtPercentage: scrobbleSettings?.scrobbleAtPercentage,
@@ -331,123 +334,123 @@ export const useScrobble = () => {
], ],
); );
const handleScrobbleFromStatusChange = useCallback( // const handleScrobbleFromStatusChange = useCallback(
// eslint-disable-next-line @typescript-eslint/no-unused-vars // // eslint-disable-next-line @typescript-eslint/no-unused-vars
(properties: { status: PlayerStatus }, _prev: { status: PlayerStatus }) => { // (properties: { status: PlayerStatus }, _prev: { status: PlayerStatus }) => {
if (!isScrobbleEnabled || isPrivateModeEnabled) return; // if (!isScrobbleEnabled || isPrivateModeEnabled) return;
const currentSong = usePlayerStore.getState().getCurrentSong(); // const currentSong = usePlayerStore.getState().getCurrentSong();
const currentTimestamp = // const currentTimestamp =
usePlayerStore.getState().player.index >= 0 ? previousTimestampRef.current : 0; // usePlayerStore.getState().player.index >= 0 ? previousTimestampRef.current : 0;
if (!currentSong?.id) return; // if (!currentSong?.id) return;
const position = // const position =
currentSong._serverType === ServerType.JELLYFIN // currentSong._serverType === ServerType.JELLYFIN
? currentTimestamp * 1e7 // ? currentTimestamp * 1e7
: undefined; // : undefined;
const currentStatus = properties.status; // const currentStatus = properties.status;
if (currentStatus === PlayerStatus.PLAYING) { // if (currentStatus === PlayerStatus.PLAYING) {
// Send unpause event // // Send unpause event
sendScrobble.mutate( // sendScrobble.mutate(
{ // {
apiClientProps: { serverId: currentSong._serverId || '' }, // apiClientProps: { serverId: currentSong._serverId || '' },
query: { // query: {
event: 'unpause', // event: 'unpause',
id: currentSong.id, // id: currentSong.id,
position, // position,
submission: false, // submission: false,
}, // },
}, // },
{ // {
onSuccess: () => { // onSuccess: () => {
logFn.debug(logMsg[LogCategory.SCROBBLE].scrobbledUnpause, { // logFn.debug(logMsg[LogCategory.SCROBBLE].scrobbledUnpause, {
category: LogCategory.SCROBBLE, // category: LogCategory.SCROBBLE,
meta: { // meta: {
id: currentSong.id, // id: currentSong.id,
}, // },
}); // });
}, // },
}, // },
); // );
} else if (currentSong._serverType === ServerType.JELLYFIN) { // } else if (currentSong._serverType === ServerType.JELLYFIN) {
// Send pause event for Jellyfin // // Send pause event for Jellyfin
sendScrobble.mutate( // sendScrobble.mutate(
{ // {
apiClientProps: { serverId: currentSong._serverId || '' }, // apiClientProps: { serverId: currentSong._serverId || '' },
query: { // query: {
event: 'pause', // event: 'pause',
id: currentSong.id, // id: currentSong.id,
position, // position,
submission: false, // submission: false,
}, // },
}, // },
{ // {
onSuccess: () => { // onSuccess: () => {
logFn.debug(logMsg[LogCategory.SCROBBLE].scrobbledPause, { // logFn.debug(logMsg[LogCategory.SCROBBLE].scrobbledPause, {
category: LogCategory.SCROBBLE, // category: LogCategory.SCROBBLE,
meta: { // meta: {
id: currentSong.id, // id: currentSong.id,
}, // },
}); // });
}, // },
}, // },
); // );
} else { // } else {
// For non-Jellyfin servers, check scrobble conditions on pause // // For non-Jellyfin servers, check scrobble conditions on pause
const isLastTrackInQueue = usePlayerStore.getState().isLastTrackInQueue(); // const isLastTrackInQueue = usePlayerStore.getState().isLastTrackInQueue();
const isFirstTrackInQueue = usePlayerStore.getState().isFirstTrackInQueue(); // const isFirstTrackInQueue = usePlayerStore.getState().isFirstTrackInQueue();
const previousTimestamp = previousTimestampRef.current; // const previousTimestamp = previousTimestampRef.current;
const shouldSubmitScrobble = checkScrobbleConditions({ // const shouldSubmitScrobble = checkScrobbleConditions({
scrobbleAtDurationMs: (scrobbleSettings?.scrobbleAtDuration ?? 0) * 1000, // scrobbleAtDurationMs: (scrobbleSettings?.scrobbleAtDuration ?? 0) * 1000,
scrobbleAtPercentage: scrobbleSettings?.scrobbleAtPercentage, // scrobbleAtPercentage: scrobbleSettings?.scrobbleAtPercentage,
// If scrobbling the last song in the queue, use the previous time // // If scrobbling the last song in the queue, use the previous time
// Note: if queue has one item (both first and last), use current time // // Note: if queue has one item (both first and last), use current time
songCompletedDurationMs: // songCompletedDurationMs:
(isLastTrackInQueue && !isFirstTrackInQueue // (isLastTrackInQueue && !isFirstTrackInQueue
? previousTimestamp // ? previousTimestamp
: currentTimestamp) * 1000, // : currentTimestamp) * 1000,
songDurationMs: currentSong.duration, // songDurationMs: currentSong.duration,
}); // });
if (!isCurrentSongScrobbled && shouldSubmitScrobble) { // if (!isCurrentSongScrobbled && shouldSubmitScrobble) {
sendScrobble.mutate( // sendScrobble.mutate(
{ // {
apiClientProps: { serverId: currentSong._serverId || '' }, // apiClientProps: { serverId: currentSong._serverId || '' },
query: { // query: {
id: currentSong.id, // id: currentSong.id,
submission: true, // submission: true,
}, // },
}, // },
{ // {
onSuccess: () => { // onSuccess: () => {
logFn.debug(logMsg[LogCategory.SCROBBLE].scrobbledSubmission, { // logFn.debug(logMsg[LogCategory.SCROBBLE].scrobbledSubmission, {
category: LogCategory.SCROBBLE, // category: LogCategory.SCROBBLE,
meta: { // meta: {
id: currentSong.id, // id: currentSong.id,
reason: 'from status change', // reason: 'from status change',
}, // },
}); // });
}, // },
}, // },
); // );
setIsCurrentSongScrobbled(true); // setIsCurrentSongScrobbled(true);
} // }
} // }
}, // },
[ // [
isScrobbleEnabled, // isScrobbleEnabled,
isPrivateModeEnabled, // isPrivateModeEnabled,
scrobbleSettings?.scrobbleAtDuration, // scrobbleSettings?.scrobbleAtDuration,
scrobbleSettings?.scrobbleAtPercentage, // scrobbleSettings?.scrobbleAtPercentage,
isCurrentSongScrobbled, // isCurrentSongScrobbled,
sendScrobble, // sendScrobble,
], // ],
); // );
const handleScrobbleFromSeek = useCallback( const handleScrobbleFromSeek = useCallback(
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -458,8 +461,19 @@ export const useScrobble = () => {
if (!currentSong?.id || currentSong._serverType !== ServerType.JELLYFIN) return; if (!currentSong?.id || currentSong._serverType !== ServerType.JELLYFIN) return;
const now = Date.now();
const timeSinceLastSeek = now - lastSeekEventRef.current;
// Only allow seek scrobble once per second
if (timeSinceLastSeek < 1000) {
return;
}
const position = properties.timestamp * 1e7; const position = properties.timestamp * 1e7;
lastProgressEventRef.current = properties.timestamp;
lastSeekEventRef.current = now;
sendScrobble.mutate( sendScrobble.mutate(
{ {
apiClientProps: { serverId: currentSong._serverId || '' }, apiClientProps: { serverId: currentSong._serverId || '' },
@@ -499,13 +513,7 @@ export const useScrobble = () => {
onCurrentSongChange: handleScrobbleFromSongChange, onCurrentSongChange: handleScrobbleFromSongChange,
onPlayerProgress: handleProgressUpdate, onPlayerProgress: handleProgressUpdate,
onPlayerSeekToTimestamp: handleScrobbleFromSeek, onPlayerSeekToTimestamp: handleScrobbleFromSeek,
onPlayerStatus: handleScrobbleFromStatusChange,
}, },
[ [handleScrobbleFromSongChange, handleProgressUpdate, handleScrobbleFromSeek],
handleScrobbleFromSongChange,
handleProgressUpdate,
handleScrobbleFromSeek,
handleScrobbleFromStatusChange,
],
); );
}; };