1/*! videojs-resolution-switcher - 2015-7-26 2 * Copyright (c) 2016 Kasper Moskwiak 3 * Modified by Pierre Kraft 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 19 20 var defaults = {}, 21 videoJsResolutionSwitcher, 22 currentResolution = {}, // stores current resolution 23 menuItemsHolder = {}; // stores menuItems 24 25 function setSourcesSanitized(player, sources, label, customSourcePicker) { 26 currentResolution = { 27 label: label, 28 sources: sources 29 }; 30 if(typeof customSourcePicker === 'function'){ 31 return customSourcePicker(player, sources, label); 32 } 33 return player.src(sources.map(function(src) { 34 return {src: src.src, type: src.type, res: src.res}; 35 })); 36 } 37 38 /* 39 * Resolution menu item 40 */ 41 var MenuItem = videojs.getComponent('MenuItem'); 42 var ResolutionMenuItem = videojs.extend(MenuItem, { 43 constructor: function(player, options, onClickListener, label){ 44 this.onClickListener = onClickListener; 45 this.label = label; 46 // Sets this.player_, this.options_ and initializes the component 47 MenuItem.call(this, player, options); 48 this.src = options.src; 49 50 this.on('click', this.onClick); 51 this.on('touchstart', this.onClick); 52 53 if (options.initialySelected) { 54 this.showAsLabel(); 55 this.selected(true); 56 57 this.addClass('vjs-selected'); 58 } 59 }, 60 showAsLabel: function() { 61 // Change menu button label to the label of this item if the menu button label is provided 62 if(this.label) { 63 this.label.innerHTML = this.options_.label; 64 } 65 }, 66 onClick: function(customSourcePicker){ 67 this.onClickListener(this); 68 // Remember player state 69 var currentTime = this.player_.currentTime(); 70 var isPaused = this.player_.paused(); 71 this.showAsLabel(); 72 73 // add .current class 74 this.addClass('vjs-selected'); 75 76 // Hide bigPlayButton 77 if(!isPaused){ 78 this.player_.bigPlayButton.hide(); 79 } 80 if(typeof customSourcePicker !== 'function' && 81 typeof this.options_.customSourcePicker === 'function'){ 82 customSourcePicker = this.options_.customSourcePicker; 83 } 84 // Change player source and wait for loadeddata event, then play video 85 // loadedmetadata doesn't work right now for flash. 86 // Probably because of https://github.com/videojs/video-js-swf/issues/124 87 // 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) 88 var handleSeekEvent = 'loadeddata'; 89 if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') { 90 handleSeekEvent = 'timeupdate'; 91 } 92 setSourcesSanitized(this.player_, this.src, this.options_.label, customSourcePicker).one(handleSeekEvent, function() { 93 this.player_.currentTime(currentTime); 94 this.player_.handleTechSeeked_(); 95 if(!isPaused){ 96 // Start playing and hide loadingSpinner (flash issue ?) 97 this.player_.play().handleTechSeeked_(); 98 } 99 this.player_.trigger('resolutionchange'); 100 }); 101 } 102 }); 103 104 105 /* 106 * Resolution menu button 107 */ 108 var MenuButton = videojs.getComponent('MenuButton'); 109 var ResolutionMenuButton = videojs.extend(MenuButton, { 110 constructor: function(player, options, settings, label){ 111 this.sources = options.sources; 112 this.label = label; 113 this.label.innerHTML = options.initialySelectedLabel; 114 // Sets this.player_, this.options_ and initializes the component 115 MenuButton.call(this, player, options, settings); 116 this.controlText('Quality'); 117 118 if(settings.dynamicLabel){ 119 this.el().appendChild(label); 120 }else{ 121 var staticLabel = document.createElement('span'); 122 videojs.addClass(staticLabel, 'vjs-resolution-button-staticlabel'); 123 this.el().appendChild(staticLabel); 124 } 125 }, 126 createItems: function(){ 127 var menuItems = []; 128 var labels = (this.sources && this.sources.label) || {}; 129 var onClickUnselectOthers = function(clickedItem) { 130 menuItems.map(function(item) { 131 item.selected(item === clickedItem); 132 item.removeClass('vjs-selected'); 133 }); 134 }; 135 136 for (var key in labels) { 137 if (labels.hasOwnProperty(key)) { 138 menuItems.push(new ResolutionMenuItem( 139 this.player_, 140 {
141 label: key, 142 src: labels[key], 143 initialySelected: key === this.options_.initialySelectedLabel, 144 customSourcePicker: this.options_.customSourcePicker 145 }, 146 onClickUnselectOthers, 147 this.label)); 148 // Store menu item for API calls 149 menuItemsHolder[key] = menuItems[menuItems.length - 1]; 150 } 151 } 152 return menuItems; 153 } 154 }); 155 156 /** 157 * Initialize the plugin. 158 * @param {object} [options] configuration for the plugin 159 */ 160 videoJsResolutionSwitcher = function(options) { 161 var settings = videojs.mergeOptions(defaults, options), 162 player = this, 163 label = document.createElement('span'), 164 groupedSrc = {}; 165 166 videojs.addClass(label, 'vjs-resolution-button-label'); 167 168 /** 169 * Updates player sources or returns current source URL 170 * @param {Array} [src] array of sources [{src: '', type: '', label: '', res: ''}] 171 * @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources 172 */ 173 player.updateSrc = function(src){ 174 //Return current src if src is not given 175 if(!src){ return player.src(); } 176 // Dispose old resolution menu button before adding new sources 177 if(player.controlBar.resolutionSwitcher){ 178 player.controlBar.resolutionSwitcher.dispose(); 179 delete player.controlBar.resolutionSwitcher; 180 } 181 //Sort sources 182 src = src.sort(compareResolutions); 183 groupedSrc = bucketSources(src); 184 var choosen = chooseSrc(groupedSrc, src); 185 var menuButton = new ResolutionMenuButton(player, { sources: groupedSrc, initialySelectedLabel: choosen.label , initialySelectedRes: choosen.res , customSourcePicker: settings.customSourcePicker}, settings, label); 186 videojs.addClass(menuButton.el(), 'vjs-resolution-button'); 187 player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_); 188 player.controlBar.resolutionSwitcher.dispose = function(){ 189 this.parentNode.removeChild(this); 190 }; 191 return setSourcesSanitized(player, choosen.sources, choosen.label); 192 }; 193 194 /** 195 * Returns current resolution or sets one when label is specified 196 * @param {String} [label] label name 197 * @param {Function} [customSourcePicker] custom function to choose source. Takes 3 arguments: player, sources, label. Must return player object. 198 * @returns {Object} current resolution object {label: '', sources: []} if used as getter or player object if used as setter 199 */ 200 player.currentResolution = function(label, customSourcePicker){ 201 if(label == null) { return currentResolution; } 202 if(menuItemsHolder[label] != null){ 203 menuItemsHolder[label].onClick(customSourcePicker); 204 } 205 return player; 206 }; 207 208 /** 209 * Returns grouped sources by label, resolution and type 210 * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } 211 */ 212 player.getGroupedSrc = function(){ 213 return groupedSrc; 214 }; 215 216 /** 217 * Method used for sorting list of sources 218 * @param {Object} a - source object with res property 219 * @param {Object} b - source object with res property 220 * @returns {Number} result of comparation 221 */ 222 function compareResolutions(a, b){ 223 if(!a.res || !b.res){ return 0; } 224 return (+b.res)-(+a.res); 225 } 226 227 /** 228 * Group sources by label, resolution and type 229 * @param {Array} src Array of sources 230 * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } 231 */ 232 function bucketSources(src){ 233 var resolutions = { 234 label: {}, 235 res: {}, 236 type: {} 237 }; 238 src.map(function(source) { 239 initResolutionKey(resolutions, 'label', source); 240 initResolutionKey(resolutions, 'res', source); 241 initResolutionKey(resolutions, 'type', source); 242 243 appendSourceToKey(resolutions, 'label', source); 244 appendSourceToKey(resolutions, 'res', source); 245 appendSourceToKey(resolutions, 'type', source); 246 }); 247 return resolutions; 248 } 249 250 function initResolutionKey(resolutions, key, source) { 251 if(resolutions[key][source[key]] == null) { 252 resolutions[key][source[key]] = []; 253 } 254 } 255 256 function appendSourceToKey(resolutions, key, source) { 257 resolutions[key][source[key]].push(source); 258 } 259 260 /** 261 * Choose src if option.default is specified 262 * @param {Object} groupedSrc {res: { key: [] }} 263 * @param {Array} src Array of sources sorted by resolution used to find high and low res 264 * @returns {Object} {res: string, sources: []} 265 */ 266 function chooseSrc(groupedSrc, src){ 267 var selectedRes = settings['default']; // use array access as default is a reserved keyword 268 var selectedLabel = ''; 269 if (selectedRes === 'high') { 270 selectedRes = src[0].res; 271 selectedLabel = src[0].label; 272 } else if (selectedRes === 'low' || selectedRes == null || !groupedSrc.res[selectedRes]) { 273 // Select low-res if default is low or not set 274 selectedRes = src[src.length - 1].res; 275 selectedLabel = src[src.length -1].label; 276 } else if (groupedSrc.res[selectedRes]) { 277 selectedLabel = groupedSrc.res[selectedRes][0].label; 278 } 279 280 return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]}; 281 } 282 283 function initResolutionForYt(player){ 284 // Init resolution 285 player.tech_.ytPlayer.setPlaybackQuality('default'); 286 287 // Capture events 288 player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(){ 289 player.trigger('resolutionchange'); 290 }); 291 292 // We must wait for play event 293 player.one('play', function(){ 294 var qualities = player.tech_.ytPlayer.getAvailableQualityLevels(); 295 // Map youtube qualities names 296 var _yts = { 297 highres: {res: 1080, label: '1080', yt: 'highres'}, 298 hd1080: {res: 1080, label: '1080', yt: 'hd1080'}, 299 hd720: {res: 720, label: '720', yt: 'hd720'}, 300 large: {res: 480, label: '480', yt: 'large'}, 301 medium: {res: 360, label: '360', yt: 'medium'}, 302 small: {res: 240, label: '240', yt: 'small'}, 303 tiny: {res: 144, label: '144', yt: 'tiny'}, 304 auto: {res: 0, label: 'auto', yt: 'default'} 305 }; 306 307 var _sources = []; 308 309 qualities.map(function(q){ 310 _sources.push({ 311 src: player.src().src, 312 type: player.src().type, 313 label: _yts[q].label, 314 res: _yts[q].res, 315 _yt: _yts[q].yt 316 }); 317 }); 318 319 groupedSrc = bucketSources(_sources); 320 321 // Overwrite defualt sourcePicer function 322 var _customSourcePicker = function(_player, _sources, _label){ 323 player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt); 324 return player; 325 }; 326 327 var choosen = {label: 'auto', res: 0, sources: groupedSrc.label.auto}; 328 var menuButton = new ResolutionMenuButton(player, { 329 sources: groupedSrc, 330 initialySelectedLabel: choosen.label, 331 initialySelectedRes: choosen.res, 332 customSourcePicker: _customSourcePicker 333 }, settings, label); 334 335 menuButton.el().classList.add('vjs-resolution-button'); 336 player.controlBar.resolutionSwitcher = player.controlBar.addChild(menuButton); 337 }); 338 } 339 340 player.ready(function(){ 341 if(player.options_.sources.length > 1){ 342 // tech: Html5 and Flash 343 // Create resolution switcher for videos form <source> tag inside <video> 344 player.updateSrc(player.options_.sources); 345 } 346 347 if(player.techName_ === 'Youtube'){ 348 // tech: YouTube 349 initResolutionForYt(player); 350 } 351 }); 352 353 }; 354 355 // register the plugin 356 videojs.plugin('videoJsResolutionSwitcher', videoJsResolutionSwitcher); 357 })(window, videojs); 358})();
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.