Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am new to reactJS and trying to build a component.

I want to build a basic utility where I have a video component that shows live webcam feed and there would be a capture button, to capture and store a snap of the feed to disk. I want it to be in a single component ( video feed + capture button)

This code streams the feed in browser but I want it in a component,

<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>

This works fine, but not it's not a component.

I tried following in reaact js but it's not working:

<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>

The error is in handleVideo()

ReferenceError: video is not defined.

My understanding of error is,

since the video tag is coming in later part(in return), it's trying to use before the definition in handleVideo function. i am confused how to make this work.

Thank you!

like image 471
Arjun Chiddarwar Avatar asked Dec 13 '16 00:12

Arjun Chiddarwar


4 Answers

There are a few things to understand about the way React components work. Firstly according to the 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.

You should move initializing your video element to an alternate lifecycle method like componentDidMount to ensure that it is only initialized once.

Secondly, you rarely need to interact with the DOM directly. In this case we can use the component's internal state to manage the src attribute for the video stream, ensuring it only updates after the stream has initialized.

Here's an updated component that might work:

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>;
    }
});
like image 168
Jordan Burnett Avatar answered Nov 04 '22 08:11

Jordan Burnett


Some of the code posted in the answer has been deprecated and may no longer work in all browsers.
I have created a new React application which uses the latest syntax. It is very simple and straightforward. I hope it helps someone :)

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>
    );
  }
}

Here's my Medium article about it :
How to create a React application to stream your webcam | Basic, Beginner

If it helped don't forget to clap :)

Finally, here's the github repo of the working example : Basic-WebCam-Streamer

like image 21
AG_HIHI Avatar answered Nov 04 '22 08:11

AG_HIHI


Your handleVideo method references video, but you don't define that variable anywhere handleVideo can see it. Instead, you define it in render:

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

So there's your first problem, but it's not your real problem. Your real problem is that in React-land you want to avoid document.anything (getElementById, getByTagAndClassName, querySelector, etc.). This is because React uses a virtual DOM, and if you aren't careful when you reference actual DOM elements those references can quickly go bad (as the virtual DOM replaces the actual DOM after a render).

The solution is to use React's own alternative technique: refs. They're easier to explain by example, so here's your code fixed using 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>;

In other words, if you add a ref="something" attribute to any element you return in your render function, you can reference that element anywhere else in your (React) code by referring to this.refs.something.

There's also other ways to use refs (eg. you can pass a function), but that's outside the scope of this question, so I'd just recommend you read up on the React docs for refs:

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

EDIT

As Jordan Burnett's answer highlighted, there are other important differences between React and normal JS DOM work. Again, explaining all of them would be outside the scope of this question, but I strongly encourage you to read:

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

to learn the appropriate methods to override to do DOM-interaction work (eg. binding event handlers on non-React components).

like image 32
machineghost Avatar answered Nov 04 '22 08:11

machineghost


This is AG_HIHI's solution written as functional component. I removed the start button, instead start right away once mouted. Place the AppStreamCam component in you parant react component and it shows your webcam feed. Worked like a charm for me.

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
like image 1
Marco Avatar answered Nov 04 '22 07:11

Marco