Skip to content

Commit 9370b5d

Browse files
fix(Media): support boolean and parameterized loop for both video types (#1298)
* fix(Media): support boolean loop for default video * fix: separate loop logic for different elements * fix: lint
1 parent 217f66b commit 9370b5d

File tree

3 files changed

+59
-55
lines changed

3 files changed

+59
-55
lines changed

src/components/DefaultVideo/DefaultVideo.tsx

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ interface DefaultVideoProps {
2121
export const DefaultVideo = React.forwardRef<DefaultVideoRefType, DefaultVideoProps>(
2222
(props, ref) => {
2323
const {video, qa, customBarControlsClassName} = props;
24-
const {controls, customControlsOptions, muted: initiallyMuted = true, onVideoEnd} = video;
24+
const {
25+
controls,
26+
customControlsOptions,
27+
muted: initiallyMuted = true,
28+
onVideoEnd,
29+
loop,
30+
} = video;
2531
const {
2632
muteButtonShown,
2733
positioning,
@@ -45,22 +51,6 @@ export const DefaultVideo = React.forwardRef<DefaultVideoRefType, DefaultVideoPr
4551
return videoRef.current;
4652
}, [videoRef]);
4753

48-
React.useEffect(() => {
49-
const videoElement = videoRef.current;
50-
if (!videoElement || !onVideoEnd) {
51-
return undefined;
52-
}
53-
54-
const handleVideoEnd = () => {
55-
onVideoEnd?.();
56-
};
57-
58-
videoElement.addEventListener('ended', handleVideoEnd);
59-
return () => {
60-
videoElement.removeEventListener('ended', handleVideoEnd);
61-
};
62-
}, [videoRef, onVideoEnd]);
63-
6454
// to guarantee setting a muted attribute in HTML. https://github.com/facebook/react/issues/10389
6555
React.useEffect(() => {
6656
const videoElement = videoRef.current;
@@ -81,6 +71,7 @@ export const DefaultVideo = React.forwardRef<DefaultVideoRefType, DefaultVideoPr
8171
return !value;
8272
});
8373
}, [videoRef]);
74+
8475
const onMuteToggle = React.useCallback(() => {
8576
setIsMuted((value) => !value);
8677
}, []);
@@ -91,6 +82,25 @@ export const DefaultVideo = React.forwardRef<DefaultVideoRefType, DefaultVideoPr
9182
}
9283
}, [onPlayToggle, customControlsType]);
9384

85+
const onEnded = React.useCallback(() => {
86+
const videoElement = videoRef.current;
87+
if (!videoElement) {
88+
return;
89+
}
90+
91+
if (loop) {
92+
const {start = 0, end = videoElement.duration} =
93+
typeof loop === 'boolean' ? {} : loop;
94+
95+
if (videoElement.currentTime >= end) {
96+
videoElement.currentTime = start;
97+
videoElement.play();
98+
}
99+
}
100+
101+
onVideoEnd?.();
102+
}, [loop, onVideoEnd]);
103+
94104
return (
95105
<React.Fragment>
96106
<video
@@ -105,6 +115,7 @@ export const DefaultVideo = React.forwardRef<DefaultVideoRefType, DefaultVideoPr
105115
muted={isMuted}
106116
aria-label={video.ariaLabel}
107117
onClick={onClick}
118+
onEnded={onEnded}
108119
>
109120
{getVideoTypesWithPriority(video.src).map(({src, type}, index) => (
110121
<source key={index} src={src} type={type} data-qa={qa} />

src/components/Media/Video/Video.tsx

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,6 @@ const Video = (props: VideoAllProps) => {
5050

5151
React.useEffect(() => {
5252
if (ref && ref.current) {
53-
const {loop} = video;
54-
55-
if (loop && typeof loop !== 'boolean') {
56-
const {start = 0, end} = loop;
57-
58-
ref.current.addEventListener(
59-
'timeupdate',
60-
() => {
61-
const videoRef = ref.current;
62-
const endTime = end || (videoRef && videoRef.duration);
63-
64-
if (videoRef && videoRef.currentTime === endTime) {
65-
videoRef.currentTime = start;
66-
videoRef.play().catch(() => setHasVideoFallback(true));
67-
}
68-
},
69-
{passive: true},
70-
);
71-
}
72-
7353
if (playVideo) {
7454
ref.current.play().catch(() => setHasVideoFallback(true));
7555
}
@@ -96,7 +76,7 @@ const Video = (props: VideoAllProps) => {
9676
className={b('react-player', videoClassName)}
9777
src={src}
9878
previewImgUrl={previewImg}
99-
loop={Boolean(loop)}
79+
loop={loop}
10080
controls={controls}
10181
muted={muted}
10282
autoplay={autoplay && playVideo}

src/components/ReactPlayer/ReactPlayer.tsx

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {PlayFill} from '@gravity-ui/icons';
44
import {Icon} from '@gravity-ui/uikit';
55
import debounce from 'lodash/debounce';
66
import _ReactPlayer from 'react-player';
7+
import type {ReactPlayerProps} from 'react-player';
78

89
import {MobileContext} from '../../context/mobileContext';
910
import {VideoContext} from '../../context/videoContext';
@@ -41,11 +42,10 @@ const ReactPlayer =
4142
: _ReactPlayer;
4243

4344
export interface ReactPlayerBlockProps
44-
extends Omit<MediaVideoProps, 'loop' | 'src' | 'ref'>,
45+
extends Omit<MediaVideoProps, 'src' | 'ref'>,
4546
ClassNameProps {
4647
src: string | string[];
4748
previewImgUrl?: string;
48-
loop?: boolean;
4949
customBarControlsClassName?: string;
5050
showPreview?: boolean;
5151
onClickPreview?: () => void;
@@ -57,6 +57,7 @@ export interface ReactPlayerBlockProps
5757

5858
interface PlayerPropgress {
5959
played: number;
60+
playedSeconds: number;
6061
}
6162

6263
// eslint-disable-next-line react/display-name
@@ -108,6 +109,7 @@ export const ReactPlayerBlock = React.forwardRef<ReactPlayerBlockHandler, ReactP
108109
const [isPlaying, setIsPlaying] = React.useState(autoPlay);
109110
const [playedPercent, setPlayedPercent] = React.useState<number>(0);
110111
const [currentHeight, setCurrentHeight] = React.useState(height);
112+
const [duration, setDuration] = React.useState<null | number>(null);
111113
const [width, setWidth] = React.useState<number>(0);
112114
const [actualRatio, setActualRatio] = React.useState<number>();
113115
const [muted, setMuted] = React.useState<boolean>(mute);
@@ -325,26 +327,36 @@ export const ReactPlayerBlock = React.forwardRef<ReactPlayerBlockHandler, ReactP
325327
}
326328
}, []);
327329

328-
const onProgress = React.useCallback((progress: PlayerPropgress) => {
329-
setPlayedPercent(progress.played);
330+
const onProgress: ReactPlayerProps['onProgress'] = React.useCallback(
331+
({played, playedSeconds}: PlayerPropgress) => {
332+
setPlayedPercent(played);
330333

331-
if (progress.played === 1) {
332-
setMuted(true);
333-
}
334+
if (loop) {
335+
const {start = 0, end = duration} = typeof loop === 'boolean' ? {} : loop;
336+
337+
// Youtube videos not muted after finishing playing and start again.
338+
// 'onEnded' does not fire when 'loop' is set to true.
339+
// It is custom loop with muted sound after finishing playing and start again.
340+
if (end !== null && playedSeconds >= end) {
341+
setIsPlaying(true);
342+
playerRef?.seekTo(start);
343+
}
344+
}
345+
346+
if (played === 1) {
347+
setMuted(true);
348+
}
349+
},
350+
[duration, loop, playerRef],
351+
);
352+
353+
const onDuration = React.useCallback((currentDuration: number) => {
354+
setDuration(currentDuration);
334355
}, []);
335356

336357
const onEnded = React.useCallback(() => {
337-
// Youtube videos not muted after finishing playing and start again.
338-
// 'onEnded' does not fire when 'loop' is set to true.
339-
// It is custom loop with muted sound after finishing playing and start again.
340-
if (loop) {
341-
setPlayedPercent(0);
342-
setIsPlaying(true);
343-
playerRef?.seekTo(0);
344-
}
345-
346358
setEnded(true);
347-
}, [loop, playerRef]);
359+
}, []);
348360

349361
const onPlayClick = React.useCallback(() => {
350362
if (isPlaying) {
@@ -422,6 +434,7 @@ export const ReactPlayerBlock = React.forwardRef<ReactPlayerBlockHandler, ReactP
422434
} // to prevent pause icon flickering when autoplayed video ends
423435
onProgress={onProgress}
424436
onEnded={onEnded}
437+
onDuration={onDuration}
425438
aria-label={ariaLabel}
426439
previewTabIndex={-1}
427440
config={{

0 commit comments

Comments
 (0)