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;