Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to integrate properly videojs with reactjs? #3816

Closed
jiteshlalwani opened this issue Dec 1, 2016 · 24 comments
Closed

How to integrate properly videojs with reactjs? #3816

jiteshlalwani opened this issue Dec 1, 2016 · 24 comments
Labels

Comments

@jiteshlalwani
Copy link

Videojs is working fine when added to html page but when it is added to rectjs component than it is not loading as expected. I have also change class attribute to className. Is there any guideline about how to integrate videojs with reactjs?

P.S. I have tried to find some tutorial for reactjs but couldn't find so.

@misteroneill
Copy link
Member

misteroneill commented Dec 1, 2016

This is something we are planning to write a guide for.

There has been a fair amount of discussion on this topic, though. There was some discussion of using React with video.js in #2006 (particularly, check out @chemoish's comment). And there are a few packages on npm offering React components with video.js integration; however, they don't look very popular.

That said, video.js operates on DOM elements; so, it's definitely possible. I'm not a React expert, but it looks like you'll want to use render to create a <video> element, componentDidMount to run videojs(), then componentWillUnmount to call dispose() on your player.

I believe there was also a caveat with getting React to ignore the player's DOM as well.

@jiteshlalwani
Copy link
Author

Thanks for the reply. I wish that we get the guide pretty soon since this is the best JS player that we could find and we are really excited to see it in Action (in React website).

@misteroneill
Copy link
Member

You're welcome! I wouldn't wait on the guide, though. We have a lot of work that is currently in progress and we likely won't get to new guides until after the new year.

@chemoish
Copy link
Member

chemoish commented Dec 1, 2016

@codeismine The code I posted is very old, as things change every week, but the concepts are the same.

  1. Since the library is client side javascript, you need to instantiate it in the client side lifecycle componentDidMount (all or almost all configurations can be done in there)
  2. Player can also be updated in componentWillReceiveProps if anything needs to be changed
  3. Player needs to be cleaned up in componentWillUnmount to remove listeners, etc (this is to be safe, not sure if your application will even cause the component to unmount—however, i put my player in a model that I close, so in that case I need it).

However, there is nothing special to do that to use normal React.Component .

render() {
  return (
    <video ref={(c) => this.player = c } />
  );
}

Reference/execute videojs(this.player) or whatever, in any of your methods.

(This is just one approach, you can do it differently, hopefully concepts are understandable)

@misteroneill
Copy link
Member

@chemoish Maybe you could write a guide for using video.js with React? #3818 😄

@jiteshlalwani
Copy link
Author

@chemoish: I have followed your's comment on #2006
but it seems it's still not working. PFB code:

class ReviewDetailVideo extends Component {

componentDidMount() {
    document.querySelector('.video-container').innerHTML = '<video id="videoplayer"></video>';

    this.player = videojs('videoplayer');
}

componentWillUnmount() {
    this.player.dispose();
}

togglePlayback() {
    if (this.player.paused()) {
        this.player.play();
    } else {
        this.player.pause();
    }
}

render() {

    return (
        <div>
            <div className="video-container"></div>

            <button onClick={this.togglePlayback}></button>
        </div>
    )
}

}

@chemoish
Copy link
Member

chemoish commented Dec 6, 2016

@codeismine Not sure what the problem is? What is not working? What is PFB code?

The comment I made above should indicate that my post was old and that the code is out of date, but maybe that wasn't clear?

If you notice, I posted a code block in the previous comment which would indicate that the implementation can/has changed (maybe the es6 syntax is confusing?).

render() {
  return (
    <video ref={(c) => this.player = c } />
  );
}

It looks like there might be fundamental knowledge gaps in the usage of React as well as a lack of video sources, at least from your comment. Not sure how to help.

@jiteshlalwani
Copy link
Author

@chemoish Never mind. I guess I was asking help from the wrong person! Thanks for providing information anyways.

@chemoish
Copy link
Member

chemoish commented Dec 6, 2016

Sorry,,, I can't help.

I would recommend not managing the DOM elements yourself because React manages them through a virtual DOM (don't do innerHTML calls, don't do jQuery calls).

I might recommend trying this out (looks like with your code it is already es6'ish)… If it is not on the right path, feel free to ignore—it actually may be a React problem and not a videojs problem.

/* global videojs */

componentDidMount() {
  this.player = videojs(this.videoPlayer, {
    src: 'http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4',
  }, () => {
    // on ready function for anything else you need to do
    // after the player is set up…
  });
}

componentWillUnmount() {
  this.player.dispose();

  // whatever other things you need to clean up—maybe remove the DOM reference
  this.videoPlayer = undefined;
}

render() {
  return (
    <video
      id="videoPlayer"
      ref={(c) => { this.videoPlayer = c; }}
    />
  );
}

Hope this helps or gives some insight.

@haio
Copy link

haio commented Dec 18, 2016

This is how we use it in React

import React, {Component} from 'react'

export default class VideoPlayer extends Component {
  static propTypes = {
    video: PropTypes.object.isRequired,
  }

  state = {}

  componentDidMount() {
    const {videojs} = global
    if (!videojs) {
      return
    }
    const {video} = this.props
    this.state.player = videojs(video.id)
  }

  componentWillUnmount() {
    if (this.state.player) {
      this.state.player.dispose()
    }
  }

  render() {
    const {video: {id, src, poster}} = this.props
    const videoHtml = `
      <video id="${id}" class="video-js vjs-default-skin" controls
       preload="auto" poster="${poster}"
      >
        <source src="${src}" type="video/mp4" />
        <p class="vjs-no-js">
          To view this video please enable JavaScript
        </p>
      </video>
    `
    return (
      <div dangerouslySetInnerHTML={{__html: videoHtml}}></div>
    )
  }
}

@misteroneill
Copy link
Member

Thanks for sharing @haio!

@gkatsev
Copy link
Member

gkatsev commented Dec 22, 2016

Also, I just wanted to add that as part of 5.15 we added a new feature to allow ingesting a div wrapper as the player element rather than using creating a new one: #3856.

All that's required is adding a data-vjs-player on the div that wraps the video element that is used for the embed code and videojs will re-use that div for the player element and that video element for the tech.

@fgarcia
Copy link

fgarcia commented Jan 21, 2017

So far no one has commented that styles must also be loaded properly

I am using data-vjs-player and everything works great, but for those like me reaching this thread, the one line not mentioned previously is this:

require('!style-loader!css-loader!video.js/dist/video-js.min.css')

This assumes Webpack is being used, so the loaders are explicit to prevent CSS Modules wrapping everything into a namespace. This way one can make sure that React is using the same version of JS and CSS from video.js

@brandonocasey
Copy link
Contributor

A guide for using React & video.js is currently in being reviewed. Hopefully that will help everyone that is currently having an issue working with React and video.js

@jiteshlalwani
Copy link
Author

jiteshlalwani commented Jan 24, 2017

@brandonocasey - Affirmative its working. Thanks for a great job!
The only problem that I am getting is that if I replace videoJsOptions const with directly json (as I need value from props for video url), it's not working anymore.

Something like this:
<VideoPlayer { ... {
autoPlay: true,
controls: true,
poster: this.props.poster,
sources: [{
src: this.props.videoUrl,
type: 'video/mp4'
}]
} }/>

@chemoish
Copy link
Member

@codeismine Here should be an example of this working—hopefully it meets your use case.

http://jsbin.com/sabamugiku/edit?html,js,output

@MohammedSiddiqui10p
Copy link

MohammedSiddiqui10p commented Mar 28, 2017

I've tried the recommendation given in your guides about how to integrate this with React.
My team and I are working on a project which is stuck with React 0.12.
And though things seem to run fine, the console is piled up with this error:
"Invariant Violation: ReactMount: Two valid but unequal nodes with the same data-reactid..."

I was able to track the issue down to this line in the "Player.prototype.createEl" function:

Object.getOwnPropertyNames(attrs).forEach(function (attr) {
      // workaround so we don't totally break IE7
      // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
       if (attr === 'class') {
        el.className += ' ' + attrs[attr];
      } else {
        el.setAttribute(attr, attrs[attr]);
      }
    });

This basically copies all the attributes of the video element onto the parent div element right ? And unfortunately the 'data-reactid' attribute also gets copied and hence you now have two DOM elements with the same id, and that's where React starts screaming.
The issue doesn't arise in React 15 because I believe they've stopped adding react id on child nodes.

I could make a pull request with a quick fix of ignoring the data-reactid attribute when adding it on the parent div element. What would you guys suggest ?

@chemoish
Copy link
Member

@MohammedSiddiqui10p maybe things aren't getting mounted and unmounted correctly?

It is been a very long time since I have used 0.12, but I did some small case that seems to work. Maybe this will work for you?

http://jsbin.com/tahutazaha/edit?html,js,output

@MohammedSiddiqui10p
Copy link

Thank you @chemoish, this should do the trick (Y)

@miguelpeixe
Copy link

Any tips on updating the component through componentWillReceiveProps?

From my attempts, I got the following problems:

  • .dispose() method is no good because it destroys the DOM, so the player can't be reinitialized
  • .options(obj) looks like it does nothing
  • .reset() apparently removes the sources but I can't figure out how to set it up again (along with all the other options, which I also need to be dynamic)

@miguelpeixe
Copy link

miguelpeixe commented Aug 25, 2017

Phew, I finally got it working. I'm recreating the video instance and disposing old ones by managing an updateCount state along with react's key prop.

Here's an untested sample of the main idea (my actual component has a lot of unrelated stuff):

export default class VideoPlayer extends Component {

  constructor(props) {
    super(props);
    this.state = {
      updateCount: 0
    };
  }
  
  setup() {
    let updateCount = this.state.updateCount;
    this.setState({
      updateCount: updateCount + 1
    });
  }

  componentDidMount() {
    this.setup();
  }

  componentWillReceiveProps(nextProps) {
    // You should probably change this check
    if(this.props !== nextProps)
      this.setup();
  }

  componentDidUpdate(prevProps, prevState) {
    if(this.state.updateCount !== prevState.updateCount) {
      // If it has a player, dispose
      if(this.player) {
        this.player.dispose();
      }
      // Create new player
      this.player = videojs(this.videoNode, this.props);
    }
  }

  componentWillUnmount() {
    // Dispose player on unmount
    if(this.player) {
      this.player.dispose();
    }
  }
  render() {
    // Use `key` so React knows this item is going to change
    const key = `${this.props.id || ''}-${this.state.updateCount}`;
    return (
      <div key={key} data-vjs-player>
        <video ref={ node => this.videoNode = node } className="video-js"></video>
      </div>
    )
  }
}

@gkatsev
Copy link
Member

gkatsev commented Jan 2, 2018

We have a simple starting guide now. http://docs.videojs.com/tutorial-react.html

@gkatsev gkatsev closed this as completed Jan 2, 2018
@ubrmensch
Copy link

Hi,

I have successfully implemented the basic example of creating a child vjs component within a parent component that initializes fine when I pass a videoJSOptions const via a spread operator like so:

<VideoPlayer {...videoJsOptions} />

where videoJSOptions is defined as a const in the parent component:

const videoJsOptions = {
autoplay: false,
controls: true,
playbackRates: [0.5, 1, 1.5, 2, 3],
width: "720px",
height: "480px",
sources: [{
src: 'http://www.mydomain.com/sample.mp4',
type: 'video/mp4'
}]
}

What I am trying to do now is to dynamically update the sources array by instead changing the initial videoJsOptions to remove the sources array completely, and then create a state object in the parent called sources which initializes as an empty array.

So the parent state looks like this:

state = {
sources: []
}

...and the VJS component now looks like this:

<VideoPlayer {...videoJsOptions} sources={this.state.sources} />

Then there is a play list of various elements in the parent that I've created an onClick that is handled by the parent like so:

handler(videoUrl){
this.setState({
sources: [{
src: videoUrl,
type: 'video/mp4'
}]
});
}

This approach does not seem to update the player at all. When I do a console log within the VJS component's componentDidUpdate hook, I can confirm that sources was correctly updated in the component props. console.log(props) outputs the following:

{autoplay: false, controls: true, playbackRates: Array(10), width: "720px", height: "480px", …}
autoplay: false
controls: true
height: "480px"
playbackRates: (10) [0.5, 1, 1.5, 2, 3]
sources: Array(1)
0: {src: "http://www.mydomain.com/sample.mp4", type: "video/mp4"}
length: 1
proto: Array(0)
width: "720px"

Is this approach of attaching the parent state to the VSJ component's sources prop not a valid way to dynamically update the video source?

Thx.

@ubrmensch
Copy link

Hi,

I have successfully implemented the basic example of creating a child vjs component within a parent component that initializes fine when I pass a videoJSOptions const via a spread operator like so:

<VideoPlayer {...videoJsOptions} />

where videoJSOptions is defined as a const in the parent component:

const videoJsOptions = {
autoplay: false,
controls: true,
playbackRates: [0.5, 1, 1.5, 2, 3],
width: "720px",
height: "480px",
sources: [{
src: 'http://www.mydomain.com/sample.mp4',
type: 'video/mp4'
}]
}

What I am trying to do now is to dynamically update the sources array by instead changing the initial videoJsOptions to remove the sources array completely, and then create a state object in the parent called sources which initializes as an empty array.

So the parent state looks like this:

state = {
sources: []
}

...and the VJS component now looks like this:

<VideoPlayer {...videoJsOptions} sources={this.state.sources} />

Then there is a play list of various elements in the parent that I've created an onClick that is handled by the parent like so:

handler(videoUrl){
this.setState({
sources: [{
src: videoUrl,
type: 'video/mp4'
}]
});
}

This approach does not seem to update the player at all. When I do a console log within the VJS component's componentDidUpdate hook, I can confirm that sources was correctly updated in the component props. console.log(props) outputs the following:

{autoplay: false, controls: true, playbackRates: Array(10), width: "720px", height: "480px", …}
autoplay: false
controls: true
height: "480px"
playbackRates: (10) [0.5, 1, 1.5, 2, 3]
sources: Array(1)
0: {src: "http://www.mydomain.com/sample.mp4", type: "video/mp4"}
length: 1
proto: Array(0)
width: "720px"

Is this approach of attaching the parent state to the VSJ component's sources prop not a valid way to dynamically update the video source?

Thx.

OK, this seemed to do the trick. I just updated the VJS player.src in the componentWillReceiveProps hook:

componentWillReceiveProps(props){

if(props.sources.length >0){
      this.player.src({
      type: props.sources[0].type,
      src: props.sources[0].src
  });
}

}

So every time the parent state.sources is updated, it triggers this event but then I read the sources prop and directly set it via this.player. I don't know if this is a garbage way of doing this, but it seems to work based on my limited knowledge of React. If someone has a better and cleaner solution, please do let me know.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

10 participants