@react-three/uikit
Version:
Build performant 3D user interfaces with react-three-fiber and yoga.
60 lines (59 loc) • 2.77 kB
JavaScript
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 }) }));
});