UNPKG

@react-three/uikit

Version:

Build performant 3D user interfaces with react-three-fiber and yoga.

60 lines (59 loc) 2.77 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { createContext, forwardRef, useContext, useEffect, useImperativeHandle, useMemo, useRef, } from 'react'; import { Image } from './image.js'; import { SRGBColorSpace, VideoTexture } from 'three'; import { signal } from '@preact/signals-core'; import { setupVideoElementInvalidation, updateVideoElement } from '@pmndrs/uikit/internals'; import { useThree } from '@react-three/fiber'; const VideoContext = createContext(undefined); export function useVideoElement() { const element = useContext(VideoContext); if (element == null) { throw new Error(`useVideoElement can only be executed inside a Video component`); } return element; } export const Video = forwardRef((props, ref) => { const texture = useMemo(() => signal(undefined), []); const aspectRatio = useMemo(() => signal(1), []); const providedHtmlElement = props.src instanceof HTMLVideoElement ? props.src : undefined; const element = useMemo(() => { if (providedHtmlElement != null) { return providedHtmlElement; } const result = document.createElement('video'); result.style.position = 'absolute'; result.style.width = '1px'; result.style.zIndex = '-1000'; result.style.top = '0px'; result.style.left = '0px'; return result; }, [providedHtmlElement]); const isElementProvided = props.src instanceof HTMLVideoElement; useEffect(() => { if (isElementProvided) { return; } document.body.appendChild(element); return () => element.remove(); }, [element, isElementProvided]); const invalidate = useThree((s) => s.invalidate); useEffect(() => setupVideoElementInvalidation(element, invalidate), [element, invalidate]); updateVideoElement(element, props); useEffect(() => { const updateAspectRatio = () => (aspectRatio.value = element.videoWidth / element.videoHeight); updateAspectRatio(); element.addEventListener('resize', updateAspectRatio); return () => element.removeEventListener('resize', updateAspectRatio); }, [aspectRatio, element]); useEffect(() => { const videoTexture = new VideoTexture(element); videoTexture.colorSpace = SRGBColorSpace; videoTexture.needsUpdate = true; texture.value = videoTexture; return () => videoTexture.dispose(); }, [texture, element]); const internalRef = useRef(null); useImperativeHandle(ref, () => ({ ...internalRef.current, element: element }), [element]); return (_jsx(VideoContext.Provider, { value: element, children: _jsx(Image, { aspectRatio: aspectRatio, ...props, ref: internalRef, src: texture }) })); });