|
|
@@ -1,44 +1,54 @@ |
|
|
var React = require('react'); |
|
|
var cx = require('classnames'); |
|
|
var vjs = require('video.js'); |
|
|
var _ = require('lodash'); |
|
|
var _forEach = require('lodash/collection/forEach'); |
|
|
var _debounce = require('lodash/function/debounce'); |
|
|
var _defaults = require('lodash/object/defaults'); |
|
|
|
|
|
var DEFAULT_HEIGHT = 800; |
|
|
var DEFAULT_WIDTH = 600; |
|
|
var DEFAULT_ASPECT_RATIO = (9 / 16); |
|
|
var DEFAULT_ADJUSTED_SIZE = 0; |
|
|
var DEFAULT_RESIZE_DEBOUNCE_TIME = 500; |
|
|
var DEFAULT_VIDEO_OPTIONS = { |
|
|
preload: 'auto', |
|
|
autoplay: true, |
|
|
controls: true |
|
|
}; |
|
|
|
|
|
function noop() {} |
|
|
|
|
|
var Video = React.createClass({ |
|
|
propTypes: { |
|
|
src: React.PropTypes.string.isRequired, |
|
|
pause: React.PropTypes.bool, |
|
|
endless: React.PropTypes.bool, |
|
|
height: React.PropTypes.number, |
|
|
width: React.PropTypes.number, |
|
|
endlessMode: React.PropTypes.bool, |
|
|
options: React.PropTypes.object, |
|
|
onReady: React.PropTypes.func, |
|
|
eventListeners: React.PropTypes.object, |
|
|
resize: React.PropTypes.shape({ |
|
|
aspectRatio: React.PropTypes.number.isRequired, |
|
|
shortWindowVideoHeightAdjustment: React.PropTypes.number.isRequired, |
|
|
defaultVideoWidthAdjustment: React.PropTypes.number.isRequired |
|
|
resize: React.PropTypes.bool, |
|
|
resizeOptions: React.PropTypes.shape({ |
|
|
aspectRatio: React.PropTypes.number, |
|
|
shortWindowVideoHeightAdjustment: React.PropTypes.number, |
|
|
defaultVideoWidthAdjustment: React.PropTypes.number, |
|
|
debounceTime: React.PropTypes.number |
|
|
}), |
|
|
vjsDefaultSkin: React.PropTypes.bool, |
|
|
vjsBigPlayCentered: React.PropTypes.bool, |
|
|
warningComponent: React.PropTypes.element, |
|
|
children: React.PropTypes.element, |
|
|
dispose: React.PropTypes.bool, |
|
|
onNextVideo: React.PropTypes.func |
|
|
}, |
|
|
|
|
|
getDefaultProps: function() { |
|
|
return { |
|
|
pause: false, |
|
|
endless: false, |
|
|
options: { |
|
|
preload: 'auto', |
|
|
autoplay: true, |
|
|
controls: true |
|
|
}, |
|
|
endlessMode: false, |
|
|
options: DEFAULT_VIDEO_OPTIONS, |
|
|
onReady: noop, |
|
|
eventListeners: {}, |
|
|
resize: false, |
|
|
resizeOptions: {}, |
|
|
vjsDefaultSkin: true, |
|
|
vjsBigPlayCentered: true, |
|
|
onNextVideo: noop |
|
|
@@ -50,29 +60,36 @@ var Video = React.createClass({ |
|
|
}, |
|
|
|
|
|
componentWillReceiveProps: function(nextProps) { |
|
|
var isPaused = this.props.pause; |
|
|
var willBePaused = nextProps.pause; |
|
|
var isEndless = this.props.endlessMode; |
|
|
var willBeEndless = nextProps.endlessMode; |
|
|
|
|
|
if (isPaused !== willBePaused) { |
|
|
var player = this._player; |
|
|
|
|
|
if (willBePaused) { |
|
|
player.pause(); |
|
|
if (isEndless !== willBeEndless) { |
|
|
if (willBeEndless) { |
|
|
this.addEndlessMode(); |
|
|
} else { |
|
|
player.play(); |
|
|
this.removeEndlessMode(); |
|
|
} |
|
|
} |
|
|
|
|
|
var isEndless = this.props.endless; |
|
|
var willBeEndless = nextProps.endless; |
|
|
var isResizable = this.props.resize; |
|
|
var willBeResizeable = nextProps.resize; |
|
|
|
|
|
if (isEndless !== willBeEndless) { |
|
|
if (willBeEndless) { |
|
|
this.attachEndlessMode(); |
|
|
if (isResizable !== willBeResizeable) { |
|
|
if (willBeResizeable) { |
|
|
this.addResizeEventListener(); |
|
|
} else { |
|
|
this.detachEndlessMode(); |
|
|
this.removeResizeEventListener(); |
|
|
} |
|
|
} |
|
|
|
|
|
var currentSrc = this.props.src; |
|
|
var newSrc = nextProps.src; |
|
|
|
|
|
if (currentSrc !== newSrc) { |
|
|
this.setVideoPlayerSrc(newSrc); |
|
|
} else if (isEndless === willBeEndless) { |
|
|
this.restartVideo(); |
|
|
} |
|
|
}, |
|
|
|
|
|
shouldComponentUpdate: function() { |
|
|
@@ -83,17 +100,58 @@ var Video = React.createClass({ |
|
|
this.unmountVideoPlayer(); |
|
|
}, |
|
|
|
|
|
getVideoPlayer: function() { |
|
|
return this._player; |
|
|
}, |
|
|
|
|
|
getVideoPlayerEl: function() { |
|
|
return React.findDOMNode(this.refs.videoPlayer); |
|
|
}, |
|
|
|
|
|
getVideoPlayerOptions: function() { |
|
|
return _.defaults({}, this.props.options, { |
|
|
height: this.props.resize ? 'auto' : (this.props.options.height || DEFAULT_HEIGHT), |
|
|
width: this.props.resize ? 'auto' : (this.props.options.width || DEFAULT_WIDTH) |
|
|
return _defaults( |
|
|
{}, this.props.options, { |
|
|
height: this.props.resize ? 'auto' : (this.props.height || DEFAULT_HEIGHT), |
|
|
width: this.props.resize ? 'auto' : (this.props.width || DEFAULT_WIDTH) |
|
|
}, DEFAULT_VIDEO_OPTIONS); |
|
|
}, |
|
|
|
|
|
getVideoResizeOptions: function() { |
|
|
return _defaults({}, this.props.resizeOptions, { |
|
|
aspectRatio: DEFAULT_ASPECT_RATIO, |
|
|
shortWindowVideoHeightAdjustment: DEFAULT_ADJUSTED_SIZE, |
|
|
defaultVideoWidthAdjustment: DEFAULT_ADJUSTED_SIZE, |
|
|
debounceTime: DEFAULT_RESIZE_DEBOUNCE_TIME |
|
|
}); |
|
|
}, |
|
|
|
|
|
getResizedVideoPlayerMeasurements: function() { |
|
|
var resizeOptions = this.getVideoResizeOptions(); |
|
|
var aspectRatio = resizeOptions.aspectRatio; |
|
|
var defaultVideoWidthAdjustment = resizeOptions.defaultVideoWidthAdjustment; |
|
|
|
|
|
var winHeight = this._windowHeight(); |
|
|
|
|
|
var baseWidth = this._videoElementWidth(); |
|
|
|
|
|
var vidWidth = baseWidth - defaultVideoWidthAdjustment; |
|
|
var vidHeight = vidWidth * aspectRatio; |
|
|
|
|
|
if (winHeight < vidHeight) { |
|
|
var shortWindowVideoHeightAdjustment = resizeOptions.shortWindowVideoHeightAdjustment; |
|
|
vidHeight = winHeight - shortWindowVideoHeightAdjustment; |
|
|
} |
|
|
|
|
|
return { |
|
|
width: vidWidth, |
|
|
height: vidHeight |
|
|
}; |
|
|
}, |
|
|
|
|
|
setVideoPlayerSrc: function(src) { |
|
|
this._player.src(src); |
|
|
}, |
|
|
|
|
|
mountVideoPlayer: function() { |
|
|
var src = this.props.src; |
|
|
var options = this.getVideoPlayerOptions(); |
|
|
@@ -104,25 +162,23 @@ var Video = React.createClass({ |
|
|
|
|
|
player.ready(this.handleVideoPlayerReady); |
|
|
|
|
|
_.forEach(this.props.eventListeners, function(val, key) { |
|
|
_forEach(this.props.eventListeners, function(val, key) { |
|
|
player.on(key, val); |
|
|
}); |
|
|
|
|
|
player.src(src); |
|
|
|
|
|
if (this.props.endlessMode) { |
|
|
this.addEndlessMode(); |
|
|
} |
|
|
}, |
|
|
|
|
|
unmountVideoPlayer: function() { |
|
|
window.removeEventListener('resize', this._handleVideoPlayerResize); |
|
|
|
|
|
// _.forEach(this.props.eventListeners, function(val, key) { |
|
|
// this._player.off(key); |
|
|
// }); |
|
|
|
|
|
this._player.off(); |
|
|
this.removeResizeEventListener(); |
|
|
this._player.dispose(); |
|
|
}, |
|
|
|
|
|
attachEndlessMode: function() { |
|
|
addEndlessMode: function() { |
|
|
var player = this._player; |
|
|
|
|
|
player.on('ended', this.handleNextVideo); |
|
|
@@ -132,37 +188,62 @@ var Video = React.createClass({ |
|
|
} |
|
|
}, |
|
|
|
|
|
detachEndlessMode: function() { |
|
|
addResizeEventListener: function() { |
|
|
var debounceTime = this.getVideoResizeOptions().debounceTime; |
|
|
|
|
|
this._handleVideoPlayerResize = _debounce(this.handleVideoPlayerResize, debounceTime); |
|
|
window.addEventListener('resize', this._handleVideoPlayerResize); |
|
|
}, |
|
|
|
|
|
removeEndlessMode: function() { |
|
|
var player = this._player; |
|
|
|
|
|
player.off('ended', this.handleNextVideo); |
|
|
}, |
|
|
|
|
|
removeResizeEventListener: function() { |
|
|
window.removeEventListener('resize', this._handleVideoPlayerResize); |
|
|
}, |
|
|
|
|
|
pauseVideo: function() { |
|
|
this._player.pause(); |
|
|
}, |
|
|
|
|
|
playVideo: function() { |
|
|
this._player.play(); |
|
|
}, |
|
|
|
|
|
restartVideo: function() { |
|
|
this._player.currentTime(0).play(); |
|
|
}, |
|
|
|
|
|
togglePauseVideo: function() { |
|
|
if (this._player.paused()) { |
|
|
this.playVideo(); |
|
|
} else { |
|
|
this.pauseVideo(); |
|
|
} |
|
|
}, |
|
|
|
|
|
handleVideoPlayerReady: function() { |
|
|
this |
|
|
.getVideoPlayerEl() |
|
|
.parentElement |
|
|
.removeAttribute('data-reactid'); |
|
|
|
|
|
if (this.props.resize) { |
|
|
this.handleVideoPlayerResize(); |
|
|
this._handleVideoPlayerResize = _.debounce(this.handleVideoPlayerResize, 100); |
|
|
window.addEventListener('resize', this._handleVideoPlayerResize); |
|
|
this.addResizeEventListener(); |
|
|
} |
|
|
|
|
|
this.props.onReady(); |
|
|
}, |
|
|
|
|
|
handleVideoPlayerResize: function() { |
|
|
var player = this._player; |
|
|
var aspectRatio = this.props.resize.aspectRatio; |
|
|
var vWidth = this.getVideoPlayerEl().parentElement.parentElement.offsetWidth; |
|
|
var vHeight = vWidth * aspectRatio; |
|
|
var winHeight = window.innerHeight; |
|
|
var defaultVideoWidthAdjustment = this.props.resize.defaultVideoWidthAdjustment; |
|
|
|
|
|
// Ensures videojs fits to the users viewable screen. |
|
|
if (winHeight < vHeight) { |
|
|
var shortWindowVideoHeightAdjustment = this.props.resize.shortWindowVideoHeightAdjustment; |
|
|
vHeight = winHeight - shortWindowVideoHeightAdjustment; |
|
|
} |
|
|
var videoMeasurements = this.getResizedVideoPlayerMeasurements(); |
|
|
|
|
|
player.width(vWidth - defaultVideoWidthAdjustment).height(vHeight); |
|
|
player.dimensions(videoMeasurements.width, videoMeasurements.height); |
|
|
}, |
|
|
|
|
|
handleNextVideo: function() { |
|
|
@@ -176,6 +257,14 @@ var Video = React.createClass({ |
|
|
); |
|
|
}, |
|
|
|
|
|
_windowHeight: function() { |
|
|
return window.innerHeight; |
|
|
}, |
|
|
|
|
|
_videoElementWidth: function() { |
|
|
return this.getVideoPlayerEl().parentElement.parentElement.offsetWidth; |
|
|
}, |
|
|
|
|
|
render: function() { |
|
|
var videoPlayerClasses = cx({ |
|
|
'video-js': true, |
|
|
@@ -185,7 +274,7 @@ var Video = React.createClass({ |
|
|
|
|
|
return ( |
|
|
<video ref="videoPlayer" className={videoPlayerClasses}> |
|
|
{this.props.warningComponent || this.renderDefaultWarning()} |
|
|
{this.props.children || this.renderDefaultWarning()} |
|
|
</video> |
|
|
); |
|
|
} |
|
|
|