正确处理用于将摄像机流式传输到 HTML 视频元素的 React Hooks
Correct handling of React Hooks for streaming video camera to HTML Video Element
我一直在尝试编写一个 React Hook 来处理从用户相机捕获的流视频到 HTML 视频元素。我无法找到处理初始化和取消初始化相机和 HTML 视频元素的最佳方法。
我曾尝试在我的钩子末尾添加一个清理函数,但我的尝试以视频反复重新初始化或任何其他奇怪的错误而告终。
真的,我正在努力弄清楚如何以及为什么调用清理函数。它似乎与正在卸载的组件无关。
另外,我不确定如何最好地销毁视频,虽然这里已经有很多答案,但我不确定是否需要完全删除它。如果它挂在身边也没什么坏处,只有六页。我想我只是想在用户离开页面时停止摄像头流,并在他们 return 到视频页面时再次启动它。
相机视频流挂钩
import { useEffect, useState } from 'react';
const initialiseCamera = async() => await
navigator
.mediaDevices
.getUserMedia({audio: false, video: true});
export const useCamera = videoRef => {
const [isCameraInitialised, setIsCameraInitialised] = useState(false);
const [video, setVideo] = useState(null);
const [error, setError] = useState('');
const [playing, setPlaying] = useState(true);
useEffect(() => {
if(video || !videoRef.current) {
return;
}
const videoElement = videoRef.current;
if(videoElement instanceof HTMLVideoElement) {
setVideo(videoRef.current);
}
}, [videoRef, video]);
useEffect(() => {
if(!video || isCameraInitialised || !playing) {
return;
}
initialiseCamera()
.then(stream => {
video.srcObject = stream;
setIsCameraInitialised(true);
})
.catch(e => {
setError(e.message);
setPlaying(false);
});
}, [video, isCameraInitialised, playing]);
useEffect(() => {
const videoElement = videoRef.current;
if(playing) {
videoElement.play();
} else {
videoElement.pause();
}
},[playing, videoRef]);
return [video, isCameraInitialised, playing, setPlaying, error];
};
视频观看
import React, {createRef} from 'react';
import { useCamera } from '../hooks/use-camera';
import { Button } from '@orderandchaos/react-components';
const VideoViewDemo = () => {
const videoRef = createRef();
const [video, isCameraInitialised, running, setPlaying, error] = useCamera(videoRef);
return (
<div>
<video
ref={videoRef}
autoPlay={true}
muted={true}
controls
width={480}
height={270}
/>
<Button
onClick={() => setPlaying(!running)}
ariaLabel='Start/Stop Audio'
>{running ? 'Stop' : 'Start'}</Button>
</div>
);
};
export default VideoViewDemo;
如果您在任何将参数指定为依赖项数组的 useEffect 中添加清理函数,则只要任何参数发生变化,清理函数就会 运行。
为了在卸载时仅 运行 进行视频清理,您必须传递一个空的依赖项数组。现在,由于 effect 中的变量将属于初始 运行 处的闭包,您需要有一个引用这些值的 ref。
您可以编写一个清理挂钩来处理这个问题
const useCleanup = (val) => {
const valRef = useRef(val);
useEffect(() => {
valRef.current = val;
}, [val])
useEffect(() => {
return () => {
// cleanup based on valRef.current
}
}, [])
}
import { useEffect, useState } from 'react';
const initialiseCamera = async() => await
navigator
.mediaDevices
.getUserMedia({audio: false, video: true});
export const useCamera = videoRef => {
const [isCameraInitialised, setIsCameraInitialised] = useState(false);
const [video, setVideo] = useState(null);
const [error, setError] = useState('');
const [playing, setPlaying] = useState(true);
useEffect(() => {
if(video || !videoRef.current) {
return;
}
const videoElement = videoRef.current;
if(videoElement instanceof HTMLVideoElement) {
setVideo(videoRef.current);
}
}, [videoRef, video]);
useCleanup(video)
useEffect(() => {
if(!video || isCameraInitialised || !playing) {
return;
}
initialiseCamera()
.then(stream => {
video.srcObject = stream;
setIsCameraInitialised(true);
})
.catch(e => {
setError(e.message);
setPlaying(false);
});
}, [video, isCameraInitialised, playing]);
useEffect(() => {
const videoElement = videoRef.current;
if(playing) {
videoElement.play();
} else {
videoElement.pause();
}
},[playing, videoRef]);
return [video, isCameraInitialised, playing, setPlaying, error];
};
我一直在尝试编写一个 React Hook 来处理从用户相机捕获的流视频到 HTML 视频元素。我无法找到处理初始化和取消初始化相机和 HTML 视频元素的最佳方法。
我曾尝试在我的钩子末尾添加一个清理函数,但我的尝试以视频反复重新初始化或任何其他奇怪的错误而告终。
真的,我正在努力弄清楚如何以及为什么调用清理函数。它似乎与正在卸载的组件无关。
另外,我不确定如何最好地销毁视频,虽然这里已经有很多答案,但我不确定是否需要完全删除它。如果它挂在身边也没什么坏处,只有六页。我想我只是想在用户离开页面时停止摄像头流,并在他们 return 到视频页面时再次启动它。
相机视频流挂钩
import { useEffect, useState } from 'react';
const initialiseCamera = async() => await
navigator
.mediaDevices
.getUserMedia({audio: false, video: true});
export const useCamera = videoRef => {
const [isCameraInitialised, setIsCameraInitialised] = useState(false);
const [video, setVideo] = useState(null);
const [error, setError] = useState('');
const [playing, setPlaying] = useState(true);
useEffect(() => {
if(video || !videoRef.current) {
return;
}
const videoElement = videoRef.current;
if(videoElement instanceof HTMLVideoElement) {
setVideo(videoRef.current);
}
}, [videoRef, video]);
useEffect(() => {
if(!video || isCameraInitialised || !playing) {
return;
}
initialiseCamera()
.then(stream => {
video.srcObject = stream;
setIsCameraInitialised(true);
})
.catch(e => {
setError(e.message);
setPlaying(false);
});
}, [video, isCameraInitialised, playing]);
useEffect(() => {
const videoElement = videoRef.current;
if(playing) {
videoElement.play();
} else {
videoElement.pause();
}
},[playing, videoRef]);
return [video, isCameraInitialised, playing, setPlaying, error];
};
视频观看
import React, {createRef} from 'react';
import { useCamera } from '../hooks/use-camera';
import { Button } from '@orderandchaos/react-components';
const VideoViewDemo = () => {
const videoRef = createRef();
const [video, isCameraInitialised, running, setPlaying, error] = useCamera(videoRef);
return (
<div>
<video
ref={videoRef}
autoPlay={true}
muted={true}
controls
width={480}
height={270}
/>
<Button
onClick={() => setPlaying(!running)}
ariaLabel='Start/Stop Audio'
>{running ? 'Stop' : 'Start'}</Button>
</div>
);
};
export default VideoViewDemo;
如果您在任何将参数指定为依赖项数组的 useEffect 中添加清理函数,则只要任何参数发生变化,清理函数就会 运行。
为了在卸载时仅 运行 进行视频清理,您必须传递一个空的依赖项数组。现在,由于 effect 中的变量将属于初始 运行 处的闭包,您需要有一个引用这些值的 ref。
您可以编写一个清理挂钩来处理这个问题
const useCleanup = (val) => {
const valRef = useRef(val);
useEffect(() => {
valRef.current = val;
}, [val])
useEffect(() => {
return () => {
// cleanup based on valRef.current
}
}, [])
}
import { useEffect, useState } from 'react';
const initialiseCamera = async() => await
navigator
.mediaDevices
.getUserMedia({audio: false, video: true});
export const useCamera = videoRef => {
const [isCameraInitialised, setIsCameraInitialised] = useState(false);
const [video, setVideo] = useState(null);
const [error, setError] = useState('');
const [playing, setPlaying] = useState(true);
useEffect(() => {
if(video || !videoRef.current) {
return;
}
const videoElement = videoRef.current;
if(videoElement instanceof HTMLVideoElement) {
setVideo(videoRef.current);
}
}, [videoRef, video]);
useCleanup(video)
useEffect(() => {
if(!video || isCameraInitialised || !playing) {
return;
}
initialiseCamera()
.then(stream => {
video.srcObject = stream;
setIsCameraInitialised(true);
})
.catch(e => {
setError(e.message);
setPlaying(false);
});
}, [video, isCameraInitialised, playing]);
useEffect(() => {
const videoElement = videoRef.current;
if(playing) {
videoElement.play();
} else {
videoElement.pause();
}
},[playing, videoRef]);
return [video, isCameraInitialised, playing, setPlaying, error];
};