Home Reference Source Repository

component/ViewCourse/Youtube.js

import React from 'react';
import isEqual from 'lodash.isequal';
import youTubePlayer from 'youtube-player';

/**
* @ignore
*/
function shouldUpdateVideo(prevProps, props) {
  // A changing video should always trigger an update
  if (prevProps.videoId !== props.videoId) {
    return true;
  }

  // Otherwise, a change in the start/end time playerVars also requires a player
  // update.
  const prevVars = prevProps.opts.playerVars || {};
  const vars = props.opts.playerVars || {};

  return prevVars.start !== vars.start || prevVars.end !== vars.end;
}

/**
* @ignore
*/
function filterResetOptions(opts) {
  return {
    ...opts,
    playerVars: {
      ...opts.playerVars,
      controls: 1,
      autoplay: 0,
      start: 0,
      end: 0,
    },
  };
}

/**
* @ignore
*/
function shouldResetPlayer(prevProps, props) {
  return !isEqual(
    filterResetOptions(prevProps.opts),
    filterResetOptions(props.opts)
  );
}

/**
* @ignore
*/
function shouldUpdatePlayer(prevProps, props) {
  return (
     prevProps.id === props.id || prevProps.className === props.className
  );
}
/**
* @ignore
*/
class YouTube extends React.Component {
  static propTypes = {
    videoId: React.PropTypes.string,

    // custom ID for player element
    id: React.PropTypes.string,

    // custom class name for player element
    className: React.PropTypes.string,

    // https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player
    opts: React.PropTypes.object,

    // event subscriptions
    onReady: React.PropTypes.func,
    onError: React.PropTypes.func,
    onPlay: React.PropTypes.func,
    onPause: React.PropTypes.func,
    onEnd: React.PropTypes.func,
    onStateChange: React.PropTypes.func,
    onPlaybackRateChange: React.PropTypes.func,
    onPlaybackQualityChange: React.PropTypes.func,
  };

  static defaultProps = {
    opts: {},
    onReady: () => {},
    onError: () => {},
    onPlay: () => {},
    onPause: () => {},
    onEnd: () => {},
    onStateChange: () => {},
    onPlaybackRateChange: () => {},
    onPlaybackQualityChange: () => {},
  };

 /**
   * Expose PlayerState constants for convenience. These constants can also be
   * accessed through the global YT object after the YouTube IFrame API is instantiated.
   * https://developers.google.com/youtube/iframe_api_reference#onStateChange
   */
  static PlayerState = {
    UNSTARTED: -1,
    ENDED: 0,
    PLAYING: 1,
    PAUSED: 2,
    BUFFERING: 3,
    CUED: 5,
  };
  /**
  * @ignore
  */
  constructor(props) {
    super(props);

    this.container = null;
    this.internalPlayer = null;
  }
  /**
  * @ignore
  */
  componentDidMount() {
    this.createPlayer();
  }
  /**
  * @ignore
  */
  componentDidUpdate(prevProps) {
    if (shouldUpdatePlayer(prevProps, this.props)) {
      this.updatePlayer();
    }

    if (shouldResetPlayer(prevProps, this.props)) {
      this.resetPlayer();
    }

    if (shouldUpdateVideo(prevProps, this.props)) {
      this.updateVideo();
    }
  }
  /**
  * @ignore
  */
  componentWillUnmount() {
     /**
      * Note: The `youtube-player` package that is used promisifies all Youtube
      * Player API calls, which introduces a delay of a tick before it actually
      * gets destroyed. Since React attempts to remove the element instantly
      * this method isn't quick enough to reset the container element.
      */
    this.internalPlayer.destroy();
  }

  /**
  * @ignore
  */
  onPlayerReady = event => this.props.onReady(event);

  /**
  * @ignore
  */
  onPlayerError = event => this.props.onError(event);

  /**
  * @ignore
  */
  onPlayerStateChange = (event) => {
    this.props.onStateChange(event);
    switch (event.data) {

      case YouTube.PlayerState.ENDED:
        this.props.onEnd(event);
        break;

      case YouTube.PlayerState.PLAYING:
        this.props.onPlay(event);
        break;

      case YouTube.PlayerState.PAUSED:
        this.props.onPause(event);
        break;

      default:
        return;
    }
  };

  /**
  * @ignore
  */
  onPlayerPlaybackRateChange = event => this.props.onPlaybackRateChange(event);

  /**
  * @ignore
  */
  onPlayerPlaybackQualityChange = event => this.props.onPlaybackQualityChange(event);

  /**
  * @ignore
  */
  createPlayer = () => {
    // do not attempt to create a player server-side, it won't work
    if (typeof document === 'undefined') return;
    // create player
    const playerOpts = {
      ...this.props.opts,
      // preload the `videoId` video if one is already given
      videoId: this.props.videoId,
    };
    this.internalPlayer = youTubePlayer(this.container, playerOpts);
    // attach event handlers
    this.internalPlayer.on('ready', this.onPlayerReady);
    this.internalPlayer.on('error', this.onPlayerError);
    this.internalPlayer.on('stateChange', this.onPlayerStateChange);
    this.internalPlayer.on('playbackRateChange', this.onPlayerPlaybackRateChange);
    this.internalPlayer.on('playbackQualityChange', this.onPlayerPlaybackQualityChange);
  };

  /**
  * @ignore
  */
  resetPlayer = () => this.internalPlayer.destroy().then(this.createPlayer);

  /**
  * @ignore
  */
  updatePlayer = () => {
    this.internalPlayer.getIframe().then((iframe) => {
      iframe.setAttribute('id', this.props.id);
      iframe.setAttribute('class', this.props.className);
    });
  };

  /**
  * @ignore
  */
  updateVideo = () => {
    if (typeof this.props.videoId === 'undefined' || this.props.videoId === null) {
      this.internalPlayer.stopVideo();
      return;
    }

    // set queueing options
    let autoplay = false;
    const opts = {
      videoId: this.props.videoId,
    };
    if ('playerVars' in this.props.opts) {
      autoplay = this.props.opts.playerVars.autoplay === 1;
      if ('start' in this.props.opts.playerVars) {
        opts.startSeconds = this.props.opts.playerVars.start;
      }
      if ('end' in this.props.opts.playerVars) {
        opts.endSeconds = this.props.opts.playerVars.end;
      }
    }

    // if autoplay is enabled loadVideoById
    if (autoplay) {
      this.internalPlayer.loadVideoById(opts);
      return;
    }
    // default behaviour just cues the video
    this.internalPlayer.cueVideoById(opts);
  };
  /**
  * @ignore
  */
  refContainer = (container) => {
    this.container = container;
  };
  /**
  * @ignore
  */
  render() {
    return (
      <span>
        <div id={this.props.id} className={this.props.className} ref={this.refContainer} />
      </span>
    );
  }
}

export default YouTube;