@@ -14,9 +14,24 @@ const videoContainerStyles = css`
14
14
position : relative;
15
15
` ;
16
16
17
+ type CustomPlayEventDetail = { uniqueId : string } ;
18
+ const customPlayAudioEventName = 'looping-video:play-with-audio' ;
19
+
20
+ /**
21
+ * Dispatches a custom play event so that other players listening
22
+ * for this event will stop playing
23
+ */
24
+ export const dispatchCustomPlayAudioEvent = ( uniqueId : string ) => {
25
+ document . dispatchEvent (
26
+ new CustomEvent ( customPlayAudioEventName , {
27
+ detail : { uniqueId } ,
28
+ } ) ,
29
+ ) ;
30
+ } ;
31
+
17
32
type Props = {
18
33
src : string ;
19
- videoId : string ;
34
+ uniqueId : string ;
20
35
width : number ;
21
36
height : number ;
22
37
thumbnailImage : string ;
@@ -25,7 +40,7 @@ type Props = {
25
40
26
41
export const LoopVideo = ( {
27
42
src,
28
- videoId ,
43
+ uniqueId ,
29
44
width,
30
45
height,
31
46
thumbnailImage,
@@ -57,26 +72,54 @@ export const LoopVideo = ({
57
72
} ) ;
58
73
59
74
/**
75
+ * Setup.
76
+ *
60
77
* Register the users motion preferences.
78
+ * Create event listeners to ensure we don't play audio from multiple loops
61
79
*/
62
80
useEffect ( ( ) => {
63
81
const userPrefersReducedMotion = window . matchMedia (
64
82
'(prefers-reduced-motion: reduce)' ,
65
83
) . matches ;
66
84
setPrefersReducedMotion ( userPrefersReducedMotion ) ;
67
- } , [ ] ) ;
85
+
86
+ /**
87
+ * Pause the current video when another video is played
88
+ * Triggered by the CustomEvent sent by each player on play
89
+ */
90
+ const handleCustomPlayAudioEvent = (
91
+ event : CustomEventInit < CustomPlayEventDetail > ,
92
+ ) => {
93
+ if ( event . detail ) {
94
+ const playedVideoId = event . detail . uniqueId ;
95
+ const thisVideoId = uniqueId ;
96
+
97
+ if ( playedVideoId !== thisVideoId ) {
98
+ setIsMuted ( true ) ;
99
+ }
100
+ }
101
+ } ;
102
+
103
+ document . addEventListener (
104
+ customPlayAudioEventName ,
105
+ handleCustomPlayAudioEvent ,
106
+ ) ;
107
+ } , [ uniqueId ] ) ;
68
108
69
109
/**
70
- * Autoplays the video when it comes into view.
110
+ * Autoplay the video when it comes into view.
71
111
*/
72
112
useEffect ( ( ) => {
73
- if ( ! vidRef . current || playerState === 'PAUSED_BY_USER' ) return ;
74
-
75
- if ( isInView && isPlayable && playerState !== 'PLAYING' ) {
76
- if ( prefersReducedMotion !== false ) {
77
- return ;
78
- }
113
+ if ( ! vidRef . current || prefersReducedMotion !== false ) {
114
+ return ;
115
+ }
79
116
117
+ if (
118
+ isInView &&
119
+ isPlayable &&
120
+ ( playerState === 'NOT_STARTED' ||
121
+ playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' )
122
+ ) {
80
123
setPlayerState ( 'PLAYING' ) ;
81
124
setHasBeenInView ( true ) ;
82
125
@@ -98,9 +141,11 @@ export const LoopVideo = ({
98
141
setIsMuted ( true ) ;
99
142
}
100
143
101
- // If a user action paused the video, they have indicated
102
- // that they don't want to watch the video. Therefore, don't
103
- // resume the video when it comes back in view
144
+ /**
145
+ * If a user action paused the video, they have indicated
146
+ * that they don't want to watch the video. Therefore, don't
147
+ * resume the video when it comes back in view
148
+ */
104
149
const isBackInView =
105
150
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' && isInView ;
106
151
if ( isBackInView ) {
@@ -139,11 +184,23 @@ export const LoopVideo = ({
139
184
}
140
185
} ;
141
186
142
- const handleClick = ( event : React . SyntheticEvent ) => {
187
+ const handlePlayPauseClick = ( event : React . SyntheticEvent ) => {
143
188
event . preventDefault ( ) ;
144
189
playPauseVideo ( ) ;
145
190
} ;
146
191
192
+ const handleAudioClick = ( event : React . SyntheticEvent ) => {
193
+ event . stopPropagation ( ) ; // Don't pause the video
194
+
195
+ if ( isMuted ) {
196
+ // Emit video play audio event so other components are aware when a video is played with sound
197
+ dispatchCustomPlayAudioEvent ( uniqueId ) ;
198
+ setIsMuted ( false ) ;
199
+ } else {
200
+ setIsMuted ( true ) ;
201
+ }
202
+ } ;
203
+
147
204
const onError = ( ) => {
148
205
window . guardian . modules . sentry . reportError (
149
206
new Error ( `Loop video could not be played. source: ${ src } ` ) ,
@@ -225,7 +282,7 @@ export const LoopVideo = ({
225
282
>
226
283
< LoopVideoPlayer
227
284
src = { src }
228
- videoId = { videoId }
285
+ uniqueId = { uniqueId }
229
286
width = { width }
230
287
height = { height }
231
288
posterImage = { posterImage }
@@ -236,10 +293,9 @@ export const LoopVideo = ({
236
293
isPlayable = { isPlayable }
237
294
setIsPlayable = { setIsPlayable }
238
295
playerState = { playerState }
239
- setPlayerState = { setPlayerState }
240
296
isMuted = { isMuted }
241
- setIsMuted = { setIsMuted }
242
- handleClick = { handleClick }
297
+ handlePlayPauseClick = { handlePlayPauseClick }
298
+ handleAudioClick = { handleAudioClick }
243
299
handleKeyDown = { handleKeyDown }
244
300
onError = { onError }
245
301
AudioIcon = { AudioIcon }
0 commit comments