fix latched loading spinner after cancelling a timeline selection (#24162)

isLoading and isBuffering only clear on playback progress, and scrubbing holds the player paused, so a source rebuild during a timeline selection left them set with nothing able to clear them. Cancelling made them visible as a spinner over an already-loaded frame, which stayed until the next manual seek. Leaving a scrub now clears them when the source is loaded and the element holds a frame.
This commit is contained in:
Josh Hawkins
2026-09-12 07:30:04 -06:00
committed by Nicolas Mowen
parent d183f03fee
commit 9eef369dd0
@@ -158,6 +158,7 @@ export default function DynamicVideoPlayer({
const [isLoading, setIsLoading] = useState(false);
const [isBuffering, setIsBuffering] = useState(false);
const loadingTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const prevCameraRef = useRef(camera);
// Don't set source until recordings load - we need accurate startPosition
// to avoid hls.js clamping to video end when startPosition exceeds duration
@@ -166,13 +167,34 @@ export default function DynamicVideoPlayer({
// start at correct time
useEffect(() => {
// isLoading/isBuffering only clear on playback progress, which a
// paused scrub never reaches. A camera switch is excluded: its old
// source still reads as settled while the new one is fetched
const scrubExit = prevCameraRef.current === camera;
prevCameraRef.current = camera;
const settled = () =>
sourceLoadedRef.current &&
(playerRef.current?.readyState ?? 0) >=
HTMLMediaElement.HAVE_CURRENT_DATA;
if (!isScrubbing && scrubExit && settled()) {
setIsLoading(false);
setIsBuffering(false);
}
if (!isScrubbing) {
// never overwrite a pending timer: an orphaned one escapes
// onPlaying's clearTimeout and flashes loading mid-playback
if (loadingTimeoutRef.current) {
clearTimeout(loadingTimeoutRef.current);
}
loadingTimeoutRef.current = setTimeout(() => setIsLoading(true), 1000);
loadingTimeoutRef.current = setTimeout(() => {
// re-checked here: readyState drops while a seek loads
if (scrubExit && settled()) {
return;
}
setIsLoading(true);
}, 1000);
}
return () => {