@@ -110,10 +110,10 @@ export const LoopVideo = ({
110
110
const [ currentTime , setCurrentTime ] = useState ( 0 ) ;
111
111
const [ playerState , setPlayerState ] =
112
112
useState < ( typeof PLAYER_STATES ) [ number ] > ( 'NOT_STARTED' ) ;
113
-
114
113
const [ isAutoplayAllowed , setIsAutoplayAllowed ] = useState < boolean | null > (
115
114
null ,
116
115
) ;
116
+ const [ isRestoredFromBFCache , setIsRestoredFromBFCache ] = useState ( false ) ;
117
117
118
118
/**
119
119
* Keep a track of whether the video has been in view. We only
@@ -190,31 +190,34 @@ export const LoopVideo = ({
190
190
/>
191
191
) ;
192
192
193
- /**
194
- * Setup.
195
- *
196
- * 1. Register the user's motion preferences.
197
- * 2. Creates event listeners to control playback when there are multiple videos.
198
- */
199
- useEffect ( ( ) => {
193
+ const doesUserPermitAutoplay = ( ) : boolean => {
200
194
/**
201
195
* The user indicates a preference for reduced motion: https://web.dev/articles/prefers-reduced-motion
202
196
*/
203
197
const userPrefersReducedMotion = window . matchMedia (
204
198
'(prefers-reduced-motion: reduce)' ,
205
199
) . matches ;
206
200
207
- const autoplayPreference = storage . local . get (
208
- 'gu.prefs.accessibility.autoplay-video' ,
209
- ) ;
210
201
/**
211
- * `autoplayPreference` is explicitly `false`
212
- * when the user has said they don't want autoplay video.
202
+ * The user can set this on their Accessibility Settings page.
203
+ * Explicitly `false` when the user has said they don't want autoplay video.
213
204
*/
214
- setIsAutoplayAllowed (
215
- ! userPrefersReducedMotion && autoplayPreference !== false ,
205
+ const autoplayPreference = storage . local . get (
206
+ 'gu.prefs.accessibility.autoplay-video' ,
216
207
) ;
217
208
209
+ return ! userPrefersReducedMotion && autoplayPreference !== false ;
210
+ } ;
211
+
212
+ /**
213
+ * Setup.
214
+ *
215
+ * 1. Determine whether we can autoplay video.
216
+ * 2. Creates event listeners to control playback when there are multiple videos.
217
+ */
218
+ useEffect ( ( ) => {
219
+ setIsAutoplayAllowed ( doesUserPermitAutoplay ( ) ) ;
220
+
218
221
/**
219
222
* Mutes the current video when another video is unmuted
220
223
* Triggered by the CustomEvent sent by each player on unmuting
@@ -308,18 +311,33 @@ export const LoopVideo = ({
308
311
} , [ isInView , hasBeenInView , atomId ] ) ;
309
312
310
313
/**
311
- * Autoplay the video when it comes into view .
314
+ * Handle play/pause, when instigated by the browser .
312
315
*/
313
316
useEffect ( ( ) => {
314
- if ( ! vidRef . current || isAutoplayAllowed === false ) {
317
+ if ( ! vidRef . current || ! isPlayable ) {
318
+ return ;
319
+ }
320
+
321
+ /**
322
+ * Stops playback when the video is scrolled out of view.
323
+ */
324
+ const isNoLongerInView =
325
+ playerState === 'PLAYING' && hasBeenInView && isInView === false ;
326
+ if ( isNoLongerInView ) {
327
+ pauseVideo ( 'PAUSED_BY_INTERSECTION_OBSERVER' ) ;
315
328
return ;
316
329
}
317
330
331
+ /**
332
+ * Autoplay/resume playback when the player comes into view or when
333
+ * the page has been restored from the BFCache.
334
+ */
318
335
if (
336
+ isAutoplayAllowed &&
319
337
isInView &&
320
- isPlayable &&
321
338
( playerState === 'NOT_STARTED' ||
322
- playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' )
339
+ playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' ||
340
+ ( isRestoredFromBFCache && playerState === 'PAUSED_BY_BROWSER' ) )
323
341
) {
324
342
/**
325
343
* Check if the video has not been in view before tracking the play.
@@ -329,6 +347,7 @@ export const LoopVideo = ({
329
347
ophanTrackerWeb ( atomId , 'loop' ) ( 'play' ) ;
330
348
}
331
349
350
+ setIsRestoredFromBFCache ( false ) ;
332
351
void playVideo ( ) ;
333
352
}
334
353
} , [
@@ -338,34 +357,10 @@ export const LoopVideo = ({
338
357
playerState ,
339
358
playVideo ,
340
359
hasBeenInView ,
360
+ isRestoredFromBFCache ,
341
361
atomId ,
342
362
] ) ;
343
363
344
- /**
345
- * Stops playback when the video is scrolled out of view.
346
- * Resumes playback when the video is back in the viewport.
347
- */
348
- useEffect ( ( ) => {
349
- if ( ! vidRef . current || ! hasBeenInView ) return ;
350
-
351
- const isNoLongerInView =
352
- playerState === 'PLAYING' && isInView === false ;
353
- if ( isNoLongerInView ) {
354
- pauseVideo ( 'PAUSED_BY_INTERSECTION_OBSERVER' ) ;
355
- }
356
-
357
- /**
358
- * If a user action paused the video, they have indicated
359
- * that they don't want to watch the video. Therefore, don't
360
- * resume the video when it comes back in view.
361
- */
362
- const isBackInView =
363
- playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' && isInView ;
364
- if ( isBackInView ) {
365
- void playVideo ( ) ;
366
- }
367
- } , [ isInView , hasBeenInView , playerState , playVideo ] ) ;
368
-
369
364
/**
370
365
* Show the play icon when the video is not playing, except for when it is scrolled
371
366
* out of view. In this case, the intersection observer will resume playback and
@@ -404,6 +399,20 @@ export const LoopVideo = ({
404
399
setPreloadPartialData ( isAutoplayAllowed === false || ! ! isInView ) ;
405
400
} , [ isAutoplayAllowed , isInView ] ) ;
406
401
402
+ /**
403
+ * Handle the case where the user navigates back to the page.
404
+ */
405
+ useEffect ( ( ) => {
406
+ window . addEventListener ( 'pageshow' , function ( event ) {
407
+ if ( event . persisted ) {
408
+ setIsAutoplayAllowed ( doesUserPermitAutoplay ( ) ) ;
409
+ setIsRestoredFromBFCache ( true ) ;
410
+ } else {
411
+ setIsRestoredFromBFCache ( false ) ;
412
+ }
413
+ } ) ;
414
+ } , [ ] ) ;
415
+
407
416
if ( renderingTarget !== 'Web' ) return null ;
408
417
409
418
if ( adapted ) {
0 commit comments