hooks for jellyfin data, playback, tmdb, player chrome
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getMovieFull, getTvShowFull } from '../api/tmdb'
|
||||
import type { BaseItemDto } from '../api/types'
|
||||
import { getTmdbId } from '../lib/item-types'
|
||||
|
||||
/**
|
||||
* Returns the YouTube key of the first official Trailer/Teaser for the
|
||||
* given item, lazy-fetched only after the user has hovered the card for
|
||||
* `delayMs`. Avoids slamming TMDB when users sweep the cursor across a
|
||||
* grid.
|
||||
*
|
||||
* The fetch is gated by an `armed` boolean toggled by the consumer
|
||||
* (PosterCard's hover effect) so we don't dispatch network requests for
|
||||
* every card on the screen, only the ones the user lingers on.
|
||||
*/
|
||||
export function useHoverTrailer(
|
||||
item: BaseItemDto | null | undefined,
|
||||
hovered: boolean,
|
||||
enabled: boolean,
|
||||
delayMs = 700,
|
||||
): { videoKey: string | null; ready: boolean } {
|
||||
const [armed, setArmed] = useState(false)
|
||||
|
||||
// Arm only after the user has stayed on the card for delayMs.
|
||||
useEffect(() => {
|
||||
if (!hovered || !enabled) {
|
||||
setArmed(false)
|
||||
return
|
||||
}
|
||||
const id = setTimeout(() => setArmed(true), delayMs)
|
||||
return () => clearTimeout(id)
|
||||
}, [hovered, enabled, delayMs])
|
||||
|
||||
const tmdbId = getTmdbId(item)
|
||||
const numericId = tmdbId ? Number(tmdbId) : null
|
||||
const isTv = item?.Type === 'Series'
|
||||
|
||||
const movieQ = useQuery({
|
||||
queryKey: ['tmdb', 'movie-full', numericId],
|
||||
queryFn: () => (numericId ? getMovieFull(numericId) : null),
|
||||
enabled: !!numericId && !isTv && armed,
|
||||
staleTime: 24 * 60 * 60 * 1000,
|
||||
})
|
||||
const tvQ = useQuery({
|
||||
queryKey: ['tmdb', 'tv-full', numericId],
|
||||
queryFn: () => (numericId ? getTvShowFull(numericId) : null),
|
||||
enabled: !!numericId && isTv && armed,
|
||||
staleTime: 24 * 60 * 60 * 1000,
|
||||
})
|
||||
|
||||
const videos = (isTv ? tvQ.data?.videos?.results : movieQ.data?.videos?.results) || []
|
||||
// Prefer official trailers, then teasers; restricted to YouTube since
|
||||
// we render via the YT iframe player.
|
||||
const pickPriority = (t: string) => (t === 'Trailer' ? 0 : t === 'Teaser' ? 1 : 2)
|
||||
const trailer = [...videos]
|
||||
.filter(v => v.site === 'YouTube' && v.key)
|
||||
.sort((a, b) => {
|
||||
if (a.official !== b.official) return a.official ? -1 : 1
|
||||
return pickPriority(a.type) - pickPriority(b.type)
|
||||
})[0]
|
||||
|
||||
return {
|
||||
videoKey: trailer?.key || null,
|
||||
ready: armed && (movieQ.isFetched || tvQ.isFetched),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user