reactjs - 正确处理将摄像机流式传输到 HTML 视频元素的 React Hooks
问题描述
我一直在尝试编写一个 React Hook 来处理从用户相机捕获的蒸汽视频到 HTML 视频元素。我无法找出处理初始化和取消初始化相机和 HTML 视频元素的最佳方法。
我试图在我的钩子末尾添加一个清理功能,但我的尝试最终导致视频反复重新初始化或任何数量的其他奇怪错误。
真的,我正在努力弄清楚如何以及为什么调用清理函数。它似乎与正在卸载的组件无关。
另外,我不确定如何最好地销毁视频,尽管这里已经有很多答案,但我不确定是否需要完全删除它。挂在上面也无妨,只有六七页。我想我只想在用户离开页面时停止相机流,并在他们返回视频页面时再次启动它。
摄像头视频流挂钩
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 中添加清理函数,则只要任何参数更改,清理函数就会运行。
为了让视频清理仅在卸载时运行,您必须传递一个空的依赖数组。现在,由于效果内的变量在初始运行时属于闭包,因此您需要一个引用这些值的 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];
};
推荐阅读
- java - 回收站视图扩展可滚动部分?
- liquibase - Liquibase - 执行前测试变更集
- matrix - 如何在 Verilog 代码中给出输入矩阵?
- c++ - 指向整数数组的指针的 C++ 程序行为
- c# - HttpWebRequest 远程服务器返回错误:(401) Unauthorized
- sql - 为什么我得到一个超出范围的值:下溢以及如何解决它?
- r - 有没有办法在 R 中的最后一个索引处重新启动 for 循环?
- database - Oracle - ORA-38101:合并时的 INSERT VALUES 子句中的列无效
- python - 如何调试由第三方应用程序启动的 python 脚本
- java - 为 Fragment 实现 RecyclerView 适配器的问题