0fff2542b7
Adds custom video.js hls quality selector plugin This allows the quality selector plugin to stay active and listen for source changes on the player to prevent the need to recreate the player when switching between MP4's and M3U8's
260 lines
6.7 KiB
JavaScript
260 lines
6.7 KiB
JavaScript
import videojs from 'video.js';
|
|
import {version as VERSION} from './package.json';
|
|
import ConcreteButton from './ConcreteButton';
|
|
import ConcreteMenuItem from './ConcreteMenuItem';
|
|
|
|
// Default options for the plugin.
|
|
const defaults = {};
|
|
|
|
// Cross-compatibility for Video.js 5 and 6.
|
|
const registerPlugin = videojs.registerPlugin || videojs.plugin;
|
|
// const dom = videojs.dom || videojs;
|
|
|
|
/**
|
|
* VideoJS HLS Quality Selector Plugin class.
|
|
*/
|
|
class HlsQualitySelectorPlugin {
|
|
|
|
/**
|
|
* Plugin Constructor.
|
|
*
|
|
* @param {Player} player - The videojs player instance.
|
|
* @param {Object} options - The plugin options.
|
|
*/
|
|
constructor(player, options) {
|
|
this.player = player;
|
|
this.config = options;
|
|
|
|
if (!this.player.qualityLevels) {
|
|
console.warn(`WARNING: Missing video.js quality levels plugin (required)`);
|
|
return;
|
|
}
|
|
|
|
this.setupPlugin();
|
|
}
|
|
|
|
setupPlugin() {
|
|
// Create the quality button.
|
|
this.createQualityButton();
|
|
|
|
// Bind event listeners
|
|
this.bindPlayerEvents();
|
|
|
|
// Listen for source changes
|
|
this.player.on('loadedmetadata', (e) => {
|
|
console.log(`Loaded Metadata detected by plugin!`, e);
|
|
this.updatePlugin();
|
|
});
|
|
}
|
|
|
|
updatePlugin() {
|
|
console.log(`Updating Quality Selector...`);
|
|
|
|
// If there is quality levels plugin and the HLS tech exists
|
|
// then continue.
|
|
if (this.getHls()) {
|
|
console.log('Show quality selector');
|
|
// Show quality selector
|
|
this._qualityButton.show();
|
|
} else {
|
|
console.log('Hide quality selector');
|
|
console.log('Source type does not support multiple qulaity levels...');
|
|
// Hide quality selector
|
|
this._qualityButton.hide();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns HLS Plugin
|
|
*
|
|
* @return {*} - videojs-hls-contrib plugin.
|
|
*/
|
|
getHls() {
|
|
return this.player.tech({ IWillNotUseThisInPlugins: true }).hls;
|
|
}
|
|
|
|
/**
|
|
* Binds listener for quality level changes.
|
|
*/
|
|
bindPlayerEvents() {
|
|
this.player.qualityLevels().on('addqualitylevel', this.onAddQualityLevel.bind(this));
|
|
}
|
|
|
|
/**
|
|
* Adds the quality menu button to the player control bar.
|
|
*/
|
|
createQualityButton() {
|
|
|
|
const player = this.player;
|
|
|
|
this._qualityButton = new ConcreteButton(player);
|
|
|
|
const placementIndex = player.controlBar.children().length - 2;
|
|
const concreteButtonInstance = player.controlBar.addChild(this._qualityButton,
|
|
{componentClass: 'qualitySelector'},
|
|
this.config.placementIndex || placementIndex);
|
|
|
|
concreteButtonInstance.addClass('vjs-quality-selector');
|
|
if (!this.config.displayCurrentQuality) {
|
|
const icon = ` ${this.config.vjsIconClass || 'vjs-icon-hd'}`;
|
|
|
|
concreteButtonInstance
|
|
.menuButton_.$('.vjs-icon-placeholder').className += icon;
|
|
} else {
|
|
this.setButtonInnerText('auto');
|
|
}
|
|
concreteButtonInstance.removeClass('vjs-hidden');
|
|
|
|
}
|
|
|
|
/**
|
|
*Set inner button text.
|
|
*
|
|
* @param {string} text - the text to display in the button.
|
|
*/
|
|
setButtonInnerText(text) {
|
|
this._qualityButton
|
|
.menuButton_.$('.vjs-icon-placeholder').innerHTML = text;
|
|
}
|
|
|
|
/**
|
|
* Builds individual quality menu items.
|
|
*
|
|
* @param {Object} item - Individual quality menu item.
|
|
* @return {ConcreteMenuItem} - Menu item
|
|
*/
|
|
getQualityMenuItem(item) {
|
|
const player = this.player;
|
|
|
|
return new ConcreteMenuItem(player, item, this._qualityButton, this);
|
|
}
|
|
|
|
/**
|
|
* Executed when a quality level is added from HLS playlist.
|
|
*/
|
|
onAddQualityLevel() {
|
|
|
|
const player = this.player;
|
|
const qualityList = player.qualityLevels();
|
|
const levels = qualityList.levels_ || [];
|
|
const levelItems = [];
|
|
|
|
for (let i = 0; i < levels.length; ++i) {
|
|
if (!levelItems.filter(_existingItem => {
|
|
return _existingItem.item && _existingItem.item.value === levels[i].height;
|
|
}).length) {
|
|
const levelItem = this.getQualityMenuItem.call(this, {
|
|
label: levels[i].height + 'p',
|
|
value: levels[i].height
|
|
});
|
|
|
|
levelItems.push(levelItem);
|
|
}
|
|
}
|
|
|
|
levelItems.sort((current, next) => {
|
|
if ((typeof current !== 'object') || (typeof next !== 'object')) {
|
|
return -1;
|
|
}
|
|
if (current.item.value < next.item.value) {
|
|
return -1;
|
|
}
|
|
if (current.item.value > next.item.value) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
levelItems.push(this.getQualityMenuItem.call(this, {
|
|
label: player.localize('Auto'),
|
|
value: 'auto',
|
|
selected: true
|
|
}));
|
|
|
|
if (this._qualityButton) {
|
|
this._qualityButton.createItems = function() {
|
|
return levelItems;
|
|
};
|
|
this._qualityButton.update();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets quality (based on media height)
|
|
*
|
|
* @param {number} height - A number representing HLS playlist.
|
|
*/
|
|
setQuality(height) {
|
|
const qualityList = this.player.qualityLevels();
|
|
|
|
// Set quality on plugin
|
|
this._currentQuality = height;
|
|
|
|
if (this.config.displayCurrentQuality) {
|
|
this.setButtonInnerText(height === 'auto' ? height : `${height}p`);
|
|
}
|
|
|
|
for (let i = 0; i < qualityList.length; ++i) {
|
|
const quality = qualityList[i];
|
|
|
|
quality.enabled = (quality.height === height || height === 'auto');
|
|
}
|
|
this._qualityButton.unpressButton();
|
|
}
|
|
|
|
/**
|
|
* Return the current set quality or 'auto'
|
|
*
|
|
* @return {string} the currently set quality
|
|
*/
|
|
getCurrentQuality() {
|
|
return this._currentQuality || 'auto';
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Function to invoke when the player is ready.
|
|
*
|
|
* This is a great place for your plugin to initialize itself. When this
|
|
* function is called, the player will have its DOM and child components
|
|
* in place.
|
|
*
|
|
* @function onPlayerReady
|
|
* @param {Player} player
|
|
* A Video.js player object.
|
|
*
|
|
* @param {Object} [options={}]
|
|
* A plain object containing options for the plugin.
|
|
*/
|
|
const onPlayerReady = (player, options) => {
|
|
player.addClass('vjs-hls-quality-selector');
|
|
player.hlsQualitySelector = new HlsQualitySelectorPlugin(player, options);
|
|
};
|
|
|
|
/**
|
|
* A video.js plugin.
|
|
*
|
|
* In the plugin function, the value of `this` is a video.js `Player`
|
|
* instance. You cannot rely on the player being in a "ready" state here,
|
|
* depending on how the plugin is invoked. This may or may not be important
|
|
* to you; if not, remove the wait for "ready"!
|
|
*
|
|
* @function hlsQualitySelector
|
|
* @param {Object} [options={}]
|
|
* An object of options left to the plugin author to define.
|
|
*/
|
|
const hlsQualitySelector = function(options) {
|
|
this.ready(() => {
|
|
onPlayerReady(this, videojs.mergeOptions(defaults, options));
|
|
});
|
|
};
|
|
|
|
// Register the plugin with video.js.
|
|
registerPlugin('hlsQualitySelector', hlsQualitySelector);
|
|
|
|
// Include the version number.
|
|
hlsQualitySelector.VERSION = VERSION;
|
|
|
|
export default hlsQualitySelector;
|