I'm trying to update the src source property of a html5 video in order to play a new video selected from a list by the user. The selected video file is stored inside the fileSelected variable. The src is changing each time a new video is selected, but the html5 video player itself continues to play the old video. Anyone know where im going wrong? Here is the relevant code:
function Videos({files,fileSelected}){
var page =null
console.log(fileSelected)
if(!fileSelected && files){
fileSelected= files[0][0]
page = <SecondPage files = {files} fileSelected={fileSelected}/>
}
else if(files && fileSelected){
page = <SecondPage files = {files} fileSelected={fileSelected}/>
}else if(!files){
page = <FirstPage files = {files} fileSelected={fileSelected}/>
}
return(page)
}
export default Videos
function FirstPage({files,fileSelected}){
return(<div> <h2> No Video Uploaded </h2></div>)
}
class SecondPage extends React.Component{
constructor(props){
super(props)
}
render(){
const {files, fileSelected}=this.props;
var src
return(
<div>
{ src = (fileSelected) ? URL.createObjectURL(fileSelected): URL.createObjectURL(files[0][0])}
<div className="ui one column centered grid">
<div className="one wide column"> {/*needs to be one wide here not just column for center to work*/}
<h3>Videos</h3>
</div>
</div>
<div className="ui grid">
<div className="ten wide column">
<video controls width="566" height="320">
<source src={src} id='video' type="video/mp4" />
Your browser does not support HTML5 video.
</video>
</div>
<div className="six wide column">
{files[1]}
</div>
</div>
</div>
)
}
}
Note that the files variable contains all the videos the user can choose from.
When you change the source of a HTML5 video element you also need to tell the element to load to make it actually load the new source.
The example below, which is function rather than class based and uses hooks, works well. It essentially watches the source property (using useEffect) and then reloads the video when the source changes.
const MyVideo = forwardRef( (props, ref) => {
// This component represents a single video element.
// It includes a forward ref which means it passes it ref back to its parent
// so it's partent can use the ref too
const reloadSource = (newSource) => {
//Function to reload the video element with a new source
let vidElement = ref.current;
if (vidElement) {
vidElement.load();
}
};
useEffect (reloadSource, [props.source]);
return(
<div className="video-wrapper">
<video className="video-area" controls ref={ref}>
<source src={props.source} type="video/mp4"/>
Your browser does not support the video tag.
</video>
</div>
)
});
Related
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.
Well, as counter-intuitive as it sounds, muted tag is somehow ignored; check out the snippet below,
first one is rendered with react, the second one regular html; inspect them with your dev tools, and you see the react on doesn't have muted attribute; I already tried muted={true}, muted="true" but non is working.
function VideoPreview() {
return (
<div className="videopreview-container">
React tag:
<video
className="videopreview-container_video"
width="320"
height="240"
controls
autoPlay
muted
>
<source src="https://raw.githubusercontent.com/rpsthecoder/h/gh-pages/OSRO-animation.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
}
ReactDOM.render(<VideoPreview />, root)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<hr/>
Regular html:
<video
width="320"
height="240"
controls
autoplay
muted
>
<source src="https://raw.githubusercontent.com/rpsthecoder/h/gh-pages/OSRO-animation.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
This is actually a known issue which has existed since 2016.
The video will be muted correctly, but the property will not be set in the DOM.
You can find multiple workarounds in the GitHub issue, although there might be pros and cons with any of them.
As mentioned by #FluidSense it is an open bug since forever.
I could achieve it like this:
import React, { useRef, useEffect } from "react";
export default function Video({ src, isMuted }) {
const refVideo = useRef(null);
useEffect(() => {
if (!refVideo.current) {
return;
}
if (isMuted) {
//open bug since 2017 that you cannot set muted in video element https://github.com/facebook/react/issues/10389
refVideo.current.defaultMuted = true;
refVideo.current.muted = true;
}
refVideo.current.srcObject = src;
}, [src]);
return (
<video
ref={refVideo}
autoPlay
playsInline //FIX iOS black screen
/>
);
}
muted works if you type it as muted="true". Using the string true sends the attribute to the DOM now
Here is how I dealt with it using dangerouslySetInnerHTML:
import React, { Component } from "react";
export default class VideoComponent extends Component {
state = {
videoStr: "",
};
componentDidMount() {
const { src } = this.props;
const videoStr = `
<video autoplay loop muted>
<source src=${src} type="video/mp4" />
</video>
`;
this.setState({ videoStr });
}
render() {
return (
<div
className={this.props.className}
dangerouslySetInnerHTML={{ __html: this.state.videoStr }}
/>
);
}
}
I would also note that some browsers like Chrome might have a limit for the file size a video can be. I was running my own videos on my website and when I inspected the page and looked under Sources I did not find the video I had used and been looking for. This forced me to investigate further. I realized the video that I was running was about 10.4mb. It was large relative to the usual payload of a website so I lowered the size to around 5mb and the video appeared on my site.
Some other information about my steps to finding a solutions was that I was using my localhost to run my React app. I also ran my React app on Safari which surprisingly displayed my video even when the size was 10.4mb. I'm guessing that browsers have different criteria for video sizes.
I ran into the same problem, so I made a custom HTML element that adds the muted video. Here is the custom muted video:
class MutedVideo extends HTMLVideoElement {
constructor() {
super();
this.muted = true;
// I also noticed that you used autoplay, so I added it too.
this.autoplay = true;
}
}
customElements.define("x-muted", MutedVideo, { extends: "video" });
And here the video preview.
// Notice how I removed the muted and autoPlay props and added the 'is' prop
function VideoPreview() {
return (
<video
is="x-muted"
width="320"
height="240"
controls
src="https://raw.githubusercontent.com/rpsthecoder/h/gh-pages/OSRO-animation.mp4"
>
Sorry, your browser does not support the HTML video tag
</video>
);
}
Here is a working demo
from my own project. (WORK)
const Header = () => {
const [isMuted, setIsMuted] = useState(false);
return (
<div className="header">
<video src={headerBg} autoPlay loop muted={isMuted? true : false} />
<div className="container" >
<div className="btn-mute" onClick={() => setIsMuted(!isMuted)}/>
</div>
</div>
);
};
I'm trying to implement a lazy-loading image component with React currently.
My original solution was a very simple and basic approach. I used Image API and listened to onload event.
There is an example of React hook that was handling this functionality:
function useImage(src) {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (!loaded) {
loadImage();
}
function loadImage() {
const img = new Image();
img.onload = () => setLoaded(true);
img.src = src;
}
});
return loaded;
}
and code of the React component looked as follows:
function LazyImage({ src, ...props }) {
const isLoaded = useImage(src);
return (
<div className="container">
{isLoaded ? (
<img src={src} {...props} />
) : (
<div className="preloader" />
)}
</div>
);
}
Now I want to get the benefit of webp format which is supported by the API where I load the images.
Though browser support of webp format is not that good, it's possible to provide images inside a <source /> tag and browser will decide which image to load, also <img /> will be used as a fallback.
My React component was updated accordingly:
function LazyImage({ src, ...props }) {
const webpSrc = getWebpSrc(src);
const isLoaded = useImage(src);
return (
<div className="container">
{isLoaded ? (
<picture>
<source srcset={webpSrc} type="image/webp" />
<source srcset={src} type="image/jpeg" />
<img src={src} {...props} />
</picture>
) : (
<div className="preloader" />
)}
</div>
);
}
The problem here is that I'm still listening to the original jpg src to load in order to display the image. Unfortunately I couldn't just switch to listening to webp src as in browsers like Safari it will fail to load...
Possibly I can try to load both resources (webp and jpg) in parallel and update the state when the first one resolves. However this feels like making more requests instead of optimizing performance actually.
What do you think would be a good approach here? Or am I going in a wrong direction? Thanks!
You can use the IntersectionObserver Api.
First load an placeholder, when the element is intersected you can switch your placeholder with your desired src, this way you can only render the image if the element is in view
Intersection Observer API
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>
}<);
}
I'm working on a simple project that includes a media (mp3) player in the sidebar. I can get the play/pause button to visually switch and I can turn off the audio by assigning a href to another image however when trying to get the swapped image to pause audio I just can't seem to figure it out, here's my code..
EDIT: deleted shotty code
EDIT: Figured out three ways to do this, the two kind people below posted great ways but I also figured out how to crudely do this via jquery.
$('#left-05-pause_').click(function(){
$('#left-05-pause_').hide();
$('#left-05-play_').show();
});
$('#left-06-audio_').click(function(){
audio.volume = 1;
$('#left-06-audio_').hide();
$('#left-06-mute_').show();
});
Mitch, I have three points for you:
there's no need to wrap <a> around <img>
for performance avoid overuse of selecting elements (like getElementById), because once you've selected a link to the element put it into a variable and use again to access the same element
use native info about element's state (for <audio> in this example) - explore its properties
All in all just try next sample (file names have been changed for clarity):
<body>
<audio id="audioId">
<source src="song.mp3" type="audio/mp3" />
</audio>
<img id="imageId" src="play.png" onclick="toggle()" />
<script>
var audio = document.getElementById( 'audioId' )
, image = document.getElementById( 'imageId' )
function toggle()
{
if ( audio.paused )
{
image.src = 'pause.png'
audio.play()
}
else
{
image.src = 'play.png'
audio.pause()
}
}
</script>
</body>
You can try this
<a href="#">
<img src="http://royaltrax.com/aadev/images/left/images/left_05.png" id="imgPauseChange" onclick="changeImage()">
</a>
<script language="javascript">
function changeImage() {
if (document.getElementById("imgPauseChange").src == "http://royaltrax.com/aadev/images/left/images/left_05.png")
{
document.getElementById("aud").play();
document.getElementById("imgPauseChange").src = "http://royaltrax.com/aadev/images/left/images/left_05-pause.png";
}
else
{
document.getElementById("aud").pause();
document.getElementById("imgPauseChange").src = "http://royaltrax.com/aadev/images/left/images/left_05.png";
}
}
</script>