hooks for jellyfin data, playback, tmdb, player chrome
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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])
|
||||
}
|
||||
Reference in New Issue
Block a user