From 850daef3b09400e8e350dad61357d45cde836038 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 4 Jun 2025 20:42:23 +0200 Subject: [PATCH] Video Player: Fix `isSeeking` change (#5977) --- src/components/mediaViewer/SeekLine.tsx | 3 +- src/components/mediaViewer/VideoPlayer.tsx | 44 ++++++++++++++++--- .../mediaViewer/VideoPlayerControls.tsx | 4 ++ src/components/ui/Draggable.tsx | 21 ++------- src/hooks/useDraggable.ts | 21 ++++----- src/util/events/getPointerPosition.ts | 13 ++++++ 6 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 src/util/events/getPointerPosition.ts diff --git a/src/components/mediaViewer/SeekLine.tsx b/src/components/mediaViewer/SeekLine.tsx index c3152e2a0..891bdab04 100644 --- a/src/components/mediaViewer/SeekLine.tsx +++ b/src/components/mediaViewer/SeekLine.tsx @@ -13,6 +13,7 @@ import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment'; import buildClassName from '../../util/buildClassName'; import { captureEvents } from '../../util/captureEvents'; import { formatMediaDuration } from '../../util/dates/dateFormat'; +import getPointerPosition from '../../util/events/getPointerPosition'; import { clamp, round } from '../../util/math'; import { useThrottledSignal } from '../../hooks/useAsyncResolvers'; @@ -151,7 +152,7 @@ const SeekLine: FC = ({ let offset = 0; const getPreviewProps = (e: MouseEvent | TouchEvent) => { - const pageX = e instanceof MouseEvent ? e.pageX : e.touches[0].pageX; + const pageX = getPointerPosition(e).x; const t = clamp(duration * ((pageX - seekerSize.left) / seekerSize.width), 0, duration); if (isPreviewDisabled) return [t, 0]; if (!seekerSize.width) seekerSize = seeker.getBoundingClientRect(); diff --git a/src/components/mediaViewer/VideoPlayer.tsx b/src/components/mediaViewer/VideoPlayer.tsx index 2decd2f5b..4ead7829c 100644 --- a/src/components/mediaViewer/VideoPlayer.tsx +++ b/src/components/mediaViewer/VideoPlayer.tsx @@ -1,13 +1,14 @@ import type { FC } from '../../lib/teact/teact'; import type React from '../../lib/teact/teact'; import { - memo, useEffect, useRef, useState, + memo, useEffect, useRef, useSignal, useState, } from '../../lib/teact/teact'; import { getActions } from '../../global'; import type { ApiDimensions } from '../../api/types'; import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/browser/windowEnvironment'; +import getPointerPosition from '../../util/events/getPointerPosition'; import { clamp } from '../../util/math'; import safePlay from '../../util/safePlay'; import stopEvent from '../../util/stopEvent'; @@ -112,17 +113,47 @@ const VideoPlayer: FC = ({ ] = usePictureInPicture(videoRef, handleEnterFullscreen, handleLeaveFullscreen); const [, toggleControls, lockControls] = useControlsSignal(); + const [getIsSeeking, setIsSeeking] = useSignal(false); + const lastMousePosition = useRef({ x: 0, y: 0 }); + + useEffect(() => { + const updateMousePosition = (e: MouseEvent | TouchEvent) => { + lastMousePosition.current = getPointerPosition(e); + }; + + window.addEventListener('mousemove', updateMousePosition); + window.addEventListener('touchmove', updateMousePosition); + + return () => { + window.removeEventListener('mousemove', updateMousePosition); + window.removeEventListener('touchmove', updateMousePosition); + }; + }, []); + + const checkMousePositionAndToggleControls = useLastCallback((clientX: number, clientY: number) => { + const bounds = videoRef.current?.getBoundingClientRect(); + if (!bounds) return; + if (clientX <= bounds.left || clientX >= bounds.right + || clientY <= bounds.top || clientY >= bounds.bottom) { + if (!getIsSeeking()) { + toggleControls(false); + } + } + }); const handleVideoMove = useLastCallback(() => { toggleControls(true); }); const handleVideoLeave = useLastCallback((e) => { - const bounds = videoRef.current?.getBoundingClientRect(); - if (!bounds) return; - if (e.clientX <= bounds.left || e.clientX >= bounds.right - || e.clientY <= bounds.top || e.clientY >= bounds.bottom) { - toggleControls(false); + checkMousePositionAndToggleControls(e.clientX, e.clientY); + }); + + const handleSeekingChange = useLastCallback((isSeeking: boolean) => { + setIsSeeking(isSeeking); + if (!isSeeking) { + const { x, y } = lastMousePosition.current; + checkMousePositionAndToggleControls(x, y); } }); @@ -374,6 +405,7 @@ const VideoPlayer: FC = ({ onVolumeClick={handleVolumeMuted} onVolumeChange={handleVolumeChange} onPlaybackRateChange={handlePlaybackRateChange} + onSeekingChange={handleSeekingChange} /> )} diff --git a/src/components/mediaViewer/VideoPlayerControls.tsx b/src/components/mediaViewer/VideoPlayerControls.tsx index a854f727c..d93e974e9 100644 --- a/src/components/mediaViewer/VideoPlayerControls.tsx +++ b/src/components/mediaViewer/VideoPlayerControls.tsx @@ -58,6 +58,7 @@ type OwnProps = { onPlaybackRateChange: (playbackRate: number) => void; onPlayPause: (e: React.MouseEvent) => void; onSeek: (position: number) => void; + onSeekingChange: (isSeeking: boolean) => void; }; const stopEvent = (e: React.MouseEvent) => { @@ -98,6 +99,7 @@ const VideoPlayerControls: FC = ({ onPictureInPictureChange, onPlayPause, onSeek, + onSeekingChange, }) => { const [isPlaybackMenuOpen, openPlaybackMenu, closePlaybackMenu] = useFlag(); const [getCurrentTime] = useCurrentTimeSignal(); @@ -146,10 +148,12 @@ const VideoPlayerControls: FC = ({ const handleSeek = useLastCallback((position: number) => { setIsSeeking(false); onSeek(position); + onSeekingChange(false); }); const handleSeekStart = useLastCallback(() => { setIsSeeking(true); + onSeekingChange(true); }); const volumeIcon: IconName = useMemo(() => { diff --git a/src/components/ui/Draggable.tsx b/src/components/ui/Draggable.tsx index 70e97b8e5..04dd3d494 100644 --- a/src/components/ui/Draggable.tsx +++ b/src/components/ui/Draggable.tsx @@ -1,11 +1,11 @@ import type { FC } from '../../lib/teact/teact'; -import type React from '../../lib/teact/teact'; import { memo, useCallback, useEffect, useMemo, useRef, useState, } from '../../lib/teact/teact'; import buildClassName from '../../util/buildClassName'; import buildStyle from '../../util/buildStyle'; +import getPointerPosition from '../../util/events/getPointerPosition'; import useOldLang from '../../hooks/useOldLang'; @@ -59,7 +59,7 @@ const Draggable: FC = ({ const handleMouseDown = (e: React.MouseEvent | React.TouchEvent) => { e.stopPropagation(); e.preventDefault(); - const { x, y } = getClientCoordinate(e); + const { x, y } = getPointerPosition(e); setState({ ...state, @@ -71,7 +71,7 @@ const Draggable: FC = ({ }; const handleMouseMove = useCallback((e: MouseEvent | TouchEvent) => { - const { x, y } = getClientCoordinate(e); + const { x, y } = getPointerPosition(e); const translation = { x: x - state.origin.x, @@ -172,18 +172,3 @@ const Draggable: FC = ({ }; export default memo(Draggable); - -function getClientCoordinate(e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) { - let x; - let y; - - if ('touches' in e) { - x = e.touches[0].clientX; - y = e.touches[0].clientY; - } else { - x = e.clientX; - y = e.clientY; - } - - return { x, y }; -} diff --git a/src/hooks/useDraggable.ts b/src/hooks/useDraggable.ts index a593d5a5d..b755aa626 100644 --- a/src/hooks/useDraggable.ts +++ b/src/hooks/useDraggable.ts @@ -9,6 +9,7 @@ import type { Point, Size } from '../types'; import { RESIZE_HANDLE_SELECTOR } from '../config'; import buildStyle from '../util/buildStyle'; import { captureEvents } from '../util/captureEvents'; +import getPointerPosition from '../util/events/getPointerPosition'; import useFlag from './useFlag'; import useLastCallback from './useLastCallback'; @@ -124,11 +125,11 @@ export default function useDraggable( if (targetElement.closest('.no-drag') || !element) { return; } - const { pageX, pageY } = ('touches' in event) ? event.touches[0] : event; + const { x, y } = getPointerPosition(event); const { left, top } = element.getBoundingClientRect(); setElementPositionOnStartTransform({ x: left, y: top }); - setTransformStartPoint({ x: pageX, y: pageY }); + setTransformStartPoint({ x, y }); startDragging(); }); @@ -159,14 +160,14 @@ export default function useDraggable( if (resizeHandle === undefined) return; setHitResizeHandle(resizeHandle); - const { pageX, pageY } = ('touches' in event) ? event.touches[0] : event; + const { x, y } = getPointerPosition(event); const { left, right, top, bottom, } = element.getBoundingClientRect(); setElementPositionOnStartTransform({ x: left, y: top }); setElementSizeOnStartTransform({ width: right - left, height: bottom - top }); - setTransformStartPoint({ x: pageX, y: pageY }); + setTransformStartPoint({ x, y }); startResizing(); }); @@ -269,10 +270,10 @@ export default function useDraggable( const handleDrag = useLastCallback((event: MouseEvent | TouchEvent) => { if (!isDragging || !element) return; - const { pageX, pageY } = ('touches' in event) ? event.touches[0] : event; + const { x, y } = getPointerPosition(event); - const offsetX = pageX - transformStartPoint.x; - const offsetY = pageY - transformStartPoint.y; + const offsetX = x - transformStartPoint.x; + const offsetY = y - transformStartPoint.y; const newX = elementPositionOnStartTransform.x + offsetX; const newY = elementPositionOnStartTransform.y + offsetY; @@ -282,11 +283,11 @@ export default function useDraggable( const handleResize = useLastCallback((event: MouseEvent | TouchEvent) => { if (!isResizing || !element || hitResizeHandle === undefined) return; - const { pageX, pageY } = ('touches' in event) ? event.touches[0] : event; + const { x, y } = getPointerPosition(event); const sizeOnStartTransform = getElementSizeOnStartTransform(); - const pageVisibleX = Math.min(Math.max(0, pageX), getVisibleArea().width); - const pageVisibleY = Math.min(Math.max(0, pageY), getVisibleArea().height); + const pageVisibleX = Math.min(Math.max(0, x), getVisibleArea().width); + const pageVisibleY = Math.min(Math.max(0, y), getVisibleArea().height); const offsetX = pageVisibleX - transformStartPoint.x; const offsetY = pageVisibleY - transformStartPoint.y; diff --git a/src/util/events/getPointerPosition.ts b/src/util/events/getPointerPosition.ts new file mode 100644 index 000000000..ee8525fe7 --- /dev/null +++ b/src/util/events/getPointerPosition.ts @@ -0,0 +1,13 @@ +export default function getPointerPosition(e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) { + if ('touches' in e) { + return { + x: e.touches[0].clientX, + y: e.touches[0].clientY, + }; + } + + return { + x: e.clientX, + y: e.clientY, + }; +}