79 lines
2.7 KiB
TypeScript
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])
|
|
}
|