Skip to content

Commit 39a289d

Browse files
committed
Mute other loop audio when one loop is playing audio
1 parent 159236c commit 39a289d

File tree

3 files changed

+85
-38
lines changed

3 files changed

+85
-38
lines changed

dotcom-rendering/src/components/Card/Card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,6 @@ export const Card = ({
904904
src={media.mainMedia.videoId}
905905
height={media.mainMedia.height}
906906
width={media.mainMedia.width}
907-
videoId={media.mainMedia.videoId}
908907
thumbnailImage={
909908
media.mainMedia.thumbnailImage ?? ''
910909
}
@@ -917,6 +916,7 @@ export const Card = ({
917916
aspectRatio={aspectRatio}
918917
/>
919918
}
919+
uniqueId={uniqueId}
920920
/>
921921
</Island>
922922
)}

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

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,24 @@ const videoContainerStyles = css`
1414
position: relative;
1515
`;
1616

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+
1732
type Props = {
1833
src: string;
19-
videoId: string;
34+
uniqueId: string;
2035
width: number;
2136
height: number;
2237
thumbnailImage: string;
@@ -25,7 +40,7 @@ type Props = {
2540

2641
export const LoopVideo = ({
2742
src,
28-
videoId,
43+
uniqueId,
2944
width,
3045
height,
3146
thumbnailImage,
@@ -57,26 +72,54 @@ export const LoopVideo = ({
5772
});
5873

5974
/**
75+
* Setup.
76+
*
6077
* Register the users motion preferences.
78+
* Create event listeners to ensure we don't play audio from multiple loops
6179
*/
6280
useEffect(() => {
6381
const userPrefersReducedMotion = window.matchMedia(
6482
'(prefers-reduced-motion: reduce)',
6583
).matches;
6684
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]);
68108

69109
/**
70-
* Autoplays the video when it comes into view.
110+
* Autoplay the video when it comes into view.
71111
*/
72112
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+
}
79116

117+
if (
118+
isInView &&
119+
isPlayable &&
120+
(playerState === 'NOT_STARTED' ||
121+
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER')
122+
) {
80123
setPlayerState('PLAYING');
81124
setHasBeenInView(true);
82125

@@ -98,9 +141,11 @@ export const LoopVideo = ({
98141
setIsMuted(true);
99142
}
100143

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+
*/
104149
const isBackInView =
105150
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' && isInView;
106151
if (isBackInView) {
@@ -139,11 +184,23 @@ export const LoopVideo = ({
139184
}
140185
};
141186

142-
const handleClick = (event: React.SyntheticEvent) => {
187+
const handlePlayPauseClick = (event: React.SyntheticEvent) => {
143188
event.preventDefault();
144189
playPauseVideo();
145190
};
146191

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+
147204
const onError = () => {
148205
window.guardian.modules.sentry.reportError(
149206
new Error(`Loop video could not be played. source: ${src}`),
@@ -225,7 +282,7 @@ export const LoopVideo = ({
225282
>
226283
<LoopVideoPlayer
227284
src={src}
228-
videoId={videoId}
285+
uniqueId={uniqueId}
229286
width={width}
230287
height={height}
231288
posterImage={posterImage}
@@ -236,10 +293,9 @@ export const LoopVideo = ({
236293
isPlayable={isPlayable}
237294
setIsPlayable={setIsPlayable}
238295
playerState={playerState}
239-
setPlayerState={setPlayerState}
240296
isMuted={isMuted}
241-
setIsMuted={setIsMuted}
242-
handleClick={handleClick}
297+
handlePlayPauseClick={handlePlayPauseClick}
298+
handleAudioClick={handleAudioClick}
243299
handleKeyDown={handleKeyDown}
244300
onError={onError}
245301
AudioIcon={AudioIcon}

dotcom-rendering/src/components/LoopVideoPlayer.tsx

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,18 @@ export const PLAYER_STATES = [
6060

6161
type Props = {
6262
src: string;
63-
videoId: string;
63+
uniqueId: string;
6464
width: number;
6565
height: number;
6666
fallbackImageComponent: JSX.Element;
6767
isPlayable: boolean;
6868
setIsPlayable: Dispatch<SetStateAction<boolean>>;
6969
playerState: (typeof PLAYER_STATES)[number];
70-
setPlayerState: Dispatch<SetStateAction<(typeof PLAYER_STATES)[number]>>;
7170
currentTime: number;
7271
setCurrentTime: Dispatch<SetStateAction<number>>;
7372
isMuted: boolean;
74-
setIsMuted: Dispatch<SetStateAction<boolean>>;
75-
handleClick: (event: SyntheticEvent) => void;
73+
handlePlayPauseClick: (event: SyntheticEvent) => void;
74+
handleAudioClick: (event: SyntheticEvent) => void;
7675
handleKeyDown: (event: React.KeyboardEvent<HTMLVideoElement>) => void;
7776
onError: (event: SyntheticEvent<HTMLVideoElement>) => void;
7877
AudioIcon: (iconProps: IconProps) => JSX.Element;
@@ -89,20 +88,19 @@ export const LoopVideoPlayer = forwardRef(
8988
(
9089
{
9190
src,
92-
videoId,
91+
uniqueId,
9392
width,
9493
height,
9594
fallbackImageComponent,
9695
posterImage,
9796
isPlayable,
9897
setIsPlayable,
9998
playerState,
100-
setPlayerState,
10199
currentTime,
102100
setCurrentTime,
103101
isMuted,
104-
setIsMuted,
105-
handleClick,
102+
handlePlayPauseClick,
103+
handleAudioClick,
106104
handleKeyDown,
107105
onError,
108106
AudioIcon,
@@ -111,8 +109,7 @@ export const LoopVideoPlayer = forwardRef(
111109
}: Props,
112110
ref: React.ForwardedRef<HTMLVideoElement>,
113111
) => {
114-
// Assumes that the video is unique on the page.
115-
const loopVideoId = `loop-video-${videoId}`;
112+
const loopVideoId = `loop-video-${uniqueId}`;
116113

117114
return (
118115
<>
@@ -127,9 +124,6 @@ export const LoopVideoPlayer = forwardRef(
127124
height={height}
128125
width={width}
129126
poster={posterImage}
130-
onPlaying={() => {
131-
setPlayerState('PLAYING');
132-
}}
133127
onCanPlay={() => {
134128
setIsPlayable(true);
135129
}}
@@ -143,7 +137,7 @@ export const LoopVideoPlayer = forwardRef(
143137
setCurrentTime(ref.current.currentTime);
144138
}
145139
}}
146-
onClick={handleClick}
140+
onClick={handlePlayPauseClick}
147141
onKeyDown={handleKeyDown}
148142
role="button"
149143
tabIndex={0}
@@ -160,7 +154,7 @@ export const LoopVideoPlayer = forwardRef(
160154
{showPlayIcon && (
161155
<button
162156
type="button"
163-
onClick={handleClick}
157+
onClick={handlePlayPauseClick}
164158
css={playIconStyles}
165159
>
166160
<PlayIcon iconWidth="narrow" />
@@ -175,10 +169,7 @@ export const LoopVideoPlayer = forwardRef(
175169
{/* Audio icon */}
176170
<button
177171
type="button"
178-
onClick={(event) => {
179-
event.stopPropagation(); // Don't pause the video
180-
setIsMuted(!isMuted);
181-
}}
172+
onClick={handleAudioClick}
182173
css={audioButtonStyles}
183174
>
184175
<div css={audioIconContainerStyles}>

0 commit comments

Comments
 (0)