vendor: 14,558 bytes, lines 1-398
1/*! videojs-resolution-switcher - 2015-7-26 2 * Copyright (c) 2016 Kasper Moskwiak 3 * Modified by Pierre Kraft and Derk-Jan Hartman 4 * Licensed under the Apache-2.0 license. */ 5 6(function() { 7 /* jshint eqnull: true*/ 8 /* global require */ 9 'use strict'; 10 var videojs = null; 11 if(typeof window.videojs === 'undefined' && typeof require === 'function') { 12 videojs = require('video.js'); 13 } else { 14 videojs = window.videojs; 15 } 16 17 (function(window, videojs) { 18 var videoJsResolutionSwitcher, 19 defaults = { 20 ui: true 21 }; 22 23 /* 24 * Resolution menu item 25 */ 26 var MenuItem = videojs.getComponent('MenuItem'); 27 var ResolutionMenuItem = videojs.extend(MenuItem, { 28 constructor: function(player, options){ 29 options.selectable = true; 30 // Sets this.player_, this.options_ and initializes the component 31 MenuItem.call(this, player, options); 32 this.src = options.src; 33 34 player.on('resolutionchange', videojs.bind(this, this.update)); 35 } 36 } ); 37 ResolutionMenuItem.prototype.handleClick = function(event){ 38 MenuItem.prototype.handleClick.call(this,event); 39 this.player_.currentResolution(this.options_.label); 40 }; 41 ResolutionMenuItem.prototype.update = function(){ 42 if (!this.player_) { 43 return; 44 } 45 var selection = this.player_.currentResolution(); 46 this.selected(this.options_.label === selection.label); 47 }; 48 MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem); 49 50 /* 51 * Resolution menu button 52 */ 53 var MenuButton = videojs.getComponent('MenuButton'); 54 var ResolutionMenuButton = videojs.extend(MenuButton, { 55 constructor: function(player, options){ 56 this.label = document.createElement('span'); 57 options.label = 'Quality'; 58 // Sets this.player_, this.options_ and initializes the component 59 MenuButton.call(this, player, options); 60 this.el().setAttribute('aria-label','Quality'); 61 this.controlText('Quality'); 62 63 if(options.dynamicLabel){ 64 videojs.dom.addClass(this.label, 'vjs-resolution-button-label'); 65 this.el().children[0].appendChild(this.label); 66 }else{ 67 var staticLabel = document.createElement('span'); 68 videojs.addClass(staticLabel, 'vjs-menu-icon'); 69 this.el().children[0].appendChild(staticLabel); 70 } 71 player.on('updateSources', videojs.bind( this, this.update ) ); 72 player.on('resolutionchange', videojs.bind( this, this.updateLabel ) ); 73 }, 74 update: function(){ 75 if (!this.player_) { 76 return; 77 } 78 this.sources = this.player_.getGroupedSrc(); 79 this.currentSelection = this.player_.currentResolution(); 80 this.label.innerHTML = this.currentSelection ? this.currentSelection.label : ''; 81 return MenuButton.prototype.update.call(this); 82 }, 83 updateLabel: function(){ 84 if (!this.player_) { 85 return; 86 } 87 this.currentSelection = this.player_.currentResolution(); 88 this.label.innerHTML = this.currentSelection ? this.currentSelection.label : ''; 89 } 90 } ); 91 ResolutionMenuButton.prototype.createItems = function(){ 92 var menuItems = []; 93 var labels = (this.sources && this.sources.label) || {}; 94 95 // FIXME order is not guaranteed here. 96 for (var key in labels) { 97 if (labels.hasOwnProperty(key)) { 98 menuItems.push(new ResolutionMenuItem( 99 this.player_, 100 { 101 label: key, 102 src: labels[key], 103 selected: key === (this.currentSelection ? this.currentSelection.label : false) 104 }) 105 ); 106 } 107 } 108 return menuItems; 109 }; 110 ResolutionMenuButton.prototype.buildCSSClass = function(){ 111 return MenuButton.prototype.buildCSSClass.call( this ) + ' vjs-resolution-button'; 112 }; 113 MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton); 114 115 /** 116 * Initialize the plugin. 117 * @param {object} [options] configuration for the plugin 118 */ 119 videoJsResolutionSwitcher = function(options) { 120 var settings = videojs.mergeOptions(defaults, options), 121 player = this, 122 groupedSrc = {}, 123 currentSources = {}, 124 currentResolutionState = {}; 125 126 /** 127 * Updates player sources or returns current source URL 128 * @param {Array} [src] array of sources [{src: '', type: '', label: '', res: ''}] 129 * @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources 130 */ 131 player.updateSrc = function(src){ 132 //Return current src if src is not given 133 if(!src){ return player.src(); } 134 135 // Only add those sources which we can (maybe) play 136 src = src.filter( function(source) { 137 try { 138 return ( player.canPlayType( source.type ) !== '' ); 139 } catch (e) { 140 // If a Tech doesn't yet have canPlayType just add it 141 return true; 142 } 143 }); 144 //Sort sources 145 this.currentSources = src.sort(compareResolutions); 146 this.groupedSrc = bucketSources(this.currentSources); 147 // Pick one by default 148 var chosen = chooseSrc(this.groupedSrc, this.currentSources, player.currentDimension('width')); 149 this.currentResolutionState = { 150 label: chosen.label, 151 sources: chosen.sources 152 }; 153 154 player.trigger('updateSources'); 155 player.setSourcesSanitized(chosen.sources, chosen.label); 156 player.trigger('resolutionchange'); 157 return player; 158 }; 159 /** 160 * Add a new source 161 * @param {Array} [src] source info [{src: '', type: '', label: '', res: ''}] 162 * @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources 163 */ 164 player.addSrc = function(src){ 165 //Return current src if src is not given 166 if(!src){ return player.src(); } 167 168 var source_list = this.currentSources; 169 if (!Array.isArray(source_list)) { 170 source_list = [src]; 171 } else { 172 source_list.push(src); 173 } 174 return player.updateSrc(source_list); 175 }; 176 177 /** 178 * Returns current resolution or sets one when label is specified 179 * @param {String} [label] label name 180 * @param {Function} [customSourcePicker] custom function to choose source. Takes 2 arguments: sources, label. Must return player object. 181 * @returns {Object} current resolution object {label: '', sources: []} if used as getter or player object if used as setter 182 */ 183 player.currentResolution = function(label, customSourcePicker){ 184 if(label == null) { return this.currentResolutionState; } 185 186 // Lookup sources for label 187 if(!this.groupedSrc || !this.groupedSrc.label || !this.groupedSrc.label[label]){ 188 return; 189 } 190 var sources = this.groupedSrc.label[label]; 191 // Remember player state 192 var currentTime = player.currentTime(); 193 var isPaused = player.paused(); 194 195 // Hide bigPlayButton 196 if(!isPaused && this.player_.options_.bigPlayButton){ 197 this.player_.bigPlayButton.hide(); 198 } 199 200 // Change player source and wait for loadeddata event, then play video 201 // loadedmetadata doesn't work right now for flash. 202 // Probably because of https://github.com/videojs/video-js-swf/issues/124 203 // 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) 204 var handleSeekEvent = 'loadeddata'; 205 if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') { 206 handleSeekEvent = 'timeupdate'; 207 } 208 player 209 .setSourcesSanitized(sources, label, customSourcePicker || settings.customSourcePicker) 210 .one(handleSeekEvent, function() { 211 player.currentTime(currentTime); 212 if(!isPaused){ 213 // Start playing and hide loadingSpinner (flash issue ?) 214 player.play(); 215 } 216 player.handleTechSeeked_(); 217 player.trigger('resolutionchange'); 218 }); 219 return player; 220 }; 221 222 /** 223 * Returns grouped sources by label, resolution and type 224 * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } 225 */ 226 player.getGroupedSrc = function(){ 227 return this.groupedSrc; 228 }; 229 230 player.setSourcesSanitized = function(sources, label, customSourcePicker) { 231 this.currentResolutionState = { 232 label: label, 233 sources: sources 234 }; 235 if(typeof customSourcePicker === 'function'){ 236 return customSourcePicker(player, sources, label); 237 } 238 player.src(sources.map(function(src) { 239 return {src: src.src, type: src.type, res: src.res}; 240 })); 241 return player; 242 }; 243 244 /** 245 * Method used for sorting list of sources 246 * @param {Object} a - source object with res property 247 * @param {Object} b - source object with res property 248 * @returns {Number} result of comparation 249 */ 250 function compareResolutions(a, b){ 251 if(!a.res || !b.res){ return 0; } 252 return (+b.res)-(+a.res); 253 } 254 255 /** 256 * Group sources by label, resolution and type 257 * @param {Array} src Array of sources 258 * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } 259 */ 260 function bucketSources(src){ 261 var resolutions = { 262 label: {}, 263 res: {}, 264 type: {} 265 }; 266 src.map(function(source) { 267 initResolutionKey(resolutions, 'label', source); 268 initResolutionKey(resolutions, 'res', source); 269 initResolutionKey(resolutions, 'type', source); 270 271 appendSourceToKey(resolutions, 'label', source); 272 appendSourceToKey(resolutions, 'res', source); 273 appendSourceToKey(resolutions, 'type', source); 274 }); 275 return resolutions; 276 } 277 278 function initResolutionKey(resolutions, key, source) { 279 if(resolutions[key][source[key]] == null) { 280 resolutions[key][source[key]] = []; 281 } 282 } 283 284 function appendSourceToKey(resolutions, key, source) { 285 resolutions[key][source[key]].push(source); 286 } 287 288 /** 289 * Choose the highest source that is lower than the current player width 290 * @param {Object} groupedSrc {res: { key: [] }} 291 * @param {Array} src Array of sources sorted by resolution used to find high and low res 292 * @param {int} player_width Width of the player 293 * @returns {Object} {res: string, sources: []} 294 */ 295 function chooseSrc(groupedSrc, src, player_width){ 296 var last_index = src.length - 1, 297 selectedRes = src[last_index].res, // default to the lowest resolution 298 selectedLabel = src[last_index].label, 299 i; 300 for (i in src) { 301 if (player_width > src[i].res) { 302 selectedRes = src[i].res; 303 selectedLabel = src[i].label; 304 break; 305 } 306 } 307 308 return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]}; 309 } 310 311 function initResolutionForYt(player){ 312 // Map youtube qualities names 313 var _yts = { 314 highres: {res: 1080, label: '1080', yt: 'highres'}, 315 hd1080: {res: 1080, label: '1080', yt: 'hd1080'}, 316 hd720: {res: 720, label: '720', yt: 'hd720'}, 317 large: {res: 480, label: '480', yt: 'large'}, 318 medium: {res: 360, label: '360', yt: 'medium'}, 319 small: {res: 240, label: '240', yt: 'small'}, 320 tiny: {res: 144, label: '144', yt: 'tiny'}, 321 auto: {res: 0, label: 'auto', yt: 'auto'} 322 }; 323 // Overwrite default sourcePicker function 324 var _customSourcePicker = function(_player, _sources, _label){ 325 // Note that setPlayebackQuality is a suggestion. YT does not always obey it. 326 player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt); 327 player.trigger('updateSources'); 328 return player; 329 }; 330 settings.customSourcePicker = _customSourcePicker; 331 332 // Init resolution 333 player.tech_.ytPlayer.setPlaybackQuality('auto'); 334 335 // This is triggered when the resolution actually changes 336 player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(event){ 337 for(var res in _yts) { 338 if(res.yt === event.data) { 339 player.currentResolution(res.label, _customSourcePicker); 340 return; 341 } 342 } 343 }); 344 345 // We must wait for play event 346 player.one('play', function(){ 347 var qualities = player.tech_.ytPlayer.getAvailableQualityLevels(); 348 var _sources = []; 349 350 qualities.map(function(q){ 351 _sources.push({ 352 src: player.src().src, 353 type: player.src().type, 354 label: _yts[q].label, 355 res: _yts[q].res, 356 _yt: _yts[q].yt 357 }); 358 }); 359 360 player.groupedSrc = bucketSources(_sources); 361 var chosen = {label: 'auto', res: 0, sources: player.groupedSrc.label.auto}; 362 363 this.currentResolutionState = { 364 label: chosen.label, 365 sources: chosen.sources 366 }; 367 368 player.trigger('updateSources'); 369 player.setSourcesSanitized(chosen.sources, chosen.label, _customSourcePicker); 370 }); 371 } 372 373 player.ready(function(){ 374 if( settings.ui ) { 375 var menuButton = new ResolutionMenuButton(player, settings); 376 player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_); 377 player.controlBar.resolutionSwitcher.dispose = function(){ 378 this.parentNode.removeChild(this); 379 }; 380 } 381 if(player.options_.sources.length > 1){ 382 // tech: Html5 and Flash 383 // Create resolution switcher for videos form <source> tag inside <video> 384 player.updateSrc(player.options_.sources); 385 } 386 387 if(player.techName_ === 'Youtube'){ 388 // tech: YouTube 389 initResolutionForYt(player); 390 } 391 }); 392 393 }; 394 395 // register the plugin 396 videojs.registerPlugin('videoJsResolutionSwitcher', videoJsResolutionSwitcher); 397 })(window, videojs); 398})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.