import React, { createContext, useContext, useRef, useState, useCallback, useEffect, } from "react"; type TimeContextType = { currentTime: number; setCurrentTime: (t: number) => void; subscribe: (cb: (t: number) => void) => () => void; isPlaying: boolean; setIsPlaying: React.Dispatch>; duration: number; setDuration: React.Dispatch>; }; const TimeContext = createContext(undefined); export const useTime = () => { const ctx = useContext(TimeContext); if (!ctx) throw new Error("useTime must be used within a TimeProvider"); return ctx; }; const TIME_RENDER_THROTTLE_MS = 80; export const TimeProvider: React.FC<{ children: React.ReactNode; duration: number; }> = ({ children, duration: initialDuration }) => { const [currentTime, setCurrentTimeState] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [duration, setDuration] = useState(initialDuration); const listeners = useRef void>>(new Set()); // Keep the authoritative time in a ref so subscribers and sync effects // always see the latest value without waiting for a React render cycle. const timeRef = useRef(0); const rafId = useRef(null); const lastRenderTime = useRef(0); const updateTime = useCallback((t: number) => { timeRef.current = t; listeners.current.forEach((fn) => fn(t)); // Throttle React state updates — during playback, timeupdate fires ~4×/sec // per video. Coalescing into rAF + a minimum interval avoids cascading // re-renders across PlaybackBar, charts, etc. if (rafId.current === null) { rafId.current = requestAnimationFrame(() => { rafId.current = null; const now = performance.now(); if (now - lastRenderTime.current >= TIME_RENDER_THROTTLE_MS) { lastRenderTime.current = now; setCurrentTimeState(timeRef.current); } }); } }, []); // Flush any pending rAF on unmount useEffect(() => { return () => { if (rafId.current !== null) cancelAnimationFrame(rafId.current); }; }, []); // When playback stops, flush the exact final time so the UI matches useEffect(() => { if (!isPlaying) { setCurrentTimeState(timeRef.current); } }, [isPlaying]); const subscribe = useCallback((cb: (t: number) => void) => { listeners.current.add(cb); return () => listeners.current.delete(cb); }, []); return ( {children} ); };