Initial implementation

This commit is contained in:
Ethan Smith 2017-06-22 09:34:19 -04:00
parent f261cc04a2
commit 156e5923c7
14 changed files with 413 additions and 9 deletions

View file

@ -1,3 +0,0 @@
'use strict';
module.exports = {};

View file

@ -0,0 +1,46 @@
'use strict';
var _ = require('underscore'),
events = require('../events');
module.exports = function(videojs) {
var MenuItem = videojs.getComponent('MenuItem');
/**
* A MenuItem to represent a video resolution
*
* @class QualityOption
* @extends videojs.MenuItem
*/
return videojs.extend(MenuItem, {
/**
* @inheritdoc
*/
constructor: function(player, options) {
var source = options.source;
if (!_.isObject(source)) {
throw new Error('was not provided a "source" object, but rather: ' + (typeof source));
}
options = _.extend({
selectable: true,
label: source.label,
}, options);
MenuItem.call(this, player, options);
this.source = source;
},
/**
* @inheritdoc
*/
handleClick: function(event) {
MenuItem.prototype.handleClick.call(this, event);
this.player().trigger(events.QUALITY_SELECTED, this.source);
},
});
};

View file

@ -0,0 +1,80 @@
'use strict';
var _ = require('underscore'),
events = require('../events'),
qualityOptionFactory = require('./QualityOption');
module.exports = function(videojs) {
var MenuButton = videojs.getComponent('MenuButton'),
QualityOption = qualityOptionFactory(videojs),
QualitySelector;
/**
* A component for changing video resolutions
*
* @class QualitySelector
* @extends videojs.Button
*/
QualitySelector = videojs.extend(MenuButton, {
/**
* @inheritdoc
*/
constructor: function(player, options) {
MenuButton.call(this, player, options);
this.selectedSource = options.selectedSource || player.currentSource();
player.on(events.QUALITY_SELECTED, function(event, source) {
this.setSelectedSource(source);
}.bind(this));
// Since it's possible for the player to get a source before the selector is
// created, make sure to update once we get a "ready" signal.
player.one('ready', function() {
this.update();
}.bind(this));
},
/**
* Updates the source that is selected in the menu
*
* @param source {object} player source to display as selected
*/
setSelectedSource: function(source) {
this.selectedSource = source;
this.update();
},
/**
* @inheritdoc
*/
createItems: function() {
var player = this.player(),
sources = player.currentSources();
if (!sources || sources.length < 2) {
return [];
}
return _.map(sources, function(source) {
return new QualityOption(player, {
source: source,
selected: this.selectedSource ? source.src === this.selectedSource.src : false,
});
}.bind(this));
},
/**
* @inheritdoc
*/
buildWrapperCSSClass: function() {
return 'vjs-quality-selector ' + MenuButton.prototype.buildWrapperCSSClass.call(this);
},
});
videojs.registerComponent('QualitySelector', QualitySelector);
return QualitySelector;
};

7
src/js/events.js Normal file
View file

@ -0,0 +1,7 @@
'use strict';
module.exports = {
QUALITY_SELECTED: 'qualitySelected',
};

14
src/js/index.js Normal file
View file

@ -0,0 +1,14 @@
'use strict';
var events = require('./events'),
qualitySelectorFactory = require('./components/QualitySelector'),
sourceInterceptorFactory = require('./middleware/SourceInterceptor');
module.exports = function(videojs) {
videojs = videojs || window.videojs;
qualitySelectorFactory(videojs);
sourceInterceptorFactory(videojs);
};
module.exports.EVENTS = events;

View file

@ -0,0 +1,72 @@
'use strict';
var _ = require('underscore'),
events = require('../events'),
QUALITY_CHANGE_CLASS = 'vjs-quality-changing';
module.exports = function(videojs) {
videojs.use('*', function(player) {
player.on(events.QUALITY_SELECTED, function(event, newSource) {
var sources = player.currentSources(),
currentTime = player.currentTime(),
isPaused = player.paused(),
selectedSource;
player.addClass(QUALITY_CHANGE_CLASS);
// Find and set the new selected source
// Note: See `setSource` for the reason behind using both 'isDefault'
// and 'isdefault'
sources = _.map(sources, _.partial(_.omit, _, [ 'isDefault', 'isdefault' ]));
selectedSource = _.findWhere(sources, { src: newSource.src });
// Note: `_.findWhere` returns a reference to an object. Thus the
// following updates the original object in `sources`.
selectedSource.isDefault = true;
player.src(sources);
player.one('loadeddata', function() {
player.removeClass(QUALITY_CHANGE_CLASS);
player.currentTime(currentTime);
if (!isPaused) {
player.play();
}
});
});
return {
setSource: function(autoSelectedSource, next) {
var sources = player.currentSources(),
defaultSource, selectedSource,
qualitySelector;
defaultSource = _.find(sources, function(source) {
// While the simplest check would be `!!source.isDefault`, remember that
// the sources can come from a `<source>` tag. Therefore, the lowercase
// form, `isdefault`, needs to be checked.
return source.isDefault === true
|| source.isDefault === 'true'
|| source.isdefault === true
|| source.isdefault === 'true';
});
selectedSource = defaultSource || autoSelectedSource;
// Update the quality selector with the new source
qualitySelector = player.controlBar.getChild('qualitySelector');
if (qualitySelector) {
qualitySelector.update();
}
// Pass along selected source
next(null, selectedSource);
},
};
});
};

3
src/js/standalone.js Normal file
View file

@ -0,0 +1,3 @@
'use strict';
require('./index')();

View file

@ -0,0 +1,28 @@
.vjs-quality-selector {
.vjs-menu-button {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
.vjs-icon-placeholder {
// From video.js font: https://github.com/videojs/font
font-family: 'VideoJS';
font-weight: normal;
font-style: normal;
&:before {
content: '\f110';
}
}
}
.vjs-quality-changing {
.vjs-big-play-button {
display: none;
}
.vjs-control-bar {
display: flex;
visibility: visible;
opacity: 1;
}
}