Skip to content
Open
33 changes: 20 additions & 13 deletions dotcom-rendering/src/components/SelfHostedVideo.island.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ export const SelfHostedVideo = ({
null,
);
const [hasPageBecomeActive, setHasPageBecomeActive] = useState(false);
const [hasTrackedPlay, setHasTrackedPlay] = useState(false);
const [width, setWidth] = useState<number | undefined>();
const [height, setHeight] = useState<number | undefined>();
const [optimisedSources, setOptimisedSources] = useState<Source[]>([]);
Expand Down Expand Up @@ -544,7 +543,10 @@ export const SelfHostedVideo = ({
currentTime,
});

const trackMilestones = useVideoMilestoneTracking(sendOphanTrackingEvent);
const [trackMilestones, resetMilestones] = useVideoMilestoneTracking(
sendOphanTrackingEvent,
videoStyle === 'Default',
);

const playVideo = useCallback(async () => {
const video = vidRef.current;
Expand Down Expand Up @@ -608,10 +610,8 @@ export const SelfHostedVideo = ({
}
} else {
void playVideo();
if (hasTrackedPlay) {
if (playerState !== 'NOT_STARTED' && playerState !== 'ENDED') {
sendOphanTrackingEvent('resume');
} else {
sendOphanTrackingEvent('play');
}
}
};
Expand Down Expand Up @@ -937,11 +937,7 @@ export const SelfHostedVideo = ({
* Track the first successful video play in Ophan.
*/
const handlePlaying = () => {
if (hasTrackedPlay) {
return;
}
sendOphanTrackingEvent('play');
setHasTrackedPlay(true);
trackMilestones({ started: true });
};

const handlePlayPauseClick = (event: React.SyntheticEvent) => {
Expand Down Expand Up @@ -1029,6 +1025,12 @@ export const SelfHostedVideo = ({
pauseVideo('PAUSED_BY_BROWSER');
};

const handleEnded = () => {
trackMilestones({ ended: true });
resetMilestones();
setPlayerState('ENDED');
};

/**
* If the video could not be loaded due to an error, report to
* Sentry and log in the console.
Expand Down Expand Up @@ -1078,13 +1080,17 @@ export const SelfHostedVideo = ({

if (playerState === 'PLAYING') {
setCurrentTime(video.currentTime);

/**
* We only want to track milestone events for "long-form"
* videos, not loops or cinemagraphs.
*/
if (videoStyle === 'Default') {
trackMilestones(video.currentTime, video.duration);
trackMilestones({
currentTime: video.currentTime,
duration: video.duration,
});

if (video.currentTime < 1) {
resetMilestones();
}
}
};
Expand Down Expand Up @@ -1206,6 +1212,7 @@ export const SelfHostedVideo = ({
}
handlePause={handlePause}
handleFullscreenClick={handleFullscreenClick}
handleEnded={handleEnded}
updateCurrentTime={updateCurrentTime}
onError={onError}
preloadPartialData={!!shouldAutoplay}
Expand Down
4 changes: 4 additions & 0 deletions dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const PLAYER_STATES = [
* For example, iOS devices in low power mode will suspend playback on autoplaying videos.
*/
'PAUSED_BY_BROWSER',
'ENDED',
] as const;

export type PlayerStates = (typeof PLAYER_STATES)[number];
Expand All @@ -123,6 +124,7 @@ export type Props = {
handleTimeUpdate: (event: SyntheticEvent<HTMLVideoElement>) => void;
handlePause: (event: SyntheticEvent) => void;
handleFullscreenClick?: (event: SyntheticEvent) => void;
handleEnded?: (event: SyntheticEvent) => void;
updateCurrentTime: (time: number) => void;
onError: (event: SyntheticEvent<HTMLVideoElement>) => void;
posterImage?: string;
Expand Down Expand Up @@ -177,6 +179,7 @@ export const SelfHostedVideoPlayer = forwardRef(
handleTimeUpdate,
handlePause,
handleFullscreenClick,
handleEnded,
updateCurrentTime,
onError,
preloadPartialData,
Expand Down Expand Up @@ -238,6 +241,7 @@ export const SelfHostedVideoPlayer = forwardRef(
onClick={handlePlayPauseClick}
onKeyDown={handleKeyDown}
onError={onError}
onEnded={handleEnded}
disablePictureInPicture={true}
>
{sources.map(({ src, mimeType }) => (
Expand Down
97 changes: 35 additions & 62 deletions dotcom-rendering/src/components/YoutubeAtom/YoutubeAtomPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,6 @@ type Props = {
renderingTarget: RenderingTarget;
};

type ProgressEvents = {
hasSentPlayEvent: boolean;
hasSentEndEvent: boolean;
};

/**
* Player listeners e.g.
* name: onReady, onStateChange, etc...
Expand Down Expand Up @@ -194,9 +189,13 @@ const createOnStateChangeListener =
(
videoId: string,
uniqueId: string,
progressEvents: ProgressEvents,
playerState: {
paused: boolean;
progressIntervalId: ReturnType<typeof setInterval> | undefined;
},
sendOphanTrackingEvent: (event: VideoEventKey) => void,
trackMilestones: (currentTime: number, duration: number) => void,
trackMilestones: ReturnType<typeof useVideoMilestoneTracking>[0],
resetMilestones: () => void,
): YT.PlayerEventHandler<YT.OnStateChangeEvent> =>
(event) => {
const loggerFrom = 'YoutubeAtomPlayer onStateChange';
Expand All @@ -218,44 +217,24 @@ const createOnStateChangeListener =
*/
dispatchCustomPlayEvent(uniqueId);

if (!progressEvents.hasSentPlayEvent) {
log('dotcom', {
from: loggerFrom,
videoId,
msg: 'start play',
event,
});
sendOphanTrackingEvent('play');
progressEvents.hasSentPlayEvent = true;

/**
* Set a timeout to check progress again in the future
*/
setTimeout(() => {
checkProgress();
}, 3000);
} else {
log('dotcom', {
from: loggerFrom,
videoId,
msg: 'resume',
event,
});
trackMilestones({ started: true });
if (playerState.paused) {
sendOphanTrackingEvent('resume');
}

const checkProgress = () => {
trackMilestones(player.getCurrentTime(), player.getDuration());
playerState.paused = false;

if (player.getPlayerState() !== YT.PlayerState.ENDED) {
/**
* Set a timeout to check progress again in the future
*/
setTimeout(() => checkProgress(), 3000);
}
clearInterval(playerState.progressIntervalId);
playerState.progressIntervalId = setInterval(() => {
trackMilestones({
currentTime: player.getCurrentTime(),
duration: player.getDuration(),
});

return null;
};
if (player.getPlayerState() === YT.PlayerState.ENDED) {
clearInterval(playerState.progressIntervalId);
}
}, 3000);
}

if (event.data === YT.PlayerState.PAUSED) {
Expand All @@ -268,6 +247,8 @@ const createOnStateChangeListener =
event,
});
sendOphanTrackingEvent('pause');
playerState.paused = true;
clearInterval(playerState.progressIntervalId);
}

if (event.data === YT.PlayerState.CUED) {
Expand All @@ -278,24 +259,13 @@ const createOnStateChangeListener =
event,
});
sendOphanTrackingEvent('cued');
progressEvents.hasSentPlayEvent = false;
}

if (
event.data === YT.PlayerState.ENDED &&
!progressEvents.hasSentEndEvent
) {
if (event.data === YT.PlayerState.ENDED) {
dispatchCustomPauseEvent(uniqueId);

log('dotcom', {
from: loggerFrom,
videoId,
msg: 'ended',
event,
});
sendOphanTrackingEvent('end');
progressEvents.hasSentEndEvent = true;
progressEvents.hasSentPlayEvent = false;
clearInterval(playerState.progressIntervalId);
trackMilestones({ ended: true });
resetMilestones();
}
};

Expand Down Expand Up @@ -441,12 +411,13 @@ export const YoutubeAtomPlayer = ({
},
[eventEmitters],
);
const trackMilestones = useVideoMilestoneTracking(sendOphanTrackingEvent);

const progressEvents = useRef<ProgressEvents>({
hasSentPlayEvent: false,
hasSentEndEvent: false,
});
const [trackMilestones, resetMilestones] = useVideoMilestoneTracking(
sendOphanTrackingEvent,
);
const playerPauseState = useRef<{
paused: boolean;
progressIntervalId: ReturnType<typeof setInterval> | undefined;
}>({ paused: false, progressIntervalId: undefined });

const [playerReady, setPlayerReady] = useState<boolean>(false);
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
Expand Down Expand Up @@ -485,9 +456,10 @@ export const YoutubeAtomPlayer = ({
const onStateChangeListener = createOnStateChangeListener(
videoId,
uniqueId,
progressEvents.current,
playerPauseState.current,
sendOphanTrackingEvent,
trackMilestones,
resetMilestones,
);

/**
Expand Down Expand Up @@ -648,6 +620,7 @@ export const YoutubeAtomPlayer = ({
origin,
playerReadyCallback,
renderingTarget,
resetMilestones,
trackMilestones,
uniqueId,
videoId,
Expand Down
Loading
Loading