Skip to content

Commit cc20df8

Browse files
authored
Merge pull request #14324 from guardian/doml/bfcache
Rerun LoopVideo setup if restored from BFCache
2 parents 6b03b4e + aed2510 commit cc20df8

File tree

1 file changed

+53
-44
lines changed

1 file changed

+53
-44
lines changed

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

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,10 @@ export const LoopVideo = ({
143143
const [currentTime, setCurrentTime] = useState(0);
144144
const [playerState, setPlayerState] =
145145
useState<(typeof PLAYER_STATES)[number]>('NOT_STARTED');
146-
147146
const [isAutoplayAllowed, setIsAutoplayAllowed] = useState<boolean | null>(
148147
null,
149148
);
149+
const [isRestoredFromBFCache, setIsRestoredFromBFCache] = useState(false);
150150

151151
/**
152152
* Keep a track of whether the video has been in view. We only
@@ -223,31 +223,34 @@ export const LoopVideo = ({
223223
/>
224224
);
225225

226-
/**
227-
* Setup.
228-
*
229-
* 1. Register the user's motion preferences.
230-
* 2. Creates event listeners to control playback when there are multiple videos.
231-
*/
232-
useEffect(() => {
226+
const doesUserPermitAutoplay = (): boolean => {
233227
/**
234228
* The user indicates a preference for reduced motion: https://web.dev/articles/prefers-reduced-motion
235229
*/
236230
const userPrefersReducedMotion = window.matchMedia(
237231
'(prefers-reduced-motion: reduce)',
238232
).matches;
239233

240-
const autoplayPreference = storage.local.get(
241-
'gu.prefs.accessibility.autoplay-video',
242-
);
243234
/**
244-
* `autoplayPreference` is explicitly `false`
245-
* when the user has said they don't want autoplay video.
235+
* The user can set this on their Accessibility Settings page.
236+
* Explicitly `false` when the user has said they don't want autoplay video.
246237
*/
247-
setIsAutoplayAllowed(
248-
!userPrefersReducedMotion && autoplayPreference !== false,
238+
const autoplayPreference = storage.local.get(
239+
'gu.prefs.accessibility.autoplay-video',
249240
);
250241

242+
return !userPrefersReducedMotion && autoplayPreference !== false;
243+
};
244+
245+
/**
246+
* Setup.
247+
*
248+
* 1. Determine whether we can autoplay video.
249+
* 2. Creates event listeners to control playback when there are multiple videos.
250+
*/
251+
useEffect(() => {
252+
setIsAutoplayAllowed(doesUserPermitAutoplay());
253+
251254
/**
252255
* Mutes the current video when another video is unmuted
253256
* Triggered by the CustomEvent sent by each player on unmuting
@@ -341,18 +344,33 @@ export const LoopVideo = ({
341344
}, [isInView, hasBeenInView, atomId]);
342345

343346
/**
344-
* Autoplay the video when it comes into view.
347+
* Handle play/pause, when instigated by the browser.
345348
*/
346349
useEffect(() => {
347-
if (!vidRef.current || isAutoplayAllowed === false) {
350+
if (!vidRef.current || !isPlayable) {
351+
return;
352+
}
353+
354+
/**
355+
* Stops playback when the video is scrolled out of view.
356+
*/
357+
const isNoLongerInView =
358+
playerState === 'PLAYING' && hasBeenInView && isInView === false;
359+
if (isNoLongerInView) {
360+
pauseVideo('PAUSED_BY_INTERSECTION_OBSERVER');
348361
return;
349362
}
350363

364+
/**
365+
* Autoplay/resume playback when the player comes into view or when
366+
* the page has been restored from the BFCache.
367+
*/
351368
if (
369+
isAutoplayAllowed &&
352370
isInView &&
353-
isPlayable &&
354371
(playerState === 'NOT_STARTED' ||
355-
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER')
372+
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' ||
373+
(isRestoredFromBFCache && playerState === 'PAUSED_BY_BROWSER'))
356374
) {
357375
/**
358376
* Check if the video has not been in view before tracking the play.
@@ -362,6 +380,7 @@ export const LoopVideo = ({
362380
ophanTrackerWeb(atomId, 'loop')('play');
363381
}
364382

383+
setIsRestoredFromBFCache(false);
365384
void playVideo();
366385
}
367386
}, [
@@ -371,34 +390,10 @@ export const LoopVideo = ({
371390
playerState,
372391
playVideo,
373392
hasBeenInView,
393+
isRestoredFromBFCache,
374394
atomId,
375395
]);
376396

377-
/**
378-
* Stops playback when the video is scrolled out of view.
379-
* Resumes playback when the video is back in the viewport.
380-
*/
381-
useEffect(() => {
382-
if (!vidRef.current || !hasBeenInView) return;
383-
384-
const isNoLongerInView =
385-
playerState === 'PLAYING' && isInView === false;
386-
if (isNoLongerInView) {
387-
pauseVideo('PAUSED_BY_INTERSECTION_OBSERVER');
388-
}
389-
390-
/**
391-
* If a user action paused the video, they have indicated
392-
* that they don't want to watch the video. Therefore, don't
393-
* resume the video when it comes back in view.
394-
*/
395-
const isBackInView =
396-
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' && isInView;
397-
if (isBackInView) {
398-
void playVideo();
399-
}
400-
}, [isInView, hasBeenInView, playerState, playVideo]);
401-
402397
/**
403398
* Show the play icon when the video is not playing, except for when it is scrolled
404399
* out of view. In this case, the intersection observer will resume playback and
@@ -437,6 +432,20 @@ export const LoopVideo = ({
437432
setPreloadPartialData(isAutoplayAllowed === false || !!isInView);
438433
}, [isAutoplayAllowed, isInView]);
439434

435+
/**
436+
* Handle the case where the user navigates back to the page.
437+
*/
438+
useEffect(() => {
439+
window.addEventListener('pageshow', function (event) {
440+
if (event.persisted) {
441+
setIsAutoplayAllowed(doesUserPermitAutoplay());
442+
setIsRestoredFromBFCache(true);
443+
} else {
444+
setIsRestoredFromBFCache(false);
445+
}
446+
});
447+
}, []);
448+
440449
if (renderingTarget !== 'Web') return null;
441450

442451
if (adapted) {

0 commit comments

Comments
 (0)