Round Video: Fix smooth progress indicator
This commit is contained in:
parent
1e0f4e654a
commit
9bc780c1f9
@ -1,6 +1,7 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@ -25,6 +26,8 @@ import useMediaTransition from '../../../hooks/useMediaTransition';
|
|||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||||
|
import useSignal from '../../../hooks/useSignal';
|
||||||
|
import { useThrottledSignal } from '../../../hooks/useAsyncResolvers';
|
||||||
|
|
||||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||||
@ -39,6 +42,8 @@ type OwnProps = {
|
|||||||
isDownloading?: boolean;
|
isDownloading?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PROGRESS_THROTTLE = 16; // Min period needed for `playerEl.currentTime` to update
|
||||||
|
|
||||||
let stopPrevious: NoneToVoidFunction;
|
let stopPrevious: NoneToVoidFunction;
|
||||||
|
|
||||||
const RoundVideo: FC<OwnProps> = ({
|
const RoundVideo: FC<OwnProps> = ({
|
||||||
@ -89,8 +94,19 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
transitionClassNames: spinnerClassNames,
|
transitionClassNames: spinnerClassNames,
|
||||||
} = useShowTransition(isTransferring, undefined, wasLoadDisabled);
|
} = useShowTransition(isTransferring, undefined, wasLoadDisabled);
|
||||||
|
|
||||||
const [isActivated, setIsActivated] = useState<boolean>(false);
|
const [isActivated, setIsActivated] = useState(false);
|
||||||
const [progress, setProgress] = useState<number>(0);
|
|
||||||
|
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(() => {
|
useLayoutEffect(() => {
|
||||||
if (!isActivated) {
|
if (!isActivated) {
|
||||||
@ -100,9 +116,7 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
const svgCenter = ROUND_VIDEO_DIMENSIONS_PX / 2;
|
const svgCenter = ROUND_VIDEO_DIMENSIONS_PX / 2;
|
||||||
const svgMargin = 6;
|
const svgMargin = 6;
|
||||||
const circumference = (svgCenter - svgMargin) * 2 * Math.PI;
|
const circumference = (svgCenter - svgMargin) * 2 * Math.PI;
|
||||||
const strokeDashOffset = circumference - progress * circumference;
|
const strokeDashOffset = circumference - getThrottledProgress() * circumference;
|
||||||
|
|
||||||
const playerEl = playerRef.current!;
|
|
||||||
const playingProgressEl = playingProgressRef.current!;
|
const playingProgressEl = playingProgressRef.current!;
|
||||||
const svgEl = playingProgressEl.firstElementChild;
|
const svgEl = playingProgressEl.firstElementChild;
|
||||||
|
|
||||||
@ -118,9 +132,7 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
(svgEl.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', strokeDashOffset.toString());
|
(svgEl.firstElementChild as SVGElement).setAttribute('stroke-dashoffset', strokeDashOffset.toString());
|
||||||
}
|
}
|
||||||
|
}, [isActivated, getThrottledProgress]);
|
||||||
setProgress(playerEl.currentTime / playerEl.duration);
|
|
||||||
}, [isActivated, progress]);
|
|
||||||
|
|
||||||
const shouldPlay = Boolean(mediaData && isIntersecting);
|
const shouldPlay = Boolean(mediaData && isIntersecting);
|
||||||
|
|
||||||
@ -136,7 +148,7 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
requestMutation(() => {
|
requestMutation(() => {
|
||||||
playingProgressRef.current!.innerHTML = '';
|
playingProgressRef.current!.innerHTML = '';
|
||||||
});
|
});
|
||||||
}, []);
|
}, [setProgress]);
|
||||||
|
|
||||||
const capturePlaying = useCallback(() => {
|
const capturePlaying = useCallback(() => {
|
||||||
stopPrevious?.();
|
stopPrevious?.();
|
||||||
@ -179,7 +191,7 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
const playerEl = e.currentTarget;
|
const playerEl = e.currentTarget;
|
||||||
|
|
||||||
setProgress(playerEl.currentTime / playerEl.duration);
|
setProgress(playerEl.currentTime / playerEl.duration);
|
||||||
}, []);
|
}, [setProgress]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1,17 +1,41 @@
|
|||||||
import type { Signal } from '../util/signals';
|
import type { Signal } from '../util/signals';
|
||||||
|
import type { Scheduler } from '../util/schedulers';
|
||||||
|
|
||||||
import useThrottledCallback from './useThrottledCallback';
|
import useThrottledCallback from './useThrottledCallback';
|
||||||
import useDebouncedCallback from './useDebouncedCallback';
|
import useDebouncedCallback from './useDebouncedCallback';
|
||||||
import useDerivedSignal from './useDerivedSignal';
|
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) => {
|
return useThrottledCallback((setValue: (newValue: T) => void) => {
|
||||||
setValue(resolver());
|
setValue(resolver());
|
||||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
// 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) => {
|
return useDebouncedCallback((setValue: (newValue: T) => void) => {
|
||||||
setValue(resolver());
|
setValue(resolver());
|
||||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user