diff options
Diffstat (limited to 'librevideojs/selector/video-quality-selector.js')
-rw-r--r-- | librevideojs/selector/video-quality-selector.js | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/librevideojs/selector/video-quality-selector.js b/librevideojs/selector/video-quality-selector.js new file mode 100644 index 0000000..9e3184f --- /dev/null +++ b/librevideojs/selector/video-quality-selector.js @@ -0,0 +1,439 @@ +/*! + * @source: + * @base: https://github.com/dominic-p/videojs-resolution-selector + * + * @licstart The following is the entire license notice for the + * JavaScript code in this page. + * + * Copyleft 2016 Jesus E. M. + * + * The JavaScript code in this page is free software: you can + * redistribute it and/or modify it under the terms of the GNU + * General Public License (GNU GPL) as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. The code is distributed WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. + * + * As additional permission under GNU GPL version 3 section 7, you + * may distribute non-source (e.g., minimized or compacted) forms of + * that code without the copy of the GNU GPL normally required by + * section 4, provided you include this license notice and a URL + * through which recipients can access the Corresponding Source. + * + * @licend The above is the entire license notice + * for the JavaScript code in this page. + * +*/ +/** + * LibreVideoJS Resolution Selector + * + * This plugin for LibreVideo.js adds a resolution selector option + * to the toolbar. Usage: + * + * <video> + * <source data-res="480" src="..." /> + * <source data-res="240" src="..." /> + * </video> + */ + + (function( _V_ ) { + + /*********************************************************************************** + * Define some helper functions + ***********************************************************************************/ + var methods = { + + /** + * Utility function for merging 2 objects recursively. It treats + * arrays like plain objects and it relies on a for...in loop which will + * break if the Object prototype is messed with. + * + * @param (object) destination The object to modify and return + * @param (object) source The object to use to overwrite the first + * object + * + * @returns (object) The modified first object is returned + */ + extend : function( destination, source ) { + + for ( var prop in source ) { + + // Sanity check + if ( ! source.hasOwnProperty( prop ) ) { continue; } + + // Enable recursive (deep) object extension + if ( typeof source[prop] == 'object' && null !== source[prop] ) { + + destination[prop] = methods.extend( destination[prop] || {}, source[prop] ); + + } else { + + destination[prop] = source[prop]; + } + } + + return destination; + }, + + /** + * In a future version, this can be made more intelligent, + * but for now, we'll just add a "p" at the end if we are passed + * numbers. + * + * @param (string) res The resolution to make a label for + * + * @returns (string) The label text string + */ + res_label : function( res ) { + + return ( /^\d+$/.test( res ) ) ? res + 'p' : res; + }, + + matchResolution: function(resStack, res) { + }, + + /** + * returns a dummy object that implements the basic get/set + * functionality of the Cookies library. Used in the case where + * the Cookies library is not present + */ + buildCookiesDummy: function() { + return { + get: function(key) { + return ""; + }, + set: function(key, val) { + return false; + } + }; + } + }; + + /*********************************************************************************** + * Setup our resolution menu items + ***********************************************************************************/ + _V_.ResolutionMenuItem = _V_.MenuItem.extend({ + + /** @constructor */ + init : function( player, options ){ + + // Modify options for parent MenuItem class's init. + options.label = methods.res_label( options.res ); + options.selected = ( options.res.toString() === player.getCurrentRes().toString() ); + + // Call the parent constructor + _V_.MenuItem.call( this, player, options ); + + // Store the resolution as a call property + this.resolution = options.res; + + // Register our click handler + this.on( 'click', this.onClick ); + + // Toggle the selected class whenever the resolution changes + player.on( 'changeRes', _V_.bind( this, function() { + + if ( this.resolution == player.getCurrentRes() ) { + + this.selected( true ); + + } else { + + this.selected( false ); + } + })); + } + }); + + // Handle clicks on the menu items + _V_.ResolutionMenuItem.prototype.onClick = function() { + + var player = this.player(), + video_el = player.el().firstChild, + current_time = player.currentTime(), + is_paused = player.paused(), + button_nodes = player.controlBar.resolutionSelector.el().firstChild.children, + button_node_count = button_nodes.length; + + // Do nothing if we aren't changing resolutions + if ( player.getCurrentRes() == this.resolution ) { return; } + + // Make sure the loadedmetadata event will fire + if ( 'none' == video_el.preload ) { video_el.preload = 'metadata'; } + + // Change the source and make sure we don't start the video over + player.src( player.availableRes[this.resolution] ).one( 'loadedmetadata', function() { + + player.currentTime( current_time ); + + if ( ! is_paused ) { player.play(); } + }); + + // Save the newly selected resolution in our player options property + player.currentRes = this.resolution; + + // Update the button text + while ( button_node_count > 0 ) { + + button_node_count--; + + if ( 'librevjs-current-res' == button_nodes[button_node_count].className ) { + + button_nodes[button_node_count].innerHTML = methods.res_label( this.resolution ); + break; + } + } + + // Update the classes to reflect the currently selected resolution + player.trigger( 'changeRes' ); + }; + + /*********************************************************************************** + * Setup our resolution menu title item + ***********************************************************************************/ + _V_.ResolutionTitleMenuItem = _V_.MenuItem.extend({ + + init : function( player, options ) { + + // Call the parent constructor + _V_.MenuItem.call( this, player, options ); + + // No click handler for the menu title + this.off( 'click' ); + } + }); + + /*********************************************************************************** + * Define our resolution selector button + ***********************************************************************************/ + _V_.ResolutionSelector = _V_.MenuButton.extend({ + + /** @constructor */ + init : function( player, options ) { + + // Add our list of available resolutions to the player object + player.availableRes = options.available_res; + + // Call the parent constructor + _V_.MenuButton.call( this, player, options ); + } + }); + + // Create a menu item for each available resolution + _V_.ResolutionSelector.prototype.createItems = function() { + + var player = this.player(), + items = [], + current_res; + + // Add the menu title item + items.push( new _V_.ResolutionTitleMenuItem( player, { + + el : _V_.Component.prototype.createEl( 'li', { + + className : 'librevjs-menu-title librevjs-res-menu-title', + innerHTML : 'Calidad' + }) + })); + + // Add an item for each available resolution + for ( current_res in player.availableRes ) { + + // Don't add an item for the length attribute + if ( 'length' == current_res ) { continue; } + + items.push( new _V_.ResolutionMenuItem( player, { + res : current_res + })); + } + + // Sort the available resolutions in descending order + items.sort(function( a, b ) { + + if ( typeof a.resolution == 'undefined' ) { + + return -1; + + } else { + + return parseInt( b.resolution ) - parseInt( a.resolution ); + } + }); + + return items; + }; + + /*********************************************************************************** + * Register the plugin with cliplibrejs, main plugin function + ***********************************************************************************/ + _V_.plugin( 'resolutionSelector', function( options ) { + + // Only enable the plugin on HTML5 videos + if ( ! this.el().firstChild.canPlayType ) { return; } + + var player = this, + sources = player.options().sources, + i = sources.length, + j, + found_type, + + // Override default options with those provided + settings = methods.extend({ + + default_res : '', // (string) The resolution that should be selected by default ( '480' or '480,1080,240' ) + force_types : false // (array) List of media types. If passed, we need to have source for each type in each resolution or that resolution will not be an option + + }, options || {} ), + + available_res = { length : 0 }, + current_res, + resolutionSelector, + + // Split default resolutions if set and valid, otherwise default to an empty array + default_resolutions = ( settings.default_res && typeof settings.default_res == 'string' ) ? settings.default_res.split( ',' ) : [], + cookieNamespace = 'cliplibrejs.resolutionSelector', + resCookieName = cookieNamespace + '.res', + cookieRef = (typeof(Cookies) === "function") ? Cookies : methods.buildCookiesDummy(); + + // Get all of the available resoloutions + while ( i > 0 ) { + + i--; + + // Skip sources that don't have data-res attributes + if ( ! sources[i]['data-res'] ) { continue; } + + current_res = sources[i]['data-res']; + + if ( typeof available_res[current_res] !== 'object' ) { + + available_res[current_res] = []; + available_res.length++; + } + + available_res[current_res].push( sources[i] ); + } + + // Check for forced types + if ( settings.force_types ) { + + // Loop through all available resoultions + for ( current_res in available_res ) { + + // Don't count the length property as a resolution + if ( 'length' == current_res ) { continue; } + + i = settings.force_types.length; + + // For each resolution loop through the required types + while ( i > 0 ) { + + i--; + + j = available_res[current_res].length; + found_types = 0; + + // For each required type loop through the available sources to check if its there + while ( j > 0 ) { + + j--; + + if ( settings.force_types[i] === available_res[current_res][j].type ) { + + found_types++; + } + } // End loop through current resolution sources + + if ( found_types < settings.force_types.length ) { + + delete available_res[current_res]; + available_res.length--; + break; + } + } // End loop through required types + } // End loop through resolutions + } + + // Make sure we have at least 2 available resolutions before we add the button + if ( available_res.length < 2 ) { return; } + + var resCookie = cookieRef.get(resCookieName) + + if (resCookie) { + // rebuild the default_resolutions stack with the cookie's resolution on top + default_resolutions = [resCookie].concat(default_resolutions); + } + + // Loop through the choosen default resolutions if there were any + for ( i = 0; i < default_resolutions.length; i++ ) { + // Set the video to start out with the first available default res + if ( available_res[default_resolutions[i]] ) { + + player.src( available_res[default_resolutions[i]] ); + player.currentRes = default_resolutions[i]; + break; + } + } + + // Helper function to get the current resolution + player.getCurrentRes = function() { + + if ( typeof player.currentRes !== 'undefined' ) { + + return player.currentRes; + + } else { + + try { + + return res = player.options().sources[0]['data-res']; + + } catch(e) { + + return ''; + } + } + }; + + // Get the started resolution + current_res = player.getCurrentRes(); + + if ( current_res ) { current_res = methods.res_label( current_res ); } + + // Add the resolution selector button + resolutionSelector = new _V_.ResolutionSelector( player, { + + el : _V_.Component.prototype.createEl( null, { + + className : 'librevjs-res-button librevjs-menu-button librevjs-control', + innerHTML : '<div class="librevjs-control-content"><span class="librevjs-current-res">' + ( current_res || 'Quality' ) + '</span></div>', + role : 'button', + 'aria-live' : 'polite', // let the screen reader user know that the text of the button may change + tabIndex : 0 + + }), + available_res : available_res + }); + + // Attach an event to remember previous res selection via cookie + this.on('changeRes', function() { + cookieRef.set(resCookieName, player.getCurrentRes()); + }); + + // Attach an event to update player.src once on loadedmetadata + // if a resolution was previously set + this.one('loadedmetadata', function() { + var resCookie = cookieRef.get(resCookieName); + + if (resCookie) { + player.src(player.availableRes[resCookie]); + player.currentRes = resCookie; + player.trigger( 'changeRes' ); + } + }); + + // Add the button to the control bar object and the DOM + player.controlBar.resolutionSelector = player.controlBar.addChild( resolutionSelector ); + }); + })( cliplibrejs ); |