Skip to content

Commit 5a45ab6

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

File tree

2 files changed

+77
-40
lines changed

2 files changed

+77
-40
lines changed

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

Lines changed: 74 additions & 39 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,64 @@ 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+
if (error instanceof Error) {
96+
window.guardian.modules.sentry.reportError(
97+
new Error(message),
98+
'loop-video',
99+
);
100+
}
101+
102+
log('dotcom', message);
103+
104+
setPosterImage(image);
105+
setShowPlayIcon(true);
106+
})
107+
.then(() => {
108+
// Autoplay succeeded
109+
setPlayerState('PLAYING');
110+
setHasBeenInView(true);
111+
});
112+
}
113+
}, [src, image]);
114+
115+
const pauseVideo = (
116+
reason: Extract<
117+
PlayerStates,
118+
'PAUSED_BY_USER' | 'PAUSED_BY_INTERSECTION_OBSERVER'
119+
>,
120+
) => {
121+
if (!vidRef.current) return;
122+
123+
if (reason === 'PAUSED_BY_INTERSECTION_OBSERVER') {
124+
setIsMuted(true);
125+
}
126+
127+
setPlayerState(reason);
128+
void vidRef.current.pause();
129+
};
130+
131+
const playPauseVideo = () => {
132+
if (playerState === 'PLAYING') {
133+
if (isInView) {
134+
pauseVideo('PAUSED_BY_USER');
135+
}
136+
} else {
137+
void playVideo();
138+
}
139+
};
140+
83141
/**
84142
* Setup.
85143
*
@@ -161,10 +219,9 @@ export const LoopVideo = ({
161219
(playerState === 'NOT_STARTED' ||
162220
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER')
163221
) {
164-
setPlayerState('PLAYING');
165-
void vidRef.current.play();
222+
void playVideo();
166223
}
167-
}, [isInView, isPlayable, playerState, isAutoplayAllowed]);
224+
}, [isAutoplayAllowed, isInView, isPlayable, playerState, playVideo]);
168225

169226
/**
170227
* Stops playback when the video is scrolled out of view, resumes playbacks
@@ -176,9 +233,7 @@ export const LoopVideo = ({
176233
const isNoLongerInView =
177234
playerState === 'PLAYING' && isInView === false;
178235
if (isNoLongerInView) {
179-
setPlayerState('PAUSED_BY_INTERSECTION_OBSERVER');
180-
void vidRef.current.pause();
181-
setIsMuted(true);
236+
pauseVideo('PAUSED_BY_INTERSECTION_OBSERVER');
182237
}
183238

184239
/**
@@ -189,11 +244,9 @@ export const LoopVideo = ({
189244
const isBackInView =
190245
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' && isInView;
191246
if (isBackInView) {
192-
setPlayerState('PLAYING');
193-
194-
void vidRef.current.play();
247+
void playVideo();
195248
}
196-
}, [isInView, hasBeenInView, playerState]);
249+
}, [isInView, hasBeenInView, playerState, playVideo]);
197250

198251
/**
199252
* Show the play icon when the video is not playing, except for when it is scrolled
@@ -237,30 +290,6 @@ export const LoopVideo = ({
237290

238291
if (adapted) return fallbackImageComponent;
239292

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-
264293
const handlePlayPauseClick = (event: React.SyntheticEvent) => {
265294
event.preventDefault();
266295
playPauseVideo();
@@ -280,12 +309,18 @@ export const LoopVideo = ({
280309
}
281310
};
282311

312+
/**
313+
* If the video could not be loaded due to an error, report to
314+
* Sentry and log in the console.
315+
*/
283316
const onError = () => {
317+
const message = `Loop video could not be played. source: ${src}`;
318+
284319
window.guardian.modules.sentry.reportError(
285-
new Error(`Loop video could not be played. source: ${src}`),
320+
new Error(message),
286321
'loop-video',
287322
);
288-
log('dotcom', `Loop video could not be played. source: ${src}`);
323+
log('dotcom', message);
289324
};
290325

291326
const seekForward = () => {
@@ -323,7 +358,7 @@ export const LoopVideo = ({
323358
playPauseVideo();
324359
break;
325360
case 'Escape':
326-
pauseVideo();
361+
pauseVideo('PAUSED_BY_USER');
327362
break;
328363
case 'ArrowRight':
329364
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)