Files
jellybloom/src/hooks/use-trakt-scrobble.ts
T

79 lines
2.7 KiB
TypeScript

import { useEffect, useRef } from 'react'
import type { BaseItemDto } from '../api/types'
import { useTrakt } from '../stores/trakt-store'
import { scrobbleStart, scrobblePause, scrobbleStop } from '../lib/trakt'
interface Args {
item: BaseItemDto | null | undefined
isPaused: boolean
currentTime: number
duration: number
}
/**
* Fire Trakt scrobble calls in response to local playback transitions.
*
* - start on first play
* - pause on pause
* - resume sends another start
* - stop on unmount (covers navigating away mid-playback)
*
* Progress + duration are tracked through refs so the stop-on-unmount
* effect (which only re-binds when the item changes) sees the latest
* values rather than the ones captured at mount. Scrobble transitions
* key off `isPaused` only - they shouldn't fire on every time-update tick.
*/
export function useTraktScrobble({ item, isPaused, currentTime, duration }: Args) {
const tokens = useTrakt(s => s.tokens)
const enabled = useTrakt(s => s.enabled)
const lastState = useRef<'idle' | 'playing' | 'paused'>('idle')
const lastItemId = useRef<string | null>(null)
const progressRef = useRef({ currentTime: 0, duration: 0 })
// Keep progress in a ref so cleanup callbacks see the latest value.
progressRef.current = { currentTime, duration }
// Reset when item changes
useEffect(() => {
if (item?.Id && item.Id !== lastItemId.current) {
lastState.current = 'idle'
lastItemId.current = item.Id
}
}, [item?.Id])
// State transitions. Only depends on isPaused - currentTime updates
// would otherwise re-run this effect dozens of times a second.
useEffect(() => {
if (!enabled || !tokens || !item) return
const { currentTime: t, duration: d } = progressRef.current
const pct = d > 0 ? (t / d) * 100 : 0
if (isPaused) {
if (lastState.current === 'playing') {
lastState.current = 'paused'
scrobblePause(item, pct).catch(() => {})
}
} else {
if (lastState.current !== 'playing') {
lastState.current = 'playing'
scrobbleStart(item, pct).catch(() => {})
}
}
}, [enabled, tokens, item, isPaused])
// Stop scrobble on unmount or item swap. Reads progress from the ref
// so the percentage reflects where the user actually stopped.
useEffect(() => {
const captured = item
return () => {
if (!useTrakt.getState().enabled) return
if (!useTrakt.getState().tokens) return
if (!captured) return
if (lastState.current === 'idle') return
const { currentTime: t, duration: d } = progressRef.current
const pct = d > 0 ? (t / d) * 100 : 0
scrobbleStop(captured, pct).catch(() => {})
lastState.current = 'idle'
}
}, [item])
}