Files
mice/hooks/use-lastfm-scrobbler.ts

124 lines
4.0 KiB
TypeScript

import { useCallback, useRef } from 'react';
import { getNavidromeAPI } from '@/lib/navidrome';
import { Track } from '@/app/components/AudioPlayerContext';
interface ScrobbleState {
trackId: string | null;
hasScrobbled: boolean;
hasUpdatedNowPlaying: boolean;
playStartTime: number;
lastPlayedDuration: number;
}
export function useLastFmScrobbler() {
const scrobbleStateRef = useRef<ScrobbleState>({
trackId: null,
hasScrobbled: false,
hasUpdatedNowPlaying: false,
playStartTime: 0,
lastPlayedDuration: 0,
});
const isScrobblingEnabled = () => {
if (typeof window === 'undefined') return false;
return localStorage.getItem('lastfm-scrobbling-enabled') !== 'false';
};
const updateNowPlaying = useCallback(async (track: Track) => {
if (!isScrobblingEnabled()) return;
const api = getNavidromeAPI();
if (!api || !track.id) return;
try {
await api.updateNowPlaying(track.id);
scrobbleStateRef.current.hasUpdatedNowPlaying = true;
console.log('Updated now playing for Last.fm:', track.name);
} catch (error) {
console.error('Failed to update now playing:', error);
}
}, []);
const onTrackStart = useCallback(async (track: Track) => {
// Reset scrobble state for new track
scrobbleStateRef.current = {
trackId: track.id,
hasScrobbled: false,
hasUpdatedNowPlaying: false,
playStartTime: Date.now(),
lastPlayedDuration: 0,
};
// Update now playing on Last.fm
await updateNowPlaying(track);
}, [updateNowPlaying]);
const onTrackPlay = useCallback(async (track: Track) => {
scrobbleStateRef.current.playStartTime = Date.now();
// Update now playing if we haven't already for this track
if (!scrobbleStateRef.current.hasUpdatedNowPlaying || scrobbleStateRef.current.trackId !== track.id) {
await onTrackStart(track);
}
}, [onTrackStart]);
const onTrackPause = useCallback((currentTime: number) => {
const now = Date.now();
const sessionDuration = (now - scrobbleStateRef.current.playStartTime) / 1000;
scrobbleStateRef.current.lastPlayedDuration += sessionDuration;
}, []);
const onTrackProgress = useCallback(async (track: Track, currentTime: number, duration: number) => {
if (!isScrobblingEnabled()) return;
const api = getNavidromeAPI();
if (!api || !track.id || scrobbleStateRef.current.hasScrobbled) return;
// Calculate total played time
const now = Date.now();
const currentSessionDuration = (now - scrobbleStateRef.current.playStartTime) / 1000;
const totalPlayedDuration = scrobbleStateRef.current.lastPlayedDuration + currentSessionDuration;
// Check if we should scrobble according to Last.fm guidelines
if (api.shouldScrobble(totalPlayedDuration, duration)) {
try {
await api.scrobbleTrack(track.id);
scrobbleStateRef.current.hasScrobbled = true;
console.log('Scrobbled track to Last.fm:', track.name);
} catch (error) {
console.error('Failed to scrobble track:', error);
}
}
}, []);
const onTrackEnd = useCallback(async (track: Track, currentTime: number, duration: number) => {
if (!isScrobblingEnabled()) return;
const api = getNavidromeAPI();
if (!api || !track.id) return;
// Calculate final played duration
const now = Date.now();
const finalSessionDuration = (now - scrobbleStateRef.current.playStartTime) / 1000;
const totalPlayedDuration = scrobbleStateRef.current.lastPlayedDuration + finalSessionDuration;
// Scrobble if we haven't already and the track qualifies
if (!scrobbleStateRef.current.hasScrobbled && api.shouldScrobble(totalPlayedDuration, duration)) {
try {
await api.scrobbleTrack(track.id);
console.log('Final scrobble for completed track:', track.name);
} catch (error) {
console.error('Failed to scrobble completed track:', error);
}
}
}, []);
return {
onTrackStart,
onTrackPlay,
onTrackPause,
onTrackProgress,
onTrackEnd,
};
}