UNPKG

@react-three/drei

Version:

useful add-ons for react-three-fiber

112 lines (103 loc) 3.35 kB
import * as React from 'react'; import { useRef, useEffect, forwardRef, useImperativeHandle } from 'react'; import * as THREE from 'three'; import { useThree } from '@react-three/fiber'; import { suspend } from 'suspend-react'; import { Events } from 'hls.js'; /* eslint react-hooks/exhaustive-deps: 1 */ const IS_BROWSER = /* @__PURE__ */((_window$document, _window$navigator) => typeof window !== 'undefined' && typeof ((_window$document = window.document) == null ? void 0 : _window$document.createElement) === 'function' && typeof ((_window$navigator = window.navigator) == null ? void 0 : _window$navigator.userAgent) === 'string')(); let _HLSModule = null; async function getHls(...args) { var _HLSModule2; (_HLSModule2 = _HLSModule) !== null && _HLSModule2 !== void 0 ? _HLSModule2 : _HLSModule = await import('hls.js'); // singleton const Ctor = _HLSModule.default; if (Ctor.isSupported()) { return new Ctor(...args); } return null; } function useVideoTexture(srcOrSrcObject, { unsuspend = 'loadedmetadata', start = true, hls: hlsConfig = {}, crossOrigin = 'anonymous', muted = true, loop = true, playsInline = true, onVideoFrame, ...videoProps } = {}) { const gl = useThree(state => state.gl); const hlsRef = useRef(null); const texture = suspend(() => new Promise(async res => { let src = undefined; let srcObject = undefined; if (typeof srcOrSrcObject === 'string') { src = srcOrSrcObject; } else { srcObject = srcOrSrcObject; } const video = Object.assign(document.createElement('video'), { src, srcObject, crossOrigin, loop, muted, playsInline, ...videoProps }); // hlsjs extension if (src && IS_BROWSER && src.endsWith('.m3u8')) { const hls = hlsRef.current = await getHls(hlsConfig); if (hls) { hls.on(Events.MEDIA_ATTACHED, () => void hls.loadSource(src)); hls.attachMedia(video); } } const texture = new THREE.VideoTexture(video); texture.colorSpace = gl.outputColorSpace; video.addEventListener(unsuspend, () => res(texture)); }), [srcOrSrcObject]); const video = texture.source.data; useVideoFrame(video, onVideoFrame); useEffect(() => { start && texture.image.play(); return () => { if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } }; }, [texture, start]); return texture; } // // VideoTexture // const VideoTexture = /* @__PURE__ */forwardRef(({ children, src, ...config }, fref) => { const texture = useVideoTexture(src, config); useEffect(() => { return () => void texture.dispose(); }, [texture]); useImperativeHandle(fref, () => texture, [texture]); // expose texture through ref return /*#__PURE__*/React.createElement(React.Fragment, null, children == null ? void 0 : children(texture)); }); // rVFC hook const useVideoFrame = (video, f) => { useEffect(() => { if (!f) return; if (!video.requestVideoFrameCallback) return; let handle; const callback = (...args) => { f(...args); handle = video.requestVideoFrameCallback(callback); }; video.requestVideoFrameCallback(callback); return () => video.cancelVideoFrameCallback(handle); }, [video, f]); }; export { VideoTexture, useVideoTexture };