Skip to content

Commit a390e88

Browse files
authored
Merge pull request #14220 from guardian/doml/fix-autoplay-failure-ios
Handle state when browser pauses video
2 parents ff5910a + af80114 commit a390e88

File tree

2 files changed

+48
-15
lines changed

2 files changed

+48
-15
lines changed

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ export const LoopVideo = ({
133133
const pauseVideo = (
134134
reason: Extract<
135135
PlayerStates,
136-
'PAUSED_BY_USER' | 'PAUSED_BY_INTERSECTION_OBSERVER'
136+
| 'PAUSED_BY_USER'
137+
| 'PAUSED_BY_INTERSECTION_OBSERVER'
138+
| 'PAUSED_BY_BROWSER'
137139
>,
138140
) => {
139141
if (!vidRef.current) return;
@@ -239,6 +241,9 @@ export const LoopVideo = ({
239241
};
240242
}, [uniqueId]);
241243

244+
/**
245+
* Keeps track of whether the video has been in view or not.
246+
*/
242247
useEffect(() => {
243248
if (isInView && !hasBeenInView) {
244249
/**
@@ -274,7 +279,7 @@ export const LoopVideo = ({
274279
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER')
275280
) {
276281
/**
277-
* check if the video has not been in view before tracking the play.
282+
* Check if the video has not been in view before tracking the play.
278283
* This is so we only track the first play.
279284
*/
280285
if (!hasBeenInView) {
@@ -294,8 +299,8 @@ export const LoopVideo = ({
294299
]);
295300

296301
/**
297-
* Stops playback when the video is scrolled out of view, resumes playbacks
298-
* when the video is back in the viewport.
302+
* Stops playback when the video is scrolled out of view.
303+
* Resumes playback when the video is back in the viewport.
299304
*/
300305
useEffect(() => {
301306
if (!vidRef.current || !hasBeenInView) return;
@@ -309,7 +314,7 @@ export const LoopVideo = ({
309314
/**
310315
* If a user action paused the video, they have indicated
311316
* that they don't want to watch the video. Therefore, don't
312-
* resume the video when it comes back in view
317+
* resume the video when it comes back in view.
313318
*/
314319
const isBackInView =
315320
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' && isInView;
@@ -326,8 +331,8 @@ export const LoopVideo = ({
326331
useEffect(() => {
327332
const shouldShowPlayIcon =
328333
playerState === 'PAUSED_BY_USER' ||
329-
(!isAutoplayAllowed && playerState === 'NOT_STARTED');
330-
334+
playerState === 'PAUSED_BY_BROWSER' ||
335+
(playerState === 'NOT_STARTED' && !isAutoplayAllowed);
331336
setShowPlayIcon(shouldShowPlayIcon);
332337
}, [playerState, isAutoplayAllowed]);
333338

@@ -381,6 +386,22 @@ export const LoopVideo = ({
381386
}
382387
};
383388

389+
/**
390+
* If the video was paused and we know that it wasn't paused by the user
391+
* or the intersection observer, we can deduce that it was paused by the
392+
* browser. Therefore we need to apply the pause state to the video.
393+
*/
394+
const handlePause = () => {
395+
if (
396+
playerState === 'PAUSED_BY_USER' ||
397+
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER'
398+
) {
399+
return;
400+
}
401+
402+
pauseVideo('PAUSED_BY_BROWSER');
403+
};
404+
384405
/**
385406
* If the video could not be loaded due to an error, report to
386407
* Sentry and log in the console.
@@ -471,6 +492,7 @@ export const LoopVideo = ({
471492
handlePlayPauseClick={handlePlayPauseClick}
472493
handleAudioClick={handleAudioClick}
473494
handleKeyDown={handleKeyDown}
495+
handlePause={handlePause}
474496
onError={onError}
475497
AudioIcon={AudioIcon}
476498
preloadPartialData={preloadPartialData}

dotcom-rendering/src/components/LoopVideoPlayer.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,15 @@ export const PLAYER_STATES = [
6060
'NOT_STARTED',
6161
'PLAYING',
6262
'PAUSED_BY_USER',
63+
/**
64+
* The video is paused when the user scrolls away.
65+
*/
6366
'PAUSED_BY_INTERSECTION_OBSERVER',
67+
/**
68+
* The browser may elect to suspend playback under certain circumstances.
69+
* For example, iOS devices in low power mode will suspend playback on autoplaying videos.
70+
*/
71+
'PAUSED_BY_BROWSER',
6472
] as const;
6573

6674
export type PlayerStates = (typeof PLAYER_STATES)[number];
@@ -81,6 +89,7 @@ type Props = {
8189
handlePlayPauseClick: (event: SyntheticEvent) => void;
8290
handleAudioClick: (event: SyntheticEvent) => void;
8391
handleKeyDown: (event: React.KeyboardEvent<HTMLVideoElement>) => void;
92+
handlePause: (event: SyntheticEvent) => void;
8493
onError: (event: SyntheticEvent<HTMLVideoElement>) => void;
8594
AudioIcon: (iconProps: IconProps) => JSX.Element;
8695
posterImage?: string;
@@ -111,6 +120,7 @@ export const LoopVideoPlayer = forwardRef(
111120
handlePlayPauseClick,
112121
handleAudioClick,
113122
handleKeyDown,
123+
handlePause,
114124
onError,
115125
AudioIcon,
116126
preloadPartialData,
@@ -125,13 +135,19 @@ export const LoopVideoPlayer = forwardRef(
125135
{/* eslint-disable-next-line jsx-a11y/media-has-caption -- Captions will be considered later. */}
126136
<video
127137
id={loopVideoId}
138+
css={videoStyles(width, height)}
128139
ref={ref}
140+
role="button"
141+
tabIndex={0}
142+
height={height}
143+
width={width}
144+
data-link-name={`gu-video-loop-${
145+
showPlayIcon ? 'play' : 'pause'
146+
}-${atomId}`}
129147
preload={preloadPartialData ? 'metadata' : 'none'}
130148
loop={true}
131149
muted={isMuted}
132150
playsInline={true}
133-
height={height}
134-
width={width}
135151
poster={posterImage}
136152
onCanPlay={() => {
137153
setIsPlayable(true);
@@ -146,15 +162,10 @@ export const LoopVideoPlayer = forwardRef(
146162
setCurrentTime(ref.current.currentTime);
147163
}
148164
}}
165+
onPause={handlePause}
149166
onClick={handlePlayPauseClick}
150167
onKeyDown={handleKeyDown}
151-
role="button"
152-
tabIndex={0}
153168
onError={onError}
154-
css={videoStyles(width, height)}
155-
data-link-name={`gu-video-loop-${
156-
showPlayIcon ? 'play' : 'pause'
157-
}-${atomId}`}
158169
>
159170
{/* Only mp4 is currently supported. Assumes the video file type is mp4. */}
160171
{/* The start time is set to 1ms so that Safari will autoplay the video */}

0 commit comments

Comments
 (0)