Cannot set property 'srcObject' of null, getUserMedia in ReactJs - javascript

I am trying to do a little project and I need to get a webcam stream. I have read many articles and followed them but they have all come out with the same error.
Cannot set property 'srcObject' of null TypeError
function James() {
var video = document.getElementById('video');
var select = document.getElementsByClassName('select');
function handleError(error) {
console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}
function start() {
navigator.mediaDevices.getUserMedia({video: true})
.then(function(stream) {
video.srcObject = stream;
video.play();
}) .catch(handleError);
}
start();
return (
<div className="contentarea">
<div className="camera">
<video id="video" autoPlay></video>
</div>
</div>
)
}
export default James;

In React, it's generally not a good idea to use getElementById. Chances are in this case, that's running before the video element is even rendered, so you're storing null there.
Instead, you should be using a ref. Something like const video = useRef() and then setting ref={video} on your video element. Then you can use video.current to get a reference to the video element at any time, without worrying about rendering issues.

Related

React img onError not firing

I am trying to use a third-party API for getting YouTube thumbnails with higher resolution, which sometimes fails with code 404. If I fail to fetch the image, I would like to replace the src with the default YouTube thumbnail retrieved using its own API (I have the url stored in a json). When I tried to implement this using the img onError event, it appears that nothing is fired when the fetch fails. Does anybody know what's going on? Thanks!
const TestView = () => {
const videoId = "tuZty35Fk7M"
return (
<div>
<img src={`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`} onError={() => {console.log("errored")}}></img>
</div>
)
}
Update:
This ended up working pretty well
const TestView = () => {
const videoId = "tuZty35Fk7M"
const imgRef = useRef(null)
const [thumbnailSrc, setThumbnailSrc] = useState(`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`)
const defaultThumbnailSrc = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`
const handleImgValidity = () => {
if (imgRef.current.naturalHeight == 90) {
setThumbnailSrc(defaultThumbnailSrc)
}
}
return (
<div>
<img ref={imgRef} src={thumbnailSrc} onLoad={handleImgValidity}></img>
</div>
)
}
It's nothing to do with React. Try visiting that url in your browser with a random string for the videoId. It will still display an image - in this case the default youtube thumbnail. So even though it's technically a 404, and your browser will report it as such, the image still has something to display so it won't fire the onError function.
If you replace the url in your code with something that is definitely a 404, your code works fine. Run the snippet below where i've swapped out youtube for google and you'll see your error message.
function onError() {
console.log('errored')
}
<img src="https://img.google.com/vi/some-random-id/maxresdefault.jpg" onError="onError()" />
Here is the working example of firing onError function.
https://codesandbox.io/s/reactjs-onerror-f7dq77

HTML <track> element shows cues in console but also length = 0 and they cannot be accessed by indexing

I'm working on a page with a video player in which I'm trying to show vtt captions and get information from the cues in the <track> element.
Here's what's relevant for the player in HTML:
<div class="video">
<video id="vid">
<source src="Video.mp4" type="video/mp4">
<track kind="subtitles" src="Captions.vtt" srclang="en">
</video>
</div>
I had something like this in my JavaScript first, just to see what I was getting before manipulating anything in code.
var trackObject = $('track')[0].track;
trackObject.mode = 'showing';
console.log('Track cues:');
console.log(trackObject.cues);
console.log(trackObject.cues[0]);
The change to the mode attribute is done there because, if I set default to the <track> element in HTML, then the video doesn't appear in many browsers. Still don't know why.
What this prints to the console is the following:
However, when I expand the TextTrackCueList, I do see the cues:
This has only made sense to me if I assume that the cues have loaded into the element, but the length attribute hasn't been updated. But I still don't know what's that length I'm seeing at the end of the list, which shows the actual number of cues.
I haven't found any kind of load event on the text track, so this is what I did to make sure I can get the cues:
var trackObject = $('track')[0].track;
trackObject.mode = 'showing';
var waitForCues = setInterval(function() {
if (trackObject.cues.length > 0) {
var cueList = getTracks(trackObject)
// ...Do some processing with the cues...
clearInterval(waitForCues);
}
}, 40);
Why does this happen with the length attribute of the track element? How can I get rid of that waiting for the length to be greater than 0?
As an HTMLMediaElement, the <track> element supports the same global load and error events that any other HTMLElement does, so you can listen for those to determine whether your VTT is ready for business or not.
mytrack.addEventListener(`load`, evt => {
console.log(`good to go`);
const { track } = mytrack;
// force this track to become active so we can get the cues:
track.mode = "showing";
const { cues } = track;
console.log(`${cues.length} cues found`);
});
mytrack.addEventListener(`error`, evt => {
console.log(`yeah that's a problem`);
});
<video controls>
<source src="https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4" type="video/mp4">
<track
id="mytrack"
kind="captions"
src="data:text/plain;charset=utf-8,WEBVTT%0A%0A00%3A00%3A00.500%20--%3E%2000%3A00%3A02.000%0AThe%20Web%20is%20always%20changing%0A%0A00%3A00%3A02.500%20--%3E%2000%3A00%3A04.300%0Aand%20the%20way%20we%20access%20it%20is%20changing"
srclang="en"
label="English"
default="default">
>
</video>

Javascript, dynamically change Video-Url - Video URL is set / Just Placeholder Poster Shown

i want to dynamically change the Urls of two Videos. My Problem is that the first Video's URL is getting Changed and plays correctly and the second Video just changes the Videos-Url without Playing it. The Second Video still shows the Placeholder-Poster.
Without changing the Video.source.src my Page looks like This:
<div class="container-fluid" id="container">
<div class="cocoen">
<video loop autoplay muted poster="img/placeholder_0.png">
<source type="video/mp4">
</video>
<video loop autoplay muted poster="img/placeholder_1.png">
<source type="video/mp4">
</video>
</div>
When trying to set the Video-Sources (yes, i know that both video-urls are the same, its just for test purposes :p)
var urls = ["img/small_1.mp4", "img/small_1.mp4"];
var sources = $("video > source").toArray();
sources[0].src = urls[0];
sources[1].src = urls[1];
startVideos();
Only the first (left) one is working:
But the Sources are correctly set on both videos.
I want to change the URLS more then Once, so just setting the URLS in the HTML isnt an Option.
If i set the src of video#1 to movie1 in html and video#2.src to movie2
and use JS to set video#1.src = movie2 and video#2.src=movie1
only the left video changes in display even tho the html has the correct src's.
I dont know if this Information is important, but this is how i start the Videos, after i set the URLS:
function reduceRetryOnError(array, callbackFunction, functionToRetry) {
success = array.reduce((acc, value) => acc && callbackFunction(value));
if (!success) {
console.log("Reduce failed: Retry in 300 ms");
setTimeout(functionToRetry(), 300);
}
}
function startVideos() {
function startVideo(video) {
if (!video) {
return false;
}
if (video.paused) {
video.play();
}
return true;
}
reduceRetryOnError($("video").toArray(), startVideo, startVideos);
}
I also tried to Pause and the start the Video again within the Starting Function.
Ty in advanced.

player.stop() dosen 't works on videojs

I'm trying to implement videojs with the IMA plugin, and i need to capture the stop and play event from a button outside the videobox in my react app.
The thing is i capture the event on my buttons and works fine, but they dosen t work when i want to stop the ADS.. why? Any clue?
i use this example
https://googleads.github.io/videojs-ima/examples/simple/
and i log the player.ima and return this, but i cant access to any element..
here is my code! thanks!
index.js
<video id="content_video"
class="video-js vjs-default-skin""
controls
>
<source src="//commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4" ></source>
</video>
<script>
window.player = videojs
var player = window.player('content_video');
var options = {
id: 'content_video',
adTagUrl: 'http://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=xml_vmap1&unviewed_position_start=1&cust_params=sample_ar%3Dpremidpostpod%26deployment%3Dgmf-js&cmsid=496&vid=short_onecue&correlator='
};
player.ima(options);
</script>
home.js
handlePlay = () => {
if(this.props.showPlay == 'block'){
const player = window.videojs(`#content_video`)
player.play() //only work on the video not in ads
} else {
const player = window.videojs(`#content_video`)
player.pause() //only work on the video not in ads
}
}

Video displayed in ReactJS component not updating

I'm new to ReactJS (0.13.1), and I've created a component in my app to display HTML5 video.
It seems to work perfectly but only for the first selection. The video that is actually displayed and playing in the page doesn't change when you switch from one video to another (when this.props.video changes).
I can see the <source src='blah.mp4' /> elements update in the Chrome inspector but the actually rendered video in the page doesn't change and keeps playing if it was already. Same thing happens in Safari & Firefox. All the other elements update appropriately as well.
Any ideas?
Anyway my component below:
(function(){
var React = require('react');
var VideoView = React.createClass({
render: function(){
var video = this.props.video;
var title = video.title === ''? video.id : video.title;
var sourceNodes = video.media.map(function(media){
media = 'content/'+media;
return ( <source src={media} /> )
});
var downloadNodes = video.media.map(function(media){
var ext = media.split('.').slice(-1)[0].toUpperCase();
media = 'content/'+media;
return (<li><a className="greybutton" href={media}>{ext}</a></li>)
});
return (
<div className="video-container">
<video title={title} controls width="100%">
{sourceNodes}
</video>
<h3 className="video-title">{title}</h3>
<p>{video.description}</p>
<div className="linkbox">
<span>Downloads:</span>
<ul className="downloadlinks">
{downloadNodes}
</ul>
</div>
</div>
)
}
});
module.exports = VideoView;
})();
UPDATE:
To describe it another way:
I have a list of links with onClick handlers that set the props of the component.
When I click on a video link ("Video Foo") for the first time I get
<video title="Video Foo" controls>
<source src="video-foo.mp4"/>
<source src="video-foo.ogv"/>
</video>
and "Video Foo" appears and can be played.
Then when I click on the next one ("Video Bar") the DOM updates and I get
<video title="Video Bar" controls>
<source src="video-bar.mp4"/>
<source src="video-bar.ogv"/>
</video>
However it is still "Video Foo" that is visible and can be played.
It's like once the browser has loaded media for a <video> it ignores any changes to the <source> elements.
I have described some approaches for plain JavaScript here. Based on that I have found solutions for React which work for me:
using src attribute on video itself:
var Video = React.createComponent({
render() {
return <video src={this.props.videoUrl} />;
}
});
Dana's answer is a great option extending this solution.
using .load() call on video element:
var Video = React.createComponent({
componentDidUpdate(_prevProps, _prevState) {
React.findDOMNode(this.refs.video).load(); // you can add logic to check if sources have been changed
},
render() {
return (
<video ref="video">
{this.props.sources.map(function (srcUrl, index) {
return <source key={index} src={srcUrl} />;
})}
</video>
);
}
});
UPD:
of course it's possible to add unique key attribute for <video> tag (for example based on your sources), so when sources will change it will be changed as well. But it will cause <video> to be re-rendered completely and it may cause some UI flashes.
var Video = React.createComponent({
render() {
return (
<video key={this.props.videoId}>
{this.props.sources.map(function (srcUrl, index) {
return <source key={index} src={srcUrl} />;
})}
</video>
);
}
});
I faced the same issue and I didn't have access to the <video> HTML tag as I was using a library to render the video (not the native <video> HTML tag) which is internally responsible for rendering the <video> tag.
In this case I have found another solution which I think is better to solve the same issue.
Before:
<VideoLibrary src={this.props.src} />
After:
<React.Fragment key={this.props.src}>
<VideoLibrary src={this.props.src} />
</React.Fragment>
Or this if you're using the native <video> HTML tag:
<React.Fragment key={this.props.src}>
<video src={this.props.src} />
</React.Fragment>
This way React will render different video tags because the src prop will be different hence rendering a different HTML tag each time to avoid this issue.
I find this way cleaner and simpler and will work in both cases if you have or don't have access to the <video> HTML tag.
Found the answer
Dynamically modifying a source element and its attribute when the element is already inserted in a video or audio element will have no effect. To change what is playing, just use the src attribute on the media element directly, possibly making use of the canPlayType() method to pick from amongst available resources. Generally, manipulating source elements manually after the document has been parsed is an unnecessarily complicated approach
https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element
It's a pretty hacky and fragile, but it got the job done for my cases.
(function(){
var React = require('react');
var VideoView = React.createClass({
pickSource: function(media){
var vid = document.createElement('video');
var maybes = media.filter(function(media){
var ext = media.split('.').slice(-1)[0].toUpperCase();
return (vid.canPlayType('video/'+ext) === 'maybe');
});
var probablies = media.filter(function(media){
var ext = media.split('.').slice(-1)[0].toUpperCase();
return (vid.canPlayType('video/'+ext) === 'probably');
});
var source = '';
if (maybes.length > 0) { source = maybes[0]; }
if (probablies.length > 0) { source = probablies[0]; }
source = (source === '')? '' : 'content/'+source;
return source;
},
render: function(){
var video = this.props.video;
var title = video.title === ''? video.id : video.title;
var src = this.pickSource(video.media);
var downloadNodes = video.media.map(function(media){
var ext = media.split('.').slice(-1)[0].toUpperCase();
media = 'content/'+media;
return (
<li><a className="greybutton" href={media}>{ext}</a></li>
)
});
return (
<div className="video-container">
<video title={title} src={src} controls width="100%"></video>
<h3 className="video-title">{title}</h3>
<p>{video.description}</p>
<div className="linkbox">
<span>Downloads:</span>
<ul className="downloadlinks">
{downloadNodes}
</ul>
</div>
</div>
)
}
});
module.exports = VideoView;
})();
Try this way
import React, { Component } from "react";
class Video extends Component<any, any> {
video: any = React.createRef();
componentDidUpdate(preProps: any) {
const { url } = this.props;
if (preProps && preProps.url && url) {
if (preProps.url !== url) {
this.video.current.src = url;
}
}
}
render() {
const { url } = this.props;
return (
<video controls ref={this.video}>
<source src={url} type="video/mp4" />
Your browser does not support HTML5 video.
</video>
);
}
}
I had the same problem with making a playlist with videos.
So I separated the video player to another react component
and that component received two props: contentId (video identify) & videoUrl (video URL).
Also I added a ref to the video tag so I can manage the tag.
var Video = React.createClass({
componentWillReceiveProps (nextProps) {
if (nextProps.contentId != this.props.contentId) {
this.refs['videoPlayer'].firstChild.src = this.props.videoUrl;
this.refs['videoPlayer'].load()
}
},
propType: {
contentId: React.PropTypes.string, // this is the id of the video so you can see if they equal, if not render the component with the new url
videoUrl: React.PropTypes.string, // the video url
} ,
getDefaultProps(){
return {
};
},
render() {
return (
<video ref="videoPlayer" id="video" width="752" height="423">
<source src={this.props.videoUrl} type="video/mp4" />
</video>
);
}
});
module.exports = Video;
this is much more clean:
<Video contentId="12" videoUrl="VIDEO_URL" />
Try to remove the source tag and instead have only the video tag and add src attribute to it like this example
<video src={video} className="lazy" loop autoPlay muted playsInline poster="" />
If you are getting the data from server and you want it to update the video link once you have new data.
import React, {Fragment, useEffect, useState} from "react";
const ChangeVID =(props)=> {
const [prevUploaded, setPrevUploaded] =useState(null);
useEffect(()=>{
if(props.changeIMG) {
setPrevUploaded(props.changeIMG)}
},[])
return(<Fragment>
{prevUploaded && <Suspense
fallback={<div>loading</div>}>
<div className="py-2" >
{ <video id={prevUploaded.duration} width="320" height="240" controls >
<source src={prevUploaded.url} type="video/mp4" />
</video>}
</div>
</Suspense>
}<);
}

Categories

Resources