Round Video: Fix smooth progress indicator

This commit is contained in:
Alexander Zinchuk 2023-04-26 21:15:46 +04:00
parent 1e0f4e654a
commit 9bc780c1f9
2 changed files with 49 additions and 13 deletions

View File

@ -1,6 +1,7 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
@ -25,6 +26,8 @@ import useMediaTransition from '../../../hooks/useMediaTransition';
import usePrevious from '../../../hooks/usePrevious';
import useFlag from '../../../hooks/useFlag';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import useSignal from '../../../hooks/useSignal';
import { useThrottledSignal } from '../../../hooks/useAsyncResolvers';
import ProgressSpinner from '../../ui/ProgressSpinner';
import OptimizedVideo from '../../ui/OptimizedVideo';
@ -39,6 +42,8 @@ type OwnProps = {
isDownloading?: boolean;
};
const PROGRESS_THROTTLE = 16; // Min period needed for `playerEl.currentTime` to update
let stopPrevious: NoneToVoidFunction;
const RoundVideo: FC<OwnProps> = ({
@ -89,8 +94,19 @@ const RoundVideo: FC<OwnProps> = ({
transitionClassNames: spinnerClassNames,
} = useShowTransition(isTransferring, undefined, wasLoadDisabled);
const [isActivated, setIsActivated] = useState<boolean>(false);
const [progress, setProgress] = useState<number>(0);
const [isActivated, setIsActivated] = useState(false);
const [getProgress, setProgress] = useSignal(0);
const getThrottledProgress = useThrottledSignal(getProgress, PROGRESS_THROTTLE);
useEffect(() => {
if (!isActivated) {
return;
}
const playerEl = playerRef.current!;
setProgress(playerEl.currentTime / playerEl.duration);
}, [setProgress, isActivated, getThrottledProgress]);
useLayoutEffect(() => {
if (!isActivated) {
@ -100,9 +116,7 @@ const RoundVideo: FC<OwnProps> = ({
const svgCenter = ROUND_VIDEO_DIMENSIONS_PX / 2;
const svgMargin = 6;
const circumference = (svgCenter - svgMargin) * 2 * Math.PI;
const strokeDashOffset = circumference - progress * circumference;
const playerEl = playerRef.current!;
const strokeDashOffset = circumference - getThrottledProgress() * circumference;
const playingProgressEl = playingProgressRef.current!;
const svgEl = playingProgressEl.firstElementChild;
@ -118,9 +132,7 @@ const RoundVideo: FC<OwnProps> = ({
} else {
(svgEl.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', strokeDashOffset.toString());
}
setProgress(playerEl.currentTime / playerEl.duration);
}, [isActivated, progress]);
}, [isActivated, getThrottledProgress]);
const shouldPlay = Boolean(mediaData && isIntersecting);
@ -136,7 +148,7 @@ const RoundVideo: FC<OwnProps> = ({
requestMutation(() => {
playingProgressRef.current!.innerHTML = '';
});
}, []);
}, [setProgress]);
const capturePlaying = useCallback(() => {
stopPrevious?.();
@ -179,7 +191,7 @@ const RoundVideo: FC<OwnProps> = ({
const playerEl = e.currentTarget;
setProgress(playerEl.currentTime / playerEl.duration);
}, []);
}, [setProgress]);
return (
<div

View File

@ -1,17 +1,41 @@
import type { Signal } from '../util/signals';
import type { Scheduler } from '../util/schedulers';
import useThrottledCallback from './useThrottledCallback';
import useDebouncedCallback from './useDebouncedCallback';
import useDerivedSignal from './useDerivedSignal';
export function useThrottledResolver<T>(resolver: () => T, deps: any[], ms: number, noFirst = false) {
export function useThrottledResolver<T>(
resolver: () => T,
deps: any[],
msOrSchedulerFn: number | Scheduler,
noFirst = false,
) {
return useThrottledCallback((setValue: (newValue: T) => void) => {
setValue(resolver());
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
}, deps, ms, noFirst);
}, deps, msOrSchedulerFn, noFirst);
}
export function useDebouncedResolver<T>(resolver: () => T, deps: any[], ms: number, noFirst = false, noLast = false) {
export function useThrottledSignal<T extends any>(
getValue: Signal<T>,
ms: number,
noFirst = false,
): Signal<T> {
const throttledResolver = useThrottledResolver(() => getValue(), [getValue], ms, noFirst);
return useDerivedSignal(
throttledResolver, [throttledResolver, getValue], true,
);
}
export function useDebouncedResolver<T>(
resolver: () => T,
deps: any[],
ms: number,
noFirst = false,
noLast = false,
) {
return useDebouncedCallback((setValue: (newValue: T) => void) => {
setValue(resolver());
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps