|
17 | 17 | ***********************************************************************************/
|
18 | 18 | var methods = {
|
19 | 19 |
|
20 |
| - /** |
21 |
| - * Utility function for merging 2 objects recursively. It treats |
22 |
| - * arrays like plain objects and it relies on a for...in loop which will |
23 |
| - * break if the Object prototype is messed with. |
24 |
| - * |
25 |
| - * @param (object) destination The object to modify and return |
26 |
| - * @param (object) source The object to use to overwrite the first |
27 |
| - * object |
28 |
| - * |
29 |
| - * @returns (object) The modified first object is returned |
30 |
| - */ |
31 |
| - extend : function( destination, source ) { |
32 |
| - |
33 |
| - for ( var prop in source ) { |
34 |
| - |
35 |
| - // Sanity check |
36 |
| - if ( ! source.hasOwnProperty( prop ) ) { continue; } |
37 |
| - |
38 |
| - // Enable recursive (deep) object extension |
39 |
| - if ( typeof source[prop] == 'object' && null !== source[prop] ) { |
40 |
| - |
41 |
| - destination[prop] = methods.extend( destination[prop] || {}, source[prop] ); |
42 |
| - |
43 |
| - } else { |
44 |
| - |
45 |
| - destination[prop] = source[prop]; |
46 |
| - } |
47 |
| - } |
48 |
| - |
49 |
| - return destination; |
50 |
| - }, |
51 |
| - |
52 | 20 | /**
|
53 | 21 | * In a future version, this can be made more intelligent,
|
54 | 22 | * but for now, we'll just add a "p" at the end if we are passed
|
|
81 | 49 | // Call the parent constructor
|
82 | 50 | _V_.MenuItem.call( this, player, options );
|
83 | 51 |
|
84 |
| - // Store the resolution as a call property |
| 52 | + // Store the resolution as a property |
85 | 53 | this.resolution = options.res;
|
86 | 54 |
|
87 |
| - // Register our click handler |
88 |
| - this.on( 'click', this.onClick ); |
89 |
| - |
| 55 | + // The parent constructor registers the click handler for us, so this would cause the event to fire twice |
| 56 | + // this.on( 'click', this.onClick ); |
| 57 | + |
90 | 58 | // Register touch handler
|
91 | 59 | this.on( 'touchstart', function () { touchstart = true; });
|
92 | 60 |
|
|
121 | 89 | // Handle clicks on the menu items
|
122 | 90 | _V_.ResolutionMenuItem.prototype.onClick = function() {
|
123 | 91 |
|
124 |
| - var player = this.player(), |
125 |
| - video_el = player.el().firstChild, |
126 |
| - current_time = player.currentTime(), |
127 |
| - is_paused = player.paused(), |
128 |
| - button_nodes = player.controlBar.resolutionSelector.el().firstChild.children, |
129 |
| - button_node_count = button_nodes.length; |
130 |
| - |
131 |
| - // Do nothing if we aren't changing resolutions |
132 |
| - if ( player.getCurrentRes() == this.resolution ) { return; } |
133 |
| - |
134 |
| - // Make sure the loadedmetadata event will fire |
135 |
| - if ( 'none' == video_el.preload ) { video_el.preload = 'metadata'; } |
136 |
| - |
137 |
| - // Change the source and make sure we don't start the video over |
138 |
| - player.src( player.availableRes[this.resolution] ).one( 'loadedmetadata', function() { |
139 |
| - |
140 |
| - player.currentTime( current_time ); |
141 |
| - |
142 |
| - if ( ! is_paused ) { player.play(); } |
143 |
| - }); |
144 |
| - |
145 |
| - // Save the newly selected resolution in our player options property |
146 |
| - player.currentRes = this.resolution; |
147 |
| - |
148 |
| - // Update the button text |
149 |
| - while ( button_node_count > 0 ) { |
150 |
| - |
151 |
| - button_node_count--; |
152 |
| - |
153 |
| - if ( 'vjs-current-res' == button_nodes[button_node_count].className ) { |
154 |
| - |
155 |
| - button_nodes[button_node_count].innerHTML = methods.res_label( this.resolution ); |
156 |
| - break; |
157 |
| - } |
158 |
| - } |
159 |
| - |
160 |
| - // Update the classes to reflect the currently selected resolution |
161 |
| - player.trigger( 'changeRes' ); |
| 92 | + // Call the player.changeRes method |
| 93 | + this.player().changeRes( this.resolution ); |
162 | 94 | };
|
163 | 95 |
|
164 | 96 | /***********************************************************************************
|
|
242 | 174 | _V_.plugin( 'resolutionSelector', function( options ) {
|
243 | 175 |
|
244 | 176 | // Only enable the plugin on HTML5 videos
|
245 |
| - if ( ! this.el().firstChild.canPlayType ) { return; } |
| 177 | + if ( ! this.el().firstChild.canPlayType ) { return; } |
246 | 178 |
|
| 179 | + /******************************************************************* |
| 180 | + * Setup variables, parse settings |
| 181 | + *******************************************************************/ |
247 | 182 | var player = this,
|
248 | 183 | sources = player.options().sources,
|
249 | 184 | i = sources.length,
|
250 | 185 | j,
|
251 | 186 | found_type,
|
252 | 187 |
|
253 | 188 | // Override default options with those provided
|
254 |
| - settings = methods.extend({ |
| 189 | + settings = _V_.util.mergeOptions({ |
255 | 190 |
|
256 | 191 | default_res : '', // (string) The resolution that should be selected by default ( '480' or '480,1080,240' )
|
257 | 192 | 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
|
|
294 | 229 | if ( 'length' == current_res ) { continue; }
|
295 | 230 |
|
296 | 231 | i = settings.force_types.length;
|
| 232 | + found_types = 0; |
297 | 233 |
|
298 |
| - // For each resolution loop through the required types |
| 234 | + // Loop through all required types |
299 | 235 | while ( i > 0 ) {
|
300 | 236 |
|
301 | 237 | i--;
|
302 | 238 |
|
303 | 239 | j = available_res[current_res].length;
|
304 |
| - found_types = 0; |
305 | 240 |
|
306 |
| - // For each required type loop through the available sources to check if its there |
| 241 | + // Loop through all available sources in current resolution |
307 | 242 | while ( j > 0 ) {
|
308 | 243 |
|
309 | 244 | j--;
|
310 | 245 |
|
| 246 | + // Check if the current source matches the current type we're checking |
311 | 247 | if ( settings.force_types[i] === available_res[current_res][j].type ) {
|
312 | 248 |
|
313 | 249 | found_types++;
|
| 250 | + break; |
314 | 251 | }
|
315 |
| - } // End loop through current resolution sources |
316 |
| - |
317 |
| - if ( found_types < settings.force_types.length ) { |
318 |
| - |
319 |
| - delete available_res[current_res]; |
320 |
| - available_res.length--; |
321 |
| - break; |
322 | 252 | }
|
323 |
| - } // End loop through required types |
324 |
| - } // End loop through resolutions |
| 253 | + } |
| 254 | + |
| 255 | + // If we didn't find sources for all of the required types in the current res, remove it |
| 256 | + if ( found_types < settings.force_types.length ) { |
| 257 | + |
| 258 | + delete available_res[current_res]; |
| 259 | + available_res.length--; |
| 260 | + } |
| 261 | + } |
325 | 262 | }
|
326 | 263 |
|
327 | 264 | // Make sure we have at least 2 available resolutions before we add the button
|
|
339 | 276 | }
|
340 | 277 | }
|
341 | 278 |
|
| 279 | + /******************************************************************* |
| 280 | + * Add methods to player object |
| 281 | + *******************************************************************/ |
| 282 | + |
342 | 283 | // Helper function to get the current resolution
|
343 | 284 | player.getCurrentRes = function() {
|
344 | 285 |
|
|
359 | 300 | }
|
360 | 301 | };
|
361 | 302 |
|
362 |
| - // Get the started resolution |
| 303 | + // Define the change res method |
| 304 | + player.changeRes = function( target_resolution ) { |
| 305 | + |
| 306 | + var video_el = player.el().firstChild, |
| 307 | + is_paused = player.paused(), |
| 308 | + current_time, |
| 309 | + button_nodes, |
| 310 | + button_node_count, |
| 311 | + dummy_vid, |
| 312 | + dummy_src, |
| 313 | + doc = document, |
| 314 | + i; |
| 315 | + |
| 316 | + // Do nothing if we aren't changing resolutions or if the resolution isn't defined |
| 317 | + if ( player.getCurrentRes() == target_resolution |
| 318 | + || ! player.availableRes |
| 319 | + || ! player.availableRes[target_resolution] ) { return; } |
| 320 | + |
| 321 | + // Make sure the loadedmetadata event will fire |
| 322 | + if ( 'none' == video_el.preload ) { video_el.preload = 'metadata'; } |
| 323 | + |
| 324 | + // Preload the new resolution |
| 325 | + player.addClass( 'vjs-waiting' ); |
| 326 | + |
| 327 | + // Create a dummy video element to use to preload the new resolution |
| 328 | + dummy_vid = doc.createElement( 'video' ); |
| 329 | + |
| 330 | + i = player.availableRes[target_resolution].length; |
| 331 | + |
| 332 | + while ( i > 0 ) { |
| 333 | + |
| 334 | + i--; |
| 335 | + |
| 336 | + dummy_src = doc.createElement( 'source' ); |
| 337 | + dummy_src.setAttribute( 'src', player.availableRes[target_resolution][i].src ); |
| 338 | + dummy_src.setAttribute( 'type', player.availableRes[target_resolution][i].type ); |
| 339 | + |
| 340 | + dummy_vid.appendChild( dummy_src ); |
| 341 | + } |
| 342 | + |
| 343 | + // Init Video.js on our dummy video |
| 344 | + _V_( dummy_vid, {}, function() { |
| 345 | + |
| 346 | + dummy_vid = this; |
| 347 | + |
| 348 | + // Wait for the dummy video to be ready |
| 349 | + dummy_vid.one( 'loadedmetadata', function() { |
| 350 | + |
| 351 | + // Set the dummy video to the current time |
| 352 | + dummy_vid.currentTime( player.currentTime() ); |
| 353 | + |
| 354 | + // Wait for the dummy video to be ready again after setting the time |
| 355 | + dummy_vid.one( 'canplay', function() { |
| 356 | + |
| 357 | + player.removeClass( 'vjs-waiting' ); |
| 358 | + |
| 359 | + // Get the current time |
| 360 | + current_time = player.currentTime(); |
| 361 | + |
| 362 | + // Change the source and make sure we don't start the video over |
| 363 | + player.src( player.availableRes[target_resolution] ).one( 'loadedmetadata', function() { |
| 364 | + |
| 365 | + player.currentTime( current_time ); |
| 366 | + |
| 367 | + // If the video was paused, don't show the poster image again |
| 368 | + player.addClass( 'vjs-has-started' ); |
| 369 | + |
| 370 | + if ( ! is_paused ) { player.play(); } |
| 371 | + }); |
| 372 | + |
| 373 | + // Save the newly selected resolution in our player options property |
| 374 | + player.currentRes = target_resolution; |
| 375 | + |
| 376 | + // Make sure the button has been added to the control bar |
| 377 | + if ( player.controlBar.resolutionSelector ) { |
| 378 | + |
| 379 | + button_nodes = player.controlBar.resolutionSelector.el().firstChild.children; |
| 380 | + button_node_count = button_nodes.length; |
| 381 | + |
| 382 | + // Update the button text |
| 383 | + while ( button_node_count > 0 ) { |
| 384 | + |
| 385 | + button_node_count--; |
| 386 | + |
| 387 | + if ( 'vjs-current-res' == button_nodes[button_node_count].className ) { |
| 388 | + |
| 389 | + button_nodes[button_node_count].innerHTML = methods.res_label( target_resolution ); |
| 390 | + break; |
| 391 | + } |
| 392 | + } |
| 393 | + } |
| 394 | + |
| 395 | + // Update the classes to reflect the currently selected resolution |
| 396 | + player.trigger( 'changeRes' ); |
| 397 | + |
| 398 | + // Destroy our dummy video |
| 399 | + dummy_vid.dispose(); |
| 400 | + }); |
| 401 | + }); |
| 402 | + }); |
| 403 | + }; |
| 404 | + |
| 405 | + /******************************************************************* |
| 406 | + * Add the resolution selector button |
| 407 | + *******************************************************************/ |
| 408 | + |
| 409 | + // Get the starting resolution |
363 | 410 | current_res = player.getCurrentRes();
|
364 | 411 |
|
365 | 412 | if ( current_res ) { current_res = methods.res_label( current_res ); }
|
|
0 commit comments