Skip to content

Instantly share code, notes, and snippets.

@mikechau
Last active August 20, 2021 12:50
Show Gist options
  • Save mikechau/5547c67d0dc2957e907d to your computer and use it in GitHub Desktop.
Save mikechau/5547c67d0dc2957e907d to your computer and use it in GitHub Desktop.

Revisions

  1. mikechau revised this gist Sep 24, 2015. 1 changed file with 146 additions and 57 deletions.
    203 changes: 146 additions & 57 deletions video.jsx
    Original file line number Diff line number Diff line change
    @@ -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>
    );
    }
  2. mikechau created this gist Jul 12, 2015.
    194 changes: 194 additions & 0 deletions video.jsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,194 @@
    var React = require('react');
    var cx = require('classnames');
    var vjs = require('video.js');
    var _ = require('lodash');

    var DEFAULT_HEIGHT = 800;
    var DEFAULT_WIDTH = 600;

    function noop() {}

    var Video = React.createClass({
    propTypes: {
    src: React.PropTypes.string.isRequired,
    pause: React.PropTypes.bool,
    endless: 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
    }),
    vjsDefaultSkin: React.PropTypes.bool,
    vjsBigPlayCentered: React.PropTypes.bool,
    warningComponent: 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
    },
    onReady: noop,
    eventListeners: {},
    vjsDefaultSkin: true,
    vjsBigPlayCentered: true,
    onNextVideo: noop
    };
    },

    componentDidMount: function() {
    this.mountVideoPlayer();
    },

    componentWillReceiveProps: function(nextProps) {
    var isPaused = this.props.pause;
    var willBePaused = nextProps.pause;

    if (isPaused !== willBePaused) {
    var player = this._player;

    if (willBePaused) {
    player.pause();
    } else {
    player.play();
    }
    }

    var isEndless = this.props.endless;
    var willBeEndless = nextProps.endless;

    if (isEndless !== willBeEndless) {
    if (willBeEndless) {
    this.attachEndlessMode();
    } else {
    this.detachEndlessMode();
    }
    }
    },

    shouldComponentUpdate: function() {
    return false;
    },

    componentWillUnmount: function() {
    this.unmountVideoPlayer();
    },

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

    mountVideoPlayer: function() {
    var src = this.props.src;
    var options = this.getVideoPlayerOptions();

    this._player = vjs(this.getVideoPlayerEl(), options);

    var player = this._player;

    player.ready(this.handleVideoPlayerReady);

    _.forEach(this.props.eventListeners, function(val, key) {
    player.on(key, val);
    });

    player.src(src);
    },

    unmountVideoPlayer: function() {
    window.removeEventListener('resize', this._handleVideoPlayerResize);

    // _.forEach(this.props.eventListeners, function(val, key) {
    // this._player.off(key);
    // });

    this._player.off();
    this._player.dispose();
    },

    attachEndlessMode: function() {
    var player = this._player;

    player.on('ended', this.handleNextVideo);

    if (player.ended()) {
    this.handleNextVideo();
    }
    },

    detachEndlessMode: function() {
    var player = this._player;

    player.off('ended', this.handleNextVideo);
    },

    handleVideoPlayerReady: function() {
    if (this.props.resize) {
    this.handleVideoPlayerResize();
    this._handleVideoPlayerResize = _.debounce(this.handleVideoPlayerResize, 100);
    window.addEventListener('resize', this._handleVideoPlayerResize);
    }

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

    player.width(vWidth - defaultVideoWidthAdjustment).height(vHeight);
    },

    handleNextVideo: function() {
    this.props.onNextVideo();
    },

    renderDefaultWarning: function() {
    return (
    <p className="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>.
    </p>
    );
    },

    render: function() {
    var videoPlayerClasses = cx({
    'video-js': true,
    'vjs-default-skin': this.props.vjsDefaultSkin,
    'vjs-big-play-centered': this.props.vjsBigPlayCentered
    });

    return (
    <video ref="videoPlayer" className={videoPlayerClasses}>
    {this.props.warningComponent || this.renderDefaultWarning()}
    </video>
    );
    }
    });

    module.exports = Video;