Search code examples
javascripthtml5-videoreactjs

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.


Solution

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