resolve conflicts

This commit is contained in:
Kasper Moskwiak 2016-07-09 18:55:24 +02:00
commit b928c07378
3 changed files with 213 additions and 208 deletions

View file

@ -59,6 +59,7 @@
width: 1000,
plugins: {
videoJsResolutionSwitcher: {
ui: true,
default: 'low', // Default resolution [{Number}, 'low', 'high'],
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';
font-family: VideoJS;
font-weight: normal;
font-style: normal;
font-size: 1.8em;
line-height: 1.67em;
}
.vjs-resolution-button .vjs-resolution-button-label {

View file

@ -1,6 +1,6 @@
/*! videojs-resolution-switcher - 2015-7-26
* Copyright (c) 2016 Kasper Moskwiak
* Modified by Pierre Kraft
* Modified by Pierre Kraft and Derk-Jan Hartman
* Licensed under the Apache-2.0 license. */
(function() {
@ -15,122 +15,64 @@
}
(function(window, videojs) {
var defaults = {},
videoJsResolutionSwitcher,
currentResolution = {}, // stores current resolution
menuItemsHolder = {}; // stores menuItems
function setSourcesSanitized(player, sources, label, customSourcePicker) {
currentResolution = {
label: label,
sources: sources
var videoJsResolutionSwitcher,
defaults = {
ui: true
};
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
*/
var MenuItem = videojs.getComponent('MenuItem');
var ResolutionMenuItem = videojs.extend(MenuItem, {
constructor: function(player, options, onClickListener, label){
this.onClickListener = onClickListener;
this.label = label;
constructor: function(player, options){
options.selectable = true;
// Sets this.player_, this.options_ and initializes the component
MenuItem.call(this, player, options);
this.src = options.src;
this.on('click', this.onClick);
this.on('touchstart', this.onClick);
if (options.initialySelected) {
this.showAsLabel();
this.selected(true);
this.addClass('vjs-selected');
player.on('resolutionchange', videojs.bind(this, this.update));
}
},
showAsLabel: function() {
// Change menu button label to the label of this item if the menu button label is provided
if(this.label) {
this.label.innerHTML = this.options_.label;
}
},
onClick: function(customSourcePicker){
this.onClickListener(this);
// Remember player state
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');
});
}
});
} );
ResolutionMenuItem.prototype.handleClick = function(event){
MenuItem.prototype.handleClick.call(this,event);
this.player_.currentResolution(this.options_.label);
};
ResolutionMenuItem.prototype.update = function(){
var selection = this.player_.currentResolution();
this.selected(this.options_.label === selection.label);
};
MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem);
/*
* Resolution menu button
*/
var MenuButton = videojs.getComponent('MenuButton');
var ResolutionMenuButton = videojs.extend(MenuButton, {
constructor: function(player, options, settings, label){
this.sources = options.sources;
this.label = label;
this.label.innerHTML = options.initialySelectedLabel;
constructor: function(player, options){
this.label = document.createElement('span');
options.label = 'Quality';
// 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');
if(settings.dynamicLabel){
this.el().appendChild(label);
} else {
videojs.addClass(this.el(), 'vjs-menu-icon');
if(options.dynamicLabel){
videojs.addClass(this.label, 'vjs-resolution-button-label');
this.el().appendChild(this.label);
}else{
var staticLabel = document.createElement('span');
videojs.addClass(staticLabel, 'vjs-menu-icon');
this.el().appendChild(staticLabel);
}
},
createItems: function(){
player.on('updateSources', videojs.bind( this, this.update ) );
}
} );
ResolutionMenuButton.prototype.createItems = function(){
var menuItems = [];
var labels = (this.sources && this.sources.label) || {};
var onClickUnselectOthers = function(clickedItem) {
menuItems.map(function(item) {
item.selected(item === clickedItem);
item.removeClass('vjs-selected');
});
};
// FIXME order is not guaranteed here.
for (var key in labels) {
if (labels.hasOwnProperty(key)) {
menuItems.push(new ResolutionMenuItem(
@ -138,18 +80,23 @@
{
label: key,
src: labels[key],
initialySelected: key === this.options_.initialySelectedLabel,
customSourcePicker: this.options_.customSourcePicker
},
onClickUnselectOthers,
this.label));
// Store menu item for API calls
menuItemsHolder[key] = menuItems[menuItems.length - 1];
selected: key === (this.currentSelection ? this.currentSelection.label : false)
})
);
}
}
return menuItems;
}
});
};
ResolutionMenuButton.prototype.update = function(){
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.
@ -158,10 +105,9 @@
videoJsResolutionSwitcher = function(options) {
var settings = videojs.mergeOptions(defaults, options),
player = this,
label = document.createElement('span'),
groupedSrc = {};
videojs.addClass(label, 'vjs-resolution-button-label');
groupedSrc = {},
currentSources = {},
currentResolutionState = {};
/**
* Updates player sources or returns current source URL
@ -171,35 +117,65 @@
player.updateSrc = function(src){
//Return current src if src is not given
if(!src){ return player.src(); }
// Dispose old resolution menu button before adding new sources
if(player.controlBar.resolutionSwitcher){
player.controlBar.resolutionSwitcher.dispose();
delete player.controlBar.resolutionSwitcher;
}
//Sort sources
src = src.sort(compareResolutions);
groupedSrc = bucketSources(src);
var choosen = chooseSrc(groupedSrc, src);
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);
// Sort sources
this.currentSources = src.sort(compareResolutions);
this.groupedSrc = bucketSources(this.currentSources);
// Pick one by default
var chosen = chooseSrc(this.groupedSrc, this.currentSources);
this.currentResolutionState = {
label: chosen.label,
sources: chosen.sources
};
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
* @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
*/
player.currentResolution = function(label, customSourcePicker){
if(label == null) { return currentResolution; }
if(menuItemsHolder[label] != null){
menuItemsHolder[label].onClick(customSourcePicker);
if(label == null) { return this.currentResolutionState; }
// 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;
};
@ -208,7 +184,21 @@
* @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
*/
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;
};
/**
@ -279,17 +269,6 @@
}
function initResolutionForYt(player){
// Init resolution
player.tech_.ytPlayer.setPlaybackQuality('default');
// Capture events
player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(){
player.trigger('resolutionchange');
});
// We must wait for play event
player.one('play', function(){
var qualities = player.tech_.ytPlayer.getAvailableQualityLevels();
// Map youtube qualities names
var _yts = {
highres: {res: 1080, label: '1080', yt: 'highres'},
@ -299,9 +278,33 @@
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'}
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;
// Init resolution
player.tech_.ytPlayer.setPlaybackQuality('auto');
// This is triggered when the resolution actually changes
player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(event){
for(var res in _yts) {
if(res.yt === event.data) {
player.currentResolution(res.label, _customSourcePicker);
return;
}
}
});
// We must wait for play event
player.one('play', function(){
var qualities = player.tech_.ytPlayer.getAvailableQualityLevels();
var _sources = [];
qualities.map(function(q){
@ -314,28 +317,27 @@
});
});
groupedSrc = bucketSources(_sources);
player.groupedSrc = bucketSources(_sources);
var chosen = {label: 'auto', res: 0, sources: player.groupedSrc.label.auto};
// Overwrite defualt sourcePicer function
var _customSourcePicker = function(_player, _sources, _label){
player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt);
return player;
this.currentResolutionState = {
label: chosen.label,
sources: chosen.sources
};
var choosen = {label: 'auto', res: 0, sources: groupedSrc.label.auto};
var menuButton = new ResolutionMenuButton(player, {
sources: groupedSrc,
initialySelectedLabel: choosen.label,
initialySelectedRes: choosen.res,
customSourcePicker: _customSourcePicker
}, settings, label);
menuButton.el().classList.add('vjs-resolution-button');
player.controlBar.resolutionSwitcher = player.controlBar.addChild(menuButton);
player.trigger('updateSources');
player.setSourcesSanitized(chosen.sources, chosen.label, _customSourcePicker);
});
}
player.ready(function(){
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>