Merge branch 'hartman-rewrite'

This commit is contained in:
Kasper Moskwiak 2016-07-09 18:57:21 +02:00
commit 89c85ff04a
3 changed files with 213 additions and 208 deletions

View file

@ -59,6 +59,7 @@
width: 1000, width: 1000,
plugins: { plugins: {
videoJsResolutionSwitcher: { videoJsResolutionSwitcher: {
ui: true,
default: 'low', // Default resolution [{Number}, 'low', 'high'], default: 'low', // Default resolution [{Number}, 'low', 'high'],
dynamicLabel: true // Display dynamic labels or gear symbol dynamicLabel: true // Display dynamic labels or gear symbol
} }

View file

@ -1,8 +1,10 @@
.vjs-resolution-button.vjs-menu-icon:before { .vjs-resolution-button .vjs-menu-icon:before {
content: '\f110'; content: '\f110';
font-family: VideoJS; font-family: VideoJS;
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-size: 1.8em;
line-height: 1.67em;
} }
.vjs-resolution-button .vjs-resolution-button-label { .vjs-resolution-button .vjs-resolution-button-label {

View file

@ -1,6 +1,6 @@
/*! videojs-resolution-switcher - 2015-7-26 /*! videojs-resolution-switcher - 2015-7-26
* Copyright (c) 2016 Kasper Moskwiak * Copyright (c) 2016 Kasper Moskwiak
* Modified by Pierre Kraft * Modified by Pierre Kraft and Derk-Jan Hartman
* Licensed under the Apache-2.0 license. */ * Licensed under the Apache-2.0 license. */
(function() { (function() {
@ -15,141 +15,88 @@
} }
(function(window, videojs) { (function(window, videojs) {
var videoJsResolutionSwitcher,
defaults = {
var defaults = {}, ui: true
videoJsResolutionSwitcher,
currentResolution = {}, // stores current resolution
menuItemsHolder = {}; // stores menuItems
function setSourcesSanitized(player, sources, label, customSourcePicker) {
currentResolution = {
label: label,
sources: sources
}; };
if(typeof customSourcePicker === 'function'){
return customSourcePicker(player, sources, label);
}
return player.src(sources.map(function(src) {
return {src: src.src, type: src.type, res: src.res};
}));
}
/* /*
* Resolution menu item * Resolution menu item
*/ */
var MenuItem = videojs.getComponent('MenuItem'); var MenuItem = videojs.getComponent('MenuItem');
var ResolutionMenuItem = videojs.extend(MenuItem, { var ResolutionMenuItem = videojs.extend(MenuItem, {
constructor: function(player, options, onClickListener, label){ constructor: function(player, options){
this.onClickListener = onClickListener; options.selectable = true;
this.label = label; // Sets this.player_, this.options_ and initializes the component
// Sets this.player_, this.options_ and initializes the component MenuItem.call(this, player, options);
MenuItem.call(this, player, options); this.src = options.src;
this.src = options.src;
this.on('click', this.onClick); player.on('resolutionchange', videojs.bind(this, this.update));
this.on('touchstart', this.onClick);
if (options.initialySelected) {
this.showAsLabel();
this.selected(true);
this.addClass('vjs-selected');
} }
}, } );
showAsLabel: function() { ResolutionMenuItem.prototype.handleClick = function(event){
// Change menu button label to the label of this item if the menu button label is provided MenuItem.prototype.handleClick.call(this,event);
if(this.label) { this.player_.currentResolution(this.options_.label);
this.label.innerHTML = this.options_.label; };
} ResolutionMenuItem.prototype.update = function(){
}, var selection = this.player_.currentResolution();
onClick: function(customSourcePicker){ this.selected(this.options_.label === selection.label);
this.onClickListener(this); };
// Remember player state MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem);
var currentTime = this.player_.currentTime();
var isPaused = this.player_.paused();
this.showAsLabel();
// add .current class
this.addClass('vjs-selected');
// Hide bigPlayButton
if(!isPaused){
this.player_.bigPlayButton.hide();
}
if(typeof customSourcePicker !== 'function' &&
typeof this.options_.customSourcePicker === 'function'){
customSourcePicker = this.options_.customSourcePicker;
}
// Change player source and wait for loadeddata event, then play video
// loadedmetadata doesn't work right now for flash.
// Probably because of https://github.com/videojs/video-js-swf/issues/124
// If player preload is 'none' and then loadeddata not fired. So, we need timeupdate event for seek handle (timeupdate doesn't work properly with flash)
var handleSeekEvent = 'loadeddata';
if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') {
handleSeekEvent = 'timeupdate';
}
setSourcesSanitized(this.player_, this.src, this.options_.label, customSourcePicker).one(handleSeekEvent, function() {
this.player_.currentTime(currentTime);
this.player_.handleTechSeeked_();
if(!isPaused){
// Start playing and hide loadingSpinner (flash issue ?)
this.player_.play().handleTechSeeked_();
}
this.player_.trigger('resolutionchange');
});
}
});
/* /*
* Resolution menu button * Resolution menu button
*/ */
var MenuButton = videojs.getComponent('MenuButton'); var MenuButton = videojs.getComponent('MenuButton');
var ResolutionMenuButton = videojs.extend(MenuButton, { var ResolutionMenuButton = videojs.extend(MenuButton, {
constructor: function(player, options, settings, label){ constructor: function(player, options){
this.sources = options.sources; this.label = document.createElement('span');
this.label = label; options.label = 'Quality';
this.label.innerHTML = options.initialySelectedLabel;
// Sets this.player_, this.options_ and initializes the component // Sets this.player_, this.options_ and initializes the component
MenuButton.call(this, player, options, settings); MenuButton.call(this, player, options);
this.el().setAttribute('aria-label','Quality');
this.controlText('Quality'); this.controlText('Quality');
if(settings.dynamicLabel){ if(options.dynamicLabel){
this.el().appendChild(label); videojs.addClass(this.label, 'vjs-resolution-button-label');
} else { this.el().appendChild(this.label);
videojs.addClass(this.el(), 'vjs-menu-icon'); }else{
var staticLabel = document.createElement('span');
videojs.addClass(staticLabel, 'vjs-menu-icon');
this.el().appendChild(staticLabel);
} }
}, player.on('updateSources', videojs.bind( this, this.update ) );
createItems: function(){ }
var menuItems = []; } );
var labels = (this.sources && this.sources.label) || {}; ResolutionMenuButton.prototype.createItems = function(){
var onClickUnselectOthers = function(clickedItem) { var menuItems = [];
menuItems.map(function(item) { var labels = (this.sources && this.sources.label) || {};
item.selected(item === clickedItem);
item.removeClass('vjs-selected');
});
};
for (var key in labels) { // FIXME order is not guaranteed here.
if (labels.hasOwnProperty(key)) { for (var key in labels) {
menuItems.push(new ResolutionMenuItem( if (labels.hasOwnProperty(key)) {
this.player_, menuItems.push(new ResolutionMenuItem(
{ this.player_,
label: key, {
src: labels[key], label: key,
initialySelected: key === this.options_.initialySelectedLabel, src: labels[key],
customSourcePicker: this.options_.customSourcePicker selected: key === (this.currentSelection ? this.currentSelection.label : false)
}, })
onClickUnselectOthers, );
this.label)); }
// Store menu item for API calls }
menuItemsHolder[key] = menuItems[menuItems.length - 1]; return menuItems;
} };
} ResolutionMenuButton.prototype.update = function(){
return menuItems; this.sources = this.player_.getGroupedSrc();
} this.currentSelection = this.player_.currentResolution();
}); this.label.innerHTML = this.currentSelection ? this.currentSelection.label : '';
return MenuButton.prototype.update.call(this);
};
ResolutionMenuButton.prototype.buildCSSClass = function(){
return MenuButton.prototype.buildCSSClass.call( this ) + ' vjs-resolution-button';
};
MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton);
/** /**
* Initialize the plugin. * Initialize the plugin.
@ -158,10 +105,9 @@
videoJsResolutionSwitcher = function(options) { videoJsResolutionSwitcher = function(options) {
var settings = videojs.mergeOptions(defaults, options), var settings = videojs.mergeOptions(defaults, options),
player = this, player = this,
label = document.createElement('span'), groupedSrc = {},
groupedSrc = {}; currentSources = {},
currentResolutionState = {};
videojs.addClass(label, 'vjs-resolution-button-label');
/** /**
* Updates player sources or returns current source URL * Updates player sources or returns current source URL
@ -171,35 +117,65 @@
player.updateSrc = function(src){ player.updateSrc = function(src){
//Return current src if src is not given //Return current src if src is not given
if(!src){ return player.src(); } if(!src){ return player.src(); }
// Dispose old resolution menu button before adding new sources
if(player.controlBar.resolutionSwitcher){ // Sort sources
player.controlBar.resolutionSwitcher.dispose(); this.currentSources = src.sort(compareResolutions);
delete player.controlBar.resolutionSwitcher; this.groupedSrc = bucketSources(this.currentSources);
} // Pick one by default
//Sort sources var chosen = chooseSrc(this.groupedSrc, this.currentSources);
src = src.sort(compareResolutions); this.currentResolutionState = {
groupedSrc = bucketSources(src); label: chosen.label,
var choosen = chooseSrc(groupedSrc, src); sources: chosen.sources
var menuButton = new ResolutionMenuButton(player, { sources: groupedSrc, initialySelectedLabel: choosen.label , initialySelectedRes: choosen.res , customSourcePicker: settings.customSourcePicker}, settings, label);
videojs.addClass(menuButton.el(), 'vjs-resolution-button');
player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_);
player.controlBar.resolutionSwitcher.dispose = function(){
this.parentNode.removeChild(this);
}; };
return setSourcesSanitized(player, choosen.sources, choosen.label);
player.trigger('updateSources');
player.setSourcesSanitized(chosen.sources, chosen.label);
player.trigger('resolutionchange');
return player;
}; };
/** /**
* Returns current resolution or sets one when label is specified * Returns current resolution or sets one when label is specified
* @param {String} [label] label name * @param {String} [label] label name
* @param {Function} [customSourcePicker] custom function to choose source. Takes 3 arguments: player, sources, label. Must return player object. * @param {Function} [customSourcePicker] custom function to choose source. Takes 2 arguments: sources, label. Must return player object.
* @returns {Object} current resolution object {label: '', sources: []} if used as getter or player object if used as setter * @returns {Object} current resolution object {label: '', sources: []} if used as getter or player object if used as setter
*/ */
player.currentResolution = function(label, customSourcePicker){ player.currentResolution = function(label, customSourcePicker){
if(label == null) { return currentResolution; } if(label == null) { return this.currentResolutionState; }
if(menuItemsHolder[label] != null){
menuItemsHolder[label].onClick(customSourcePicker); // Lookup sources for label
if(!this.groupedSrc || !this.groupedSrc.label || !this.groupedSrc.label[label]){
return;
} }
var sources = this.groupedSrc.label[label];
// Remember player state
var currentTime = player.currentTime();
var isPaused = player.paused();
// Hide bigPlayButton
if(!isPaused){
this.player_.bigPlayButton.hide();
}
// Change player source and wait for loadeddata event, then play video
// loadedmetadata doesn't work right now for flash.
// Probably because of https://github.com/videojs/video-js-swf/issues/124
// If player preload is 'none' and then loadeddata not fired. So, we need timeupdate event for seek handle (timeupdate doesn't work properly with flash)
var handleSeekEvent = 'loadeddata';
if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') {
handleSeekEvent = 'timeupdate';
}
player
.setSourcesSanitized(sources, label, customSourcePicker || settings.customSourcePicker)
.one(handleSeekEvent, function() {
player.currentTime(currentTime);
player.handleTechSeeked_();
if(!isPaused){
// Start playing and hide loadingSpinner (flash issue ?)
player.play().handleTechSeeked_();
}
player.trigger('resolutionchange');
});
return player; return player;
}; };
@ -208,7 +184,21 @@
* @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
*/ */
player.getGroupedSrc = function(){ player.getGroupedSrc = function(){
return groupedSrc; return this.groupedSrc;
};
player.setSourcesSanitized = function(sources, label, customSourcePicker) {
this.currentResolutionState = {
label: label,
sources: sources
};
if(typeof customSourcePicker === 'function'){
return customSourcePicker(player, sources, label);
}
player.src(sources.map(function(src) {
return {src: src.src, type: src.type, res: src.res};
}));
return player;
}; };
/** /**
@ -278,75 +268,87 @@
return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]}; return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]};
} }
function initResolutionForYt(player){ function initResolutionForYt(player){
// Init resolution // Map youtube qualities names
player.tech_.ytPlayer.setPlaybackQuality('default'); var _yts = {
highres: {res: 1080, label: '1080', yt: 'highres'},
hd1080: {res: 1080, label: '1080', yt: 'hd1080'},
hd720: {res: 720, label: '720', yt: 'hd720'},
large: {res: 480, label: '480', yt: 'large'},
medium: {res: 360, label: '360', yt: 'medium'},
small: {res: 240, label: '240', yt: 'small'},
tiny: {res: 144, label: '144', yt: 'tiny'},
auto: {res: 0, label: 'auto', yt: 'auto'}
};
// Overwrite default sourcePicker function
var _customSourcePicker = function(_player, _sources, _label){
// Note that setPlayebackQuality is a suggestion. YT does not always obey it.
player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt);
player.trigger('updateSources');
return player;
};
settings.customSourcePicker = _customSourcePicker;
// Capture events // Init resolution
player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(){ player.tech_.ytPlayer.setPlaybackQuality('auto');
player.trigger('resolutionchange');
});
// We must wait for play event // This is triggered when the resolution actually changes
player.one('play', function(){ player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(event){
var qualities = player.tech_.ytPlayer.getAvailableQualityLevels(); for(var res in _yts) {
// Map youtube qualities names if(res.yt === event.data) {
var _yts = { player.currentResolution(res.label, _customSourcePicker);
highres: {res: 1080, label: '1080', yt: 'highres'}, return;
hd1080: {res: 1080, label: '1080', yt: 'hd1080'}, }
hd720: {res: 720, label: '720', yt: 'hd720'}, }
large: {res: 480, label: '480', yt: 'large'}, });
medium: {res: 360, label: '360', yt: 'medium'},
small: {res: 240, label: '240', yt: 'small'},
tiny: {res: 144, label: '144', yt: 'tiny'},
auto: {res: 0, label: 'auto', yt: 'default'}
};
var _sources = []; // We must wait for play event
player.one('play', function(){
var qualities = player.tech_.ytPlayer.getAvailableQualityLevels();
var _sources = [];
qualities.map(function(q){ qualities.map(function(q){
_sources.push({ _sources.push({
src: player.src().src, src: player.src().src,
type: player.src().type, type: player.src().type,
label: _yts[q].label, label: _yts[q].label,
res: _yts[q].res, res: _yts[q].res,
_yt: _yts[q].yt _yt: _yts[q].yt
}); });
}); });
groupedSrc = bucketSources(_sources); player.groupedSrc = bucketSources(_sources);
var chosen = {label: 'auto', res: 0, sources: player.groupedSrc.label.auto};
// Overwrite defualt sourcePicer function this.currentResolutionState = {
var _customSourcePicker = function(_player, _sources, _label){ label: chosen.label,
player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt); sources: chosen.sources
return player; };
};
var choosen = {label: 'auto', res: 0, sources: groupedSrc.label.auto}; player.trigger('updateSources');
var menuButton = new ResolutionMenuButton(player, { player.setSourcesSanitized(chosen.sources, chosen.label, _customSourcePicker);
sources: groupedSrc, });
initialySelectedLabel: choosen.label, }
initialySelectedRes: choosen.res,
customSourcePicker: _customSourcePicker
}, settings, label);
menuButton.el().classList.add('vjs-resolution-button'); player.ready(function(){
player.controlBar.resolutionSwitcher = player.controlBar.addChild(menuButton); if( settings.ui ) {
}); var menuButton = new ResolutionMenuButton(player, settings);
} player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_);
player.controlBar.resolutionSwitcher.dispose = function(){
this.parentNode.removeChild(this);
};
}
if(player.options_.sources.length > 1){
// tech: Html5 and Flash
// Create resolution switcher for videos form <source> tag inside <video>
player.updateSrc(player.options_.sources);
}
player.ready(function(){ if(player.techName_ === 'Youtube'){
if(player.options_.sources.length > 1){ // tech: YouTube
// tech: Html5 and Flash initResolutionForYt(player);
// Create resolution switcher for videos form <source> tag inside <video> }
player.updateSrc(player.options_.sources); });
}
if(player.techName_ === 'Youtube'){
// tech: YouTube
initResolutionForYt(player);
}
});
}; };