如何使用 react js 组件在网页上流式传输网络摄像头?

How to stream webcam feed on web page using react js component?

我是 reactJS 的新手,正在尝试构建一个组件。

我想构建一个基本实用程序,其中我有一个显示实时网络摄像头提要的视频组件,并且会有一个捕获按钮,用于捕获提要的快照并将其存储到磁盘。我希望它位于单个组件中(视频提要 + 捕获按钮)

此代码在浏览器中流式传输提要,但我希望它在组件中,

<body>
<div id="container">
    <video autoplay="true" id="videoElement">

    </video>
</div>
<script>
 var video = document.querySelector("#videoElement");

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;

if (navigator.getUserMedia) {       
    navigator.getUserMedia({video: true}, handleVideo, videoError);
}

function handleVideo(stream) {
    video.src = window.URL.createObjectURL(stream);
}

function videoError(e) {}
</script>
</body>
</html>

这工作正常,但不是一个组件。

我尝试在 reaact js 中跟随,但它不起作用:

<body>
    <div id="container">

    </div>
    <script type="text/jsx">
        var MyComponent = React.createClass({
            handleVideo: function(stream) {
                video.src = window.URL.createObjectURL(stream);
            },
            videoError: function() {

            },
            render: function() {

            var video = document.querySelector("#videoElement");

            navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;

            if (navigator.getUserMedia) {       
                navigator.getUserMedia({video: true}, this.handleVideo, this.videoError);
            }
                return <div>
                        <video autoplay="true" id="videoElement">
                        </video>
                       </div>;
            }
        });
    React.render( <MyComponent />, document.getElementById('container'));
    </script>
    </body>

错误在 handleVideo()

ReferenceError: 视频未定义。

我对错误的理解是,

因为video标签在后面的部分(在return中),它试图在handleVideo函数的定义之前使用。 我很困惑如何使这项工作。

谢谢!

您的 handleVideo 方法引用了 video,但您没有在 handleVideo 可以看到的任何地方定义该变量。相反,您在 render:

中定义它
var video = document.querySelector("#videoElement");

所以这是你的第一个问题,但这不是你真正的问题。你真正的问题是,在 React-land 中,你想避免 document.anythinggetElementByIdgetByTagAndClassNamequerySelector 等)。这是因为 React 使用虚拟的 DOM,如果你在引用实际的 DOM 元素时不小心,这些引用很快就会变坏(因为虚拟的 DOM 取代了实际的 DOM 在 render).

之后

解决方案是使用 React 自己的替代技术:refs。它们更容易通过示例进行解释,因此这是使用 refs:

修复的代码
handleVideo: function(stream) {
    // KEY CHANGE #1
    this.refs.video.src = window.URL.createObjectURL(stream);
},
render: function() {
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;

    if (navigator.getUserMedia) {       
        navigator.getUserMedia({video: true}, this.handleVideo, this.videoError);
    }
    return <div>
        {{/* KEY CHANGE #2 */}}
        <video autoplay="true" id="videoElement" ref="video">
        </video>
    </div>;

换句话说,如果您在 render 函数中向 return 的任何元素添加 ref="something" 属性,则可以在 (React) 代码中的任何其他位置引用该元素参考this.refs.something.

还有其他使用 refs 的方法(例如,您可以传递一个函数),但这超出了这个问题的范围,所以我建议您阅读 React 文档以获得 refs:

https://facebook.github.io/react/docs/refs-and-the-dom.html

编辑

正如 Jordan Burnett 的回答所强调的,React 和普通 JS DOM 工作之间还有其他重要的区别。同样,解释所有这些超出了这个问题的范围,但我强烈建议您阅读:

https://facebook.github.io/react/docs/react-component.html

学习适当的方法来重写以进行 DOM 交互工作(例如,在非 React 组件上绑定事件处理程序)。

关于 React 组件的工作方式,有几点需要了解。首先根据 React docs:

The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it's invoked, and it does not directly interact with the browser.

您应该将视频元素的初始化转移到备用生命周期方法,例如 componentDidMount 以确保它只被初始化一次。

其次,您很少需要直接与 DOM 互动。在这种情况下,我们可以使用组件的内部状态来管理视频流的 src 属性,确保它仅在流初始化后更新。

这是一个可能有效的更新组件:

var MyComponent = React.createClass({
  getInitialState: function(){
    return { videoSrc: null }
  },
  componentDidMount: function(){
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
    if (navigator.getUserMedia) {
        navigator.getUserMedia({video: true}, this.handleVideo, this.videoError);
    }
  },
  handleVideo: function(stream) {
    // Update the state, triggering the component to re-render with the correct stream
    this.setState({ videoSrc: window.URL.createObjectURL(stream) });
  },
  videoError: function() {

  },
  render: function() {
    return <div>
      <video src={this.state.videoSrc} autoPlay="true" />
    </div>;
    }
});

答案中发布的一些代码已被弃用,可能不再适用于所有浏览器。
我创建了一个使用最新语法的新 React 应用程序。 它非常简单明了。我希望它能帮助别人 :)

class AppStreamCam extends React.Component {
  constructor(props) {
    super(props);
    this.streamCamVideo= this.streamCamVideo.bind(this)
  }
  streamCamVideo() {
    var constraints = { audio: true, video: { width: 1280, height: 720 } };
    navigator.mediaDevices
      .getUserMedia(constraints)
      .then(function(mediaStream) {
        var video = document.querySelector("video");

        video.srcObject = mediaStream;
        video.onloadedmetadata = function(e) {
          video.play();
        };
      })
      .catch(function(err) {
        console.log(err.name + ": " + err.message);
      }); // always check for errors at the end.
  }
  render() {
    return (
      <div>
        <div id="container">
          <video autoPlay={true} id="videoElement" controls></video>
        </div>
        <br/>
        <button onClick={this.streamCamVideo}>Start streaming</button>
      </div>
    );
  }
}

这是我关于它的 Medium 文章:
How to create a React application to stream your webcam | Basic, Beginner

如果有帮助别忘了鼓掌:)

最后,这里是工作示例的 github 存储库: Basic-WebCam-Streamer

您可以使用 react-webcam。按照下面的link,它已经有一个"getScreenshot"函数,它returns来自网络摄像头的当前帧的base64编码图片。

https://www.npmjs.com/package/react-webcam

const WebcamCapture = () => {
  const webcamRef = React.useRef(null);
  const [imgSrc, setimgSrc] = React.useState(null);

  const capture = React.useCallback(
    () => {
      const imageSrc = webcamRef.current.getScreenshot();
      setimgSrc(imageSrc);

    },
    [webcamRef,setimgSrc]
  );

  return (
    <div>
      <Webcam
        audio={false}
        height={720}
        ref={webcamRef}
        screenshotFormat="image/jpeg"
        width={1280}
      />
      <button onClick={capture}>Capture photo</button>
      {imgSrc && (<img src = {imgSrc}/>) }
    </div>
  );
};    

乐于助人!

这是AG_HIHI作为功能组件编写的解决方案。我删除了开始按钮,而是在静音后立即开始。将 AppStreamCam 组件放在您的父反应组件中,它会显示您的网络摄像头提要。对我来说就像一个魅力。

import React, {useEffect} from 'react'

const AppStreamCam = () => {

  const streamCamVideo = () => {
  var constraints = { audio: true, video: { width: 1280, height: 720 } };
  navigator.mediaDevices
    .getUserMedia(constraints)
    .then(function(mediaStream) {
      var video = document.querySelector("video");

      video.srcObject = mediaStream;
      video.onloadedmetadata = function(e) {
        video.play();
      };
    })
    .catch(function(err) {
      console.log(err.name + ": " + err.message);
    }); // always check for errors at the end.
  }

  useEffect(()=>{
    streamCamVideo()
  },[])

  return (
    <div>
        <video autoPlay={true} id="videoElement" ></video>
    </div>
  );
}

export default AppStreamCam