mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 18:13:31 +00:00
More typechecks on scrobble, use timeout on notification (#1004)
This commit is contained in:
parent
56130d8503
commit
69341f4492
1 changed files with 35 additions and 23 deletions
|
|
@ -34,6 +34,8 @@ Progress Events (Jellyfin only):
|
||||||
- Sends the 'progress' scrobble event on an interval
|
- Sends the 'progress' scrobble event on an interval
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type PlayerEvent = [PlayerStatus, number];
|
||||||
|
|
||||||
type SongEvent = [QueueSong | undefined, number, 1 | 2];
|
type SongEvent = [QueueSong | undefined, number, 1 | 2];
|
||||||
|
|
||||||
const checkScrobbleConditions = (args: {
|
const checkScrobbleConditions = (args: {
|
||||||
|
|
@ -86,21 +88,35 @@ export const useScrobble = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const progressIntervalId = useRef<null | ReturnType<typeof setInterval>>(null);
|
const progressIntervalId = useRef<null | ReturnType<typeof setInterval>>(null);
|
||||||
const songChangeTimeoutId = useRef<null | ReturnType<typeof setTimeout>>(null);
|
const songChangeTimeoutId = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||||
|
const notifyTimeoutId = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||||
|
|
||||||
const handleScrobbleFromSongChange = useCallback(
|
const handleScrobbleFromSongChange = useCallback(
|
||||||
(current: SongEvent, previous: SongEvent) => {
|
(current: SongEvent, previous: SongEvent) => {
|
||||||
if (scrobbleSettings?.notify && current[0]) {
|
if (scrobbleSettings?.notify && current[0]?.id) {
|
||||||
|
clearTimeout(notifyTimeoutId.current);
|
||||||
const currentSong = current[0];
|
const currentSong = current[0];
|
||||||
|
|
||||||
const artists =
|
// Set a delay so that quickly (within a second) switching songs doesn't trigger multiple
|
||||||
currentSong.artists?.length > 0
|
// notifications
|
||||||
? currentSong.artists.map((artist) => artist.name).join(', ')
|
notifyTimeoutId.current = setTimeout(() => {
|
||||||
: currentSong.artistName;
|
// Only trigger if the song changed, or the player changed. This should be the case
|
||||||
|
// anyways, but who knows
|
||||||
|
if (
|
||||||
|
currentSong.uniqueId !== previous[0]?.uniqueId ||
|
||||||
|
current[2] !== previous[2]
|
||||||
|
) {
|
||||||
|
const artists =
|
||||||
|
currentSong.artists?.length > 0
|
||||||
|
? currentSong.artists.map((artist) => artist.name).join(', ')
|
||||||
|
: currentSong.artistName;
|
||||||
|
|
||||||
new Notification(`Now playing ${currentSong.name}`, {
|
new Notification(`Now playing ${currentSong.name}`, {
|
||||||
body: `by ${artists} on ${currentSong.album}`,
|
body: `by ${artists} on ${currentSong.album}`,
|
||||||
icon: currentSong.imageUrl || undefined,
|
icon: currentSong.imageUrl || undefined,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isScrobbleEnabled) return;
|
if (!isScrobbleEnabled) return;
|
||||||
|
|
@ -110,7 +126,6 @@ export const useScrobble = () => {
|
||||||
progressIntervalId.current = null;
|
progressIntervalId.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const currentSong = current[0] as QueueSong | undefined;
|
|
||||||
const previousSong = previous[0];
|
const previousSong = previous[0];
|
||||||
const previousSongTimeSec = previous[1];
|
const previousSongTimeSec = previous[1];
|
||||||
|
|
||||||
|
|
@ -146,7 +161,7 @@ export const useScrobble = () => {
|
||||||
setIsCurrentSongScrobbled(false);
|
setIsCurrentSongScrobbled(false);
|
||||||
|
|
||||||
// Use a timeout to prevent spamming the server with scrobble events when switching through songs quickly
|
// Use a timeout to prevent spamming the server with scrobble events when switching through songs quickly
|
||||||
clearTimeout(songChangeTimeoutId.current as ReturnType<typeof setTimeout>);
|
clearTimeout(songChangeTimeoutId.current);
|
||||||
songChangeTimeoutId.current = setTimeout(() => {
|
songChangeTimeoutId.current = setTimeout(() => {
|
||||||
const currentSong = current[0];
|
const currentSong = current[0];
|
||||||
// Get the current status from the state, not variable. This is because
|
// Get the current status from the state, not variable. This is because
|
||||||
|
|
@ -193,10 +208,7 @@ export const useScrobble = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleScrobbleFromStatusChange = useCallback(
|
const handleScrobbleFromStatusChange = useCallback(
|
||||||
(
|
(current: PlayerEvent, previous: PlayerEvent) => {
|
||||||
current: (number | PlayerStatus | undefined)[],
|
|
||||||
previous: (number | PlayerStatus | undefined)[],
|
|
||||||
) => {
|
|
||||||
if (!isScrobbleEnabled) return;
|
if (!isScrobbleEnabled) return;
|
||||||
|
|
||||||
const currentSong = usePlayerStore.getState().current.song;
|
const currentSong = usePlayerStore.getState().current.song;
|
||||||
|
|
@ -208,8 +220,8 @@ export const useScrobble = () => {
|
||||||
? usePlayerStore.getState().current.time * 1e7
|
? usePlayerStore.getState().current.time * 1e7
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const currentStatus = current[0] as PlayerStatus;
|
const currentStatus = current[0];
|
||||||
const currentTimeSec = current[1] as number;
|
const currentTimeSec = current[1];
|
||||||
|
|
||||||
// Whenever the player is restarted, send a 'start' scrobble
|
// Whenever the player is restarted, send a 'start' scrobble
|
||||||
if (currentStatus === PlayerStatus.PLAYING) {
|
if (currentStatus === PlayerStatus.PLAYING) {
|
||||||
|
|
@ -249,12 +261,12 @@ export const useScrobble = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (progressIntervalId.current) {
|
if (progressIntervalId.current) {
|
||||||
clearInterval(progressIntervalId.current as ReturnType<typeof setInterval>);
|
clearInterval(progressIntervalId.current);
|
||||||
progressIntervalId.current = null;
|
progressIntervalId.current = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const isLastTrackInQueue = usePlayerStore.getState().actions.checkIsLastTrack();
|
const isLastTrackInQueue = usePlayerStore.getState().actions.checkIsLastTrack();
|
||||||
const previousTimeSec = previous[1] as number;
|
const previousTimeSec = previous[1];
|
||||||
|
|
||||||
// If not already scrobbled, send a 'submission' scrobble if conditions are met
|
// If not already scrobbled, send a 'submission' scrobble if conditions are met
|
||||||
const shouldSubmitScrobble = checkScrobbleConditions({
|
const shouldSubmitScrobble = checkScrobbleConditions({
|
||||||
|
|
@ -358,17 +370,17 @@ export const useScrobble = () => {
|
||||||
// multiple times in a row and playback goes normally (no next/previous)
|
// multiple times in a row and playback goes normally (no next/previous)
|
||||||
equalityFn: (a, b) =>
|
equalityFn: (a, b) =>
|
||||||
// compute whether the song changed
|
// compute whether the song changed
|
||||||
(a[0] as QueueSong)?.uniqueId === (b[0] as QueueSong)?.uniqueId &&
|
a[0]?.uniqueId === b[0]?.uniqueId &&
|
||||||
// compute whether the same player: relevant for repeat one and repeat all (one track)
|
// compute whether the same player: relevant for repeat one and repeat all (one track)
|
||||||
a[2] === b[2],
|
a[2] === b[2],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const unsubStatusChange = usePlayerStore.subscribe(
|
const unsubStatusChange = usePlayerStore.subscribe(
|
||||||
(state) => [state.current.status, state.current.time],
|
(state): PlayerEvent => [state.current.status, state.current.time],
|
||||||
handleScrobbleFromStatusChange,
|
handleScrobbleFromStatusChange,
|
||||||
{
|
{
|
||||||
equalityFn: (a, b) => (a[0] as PlayerStatus) === (b[0] as PlayerStatus),
|
equalityFn: (a, b) => a[0] === b[0],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue