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!
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>;
}
});
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
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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With