From eaf2d150efa1187033ba732a350a4db20f260435 Mon Sep 17 00:00:00 2001 From: Robert Reinhard Date: Tue, 13 Aug 2024 18:58:40 -0700 Subject: [PATCH] Make controls work --- .../react/cypress/component/LazyVideo.cy.tsx | 33 ++++++++++++-- .../src/LazyVideo/AccessibilityControls.tsx | 44 ++++++++++--------- .../react/src/LazyVideo/LazyVideoClient.tsx | 41 +++++++++++++++-- packages/react/src/ReactVisual.tsx | 4 ++ packages/react/src/types/lazyVideoTypes.ts | 2 + packages/react/src/types/reactVisualTypes.ts | 2 + 6 files changed, 98 insertions(+), 28 deletions(-) diff --git a/packages/react/cypress/component/LazyVideo.cy.tsx b/packages/react/cypress/component/LazyVideo.cy.tsx index d7b5dc5..351f446 100644 --- a/packages/react/cypress/component/LazyVideo.cy.tsx +++ b/packages/react/cypress/component/LazyVideo.cy.tsx @@ -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( + + ); + + 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( { 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', () => { @@ -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', () => { @@ -116,7 +141,7 @@ describe('Accessibility controls', () => { pauseIcon={() => Pause} /> ); - cy.get('button').contains('Pause') + cy.get("[aria-label=Pause]").contains("Pause"); }) }) diff --git a/packages/react/src/LazyVideo/AccessibilityControls.tsx b/packages/react/src/LazyVideo/AccessibilityControls.tsx index ff5966d..0abc4cb 100644 --- a/packages/react/src/LazyVideo/AccessibilityControls.tsx +++ b/packages/react/src/LazyVideo/AccessibilityControls.tsx @@ -12,39 +12,39 @@ const minAccessibleBtnSize = 24 // How far from the edge to position the button const positionGutter = '1em' -type AccessibilityControlsProps = Pick & { - 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 ( ); - } // Make the styles for positioning the button diff --git a/packages/react/src/LazyVideo/LazyVideoClient.tsx b/packages/react/src/LazyVideo/LazyVideoClient.tsx index 7bf2a94..855664f 100644 --- a/packages/react/src/LazyVideo/LazyVideoClient.tsx +++ b/packages/react/src/LazyVideo/LazyVideoClient.tsx @@ -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' @@ -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(); @@ -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; @@ -122,7 +157,7 @@ export default function LazyVideoClient({ {...{ play, pause, - paused, + isVideoPaused, playIcon, pauseIcon, hideAccessibilityControls, diff --git a/packages/react/src/ReactVisual.tsx b/packages/react/src/ReactVisual.tsx index cb02e13..953dca5 100644 --- a/packages/react/src/ReactVisual.tsx +++ b/packages/react/src/ReactVisual.tsx @@ -29,6 +29,8 @@ export default function ReactVisual( sourceTypes, sourceMedia, paused, + onPause, + onPlay, playIcon, pauseIcon, hideAccessibilityControls, @@ -90,6 +92,8 @@ export default function ReactVisual( priority, noPoster: !!image, // Use `image` as poster frame paused, + onPause, + onPlay, playIcon, pauseIcon, hideAccessibilityControls, diff --git a/packages/react/src/types/lazyVideoTypes.ts b/packages/react/src/types/lazyVideoTypes.ts index f9a01ce..a7e9195 100644 --- a/packages/react/src/types/lazyVideoTypes.ts +++ b/packages/react/src/types/lazyVideoTypes.ts @@ -9,6 +9,8 @@ export type LazyVideoProps = Pick void; + onPlay?: () => void; playIcon?: ComponentType; pauseIcon?: ComponentType;