Skip to content

Commit

Permalink
Make controls work
Browse files Browse the repository at this point in the history
  • Loading branch information
weotch committed Aug 14, 2024
1 parent de1fd13 commit eaf2d15
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 28 deletions.
33 changes: 29 additions & 4 deletions packages/react/cypress/component/LazyVideo.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ describe('Accessibility controls', () => {
cy.get("button").and("have.css", "left");
})

it('controls affect playback', () => {

const onPauseSpy = cy.spy().as("onPauseSpy")
const onPlaySpy = cy.spy().as("onPlaySpy");

cy.mount(
<LazyVideo
src="https://placehold.co/300x200.mp4"
alt="Accessibility controls test"
onPause={onPauseSpy}
onPlay={onPlaySpy}
/>
);

cy.get("video").isPlaying();
cy.get("[aria-label=Pause]").click();
cy.get("video").isPaused();
cy.get("[aria-label=Play]").click();
cy.get("video").isPlaying(); // The second time

cy.get("@onPauseSpy").should("have.been.calledOnce");
cy.get("@onPlaySpy").should("have.been.calledTwice");

})

it("allows a different position to be set", () => {
cy.mount(
<LazyVideo
Expand All @@ -92,8 +117,8 @@ describe('Accessibility controls', () => {
accessibilityControlsPosition='top right'
/>
);
cy.get("button").should("have.css", "top")
cy.get("button").and("have.css", "right");
cy.get("[aria-label=Pause]").should("have.css", "top")
cy.get("[aria-label=Pause]").and("have.css", "right");
});

it('allows the controls to be hidden', () => {
Expand All @@ -104,7 +129,7 @@ describe('Accessibility controls', () => {
hideAccessibilityControls
/>
);
cy.get("button").should('not.exist')
cy.get("[aria-label=Pause]").should("not.exist");
})

it('can have custom icons', () => {
Expand All @@ -116,7 +141,7 @@ describe('Accessibility controls', () => {
pauseIcon={() => <span>Pause</span>}
/>
);
cy.get('button').contains('Pause')
cy.get("[aria-label=Pause]").contains("Pause");
})

})
44 changes: 23 additions & 21 deletions packages/react/src/LazyVideo/AccessibilityControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,49 @@ const minAccessibleBtnSize = 24
// How far from the edge to position the button
const positionGutter = '1em'

type AccessibilityControlsProps = Pick<LazyVideoProps,
'paused' |
'playIcon' |
'pauseIcon' |
'hideAccessibilityControls' |
'accessibilityControlsPosition'> & {
play: () => void
pause: () => void
}
type AccessibilityControlsProps = Pick<
LazyVideoProps,
| "playIcon"
| "pauseIcon"
| "hideAccessibilityControls"
| "accessibilityControlsPosition"
> & {
isVideoPaused: boolean;
play: () => void;
pause: () => void;
};

// Adds a simple pause/play UI for accessibility use cases
export default function AccessibilityControls({ play,
export default function AccessibilityControls({
play,
pause,
paused,
isVideoPaused,
playIcon,
pauseIcon,
hideAccessibilityControls,
accessibilityControlsPosition
accessibilityControlsPosition,
}: AccessibilityControlsProps): ReactElement | null {

// If hidden, return nothing
if (hideAccessibilityControls) return null
if (hideAccessibilityControls) return null;

// Determine the icon to display
const Icon = paused
? playIcon || PlayIcon
: pauseIcon || PauseIcon;
const Icon = isVideoPaused ? playIcon || PlayIcon : pauseIcon || PauseIcon;

return (
<button
onClick={paused ? play : pause}
aria-pressed={!paused}
aria-label={paused ? "Play" : "Pause"}
onClick={isVideoPaused ? play : pause}
aria-pressed={!isVideoPaused}
aria-label={isVideoPaused ? "Play" : "Pause"}
style={{
// Clear default sizes
appearance: "none",
border: "none",
lineHeight: 0,
padding: 0,

// Make it look clickable
cursor: "pointer",

// Position the button
position: "absolute",
...makePosition(accessibilityControlsPosition),
Expand All @@ -60,7 +63,6 @@ export default function AccessibilityControls({ play,
<Icon />
</button>
);

}

// Make the styles for positioning the button
Expand Down
41 changes: 38 additions & 3 deletions packages/react/src/LazyVideo/LazyVideoClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { useInView } from 'react-intersection-observer'
import { useMediaQueries } from '@react-hook/media-query'
import { useEffect, type ReactElement, useRef, useCallback, type MutableRefObject } from 'react'
import { useEffect, type ReactElement, useRef, useCallback, type MutableRefObject, useState } from 'react'
import type { LazyVideoProps } from '../types/lazyVideoTypes';
import { fillStyles, transparentGif } from '../lib/styles'
import AccessibilityControls from './AccessibilityControls'
Expand Down Expand Up @@ -31,12 +31,18 @@ export default function LazyVideoClient({
position,
priority,
noPoster,
paused,
paused, // Used to control externally
onPause,
onPlay,
playIcon,
pauseIcon,
hideAccessibilityControls,
accessibilityControlsPosition,
}: LazyVideoClientProps): ReactElement {

// Track the actual video playback state
const [isVideoPaused, setVideoPaused] = useState(true)

// Make a ref to the video so it can be controlled
const videoRef = useRef<HTMLVideoElement>();

Expand Down Expand Up @@ -81,6 +87,35 @@ export default function LazyVideoClient({
paused ? pause() : play();
}, [paused]);

// Update internal play/pause state
useEffect(() => {
const videoElement = videoRef.current;

const handlePlay = () => {
setVideoPaused(false);
onPlay && onPlay();
};

const handlePause = () => {
setVideoPaused(true);
onPause && onPause();
};

// Add listeners
if (videoElement) {
videoElement.addEventListener("play", handlePlay);
videoElement.addEventListener("pause", handlePause);
}

// Cleanup
return () => {
if (videoElement) {
videoElement.removeEventListener("play", handlePlay);
videoElement.removeEventListener("pause", handlePause);
}
};
}, []);

// Simplify logic for whether to load sources
const shouldLoad = priority || inView;

Expand Down Expand Up @@ -122,7 +157,7 @@ export default function LazyVideoClient({
{...{
play,
pause,
paused,
isVideoPaused,
playIcon,
pauseIcon,
hideAccessibilityControls,
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/ReactVisual.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export default function ReactVisual(
sourceTypes,
sourceMedia,
paused,
onPause,
onPlay,
playIcon,
pauseIcon,
hideAccessibilityControls,
Expand Down Expand Up @@ -90,6 +92,8 @@ export default function ReactVisual(
priority,
noPoster: !!image, // Use `image` as poster frame
paused,
onPause,
onPlay,
playIcon,
pauseIcon,
hideAccessibilityControls,
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/types/lazyVideoTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export type LazyVideoProps = Pick<ReactVisualProps,
'sourceMedia' |
'style' |
'paused' |
'onPause' |
'onPlay' |
'playIcon' |
'pauseIcon' |
'hideAccessibilityControls' |
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/types/reactVisualTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type ReactVisualProps = {
sourceMedia?: SourceMedia[];

paused?: boolean;
onPause?: () => void;
onPlay?: () => void;
playIcon?: ComponentType;
pauseIcon?: ComponentType;

Expand Down

0 comments on commit eaf2d15

Please sign in to comment.