mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-31 11:59:10 +00:00
50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
import { EntryId, MaybeNumber, Playback } from 'ontime-types';
|
||
import { useEffect, useRef, useState } from 'react';
|
||
|
||
import { getProgress } from '../utils/getProgress';
|
||
import { useIsOnline, usePlayback } from './useSocket';
|
||
|
||
/**
|
||
* Returns the live completion percentage (0–100) of a countdown, interpolated locally.
|
||
*/
|
||
export function useAnimatedProgress(current: MaybeNumber, duration: MaybeNumber, eventId?: EntryId | null): number {
|
||
const playback = usePlayback();
|
||
const isOnline = useIsOnline();
|
||
const isRunning = playback === Playback.Play || playback === Playback.Roll;
|
||
|
||
const baseline = useRef({ current, duration, eventId, playback, at: performance.now() });
|
||
const [, setTick] = useState(0);
|
||
const now = performance.now();
|
||
|
||
const hasAuthoritativeUpdate =
|
||
baseline.current.current !== current || // handle timer updates
|
||
baseline.current.duration !== duration || // handle duration changes
|
||
baseline.current.eventId !== eventId || // handle event changing
|
||
baseline.current.playback !== playback; // handle playback changes
|
||
|
||
if (hasAuthoritativeUpdate) {
|
||
// Reset during render so an event change is reflected in this very paint.
|
||
baseline.current = { current, duration, eventId, playback, at: now };
|
||
}
|
||
|
||
// There is only something to animate while a connected timer is counting down towards 0.
|
||
const shouldAnimate = isOnline && isRunning && current !== null && current > 0 && duration !== null;
|
||
|
||
// while counting down, re-render every animation frame so the derived progress stays smooth
|
||
useEffect(() => {
|
||
if (!shouldAnimate) {
|
||
return;
|
||
}
|
||
let frame = requestAnimationFrame(function tick() {
|
||
setTick((value) => value + 1);
|
||
frame = requestAnimationFrame(tick);
|
||
});
|
||
return () => cancelAnimationFrame(frame);
|
||
}, [shouldAnimate]);
|
||
|
||
// Derive from the anchor plus elapsed time at render; freeze while disconnected or not running.
|
||
const anchored = baseline.current.current;
|
||
const value = isOnline && isRunning && anchored !== null ? anchored - (now - baseline.current.at) : anchored;
|
||
return getProgress(value, duration);
|
||
}
|