add699a320
- Avoid per-render target.set dispatch that reset playback - Stop timer-based and per-frame React ticking while playing - Add regression tests for render-loop sources Made-with: Cursor
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from 'react';
|
||
|
||
import { ipcChannels } from '../../../shared/ipc/contracts';
|
||
import type { VideoPlaybackEvent, VideoPlaybackState } from '../../../shared/types';
|
||
import { getDndApi } from '../dndApi';
|
||
|
||
export function useVideoPlaybackState(): readonly [
|
||
VideoPlaybackState | null,
|
||
{ dispatch: (event: VideoPlaybackEvent) => Promise<void> },
|
||
] {
|
||
const api = getDndApi();
|
||
const [playback, setPlayback] = useState<VideoPlaybackState | null>(null);
|
||
/** serverNowMs − Date.now() at last IPC sync; lets us compute a live clock without React timers. */
|
||
const timeOffsetMsRef = useRef(0);
|
||
|
||
useEffect(() => {
|
||
void api.invoke(ipcChannels.video.getState, {}).then((r) => {
|
||
timeOffsetMsRef.current = r.state.serverNowMs - Date.now();
|
||
setPlayback(r.state);
|
||
});
|
||
return api.on(ipcChannels.video.stateChanged, ({ state: next }) => {
|
||
timeOffsetMsRef.current = next.serverNowMs - Date.now();
|
||
setPlayback((prev) => {
|
||
if (prev?.revision === next.revision) return prev;
|
||
return next;
|
||
});
|
||
});
|
||
}, [api]);
|
||
|
||
const state = useMemo((): VideoPlaybackState | null => {
|
||
if (!playback) return null;
|
||
return {
|
||
...playback,
|
||
get serverNowMs(): number {
|
||
return Date.now() + timeOffsetMsRef.current;
|
||
},
|
||
};
|
||
}, [playback]);
|
||
|
||
return [
|
||
state,
|
||
{
|
||
dispatch: async (event) => {
|
||
await api.invoke(ipcChannels.video.dispatch, { event });
|
||
},
|
||
},
|
||
] as const;
|
||
}
|