@openrails/contentkit-ui 0.90.3 → 0.92.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/client/client.d.ts +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/generated/routes.d.ts +7 -7
- package/dist/client/generated/routes.js +6 -6
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/http.d.ts +13 -8
- package/dist/client/http.js +16 -4
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/media/client.d.ts +3 -3
- package/dist/client/media/client.js +2 -2
- package/dist/client/media/client.js.map +1 -1
- package/dist/components/hover-preview.js +1 -1
- package/dist/components/hover-preview.js.map +1 -1
- package/dist/components/media-gallery.d.ts +2 -2
- package/dist/components/media-gallery.js +4 -4
- package/dist/components/media-gallery.js.map +1 -1
- package/dist/components/video-mini-player.js +1 -1
- package/dist/components/video-mini-player.js.map +1 -1
- package/dist/components/video-player.d.ts +3 -3
- package/dist/components/video-player.js +3 -3
- package/dist/components/video-player.js.map +1 -1
- package/dist/react/gallery.d.ts +6 -3
- package/dist/react/gallery.js +303 -228
- package/dist/react/gallery.js.map +1 -1
- package/dist/react/player.d.ts +4 -6
- package/dist/react/player.js +54 -61
- package/dist/react/player.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gallery.js","names":[],"sources":["../../src/react/gallery.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type KeyboardEvent, type MouseEvent, type PointerEvent } from \"react\";\nimport type { GalleryView } from \"../client/gallery.js\";\nimport type { CapLevelController, Level } from \"hls.js\";\nimport {\n NETWORK_FAILURES_BEFORE_ERROR,\n abrHlsConfig,\n capRung,\n classifyHlsError,\n classifyMediaError,\n hlsConfig,\n initialEstimate,\n otherCodecLevels,\n expiryDelay,\n refreshable,\n startRung,\n statusKind,\n type AbrPolicy,\n type ConnectionHint,\n type PlaybackError,\n} from \"../client/playback.js\";\nimport { PLAYER_TRACKS_KEY, PLAYER_VOLUME_KEY, pickTrack, readTrackPrefs, readVolume, writeTrackPrefs, type PlayerTrack } from \"../client/player.js\";\n\nexport const GALLERY_VIEW_KEY = \"ckui.media-gallery.view\";\n\nexport interface GalleryViewOptions {\n /** Controlled view. */\n view?: GalleryView;\n defaultView?: GalleryView;\n onViewChange?: (view: GalleryView) => void;\n /** localStorage key remembering the viewer's choice; null disables. */\n storageKey?: string | null;\n}\n\nfunction stored(key: string | null): GalleryView | undefined {\n if (!key) return undefined;\n try {\n const v = globalThis.localStorage?.getItem(key);\n return v === \"grid\" || v === \"carousel\" ? v : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Carousel or grid, controlled or not; the viewer's last choice is remembered. */\nexport function useGalleryView({ view, defaultView = \"carousel\", onViewChange, storageKey = GALLERY_VIEW_KEY }: GalleryViewOptions = {}) {\n const [own, setOwn] = useState<GalleryView>(() => stored(storageKey) ?? defaultView);\n const set = useCallback(\n (v: GalleryView) => {\n setOwn(v);\n if (storageKey)\n try {\n globalThis.localStorage?.setItem(storageKey, v);\n } catch {\n // private mode or blocked storage: the choice lasts this page\n }\n onViewChange?.(v);\n },\n [storageKey, onViewChange],\n );\n return [view ?? own, set] as const;\n}\n\nexport interface CarouselOptions {\n count: number;\n index?: number;\n defaultIndex?: number;\n onIndexChange?: (index: number) => void;\n}\n\nexport interface UseCarousel {\n index: number;\n go: (index: number) => void;\n next: () => void;\n prev: () => void;\n /** Pixels the track follows the finger by during a swipe. */\n offset: number;\n dragging: boolean;\n /** Spread on the swipe surface (touch-action: pan-y keeps vertical scrolling). */\n swipe: {\n onPointerDown: (e: PointerEvent<HTMLElement>) => void;\n onPointerMove: (e: PointerEvent<HTMLElement>) => void;\n onPointerUp: (e: PointerEvent<HTMLElement>) => void;\n onPointerCancel: (e: PointerEvent<HTMLElement>) => void;\n onClickCapture: (e: MouseEvent<HTMLElement>) => void;\n };\n /** ← → Home End. */\n onKeyDown: (e: KeyboardEvent<HTMLElement>) => void;\n}\n\n// A drag starting on a playing video's control bar scrubs instead of swiping.\nfunction onControls(e: PointerEvent<HTMLElement>) {\n const t = e.target as HTMLElement;\n if (t.closest?.(\"[data-ckui-noswipe]\")) return true;\n if (!(t instanceof HTMLVideoElement) || !t.controls) return false;\n return e.pointerType === \"mouse\" || e.clientY > t.getBoundingClientRect().bottom - 56;\n}\n\n/** Index, keyboard and swipe state for any carousel UI. */\nexport function useCarousel({ count, index: given, defaultIndex = 0, onIndexChange }: CarouselOptions): UseCarousel {\n const [own, setOwn] = useState(defaultIndex);\n const index = Math.min(Math.max(0, given ?? own), Math.max(0, count - 1));\n const go = useCallback(\n (i: number) => {\n const n = Math.min(Math.max(0, i), Math.max(0, count - 1));\n setOwn(n);\n onIndexChange?.(n);\n },\n [count, onIndexChange],\n );\n const [offset, setOffset] = useState(0);\n const [dragging, setDragging] = useState(false);\n const drag = useRef<{ id: number; x: number; y: number; t: number; width: number; axis?: \"x\" | \"y\" } | null>(null);\n const swallowClick = useRef(false);\n\n const end = (e: PointerEvent<HTMLElement>, cancel: boolean) => {\n const d = drag.current;\n if (!d || d.id !== e.pointerId) return;\n drag.current = null;\n if (d.axis !== \"x\") return;\n const dx = e.clientX - d.x;\n const fast = Math.abs(dx) / Math.max(1, e.timeStamp - d.t) > 0.5;\n setDragging(false);\n setOffset(0);\n swallowClick.current = true;\n if (!cancel && (Math.abs(dx) > d.width * 0.2 || (fast && Math.abs(dx) > 30))) go(index + (dx < 0 ? 1 : -1));\n };\n\n return {\n index,\n go,\n next: () => go(index + 1),\n prev: () => go(index - 1),\n offset,\n dragging,\n swipe: {\n onPointerDown: (e) => {\n // A touch swipe ends without a click, so the flag must not outlive the gesture: it would eat the next tap (a nav button's).\n swallowClick.current = false;\n if (count < 2 || (e.pointerType === \"mouse\" && e.button !== 0) || onControls(e)) return;\n drag.current = { id: e.pointerId, x: e.clientX, y: e.clientY, t: e.timeStamp, width: e.currentTarget.clientWidth || 1 };\n },\n onPointerMove: (e) => {\n const d = drag.current;\n if (!d || d.id !== e.pointerId) return;\n const dx = e.clientX - d.x;\n const dy = e.clientY - d.y;\n if (!d.axis) {\n if (Math.abs(dx) < 8 && Math.abs(dy) < 8) return;\n d.axis = Math.abs(dx) > Math.abs(dy) ? \"x\" : \"y\";\n if (d.axis === \"y\") {\n drag.current = null;\n return;\n }\n e.currentTarget.setPointerCapture?.(e.pointerId);\n setDragging(true);\n }\n const edge = (index === 0 && dx > 0) || (index === count - 1 && dx < 0);\n setOffset(edge ? dx / 3 : dx);\n },\n onPointerUp: (e) => end(e, false),\n onPointerCancel: (e) => end(e, true),\n onClickCapture: (e) => {\n if (!swallowClick.current) return;\n swallowClick.current = false;\n e.preventDefault();\n e.stopPropagation();\n },\n },\n onKeyDown: (e) => {\n const to = { ArrowLeft: index - 1, ArrowRight: index + 1, Home: 0, End: count - 1 }[e.key];\n // A key the player took (a seek) is not a slide change.\n if (to === undefined || e.defaultPrevented || (e.target as HTMLElement).closest?.(\"input, textarea, video, [contenteditable], [role=menu]\")) return;\n e.preventDefault();\n go(to);\n },\n };\n}\n\nexport type PlayerStatus = \"idle\" | \"loading\" | \"playing\" | \"paused\" | \"buffering\" | \"ended\" | \"error\";\n\nexport interface HlsPlayerOptions {\n /** The master playlist; nothing loads while absent. */\n src?: string | null;\n /** Adds auth to playlist and segment requests (e.g. a bearer header for the app's read API). */\n xhrSetup?: (xhr: XMLHttpRequest, url: string) => void | Promise<void>;\n /**\n * Re-grants access (refetch the read API). Called shortly before `expires`,\n * and once after a 401, 403 or 404 — media-gateway answers an expired token\n * with 404 — after which the player retries, resuming where it was.\n */\n refresh?: () => unknown;\n /**\n * The read's `expires` (unix seconds). When a later one arrives the player\n * loads its playlists again at the playhead if their URLs carry the token.\n */\n expires?: number;\n /** Inactive pauses (a swiped-away slide). Default true. */\n active?: boolean;\n /** No playback progress and no bytes for this long is an error. Default 10 s. */\n stallTimeout?: number;\n /** Adaptive-bitrate policy; defaults start at 1080p when the connection allows. */\n abr?: AbrPolicy;\n /** localStorage key remembering the viewer's manual quality; null disables. */\n qualityKey?: string | null;\n /** Where playback starts, seconds (a resume point); a committed preview starts here too. */\n startAt?: number;\n /** Loads and plays at once. A browser that blocks it leaves the play button: never a muted start. */\n autoPlay?: boolean;\n /** The audio language (BCP 47) until the viewer chooses one. */\n audioLanguage?: string;\n /** The subtitle language until the viewer chooses; absent or null: off. */\n subtitleLanguage?: string | null;\n /** localStorage key remembering the viewer's volume and mute; null disables. */\n volumeKey?: string | null;\n /** localStorage key remembering the viewer's audio and subtitle languages; null disables. */\n trackKey?: string | null;\n}\n\nexport const PLAYER_QUALITY_KEY = \"ckui.player.quality\";\n\n// One timer per refresh callback: a gallery and its players share a read.\nconst refreshTimers = new Map<() => unknown, { expires: number; subs: number; timer: ReturnType<typeof setTimeout> }>();\n\n/**\n * Calls refresh shortly before expires (a read's, unix seconds), once however\n * many components ask with the same callback. The next read's later expiry\n * arms it again; a refresh that fails or changes nothing does not repeat.\n */\nexport function useRefreshBeforeExpiry(expires: number | undefined, refresh: (() => unknown) | undefined) {\n useEffect(() => {\n const delay = expiryDelay(expires);\n if (delay === null || !expires || !refresh) return;\n let entry = refreshTimers.get(refresh);\n if (entry?.expires !== expires) {\n if (entry) clearTimeout(entry.timer);\n const made = {\n expires,\n subs: 0,\n timer: setTimeout(() => {\n if (refreshTimers.get(refresh) === made) refreshTimers.delete(refresh);\n Promise.resolve().then(refresh).catch(() => {});\n }, delay),\n };\n refreshTimers.set(refresh, (entry = made));\n }\n const mine = entry;\n mine.subs++;\n return () => {\n if (--mine.subs > 0 || refreshTimers.get(refresh) !== mine) return;\n clearTimeout(mine.timer);\n refreshTimers.delete(refresh);\n };\n }, [expires, refresh]);\n}\n\n/** A selectable rendition; `height` is its short side (portrait 1080×1920 is 1080). */\nexport interface QualityLevel {\n index: number;\n height: number;\n width: number;\n bitrate: number;\n}\n\nexport interface PlayerQuality {\n /** Ascending; empty until the manifest loads, and always under native HLS (Safari), which picks for itself. */\n levels: QualityLevel[];\n /** The locked level index, or -1 for Auto. */\n selected: number;\n /** The level playing now, or -1 before the first switch. */\n current: number;\n /** Locks a level (-1: back to Auto) and remembers the choice. */\n select: (index: number) => void;\n}\n\nexport interface PlayerTracks {\n /** Empty until the manifest lists them. */\n list: PlayerTrack[];\n /** The playing track's index; -1 for none (subtitles off). */\n selected: number;\n /** Switches track (-1: subtitles off) and remembers its language. */\n select: (index: number) => void;\n}\n\nexport interface UseHlsPlayer {\n /** Attach to the `<video>`. */\n ref: (el: HTMLVideoElement | null) => void;\n status: PlayerStatus;\n error?: PlaybackError;\n /** Playback began at least once (show native controls). */\n started: boolean;\n /** Plays unmuted with controls; during a preview, restarts it from 0. */\n play: () => void;\n /** Loads and plays muted from `at` seconds at the lowest rendition (ABR then takes over); not after play. */\n preview: (at: number) => void;\n /** Stops a preview and unloads the player; failures during one are silent. */\n unload: () => void;\n /** A preview is loaded or playing. */\n previewing: boolean;\n /** Reloads from scratch and plays. */\n retry: () => void;\n quality: PlayerQuality;\n audio: PlayerTracks;\n /** The selected subtitle track's TextTrack is kept \"hidden\": its cues are the caller's to draw. */\n subtitles: PlayerTracks;\n}\n\nconst HLS_TYPE = \"application/vnd.apple.mpegurl\";\nlet hinted = false;\n\nfunction corsHint() {\n if (hinted || typeof console === \"undefined\") return;\n hinted = true;\n console.warn(\n `contentkit: video requests failed with no response (status 0). If media is served from another origin, ` +\n `media-gateway must allow this page: set MEDIA_GATEWAY_CORS_ORIGINS to include ${globalThis.location?.origin ?? \"the app origin\"}.`,\n );\n}\n\n// Safari plays HLS natively and hides statuses; one playlist request tells access from absence.\nfunction probe(url: string, setup?: HlsPlayerOptions[\"xhrSetup\"]): Promise<number> {\n return new Promise((resolve) => {\n const xhr = new XMLHttpRequest();\n const send = () => {\n if (xhr.readyState === 0) xhr.open(\"GET\", url, true);\n xhr.onload = () => resolve(xhr.status);\n xhr.onerror = xhr.ontimeout = () => resolve(0);\n xhr.timeout = 8000;\n xhr.send();\n };\n Promise.resolve(setup?.(xhr, url)).then(send, () => resolve(0));\n });\n}\n\nfunction storedQuality(key: string | null): number | \"auto\" | null {\n if (!key) return null;\n try {\n const v = globalThis.localStorage?.getItem(key);\n return v === \"auto\" ? v : v && Number(v) > 0 ? Number(v) : null;\n } catch {\n return null;\n }\n}\n\nfunction storeQuality(key: string | null, v: number | \"auto\") {\n if (!key) return;\n try {\n globalThis.localStorage?.setItem(key, String(v));\n } catch {\n // private mode or blocked storage\n }\n}\n\ninterface NativeAudioTrack {\n label: string;\n language: string;\n enabled: boolean;\n}\ntype NativeAudioTracks = ArrayLike<NativeAudioTrack> & EventTarget;\n\nconst subtitleTracks = (el: HTMLVideoElement) => Array.from(el.textTracks).filter((t) => t.kind === \"subtitles\" || t.kind === \"captions\");\n\nconst asTracks = (ts: readonly { name?: string; label?: string; lang?: string; language?: string; forced?: boolean }[]): PlayerTrack[] =>\n ts.map((t, index) => {\n const lang = t.lang || t.language || undefined;\n return { index, label: t.name || t.label || lang || \"\", lang, ...(t.forced ? { forced: true } : {}) };\n });\n\n// The last throughput measured on this page seeds the next player.\nlet measured = 0;\n\nfunction connection(): ConnectionHint | null {\n const c = (globalThis.navigator as { connection?: ConnectionHint } | undefined)?.connection;\n return c ? { downlink: c.downlink, saveData: c.saveData } : null;\n}\n\n// hls.js's cap to the player's device-pixel size, with the policy's floor and ceiling.\nfunction capController(Base: typeof CapLevelController | undefined, policy: AbrPolicy | undefined): typeof CapLevelController | undefined {\n if (!Base) return undefined;\n return class extends Base {\n override getMaxLevel(capLevelIndex: number): number {\n const levels: Level[] = (this as unknown as { hls: { levels: Level[] } | null }).hls?.levels ?? [];\n return capRung(levels.slice(0, capLevelIndex + 1), this.mediaWidth, this.mediaHeight, policy);\n }\n };\n}\n\n/**\n * HLS playback that fails fast with a reason: hls.js (lazy-loaded) or native\n * HLS, tuned retries, a grant refresh before expiry and one on 401/403/404,\n * and a stall watchdog.\n */\nexport function useHlsPlayer({\n src,\n xhrSetup,\n refresh,\n expires,\n active = true,\n stallTimeout = 10_000,\n abr,\n qualityKey = PLAYER_QUALITY_KEY,\n startAt,\n autoPlay,\n audioLanguage,\n subtitleLanguage,\n volumeKey = PLAYER_VOLUME_KEY,\n trackKey = PLAYER_TRACKS_KEY,\n}: HlsPlayerOptions): UseHlsPlayer {\n const [el, setEl] = useState<HTMLVideoElement | null>(null);\n const [status, setStatus] = useState<PlayerStatus>(\"idle\");\n const [error, setError] = useState<PlaybackError>();\n const [started, setStarted] = useState(false);\n // Nothing loads until the first play.\n const [armed, setArmed] = useState(false);\n const [session, setSession] = useState(0);\n const [previewing, setPreviewing] = useState(false);\n // The preview's start while one is loaded; null otherwise.\n const previewAt = useRef<number | null>(null);\n const stopPreview = useRef<() => void>(() => {});\n const want = useRef(false);\n const start = useRef<(() => void) | null>(null);\n const refreshed = useRef(false);\n const resumeAt = useRef(0);\n const lastProgress = useRef(0);\n const [levels, setLevels] = useState<QualityLevel[]>([]);\n const [selected, setSelected] = useState(-1);\n const [current, setCurrent] = useState(-1);\n const choose = useRef<(index: number) => void>(() => {});\n const [audioList, setAudioList] = useState<PlayerTrack[]>([]);\n const [audioSel, setAudioSel] = useState(-1);\n const [subList, setSubList] = useState<PlayerTrack[]>([]);\n const [subSel, setSubSel] = useState(-1);\n const chooseAudio = useRef<(index: number) => void>(() => {});\n const chooseSubs = useRef<(index: number) => void>(() => {});\n const startedRef = useRef(started);\n startedRef.current = started;\n // A preview becoming real playback: drop its buffer so the chosen level or ABR takes over.\n const commit = useRef<() => void>(() => {});\n const opts = useRef({ xhrSetup, refresh, abr, qualityKey, startAt, audioLanguage, subtitleLanguage, volumeKey, trackKey });\n opts.current = { xhrSetup, refresh, abr, qualityKey, startAt, audioLanguage, subtitleLanguage, volumeKey, trackKey };\n // Loads the playlists again at the playhead when they hold the old token.\n const renew = useRef<() => void>(() => {});\n useRefreshBeforeExpiry(expires, refresh);\n const granted = useRef(expires);\n useEffect(() => {\n const before = granted.current;\n granted.current = expires;\n if (expires && before && expires > before) renew.current();\n }, [expires]);\n\n // A new source starts at its resume point.\n useEffect(() => {\n resumeAt.current = opts.current.startAt ?? 0;\n }, [src]);\n\n useEffect(() => {\n if (!el || !src || !armed) return;\n let dead = false;\n let destroy = () => {};\n let failures = 0;\n let native = false;\n setError(undefined);\n setLevels([]);\n setSelected(-1);\n setCurrent(-1);\n setAudioList([]);\n setAudioSel(-1);\n setSubList([]);\n setSubSel(-1);\n choose.current = () => {};\n chooseAudio.current = () => {};\n chooseSubs.current = () => {};\n commit.current = () => {};\n renew.current = () => {};\n const reload = () => {\n if (dead) return;\n if (previewAt.current !== null) return stopPreview.current();\n resumeAt.current = el.currentTime || 0;\n dead = true;\n destroy();\n setSession((s) => s + 1);\n };\n setStatus(want.current ? \"loading\" : \"idle\");\n const fail = (e: PlaybackError) => {\n if (dead) return;\n const again = opts.current.refresh;\n if (refreshable(e) && again && !refreshed.current) {\n refreshed.current = true;\n resumeAt.current = el.currentTime || 0;\n if (previewAt.current !== null) previewAt.current = resumeAt.current || previewAt.current;\n dead = true;\n destroy();\n setStatus(\"loading\");\n Promise.resolve()\n .then(again)\n .catch(() => {})\n .finally(() => setSession((s) => s + 1));\n return;\n }\n if (previewAt.current !== null) return stopPreview.current();\n if (e.kind === \"network\") corsHint();\n dead = true;\n destroy();\n want.current = false;\n setError(e);\n setStatus(\"error\");\n };\n const onMediaError = () => native && fail(classifyMediaError(el.error));\n // A blocked autoplay leaves the play button; it never retries muted.\n const play = () =>\n el.play().catch((e: unknown) => {\n if (dead || (e as { name?: string } | null)?.name !== \"NotAllowedError\") return;\n want.current = false;\n setStatus((s) => (s === \"error\" ? s : startedRef.current ? \"paused\" : \"idle\"));\n });\n const prefs = () => readTrackPrefs(opts.current.trackKey);\n const wantedSubs = (tracks: readonly { lang?: string; language?: string }[]) => {\n const p = prefs().subtitles;\n return pickTrack(\n tracks.map((t) => ({ lang: t.lang || t.language })),\n p === \"off\" ? null : (p ?? opts.current.subtitleLanguage),\n );\n };\n el.addEventListener(\"error\", onMediaError);\n import(\"hls.js\").then(\n ({ default: Hls }) => {\n if (dead) return;\n if (Hls.isSupported()) {\n let loading = false;\n const conn = connection();\n const policy = conn?.saveData ? { ...opts.current.abr, preferHighRes: false } : opts.current.abr;\n const estimate = initialEstimate(policy, conn, measured);\n const hls = new Hls({\n ...hlsConfig,\n ...abrHlsConfig(policy, estimate),\n capLevelController: capController(Hls.DefaultConfig.capLevelController, policy),\n autoStartLoad: false,\n xhrSetup: (xhr, url) => opts.current.xhrSetup?.(xhr, url),\n });\n destroy = () => hls.destroy();\n // The player draws the cues of the chosen track (\"hidden\"), above its controls.\n hls.subtitleDisplay = false;\n const syncAudio = () => {\n setAudioList(asTracks(hls.audioTracks));\n setAudioSel(hls.audioTrack);\n };\n const syncSubs = () => {\n setSubList(asTracks(hls.subtitleTracks));\n setSubSel(hls.subtitleTrack);\n };\n hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, () => {\n const i = pickTrack(hls.audioTracks, prefs().audio ?? opts.current.audioLanguage);\n if (i >= 0 && i !== hls.audioTrack) hls.audioTrack = i;\n syncAudio();\n });\n hls.on(Hls.Events.AUDIO_TRACK_SWITCHED, syncAudio);\n // A preview loads no subtitles; the commit applies the choice.\n const applySubs = () => {\n if (previewAt.current === null && hls.subtitleTracks.length) hls.subtitleTrack = wantedSubs(hls.subtitleTracks);\n };\n // hls.js selects its own default right after this event: choose after it.\n hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, () => {\n syncSubs();\n queueMicrotask(() => {\n if (dead) return;\n applySubs();\n syncSubs();\n });\n });\n hls.on(Hls.Events.SUBTITLE_TRACK_SWITCH, syncSubs);\n chooseAudio.current = (i) => {\n hls.audioTrack = i;\n };\n chooseSubs.current = (i) => {\n hls.subtitleTrack = i;\n setSubSel(i);\n };\n hls.on(Hls.Events.ERROR, (_, d) => {\n const e = classifyHlsError(d);\n if (d.fatal || e.kind === \"access\" || e.kind === \"not_found\" || e.kind === \"rate_limited\") fail(e);\n else if (e.kind === \"network\" && ++failures >= NETWORK_FAILURES_BEFORE_ERROR) fail(e);\n });\n // A manual choice before loading applies at start.\n let locked = -1;\n const autoStart = () => {\n const box = el.getBoundingClientRect();\n const dpr = globalThis.devicePixelRatio || 1;\n const cap = capRung(hls.levels, box.width * dpr, box.height * dpr, policy);\n return startRung(hls.levels, hls.bandwidthEstimate || estimate, cap, conn);\n };\n // Flush everything buffered and reload at the playhead (-1: ABR's pick).\n let reseek = false;\n const flush = (level: number) => {\n reseek = true;\n hls.currentLevel = level;\n };\n // Browsers keep playing the GOP already decoding after a flush; seeking in place drops it.\n hls.on(Hls.Events.BUFFER_FLUSHED, () => {\n if (!reseek) return;\n reseek = false;\n // eslint-disable-next-line no-self-assign -- a seek in place is the point\n el.currentTime = el.currentTime;\n });\n choose.current = (i) => {\n locked = i;\n setSelected(i);\n if (!loading) return;\n // Auto hands back to ABR without a flush.\n if (i >= 0) flush(i);\n else hls.nextLevel = -1;\n };\n commit.current = () => {\n applySubs();\n if (!loading) return;\n flush(locked);\n if (locked < 0) hls.nextAutoLevel = autoStart();\n };\n hls.on(Hls.Events.LEVEL_SWITCHED, (_, d) => setCurrent(d.level));\n hls.on(Hls.Events.FRAG_LOADED, () => {\n failures = 0;\n lastProgress.current = Date.now();\n measured = hls.bandwidthEstimate;\n });\n // Media playlists are loaded once: their segment URLs keep the token they were signed with.\n const tokenInUrl = () => hls.levels.some((l) => /[?&]t=/.test(l.details?.fragments[0]?.url ?? \"\"));\n renew.current = () => {\n if (loading && tokenInUrl()) reload();\n };\n let parsed = false;\n start.current = () => {\n if (!parsed) return;\n if (!loading) {\n loading = true;\n const at = previewAt.current;\n if (at !== null) {\n hls.startLevel = 0;\n hls.startLoad(at);\n } else {\n hls.startLevel = locked >= 0 ? locked : autoStart();\n if (locked >= 0) hls.loadLevel = locked;\n hls.startLoad(resumeAt.current || -1);\n }\n resumeAt.current = 0;\n }\n void play();\n };\n hls.on(Hls.Events.MANIFEST_PARSED, () => {\n parsed = true;\n for (const i of otherCodecLevels(hls.levels, hls.firstLevel)) hls.removeLevel(i);\n const max = policy?.maxHeight ?? Infinity;\n const list = hls.levels\n .map((l, index) => ({ index, height: Math.min(l.width, l.height) || l.height, width: l.width, bitrate: l.bitrate }))\n .filter((l) => l.height > 0 && l.height <= max);\n setLevels(list);\n // A remembered rung this video lacks (e.g. 2160p not yet encoded) plays Auto.\n const saved = storedQuality(opts.current.qualityKey);\n const match = typeof saved === \"number\" ? list.find((l) => l.height === saved) : undefined;\n if (match) choose.current(match.index);\n if (want.current) start.current?.();\n });\n hls.loadSource(src);\n hls.attachMedia(el);\n return;\n }\n if (!el.canPlayType(HLS_TYPE)) return fail({ kind: \"unsupported\", code: \"no-hls\" });\n void probe(src, opts.current.xhrSetup).then((code) => {\n if (dead) return;\n if (code < 200 || code >= 400) return fail({ kind: statusKind(code), code: `probe/${code}`, status: code });\n native = true;\n renew.current = reload; // its playlists cannot be inspected\n const audioTracks = (el as { audioTracks?: NativeAudioTracks }).audioTracks;\n const syncTracks = () => {\n const a = audioTracks ? Array.from(audioTracks) : [];\n setAudioList(asTracks(a));\n setAudioSel(a.findIndex((t) => t.enabled));\n const t = subtitleTracks(el);\n setSubList(asTracks(t));\n setSubSel(t.findIndex((x) => x.mode !== \"disabled\"));\n };\n chooseAudio.current = (i) => {\n const t = audioTracks?.[i];\n if (t) t.enabled = true;\n syncTracks();\n };\n // Safari lists text tracks as it finds them: the wanted language applies until the viewer chooses.\n let chosen = false;\n const showSubs = (i: number) => {\n subtitleTracks(el).forEach((t, j) => (t.mode = j === i ? \"hidden\" : \"disabled\"));\n syncTracks();\n };\n chooseSubs.current = (i) => {\n chosen = true;\n showSubs(i);\n };\n const wantedNative = () => {\n if (chosen || previewAt.current !== null) return syncTracks();\n showSubs(wantedSubs(asTracks(subtitleTracks(el))));\n };\n const firstTracks = () => {\n const a = audioTracks ? Array.from(audioTracks) : [];\n const i = pickTrack(asTracks(a), prefs().audio ?? opts.current.audioLanguage);\n if (i >= 0 && !a[i]!.enabled) a[i]!.enabled = true;\n wantedNative();\n };\n el.addEventListener(\"loadedmetadata\", firstTracks, { once: true });\n el.textTracks.addEventListener?.(\"addtrack\", wantedNative);\n audioTracks?.addEventListener(\"change\", syncTracks);\n el.src = src;\n if (previewAt.current !== null) el.currentTime = previewAt.current;\n else if (resumeAt.current) el.currentTime = resumeAt.current;\n resumeAt.current = 0;\n destroy = () => {\n el.removeEventListener(\"loadedmetadata\", firstTracks);\n el.textTracks.removeEventListener?.(\"addtrack\", wantedNative);\n audioTracks?.removeEventListener(\"change\", syncTracks);\n el.removeAttribute(\"src\");\n el.load();\n };\n commit.current = wantedNative;\n start.current = () => void play();\n if (want.current) start.current();\n });\n },\n () => fail({ kind: \"unsupported\", code: \"hls.js\" }),\n );\n return () => {\n dead = true;\n renew.current = () => {};\n start.current = null;\n el.removeEventListener(\"error\", onMediaError);\n destroy();\n };\n }, [el, src, armed, session]);\n\n // Media element state.\n useEffect(() => {\n if (!el) return;\n const set = (s: PlayerStatus) => () => setStatus((cur) => (cur === \"error\" ? cur : s));\n const handlers: Record<string, () => void> = {\n playing: () => {\n if (previewAt.current === null) setStarted(true);\n refreshed.current = false;\n lastProgress.current = Date.now();\n set(\"playing\")();\n },\n pause: () => {\n if (!el.ended) set(\"paused\")();\n },\n waiting: () => want.current && set(\"buffering\")(),\n ended: () => (previewAt.current !== null ? stopPreview.current() : set(\"ended\")()),\n progress: () => (lastProgress.current = Date.now()),\n play: () => {\n want.current = true;\n lastProgress.current = Date.now();\n },\n };\n for (const [k, h] of Object.entries(handlers)) el.addEventListener(k, h);\n return () => {\n for (const [k, h] of Object.entries(handlers)) el.removeEventListener(k, h);\n };\n }, [el]);\n\n // Only the visible player plays.\n useEffect(() => {\n if (!active && el && !el.paused) el.pause();\n if (!active) want.current = false;\n }, [active, el]);\n\n // Stall watchdog: wanting playback, yet neither time nor bytes move.\n const watching = status === \"loading\" || status === \"buffering\" || status === \"playing\";\n useEffect(() => {\n if (!watching || !el) return;\n let time = el.currentTime;\n lastProgress.current = Math.max(lastProgress.current, Date.now());\n const id = setInterval(() => {\n if (!want.current || el.paused) return;\n if (el.currentTime !== time) {\n time = el.currentTime;\n lastProgress.current = Date.now();\n } else if (Date.now() - lastProgress.current > stallTimeout) {\n if (previewAt.current !== null) return stopPreview.current();\n el.pause();\n want.current = false;\n setError({ kind: \"stalled\", code: \"stall\" });\n setStatus(\"error\");\n }\n }, 1000);\n return () => clearInterval(id);\n }, [watching, el, stallTimeout]);\n\n const retry = useCallback(() => {\n want.current = true;\n refreshed.current = false;\n lastProgress.current = Date.now();\n setError(undefined);\n setStatus(\"loading\");\n setArmed(true);\n setSession((s) => s + 1);\n }, []);\n\n // The viewer's volume and mute; a preview's mute is the player's, never stored.\n const restoreVolume = useCallback(() => {\n if (!el) return;\n const v = readVolume(opts.current.volumeKey);\n el.volume = v.volume;\n el.muted = v.muted;\n }, [el]);\n\n const unload = useCallback(() => {\n if (previewAt.current === null) return;\n previewAt.current = null;\n want.current = false;\n setPreviewing(false);\n setArmed(false); // the load effect's cleanup destroys hls and detaches the media\n setStatus(\"idle\");\n if (el) {\n el.pause();\n restoreVolume();\n }\n }, [el, restoreVolume]);\n stopPreview.current = unload;\n\n const preview = useCallback(\n (at: number) => {\n // Not once playback was asked for: the preview would swallow its failures.\n if (!el || started || error || want.current || previewAt.current !== null) return;\n previewAt.current = Math.max(0, at);\n el.muted = true;\n want.current = true;\n lastProgress.current = Date.now();\n setPreviewing(true);\n setArmed(true);\n start.current?.();\n },\n [el, started, error],\n );\n\n const play = useCallback(() => {\n if (error) return retry();\n if (previewAt.current !== null) {\n // Committed: the preview's player becomes the real one, from the start (or the resume point).\n previewAt.current = null;\n setPreviewing(false);\n if (el) {\n restoreVolume();\n el.currentTime = opts.current.startAt ?? 0;\n }\n commit.current();\n setStarted(true);\n } else if (!startedRef.current) restoreVolume();\n want.current = true;\n lastProgress.current = Date.now();\n setStatus(\"loading\");\n setArmed(true);\n start.current?.();\n }, [error, retry, el, restoreVolume]);\n\n const playRef = useRef(play);\n playRef.current = play;\n const auto = useRef(autoPlay);\n auto.current = autoPlay;\n useEffect(() => {\n if (el && src && auto.current) playRef.current();\n }, [el, src]);\n\n const levelsRef = useRef(levels);\n levelsRef.current = levels;\n const select = useCallback((index: number) => {\n choose.current(index);\n const h = levelsRef.current.find((l) => l.index === index)?.height;\n storeQuality(opts.current.qualityKey, index === -1 || !h ? \"auto\" : h);\n }, []);\n\n const audioRef = useRef(audioList);\n audioRef.current = audioList;\n const subsRef = useRef(subList);\n subsRef.current = subList;\n const selectAudio = useCallback((index: number) => {\n chooseAudio.current(index);\n const lang = audioRef.current[index]?.lang;\n if (lang) writeTrackPrefs(opts.current.trackKey, { audio: lang });\n }, []);\n const selectSubs = useCallback((index: number) => {\n chooseSubs.current(index);\n const lang = index < 0 ? \"off\" : subsRef.current[index]?.lang;\n if (lang) writeTrackPrefs(opts.current.trackKey, { subtitles: lang });\n }, []);\n\n return {\n ref: setEl,\n status,\n error,\n started,\n play,\n preview,\n unload,\n previewing,\n retry,\n quality: { levels, selected, current, select },\n audio: { list: audioList, selected: audioSel, select: selectAudio },\n subtitles: { list: subList, selected: subSel, select: selectSubs },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gallery.js","names":[],"sources":["../../src/react/gallery.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type KeyboardEvent, type MouseEvent, type PointerEvent } from \"react\";\nimport type { GalleryView } from \"../client/gallery.js\";\nimport type { HlsLoader } from \"../client/http.js\";\nimport type { CapLevelController, Level, Loader, LoaderCallbacks, LoaderConfiguration, LoaderStats, PlaylistLoaderConstructor, PlaylistLoaderContext } from \"hls.js\";\nimport {\n NETWORK_FAILURES_BEFORE_ERROR,\n abrHlsConfig,\n capRung,\n classifyHlsError,\n classifyMediaError,\n hlsConfig,\n initialEstimate,\n otherCodecLevels,\n expiryDelay,\n refreshable,\n startRung,\n statusKind,\n type AbrPolicy,\n type ConnectionHint,\n type PlaybackError,\n} from \"../client/playback.js\";\nimport { PLAYER_TRACKS_KEY, PLAYER_VOLUME_KEY, pickTrack, readTrackPrefs, readVolume, writeTrackPrefs, type PlayerTrack } from \"../client/player.js\";\n\nexport const GALLERY_VIEW_KEY = \"ckui.media-gallery.view\";\n\nexport interface GalleryViewOptions {\n /** Controlled view. */\n view?: GalleryView;\n defaultView?: GalleryView;\n onViewChange?: (view: GalleryView) => void;\n /** localStorage key remembering the viewer's choice; null disables. */\n storageKey?: string | null;\n}\n\nfunction stored(key: string | null): GalleryView | undefined {\n if (!key) return undefined;\n try {\n const v = globalThis.localStorage?.getItem(key);\n return v === \"grid\" || v === \"carousel\" ? v : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Carousel or grid, controlled or not; the viewer's last choice is remembered. */\nexport function useGalleryView({ view, defaultView = \"carousel\", onViewChange, storageKey = GALLERY_VIEW_KEY }: GalleryViewOptions = {}) {\n const [own, setOwn] = useState<GalleryView>(() => stored(storageKey) ?? defaultView);\n const set = useCallback(\n (v: GalleryView) => {\n setOwn(v);\n if (storageKey)\n try {\n globalThis.localStorage?.setItem(storageKey, v);\n } catch {\n // private mode or blocked storage: the choice lasts this page\n }\n onViewChange?.(v);\n },\n [storageKey, onViewChange],\n );\n return [view ?? own, set] as const;\n}\n\nexport interface CarouselOptions {\n count: number;\n index?: number;\n defaultIndex?: number;\n onIndexChange?: (index: number) => void;\n}\n\nexport interface UseCarousel {\n index: number;\n go: (index: number) => void;\n next: () => void;\n prev: () => void;\n /** Pixels the track follows the finger by during a swipe. */\n offset: number;\n dragging: boolean;\n /** Spread on the swipe surface (touch-action: pan-y keeps vertical scrolling). */\n swipe: {\n onPointerDown: (e: PointerEvent<HTMLElement>) => void;\n onPointerMove: (e: PointerEvent<HTMLElement>) => void;\n onPointerUp: (e: PointerEvent<HTMLElement>) => void;\n onPointerCancel: (e: PointerEvent<HTMLElement>) => void;\n onClickCapture: (e: MouseEvent<HTMLElement>) => void;\n };\n /** ← → Home End. */\n onKeyDown: (e: KeyboardEvent<HTMLElement>) => void;\n}\n\n// A drag starting on a playing video's control bar scrubs instead of swiping.\nfunction onControls(e: PointerEvent<HTMLElement>) {\n const t = e.target as HTMLElement;\n if (t.closest?.(\"[data-ckui-noswipe]\")) return true;\n if (!(t instanceof HTMLVideoElement) || !t.controls) return false;\n return e.pointerType === \"mouse\" || e.clientY > t.getBoundingClientRect().bottom - 56;\n}\n\n/** Index, keyboard and swipe state for any carousel UI. */\nexport function useCarousel({ count, index: given, defaultIndex = 0, onIndexChange }: CarouselOptions): UseCarousel {\n const [own, setOwn] = useState(defaultIndex);\n const index = Math.min(Math.max(0, given ?? own), Math.max(0, count - 1));\n const go = useCallback(\n (i: number) => {\n const n = Math.min(Math.max(0, i), Math.max(0, count - 1));\n setOwn(n);\n onIndexChange?.(n);\n },\n [count, onIndexChange],\n );\n const [offset, setOffset] = useState(0);\n const [dragging, setDragging] = useState(false);\n const drag = useRef<{ id: number; x: number; y: number; t: number; width: number; axis?: \"x\" | \"y\" } | null>(null);\n const swallowClick = useRef(false);\n\n const end = (e: PointerEvent<HTMLElement>, cancel: boolean) => {\n const d = drag.current;\n if (!d || d.id !== e.pointerId) return;\n drag.current = null;\n if (d.axis !== \"x\") return;\n const dx = e.clientX - d.x;\n const fast = Math.abs(dx) / Math.max(1, e.timeStamp - d.t) > 0.5;\n setDragging(false);\n setOffset(0);\n swallowClick.current = true;\n if (!cancel && (Math.abs(dx) > d.width * 0.2 || (fast && Math.abs(dx) > 30))) go(index + (dx < 0 ? 1 : -1));\n };\n\n return {\n index,\n go,\n next: () => go(index + 1),\n prev: () => go(index - 1),\n offset,\n dragging,\n swipe: {\n onPointerDown: (e) => {\n // A touch swipe ends without a click, so the flag must not outlive the gesture: it would eat the next tap (a nav button's).\n swallowClick.current = false;\n if (count < 2 || (e.pointerType === \"mouse\" && e.button !== 0) || onControls(e)) return;\n drag.current = { id: e.pointerId, x: e.clientX, y: e.clientY, t: e.timeStamp, width: e.currentTarget.clientWidth || 1 };\n },\n onPointerMove: (e) => {\n const d = drag.current;\n if (!d || d.id !== e.pointerId) return;\n const dx = e.clientX - d.x;\n const dy = e.clientY - d.y;\n if (!d.axis) {\n if (Math.abs(dx) < 8 && Math.abs(dy) < 8) return;\n d.axis = Math.abs(dx) > Math.abs(dy) ? \"x\" : \"y\";\n if (d.axis === \"y\") {\n drag.current = null;\n return;\n }\n e.currentTarget.setPointerCapture?.(e.pointerId);\n setDragging(true);\n }\n const edge = (index === 0 && dx > 0) || (index === count - 1 && dx < 0);\n setOffset(edge ? dx / 3 : dx);\n },\n onPointerUp: (e) => end(e, false),\n onPointerCancel: (e) => end(e, true),\n onClickCapture: (e) => {\n if (!swallowClick.current) return;\n swallowClick.current = false;\n e.preventDefault();\n e.stopPropagation();\n },\n },\n onKeyDown: (e) => {\n const to = { ArrowLeft: index - 1, ArrowRight: index + 1, Home: 0, End: count - 1 }[e.key];\n // A key the player took (a seek) is not a slide change.\n if (to === undefined || e.defaultPrevented || (e.target as HTMLElement).closest?.(\"input, textarea, video, [contenteditable], [role=menu]\")) return;\n e.preventDefault();\n go(to);\n },\n };\n}\n\nexport type PlayerStatus = \"idle\" | \"loading\" | \"playing\" | \"paused\" | \"buffering\" | \"ended\" | \"error\";\n\nexport interface HlsPlayerOptions {\n /** The master playlist; nothing loads while absent. */\n src?: string | null;\n /** Loads playlists with the app's credentials and prepares segment requests: the media client's `hls`. */\n loader?: HlsLoader;\n /**\n * Re-grants access (refetch the read API). Called shortly before `expires`,\n * and once after a 401, 403 or 404 — media-gateway answers an expired token\n * with 404 — after which the player retries, resuming where it was.\n */\n refresh?: () => unknown;\n /**\n * The read's `expires` (unix seconds). When a later one arrives the player\n * loads its playlists again at the playhead if their URLs carry the token.\n */\n expires?: number;\n /** Inactive pauses (a swiped-away slide). Default true. */\n active?: boolean;\n /** No playback progress and no bytes for this long is an error. Default 10 s. */\n stallTimeout?: number;\n /** Adaptive-bitrate policy; defaults start at 1080p when the connection allows. */\n abr?: AbrPolicy;\n /** localStorage key remembering the viewer's manual quality; null disables. */\n qualityKey?: string | null;\n /** Where playback starts, seconds (a resume point); a committed preview starts here too. */\n startAt?: number;\n /** Loads and plays at once. A browser that blocks it leaves the play button: never a muted start. */\n autoPlay?: boolean;\n /** The audio language (BCP 47) until the viewer chooses one. */\n audioLanguage?: string;\n /** The subtitle language until the viewer chooses; absent or null: off. */\n subtitleLanguage?: string | null;\n /** localStorage key remembering the viewer's volume and mute; null disables. */\n volumeKey?: string | null;\n /** localStorage key remembering the viewer's audio and subtitle languages; null disables. */\n trackKey?: string | null;\n}\n\nexport const PLAYER_QUALITY_KEY = \"ckui.player.quality\";\n\n// One timer per refresh callback: a gallery and its players share a read.\nconst refreshTimers = new Map<() => unknown, { expires: number; subs: number; timer: ReturnType<typeof setTimeout> }>();\n\n/**\n * Calls refresh shortly before expires (a read's, unix seconds), once however\n * many components ask with the same callback. The next read's later expiry\n * arms it again; a refresh that fails or changes nothing does not repeat.\n */\nexport function useRefreshBeforeExpiry(expires: number | undefined, refresh: (() => unknown) | undefined) {\n useEffect(() => {\n const delay = expiryDelay(expires);\n if (delay === null || !expires || !refresh) return;\n let entry = refreshTimers.get(refresh);\n if (entry?.expires !== expires) {\n if (entry) clearTimeout(entry.timer);\n const made = {\n expires,\n subs: 0,\n timer: setTimeout(() => {\n if (refreshTimers.get(refresh) === made) refreshTimers.delete(refresh);\n Promise.resolve().then(refresh).catch(() => {});\n }, delay),\n };\n refreshTimers.set(refresh, (entry = made));\n }\n const mine = entry;\n mine.subs++;\n return () => {\n if (--mine.subs > 0 || refreshTimers.get(refresh) !== mine) return;\n clearTimeout(mine.timer);\n refreshTimers.delete(refresh);\n };\n }, [expires, refresh]);\n}\n\n/** A selectable rendition; `height` is its short side (portrait 1080×1920 is 1080). */\nexport interface QualityLevel {\n index: number;\n height: number;\n width: number;\n bitrate: number;\n}\n\nexport interface PlayerQuality {\n /** Ascending; empty until the manifest loads, and always under native HLS (Safari), which picks for itself. */\n levels: QualityLevel[];\n /** The locked level index, or -1 for Auto. */\n selected: number;\n /** The level playing now, or -1 before the first switch. */\n current: number;\n /** Locks a level (-1: back to Auto) and remembers the choice. */\n select: (index: number) => void;\n}\n\nexport interface PlayerTracks {\n /** Empty until the manifest lists them. */\n list: PlayerTrack[];\n /** The playing track's index; -1 for none (subtitles off). */\n selected: number;\n /** Switches track (-1: subtitles off) and remembers its language. */\n select: (index: number) => void;\n}\n\nexport interface UseHlsPlayer {\n /** Attach to the `<video>`. */\n ref: (el: HTMLVideoElement | null) => void;\n status: PlayerStatus;\n error?: PlaybackError;\n /** Playback began at least once (show native controls). */\n started: boolean;\n /** Plays unmuted with controls; during a preview, restarts it from 0. */\n play: () => void;\n /** Loads and plays muted from `at` seconds at the lowest rendition (ABR then takes over); not after play. */\n preview: (at: number) => void;\n /** Stops a preview and unloads the player; failures during one are silent. */\n unload: () => void;\n /** A preview is loaded or playing. */\n previewing: boolean;\n /** Reloads from scratch and plays. */\n retry: () => void;\n quality: PlayerQuality;\n audio: PlayerTracks;\n /** The selected subtitle track's TextTrack is kept \"hidden\": its cues are the caller's to draw. */\n subtitles: PlayerTracks;\n}\n\nconst HLS_TYPE = \"application/vnd.apple.mpegurl\";\nlet hinted = false;\n\nfunction corsHint() {\n if (hinted || typeof console === \"undefined\") return;\n hinted = true;\n console.warn(\n `contentkit: video requests failed with no response (status 0). If media is served from another origin, ` +\n `media-gateway must allow this page: set MEDIA_GATEWAY_CORS_ORIGINS to include ${globalThis.location?.origin ?? \"the app origin\"}.`,\n );\n}\n\n/** Fetches a playlist: the loader's, else a plain fetch. */\nexport const fetchPlaylist = (loader: HlsLoader | undefined, url: string, signal?: AbortSignal): Promise<Response> =>\n loader ? loader.playlist(url, signal) : fetch(url, { signal: signal ?? null });\n\n// Safari plays HLS natively and hides statuses; one playlist request tells access from absence.\nfunction probe(url: string, loader?: HlsLoader): Promise<number> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), 8000);\n return fetchPlaylist(loader, url, ctrl.signal).then(\n (r) => r.status,\n () => 0,\n ).finally(() => clearTimeout(timer));\n}\n\n// hls.js loads playlists through fetchPlaylist (the app's credentials, DPoP\n// included) instead of its XHR loader; statuses reach its retry and the\n// player's refresh as before.\nfunction playlistLoader(load: (url: string, signal: AbortSignal) => Promise<Response>): PlaylistLoaderConstructor {\n return class implements Loader<PlaylistLoaderContext> {\n context: PlaylistLoaderContext | null = null;\n stats: LoaderStats = {\n aborted: false, loaded: 0, retry: 0, total: 0, chunkCount: 0, bwEstimate: 0,\n loading: { start: 0, first: 0, end: 0 }, parsing: { start: 0, end: 0 }, buffering: { start: 0, first: 0, end: 0 },\n };\n private ctrl: AbortController | null = null;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private headers: Headers | null = null;\n private callbacks: LoaderCallbacks<PlaylistLoaderContext> | null = null;\n\n load(context: PlaylistLoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<PlaylistLoaderContext>) {\n const ctrl = new AbortController();\n Object.assign(this, { context, callbacks, ctrl });\n const s = this.stats;\n s.loading.start = performance.now();\n const limit = config.loadPolicy.maxLoadTimeMs;\n if (limit > 0 && Number.isFinite(limit))\n this.timer = setTimeout(() => {\n ctrl.abort();\n if (this.ctrl === ctrl) callbacks.onTimeout(s, context, null);\n }, limit);\n load(context.url, ctrl.signal).then(\n async (res) => {\n s.loading.first = performance.now();\n const text = await res.text();\n if (this.ctrl !== ctrl || ctrl.signal.aborted) return;\n clearTimeout(this.timer);\n this.headers = res.headers;\n s.loaded = s.total = text.length;\n s.loading.end = performance.now();\n if (!res.ok) return callbacks.onError({ code: res.status, text: res.statusText }, context, res, s);\n callbacks.onSuccess({ url: res.url || context.url, data: text, code: res.status }, s, context, res);\n },\n (err: unknown) => {\n if (this.ctrl !== ctrl || ctrl.signal.aborted) return;\n clearTimeout(this.timer);\n callbacks.onError({ code: 0, text: String(err) }, context, null, s);\n },\n );\n }\n\n abort() {\n clearTimeout(this.timer);\n this.ctrl?.abort();\n this.stats.aborted = true;\n if (this.context) this.callbacks?.onAbort?.(this.stats, this.context, null);\n }\n\n destroy() {\n clearTimeout(this.timer);\n this.ctrl?.abort();\n this.ctrl = this.callbacks = this.context = null;\n }\n\n getResponseHeader(name: string) {\n return this.headers?.get(name) ?? null;\n }\n\n getCacheAge() {\n const age = this.headers?.get(\"age\");\n return age ? Number(age) : null;\n }\n };\n}\n\nfunction storedQuality(key: string | null): number | \"auto\" | null {\n if (!key) return null;\n try {\n const v = globalThis.localStorage?.getItem(key);\n return v === \"auto\" ? v : v && Number(v) > 0 ? Number(v) : null;\n } catch {\n return null;\n }\n}\n\nfunction storeQuality(key: string | null, v: number | \"auto\") {\n if (!key) return;\n try {\n globalThis.localStorage?.setItem(key, String(v));\n } catch {\n // private mode or blocked storage\n }\n}\n\ninterface NativeAudioTrack {\n label: string;\n language: string;\n enabled: boolean;\n}\ntype NativeAudioTracks = ArrayLike<NativeAudioTrack> & EventTarget;\n\nconst subtitleTracks = (el: HTMLVideoElement) => Array.from(el.textTracks).filter((t) => t.kind === \"subtitles\" || t.kind === \"captions\");\n\nconst asTracks = (ts: readonly { name?: string; label?: string; lang?: string; language?: string; forced?: boolean }[]): PlayerTrack[] =>\n ts.map((t, index) => {\n const lang = t.lang || t.language || undefined;\n return { index, label: t.name || t.label || lang || \"\", lang, ...(t.forced ? { forced: true } : {}) };\n });\n\n// The last throughput measured on this page seeds the next player.\nlet measured = 0;\n\nfunction connection(): ConnectionHint | null {\n const c = (globalThis.navigator as { connection?: ConnectionHint } | undefined)?.connection;\n return c ? { downlink: c.downlink, saveData: c.saveData } : null;\n}\n\n// hls.js's cap to the player's device-pixel size, with the policy's floor and ceiling.\nfunction capController(Base: typeof CapLevelController | undefined, policy: AbrPolicy | undefined): typeof CapLevelController | undefined {\n if (!Base) return undefined;\n return class extends Base {\n override getMaxLevel(capLevelIndex: number): number {\n const levels: Level[] = (this as unknown as { hls: { levels: Level[] } | null }).hls?.levels ?? [];\n return capRung(levels.slice(0, capLevelIndex + 1), this.mediaWidth, this.mediaHeight, policy);\n }\n };\n}\n\n/**\n * HLS playback that fails fast with a reason: hls.js (lazy-loaded) or native\n * HLS, tuned retries, a grant refresh before expiry and one on 401/403/404,\n * and a stall watchdog.\n */\nexport function useHlsPlayer({\n src,\n loader,\n refresh,\n expires,\n active = true,\n stallTimeout = 10_000,\n abr,\n qualityKey = PLAYER_QUALITY_KEY,\n startAt,\n autoPlay,\n audioLanguage,\n subtitleLanguage,\n volumeKey = PLAYER_VOLUME_KEY,\n trackKey = PLAYER_TRACKS_KEY,\n}: HlsPlayerOptions): UseHlsPlayer {\n const [el, setEl] = useState<HTMLVideoElement | null>(null);\n const [status, setStatus] = useState<PlayerStatus>(\"idle\");\n const [error, setError] = useState<PlaybackError>();\n const [started, setStarted] = useState(false);\n // Nothing loads until the first play.\n const [armed, setArmed] = useState(false);\n const [session, setSession] = useState(0);\n const [previewing, setPreviewing] = useState(false);\n // The preview's start while one is loaded; null otherwise.\n const previewAt = useRef<number | null>(null);\n const stopPreview = useRef<() => void>(() => {});\n const want = useRef(false);\n const start = useRef<(() => void) | null>(null);\n const refreshed = useRef(false);\n const resumeAt = useRef(0);\n const lastProgress = useRef(0);\n const [levels, setLevels] = useState<QualityLevel[]>([]);\n const [selected, setSelected] = useState(-1);\n const [current, setCurrent] = useState(-1);\n const choose = useRef<(index: number) => void>(() => {});\n const [audioList, setAudioList] = useState<PlayerTrack[]>([]);\n const [audioSel, setAudioSel] = useState(-1);\n const [subList, setSubList] = useState<PlayerTrack[]>([]);\n const [subSel, setSubSel] = useState(-1);\n const chooseAudio = useRef<(index: number) => void>(() => {});\n const chooseSubs = useRef<(index: number) => void>(() => {});\n const startedRef = useRef(started);\n startedRef.current = started;\n // A preview becoming real playback: drop its buffer so the chosen level or ABR takes over.\n const commit = useRef<() => void>(() => {});\n const opts = useRef({ loader, refresh, abr, qualityKey, startAt, audioLanguage, subtitleLanguage, volumeKey, trackKey });\n opts.current = { loader, refresh, abr, qualityKey, startAt, audioLanguage, subtitleLanguage, volumeKey, trackKey };\n // Loads the playlists again at the playhead when they hold the old token.\n const renew = useRef<() => void>(() => {});\n useRefreshBeforeExpiry(expires, refresh);\n const granted = useRef(expires);\n useEffect(() => {\n const before = granted.current;\n granted.current = expires;\n if (expires && before && expires > before) renew.current();\n }, [expires]);\n\n // A new source starts at its resume point.\n useEffect(() => {\n resumeAt.current = opts.current.startAt ?? 0;\n }, [src]);\n\n useEffect(() => {\n if (!el || !src || !armed) return;\n let dead = false;\n let destroy = () => {};\n let failures = 0;\n let native = false;\n setError(undefined);\n setLevels([]);\n setSelected(-1);\n setCurrent(-1);\n setAudioList([]);\n setAudioSel(-1);\n setSubList([]);\n setSubSel(-1);\n choose.current = () => {};\n chooseAudio.current = () => {};\n chooseSubs.current = () => {};\n commit.current = () => {};\n renew.current = () => {};\n const reload = () => {\n if (dead) return;\n if (previewAt.current !== null) return stopPreview.current();\n resumeAt.current = el.currentTime || 0;\n dead = true;\n destroy();\n setSession((s) => s + 1);\n };\n setStatus(want.current ? \"loading\" : \"idle\");\n const fail = (e: PlaybackError) => {\n if (dead) return;\n const again = opts.current.refresh;\n if (refreshable(e) && again && !refreshed.current) {\n refreshed.current = true;\n resumeAt.current = el.currentTime || 0;\n if (previewAt.current !== null) previewAt.current = resumeAt.current || previewAt.current;\n dead = true;\n destroy();\n setStatus(\"loading\");\n Promise.resolve()\n .then(again)\n .catch(() => {})\n .finally(() => setSession((s) => s + 1));\n return;\n }\n if (previewAt.current !== null) return stopPreview.current();\n if (e.kind === \"network\") corsHint();\n dead = true;\n destroy();\n want.current = false;\n setError(e);\n setStatus(\"error\");\n };\n const onMediaError = () => native && fail(classifyMediaError(el.error));\n // A blocked autoplay leaves the play button; it never retries muted.\n const play = () =>\n el.play().catch((e: unknown) => {\n if (dead || (e as { name?: string } | null)?.name !== \"NotAllowedError\") return;\n want.current = false;\n setStatus((s) => (s === \"error\" ? s : startedRef.current ? \"paused\" : \"idle\"));\n });\n const prefs = () => readTrackPrefs(opts.current.trackKey);\n const wantedSubs = (tracks: readonly { lang?: string; language?: string }[]) => {\n const p = prefs().subtitles;\n return pickTrack(\n tracks.map((t) => ({ lang: t.lang || t.language })),\n p === \"off\" ? null : (p ?? opts.current.subtitleLanguage),\n );\n };\n el.addEventListener(\"error\", onMediaError);\n import(\"hls.js\").then(\n ({ default: Hls }) => {\n if (dead) return;\n if (Hls.isSupported()) {\n let loading = false;\n const conn = connection();\n const policy = conn?.saveData ? { ...opts.current.abr, preferHighRes: false } : opts.current.abr;\n const estimate = initialEstimate(policy, conn, measured);\n const hls = new Hls({\n ...hlsConfig,\n ...abrHlsConfig(policy, estimate),\n capLevelController: capController(Hls.DefaultConfig.capLevelController, policy),\n autoStartLoad: false,\n pLoader: playlistLoader((url, signal) => fetchPlaylist(opts.current.loader, url, signal)),\n xhrSetup: (xhr, url) => opts.current.loader?.segment(xhr, url),\n });\n destroy = () => hls.destroy();\n // The player draws the cues of the chosen track (\"hidden\"), above its controls.\n hls.subtitleDisplay = false;\n const syncAudio = () => {\n setAudioList(asTracks(hls.audioTracks));\n setAudioSel(hls.audioTrack);\n };\n const syncSubs = () => {\n setSubList(asTracks(hls.subtitleTracks));\n setSubSel(hls.subtitleTrack);\n };\n hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, () => {\n const i = pickTrack(hls.audioTracks, prefs().audio ?? opts.current.audioLanguage);\n if (i >= 0 && i !== hls.audioTrack) hls.audioTrack = i;\n syncAudio();\n });\n hls.on(Hls.Events.AUDIO_TRACK_SWITCHED, syncAudio);\n // A preview loads no subtitles; the commit applies the choice.\n const applySubs = () => {\n if (previewAt.current === null && hls.subtitleTracks.length) hls.subtitleTrack = wantedSubs(hls.subtitleTracks);\n };\n // hls.js selects its own default right after this event: choose after it.\n hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, () => {\n syncSubs();\n queueMicrotask(() => {\n if (dead) return;\n applySubs();\n syncSubs();\n });\n });\n hls.on(Hls.Events.SUBTITLE_TRACK_SWITCH, syncSubs);\n chooseAudio.current = (i) => {\n hls.audioTrack = i;\n };\n chooseSubs.current = (i) => {\n hls.subtitleTrack = i;\n setSubSel(i);\n };\n hls.on(Hls.Events.ERROR, (_, d) => {\n const e = classifyHlsError(d);\n if (d.fatal || e.kind === \"access\" || e.kind === \"not_found\" || e.kind === \"rate_limited\") fail(e);\n else if (e.kind === \"network\" && ++failures >= NETWORK_FAILURES_BEFORE_ERROR) fail(e);\n });\n // A manual choice before loading applies at start.\n let locked = -1;\n const autoStart = () => {\n const box = el.getBoundingClientRect();\n const dpr = globalThis.devicePixelRatio || 1;\n const cap = capRung(hls.levels, box.width * dpr, box.height * dpr, policy);\n return startRung(hls.levels, hls.bandwidthEstimate || estimate, cap, conn);\n };\n // Flush everything buffered and reload at the playhead (-1: ABR's pick).\n let reseek = false;\n const flush = (level: number) => {\n reseek = true;\n hls.currentLevel = level;\n };\n // Browsers keep playing the GOP already decoding after a flush; seeking in place drops it.\n hls.on(Hls.Events.BUFFER_FLUSHED, () => {\n if (!reseek) return;\n reseek = false;\n // eslint-disable-next-line no-self-assign -- a seek in place is the point\n el.currentTime = el.currentTime;\n });\n choose.current = (i) => {\n locked = i;\n setSelected(i);\n if (!loading) return;\n // Auto hands back to ABR without a flush.\n if (i >= 0) flush(i);\n else hls.nextLevel = -1;\n };\n commit.current = () => {\n applySubs();\n if (!loading) return;\n flush(locked);\n if (locked < 0) hls.nextAutoLevel = autoStart();\n };\n hls.on(Hls.Events.LEVEL_SWITCHED, (_, d) => setCurrent(d.level));\n hls.on(Hls.Events.FRAG_LOADED, () => {\n failures = 0;\n lastProgress.current = Date.now();\n measured = hls.bandwidthEstimate;\n });\n // Media playlists are loaded once: their segment URLs keep the token they were signed with.\n const tokenInUrl = () => hls.levels.some((l) => /[?&]t=/.test(l.details?.fragments[0]?.url ?? \"\"));\n renew.current = () => {\n if (loading && tokenInUrl()) reload();\n };\n let parsed = false;\n start.current = () => {\n if (!parsed) return;\n if (!loading) {\n loading = true;\n const at = previewAt.current;\n if (at !== null) {\n hls.startLevel = 0;\n hls.startLoad(at);\n } else {\n hls.startLevel = locked >= 0 ? locked : autoStart();\n if (locked >= 0) hls.loadLevel = locked;\n hls.startLoad(resumeAt.current || -1);\n }\n resumeAt.current = 0;\n }\n void play();\n };\n hls.on(Hls.Events.MANIFEST_PARSED, () => {\n parsed = true;\n for (const i of otherCodecLevels(hls.levels, hls.firstLevel)) hls.removeLevel(i);\n const max = policy?.maxHeight ?? Infinity;\n const list = hls.levels\n .map((l, index) => ({ index, height: Math.min(l.width, l.height) || l.height, width: l.width, bitrate: l.bitrate }))\n .filter((l) => l.height > 0 && l.height <= max);\n setLevels(list);\n // A remembered rung this video lacks (e.g. 2160p not yet encoded) plays Auto.\n const saved = storedQuality(opts.current.qualityKey);\n const match = typeof saved === \"number\" ? list.find((l) => l.height === saved) : undefined;\n if (match) choose.current(match.index);\n if (want.current) start.current?.();\n });\n hls.loadSource(src);\n hls.attachMedia(el);\n return;\n }\n if (!el.canPlayType(HLS_TYPE)) return fail({ kind: \"unsupported\", code: \"no-hls\" });\n void probe(src, opts.current.loader).then((code) => {\n if (dead) return;\n if (code < 200 || code >= 400) return fail({ kind: statusKind(code), code: `probe/${code}`, status: code });\n native = true;\n renew.current = reload; // its playlists cannot be inspected\n const audioTracks = (el as { audioTracks?: NativeAudioTracks }).audioTracks;\n const syncTracks = () => {\n const a = audioTracks ? Array.from(audioTracks) : [];\n setAudioList(asTracks(a));\n setAudioSel(a.findIndex((t) => t.enabled));\n const t = subtitleTracks(el);\n setSubList(asTracks(t));\n setSubSel(t.findIndex((x) => x.mode !== \"disabled\"));\n };\n chooseAudio.current = (i) => {\n const t = audioTracks?.[i];\n if (t) t.enabled = true;\n syncTracks();\n };\n // Safari lists text tracks as it finds them: the wanted language applies until the viewer chooses.\n let chosen = false;\n const showSubs = (i: number) => {\n subtitleTracks(el).forEach((t, j) => (t.mode = j === i ? \"hidden\" : \"disabled\"));\n syncTracks();\n };\n chooseSubs.current = (i) => {\n chosen = true;\n showSubs(i);\n };\n const wantedNative = () => {\n if (chosen || previewAt.current !== null) return syncTracks();\n showSubs(wantedSubs(asTracks(subtitleTracks(el))));\n };\n const firstTracks = () => {\n const a = audioTracks ? Array.from(audioTracks) : [];\n const i = pickTrack(asTracks(a), prefs().audio ?? opts.current.audioLanguage);\n if (i >= 0 && !a[i]!.enabled) a[i]!.enabled = true;\n wantedNative();\n };\n el.addEventListener(\"loadedmetadata\", firstTracks, { once: true });\n el.textTracks.addEventListener?.(\"addtrack\", wantedNative);\n audioTracks?.addEventListener(\"change\", syncTracks);\n el.src = src;\n if (previewAt.current !== null) el.currentTime = previewAt.current;\n else if (resumeAt.current) el.currentTime = resumeAt.current;\n resumeAt.current = 0;\n destroy = () => {\n el.removeEventListener(\"loadedmetadata\", firstTracks);\n el.textTracks.removeEventListener?.(\"addtrack\", wantedNative);\n audioTracks?.removeEventListener(\"change\", syncTracks);\n el.removeAttribute(\"src\");\n el.load();\n };\n commit.current = wantedNative;\n start.current = () => void play();\n if (want.current) start.current();\n });\n },\n () => fail({ kind: \"unsupported\", code: \"hls.js\" }),\n );\n return () => {\n dead = true;\n renew.current = () => {};\n start.current = null;\n el.removeEventListener(\"error\", onMediaError);\n destroy();\n };\n }, [el, src, armed, session]);\n\n // Media element state.\n useEffect(() => {\n if (!el) return;\n const set = (s: PlayerStatus) => () => setStatus((cur) => (cur === \"error\" ? cur : s));\n const handlers: Record<string, () => void> = {\n playing: () => {\n if (previewAt.current === null) setStarted(true);\n refreshed.current = false;\n lastProgress.current = Date.now();\n set(\"playing\")();\n },\n pause: () => {\n if (!el.ended) set(\"paused\")();\n },\n waiting: () => want.current && set(\"buffering\")(),\n ended: () => (previewAt.current !== null ? stopPreview.current() : set(\"ended\")()),\n progress: () => (lastProgress.current = Date.now()),\n play: () => {\n want.current = true;\n lastProgress.current = Date.now();\n },\n };\n for (const [k, h] of Object.entries(handlers)) el.addEventListener(k, h);\n return () => {\n for (const [k, h] of Object.entries(handlers)) el.removeEventListener(k, h);\n };\n }, [el]);\n\n // Only the visible player plays.\n useEffect(() => {\n if (!active && el && !el.paused) el.pause();\n if (!active) want.current = false;\n }, [active, el]);\n\n // Stall watchdog: wanting playback, yet neither time nor bytes move.\n const watching = status === \"loading\" || status === \"buffering\" || status === \"playing\";\n useEffect(() => {\n if (!watching || !el) return;\n let time = el.currentTime;\n lastProgress.current = Math.max(lastProgress.current, Date.now());\n const id = setInterval(() => {\n if (!want.current || el.paused) return;\n if (el.currentTime !== time) {\n time = el.currentTime;\n lastProgress.current = Date.now();\n } else if (Date.now() - lastProgress.current > stallTimeout) {\n if (previewAt.current !== null) return stopPreview.current();\n el.pause();\n want.current = false;\n setError({ kind: \"stalled\", code: \"stall\" });\n setStatus(\"error\");\n }\n }, 1000);\n return () => clearInterval(id);\n }, [watching, el, stallTimeout]);\n\n const retry = useCallback(() => {\n want.current = true;\n refreshed.current = false;\n lastProgress.current = Date.now();\n setError(undefined);\n setStatus(\"loading\");\n setArmed(true);\n setSession((s) => s + 1);\n }, []);\n\n // The viewer's volume and mute; a preview's mute is the player's, never stored.\n const restoreVolume = useCallback(() => {\n if (!el) return;\n const v = readVolume(opts.current.volumeKey);\n el.volume = v.volume;\n el.muted = v.muted;\n }, [el]);\n\n const unload = useCallback(() => {\n if (previewAt.current === null) return;\n previewAt.current = null;\n want.current = false;\n setPreviewing(false);\n setArmed(false); // the load effect's cleanup destroys hls and detaches the media\n setStatus(\"idle\");\n if (el) {\n el.pause();\n restoreVolume();\n }\n }, [el, restoreVolume]);\n stopPreview.current = unload;\n\n const preview = useCallback(\n (at: number) => {\n // Not once playback was asked for: the preview would swallow its failures.\n if (!el || started || error || want.current || previewAt.current !== null) return;\n previewAt.current = Math.max(0, at);\n el.muted = true;\n want.current = true;\n lastProgress.current = Date.now();\n setPreviewing(true);\n setArmed(true);\n start.current?.();\n },\n [el, started, error],\n );\n\n const play = useCallback(() => {\n if (error) return retry();\n if (previewAt.current !== null) {\n // Committed: the preview's player becomes the real one, from the start (or the resume point).\n previewAt.current = null;\n setPreviewing(false);\n if (el) {\n restoreVolume();\n el.currentTime = opts.current.startAt ?? 0;\n }\n commit.current();\n setStarted(true);\n } else if (!startedRef.current) restoreVolume();\n want.current = true;\n lastProgress.current = Date.now();\n setStatus(\"loading\");\n setArmed(true);\n start.current?.();\n }, [error, retry, el, restoreVolume]);\n\n const playRef = useRef(play);\n playRef.current = play;\n const auto = useRef(autoPlay);\n auto.current = autoPlay;\n useEffect(() => {\n if (el && src && auto.current) playRef.current();\n }, [el, src]);\n\n const levelsRef = useRef(levels);\n levelsRef.current = levels;\n const select = useCallback((index: number) => {\n choose.current(index);\n const h = levelsRef.current.find((l) => l.index === index)?.height;\n storeQuality(opts.current.qualityKey, index === -1 || !h ? \"auto\" : h);\n }, []);\n\n const audioRef = useRef(audioList);\n audioRef.current = audioList;\n const subsRef = useRef(subList);\n subsRef.current = subList;\n const selectAudio = useCallback((index: number) => {\n chooseAudio.current(index);\n const lang = audioRef.current[index]?.lang;\n if (lang) writeTrackPrefs(opts.current.trackKey, { audio: lang });\n }, []);\n const selectSubs = useCallback((index: number) => {\n chooseSubs.current(index);\n const lang = index < 0 ? \"off\" : subsRef.current[index]?.lang;\n if (lang) writeTrackPrefs(opts.current.trackKey, { subtitles: lang });\n }, []);\n\n return {\n ref: setEl,\n status,\n error,\n started,\n play,\n preview,\n unload,\n previewing,\n retry,\n quality: { levels, selected, current, select },\n audio: { list: audioList, selected: audioSel, select: selectAudio },\n subtitles: { list: subList, selected: subSel, select: selectSubs },\n };\n}\n"],"mappings":"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"}
|
package/dist/react/player.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SpriteCue } from '../client/player.js';
|
|
2
|
-
import {
|
|
2
|
+
import { HlsLoader } from '../client/http.js';
|
|
3
3
|
/** What the controls show about a `<video>`, kept current from its events. */
|
|
4
4
|
export interface MediaState {
|
|
5
5
|
time: number;
|
|
@@ -41,10 +41,9 @@ export interface Sprite {
|
|
|
41
41
|
width: number;
|
|
42
42
|
height: number;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
export declare function
|
|
47
|
-
export declare function useSprite(vtt: string | null | undefined, setup?: XhrSetup): Sprite | null;
|
|
44
|
+
/** A ladder's sprite.vtt and its sheet, fetched once per URL (through the loader, like the playlists). */
|
|
45
|
+
export declare function loadSprite(vtt: string, loader?: HlsLoader): Promise<Sprite | null>;
|
|
46
|
+
export declare function useSprite(vtt: string | null | undefined, loader?: HlsLoader): Sprite | null;
|
|
48
47
|
/** Why a progress report was sent. */
|
|
49
48
|
export type ProgressReason = "interval" | "pause" | "seek" | "ended" | "hidden" | "unload";
|
|
50
49
|
/** The playhead and the time actually watched, for resume points and watch history. */
|
|
@@ -67,4 +66,3 @@ export declare function useProgressReports(el: HTMLVideoElement | null, o: {
|
|
|
67
66
|
interval: number;
|
|
68
67
|
onProgress?: (p: PlaybackProgress) => void;
|
|
69
68
|
}): void;
|
|
70
|
-
export {};
|
package/dist/react/player.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { parseSpriteVtt as e, playedBetween as t } from "../client/player.js";
|
|
2
|
-
import {
|
|
2
|
+
import { fetchPlaylist as n } from "./gallery.js";
|
|
3
|
+
import { useCallback as r, useEffect as i, useRef as a, useState as o } from "react";
|
|
3
4
|
//#region src/react/player.ts
|
|
4
|
-
var
|
|
5
|
+
var s = {
|
|
5
6
|
time: 0,
|
|
6
7
|
duration: 0,
|
|
7
8
|
buffered: 0,
|
|
@@ -10,7 +11,7 @@ var o = {
|
|
|
10
11
|
volume: 1,
|
|
11
12
|
muted: !1,
|
|
12
13
|
rate: 1
|
|
13
|
-
},
|
|
14
|
+
}, c = [
|
|
14
15
|
"timeupdate",
|
|
15
16
|
"durationchange",
|
|
16
17
|
"loadedmetadata",
|
|
@@ -25,7 +26,7 @@ var o = {
|
|
|
25
26
|
"resize",
|
|
26
27
|
"emptied"
|
|
27
28
|
];
|
|
28
|
-
function
|
|
29
|
+
function l(e) {
|
|
29
30
|
let t = e.currentTime || 0, n = 0;
|
|
30
31
|
for (let r = 0; r < e.buffered.length; r++) e.buffered.start(r) <= t + .5 && t <= e.buffered.end(r) && (n = e.buffered.end(r));
|
|
31
32
|
return {
|
|
@@ -40,40 +41,40 @@ function c(e) {
|
|
|
40
41
|
aspect: e.videoWidth > 0 && e.videoHeight > 0 ? e.videoWidth / e.videoHeight : void 0
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
|
-
function
|
|
44
|
-
let [t, n] =
|
|
45
|
-
return
|
|
44
|
+
function u(e) {
|
|
45
|
+
let [t, n] = o(s);
|
|
46
|
+
return i(() => {
|
|
46
47
|
if (!e) return;
|
|
47
|
-
let t = () => n(
|
|
48
|
+
let t = () => n(l(e));
|
|
48
49
|
t();
|
|
49
|
-
for (let n of
|
|
50
|
+
for (let n of c) e.addEventListener(n, t);
|
|
50
51
|
return () => {
|
|
51
|
-
for (let n of
|
|
52
|
+
for (let n of c) e.removeEventListener(n, t);
|
|
52
53
|
};
|
|
53
54
|
}, [e]), t;
|
|
54
55
|
}
|
|
55
|
-
var
|
|
56
|
+
var d = () => {
|
|
56
57
|
if (typeof document > "u") return null;
|
|
57
58
|
let e = document;
|
|
58
59
|
return e.fullscreenElement ?? e.webkitFullscreenElement ?? null;
|
|
59
60
|
};
|
|
60
|
-
function
|
|
61
|
-
let [
|
|
62
|
-
|
|
61
|
+
function f(e, t) {
|
|
62
|
+
let [n, a] = o(!1);
|
|
63
|
+
i(() => {
|
|
63
64
|
if (!e) return;
|
|
64
|
-
let n = t, r = () =>
|
|
65
|
+
let n = t, r = () => a(d() === e || !!n?.webkitDisplayingFullscreen);
|
|
65
66
|
return document.addEventListener("fullscreenchange", r), document.addEventListener("webkitfullscreenchange", r), n?.addEventListener("webkitbeginfullscreen", r), n?.addEventListener("webkitendfullscreen", r), () => {
|
|
66
67
|
document.removeEventListener("fullscreenchange", r), document.removeEventListener("webkitfullscreenchange", r), n?.removeEventListener("webkitbeginfullscreen", r), n?.removeEventListener("webkitendfullscreen", r);
|
|
67
68
|
};
|
|
68
69
|
}, [e, t]);
|
|
69
70
|
let s = e, c = t;
|
|
70
71
|
return {
|
|
71
|
-
active:
|
|
72
|
+
active: n,
|
|
72
73
|
supported: !!(s?.requestFullscreen || s?.webkitRequestFullscreen || c?.webkitEnterFullscreen),
|
|
73
|
-
toggle:
|
|
74
|
+
toggle: r(() => {
|
|
74
75
|
if (!s) return;
|
|
75
76
|
let e = document;
|
|
76
|
-
if (
|
|
77
|
+
if (d() === s) return void (e.exitFullscreen ? e.exitFullscreen().catch(() => {}) : e.webkitExitFullscreen?.());
|
|
77
78
|
if (c?.webkitDisplayingFullscreen) return c.webkitExitFullscreen?.();
|
|
78
79
|
if (s.requestFullscreen) return void s.requestFullscreen().catch(() => {});
|
|
79
80
|
if (s.webkitRequestFullscreen) return s.webkitRequestFullscreen();
|
|
@@ -81,29 +82,29 @@ function d(e, t) {
|
|
|
81
82
|
}, [s, c])
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
|
-
function
|
|
85
|
-
let [t,
|
|
86
|
-
return
|
|
85
|
+
function p(e) {
|
|
86
|
+
let [t, n] = o(!1);
|
|
87
|
+
return i(() => {
|
|
87
88
|
if (!e) return;
|
|
88
|
-
let t = () =>
|
|
89
|
-
return e.addEventListener("enterpictureinpicture", t), e.addEventListener("leavepictureinpicture",
|
|
90
|
-
e.removeEventListener("enterpictureinpicture", t), e.removeEventListener("leavepictureinpicture",
|
|
89
|
+
let t = () => n(!0), r = () => n(!1);
|
|
90
|
+
return e.addEventListener("enterpictureinpicture", t), e.addEventListener("leavepictureinpicture", r), () => {
|
|
91
|
+
e.removeEventListener("enterpictureinpicture", t), e.removeEventListener("leavepictureinpicture", r);
|
|
91
92
|
};
|
|
92
93
|
}, [e]), {
|
|
93
94
|
active: t,
|
|
94
95
|
supported: typeof document < "u" && !!document.pictureInPictureEnabled && !!e && !e.disablePictureInPicture,
|
|
95
|
-
toggle:
|
|
96
|
+
toggle: r(() => {
|
|
96
97
|
e && (document.pictureInPictureElement === e ? document.exitPictureInPicture().catch(() => {}) : e.requestPictureInPicture?.().catch(() => {}));
|
|
97
98
|
}, [e])
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
|
-
var
|
|
101
|
-
function
|
|
102
|
-
let [t, n] =
|
|
103
|
-
return
|
|
101
|
+
var m = (e) => e.kind === "subtitles" || e.kind === "captions";
|
|
102
|
+
function h(e) {
|
|
103
|
+
let [t, n] = o([]);
|
|
104
|
+
return i(() => {
|
|
104
105
|
if (!e) return;
|
|
105
106
|
let t = e.textTracks, r = null, i = () => n(r?.activeCues ? Array.from(r.activeCues) : []), a = () => {
|
|
106
|
-
let e = Array.from(t).find((e) =>
|
|
107
|
+
let e = Array.from(t).find((e) => m(e) && e.mode !== "disabled") ?? null;
|
|
107
108
|
if (e?.mode === "showing" && (e.mode = "hidden"), e === r) return i();
|
|
108
109
|
r?.removeEventListener("cuechange", i), r = e, r?.addEventListener("cuechange", i), i();
|
|
109
110
|
};
|
|
@@ -112,18 +113,10 @@ function m(e) {
|
|
|
112
113
|
};
|
|
113
114
|
}, [e]), t;
|
|
114
115
|
}
|
|
115
|
-
var
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Promise.resolve(t?.(r, e)).then(() => {
|
|
120
|
-
r.readyState === 0 && r.open("GET", e, !0), r.onload = () => n(r.status === 200 ? r.responseText : ""), r.onerror = () => n(""), r.send();
|
|
121
|
-
}, () => n(""));
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function _(t, n) {
|
|
125
|
-
let r = h.get(t);
|
|
126
|
-
return r || (r = g(t, n).then((n) => {
|
|
116
|
+
var g = /* @__PURE__ */ new Map(), _ = (e, t) => n(t, e).then((e) => e.status === 200 ? e.text() : "", () => "");
|
|
117
|
+
function v(t, n) {
|
|
118
|
+
let r = g.get(t);
|
|
119
|
+
return r || (r = _(t, n).then((n) => {
|
|
127
120
|
let r = n ? e(n, new URL(t, globalThis.location?.href).href) : [];
|
|
128
121
|
return r.length ? new Promise((e) => {
|
|
129
122
|
let t = new Image();
|
|
@@ -133,14 +126,14 @@ function _(t, n) {
|
|
|
133
126
|
height: t.naturalHeight
|
|
134
127
|
}), t.onerror = () => e(null), t.src = r[0].url;
|
|
135
128
|
}) : null;
|
|
136
|
-
}),
|
|
129
|
+
}), g.set(t, r), r.then((e) => e || g.delete(t))), r;
|
|
137
130
|
}
|
|
138
|
-
function
|
|
139
|
-
let [n,
|
|
140
|
-
return s.current = t,
|
|
131
|
+
function y(e, t) {
|
|
132
|
+
let [n, r] = o(null), s = a(t);
|
|
133
|
+
return s.current = t, i(() => {
|
|
141
134
|
if (!e) return;
|
|
142
135
|
let t = !0;
|
|
143
|
-
return
|
|
136
|
+
return v(e, s.current).then((n) => t && r({
|
|
144
137
|
vtt: e,
|
|
145
138
|
sprite: n
|
|
146
139
|
})), () => {
|
|
@@ -148,24 +141,24 @@ function v(e, t) {
|
|
|
148
141
|
};
|
|
149
142
|
}, [e]), n && n.vtt === e ? n.sprite : null;
|
|
150
143
|
}
|
|
151
|
-
function
|
|
152
|
-
let
|
|
153
|
-
|
|
154
|
-
let o =
|
|
144
|
+
function b(e, n) {
|
|
145
|
+
let r = a(n.onProgress);
|
|
146
|
+
r.current = n.onProgress;
|
|
147
|
+
let o = a(n.enabled);
|
|
155
148
|
o.current = n.enabled;
|
|
156
149
|
let { interval: s, source: c } = n;
|
|
157
|
-
|
|
150
|
+
i(() => {
|
|
158
151
|
if (!e || !c) return;
|
|
159
|
-
let n = 0,
|
|
152
|
+
let n = 0, i = null, a = 0, l = !1, u = 0, d = 0, f = !1, p = () => {
|
|
160
153
|
(e.currentSrc || e.readyState > 0) && (u = e.currentTime || 0, d = Number.isFinite(e.duration) ? e.duration : d, f = e.ended);
|
|
161
154
|
}, m = () => ({
|
|
162
155
|
at: Date.now(),
|
|
163
156
|
pos: e.currentTime || 0
|
|
164
157
|
}), h = () => {
|
|
165
|
-
let
|
|
166
|
-
|
|
158
|
+
let r = m();
|
|
159
|
+
i && !e.paused && (n += t(i, r)), i = r;
|
|
167
160
|
}, g = (e) => {
|
|
168
|
-
o.current &&
|
|
161
|
+
o.current && r.current && (l = !0, a = Date.now(), r.current({
|
|
169
162
|
time: u,
|
|
170
163
|
duration: d,
|
|
171
164
|
played: n,
|
|
@@ -174,20 +167,20 @@ function y(e, n) {
|
|
|
174
167
|
}));
|
|
175
168
|
}, _ = {
|
|
176
169
|
playing: () => {
|
|
177
|
-
p(),
|
|
170
|
+
p(), i = m();
|
|
178
171
|
},
|
|
179
172
|
timeupdate: () => {
|
|
180
|
-
e.paused || e.seeking || (h(), p(), Date.now() -
|
|
173
|
+
e.paused || e.seeking || (h(), p(), Date.now() - a >= s && g("interval"));
|
|
181
174
|
},
|
|
182
175
|
durationchange: p,
|
|
183
176
|
pause: () => {
|
|
184
177
|
h(), p(), e.ended || g("pause");
|
|
185
178
|
},
|
|
186
179
|
seeking: () => {
|
|
187
|
-
|
|
180
|
+
i = null;
|
|
188
181
|
},
|
|
189
182
|
seeked: () => {
|
|
190
|
-
p(),
|
|
183
|
+
p(), i = e.paused ? null : m(), g("seek");
|
|
191
184
|
},
|
|
192
185
|
ended: () => {
|
|
193
186
|
h(), p(), g("ended");
|
|
@@ -209,6 +202,6 @@ function y(e, n) {
|
|
|
209
202
|
]);
|
|
210
203
|
}
|
|
211
204
|
//#endregion
|
|
212
|
-
export {
|
|
205
|
+
export { v as loadSprite, h as useActiveCues, f as useFullscreen, u as useMediaState, p as usePictureInPicture, b as useProgressReports, y as useSprite };
|
|
213
206
|
|
|
214
207
|
//# sourceMappingURL=player.js.map
|
package/dist/react/player.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player.js","names":[],"sources":["../../src/react/player.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { parseSpriteVtt, playedBetween, type PlaySample, type SpriteCue } from \"../client/player.js\";\nimport type { HlsPlayerOptions } from \"./gallery.js\";\n\n/** What the controls show about a `<video>`, kept current from its events. */\nexport interface MediaState {\n time: number;\n duration: number;\n /** The end of the buffered range holding the playhead. */\n buffered: number;\n paused: boolean;\n ended: boolean;\n volume: number;\n muted: boolean;\n rate: number;\n /** The decoded frame's width / height, once known. */\n aspect?: number;\n}\n\nconst idle: MediaState = { time: 0, duration: 0, buffered: 0, paused: true, ended: false, volume: 1, muted: false, rate: 1 };\n\nconst MEDIA_EVENTS = [\"timeupdate\", \"durationchange\", \"loadedmetadata\", \"progress\", \"play\", \"pause\", \"ended\", \"volumechange\", \"ratechange\", \"seeking\", \"seeked\", \"resize\", \"emptied\"];\n\nfunction snapshot(el: HTMLVideoElement): MediaState {\n const time = el.currentTime || 0;\n let buffered = 0;\n for (let i = 0; i < el.buffered.length; i++)\n if (el.buffered.start(i) <= time + 0.5 && time <= el.buffered.end(i)) buffered = el.buffered.end(i);\n const duration = Number.isFinite(el.duration) ? el.duration : 0;\n return {\n time,\n duration,\n buffered,\n paused: el.paused,\n ended: el.ended,\n volume: el.volume,\n muted: el.muted,\n rate: el.playbackRate,\n aspect: el.videoWidth > 0 && el.videoHeight > 0 ? el.videoWidth / el.videoHeight : undefined,\n };\n}\n\nexport function useMediaState(el: HTMLVideoElement | null): MediaState {\n const [state, setState] = useState(idle);\n useEffect(() => {\n if (!el) return;\n const sync = () => setState(snapshot(el));\n sync();\n for (const e of MEDIA_EVENTS) el.addEventListener(e, sync);\n return () => {\n for (const e of MEDIA_EVENTS) el.removeEventListener(e, sync);\n };\n }, [el]);\n return state;\n}\n\ntype WebkitDocument = Document & { webkitFullscreenElement?: Element | null; webkitExitFullscreen?: () => void };\ntype WebkitElement = HTMLElement & { webkitRequestFullscreen?: () => void };\ntype WebkitVideo = HTMLVideoElement & { webkitEnterFullscreen?: () => void; webkitExitFullscreen?: () => void; webkitDisplayingFullscreen?: boolean };\n\nconst fullscreenElement = () => {\n if (typeof document === \"undefined\") return null;\n const d = document as WebkitDocument;\n return d.fullscreenElement ?? d.webkitFullscreenElement ?? null;\n};\n\n/**\n * Fullscreen on the player's root, so the controls and menus come along;\n * iPhone Safari, which only fullscreens a video, gets its native player.\n */\nexport function useFullscreen(root: HTMLElement | null, el: HTMLVideoElement | null) {\n const [active, setActive] = useState(false);\n useEffect(() => {\n if (!root) return;\n const v = el as WebkitVideo | null;\n const sync = () => setActive(fullscreenElement() === root || !!v?.webkitDisplayingFullscreen);\n document.addEventListener(\"fullscreenchange\", sync);\n document.addEventListener(\"webkitfullscreenchange\", sync);\n v?.addEventListener(\"webkitbeginfullscreen\", sync);\n v?.addEventListener(\"webkitendfullscreen\", sync);\n return () => {\n document.removeEventListener(\"fullscreenchange\", sync);\n document.removeEventListener(\"webkitfullscreenchange\", sync);\n v?.removeEventListener(\"webkitbeginfullscreen\", sync);\n v?.removeEventListener(\"webkitendfullscreen\", sync);\n };\n }, [root, el]);\n const r = root as WebkitElement | null;\n const v = el as WebkitVideo | null;\n const supported = !!(r?.requestFullscreen || r?.webkitRequestFullscreen || v?.webkitEnterFullscreen);\n const toggle = useCallback(() => {\n if (!r) return;\n const d = document as WebkitDocument;\n if (fullscreenElement() === r) return void (d.exitFullscreen ? d.exitFullscreen().catch(() => {}) : d.webkitExitFullscreen?.());\n if (v?.webkitDisplayingFullscreen) return v.webkitExitFullscreen?.();\n if (r.requestFullscreen) return void r.requestFullscreen().catch(() => {});\n if (r.webkitRequestFullscreen) return r.webkitRequestFullscreen();\n v?.webkitEnterFullscreen?.();\n }, [r, v]);\n return { active, supported, toggle };\n}\n\nexport function usePictureInPicture(el: HTMLVideoElement | null) {\n const [active, setActive] = useState(false);\n useEffect(() => {\n if (!el) return;\n const on = () => setActive(true);\n const off = () => setActive(false);\n el.addEventListener(\"enterpictureinpicture\", on);\n el.addEventListener(\"leavepictureinpicture\", off);\n return () => {\n el.removeEventListener(\"enterpictureinpicture\", on);\n el.removeEventListener(\"leavepictureinpicture\", off);\n };\n }, [el]);\n const supported = typeof document !== \"undefined\" && !!document.pictureInPictureEnabled && !!el && !el.disablePictureInPicture;\n const toggle = useCallback(() => {\n if (!el) return;\n if (document.pictureInPictureElement === el) void document.exitPictureInPicture().catch(() => {});\n else void el.requestPictureInPicture?.().catch(() => {});\n }, [el]);\n return { active, supported, toggle };\n}\n\nconst isSubtitles = (t: TextTrack) => t.kind === \"subtitles\" || t.kind === \"captions\";\n\n/**\n * The cues showing now on the video's chosen subtitle track. The track is\n * kept \"hidden\" so the browser draws nothing and the player places the\n * cues itself, clear of its controls.\n */\nexport function useActiveCues(el: HTMLVideoElement | null): VTTCue[] {\n const [cues, setCues] = useState<VTTCue[]>([]);\n useEffect(() => {\n if (!el) return;\n const tracks = el.textTracks;\n let track: TextTrack | null = null;\n const show = () => setCues(track?.activeCues ? (Array.from(track.activeCues) as VTTCue[]) : []);\n const pick = () => {\n const next = Array.from(tracks).find((t) => isSubtitles(t) && t.mode !== \"disabled\") ?? null;\n if (next?.mode === \"showing\") next.mode = \"hidden\";\n if (next === track) return show();\n track?.removeEventListener(\"cuechange\", show);\n track = next;\n track?.addEventListener(\"cuechange\", show);\n show();\n };\n pick();\n tracks.addEventListener?.(\"change\", pick);\n tracks.addEventListener?.(\"addtrack\", pick);\n tracks.addEventListener?.(\"removetrack\", pick);\n return () => {\n track?.removeEventListener(\"cuechange\", show);\n tracks.removeEventListener?.(\"change\", pick);\n tracks.removeEventListener?.(\"addtrack\", pick);\n tracks.removeEventListener?.(\"removetrack\", pick);\n };\n }, [el]);\n return cues;\n}\n\n/** A seek sprite: its cues and the size of the sheet they cut. */\nexport interface Sprite {\n cues: SpriteCue[];\n width: number;\n height: number;\n}\n\ntype XhrSetup = HlsPlayerOptions[\"xhrSetup\"];\nconst sprites = new Map<string, Promise<Sprite | null>>();\n\nfunction fetchText(url: string, setup: XhrSetup): Promise<string> {\n return new Promise((resolve) => {\n const xhr = new XMLHttpRequest();\n const send = () => {\n if (xhr.readyState === 0) xhr.open(\"GET\", url, true);\n xhr.onload = () => resolve(xhr.status === 200 ? xhr.responseText : \"\");\n xhr.onerror = () => resolve(\"\");\n xhr.send();\n };\n Promise.resolve(setup?.(xhr, url)).then(send, () => resolve(\"\"));\n });\n}\n\n/** A ladder's sprite.vtt and its sheet, fetched once per URL (through xhrSetup, like the playlists). */\nexport function loadSprite(vtt: string, setup?: XhrSetup): Promise<Sprite | null> {\n let p = sprites.get(vtt);\n if (!p) {\n p = fetchText(vtt, setup).then((text) => {\n const cues = text ? parseSpriteVtt(text, new URL(vtt, globalThis.location?.href).href) : [];\n if (!cues.length) return null;\n return new Promise<Sprite | null>((resolve) => {\n const img = new Image();\n img.onload = () => resolve({ cues, width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = () => resolve(null);\n img.src = cues[0]!.url;\n });\n });\n sprites.set(vtt, p);\n void p.then((s) => s || sprites.delete(vtt));\n }\n return p;\n}\n\nexport function useSprite(vtt: string | null | undefined, setup?: XhrSetup): Sprite | null {\n const [sprite, setSprite] = useState<{ vtt: string; sprite: Sprite | null } | null>(null);\n const setupRef = useRef(setup);\n setupRef.current = setup;\n useEffect(() => {\n if (!vtt) return;\n let live = true;\n void loadSprite(vtt, setupRef.current).then((s) => live && setSprite({ vtt, sprite: s }));\n return () => {\n live = false;\n };\n }, [vtt]);\n return sprite && sprite.vtt === vtt ? sprite.sprite : null;\n}\n\n/** Why a progress report was sent. */\nexport type ProgressReason = \"interval\" | \"pause\" | \"seek\" | \"ended\" | \"hidden\" | \"unload\";\n\n/** The playhead and the time actually watched, for resume points and watch history. */\nexport interface PlaybackProgress {\n /** Seconds. */\n time: number;\n duration: number;\n /** Seconds actually played since this source loaded; seeking adds nothing. */\n played: number;\n ended: boolean;\n reason: ProgressReason;\n}\n\n/**\n * Reports progress while a committed playback runs: every `interval` ms of\n * playback, and on pause, seek, end, page hide and unload.\n */\nexport function useProgressReports(\n el: HTMLVideoElement | null,\n o: { enabled: boolean; source?: string | null; interval: number; onProgress?: (p: PlaybackProgress) => void },\n) {\n const cb = useRef(o.onProgress);\n cb.current = o.onProgress;\n const enabled = useRef(o.enabled);\n enabled.current = o.enabled;\n const { interval, source } = o;\n useEffect(() => {\n if (!el || !source) return;\n let played = 0;\n let sample: PlaySample | null = null;\n let last = 0;\n let reported = false;\n // The last known playhead: by unload the player may have detached the media.\n let pos = 0;\n let dur = 0;\n let ended = false;\n const observe = () => {\n if (el.currentSrc || el.readyState > 0) {\n pos = el.currentTime || 0;\n dur = Number.isFinite(el.duration) ? el.duration : dur;\n ended = el.ended;\n }\n };\n const now = (): PlaySample => ({ at: Date.now(), pos: el.currentTime || 0 });\n const tally = () => {\n const s = now();\n if (sample && !el.paused) played += playedBetween(sample, s);\n sample = s;\n };\n const report = (reason: ProgressReason) => {\n if (!enabled.current || !cb.current) return;\n reported = true;\n last = Date.now();\n cb.current({ time: pos, duration: dur, played, ended, reason });\n };\n const handlers: Record<string, () => void> = {\n playing: () => {\n observe();\n sample = now();\n },\n timeupdate: () => {\n if (el.paused || el.seeking) return;\n tally();\n observe();\n if (Date.now() - last >= interval) report(\"interval\");\n },\n durationchange: observe,\n pause: () => {\n tally();\n observe();\n if (!el.ended) report(\"pause\");\n },\n seeking: () => {\n sample = null;\n },\n seeked: () => {\n observe();\n sample = el.paused ? null : now();\n report(\"seek\");\n },\n ended: () => {\n tally();\n observe();\n report(\"ended\");\n },\n };\n let pageHide = false;\n const hidden = () => {\n if (document.visibilityState !== \"hidden\" && !pageHide) return;\n tally();\n observe();\n report(\"hidden\");\n };\n const pagehide = () => {\n pageHide = true;\n hidden();\n pageHide = false;\n };\n for (const [k, h] of Object.entries(handlers)) el.addEventListener(k, h);\n document.addEventListener(\"visibilitychange\", hidden);\n globalThis.addEventListener?.(\"pagehide\", pagehide);\n return () => {\n for (const [k, h] of Object.entries(handlers)) el.removeEventListener(k, h);\n document.removeEventListener(\"visibilitychange\", hidden);\n globalThis.removeEventListener?.(\"pagehide\", pagehide);\n if (reported || played > 0) report(\"unload\");\n };\n }, [el, source, interval]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"player.js","names":[],"sources":["../../src/react/player.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { parseSpriteVtt, playedBetween, type PlaySample, type SpriteCue } from \"../client/player.js\";\nimport type { HlsLoader } from \"../client/http.js\";\nimport { fetchPlaylist } from \"./gallery.js\";\n\n/** What the controls show about a `<video>`, kept current from its events. */\nexport interface MediaState {\n time: number;\n duration: number;\n /** The end of the buffered range holding the playhead. */\n buffered: number;\n paused: boolean;\n ended: boolean;\n volume: number;\n muted: boolean;\n rate: number;\n /** The decoded frame's width / height, once known. */\n aspect?: number;\n}\n\nconst idle: MediaState = { time: 0, duration: 0, buffered: 0, paused: true, ended: false, volume: 1, muted: false, rate: 1 };\n\nconst MEDIA_EVENTS = [\"timeupdate\", \"durationchange\", \"loadedmetadata\", \"progress\", \"play\", \"pause\", \"ended\", \"volumechange\", \"ratechange\", \"seeking\", \"seeked\", \"resize\", \"emptied\"];\n\nfunction snapshot(el: HTMLVideoElement): MediaState {\n const time = el.currentTime || 0;\n let buffered = 0;\n for (let i = 0; i < el.buffered.length; i++)\n if (el.buffered.start(i) <= time + 0.5 && time <= el.buffered.end(i)) buffered = el.buffered.end(i);\n const duration = Number.isFinite(el.duration) ? el.duration : 0;\n return {\n time,\n duration,\n buffered,\n paused: el.paused,\n ended: el.ended,\n volume: el.volume,\n muted: el.muted,\n rate: el.playbackRate,\n aspect: el.videoWidth > 0 && el.videoHeight > 0 ? el.videoWidth / el.videoHeight : undefined,\n };\n}\n\nexport function useMediaState(el: HTMLVideoElement | null): MediaState {\n const [state, setState] = useState(idle);\n useEffect(() => {\n if (!el) return;\n const sync = () => setState(snapshot(el));\n sync();\n for (const e of MEDIA_EVENTS) el.addEventListener(e, sync);\n return () => {\n for (const e of MEDIA_EVENTS) el.removeEventListener(e, sync);\n };\n }, [el]);\n return state;\n}\n\ntype WebkitDocument = Document & { webkitFullscreenElement?: Element | null; webkitExitFullscreen?: () => void };\ntype WebkitElement = HTMLElement & { webkitRequestFullscreen?: () => void };\ntype WebkitVideo = HTMLVideoElement & { webkitEnterFullscreen?: () => void; webkitExitFullscreen?: () => void; webkitDisplayingFullscreen?: boolean };\n\nconst fullscreenElement = () => {\n if (typeof document === \"undefined\") return null;\n const d = document as WebkitDocument;\n return d.fullscreenElement ?? d.webkitFullscreenElement ?? null;\n};\n\n/**\n * Fullscreen on the player's root, so the controls and menus come along;\n * iPhone Safari, which only fullscreens a video, gets its native player.\n */\nexport function useFullscreen(root: HTMLElement | null, el: HTMLVideoElement | null) {\n const [active, setActive] = useState(false);\n useEffect(() => {\n if (!root) return;\n const v = el as WebkitVideo | null;\n const sync = () => setActive(fullscreenElement() === root || !!v?.webkitDisplayingFullscreen);\n document.addEventListener(\"fullscreenchange\", sync);\n document.addEventListener(\"webkitfullscreenchange\", sync);\n v?.addEventListener(\"webkitbeginfullscreen\", sync);\n v?.addEventListener(\"webkitendfullscreen\", sync);\n return () => {\n document.removeEventListener(\"fullscreenchange\", sync);\n document.removeEventListener(\"webkitfullscreenchange\", sync);\n v?.removeEventListener(\"webkitbeginfullscreen\", sync);\n v?.removeEventListener(\"webkitendfullscreen\", sync);\n };\n }, [root, el]);\n const r = root as WebkitElement | null;\n const v = el as WebkitVideo | null;\n const supported = !!(r?.requestFullscreen || r?.webkitRequestFullscreen || v?.webkitEnterFullscreen);\n const toggle = useCallback(() => {\n if (!r) return;\n const d = document as WebkitDocument;\n if (fullscreenElement() === r) return void (d.exitFullscreen ? d.exitFullscreen().catch(() => {}) : d.webkitExitFullscreen?.());\n if (v?.webkitDisplayingFullscreen) return v.webkitExitFullscreen?.();\n if (r.requestFullscreen) return void r.requestFullscreen().catch(() => {});\n if (r.webkitRequestFullscreen) return r.webkitRequestFullscreen();\n v?.webkitEnterFullscreen?.();\n }, [r, v]);\n return { active, supported, toggle };\n}\n\nexport function usePictureInPicture(el: HTMLVideoElement | null) {\n const [active, setActive] = useState(false);\n useEffect(() => {\n if (!el) return;\n const on = () => setActive(true);\n const off = () => setActive(false);\n el.addEventListener(\"enterpictureinpicture\", on);\n el.addEventListener(\"leavepictureinpicture\", off);\n return () => {\n el.removeEventListener(\"enterpictureinpicture\", on);\n el.removeEventListener(\"leavepictureinpicture\", off);\n };\n }, [el]);\n const supported = typeof document !== \"undefined\" && !!document.pictureInPictureEnabled && !!el && !el.disablePictureInPicture;\n const toggle = useCallback(() => {\n if (!el) return;\n if (document.pictureInPictureElement === el) void document.exitPictureInPicture().catch(() => {});\n else void el.requestPictureInPicture?.().catch(() => {});\n }, [el]);\n return { active, supported, toggle };\n}\n\nconst isSubtitles = (t: TextTrack) => t.kind === \"subtitles\" || t.kind === \"captions\";\n\n/**\n * The cues showing now on the video's chosen subtitle track. The track is\n * kept \"hidden\" so the browser draws nothing and the player places the\n * cues itself, clear of its controls.\n */\nexport function useActiveCues(el: HTMLVideoElement | null): VTTCue[] {\n const [cues, setCues] = useState<VTTCue[]>([]);\n useEffect(() => {\n if (!el) return;\n const tracks = el.textTracks;\n let track: TextTrack | null = null;\n const show = () => setCues(track?.activeCues ? (Array.from(track.activeCues) as VTTCue[]) : []);\n const pick = () => {\n const next = Array.from(tracks).find((t) => isSubtitles(t) && t.mode !== \"disabled\") ?? null;\n if (next?.mode === \"showing\") next.mode = \"hidden\";\n if (next === track) return show();\n track?.removeEventListener(\"cuechange\", show);\n track = next;\n track?.addEventListener(\"cuechange\", show);\n show();\n };\n pick();\n tracks.addEventListener?.(\"change\", pick);\n tracks.addEventListener?.(\"addtrack\", pick);\n tracks.addEventListener?.(\"removetrack\", pick);\n return () => {\n track?.removeEventListener(\"cuechange\", show);\n tracks.removeEventListener?.(\"change\", pick);\n tracks.removeEventListener?.(\"addtrack\", pick);\n tracks.removeEventListener?.(\"removetrack\", pick);\n };\n }, [el]);\n return cues;\n}\n\n/** A seek sprite: its cues and the size of the sheet they cut. */\nexport interface Sprite {\n cues: SpriteCue[];\n width: number;\n height: number;\n}\n\nconst sprites = new Map<string, Promise<Sprite | null>>();\n\nconst fetchText = (url: string, loader?: HlsLoader): Promise<string> =>\n fetchPlaylist(loader, url).then(\n (r) => (r.status === 200 ? r.text() : \"\"),\n () => \"\",\n );\n\n/** A ladder's sprite.vtt and its sheet, fetched once per URL (through the loader, like the playlists). */\nexport function loadSprite(vtt: string, loader?: HlsLoader): Promise<Sprite | null> {\n let p = sprites.get(vtt);\n if (!p) {\n p = fetchText(vtt, loader).then((text) => {\n const cues = text ? parseSpriteVtt(text, new URL(vtt, globalThis.location?.href).href) : [];\n if (!cues.length) return null;\n return new Promise<Sprite | null>((resolve) => {\n const img = new Image();\n img.onload = () => resolve({ cues, width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = () => resolve(null);\n img.src = cues[0]!.url;\n });\n });\n sprites.set(vtt, p);\n void p.then((s) => s || sprites.delete(vtt));\n }\n return p;\n}\n\nexport function useSprite(vtt: string | null | undefined, loader?: HlsLoader): Sprite | null {\n const [sprite, setSprite] = useState<{ vtt: string; sprite: Sprite | null } | null>(null);\n const loaderRef = useRef(loader);\n loaderRef.current = loader;\n useEffect(() => {\n if (!vtt) return;\n let live = true;\n void loadSprite(vtt, loaderRef.current).then((s) => live && setSprite({ vtt, sprite: s }));\n return () => {\n live = false;\n };\n }, [vtt]);\n return sprite && sprite.vtt === vtt ? sprite.sprite : null;\n}\n\n/** Why a progress report was sent. */\nexport type ProgressReason = \"interval\" | \"pause\" | \"seek\" | \"ended\" | \"hidden\" | \"unload\";\n\n/** The playhead and the time actually watched, for resume points and watch history. */\nexport interface PlaybackProgress {\n /** Seconds. */\n time: number;\n duration: number;\n /** Seconds actually played since this source loaded; seeking adds nothing. */\n played: number;\n ended: boolean;\n reason: ProgressReason;\n}\n\n/**\n * Reports progress while a committed playback runs: every `interval` ms of\n * playback, and on pause, seek, end, page hide and unload.\n */\nexport function useProgressReports(\n el: HTMLVideoElement | null,\n o: { enabled: boolean; source?: string | null; interval: number; onProgress?: (p: PlaybackProgress) => void },\n) {\n const cb = useRef(o.onProgress);\n cb.current = o.onProgress;\n const enabled = useRef(o.enabled);\n enabled.current = o.enabled;\n const { interval, source } = o;\n useEffect(() => {\n if (!el || !source) return;\n let played = 0;\n let sample: PlaySample | null = null;\n let last = 0;\n let reported = false;\n // The last known playhead: by unload the player may have detached the media.\n let pos = 0;\n let dur = 0;\n let ended = false;\n const observe = () => {\n if (el.currentSrc || el.readyState > 0) {\n pos = el.currentTime || 0;\n dur = Number.isFinite(el.duration) ? el.duration : dur;\n ended = el.ended;\n }\n };\n const now = (): PlaySample => ({ at: Date.now(), pos: el.currentTime || 0 });\n const tally = () => {\n const s = now();\n if (sample && !el.paused) played += playedBetween(sample, s);\n sample = s;\n };\n const report = (reason: ProgressReason) => {\n if (!enabled.current || !cb.current) return;\n reported = true;\n last = Date.now();\n cb.current({ time: pos, duration: dur, played, ended, reason });\n };\n const handlers: Record<string, () => void> = {\n playing: () => {\n observe();\n sample = now();\n },\n timeupdate: () => {\n if (el.paused || el.seeking) return;\n tally();\n observe();\n if (Date.now() - last >= interval) report(\"interval\");\n },\n durationchange: observe,\n pause: () => {\n tally();\n observe();\n if (!el.ended) report(\"pause\");\n },\n seeking: () => {\n sample = null;\n },\n seeked: () => {\n observe();\n sample = el.paused ? null : now();\n report(\"seek\");\n },\n ended: () => {\n tally();\n observe();\n report(\"ended\");\n },\n };\n let pageHide = false;\n const hidden = () => {\n if (document.visibilityState !== \"hidden\" && !pageHide) return;\n tally();\n observe();\n report(\"hidden\");\n };\n const pagehide = () => {\n pageHide = true;\n hidden();\n pageHide = false;\n };\n for (const [k, h] of Object.entries(handlers)) el.addEventListener(k, h);\n document.addEventListener(\"visibilitychange\", hidden);\n globalThis.addEventListener?.(\"pagehide\", pagehide);\n return () => {\n for (const [k, h] of Object.entries(handlers)) el.removeEventListener(k, h);\n document.removeEventListener(\"visibilitychange\", hidden);\n globalThis.removeEventListener?.(\"pagehide\", pagehide);\n if (reported || played > 0) report(\"unload\");\n };\n }, [el, source, interval]);\n}\n"],"mappings":";;;;AAoBA,IAAM,IAAmB;CAAE,MAAM;CAAG,UAAU;CAAG,UAAU;CAAG,QAAQ;CAAM,OAAO;CAAO,QAAQ;CAAG,OAAO;CAAO,MAAM;AAAE,GAErH,IAAe;CAAC;CAAc;CAAkB;CAAkB;CAAY;CAAQ;CAAS;CAAS;CAAgB;CAAc;CAAW;CAAU;CAAU;AAAS;AAEpL,SAAS,EAAS,GAAkC;CAClD,IAAM,IAAO,EAAG,eAAe,GAC3B,IAAW;CACf,KAAK,IAAI,IAAI,GAAG,IAAI,EAAG,SAAS,QAAQ,KACtC,AAAI,EAAG,SAAS,MAAM,CAAC,KAAK,IAAO,MAAO,KAAQ,EAAG,SAAS,IAAI,CAAC,MAAG,IAAW,EAAG,SAAS,IAAI,CAAC;CAEpG,OAAO;EACL;EACA,UAHe,OAAO,SAAS,EAAG,QAAQ,IAAI,EAAG,WAAW;EAI5D;EACA,QAAQ,EAAG;EACX,OAAO,EAAG;EACV,QAAQ,EAAG;EACX,OAAO,EAAG;EACV,MAAM,EAAG;EACT,QAAQ,EAAG,aAAa,KAAK,EAAG,cAAc,IAAI,EAAG,aAAa,EAAG,cAAc,KAAA;CACrF;AACF;AAEA,SAAgB,EAAc,GAAyC;CACrE,IAAM,CAAC,GAAO,KAAY,EAAS,CAAI;CAUvC,OATA,QAAgB;EACd,IAAI,CAAC,GAAI;EACT,IAAM,UAAa,EAAS,EAAS,CAAE,CAAC;EACxC,EAAK;EACL,KAAK,IAAM,KAAK,GAAc,EAAG,iBAAiB,GAAG,CAAI;EACzD,aAAa;GACX,KAAK,IAAM,KAAK,GAAc,EAAG,oBAAoB,GAAG,CAAI;EAC9D;CACF,GAAG,CAAC,CAAE,CAAC,GACA;AACT;AAMA,IAAM,UAA0B;CAC9B,IAAI,OAAO,WAAa,KAAa,OAAO;CAC5C,IAAM,IAAI;CACV,OAAO,EAAE,qBAAqB,EAAE,2BAA2B;AAC7D;AAMA,SAAgB,EAAc,GAA0B,GAA6B;CACnF,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAK;CAC1C,QAAgB;EACd,IAAI,CAAC,GAAM;EACX,IAAM,IAAI,GACJ,UAAa,EAAU,EAAkB,MAAM,KAAQ,CAAC,CAAC,GAAG,0BAA0B;EAK5F,OAJA,SAAS,iBAAiB,oBAAoB,CAAI,GAClD,SAAS,iBAAiB,0BAA0B,CAAI,GACxD,GAAG,iBAAiB,yBAAyB,CAAI,GACjD,GAAG,iBAAiB,uBAAuB,CAAI,SAClC;GAIX,AAHA,SAAS,oBAAoB,oBAAoB,CAAI,GACrD,SAAS,oBAAoB,0BAA0B,CAAI,GAC3D,GAAG,oBAAoB,yBAAyB,CAAI,GACpD,GAAG,oBAAoB,uBAAuB,CAAI;EACpD;CACF,GAAG,CAAC,GAAM,CAAE,CAAC;CACb,IAAM,IAAI,GACJ,IAAI;CAWV,OAAO;EAAE;EAAQ,WAAA,CAVE,EAAE,GAAG,qBAAqB,GAAG,2BAA2B,GAAG;EAUlD,QATb,QAAkB;GAC/B,IAAI,CAAC,GAAG;GACR,IAAM,IAAI;GACV,IAAI,EAAkB,MAAM,GAAG,OAAO,MAAM,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,uBAAuB;GAC7H,IAAI,GAAG,4BAA4B,OAAO,EAAE,uBAAuB;GACnE,IAAI,EAAE,mBAAmB,OAAO,KAAK,EAAE,kBAAkB,CAAC,CAAC,YAAY,CAAC,CAAC;GACzE,IAAI,EAAE,yBAAyB,OAAO,EAAE,wBAAwB;GAChE,GAAG,wBAAwB;EAC7B,GAAG,CAAC,GAAG,CAAC,CACoB;CAAO;AACrC;AAEA,SAAgB,EAAoB,GAA6B;CAC/D,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAK;CAkB1C,OAjBA,QAAgB;EACd,IAAI,CAAC,GAAI;EACT,IAAM,UAAW,EAAU,EAAI,GACzB,UAAY,EAAU,EAAK;EAGjC,OAFA,EAAG,iBAAiB,yBAAyB,CAAE,GAC/C,EAAG,iBAAiB,yBAAyB,CAAG,SACnC;GAEX,AADA,EAAG,oBAAoB,yBAAyB,CAAE,GAClD,EAAG,oBAAoB,yBAAyB,CAAG;EACrD;CACF,GAAG,CAAC,CAAE,CAAC,GAOA;EAAE;EAAQ,WANC,OAAO,WAAa,OAAe,CAAC,CAAC,SAAS,2BAA2B,CAAC,CAAC,KAAM,CAAC,EAAG;EAM3E,QALb,QAAkB;GAC1B,MACD,SAAS,4BAA4B,IAAI,SAAc,qBAAqB,CAAC,CAAC,YAAY,CAAC,CAAC,IAC3F,EAAQ,0BAA0B,CAAC,CAAC,YAAY,CAAC,CAAC;EACzD,GAAG,CAAC,CAAE,CACsB;CAAO;AACrC;AAEA,IAAM,KAAe,MAAiB,EAAE,SAAS,eAAe,EAAE,SAAS;AAO3E,SAAgB,EAAc,GAAuC;CACnE,IAAM,CAAC,GAAM,KAAW,EAAmB,CAAC,CAAC;CA0B7C,OAzBA,QAAgB;EACd,IAAI,CAAC,GAAI;EACT,IAAM,IAAS,EAAG,YACd,IAA0B,MACxB,UAAa,EAAQ,GAAO,aAAc,MAAM,KAAK,EAAM,UAAU,IAAiB,CAAC,CAAC,GACxF,UAAa;GACjB,IAAM,IAAO,MAAM,KAAK,CAAM,CAAC,CAAC,MAAM,MAAM,EAAY,CAAC,KAAK,EAAE,SAAS,UAAU,KAAK;GAExF,IADI,GAAM,SAAS,cAAW,EAAK,OAAO,WACtC,MAAS,GAAO,OAAO,EAAK;GAIhC,AAHA,GAAO,oBAAoB,aAAa,CAAI,GAC5C,IAAQ,GACR,GAAO,iBAAiB,aAAa,CAAI,GACzC,EAAK;EACP;EAKA,OAJA,EAAK,GACL,EAAO,mBAAmB,UAAU,CAAI,GACxC,EAAO,mBAAmB,YAAY,CAAI,GAC1C,EAAO,mBAAmB,eAAe,CAAI,SAChC;GAIX,AAHA,GAAO,oBAAoB,aAAa,CAAI,GAC5C,EAAO,sBAAsB,UAAU,CAAI,GAC3C,EAAO,sBAAsB,YAAY,CAAI,GAC7C,EAAO,sBAAsB,eAAe,CAAI;EAClD;CACF,GAAG,CAAC,CAAE,CAAC,GACA;AACT;AASA,IAAM,oBAAU,IAAI,IAAoC,GAElD,KAAa,GAAa,MAC9B,EAAc,GAAQ,CAAG,CAAC,CAAC,MACxB,MAAO,EAAE,WAAW,MAAM,EAAE,KAAK,IAAI,UAChC,EACR;AAGF,SAAgB,EAAW,GAAa,GAA4C;CAClF,IAAI,IAAI,EAAQ,IAAI,CAAG;CAevB,OAdK,MACH,IAAI,EAAU,GAAK,CAAM,CAAC,CAAC,MAAM,MAAS;EACxC,IAAM,IAAO,IAAO,EAAe,GAAM,IAAI,IAAI,GAAK,WAAW,UAAU,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC;EAE1F,OADK,EAAK,SACH,IAAI,SAAwB,MAAY;GAC7C,IAAM,IAAM,IAAI,MAAM;GAGtB,AAFA,EAAI,eAAe,EAAQ;IAAE;IAAM,OAAO,EAAI;IAAc,QAAQ,EAAI;GAAc,CAAC,GACvF,EAAI,gBAAgB,EAAQ,IAAI,GAChC,EAAI,MAAM,EAAK,EAAE,CAAE;EACrB,CAAC,IANwB;CAO3B,CAAC,GACD,EAAQ,IAAI,GAAK,CAAC,GAClB,EAAO,MAAM,MAAM,KAAK,EAAQ,OAAO,CAAG,CAAC,IAEtC;AACT;AAEA,SAAgB,EAAU,GAAgC,GAAmC;CAC3F,IAAM,CAAC,GAAQ,KAAa,EAAwD,IAAI,GAClF,IAAY,EAAO,CAAM;CAU/B,OATA,EAAU,UAAU,GACpB,QAAgB;EACd,IAAI,CAAC,GAAK;EACV,IAAI,IAAO;EAEX,OADA,EAAgB,GAAK,EAAU,OAAO,CAAC,CAAC,MAAM,MAAM,KAAQ,EAAU;GAAE;GAAK,QAAQ;EAAE,CAAC,CAAC,SAC5E;GACX,IAAO;EACT;CACF,GAAG,CAAC,CAAG,CAAC,GACD,KAAU,EAAO,QAAQ,IAAM,EAAO,SAAS;AACxD;AAoBA,SAAgB,EACd,GACA,GACA;CACA,IAAM,IAAK,EAAO,EAAE,UAAU;CAC9B,EAAG,UAAU,EAAE;CACf,IAAM,IAAU,EAAO,EAAE,OAAO;CAChC,EAAQ,UAAU,EAAE;CACpB,IAAM,EAAE,aAAU,cAAW;CAC7B,QAAgB;EACd,IAAI,CAAC,KAAM,CAAC,GAAQ;EACpB,IAAI,IAAS,GACT,IAA4B,MAC5B,IAAO,GACP,IAAW,IAEX,IAAM,GACN,IAAM,GACN,IAAQ,IACN,UAAgB;GACpB,CAAI,EAAG,cAAc,EAAG,aAAa,OACnC,IAAM,EAAG,eAAe,GACxB,IAAM,OAAO,SAAS,EAAG,QAAQ,IAAI,EAAG,WAAW,GACnD,IAAQ,EAAG;EAEf,GACM,WAAyB;GAAE,IAAI,KAAK,IAAI;GAAG,KAAK,EAAG,eAAe;EAAE,IACpE,UAAc;GAClB,IAAM,IAAI,EAAI;GAEd,AADI,KAAU,CAAC,EAAG,WAAQ,KAAU,EAAc,GAAQ,CAAC,IAC3D,IAAS;EACX,GACM,KAAU,MAA2B;GACrC,AAAC,EAAQ,WAAY,EAAG,YAC5B,IAAW,IACX,IAAO,KAAK,IAAI,GAChB,EAAG,QAAQ;IAAE,MAAM;IAAK,UAAU;IAAK;IAAQ;IAAO;GAAO,CAAC;EAChE,GACM,IAAuC;GAC3C,eAAe;IAEb,AADA,EAAQ,GACR,IAAS,EAAI;GACf;GACA,kBAAkB;IACZ,EAAG,UAAU,EAAG,YACpB,EAAM,GACN,EAAQ,GACJ,KAAK,IAAI,IAAI,KAAQ,KAAU,EAAO,UAAU;GACtD;GACA,gBAAgB;GAChB,aAAa;IAGX,AAFA,EAAM,GACN,EAAQ,GACH,EAAG,SAAO,EAAO,OAAO;GAC/B;GACA,eAAe;IACb,IAAS;GACX;GACA,cAAc;IAGZ,AAFA,EAAQ,GACR,IAAS,EAAG,SAAS,OAAO,EAAI,GAChC,EAAO,MAAM;GACf;GACA,aAAa;IAGX,AAFA,EAAM,GACN,EAAQ,GACR,EAAO,OAAO;GAChB;EACF,GACI,IAAW,IACT,UAAe;GACf,UAAS,oBAAoB,YAAa,OAC9C,EAAM,GACN,EAAQ,GACR,EAAO,QAAQ;EACjB,GACM,UAAiB;GAGrB,AAFA,IAAW,IACX,EAAO,GACP,IAAW;EACb;EACA,KAAK,IAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,CAAQ,GAAG,EAAG,iBAAiB,GAAG,CAAC;EAGvE,OAFA,SAAS,iBAAiB,oBAAoB,CAAM,GACpD,WAAW,mBAAmB,YAAY,CAAQ,SACrC;GACX,KAAK,IAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,CAAQ,GAAG,EAAG,oBAAoB,GAAG,CAAC;GAG1E,AAFA,SAAS,oBAAoB,oBAAoB,CAAM,GACvD,WAAW,sBAAsB,YAAY,CAAQ,IACjD,KAAY,IAAS,MAAG,EAAO,QAAQ;EAC7C;CACF,GAAG;EAAC;EAAI;EAAQ;CAAQ,CAAC;AAC3B"}
|