ReactJS 组件中显示的视频未更新

Video displayed in ReactJS component not updating

我是 ReactJS (0.13.1) 的新手,我在我的应用程序中创建了一个组件来显示 HTML5 视频。

它似乎工作得很好,但只适用于第一次选择。当您从一个视频切换到另一个视频时(当 this.props.video 发生变化时),页面中实际显示和播放的视频不会发生变化。

我可以在 Chrome 检查器中看到 <source src='blah.mp4' /> 元素更新,但页面中实际呈现的视频没有改变,如果已经播放的话会继续播放。 Safari 和 Firefox 中也会发生同样的事情。所有其他元素也会适当更新。

有什么想法吗?

无论如何我的组件如下:

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

更新:

换一种说法:

我有一个 link 的列表,其中包含设置组件属性的 onClick 处理程序。

当我第一次点击视频 link ("Video Foo") 时,我得到

<video title="Video Foo" controls>
  <source src="video-foo.mp4"/>
  <source src="video-foo.ogv"/>
</video>

和"Video Foo"出现并可以播放。

然后,当我点击下一个 ("Video Bar") 时,DOM 更新,我得到

<video title="Video Bar" controls>
  <source src="video-bar.mp4"/>
  <source src="video-bar.ogv"/>
</video>

但是仍然"Video Foo"可见并且可以播放。

这就像一旦浏览器加载了 <video> 的媒体,它就会忽略对 <source> 元素的任何更改。

找到答案

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

它非常笨拙且脆弱,但它完成了我的案例。

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

})();

我已经描述了一些简单的方法 JavaScript here。基于此,我找到了适合我的 React 解决方案:

  • 对视频本身使用 src 属性:

    var Video = React.createComponent({
      render() {
        return <video src={this.props.videoUrl} />;
      }
    });
    

    是扩展此解决方案的绝佳选择。

  • 使用 .load() 调用视频元素:

    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:

  • 当然可以为 <video> 标签添加唯一的 key 属性(例如基于您的来源),因此当来源发生变化时,它也会随之改变.但它会导致 <video> 完全重新渲染,并且可能会导致一些 UI 闪烁。

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

我在制作带有视频的播放列表时遇到了同样的问题。

所以我将视频播放器分离到另一个反应组件 该组件收到两个道具:contentId(视频标识)和 videoUrl(视频 URL)。

我还为视频标签添加了一个 ref,以便我可以管理该标签。

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;

这样干净多了:

<Video contentId="12" videoUrl="VIDEO_URL" />

我遇到了同样的问题,我无法访问 <video> HTML 标签,因为我正在使用库来渲染视频(不是原生的 <video> HTML 标签)内部负责呈现 <video> 标签。

在这种情况下,我找到了另一个我认为可以更好地解决同一问题的解决方案。

之前:

<VideoLibrary src={this.props.src} />

之后:

<React.Fragment key={this.props.src}>
  <VideoLibrary src={this.props.src} />
</React.Fragment>

或者,如果您使用的是原生 <video> HTML 标签:

<React.Fragment key={this.props.src}>
  <video src={this.props.src} />
</React.Fragment>

这种方式 React 将呈现不同的视频标签,因为 src 属性会不同,因此每次呈现不同的 HTML 标签以避免此问题。

我发现这种方式更清晰、更简单,无论您是否有权访问 <video> HTML 标签,这两种情况都适用。

试试这个方法

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

尝试删除 source 标签,而只使用 video 标签,并像这个例子一样向它添加 src 属性

<video src={video} className="lazy" loop autoPlay muted playsInline poster="" />

如果您正在从服务器获取数据并且希望它在获得新数据后更新视频link。

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