首页 > 解决方案 > 正确处理将摄像机流式传输到 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;

标签: reactjshtml5-videoreact-hooks

解决方案


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

推荐阅读