Skip to content

Commit 030b72f

Browse files
committed
Report autoplay error. Refactor play and pause functions
1 parent fc376a5 commit 030b72f

File tree

2 files changed

+74
-43
lines changed

2 files changed

+74
-43
lines changed

dotcom-rendering/src/components/LoopVideo.importable.tsx

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { css } from '@emotion/react';
22
import { log } from '@guardian/libs';
33
import { SvgAudio, SvgAudioMute } from '@guardian/source/react-components';
4-
import { useEffect, useRef, useState } from 'react';
4+
import { useCallback, useEffect, useRef, useState } from 'react';
55
import { submitClickComponentEvent } from '../client/ophan/ophan';
66
import { getZIndex } from '../lib/getZIndex';
77
import { useIsInView } from '../lib/useIsInView';
@@ -12,7 +12,7 @@ import {
1212
customYoutubePlayEventName,
1313
} from '../lib/video';
1414
import { useConfig } from './ConfigContext';
15-
import type { PLAYER_STATES } from './LoopVideoPlayer';
15+
import type { PLAYER_STATES, PlayerStates } from './LoopVideoPlayer';
1616
import { LoopVideoPlayer } from './LoopVideoPlayer';
1717

1818
const videoContainerStyles = css`
@@ -80,6 +80,61 @@ export const LoopVideo = ({
8080
threshold: 0.5,
8181
});
8282

83+
const playVideo = useCallback(async () => {
84+
if (!vidRef.current) return;
85+
86+
/** https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Autoplay#example_handling_play_failures */
87+
const startPlayPromise = vidRef.current.play();
88+
89+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- In earlier versions of the HTML specification, play() didn't return a value
90+
if (startPlayPromise !== undefined) {
91+
await startPlayPromise
92+
.catch((error) => {
93+
// Autoplay failed
94+
const message = `Autoplay failure for loop video. Source: ${src} could not be played. Error: ${error}`;
95+
window.guardian.modules.sentry.reportError(
96+
new Error(message),
97+
'loop-video',
98+
);
99+
log('dotcom', message);
100+
101+
setPosterImage(image);
102+
setShowPlayIcon(true);
103+
})
104+
.then(() => {
105+
// Autoplay succeeded
106+
setPlayerState('PLAYING');
107+
setHasBeenInView(true);
108+
});
109+
}
110+
}, [src, image]);
111+
112+
const pauseVideo = (
113+
reason: Extract<
114+
PlayerStates,
115+
'PAUSED_BY_USER' | 'PAUSED_BY_INTERSECTION_OBSERVER'
116+
>,
117+
) => {
118+
if (!vidRef.current) return;
119+
120+
if (reason === 'PAUSED_BY_INTERSECTION_OBSERVER') {
121+
setIsMuted(true);
122+
}
123+
124+
setPlayerState(reason);
125+
void vidRef.current.pause();
126+
};
127+
128+
const playPauseVideo = () => {
129+
if (playerState === 'PLAYING') {
130+
if (isInView) {
131+
pauseVideo('PAUSED_BY_USER');
132+
}
133+
} else {
134+
void playVideo();
135+
}
136+
};
137+
83138
/**
84139
* Setup.
85140
*
@@ -161,10 +216,9 @@ export const LoopVideo = ({
161216
(playerState === 'NOT_STARTED' ||
162217
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER')
163218
) {
164-
setPlayerState('PLAYING');
165-
void vidRef.current.play();
219+
void playVideo();
166220
}
167-
}, [isInView, isPlayable, playerState, isAutoplayAllowed]);
221+
}, [isAutoplayAllowed, isInView, isPlayable, playerState, playVideo]);
168222

169223
/**
170224
* Stops playback when the video is scrolled out of view, resumes playbacks
@@ -176,9 +230,7 @@ export const LoopVideo = ({
176230
const isNoLongerInView =
177231
playerState === 'PLAYING' && isInView === false;
178232
if (isNoLongerInView) {
179-
setPlayerState('PAUSED_BY_INTERSECTION_OBSERVER');
180-
void vidRef.current.pause();
181-
setIsMuted(true);
233+
pauseVideo('PAUSED_BY_INTERSECTION_OBSERVER');
182234
}
183235

184236
/**
@@ -189,11 +241,9 @@ export const LoopVideo = ({
189241
const isBackInView =
190242
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' && isInView;
191243
if (isBackInView) {
192-
setPlayerState('PLAYING');
193-
194-
void vidRef.current.play();
244+
void playVideo();
195245
}
196-
}, [isInView, hasBeenInView, playerState]);
246+
}, [isInView, hasBeenInView, playerState, playVideo]);
197247

198248
/**
199249
* Show the play icon when the video is not playing, except for when it is scrolled
@@ -237,30 +287,6 @@ export const LoopVideo = ({
237287

238288
if (adapted) return fallbackImageComponent;
239289

240-
const playVideo = () => {
241-
if (!vidRef.current) return;
242-
243-
setPlayerState('PLAYING');
244-
void vidRef.current.play();
245-
};
246-
247-
const pauseVideo = () => {
248-
if (!vidRef.current) return;
249-
250-
setPlayerState('PAUSED_BY_USER');
251-
void vidRef.current.pause();
252-
};
253-
254-
const playPauseVideo = () => {
255-
if (playerState === 'PLAYING') {
256-
if (isInView) {
257-
pauseVideo();
258-
}
259-
} else {
260-
playVideo();
261-
}
262-
};
263-
264290
const handlePlayPauseClick = (event: React.SyntheticEvent) => {
265291
event.preventDefault();
266292
playPauseVideo();
@@ -280,12 +306,15 @@ export const LoopVideo = ({
280306
}
281307
};
282308

309+
/**
310+
* If the video could not be loaded due to an error, report to
311+
* Sentry and log in the console.
312+
*/
283313
const onError = () => {
284-
window.guardian.modules.sentry.reportError(
285-
new Error(`Loop video could not be played. source: ${src}`),
286-
'loop-video',
287-
);
288-
log('dotcom', `Loop video could not be played. source: ${src}`);
314+
const message = `Loop video could not be played. source: ${src}`;
315+
316+
window.guardian.modules.sentry.reportError(new Error(), 'loop-video');
317+
log('dotcom', message);
289318
};
290319

291320
const seekForward = () => {
@@ -323,7 +352,7 @@ export const LoopVideo = ({
323352
playPauseVideo();
324353
break;
325354
case 'Escape':
326-
pauseVideo();
355+
pauseVideo('PAUSED_BY_USER');
327356
break;
328357
case 'ArrowRight':
329358
seekForward();

dotcom-rendering/src/components/LoopVideoPlayer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export const PLAYER_STATES = [
5858
'PAUSED_BY_INTERSECTION_OBSERVER',
5959
] as const;
6060

61+
export type PlayerStates = (typeof PLAYER_STATES)[number];
62+
6163
type Props = {
6264
src: string;
6365
atomId: string;
@@ -67,7 +69,7 @@ type Props = {
6769
fallbackImageComponent: JSX.Element;
6870
isPlayable: boolean;
6971
setIsPlayable: Dispatch<SetStateAction<boolean>>;
70-
playerState: (typeof PLAYER_STATES)[number];
72+
playerState: PlayerStates;
7173
currentTime: number;
7274
setCurrentTime: Dispatch<SetStateAction<number>>;
7375
isMuted: boolean;

0 commit comments

Comments
 (0)