Skip to content

Commit 6a1147b

Browse files
Merge branch 'main' into ei/add-xcust-param-to-skimlinks
2 parents f5d1c15 + c2a8a63 commit 6a1147b

23 files changed

+592
-127
lines changed

dotcom-rendering/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"@guardian/eslint-config-typescript": "9.0.1",
3838
"@guardian/identity-auth": "6.0.1",
3939
"@guardian/identity-auth-frontend": "8.1.0",
40-
"@guardian/libs": "22.5.0",
40+
"@guardian/libs": "23.0.0",
4141
"@guardian/ophan-tracker-js": "2.2.10",
4242
"@guardian/react-crossword": "6.3.0",
4343
"@guardian/shimport": "1.0.2",

dotcom-rendering/playwright.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export const PORT = isDev ? 3030 : 9000;
1111
*/
1212
export default defineConfig({
1313
testDir: './playwright/tests',
14-
// Don't run tests _within_ files in parallel as this causes flakiness locally - investigating
14+
// Don't run tests _within_ files in parallel locally as this causes flakiness
1515
// Test files still run in parallel as per the number of workers set below
16-
fullyParallel: false,
16+
fullyParallel: !!process.env.CI,
1717
// Fail the build on CI if you accidentally left test.only in the source code
1818
forbidOnly: !!process.env.CI,
1919
// Retry on CI only

dotcom-rendering/src/components/Lazy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const Lazy = ({ children, margin, disableFlexStyles }: Props) => {
3030
// being loaded as part of a Chromatic story or not so that
3131
// we can prevent lazy loading our storybook snapshots that we
3232
// use for visual regression
33-
const renderChildren = hasBeenSeen || Lazy.disabled;
33+
const renderChildren = !!hasBeenSeen || Lazy.disabled;
3434
return (
3535
<div ref={setRef} css={!disableFlexStyles && flexGrowStyles}>
3636
{renderChildren && <>{children}</>}

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

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getZIndex } from '../lib/getZIndex';
66
import { useIsInView } from '../lib/useIsInView';
77
import { useShouldAdapt } from '../lib/useShouldAdapt';
88
import { useConfig } from './ConfigContext';
9+
import type { PLAYER_STATES } from './LoopVideoPlayer';
910
import { LoopVideoPlayer } from './LoopVideoPlayer';
1011

1112
const videoContainerStyles = css`
@@ -20,7 +21,6 @@ type Props = {
2021
height: number;
2122
thumbnailImage: string;
2223
fallbackImageComponent: JSX.Element;
23-
hasAudio?: boolean;
2424
};
2525

2626
export const LoopVideo = ({
@@ -30,16 +30,21 @@ export const LoopVideo = ({
3030
height,
3131
thumbnailImage,
3232
fallbackImageComponent,
33-
hasAudio = true,
3433
}: Props) => {
3534
const adapted = useShouldAdapt();
3635
const { renderingTarget } = useConfig();
3736
const vidRef = useRef<HTMLVideoElement>(null);
3837
const [isPlayable, setIsPlayable] = useState(false);
39-
const [isPlaying, setIsPlaying] = useState(false);
4038
const [isMuted, setIsMuted] = useState(true);
4139
const [currentTime, setCurrentTime] = useState(0);
42-
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
40+
const [playerState, setPlayerState] =
41+
useState<(typeof PLAYER_STATES)[number]>('NOT_STARTED');
42+
43+
// The user indicates a preference for reduced motion: https://web.dev/articles/prefers-reduced-motion
44+
const [prefersReducedMotion, setPrefersReducedMotion] = useState<
45+
boolean | null
46+
>(null);
47+
4348
/**
4449
* Keep a track of whether the video has been in view. We only want to
4550
* pause the video if it has been in view.
@@ -52,51 +57,83 @@ export const LoopVideo = ({
5257
});
5358

5459
/**
55-
* Pause the video when the user scrolls past it.
60+
* Register the users motion preferences.
5661
*/
5762
useEffect(() => {
58-
if (!vidRef.current) return;
63+
const userPrefersReducedMotion = window.matchMedia(
64+
'(prefers-reduced-motion: reduce)',
65+
).matches;
66+
setPrefersReducedMotion(userPrefersReducedMotion);
67+
}, []);
5968

60-
if (isInView) {
61-
// We only autoplay the first time the video comes into view.
62-
if (hasBeenInView) return;
69+
/**
70+
* Autoplays the video when it comes into view.
71+
*/
72+
useEffect(() => {
73+
if (!vidRef.current || playerState === 'PAUSED_BY_USER') return;
6374

64-
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
65-
setPrefersReducedMotion(true);
75+
if (isInView && isPlayable && playerState !== 'PLAYING') {
76+
if (prefersReducedMotion !== false) {
6677
return;
6778
}
6879

69-
setIsPlaying(true);
70-
void vidRef.current.play();
71-
80+
setPlayerState('PLAYING');
7281
setHasBeenInView(true);
82+
83+
void vidRef.current.play();
7384
}
85+
}, [isInView, isPlayable, playerState, prefersReducedMotion]);
7486

75-
if (!isInView && hasBeenInView && isPlayable && isPlaying) {
76-
setIsPlaying(false);
87+
/**
88+
* Stops playback when the video is scrolled out of view, resumes playbacks
89+
* when the video is back in the viewport.
90+
*/
91+
useEffect(() => {
92+
if (!vidRef.current || !hasBeenInView) return;
93+
94+
const isNoLongerInView = playerState === 'PLAYING' && !isInView;
95+
if (isNoLongerInView) {
96+
setPlayerState('PAUSED_BY_INTERSECTION_OBSERVER');
7797
void vidRef.current.pause();
98+
setIsMuted(true);
99+
}
100+
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
104+
const isBackInView =
105+
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' && isInView;
106+
if (isBackInView) {
107+
setPlayerState('PLAYING');
108+
109+
void vidRef.current.play();
78110
}
79-
}, [isInView, hasBeenInView, isPlayable, isPlaying]);
111+
}, [isInView, hasBeenInView, playerState]);
80112

81113
if (renderingTarget !== 'Web') return null;
82114

83115
if (adapted) return fallbackImageComponent;
84116

85117
const playVideo = () => {
86118
if (!vidRef.current) return;
87-
setIsPlaying(true);
119+
120+
setPlayerState('PLAYING');
121+
setHasBeenInView(true);
88122
void vidRef.current.play();
89123
};
90124

91125
const pauseVideo = () => {
92126
if (!vidRef.current) return;
93-
setIsPlaying(false);
127+
128+
setPlayerState('PAUSED_BY_USER');
94129
void vidRef.current.pause();
95130
};
96131

97132
const playPauseVideo = () => {
98-
if (isPlaying) {
99-
pauseVideo();
133+
if (playerState === 'PLAYING') {
134+
if (isInView) {
135+
pauseVideo();
136+
}
100137
} else {
101138
playVideo();
102139
}
@@ -166,6 +203,20 @@ export const LoopVideo = ({
166203

167204
const AudioIcon = isMuted ? SvgAudioMute : SvgAudio;
168205

206+
// We only show a poster image when the user has indicated that they do
207+
// not want videos to play automatically, e.g. prefers reduced motion. Otherwise,
208+
// we do not need to download the image as the video will be autoplayed.
209+
const posterImage =
210+
!!prefersReducedMotion || isInView === false
211+
? thumbnailImage
212+
: undefined;
213+
214+
const showPlayIcon =
215+
playerState === 'PAUSED_BY_USER' ||
216+
(!!prefersReducedMotion && playerState === 'NOT_STARTED');
217+
218+
const shouldPreloadData = !!isInView || prefersReducedMotion === false;
219+
169220
return (
170221
<div
171222
ref={setNode}
@@ -177,24 +228,23 @@ export const LoopVideo = ({
177228
videoId={videoId}
178229
width={width}
179230
height={height}
180-
hasAudio={hasAudio}
231+
posterImage={posterImage}
181232
fallbackImageComponent={fallbackImageComponent}
182233
currentTime={currentTime}
183234
setCurrentTime={setCurrentTime}
184235
ref={vidRef}
185236
isPlayable={isPlayable}
186237
setIsPlayable={setIsPlayable}
187-
isPlaying={isPlaying}
188-
setIsPlaying={setIsPlaying}
238+
playerState={playerState}
239+
setPlayerState={setPlayerState}
189240
isMuted={isMuted}
190241
setIsMuted={setIsMuted}
191242
handleClick={handleClick}
192243
handleKeyDown={handleKeyDown}
193244
onError={onError}
194245
AudioIcon={AudioIcon}
195-
thumbnailImage={
196-
prefersReducedMotion ? thumbnailImage : undefined
197-
}
246+
shouldPreload={shouldPreloadData}
247+
showPlayIcon={showPlayIcon}
198248
/>
199249
</div>
200250
);

0 commit comments

Comments
 (0)