diff --git a/packages/react/src/LazyVideo.tsx b/packages/react/src/LazyVideo.tsx index 976d538..8cbcbaf 100644 --- a/packages/react/src/LazyVideo.tsx +++ b/packages/react/src/LazyVideo.tsx @@ -2,7 +2,7 @@ "use client"; import { useInView } from 'react-intersection-observer' -import type { ReactElement } from 'react' +import { useEffect, type ReactElement, useRef, useCallback } from 'react' import type { LazyVideoProps } from './types/lazyVideoTypes'; import { fillStyles, transparentGif } from './lib/styles' @@ -12,11 +12,44 @@ export default function LazyVideo({ src, alt, fit, position, priority, noPoster, playing = true }: LazyVideoProps): ReactElement { + // Make a ref to the video so it can be controlled + const videoRef = useRef() + // Watch for in viewport to load video unless using priority - const { ref, inView } = useInView({ + const { ref: inViewRef, inView } = useInView({ skip: priority }) + // Support multiple refs on the video. This is from the + // react-intersection-observer docs + const setRefs = useCallback((node: HTMLVideoElement) => { + videoRef.current = node + inViewRef(node) + }, [inViewRef]) + + // Store the promise that is returned from play to prevent errors when + // pause() is called while video is benginning to play. + const playPromise = useRef>() + + // Play the video, waiting until it's safe to play it. And capture any + // errors while trying to play. + const play = async () => { + if (playPromise.current) await playPromise.current + try { playPromise.current = videoRef.current?.play()} + catch (e) { console.error(e) } + } + + // Pause the video, waiting until it's safe to play it + const pause = async () => { + if (playPromise.current) await playPromise.current + videoRef.current?.pause() + } + + // Respond to playing prop and call methods that control the video playback + useEffect(() => { + playing ? play() : pause() + }, [ playing ]) + // Simplify logic for whether to load sources const shouldLoad = priority || inView @@ -37,7 +70,7 @@ export default function LazyVideo({ poster={ noPoster ? transparentGif : undefined } // Straightforward props - ref={ref} + ref={setRefs} preload={ shouldLoad ? 'auto' : 'none' } aria-label={ alt } style={{