Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion dotcom-rendering/src/components/SelfHostedVideo.importable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,12 @@ export const SelfHostedVideo = ({
*/
const isCinemagraph = videoStyle === 'Cinemagraph';

const isLoop = videoStyle === 'Loop';

const singleClickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
null,
);

const [isInView, setNode] = useIsInView({
repeat: true,
threshold: VISIBILITY_THRESHOLD,
Expand Down Expand Up @@ -609,7 +615,25 @@ export const SelfHostedVideo = ({
if (isCinemagraph) return;

event.preventDefault();
playPauseVideo();

{
if (singleClickTimerRef.current) {
clearTimeout(singleClickTimerRef.current);
}
}
/*
* Why have a delay?
*
* This is to prevent play/pause toggling when the user double-clicks.
* A double-click fires as: click -> click -> dblclick, so without the delay
* the video would toggle play/pause twice before the dblclick handler fires.
* The 250ms delay roughly matches the system double-click threshold, giving
* the dblclick handler enough time to cancel the pending single-click.
*
* */
singleClickTimerRef.current = setTimeout(() => {
playPauseVideo();
}, 250);
};

const handleAudioClick = (event: React.SyntheticEvent) => {
Expand All @@ -629,7 +653,14 @@ export const SelfHostedVideo = ({
};

const handleFullscreenClick = (event: React.SyntheticEvent) => {
if (isCinemagraph || isLoop) return;

if (singleClickTimerRef.current) {
clearTimeout(singleClickTimerRef.current);
}

void submitClickComponentEvent(event.currentTarget, renderingTarget);

event.stopPropagation(); // Don't pause the video
const video = vidRef.current;

Expand Down
47 changes: 43 additions & 4 deletions dotcom-rendering/src/components/SelfHostedVideo.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ export const InteractionObserver: Story = {
},
} satisfies Story;

export const FullscreenOpen: Story = {
export const FullscreenButtonOpen: Story = {
...Loop5to4,
name: 'Open fullscreen',
name: 'Open fullscreen with button',
args: {
...Loop5to4.args,
videoStyle: 'Default',
Expand All @@ -181,9 +181,48 @@ export const FullscreenOpen: Story = {
'requestFullscreen',
);

await canvas.findByTestId('fullscreen-icon');
const fullscreenIcon = await canvas.findByTestId('fullscreen-icon');

await userEvent.click(canvas.getByTestId('fullscreen-icon'));
await userEvent.click(fullscreenIcon);

await expect(requestFullscreenSpy).toHaveBeenCalled();
},
} satisfies Story;

export const FullscreenDoubleClickOpen: Story = {
...Loop5to4,
name: 'Open fullscreen with a double click',
args: {
...Loop5to4.args,
videoStyle: 'Default',
},
parameters: {
test: {
// The following error is received without this flag: "TypeError: ophan.trackClickComponentEvent is not a function"
Comment thread
abeddow91 marked this conversation as resolved.
dangerouslyIgnoreUnhandledErrors: true,
},
},
play: async ({ canvasElement }) => {
/**
* Ideally, this interaction test would open and close fullscreen.
* However, the Fullscreen API is not implemented in jsdom, so
* document.fullscreenElement will always be null regardless of what the
* component does. Instead, we spy on requestFullscreen to ensure
* that the correct handler is invoked when the fullscreen button is clicked.
*/

const canvas = within(canvasElement);

const requestFullscreenSpy = spyOn(
HTMLElement.prototype,
'requestFullscreen',
);

const selfHostedPlayer = await canvas.findByTestId(
'self-hosted-video-player',
);

await userEvent.dblClick(selfHostedPlayer);

await expect(requestFullscreenSpy).toHaveBeenCalled();
},
Expand Down
1 change: 1 addition & 0 deletions dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export const SelfHostedVideoPlayer = forwardRef(
}}
onPause={handlePause}
onClick={handlePlayPauseClick}
onDoubleClick={handleFullscreenClick}
onKeyDown={handleKeyDown}
onError={onError}
>
Expand Down
Loading