import { useCallback, useEffect, useRef, useState } from "react"; export interface FramePlayerState { index: number; setIndex: (i: number) => void; playing: boolean; setPlaying: (p: boolean) => void; } /** Advance a frame index at a fixed rate while playing. */ export function useFramePlayer(nf: number, fps = 20, autoplay = false): FramePlayerState { const [index, setIndexState] = useState(0); const [playing, setPlaying] = useState(autoplay); const timer = useRef(null); useEffect(() => { if (index >= nf) setIndexState(0); }, [nf, index]); useEffect(() => { if (!playing || nf <= 1) return; timer.current = window.setInterval(() => setIndexState((i) => (i + 1) % nf), 1000 / fps); return () => { if (timer.current !== null) window.clearInterval(timer.current); }; }, [playing, nf, fps]); const setIndex = useCallback((i: number) => setIndexState(Math.max(0, Math.min(nf - 1, i))), [nf]); return { index, setIndex, playing, setPlaying }; } export interface FramePlayerProps extends FramePlayerState { times: Float64Array; /** Show time in these units */ timeScale?: number; timeUnit?: string; } export function FramePlayer({ index, setIndex, playing, setPlaying, times, timeScale = 1000, timeUnit = "ms" }: FramePlayerProps) { const nf = times.length; const t = times[Math.min(index, nf - 1)] ?? 0; return (
setIndex(Number(e.target.value))} aria-label="frame" /> {(t * timeScale).toFixed(2)} {timeUnit} frame {Math.min(index, nf - 1) + 1}/{nf}
); }