正确处理将摄像机流式传输到 HTML 视频元素的 React Hooks

Posted

技术标签:

【中文标题】正确处理将摄像机流式传输到 HTML 视频元素的 React Hooks【英文标题】:Correct handling of React Hooks for streaming video camera to HTML Video Element 【发布时间】:2019-12-10 18:08:24 【问题描述】:

我一直在尝试编写一个 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;

【问题讨论】:

这个问题以前是另一个问题的一部分,为了清楚起见,我将它们分开:***.com/questions/57298567/… 【参考方案1】:

如果您在任何将参数指定为依赖数组的 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];
;

【讨论】:

啊,我明白了,这完全有道理。每次我的状态更新时,都会调用函数及其回调。今晚我会试一试,非常感谢。 对于其他阅读本文的人来说,清理清理的空[] 是必需的,否则它会经常触发。

以上是关于正确处理将摄像机流式传输到 HTML 视频元素的 React Hooks的主要内容,如果未能解决你的问题,请参考以下文章

将实时 http 流式传输到 HTML5 视频客户端的最佳方法 [关闭]

如何将 openCV 视频流式传输到 HTML 网页?

寻找最快的视频编码器,将网络摄像头流式传输到 ipad

如何使用多摄像头适配器将视频从 RPI 上的 python 流式传输到笔记本电脑上的 JS/HTML 接口?

将实时视频广播从 android 相机流式传输到服务器

Android - 将视频从摄像头流式传输到另一个Android设备