mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 01:13:55 +00:00
2e631194e1
Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
42 lines
1.6 KiB
TypeScript
42 lines
1.6 KiB
TypeScript
import { MaybeNumber, Playback } from 'ontime-types';
|
||
import { useEffect, useRef, useState } from 'react';
|
||
|
||
import { getProgress } from '../utils/getProgress';
|
||
import { usePlayback } from './useSocket';
|
||
|
||
/**
|
||
* Returns the live completion percentage (0–100) of a countdown, interpolated locally.
|
||
*/
|
||
export function useAnimatedProgress(current: MaybeNumber, duration: MaybeNumber): number {
|
||
const playback = usePlayback();
|
||
const isRunning = playback === Playback.Play || playback === Playback.Roll;
|
||
|
||
const baseline = useRef({ current, at: performance.now() });
|
||
const [, setTick] = useState(0);
|
||
|
||
// there is only something to animate while a running timer is counting down towards 0
|
||
const shouldAnimate = isRunning && current !== null && current > 0 && duration !== null;
|
||
|
||
// re-anchor to the authoritative value whenever the server pushes a new timer update
|
||
useEffect(() => {
|
||
baseline.current = { current, at: performance.now() };
|
||
}, [current, duration, playback]);
|
||
|
||
// 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; frozen to the anchor when not running
|
||
const anchored = baseline.current.current;
|
||
const value = isRunning && anchored !== null ? anchored - (performance.now() - baseline.current.at) : anchored;
|
||
return getProgress(value, duration);
|
||
}
|