Skip to content

Commit 4e02280

Browse files
authored
Merge pull request Expensify#84865 from Uzaifm127/fix/82026
fix: Issues related to video progress and controls especially on Android
2 parents 309e6d2 + 7a962c3 commit 4e02280

3 files changed

Lines changed: 63 additions & 10 deletions

File tree

src/components/VideoPlayer/BaseVideoPlayer.tsx

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {RefObject} from 'react';
66
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
77
import type {GestureResponderEvent} from 'react-native';
88
import {View} from 'react-native';
9-
import {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
9+
import {cancelAnimation, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
1010
import {scheduleOnRN} from 'react-native-worklets';
1111
import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator';
1212
import Hoverable from '@components/Hoverable';
@@ -74,6 +74,8 @@ function BaseVideoPlayer({
7474
const controlsAnimatedStyle = useAnimatedStyle(() => ({
7575
opacity: controlsOpacity.get(),
7676
}));
77+
const [isSeeking, setIsSeeking] = useState(false);
78+
const allowSharedAutoPlayRef = useRef(true);
7779

7880
/* eslint-disable no-param-reassign */
7981
// According to the library docs, the player is configured by mutating the provided instance
@@ -183,20 +185,29 @@ function BaseVideoPlayer({
183185
}
184186

185187
if (isEnded && currentTime >= duration) {
188+
allowSharedAutoPlayRef.current = true;
186189
replayVideo();
187190
return;
188191
}
189192

193+
allowSharedAutoPlayRef.current = true;
190194
playVideo();
191195
}, [isCurrentlyURLSet, isLoading, isEnded, currentTime, duration, playVideo, updateCurrentURLAndReportID, url, reportID, pauseVideo, replayVideo]);
192196

193197
const hideControl = useCallback(() => {
194-
if (isEnded) {
198+
if (isEnded || isSeeking) {
195199
return;
196200
}
197201

198-
controlsOpacity.set(withTiming(0, {duration: 500}, () => scheduleOnRN(setControlStatusState, CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE)));
199-
}, [controlsOpacity, isEnded]);
202+
controlsOpacity.set(
203+
withTiming(0, {duration: 500}, (finished) => {
204+
if (!finished) {
205+
return;
206+
}
207+
scheduleOnRN(setControlStatusState, CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE);
208+
}),
209+
);
210+
}, [controlsOpacity, isEnded, isSeeking]);
200211
const debouncedHideControl = useMemo(() => debounce(hideControl, 1500), [hideControl]);
201212

202213
useEffect(() => {
@@ -216,13 +227,13 @@ function BaseVideoPlayer({
216227
if (controlStatusState !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW) {
217228
return;
218229
}
219-
if (!isPlaying || isPopoverVisible) {
230+
if (!isPlaying || isPopoverVisible || isSeeking) {
220231
debouncedHideControl.cancel();
221232
return;
222233
}
223234

224235
debouncedHideControl();
225-
}, [isPlaying, debouncedHideControl, controlStatusState, isPopoverVisible, canUseTouchScreen]);
236+
}, [isPlaying, debouncedHideControl, controlStatusState, isPopoverVisible, canUseTouchScreen, isSeeking]);
226237

227238
useEffect(() => {
228239
if (!onTap || !controlStatusState) {
@@ -232,6 +243,14 @@ function BaseVideoPlayer({
232243
onTap(shouldShowArrows);
233244
}, [controlStatusState, onTap]);
234245

246+
const restartAutoHide = useCallback(() => {
247+
debouncedHideControl.cancel();
248+
if (!canUseTouchScreen || !isPlaying || isPopoverVisible || controlStatusState !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW) {
249+
return;
250+
}
251+
debouncedHideControl();
252+
}, [canUseTouchScreen, controlStatusState, debouncedHideControl, isPlaying, isPopoverVisible]);
253+
235254
const stopWheelPropagation = useCallback((ev: WheelEvent) => ev.stopPropagation(), []);
236255

237256
const toggleControl = useCallback(() => {
@@ -398,7 +417,7 @@ function BaseVideoPlayer({
398417
videoViewRef.current,
399418
videoPlayerElementParentRef.current,
400419
videoPlayerElementRef.current,
401-
(isUploading && !isCurrentlyURLSet) || isFullScreenRef.current || !isReadyForDisplayRef.current || hasError,
420+
(isUploading && !isCurrentlyURLSet) || isFullScreenRef.current || !isReadyForDisplayRef.current || hasError || isSeeking || !allowSharedAutoPlayRef.current,
402421
{shouldUseSharedVideoElement, url, reportID},
403422
);
404423
}, [
@@ -413,6 +432,7 @@ function BaseVideoPlayer({
413432
isFullScreenRef,
414433
hasError,
415434
isCurrentlyURLSet,
435+
isSeeking,
416436
status,
417437
updatePlayerStatus,
418438
]);
@@ -571,7 +591,6 @@ function BaseVideoPlayer({
571591
{shouldShowLoadingIndicator && <LoadingIndicator style={[styles.opacity1, styles.bgTransparent]} />}
572592
{shouldShowOfflineIndicator && <AttachmentOfflineIndicator isPreview={isPreview} />}
573593
{controlStatusState !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE &&
574-
!shouldShowLoadingIndicator &&
575594
!shouldShowOfflineIndicator &&
576595
!shouldShowErrorIndicator &&
577596
(isPopoverVisible || isHovered || canUseTouchScreen || isEnded) && (
@@ -588,6 +607,21 @@ function BaseVideoPlayer({
588607
controlsStatus={controlStatusState}
589608
showPopoverMenu={showPopoverMenu}
590609
reportID={reportID}
610+
onSeekStart={() => {
611+
allowSharedAutoPlayRef.current = false;
612+
debouncedHideControl.cancel();
613+
cancelAnimation(controlsOpacity);
614+
controlsOpacity.set(1);
615+
setIsSeeking(true);
616+
}}
617+
onSeekEnd={(shouldResumeAfterSeek) => {
618+
setIsSeeking(false);
619+
if (shouldResumeAfterSeek) {
620+
allowSharedAutoPlayRef.current = true;
621+
playVideo();
622+
}
623+
restartAutoHide();
624+
}}
591625
/>
592626
)}
593627
</View>

src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@ type ProgressBarProps = {
1616

1717
/** Function to seek to a specific position in the video. */
1818
seekPosition: (newPosition: number) => void;
19+
20+
/** Callback when user starts dragging the slider. */
21+
onSeekStart?: () => void;
22+
23+
/** Callback when user finishes dragging the slider. */
24+
onSeekEnd?: (shouldResumeAfterSeek: boolean) => void;
1925
};
2026

2127
function getProgress(currentPosition: number, maxPosition: number): number {
2228
return Math.min(Math.max((currentPosition / maxPosition) * 100, 0), 100);
2329
}
2430

25-
function ProgressBar({duration, position, seekPosition}: ProgressBarProps) {
31+
function ProgressBar({duration, position, seekPosition, onSeekStart, onSeekEnd}: ProgressBarProps) {
2632
const styles = useThemeStyles();
2733
const {pauseVideo, playVideo, checkIfVideoIsPlaying} = usePlaybackActionsContext();
2834
const [sliderWidth, setSliderWidth] = useState(1);
@@ -52,6 +58,7 @@ function ProgressBar({duration, position, seekPosition}: ProgressBarProps) {
5258
.onBegin((event) => {
5359
setIsSliderPressed(true);
5460
checkIfVideoIsPlaying(onCheckIfVideoIsPlaying);
61+
onSeekStart?.();
5562
pauseVideo();
5663
progressBarInteraction(event);
5764
})
@@ -60,7 +67,9 @@ function ProgressBar({duration, position, seekPosition}: ProgressBarProps) {
6067
})
6168
.onFinalize(() => {
6269
setIsSliderPressed(false);
63-
if (!wasVideoPlayingOnCheck.get()) {
70+
const shouldResumeAfterSeek = wasVideoPlayingOnCheck.get();
71+
onSeekEnd?.(shouldResumeAfterSeek);
72+
if (onSeekEnd || !shouldResumeAfterSeek) {
6473
return;
6574
}
6675
playVideo();

src/components/VideoPlayer/VideoPlayerControls/index.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ type VideoPlayerControlsProps = {
5050
controlsStatus: ValueOf<typeof CONST.VIDEO_PLAYER.CONTROLS_STATUS>;
5151

5252
reportID: string | undefined;
53+
54+
/** Callback when user starts dragging the progress bar. */
55+
onSeekStart?: () => void;
56+
57+
/** Callback when user finishes dragging the progress bar. */
58+
onSeekEnd?: (shouldResumeAfterSeek: boolean) => void;
5359
};
5460

5561
function VideoPlayerControls({
@@ -65,6 +71,8 @@ function VideoPlayerControls({
6571
togglePlayCurrentVideo,
6672
controlsStatus = CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW,
6773
reportID,
74+
onSeekStart,
75+
onSeekEnd,
6876
}: VideoPlayerControlsProps) {
6977
const icons = useMemoizedLazyExpensifyIcons(['ThreeDots', 'Pause', 'Play', 'Fullscreen']);
7078
const styles = useThemeStyles();
@@ -150,6 +158,8 @@ function VideoPlayerControls({
150158
duration={duration}
151159
position={position}
152160
seekPosition={seekPosition}
161+
onSeekStart={onSeekStart}
162+
onSeekEnd={onSeekEnd}
153163
/>
154164
</View>
155165
{controlsStatus === CONST.VIDEO_PLAYER.CONTROLS_STATUS.VOLUME_ONLY && <VolumeButton style={styles.ml3} />}

0 commit comments

Comments
 (0)