Skip to content

Commit 3e683c9

Browse files
Reconcile with main branch code
1 parent 3d2050a commit 3e683c9

File tree

1 file changed

+56
-9
lines changed

1 file changed

+56
-9
lines changed

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

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { css } from '@emotion/react';
22
import { log, storage } from '@guardian/libs';
3+
import { space } from '@guardian/source/foundations';
34
import { SvgAudio, SvgAudioMute } from '@guardian/source/react-components';
45
import { useCallback, useEffect, useRef, useState } from 'react';
56
import {
@@ -11,15 +12,20 @@ import { getZIndex } from '../lib/getZIndex';
1112
import { generateImageURL } from '../lib/image';
1213
import { useIsInView } from '../lib/useIsInView';
1314
import { useShouldAdapt } from '../lib/useShouldAdapt';
15+
import { useSubtitles } from '../lib/useSubtitles';
1416
import type { CustomPlayEventDetail, Source } from '../lib/video';
1517
import {
1618
customLoopPlayAudioEventName,
1719
customYoutubePlayEventName,
1820
} from '../lib/video';
1921
import { CardPicture, type Props as CardPictureProps } from './CardPicture';
2022
import { useConfig } from './ConfigContext';
23+
import type {
24+
PLAYER_STATES,
25+
PlayerStates,
26+
SubtitleSize,
27+
} from './LoopVideoPlayer';
2128
import { LoopVideoPlayer } from './LoopVideoPlayer';
22-
import type { PLAYER_STATES, PlayerStates } from './LoopVideoPlayer';
2329
import { ophanTrackerWeb } from './YoutubeAtom/eventEmitters';
2430

2531
const videoContainerStyles = css`
@@ -117,6 +123,8 @@ type Props = {
117123
fallbackImageAlt: CardPictureProps['alt'];
118124
fallbackImageAspectRatio: CardPictureProps['aspectRatio'];
119125
linkTo: string;
126+
subtitleSource?: string;
127+
subtitleSize: SubtitleSize;
120128
};
121129

122130
export const LoopVideo = ({
@@ -132,6 +140,8 @@ export const LoopVideo = ({
132140
fallbackImageAlt,
133141
fallbackImageAspectRatio,
134142
linkTo,
143+
subtitleSource,
144+
subtitleSize,
135145
}: Props) => {
136146
const adapted = useShouldAdapt();
137147
const { renderingTarget } = useConfig();
@@ -155,6 +165,8 @@ export const LoopVideo = ({
155165
* want to pause the video if it has been in view.
156166
*/
157167
const [hasBeenInView, setHasBeenInView] = useState(false);
168+
const [hasBeenPlayed, setHasBeenPlayed] = useState(false);
169+
const [hasTrackedPlay, setHasTrackedPlay] = useState(false);
158170

159171
const [devicePixelRatio, setDevicePixelRatio] = useState(1);
160172

@@ -165,6 +177,12 @@ export const LoopVideo = ({
165177
threshold: VISIBILITY_THRESHOLD,
166178
});
167179

180+
const activeCue = useSubtitles({
181+
video: vidRef.current,
182+
playerState,
183+
currentTime,
184+
});
185+
168186
const playVideo = useCallback(async () => {
169187
const video = vidRef.current;
170188
if (!video) return;
@@ -178,6 +196,7 @@ export const LoopVideo = ({
178196
.then(() => {
179197
// Autoplay succeeded
180198
dispatchOphanAttentionEvent('videoPlaying');
199+
setHasBeenPlayed(true);
181200
setPlayerState('PLAYING');
182201
})
183202
.catch((error: Error) => {
@@ -387,6 +406,19 @@ export const LoopVideo = ({
387406
}
388407
}, [isInView, hasBeenInView, atomId, linkTo]);
389408

409+
/**
410+
* Track the first successful video play in Ophan.
411+
*
412+
* This effect runs only after the video has actually started playing
413+
* for the first time. This is to ensure we don't double-report the event.
414+
*/
415+
useEffect(() => {
416+
if (!hasBeenPlayed || hasTrackedPlay) return;
417+
418+
ophanTrackerWeb(atomId, 'loop')('play');
419+
setHasTrackedPlay(true);
420+
}, [atomId, hasBeenPlayed, hasTrackedPlay]);
421+
390422
/**
391423
* Handle play/pause, when instigated by the browser.
392424
*/
@@ -416,14 +448,6 @@ export const LoopVideo = ({
416448
playerState === 'PAUSED_BY_INTERSECTION_OBSERVER' ||
417449
(hasPageBecomeActive && playerState === 'PAUSED_BY_BROWSER'))
418450
) {
419-
/**
420-
* Check if the video has not been in view before tracking the play.
421-
* This is so we only track the first play.
422-
*/
423-
if (!hasBeenInView) {
424-
ophanTrackerWeb(atomId, 'loop')('play');
425-
}
426-
427451
setHasPageBecomeActive(false);
428452
void playVideo();
429453
}
@@ -482,6 +506,25 @@ export const LoopVideo = ({
482506
return FallbackImageComponent;
483507
}
484508

509+
const handleLoadedMetadata = () => {
510+
const video = vidRef.current;
511+
if (!video) return;
512+
513+
const track = video.textTracks[0];
514+
if (!track?.cues) return;
515+
const pxFromBottom = space[3];
516+
const videoHeight = video.getBoundingClientRect().height;
517+
const percentFromTop =
518+
((videoHeight - pxFromBottom) / videoHeight) * 100;
519+
520+
for (const cue of Array.from(track.cues)) {
521+
if (cue instanceof VTTCue) {
522+
cue.snapToLines = false;
523+
cue.line = percentFromTop;
524+
}
525+
}
526+
};
527+
485528
const handleLoadedData = () => {
486529
if (vidRef.current) {
487530
setHasAudio(doesVideoHaveAudio(vidRef.current));
@@ -621,6 +664,7 @@ export const LoopVideo = ({
621664
isPlayable={isPlayable}
622665
playerState={playerState}
623666
isMuted={isMuted}
667+
handleLoadedMetadata={handleLoadedMetadata}
624668
handleLoadedData={handleLoadedData}
625669
handleCanPlay={handleCanPlay}
626670
handlePlayPauseClick={handlePlayPauseClick}
@@ -631,6 +675,9 @@ export const LoopVideo = ({
631675
AudioIcon={hasAudio ? AudioIcon : null}
632676
preloadPartialData={preloadPartialData}
633677
showPlayIcon={showPlayIcon}
678+
subtitleSource={subtitleSource}
679+
subtitleSize={subtitleSize}
680+
activeCue={activeCue}
634681
/>
635682
</figure>
636683
);

0 commit comments

Comments
 (0)