]> git.mxchange.org Git - friendica.git/blob - library/video-js/video.dev.js
Merge pull request #745 from mexon/redirect_fix
[friendica.git] / library / video-js / video.dev.js
1 /**
2  * @fileoverview Main function src.
3  */
4
5 // HTML5 Shiv. Must be in <head> to support older browsers.
6 document.createElement('video');document.createElement('audio');
7
8 /**
9  * Doubles as the main function for users to create a player instance and also
10  * the main library object.
11  *
12  * @param  {String|Element} id      Video element or video element ID
13  * @param  {Object=} options        Optional options object for config/settings
14  * @param  {Function=} ready        Optional ready callback
15  * @return {vjs.Player}             A player instance
16  */
17 var vjs = function(id, options, ready){
18   var tag; // Element of ID
19
20   // Allow for element or ID to be passed in
21   // String ID
22   if (typeof id === 'string') {
23
24     // Adjust for jQuery ID syntax
25     if (id.indexOf('#') === 0) {
26       id = id.slice(1);
27     }
28
29     // If a player instance has already been created for this ID return it.
30     if (vjs.players[id]) {
31       return vjs.players[id];
32
33     // Otherwise get element for ID
34     } else {
35       tag = vjs.el(id);
36     }
37
38   // ID is a media element
39   } else {
40     tag = id;
41   }
42
43   // Check for a useable element
44   if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also
45     throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
46   }
47
48   // Element may have a player attr referring to an already created player instance.
49   // If not, set up a new player and return the instance.
50   return tag['player'] || new vjs.Player(tag, options, ready);
51 };
52
53 // Extended name, also available externally, window.videojs
54 var videojs = vjs;
55 window.videojs = window.vjs = vjs;
56
57 // CDN Version. Used to target right flash swf.
58 vjs.CDN_VERSION = 'GENERATED_CDN_VSN';
59 vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
60
61 /**
62  * Global Player instance options, surfaced from vjs.Player.prototype.options_
63  * vjs.options = vjs.Player.prototype.options_
64  * All options should use string keys so they avoid
65  * renaming by closure compiler
66  * @type {Object}
67  */
68 vjs.options = {
69   // Default order of fallback technology
70   'techOrder': ['html5','flash'],
71   // techOrder: ['flash','html5'],
72
73   'html5': {},
74   'flash': { 'swf': vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/c/video-js.swf' },
75
76   // Default of web browser is 300x150. Should rely on source width/height.
77   'width': 300,
78   'height': 150,
79   // defaultVolume: 0.85,
80   'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!
81
82   // Included control sets
83   'children': {
84     'mediaLoader': {},
85     'posterImage': {},
86     'textTrackDisplay': {},
87     'loadingSpinner': {},
88     'bigPlayButton': {},
89     'controlBar': {}
90   }
91 };
92
93 /**
94  * Global player list
95  * @type {Object}
96  */
97 vjs.players = {};
98
99
100 // Set CDN Version of swf
101 if (vjs.CDN_VERSION != 'GENERATED_CDN_VSN') {
102   videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';
103 }
104 /**
105  * Core Object/Class for objects that use inheritance + contstructors
106  * @constructor
107  */
108 vjs.CoreObject = vjs['CoreObject'] = function(){};
109 // Manually exporting vjs['CoreObject'] here for Closure Compiler
110 // because of the use of the extend/create class methods
111 // If we didn't do this, those functions would get flattend to something like
112 // `a = ...` and `this.prototype` would refer to the global object instead of
113 // CoreObject
114
115 /**
116  * Create a new object that inherits from this Object
117  * @param {Object} props Functions and properties to be applied to the
118  *                       new object's prototype
119  * @return {vjs.CoreObject} Returns an object that inherits from CoreObject
120  * @this {*}
121  */
122 vjs.CoreObject.extend = function(props){
123   var init, subObj;
124
125   props = props || {};
126   // Set up the constructor using the supplied init method
127   // or using the init of the parent object
128   // Make sure to check the unobfuscated version for external libs
129   init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){};
130   // In Resig's simple class inheritance (previously used) the constructor
131   //  is a function that calls `this.init.apply(arguments)`
132   // However that would prevent us from using `ParentObject.call(this);`
133   //  in a Child constuctor because the `this` in `this.init`
134   //  would still refer to the Child and cause an inifinite loop.
135   // We would instead have to do
136   //    `ParentObject.prototype.init.apply(this, argumnents);`
137   //  Bleh. We're not creating a _super() function, so it's good to keep
138   //  the parent constructor reference simple.
139   subObj = function(){
140     init.apply(this, arguments);
141   };
142
143   // Inherit from this object's prototype
144   subObj.prototype = vjs.obj.create(this.prototype);
145   // Reset the constructor property for subObj otherwise
146   // instances of subObj would have the constructor of the parent Object
147   subObj.prototype.constructor = subObj;
148
149   // Make the class extendable
150   subObj.extend = vjs.CoreObject.extend;
151   // Make a function for creating instances
152   subObj.create = vjs.CoreObject.create;
153
154   // Extend subObj's prototype with functions and other properties from props
155   for (var name in props) {
156     if (props.hasOwnProperty(name)) {
157       subObj.prototype[name] = props[name];
158     }
159   }
160
161   return subObj;
162 };
163
164 /**
165  * Create a new instace of this Object class
166  * @return {vjs.CoreObject} Returns an instance of a CoreObject subclass
167  * @this {*}
168  */
169 vjs.CoreObject.create = function(){
170   // Create a new object that inherits from this object's prototype
171   var inst = vjs.obj.create(this.prototype);
172
173   // Apply this constructor function to the new object
174   this.apply(inst, arguments);
175
176   // Return the new object
177   return inst;
178 };
179 /**
180  * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
181  * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
182  * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
183  * robust as jquery's, so there's probably some differences.
184  */
185
186 /**
187  * Add an event listener to element
188  * It stores the handler function in a separate cache object
189  * and adds a generic handler to the element's event,
190  * along with a unique id (guid) to the element.
191  * @param  {Element|Object}   elem Element or object to bind listeners to
192  * @param  {String}   type Type of event to bind to.
193  * @param  {Function} fn   Event listener.
194  */
195 vjs.on = function(elem, type, fn){
196   var data = vjs.getData(elem);
197
198   // We need a place to store all our handler data
199   if (!data.handlers) data.handlers = {};
200
201   if (!data.handlers[type]) data.handlers[type] = [];
202
203   if (!fn.guid) fn.guid = vjs.guid++;
204
205   data.handlers[type].push(fn);
206
207   if (!data.dispatcher) {
208     data.disabled = false;
209
210     data.dispatcher = function (event){
211
212       if (data.disabled) return;
213       event = vjs.fixEvent(event);
214
215       var handlers = data.handlers[event.type];
216
217       if (handlers) {
218         // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
219         var handlersCopy = handlers.slice(0);
220
221         for (var m = 0, n = handlersCopy.length; m < n; m++) {
222           if (event.isImmediatePropagationStopped()) {
223             break;
224           } else {
225             handlersCopy[m].call(elem, event);
226           }
227         }
228       }
229     };
230   }
231
232   if (data.handlers[type].length == 1) {
233     if (document.addEventListener) {
234       elem.addEventListener(type, data.dispatcher, false);
235     } else if (document.attachEvent) {
236       elem.attachEvent('on' + type, data.dispatcher);
237     }
238   }
239 };
240
241 /**
242  * Removes event listeners from an element
243  * @param  {Element|Object}   elem Object to remove listeners from
244  * @param  {String=}   type Type of listener to remove. Don't include to remove all events from element.
245  * @param  {Function} fn   Specific listener to remove. Don't incldue to remove listeners for an event type.
246  */
247 vjs.off = function(elem, type, fn) {
248   // Don't want to add a cache object through getData if not needed
249   if (!vjs.hasData(elem)) return;
250
251   var data = vjs.getData(elem);
252
253   // If no events exist, nothing to unbind
254   if (!data.handlers) { return; }
255
256   // Utility function
257   var removeType = function(t){
258      data.handlers[t] = [];
259      vjs.cleanUpEvents(elem,t);
260   };
261
262   // Are we removing all bound events?
263   if (!type) {
264     for (var t in data.handlers) removeType(t);
265     return;
266   }
267
268   var handlers = data.handlers[type];
269
270   // If no handlers exist, nothing to unbind
271   if (!handlers) return;
272
273   // If no listener was provided, remove all listeners for type
274   if (!fn) {
275     removeType(type);
276     return;
277   }
278
279   // We're only removing a single handler
280   if (fn.guid) {
281     for (var n = 0; n < handlers.length; n++) {
282       if (handlers[n].guid === fn.guid) {
283         handlers.splice(n--, 1);
284       }
285     }
286   }
287
288   vjs.cleanUpEvents(elem, type);
289 };
290
291 /**
292  * Clean up the listener cache and dispatchers
293  * @param  {Element|Object} elem Element to clean up
294  * @param  {String} type Type of event to clean up
295  */
296 vjs.cleanUpEvents = function(elem, type) {
297   var data = vjs.getData(elem);
298
299   // Remove the events of a particular type if there are none left
300   if (data.handlers[type].length === 0) {
301     delete data.handlers[type];
302     // data.handlers[type] = null;
303     // Setting to null was causing an error with data.handlers
304
305     // Remove the meta-handler from the element
306     if (document.removeEventListener) {
307       elem.removeEventListener(type, data.dispatcher, false);
308     } else if (document.detachEvent) {
309       elem.detachEvent('on' + type, data.dispatcher);
310     }
311   }
312
313   // Remove the events object if there are no types left
314   if (vjs.isEmpty(data.handlers)) {
315     delete data.handlers;
316     delete data.dispatcher;
317     delete data.disabled;
318
319     // data.handlers = null;
320     // data.dispatcher = null;
321     // data.disabled = null;
322   }
323
324   // Finally remove the expando if there is no data left
325   if (vjs.isEmpty(data)) {
326     vjs.removeData(elem);
327   }
328 };
329
330 /**
331  * Fix a native event to have standard property values
332  * @param  {Object} event Event object to fix
333  * @return {Object}
334  */
335 vjs.fixEvent = function(event) {
336
337   function returnTrue() { return true; }
338   function returnFalse() { return false; }
339
340   // Test if fixing up is needed
341   // Used to check if !event.stopPropagation instead of isPropagationStopped
342   // But native events return true for stopPropagation, but don't have
343   // other expected methods like isPropagationStopped. Seems to be a problem
344   // with the Javascript Ninja code. So we're just overriding all events now.
345   if (!event || !event.isPropagationStopped) {
346     var old = event || window.event;
347
348     event = {};
349     // Clone the old object so that we can modify the values event = {};
350     // IE8 Doesn't like when you mess with native event properties
351     // Firefox returns false for event.hasOwnProperty('type') and other props
352     //  which makes copying more difficult.
353     // TODO: Probably best to create a whitelist of event props
354     for (var key in old) {
355       event[key] = old[key];
356     }
357
358     // The event occurred on this element
359     if (!event.target) {
360       event.target = event.srcElement || document;
361     }
362
363     // Handle which other element the event is related to
364     event.relatedTarget = event.fromElement === event.target ?
365       event.toElement :
366       event.fromElement;
367
368     // Stop the default browser action
369     event.preventDefault = function () {
370       if (old.preventDefault) {
371         old.preventDefault();
372       }
373       event.returnValue = false;
374       event.isDefaultPrevented = returnTrue;
375     };
376
377     event.isDefaultPrevented = returnFalse;
378
379     // Stop the event from bubbling
380     event.stopPropagation = function () {
381       if (old.stopPropagation) {
382         old.stopPropagation();
383       }
384       event.cancelBubble = true;
385       event.isPropagationStopped = returnTrue;
386     };
387
388     event.isPropagationStopped = returnFalse;
389
390     // Stop the event from bubbling and executing other handlers
391     event.stopImmediatePropagation = function () {
392       if (old.stopImmediatePropagation) {
393         old.stopImmediatePropagation();
394       }
395       event.isImmediatePropagationStopped = returnTrue;
396       event.stopPropagation();
397     };
398
399     event.isImmediatePropagationStopped = returnFalse;
400
401     // Handle mouse position
402     if (event.clientX != null) {
403       var doc = document.documentElement, body = document.body;
404
405       event.pageX = event.clientX +
406         (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
407         (doc && doc.clientLeft || body && body.clientLeft || 0);
408       event.pageY = event.clientY +
409         (doc && doc.scrollTop || body && body.scrollTop || 0) -
410         (doc && doc.clientTop || body && body.clientTop || 0);
411     }
412
413     // Handle key presses
414     event.which = event.charCode || event.keyCode;
415
416     // Fix button for mouse clicks:
417     // 0 == left; 1 == middle; 2 == right
418     if (event.button != null) {
419       event.button = (event.button & 1 ? 0 :
420         (event.button & 4 ? 1 :
421           (event.button & 2 ? 2 : 0)));
422     }
423   }
424
425   // Returns fixed-up instance
426   return event;
427 };
428
429 /**
430  * Trigger an event for an element
431  * @param  {Element|Object} elem  Element to trigger an event on
432  * @param  {String} event Type of event to trigger
433  */
434 vjs.trigger = function(elem, event) {
435   // Fetches element data and a reference to the parent (for bubbling).
436   // Don't want to add a data object to cache for every parent,
437   // so checking hasData first.
438   var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {};
439   var parent = elem.parentNode || elem.ownerDocument;
440       // type = event.type || event,
441       // handler;
442
443   // If an event name was passed as a string, creates an event out of it
444   if (typeof event === 'string') {
445     event = { type:event, target:elem };
446   }
447   // Normalizes the event properties.
448   event = vjs.fixEvent(event);
449
450   // If the passed element has a dispatcher, executes the established handlers.
451   if (elemData.dispatcher) {
452     elemData.dispatcher.call(elem, event);
453   }
454
455   // Unless explicitly stopped, recursively calls this function to bubble the event up the DOM.
456   if (parent && !event.isPropagationStopped()) {
457     vjs.trigger(parent, event);
458
459   // If at the top of the DOM, triggers the default action unless disabled.
460   } else if (!parent && !event.isDefaultPrevented()) {
461     var targetData = vjs.getData(event.target);
462
463     // Checks if the target has a default action for this event.
464     if (event.target[event.type]) {
465       // Temporarily disables event dispatching on the target as we have already executed the handler.
466       targetData.disabled = true;
467       // Executes the default action.
468       if (typeof event.target[event.type] === 'function') {
469         event.target[event.type]();
470       }
471       // Re-enables event dispatching.
472       targetData.disabled = false;
473     }
474   }
475
476   // Inform the triggerer if the default was prevented by returning false
477   return !event.isDefaultPrevented();
478   /* Original version of js ninja events wasn't complete.
479    * We've since updated to the latest version, but keeping this around
480    * for now just in case.
481    */
482   // // Added in attion to book. Book code was broke.
483   // event = typeof event === 'object' ?
484   //   event[vjs.expando] ?
485   //     event :
486   //     new vjs.Event(type, event) :
487   //   new vjs.Event(type);
488
489   // event.type = type;
490   // if (handler) {
491   //   handler.call(elem, event);
492   // }
493
494   // // Clean up the event in case it is being reused
495   // event.result = undefined;
496   // event.target = elem;
497 };
498
499 /**
500  * Trigger a listener only once for an event
501  * @param  {Element|Object}   elem Element or object to
502  * @param  {[type]}   type [description]
503  * @param  {Function} fn   [description]
504  * @return {[type]}
505  */
506 vjs.one = function(elem, type, fn) {
507   vjs.on(elem, type, function(){
508     vjs.off(elem, type, arguments.callee);
509     fn.apply(this, arguments);
510   });
511 };
512 var hasOwnProp = Object.prototype.hasOwnProperty;
513
514 /**
515  * Creates an element and applies properties.
516  * @param  {String=} tagName    Name of tag to be created.
517  * @param  {Object=} properties Element properties to be applied.
518  * @return {Element}
519  */
520 vjs.createEl = function(tagName, properties){
521   var el = document.createElement(tagName || 'div');
522
523   for (var propName in properties){
524     if (hasOwnProp.call(properties, propName)) {
525       //el[propName] = properties[propName];
526       // Not remembering why we were checking for dash
527       // but using setAttribute means you have to use getAttribute
528
529       // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.
530       // The additional check for "role" is because the default method for adding attributes does not
531       // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
532       // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
533       // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
534
535        if (propName.indexOf('aria-') !== -1 || propName=='role') {
536          el.setAttribute(propName, properties[propName]);
537        } else {
538          el[propName] = properties[propName];
539        }
540     }
541   }
542   return el;
543 };
544
545 /**
546  * Uppercase the first letter of a string
547  * @param  {String} string String to be uppercased
548  * @return {String}
549  */
550 vjs.capitalize = function(string){
551   return string.charAt(0).toUpperCase() + string.slice(1);
552 };
553
554 /**
555  * Object functions container
556  * @type {Object}
557  */
558 vjs.obj = {};
559
560 /**
561  * Object.create shim for prototypal inheritance.
562  * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
563  * @param  {Object}   obj Object to use as prototype
564  */
565  vjs.obj.create = Object.create || function(obj){
566   //Create a new function called 'F' which is just an empty object.
567   function F() {}
568
569   //the prototype of the 'F' function should point to the
570   //parameter of the anonymous function.
571   F.prototype = obj;
572
573   //create a new constructor function based off of the 'F' function.
574   return new F();
575 };
576
577 /**
578  * Loop through each property in an object and call a function
579  * whose arguments are (key,value)
580  * @param  {Object}   obj Object of properties
581  * @param  {Function} fn  Function to be called on each property.
582  * @this {*}
583  */
584 vjs.obj.each = function(obj, fn, context){
585   for (var key in obj) {
586     if (hasOwnProp.call(obj, key)) {
587       fn.call(context || this, key, obj[key]);
588     }
589   }
590 };
591
592 /**
593  * Merge two objects together and return the original.
594  * @param  {Object} obj1
595  * @param  {Object} obj2
596  * @return {Object}
597  */
598 vjs.obj.merge = function(obj1, obj2){
599   if (!obj2) { return obj1; }
600   for (var key in obj2){
601     if (hasOwnProp.call(obj2, key)) {
602       obj1[key] = obj2[key];
603     }
604   }
605   return obj1;
606 };
607
608 /**
609  * Merge two objects, and merge any properties that are objects
610  * instead of just overwriting one. Uses to merge options hashes
611  * where deeper default settings are important.
612  * @param  {Object} obj1 Object to override
613  * @param  {Object} obj2 Overriding object
614  * @return {Object}      New object. Obj1 and Obj2 will be untouched.
615  */
616 vjs.obj.deepMerge = function(obj1, obj2){
617   var key, val1, val2, objDef;
618   objDef = '[object Object]';
619
620   // Make a copy of obj1 so we're not ovewriting original values.
621   // like prototype.options_ and all sub options objects
622   obj1 = vjs.obj.copy(obj1);
623
624   for (key in obj2){
625     if (hasOwnProp.call(obj2, key)) {
626       val1 = obj1[key];
627       val2 = obj2[key];
628
629       // Check if both properties are pure objects and do a deep merge if so
630       if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {
631         obj1[key] = vjs.obj.deepMerge(val1, val2);
632       } else {
633         obj1[key] = obj2[key];
634       }
635     }
636   }
637   return obj1;
638 };
639
640 /**
641  * Make a copy of the supplied object
642  * @param  {Object} obj Object to copy
643  * @return {Object}     Copy of object
644  */
645 vjs.obj.copy = function(obj){
646   return vjs.obj.merge({}, obj);
647 };
648
649 /**
650  * Check if an object is plain, and not a dom node or any object sub-instance
651  * @param  {Object} obj Object to check
652  * @return {Boolean}     True if plain, false otherwise
653  */
654 vjs.obj.isPlain = function(obj){
655   return !!obj
656     && typeof obj === 'object'
657     && obj.toString() === '[object Object]'
658     && obj.constructor === Object;
659 };
660
661 /**
662  * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
663    It also stores a unique id on the function so it can be easily removed from events
664  * @param  {*}   context The object to bind as scope
665  * @param  {Function} fn      The function to be bound to a scope
666  * @param  {Number=}   uid     An optional unique ID for the function to be set
667  * @return {Function}
668  */
669 vjs.bind = function(context, fn, uid) {
670   // Make sure the function has a unique ID
671   if (!fn.guid) { fn.guid = vjs.guid++; }
672
673   // Create the new function that changes the context
674   var ret = function() {
675     return fn.apply(context, arguments);
676   };
677
678   // Allow for the ability to individualize this function
679   // Needed in the case where multiple objects might share the same prototype
680   // IF both items add an event listener with the same function, then you try to remove just one
681   // it will remove both because they both have the same guid.
682   // when using this, you need to use the bind method when you remove the listener as well.
683   // currently used in text tracks
684   ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid;
685
686   return ret;
687 };
688
689 /**
690  * Element Data Store. Allows for binding data to an element without putting it directly on the element.
691  * Ex. Event listneres are stored here.
692  * (also from jsninja.com, slightly modified and updated for closure compiler)
693  * @type {Object}
694  */
695 vjs.cache = {};
696
697 /**
698  * Unique ID for an element or function
699  * @type {Number}
700  */
701 vjs.guid = 1;
702
703 /**
704  * Unique attribute name to store an element's guid in
705  * @type {String}
706  * @constant
707  */
708 vjs.expando = 'vdata' + (new Date()).getTime();
709
710 /**
711  * Returns the cache object where data for an element is stored
712  * @param  {Element} el Element to store data for.
713  * @return {Object}
714  */
715 vjs.getData = function(el){
716   var id = el[vjs.expando];
717   if (!id) {
718     id = el[vjs.expando] = vjs.guid++;
719     vjs.cache[id] = {};
720   }
721   return vjs.cache[id];
722 };
723
724 /**
725  * Returns the cache object where data for an element is stored
726  * @param  {Element} el Element to store data for.
727  * @return {Object}
728  */
729 vjs.hasData = function(el){
730   var id = el[vjs.expando];
731   return !(!id || vjs.isEmpty(vjs.cache[id]));
732 };
733
734 /**
735  * Delete data for the element from the cache and the guid attr from getElementById
736  * @param  {Element} el Remove data for an element
737  */
738 vjs.removeData = function(el){
739   var id = el[vjs.expando];
740   if (!id) { return; }
741   // Remove all stored data
742   // Changed to = null
743   // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
744   // vjs.cache[id] = null;
745   delete vjs.cache[id];
746
747   // Remove the expando property from the DOM node
748   try {
749     delete el[vjs.expando];
750   } catch(e) {
751     if (el.removeAttribute) {
752       el.removeAttribute(vjs.expando);
753     } else {
754       // IE doesn't appear to support removeAttribute on the document element
755       el[vjs.expando] = null;
756     }
757   }
758 };
759
760 vjs.isEmpty = function(obj) {
761   for (var prop in obj) {
762     // Inlude null properties as empty.
763     if (obj[prop] !== null) {
764       return false;
765     }
766   }
767   return true;
768 };
769
770 /**
771  * Add a CSS class name to an element
772  * @param {Element} element    Element to add class name to
773  * @param {String} classToAdd Classname to add
774  */
775 vjs.addClass = function(element, classToAdd){
776   if ((' '+element.className+' ').indexOf(' '+classToAdd+' ') == -1) {
777     element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;
778   }
779 };
780
781 /**
782  * Remove a CSS class name from an element
783  * @param {Element} element    Element to remove from class name
784  * @param {String} classToAdd Classname to remove
785  */
786 vjs.removeClass = function(element, classToRemove){
787   if (element.className.indexOf(classToRemove) == -1) { return; }
788   var classNames = element.className.split(' ');
789   // IE8 Does not support array.indexOf so using a for loop
790   for (var i = classNames.length - 1; i >= 0; i--) {
791     if (classNames[i] === classToRemove) {
792       classNames.splice(i,1);
793     }
794   }
795   // classNames.splice(classNames.indexOf(classToRemove),1);
796   element.className = classNames.join(' ');
797 };
798
799 /**
800  * Element for testing browser HTML5 video capabilities
801  * @type {Element}
802  * @constant
803  */
804 vjs.TEST_VID = vjs.createEl('video');
805
806 /**
807  * Useragent for browser testing.
808  * @type {String}
809  * @constant
810  */
811 vjs.USER_AGENT = navigator.userAgent;
812
813 /**
814  * Device is an iPhone
815  * @type {Boolean}
816  * @constant
817  */
818 vjs.IS_IPHONE = !!vjs.USER_AGENT.match(/iPhone/i);
819 vjs.IS_IPAD = !!vjs.USER_AGENT.match(/iPad/i);
820 vjs.IS_IPOD = !!vjs.USER_AGENT.match(/iPod/i);
821 vjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD;
822
823 vjs.IOS_VERSION = (function(){
824   var match = vjs.USER_AGENT.match(/OS (\d+)_/i);
825   if (match && match[1]) { return match[1]; }
826 })();
827
828 vjs.IS_ANDROID = !!vjs.USER_AGENT.match(/Android.*AppleWebKit/i);
829 vjs.ANDROID_VERSION = (function() {
830   var match = vjs.USER_AGENT.match(/Android (\d+)\./i);
831   if (match && match[1]) {
832     return match[1];
833   }
834   return null;
835 })();
836
837 vjs.IS_FIREFOX = function(){ return !!vjs.USER_AGENT.match('Firefox'); };
838
839
840 /**
841  * Get an element's attribute values, as defined on the HTML tag
842  * Attributs are not the same as properties. They're defined on the tag
843  * or with setAttribute (which shouldn't be used with HTML)
844  * This will return true or false for boolean attributes.
845  * @param  {Element} tag Element from which to get tag attributes
846  * @return {Object}
847  */
848 vjs.getAttributeValues = function(tag){
849   var obj = {};
850
851   // Known boolean attributes
852   // We can check for matching boolean properties, but older browsers
853   // won't know about HTML5 boolean attributes that we still read from.
854   // Bookending with commas to allow for an easy string search.
855   var knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
856
857   if (tag && tag.attributes && tag.attributes.length > 0) {
858     var attrs = tag.attributes;
859     var attrName, attrVal;
860
861     for (var i = attrs.length - 1; i >= 0; i--) {
862       attrName = attrs[i].name;
863       attrVal = attrs[i].value;
864
865       // Check for known booleans
866       // The matching element property will return a value for typeof
867       if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {
868         // The value of an included boolean attribute is typically an empty string ('')
869         // which would equal false if we just check for a false value.
870         // We also don't want support bad code like autoplay='false'
871         attrVal = (attrVal !== null) ? true : false;
872       }
873
874       obj[attrName] = attrVal;
875     }
876   }
877
878   return obj;
879 };
880
881 /**
882  * Get the computed style value for an element
883  * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
884  * @param  {Element} el        Element to get style value for
885  * @param  {String} strCssRule Style name
886  * @return {String}            Style value
887  */
888 vjs.getComputedDimension = function(el, strCssRule){
889   var strValue = '';
890   if(document.defaultView && document.defaultView.getComputedStyle){
891     strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule);
892
893   } else if(el.currentStyle){
894     // IE8 Width/Height support
895     strValue = el['client'+strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1)] + 'px';
896   }
897   return strValue;
898 };
899
900 /**
901  * Insert an element as the first child node of another
902  * @param  {Element} child   Element to insert
903  * @param  {[type]} parent Element to insert child into
904  */
905 vjs.insertFirst = function(child, parent){
906   if (parent.firstChild) {
907     parent.insertBefore(child, parent.firstChild);
908   } else {
909     parent.appendChild(child);
910   }
911 };
912
913 /**
914  * Object to hold browser support information
915  * @type {Object}
916  */
917 vjs.support = {};
918
919 /**
920  * Shorthand for document.getElementById()
921  * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
922  * @param  {String} id  Element ID
923  * @return {Element}    Element with supplied ID
924  */
925 vjs.el = function(id){
926   if (id.indexOf('#') === 0) {
927     id = id.slice(1);
928   }
929
930   return document.getElementById(id);
931 };
932
933 /**
934  * Format seconds as a time string, H:MM:SS or M:SS
935  * Supplying a guide (in seconds) will force a number of leading zeros
936  * to cover the length of the guide
937  * @param  {Number} seconds Number of seconds to be turned into a string
938  * @param  {Number} guide   Number (in seconds) to model the string after
939  * @return {String}         Time formatted as H:MM:SS or M:SS
940  */
941 vjs.formatTime = function(seconds, guide) {
942   guide = guide || seconds; // Default to using seconds as guide
943   var s = Math.floor(seconds % 60),
944       m = Math.floor(seconds / 60 % 60),
945       h = Math.floor(seconds / 3600),
946       gm = Math.floor(guide / 60 % 60),
947       gh = Math.floor(guide / 3600);
948
949   // Check if we need to show hours
950   h = (h > 0 || gh > 0) ? h + ':' : '';
951
952   // If hours are showing, we may need to add a leading zero.
953   // Always show at least one digit of minutes.
954   m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
955
956   // Check if leading zero is need for seconds
957   s = (s < 10) ? '0' + s : s;
958
959   return h + m + s;
960 };
961
962 // Attempt to block the ability to select text while dragging controls
963 vjs.blockTextSelection = function(){
964   document.body.focus();
965   document.onselectstart = function () { return false; };
966 };
967 // Turn off text selection blocking
968 vjs.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };
969
970 /**
971  * Trim whitespace from the ends of a string.
972  * @param  {String} string String to trim
973  * @return {String}        Trimmed string
974  */
975 vjs.trim = function(string){
976   return string.toString().replace(/^\s+/, '').replace(/\s+$/, '');
977 };
978
979 /**
980  * Should round off a number to a decimal place
981  * @param  {Number} num Number to round
982  * @param  {Number} dec Number of decimal places to round to
983  * @return {Number}     Rounded number
984  */
985 vjs.round = function(num, dec) {
986   if (!dec) { dec = 0; }
987   return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
988 };
989
990 /**
991  * Should create a fake TimeRange object
992  * Mimics an HTML5 time range instance, which has functions that
993  * return the start and end times for a range
994  * TimeRanges are returned by the buffered() method
995  * @param  {Number} start Start time in seconds
996  * @param  {Number} end   End time in seconds
997  * @return {Object}       Fake TimeRange object
998  */
999 vjs.createTimeRange = function(start, end){
1000   return {
1001     length: 1,
1002     start: function() { return start; },
1003     end: function() { return end; }
1004   };
1005 };
1006
1007 /**
1008  * Simple http request for retrieving external files (e.g. text tracks)
1009  * @param  {String} url           URL of resource
1010  * @param  {Function=} onSuccess  Success callback
1011  * @param  {Function=} onError    Error callback
1012  */
1013 vjs.get = function(url, onSuccess, onError){
1014   var local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
1015
1016   if (typeof XMLHttpRequest === 'undefined') {
1017     window.XMLHttpRequest = function () {
1018       try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}
1019       try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}
1020       try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {}
1021       throw new Error('This browser does not support XMLHttpRequest.');
1022     };
1023   }
1024
1025   var request = new XMLHttpRequest();
1026
1027   try {
1028     request.open('GET', url);
1029   } catch(e) {
1030     onError(e);
1031   }
1032
1033   request.onreadystatechange = function() {
1034     if (request.readyState === 4) {
1035       if (request.status === 200 || local && request.status === 0) {
1036         onSuccess(request.responseText);
1037       } else {
1038         if (onError) {
1039           onError();
1040         }
1041       }
1042     }
1043   };
1044
1045   try {
1046     request.send();
1047   } catch(e) {
1048     if (onError) {
1049       onError(e);
1050     }
1051   }
1052 };
1053
1054 /* Local Storage
1055 ================================================================================ */
1056 vjs.setLocalStorage = function(key, value){
1057   try {
1058     // IE was throwing errors referencing the var anywhere without this
1059     var localStorage = window.localStorage || false;
1060     if (!localStorage) { return; }
1061     localStorage[key] = value;
1062   } catch(e) {
1063     if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
1064       vjs.log('LocalStorage Full (VideoJS)', e);
1065     } else {
1066       if (e.code == 18) {
1067         vjs.log('LocalStorage not allowed (VideoJS)', e);
1068       } else {
1069         vjs.log('LocalStorage Error (VideoJS)', e);
1070       }
1071     }
1072   }
1073 };
1074
1075 /**
1076  * Get abosolute version of relative URL. Used to tell flash correct URL.
1077  * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
1078  * @param  {String} url URL to make absolute
1079  * @return {String}     Absolute URL
1080  */
1081 vjs.getAbsoluteURL = function(url){
1082
1083   // Check if absolute URL
1084   if (!url.match(/^https?:\/\//)) {
1085     // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
1086     url = vjs.createEl('div', {
1087       innerHTML: '<a href="'+url+'">x</a>'
1088     }).firstChild.href;
1089   }
1090
1091   return url;
1092 };
1093
1094 // usage: log('inside coolFunc',this,arguments);
1095 // http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
1096 vjs.log = function(){
1097   vjs.log.history = vjs.log.history || [];   // store logs to an array for reference
1098   vjs.log.history.push(arguments);
1099   if(window.console){
1100     window.console.log(Array.prototype.slice.call(arguments));
1101   }
1102 };
1103
1104 // Offset Left
1105 // getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
1106 vjs.findPosition = function(el) {
1107     var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
1108
1109     if (el.getBoundingClientRect && el.parentNode) {
1110       box = el.getBoundingClientRect();
1111     }
1112
1113     if (!box) {
1114       return {
1115         left: 0,
1116         top: 0
1117       };
1118     }
1119
1120     docEl = document.documentElement;
1121     body = document.body;
1122
1123     clientLeft = docEl.clientLeft || body.clientLeft || 0;
1124     scrollLeft = window.pageXOffset || body.scrollLeft;
1125     left = box.left + scrollLeft - clientLeft;
1126
1127     clientTop = docEl.clientTop || body.clientTop || 0;
1128     scrollTop = window.pageYOffset || body.scrollTop;
1129     top = box.top + scrollTop - clientTop;
1130
1131     return {
1132       left: left,
1133       top: top
1134     };
1135 };
1136 /**
1137  * @fileoverview Player Component - Base class for all UI objects
1138  *
1139  */
1140
1141 /**
1142  * Base UI Component class
1143  * @param {Object} player  Main Player
1144  * @param {Object=} options
1145  * @constructor
1146  */
1147 vjs.Component = vjs.CoreObject.extend({
1148   /** @constructor */
1149   init: function(player, options, ready){
1150     this.player_ = player;
1151
1152     // Make a copy of prototype.options_ to protect against overriding global defaults
1153     this.options_ = vjs.obj.copy(this.options_);
1154
1155     // Updated options with supplied options
1156     options = this.options(options);
1157
1158     // Get ID from options, element, or create using player ID and unique ID
1159     this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );
1160
1161     this.name_ = options['name'] || null;
1162
1163     // Create element if one wasn't provided in options
1164     this.el_ = options['el'] || this.createEl();
1165
1166     this.children_ = [];
1167     this.childIndex_ = {};
1168     this.childNameIndex_ = {};
1169
1170     // Add any child components in options
1171     this.initChildren();
1172
1173     this.ready(ready);
1174     // Don't want to trigger ready here or it will before init is actually
1175     // finished for all children that run this constructor
1176   }
1177 });
1178
1179 /**
1180  * Dispose of the component and all child components.
1181  */
1182 vjs.Component.prototype.dispose = function(){
1183   // Dispose all children.
1184   if (this.children_) {
1185     for (var i = this.children_.length - 1; i >= 0; i--) {
1186       if (this.children_[i].dispose) {
1187         this.children_[i].dispose();
1188       }
1189     }
1190   }
1191
1192   // Delete child references
1193   this.children_ = null;
1194   this.childIndex_ = null;
1195   this.childNameIndex_ = null;
1196
1197   // Remove all event listeners.
1198   this.off();
1199
1200   // Remove element from DOM
1201   if (this.el_.parentNode) {
1202     this.el_.parentNode.removeChild(this.el_);
1203   }
1204
1205   vjs.removeData(this.el_);
1206   this.el_ = null;
1207 };
1208
1209 /**
1210  * Reference to main player instance.
1211  * @type {vjs.Player}
1212  * @private
1213  */
1214 vjs.Component.prototype.player_;
1215
1216 /**
1217  * Return the component's player.
1218  * @return {vjs.Player}
1219  */
1220 vjs.Component.prototype.player = function(){
1221   return this.player_;
1222 };
1223
1224 /**
1225  * Component options object.
1226  * @type {Object}
1227  * @private
1228  */
1229 vjs.Component.prototype.options_;
1230
1231 /**
1232  * Deep merge of options objects
1233  * Whenever a property is an object on both options objects
1234  * the two properties will be merged using vjs.obj.deepMerge.
1235  *
1236  * This is used for merging options for child components. We
1237  * want it to be easy to override individual options on a child
1238  * component without having to rewrite all the other default options.
1239  *
1240  * Parent.prototype.options_ = {
1241  *   children: {
1242  *     'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
1243  *     'childTwo': {},
1244  *     'childThree': {}
1245  *   }
1246  * }
1247  * newOptions = {
1248  *   children: {
1249  *     'childOne': { 'foo': 'baz', 'abc': '123' }
1250  *     'childTwo': null,
1251  *     'childFour': {}
1252  *   }
1253  * }
1254  *
1255  * this.options(newOptions);
1256  *
1257  * RESULT
1258  *
1259  * {
1260  *   children: {
1261  *     'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },
1262  *     'childTwo': null, // Disabled. Won't be initialized.
1263  *     'childThree': {},
1264  *     'childFour': {}
1265  *   }
1266  * }
1267  *
1268  * @param  {Object} obj Object whose values will be overwritten
1269  * @return {Object}      NEW merged object. Does not return obj1.
1270  */
1271 vjs.Component.prototype.options = function(obj){
1272   if (obj === undefined) return this.options_;
1273
1274   return this.options_ = vjs.obj.deepMerge(this.options_, obj);
1275 };
1276
1277 /**
1278  * The DOM element for the component.
1279  * @type {Element}
1280  * @private
1281  */
1282 vjs.Component.prototype.el_;
1283
1284 /**
1285  * Create the component's DOM element.
1286  * @param  {String=} tagName  Element's node type. e.g. 'div'
1287  * @param  {Object=} attributes An object of element attributes that should be set on the element.
1288  * @return {Element}
1289  */
1290 vjs.Component.prototype.createEl = function(tagName, attributes){
1291   return vjs.createEl(tagName, attributes);
1292 };
1293
1294 /**
1295  * Return the component's DOM element.
1296  * @return {Element}
1297  */
1298 vjs.Component.prototype.el = function(){
1299   return this.el_;
1300 };
1301
1302 /**
1303  * An optional element where, if defined, children will be inserted
1304  *   instead of directly in el_
1305  * @type {Element}
1306  * @private
1307  */
1308 vjs.Component.prototype.contentEl_;
1309
1310 /**
1311  * Return the component's DOM element for embedding content.
1312  *   will either be el_ or a new element defined in createEl
1313  * @return {Element}
1314  */
1315 vjs.Component.prototype.contentEl = function(){
1316   return this.contentEl_ || this.el_;
1317 };
1318
1319 /**
1320  * The ID for the component.
1321  * @type {String}
1322  * @private
1323  */
1324 vjs.Component.prototype.id_;
1325
1326 /**
1327  * Return the component's ID.
1328  * @return {String}
1329  */
1330 vjs.Component.prototype.id = function(){
1331   return this.id_;
1332 };
1333
1334 /**
1335  * The name for the component. Often used to reference the component.
1336  * @type {String}
1337  * @private
1338  */
1339 vjs.Component.prototype.name_;
1340
1341 /**
1342  * Return the component's ID.
1343  * @return {String}
1344  */
1345 vjs.Component.prototype.name = function(){
1346   return this.name_;
1347 };
1348
1349 /**
1350  * Array of child components
1351  * @type {Array}
1352  * @private
1353  */
1354 vjs.Component.prototype.children_;
1355
1356 /**
1357  * Returns array of all child components.
1358  * @return {Array}
1359  */
1360 vjs.Component.prototype.children = function(){
1361   return this.children_;
1362 };
1363
1364 /**
1365  * Object of child components by ID
1366  * @type {Object}
1367  * @private
1368  */
1369 vjs.Component.prototype.childIndex_;
1370
1371 /**
1372  * Returns a child component with the provided ID.
1373  * @return {Array}
1374  */
1375 vjs.Component.prototype.getChildById = function(id){
1376   return this.childIndex_[id];
1377 };
1378
1379 /**
1380  * Object of child components by Name
1381  * @type {Object}
1382  * @private
1383  */
1384 vjs.Component.prototype.childNameIndex_;
1385
1386 /**
1387  * Returns a child component with the provided ID.
1388  * @return {Array}
1389  */
1390 vjs.Component.prototype.getChild = function(name){
1391   return this.childNameIndex_[name];
1392 };
1393
1394 /**
1395  * Adds a child component inside this component.
1396  * @param {String|vjs.Component} child The class name or instance of a child to add.
1397  * @param {Object=} options Optional options, including options to be passed to
1398  *  children of the child.
1399  * @return {vjs.Component} The child component, because it might be created in this process.
1400  * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}
1401  */
1402 vjs.Component.prototype.addChild = function(child, options){
1403   var component, componentClass, componentName, componentId;
1404
1405   // If string, create new component with options
1406   if (typeof child === 'string') {
1407
1408     componentName = child;
1409
1410     // Make sure options is at least an empty object to protect against errors
1411     options = options || {};
1412
1413     // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
1414     componentClass = options['componentClass'] || vjs.capitalize(componentName);
1415
1416     // Set name through options
1417     options['name'] = componentName;
1418
1419     // Create a new object & element for this controls set
1420     // If there's no .player_, this is a player
1421     // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly.
1422     // Every class should be exported, so this should never be a problem here.
1423     component = new window['videojs'][componentClass](this.player_ || this, options);
1424
1425   // child is a component instance
1426   } else {
1427     component = child;
1428   }
1429
1430   this.children_.push(component);
1431
1432   if (typeof component.id === 'function') {
1433     this.childIndex_[component.id()] = component;
1434   }
1435
1436   // If a name wasn't used to create the component, check if we can use the
1437   // name function of the component
1438   componentName = componentName || (component.name && component.name());
1439
1440   if (componentName) {
1441     this.childNameIndex_[componentName] = component;
1442   }
1443
1444   // Add the UI object's element to the container div (box)
1445   // Having an element is not required
1446   if (typeof component['el'] === 'function' && component['el']()) {
1447     this.contentEl().appendChild(component['el']());
1448   }
1449
1450   // Return so it can stored on parent object if desired.
1451   return component;
1452 };
1453
1454 vjs.Component.prototype.removeChild = function(component){
1455   if (typeof component === 'string') {
1456     component = this.getChild(component);
1457   }
1458
1459   if (!component || !this.children_) return;
1460
1461   var childFound = false;
1462   for (var i = this.children_.length - 1; i >= 0; i--) {
1463     if (this.children_[i] === component) {
1464       childFound = true;
1465       this.children_.splice(i,1);
1466       break;
1467     }
1468   }
1469
1470   if (!childFound) return;
1471
1472   this.childIndex_[component.id] = null;
1473   this.childNameIndex_[component.name] = null;
1474
1475   var compEl = component.el();
1476   if (compEl && compEl.parentNode === this.contentEl()) {
1477     this.contentEl().removeChild(component.el());
1478   }
1479 };
1480
1481 /**
1482  * Initialize default child components from options
1483  */
1484 vjs.Component.prototype.initChildren = function(){
1485   var options = this.options_;
1486
1487   if (options && options['children']) {
1488     var self = this;
1489
1490     // Loop through components and add them to the player
1491     vjs.obj.each(options['children'], function(name, opts){
1492       // Allow for disabling default components
1493       // e.g. vjs.options['children']['posterImage'] = false
1494       if (opts === false) return;
1495
1496       // Allow waiting to add components until a specific event is called
1497       var tempAdd = function(){
1498         // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
1499         self[name] = self.addChild(name, opts);
1500       };
1501
1502       if (opts['loadEvent']) {
1503         // this.one(opts.loadEvent, tempAdd)
1504       } else {
1505         tempAdd();
1506       }
1507     });
1508   }
1509 };
1510
1511 vjs.Component.prototype.buildCSSClass = function(){
1512     // Child classes can include a function that does:
1513     // return 'CLASS NAME' + this._super();
1514     return '';
1515 };
1516
1517 /* Events
1518 ============================================================================= */
1519
1520 /**
1521  * Add an event listener to this component's element. Context will be the component.
1522  * @param  {String}   type Event type e.g. 'click'
1523  * @param  {Function} fn   Event listener
1524  * @return {vjs.Component}
1525  */
1526 vjs.Component.prototype.on = function(type, fn){
1527   vjs.on(this.el_, type, vjs.bind(this, fn));
1528   return this;
1529 };
1530
1531 /**
1532  * Remove an event listener from the component's element
1533  * @param  {String=}   type Optional event type. Without type it will remove all listeners.
1534  * @param  {Function=} fn   Optional event listener. Without fn it will remove all listeners for a type.
1535  * @return {vjs.Component}
1536  */
1537 vjs.Component.prototype.off = function(type, fn){
1538   vjs.off(this.el_, type, fn);
1539   return this;
1540 };
1541
1542 /**
1543  * Add an event listener to be triggered only once and then removed
1544  * @param  {String}   type Event type
1545  * @param  {Function} fn   Event listener
1546  * @return {vjs.Component}
1547  */
1548 vjs.Component.prototype.one = function(type, fn) {
1549   vjs.one(this.el_, type, vjs.bind(this, fn));
1550   return this;
1551 };
1552
1553 /**
1554  * Trigger an event on an element
1555  * @param  {String} type  Event type to trigger
1556  * @param  {Event|Object} event Event object to be passed to the listener
1557  * @return {vjs.Component}
1558  */
1559 vjs.Component.prototype.trigger = function(type, event){
1560   vjs.trigger(this.el_, type, event);
1561   return this;
1562 };
1563
1564 /* Ready
1565 ================================================================================ */
1566 /**
1567  * Is the component loaded.
1568  * @type {Boolean}
1569  * @private
1570  */
1571 vjs.Component.prototype.isReady_;
1572
1573 /**
1574  * Trigger ready as soon as initialization is finished.
1575  *   Allows for delaying ready. Override on a sub class prototype.
1576  *   If you set this.isReadyOnInitFinish_ it will affect all components.
1577  *   Specially used when waiting for the Flash player to asynchrnously load.
1578  *   @type {Boolean}
1579  *   @private
1580  */
1581 vjs.Component.prototype.isReadyOnInitFinish_ = true;
1582
1583 /**
1584  * List of ready listeners
1585  * @type {Array}
1586  * @private
1587  */
1588 vjs.Component.prototype.readyQueue_;
1589
1590 /**
1591  * Bind a listener to the component's ready state.
1592  *   Different from event listeners in that if the ready event has already happend
1593  *   it will trigger the function immediately.
1594  * @param  {Function} fn Ready listener
1595  * @return {vjs.Component}
1596  */
1597 vjs.Component.prototype.ready = function(fn){
1598   if (fn) {
1599     if (this.isReady_) {
1600       fn.call(this);
1601     } else {
1602       if (this.readyQueue_ === undefined) {
1603         this.readyQueue_ = [];
1604       }
1605       this.readyQueue_.push(fn);
1606     }
1607   }
1608   return this;
1609 };
1610
1611 /**
1612  * Trigger the ready listeners
1613  * @return {vjs.Component}
1614  */
1615 vjs.Component.prototype.triggerReady = function(){
1616   this.isReady_ = true;
1617
1618   var readyQueue = this.readyQueue_;
1619
1620   if (readyQueue && readyQueue.length > 0) {
1621
1622     for (var i = 0, j = readyQueue.length; i < j; i++) {
1623       readyQueue[i].call(this);
1624     }
1625
1626     // Reset Ready Queue
1627     this.readyQueue_ = [];
1628
1629     // Allow for using event listeners also, in case you want to do something everytime a source is ready.
1630     this.trigger('ready');
1631   }
1632 };
1633
1634 /* Display
1635 ============================================================================= */
1636
1637 /**
1638  * Add a CSS class name to the component's element
1639  * @param {String} classToAdd Classname to add
1640  * @return {vjs.Component}
1641  */
1642 vjs.Component.prototype.addClass = function(classToAdd){
1643   vjs.addClass(this.el_, classToAdd);
1644   return this;
1645 };
1646
1647 /**
1648  * Remove a CSS class name from the component's element
1649  * @param {String} classToRemove Classname to remove
1650  * @return {vjs.Component}
1651  */
1652 vjs.Component.prototype.removeClass = function(classToRemove){
1653   vjs.removeClass(this.el_, classToRemove);
1654   return this;
1655 };
1656
1657 /**
1658  * Show the component element if hidden
1659  * @return {vjs.Component}
1660  */
1661 vjs.Component.prototype.show = function(){
1662   this.el_.style.display = 'block';
1663   return this;
1664 };
1665
1666 /**
1667  * Hide the component element if hidden
1668  * @return {vjs.Component}
1669  */
1670 vjs.Component.prototype.hide = function(){
1671   this.el_.style.display = 'none';
1672   return this;
1673 };
1674
1675 /**
1676  * Fade a component in using CSS
1677  * @return {vjs.Component}
1678  */
1679 vjs.Component.prototype.fadeIn = function(){
1680   this.removeClass('vjs-fade-out');
1681   this.addClass('vjs-fade-in');
1682   return this;
1683 };
1684
1685 /**
1686  * Fade a component out using CSS
1687  * @return {vjs.Component}
1688  */
1689 vjs.Component.prototype.fadeOut = function(){
1690   this.removeClass('vjs-fade-in');
1691   this.addClass('vjs-fade-out');
1692   return this;
1693 };
1694
1695 /**
1696  * Lock an item in its visible state. To be used with fadeIn/fadeOut.
1697  * @return {vjs.Component}
1698  */
1699 vjs.Component.prototype.lockShowing = function(){
1700   this.addClass('vjs-lock-showing');
1701   return this;
1702 };
1703
1704 /**
1705  * Unlock an item to be hidden. To be used with fadeIn/fadeOut.
1706  * @return {vjs.Component}
1707  */
1708 vjs.Component.prototype.unlockShowing = function(){
1709   this.removeClass('vjs-lock-showing');
1710   return this;
1711 };
1712
1713 /**
1714  * Disable component by making it unshowable
1715  */
1716 vjs.Component.prototype.disable = function(){
1717   this.hide();
1718   this.show = function(){};
1719   this.fadeIn = function(){};
1720 };
1721
1722 // TODO: Get enable working
1723 // vjs.Component.prototype.enable = function(){
1724 //   this.fadeIn = vjs.Component.prototype.fadeIn;
1725 //   this.show = vjs.Component.prototype.show;
1726 // };
1727
1728 /**
1729  * If a value is provided it will change the width of the player to that value
1730  * otherwise the width is returned
1731  * http://dev.w3.org/html5/spec/dimension-attributes.html#attr-dim-height
1732  * Video tag width/height only work in pixels. No percents.
1733  * But allowing limited percents use. e.g. width() will return number+%, not computed width
1734  * @param  {Number|String=} num   Optional width number
1735  * @param  {[type]} skipListeners Skip the 'resize' event trigger
1736  * @return {vjs.Component|Number|String} Returns 'this' if dimension was set.
1737  *   Otherwise it returns the dimension.
1738  */
1739 vjs.Component.prototype.width = function(num, skipListeners){
1740   return this.dimension('width', num, skipListeners);
1741 };
1742
1743 /**
1744  * Get or set the height of the player
1745  * @param  {Number|String=} num     Optional new player height
1746  * @param  {Boolean=} skipListeners Optional skip resize event trigger
1747  * @return {vjs.Component|Number|String} The player, or the dimension
1748  */
1749 vjs.Component.prototype.height = function(num, skipListeners){
1750   return this.dimension('height', num, skipListeners);
1751 };
1752
1753 /**
1754  * Set both width and height at the same time.
1755  * @param  {Number|String} width
1756  * @param  {Number|String} height
1757  * @return {vjs.Component}   The player.
1758  */
1759 vjs.Component.prototype.dimensions = function(width, height){
1760   // Skip resize listeners on width for optimization
1761   return this.width(width, true).height(height);
1762 };
1763
1764 /**
1765  * Get or set width or height.
1766  * All for an integer, integer + 'px' or integer + '%';
1767  * Known issue: hidden elements. Hidden elements officially have a width of 0.
1768  * So we're defaulting to the style.width value and falling back to computedStyle
1769  * which has the hidden element issue.
1770  * Info, but probably not an efficient fix:
1771  * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
1772  * @param  {String=} widthOrHeight 'width' or 'height'
1773  * @param  {Number|String=} num           New dimension
1774  * @param  {Boolean=} skipListeners Skip resize event trigger
1775  * @return {vjs.Component|Number|String} Return the player if setting a dimension.
1776  *                                         Otherwise it returns the dimension.
1777  */
1778 vjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){
1779   if (num !== undefined) {
1780
1781     // Check if using css width/height (% or px) and adjust
1782     if ((''+num).indexOf('%') !== -1 || (''+num).indexOf('px') !== -1) {
1783       this.el_.style[widthOrHeight] = num;
1784     } else if (num === 'auto') {
1785       this.el_.style[widthOrHeight] = '';
1786     } else {
1787       this.el_.style[widthOrHeight] = num+'px';
1788     }
1789
1790     // skipListeners allows us to avoid triggering the resize event when setting both width and height
1791     if (!skipListeners) { this.trigger('resize'); }
1792
1793     // Return component
1794     return this;
1795   }
1796
1797   // Not setting a value, so getting it
1798   // Make sure element exists
1799   if (!this.el_) return 0;
1800
1801   // Get dimension value from style
1802   var val = this.el_.style[widthOrHeight];
1803   var pxIndex = val.indexOf('px');
1804   if (pxIndex !== -1) {
1805     // Return the pixel value with no 'px'
1806     return parseInt(val.slice(0,pxIndex), 10);
1807
1808   // No px so using % or no style was set, so falling back to offsetWidth/height
1809   // If component has display:none, offset will return 0
1810   // TODO: handle display:none and no dimension style using px
1811   } else {
1812
1813     return parseInt(this.el_['offset'+vjs.capitalize(widthOrHeight)], 10);
1814
1815     // ComputedStyle version.
1816     // Only difference is if the element is hidden it will return
1817     // the percent value (e.g. '100%'')
1818     // instead of zero like offsetWidth returns.
1819     // var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);
1820     // var pxIndex = val.indexOf('px');
1821
1822     // if (pxIndex !== -1) {
1823     //   return val.slice(0, pxIndex);
1824     // } else {
1825     //   return val;
1826     // }
1827   }
1828 };
1829 /* Button - Base class for all buttons
1830 ================================================================================ */
1831 /**
1832  * Base class for all buttons
1833  * @param {vjs.Player|Object} player
1834  * @param {Object=} options
1835  * @constructor
1836  */
1837 vjs.Button = vjs.Component.extend({
1838   /** @constructor */
1839   init: function(player, options){
1840     vjs.Component.call(this, player, options);
1841
1842     var touchstart = false;
1843     this.on('touchstart', function() {
1844       touchstart = true;
1845     });
1846     this.on('touchmove', function() {
1847       touchstart = false;
1848     });
1849     var self = this;
1850     this.on('touchend', function(event) {
1851       if (touchstart) {
1852         self.onClick(event);
1853       }
1854       event.preventDefault();
1855       event.stopPropagation();
1856     });
1857
1858     this.on('click', this.onClick);
1859     this.on('focus', this.onFocus);
1860     this.on('blur', this.onBlur);
1861   }
1862 });
1863
1864 vjs.Button.prototype.createEl = function(type, props){
1865   // Add standard Aria and Tabindex info
1866   props = vjs.obj.merge({
1867     className: this.buildCSSClass(),
1868     innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + (this.buttonText || 'Need Text') + '</span></div>',
1869     role: 'button',
1870     'aria-live': 'polite', // let the screen reader user know that the text of the button may change
1871     tabIndex: 0
1872   }, props);
1873
1874   return vjs.Component.prototype.createEl.call(this, type, props);
1875 };
1876
1877 vjs.Button.prototype.buildCSSClass = function(){
1878   // TODO: Change vjs-control to vjs-button?
1879   return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);
1880 };
1881
1882   // Click - Override with specific functionality for button
1883 vjs.Button.prototype.onClick = function(){};
1884
1885   // Focus - Add keyboard functionality to element
1886 vjs.Button.prototype.onFocus = function(){
1887   vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
1888 };
1889
1890   // KeyPress (document level) - Trigger click when keys are pressed
1891 vjs.Button.prototype.onKeyPress = function(event){
1892   // Check for space bar (32) or enter (13) keys
1893   if (event.which == 32 || event.which == 13) {
1894     event.preventDefault();
1895     this.onClick();
1896   }
1897 };
1898
1899 // Blur - Remove keyboard triggers
1900 vjs.Button.prototype.onBlur = function(){
1901   vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
1902 };
1903 /* Slider
1904 ================================================================================ */
1905 /**
1906  * Parent for seek bar and volume slider
1907  * @param {vjs.Player|Object} player
1908  * @param {Object=} options
1909  * @constructor
1910  */
1911 vjs.Slider = vjs.Component.extend({
1912   /** @constructor */
1913   init: function(player, options){
1914     vjs.Component.call(this, player, options);
1915
1916     // Set property names to bar and handle to match with the child Slider class is looking for
1917     this.bar = this.getChild(this.options_['barName']);
1918     this.handle = this.getChild(this.options_['handleName']);
1919
1920     player.on(this.playerEvent, vjs.bind(this, this.update));
1921
1922     this.on('mousedown', this.onMouseDown);
1923     this.on('touchstart', this.onMouseDown);
1924     this.on('focus', this.onFocus);
1925     this.on('blur', this.onBlur);
1926     this.on('click', this.onClick);
1927
1928     this.player_.on('controlsvisible', vjs.bind(this, this.update));
1929
1930     // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
1931     // this.player_.one('timeupdate', vjs.bind(this, this.update));
1932
1933     player.ready(vjs.bind(this, this.update));
1934
1935     this.boundEvents = {};
1936   }
1937 });
1938
1939 vjs.Slider.prototype.createEl = function(type, props) {
1940   props = props || {};
1941   // Add the slider element class to all sub classes
1942   props.className = props.className + ' vjs-slider';
1943   props = vjs.obj.merge({
1944     role: 'slider',
1945     'aria-valuenow': 0,
1946     'aria-valuemin': 0,
1947     'aria-valuemax': 100,
1948     tabIndex: 0
1949   }, props);
1950
1951   return vjs.Component.prototype.createEl.call(this, type, props);
1952 };
1953
1954 vjs.Slider.prototype.onMouseDown = function(event){
1955   event.preventDefault();
1956   vjs.blockTextSelection();
1957
1958   this.boundEvents.move = vjs.bind(this, this.onMouseMove);
1959   this.boundEvents.end = vjs.bind(this, this.onMouseUp);
1960
1961   vjs.on(document, 'mousemove', this.boundEvents.move);
1962   vjs.on(document, 'mouseup', this.boundEvents.end);
1963   vjs.on(document, 'touchmove', this.boundEvents.move);
1964   vjs.on(document, 'touchend', this.boundEvents.end);
1965
1966   this.onMouseMove(event);
1967 };
1968
1969 vjs.Slider.prototype.onMouseUp = function() {
1970   vjs.unblockTextSelection();
1971   vjs.off(document, 'mousemove', this.boundEvents.move, false);
1972   vjs.off(document, 'mouseup', this.boundEvents.end, false);
1973   vjs.off(document, 'touchmove', this.boundEvents.move, false);
1974   vjs.off(document, 'touchend', this.boundEvents.end, false);
1975
1976   this.update();
1977 };
1978
1979 vjs.Slider.prototype.update = function(){
1980   // In VolumeBar init we have a setTimeout for update that pops and update to the end of the
1981   // execution stack. The player is destroyed before then update will cause an error
1982   if (!this.el_) return;
1983
1984   // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
1985   // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
1986   // var progress =  (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
1987
1988   var barProgress,
1989       progress = this.getPercent(),
1990       handle = this.handle,
1991       bar = this.bar;
1992
1993   // Protect against no duration and other division issues
1994   if (isNaN(progress)) { progress = 0; }
1995
1996   barProgress = progress;
1997
1998   // If there is a handle, we need to account for the handle in our calculation for progress bar
1999   // so that it doesn't fall short of or extend past the handle.
2000   if (handle) {
2001
2002     var box = this.el_,
2003         boxWidth = box.offsetWidth,
2004
2005         handleWidth = handle.el().offsetWidth,
2006
2007         // The width of the handle in percent of the containing box
2008         // In IE, widths may not be ready yet causing NaN
2009         handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
2010
2011         // Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
2012         // There is a margin of half the handle's width on both sides.
2013         boxAdjustedPercent = 1 - handlePercent,
2014
2015         // Adjust the progress that we'll use to set widths to the new adjusted box width
2016         adjustedProgress = progress * boxAdjustedPercent;
2017
2018     // The bar does reach the left side, so we need to account for this in the bar's width
2019     barProgress = adjustedProgress + (handlePercent / 2);
2020
2021     // Move the handle from the left based on the adjected progress
2022     handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';
2023   }
2024
2025   // Set the new bar width
2026   bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
2027 };
2028
2029 vjs.Slider.prototype.calculateDistance = function(event){
2030   var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;
2031
2032   el = this.el_;
2033   box = vjs.findPosition(el);
2034   boxW = boxH = el.offsetWidth;
2035   handle = this.handle;
2036
2037   if (this.options_.vertical) {
2038     boxY = box.top;
2039
2040     if (event.changedTouches) {
2041       pageY = event.changedTouches[0].pageY;
2042     } else {
2043       pageY = event.pageY;
2044     }
2045
2046     if (handle) {
2047       var handleH = handle.el().offsetHeight;
2048       // Adjusted X and Width, so handle doesn't go outside the bar
2049       boxY = boxY + (handleH / 2);
2050       boxH = boxH - handleH;
2051     }
2052
2053     // Percent that the click is through the adjusted area
2054     return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
2055
2056   } else {
2057     boxX = box.left;
2058
2059     if (event.changedTouches) {
2060       pageX = event.changedTouches[0].pageX;
2061     } else {
2062       pageX = event.pageX;
2063     }
2064
2065     if (handle) {
2066       var handleW = handle.el().offsetWidth;
2067
2068       // Adjusted X and Width, so handle doesn't go outside the bar
2069       boxX = boxX + (handleW / 2);
2070       boxW = boxW - handleW;
2071     }
2072
2073     // Percent that the click is through the adjusted area
2074     return Math.max(0, Math.min(1, (pageX - boxX) / boxW));
2075   }
2076 };
2077
2078 vjs.Slider.prototype.onFocus = function(){
2079   vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
2080 };
2081
2082 vjs.Slider.prototype.onKeyPress = function(event){
2083   if (event.which == 37) { // Left Arrow
2084     event.preventDefault();
2085     this.stepBack();
2086   } else if (event.which == 39) { // Right Arrow
2087     event.preventDefault();
2088     this.stepForward();
2089   }
2090 };
2091
2092 vjs.Slider.prototype.onBlur = function(){
2093   vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
2094 };
2095
2096 /**
2097  * Listener for click events on slider, used to prevent clicks
2098  *   from bubbling up to parent elements like button menus.
2099  * @param  {Object} event Event object
2100  */
2101 vjs.Slider.prototype.onClick = function(event){
2102   event.stopImmediatePropagation();
2103   event.preventDefault();
2104 };
2105
2106 /**
2107  * SeekBar Behavior includes play progress bar, and seek handle
2108  * Needed so it can determine seek position based on handle position/size
2109  * @param {vjs.Player|Object} player
2110  * @param {Object=} options
2111  * @constructor
2112  */
2113 vjs.SliderHandle = vjs.Component.extend();
2114
2115 /**
2116  * Default value of the slider
2117  * @type {Number}
2118  */
2119 vjs.SliderHandle.prototype.defaultValue = 0;
2120
2121 /** @inheritDoc */
2122 vjs.SliderHandle.prototype.createEl = function(type, props) {
2123   props = props || {};
2124   // Add the slider element class to all sub classes
2125   props.className = props.className + ' vjs-slider-handle';
2126   props = vjs.obj.merge({
2127     innerHTML: '<span class="vjs-control-text">'+this.defaultValue+'</span>'
2128   }, props);
2129
2130   return vjs.Component.prototype.createEl.call(this, 'div', props);
2131 };
2132 /* Menu
2133 ================================================================================ */
2134 /**
2135  * The base for text track and settings menu buttons.
2136  * @param {vjs.Player|Object} player
2137  * @param {Object=} options
2138  * @constructor
2139  */
2140 vjs.Menu = vjs.Component.extend();
2141
2142 /**
2143  * Add a menu item to the menu
2144  * @param {Object|String} component Component or component type to add
2145  */
2146 vjs.Menu.prototype.addItem = function(component){
2147   this.addChild(component);
2148   component.on('click', vjs.bind(this, function(){
2149     this.unlockShowing();
2150   }));
2151 };
2152
2153 /** @inheritDoc */
2154 vjs.Menu.prototype.createEl = function(){
2155   var contentElType = this.options().contentElType || 'ul';
2156   this.contentEl_ = vjs.createEl(contentElType, {
2157     className: 'vjs-menu-content'
2158   });
2159   var el = vjs.Component.prototype.createEl.call(this, 'div', {
2160     append: this.contentEl_,
2161     className: 'vjs-menu'
2162   });
2163   el.appendChild(this.contentEl_);
2164
2165   // Prevent clicks from bubbling up. Needed for Menu Buttons,
2166   // where a click on the parent is significant
2167   vjs.on(el, 'click', function(event){
2168     event.preventDefault();
2169     event.stopImmediatePropagation();
2170   });
2171
2172   return el;
2173 };
2174
2175 /**
2176  * Menu item
2177  * @param {vjs.Player|Object} player
2178  * @param {Object=} options
2179  * @constructor
2180  */
2181 vjs.MenuItem = vjs.Button.extend({
2182   /** @constructor */
2183   init: function(player, options){
2184     vjs.Button.call(this, player, options);
2185     this.selected(options['selected']);
2186   }
2187 });
2188
2189 /** @inheritDoc */
2190 vjs.MenuItem.prototype.createEl = function(type, props){
2191   return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({
2192     className: 'vjs-menu-item',
2193     innerHTML: this.options_['label']
2194   }, props));
2195 };
2196
2197 /** @inheritDoc */
2198 vjs.MenuItem.prototype.onClick = function(){
2199   this.selected(true);
2200 };
2201
2202 /**
2203  * Set this menu item as selected or not
2204  * @param  {Boolean} selected
2205  */
2206 vjs.MenuItem.prototype.selected = function(selected){
2207   if (selected) {
2208     this.addClass('vjs-selected');
2209     this.el_.setAttribute('aria-selected',true);
2210   } else {
2211     this.removeClass('vjs-selected');
2212     this.el_.setAttribute('aria-selected',false);
2213   }
2214 };
2215
2216
2217 /**
2218  * A button class with a popup menu
2219  * @param {vjs.Player|Object} player
2220  * @param {Object=} options
2221  * @constructor
2222  */
2223 vjs.MenuButton = vjs.Button.extend({
2224   /** @constructor */
2225   init: function(player, options){
2226     vjs.Button.call(this, player, options);
2227
2228     this.menu = this.createMenu();
2229
2230     // Add list to element
2231     this.addChild(this.menu);
2232
2233     // Automatically hide empty menu buttons
2234     if (this.items && this.items.length === 0) {
2235       this.hide();
2236     }
2237
2238     this.on('keyup', this.onKeyPress);
2239     this.el_.setAttribute('aria-haspopup', true);
2240     this.el_.setAttribute('role', 'button');
2241   }
2242 });
2243
2244 /**
2245  * Track the state of the menu button
2246  * @type {Boolean}
2247  */
2248 vjs.MenuButton.prototype.buttonPressed_ = false;
2249
2250 vjs.MenuButton.prototype.createMenu = function(){
2251   var menu = new vjs.Menu(this.player_);
2252
2253   // Add a title list item to the top
2254   if (this.options().title) {
2255     menu.el().appendChild(vjs.createEl('li', {
2256       className: 'vjs-menu-title',
2257       innerHTML: vjs.capitalize(this.kind_),
2258       tabindex: -1
2259     }));
2260   }
2261
2262   this.items = this.createItems();
2263
2264   if (this.items) {
2265     // Add menu items to the menu
2266     for (var i = 0; i < this.items.length; i++) {
2267       menu.addItem(this.items[i]);
2268     }
2269   }
2270
2271   return menu;
2272 };
2273
2274 /**
2275  * Create the list of menu items. Specific to each subclass.
2276  */
2277 vjs.MenuButton.prototype.createItems = function(){};
2278
2279 /** @inheritDoc */
2280 vjs.MenuButton.prototype.buildCSSClass = function(){
2281   return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);
2282 };
2283
2284 // Focus - Add keyboard functionality to element
2285 // This function is not needed anymore. Instead, the keyboard functionality is handled by
2286 // treating the button as triggering a submenu. When the button is pressed, the submenu
2287 // appears. Pressing the button again makes the submenu disappear.
2288 vjs.MenuButton.prototype.onFocus = function(){};
2289 // Can't turn off list display that we turned on with focus, because list would go away.
2290 vjs.MenuButton.prototype.onBlur = function(){};
2291
2292 vjs.MenuButton.prototype.onClick = function(){
2293   // When you click the button it adds focus, which will show the menu indefinitely.
2294   // So we'll remove focus when the mouse leaves the button.
2295   // Focus is needed for tab navigation.
2296   this.one('mouseout', vjs.bind(this, function(){
2297     this.menu.unlockShowing();
2298     this.el_.blur();
2299   }));
2300   if (this.buttonPressed_){
2301     this.unpressButton();
2302   } else {
2303     this.pressButton();
2304   }
2305 };
2306
2307 vjs.MenuButton.prototype.onKeyPress = function(event){
2308   event.preventDefault();
2309
2310   // Check for space bar (32) or enter (13) keys
2311   if (event.which == 32 || event.which == 13) {
2312     if (this.buttonPressed_){
2313       this.unpressButton();
2314     } else {
2315       this.pressButton();
2316     }
2317   // Check for escape (27) key
2318   } else if (event.which == 27){
2319     if (this.buttonPressed_){
2320       this.unpressButton();
2321     }
2322   }
2323 };
2324
2325 vjs.MenuButton.prototype.pressButton = function(){
2326   this.buttonPressed_ = true;
2327   this.menu.lockShowing();
2328   this.el_.setAttribute('aria-pressed', true);
2329   if (this.items && this.items.length > 0) {
2330     this.items[0].el().focus(); // set the focus to the title of the submenu
2331   }
2332 };
2333
2334 vjs.MenuButton.prototype.unpressButton = function(){
2335   this.buttonPressed_ = false;
2336   this.menu.unlockShowing();
2337   this.el_.setAttribute('aria-pressed', false);
2338 };
2339
2340 /**
2341  * Main player class. A player instance is returned by _V_(id);
2342  * @param {Element} tag        The original video tag used for configuring options
2343  * @param {Object=} options    Player options
2344  * @param {Function=} ready    Ready callback function
2345  * @constructor
2346  */
2347 vjs.Player = vjs.Component.extend({
2348   /** @constructor */
2349   init: function(tag, options, ready){
2350     this.tag = tag; // Store the original tag used to set options
2351
2352     // Set Options
2353     // The options argument overrides options set in the video tag
2354     // which overrides globally set options.
2355     // This latter part coincides with the load order
2356     // (tag must exist before Player)
2357     options = vjs.obj.merge(this.getTagSettings(tag), options);
2358
2359     // Cache for video property values.
2360     this.cache_ = {};
2361
2362     // Set poster
2363     this.poster_ = options['poster'];
2364     // Set controls
2365     this.controls_ = options['controls'];
2366     // Use native controls for iOS and Android by default
2367     //  until controls are more stable on those devices.
2368     if (options['customControlsOnMobile'] !== true && (vjs.IS_IOS || vjs.IS_ANDROID)) {
2369       tag.controls = options['controls'];
2370       this.controls_ = false;
2371     } else {
2372       // Original tag settings stored in options
2373       // now remove immediately so native controls don't flash.
2374       tag.controls = false;
2375     }
2376
2377     // Run base component initializing with new options.
2378     // Builds the element through createEl()
2379     // Inits and embeds any child components in opts
2380     vjs.Component.call(this, this, options, ready);
2381
2382     // Firstplay event implimentation. Not sold on the event yet.
2383     // Could probably just check currentTime==0?
2384     this.one('play', function(e){
2385       var fpEvent = { type: 'firstplay', target: this.el_ };
2386       // Using vjs.trigger so we can check if default was prevented
2387       var keepGoing = vjs.trigger(this.el_, fpEvent);
2388
2389       if (!keepGoing) {
2390         e.preventDefault();
2391         e.stopPropagation();
2392         e.stopImmediatePropagation();
2393       }
2394     });
2395
2396     this.on('ended', this.onEnded);
2397     this.on('play', this.onPlay);
2398     this.on('firstplay', this.onFirstPlay);
2399     this.on('pause', this.onPause);
2400     this.on('progress', this.onProgress);
2401     this.on('durationchange', this.onDurationChange);
2402     this.on('error', this.onError);
2403     this.on('fullscreenchange', this.onFullscreenChange);
2404
2405     // Make player easily findable by ID
2406     vjs.players[this.id_] = this;
2407
2408     if (options['plugins']) {
2409       vjs.obj.each(options['plugins'], function(key, val){
2410         this[key](val);
2411       }, this);
2412     }
2413   }
2414 });
2415
2416 /**
2417  * Player instance options, surfaced using vjs.options
2418  * vjs.options = vjs.Player.prototype.options_
2419  * Make changes in vjs.options, not here.
2420  * All options should use string keys so they avoid
2421  * renaming by closure compiler
2422  * @type {Object}
2423  * @private
2424  */
2425 vjs.Player.prototype.options_ = vjs.options;
2426
2427 vjs.Player.prototype.dispose = function(){
2428   // this.isReady_ = false;
2429
2430   // Kill reference to this player
2431   vjs.players[this.id_] = null;
2432   if (this.tag && this.tag['player']) { this.tag['player'] = null; }
2433   if (this.el_ && this.el_['player']) { this.el_['player'] = null; }
2434
2435   // Ensure that tracking progress and time progress will stop and plater deleted
2436   this.stopTrackingProgress();
2437   this.stopTrackingCurrentTime();
2438
2439   if (this.tech) { this.tech.dispose(); }
2440
2441   // Component dispose
2442   vjs.Component.prototype.dispose.call(this);
2443 };
2444
2445 vjs.Player.prototype.getTagSettings = function(tag){
2446   var options = {
2447     'sources': [],
2448     'tracks': []
2449   };
2450
2451   vjs.obj.merge(options, vjs.getAttributeValues(tag));
2452
2453   // Get tag children settings
2454   if (tag.hasChildNodes()) {
2455     var child, childName,
2456         children = tag.childNodes,
2457         i = 0,
2458         j = children.length;
2459
2460     for (; i < j; i++) {
2461       child = children[i];
2462       // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
2463       childName = child.nodeName.toLowerCase();
2464
2465       if (childName === 'source') {
2466         options['sources'].push(vjs.getAttributeValues(child));
2467
2468       } else if (childName === 'track') {
2469         options['tracks'].push(vjs.getAttributeValues(child));
2470
2471       }
2472     }
2473   }
2474
2475   return options;
2476 };
2477
2478 vjs.Player.prototype.createEl = function(){
2479   var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
2480   var tag = this.tag;
2481
2482   // Remove width/height attrs from tag so CSS can make it 100% width/height
2483   tag.removeAttribute('width');
2484   tag.removeAttribute('height');
2485   // Empty video tag sources and tracks so the built-in player doesn't use them also.
2486   // This may not be fast enough to stop HTML5 browsers from reading the tags
2487   // so we'll need to turn off any default tracks if we're manually doing
2488   // captions and subtitles. videoElement.textTracks
2489   if (tag.hasChildNodes()) {
2490     var nrOfChildNodes = tag.childNodes.length;
2491     for (var i=0,j=tag.childNodes;i<nrOfChildNodes;i++) {
2492       if (j[0].nodeName.toLowerCase() == 'source' || j[0].nodeName.toLowerCase() == 'track') {
2493         tag.removeChild(j[0]);
2494       }
2495     }
2496   }
2497
2498   // Make sure tag ID exists
2499   tag.id = tag.id || 'vjs_video_' + vjs.guid++;
2500
2501   // Give video tag ID and class to player div
2502   // ID will now reference player box, not the video tag
2503   el.id = tag.id;
2504   el.className = tag.className;
2505
2506   // Update tag id/class for use as HTML5 playback tech
2507   // Might think we should do this after embedding in container so .vjs-tech class
2508   // doesn't flash 100% width/height, but class only applies with .video-js parent
2509   tag.id += '_html5_api';
2510   tag.className = 'vjs-tech';
2511
2512   // Make player findable on elements
2513   tag['player'] = el['player'] = this;
2514   // Default state of video is paused
2515   this.addClass('vjs-paused');
2516
2517   // Make box use width/height of tag, or rely on default implementation
2518   // Enforce with CSS since width/height attrs don't work on divs
2519   this.width(this.options_['width'], true); // (true) Skip resize listener on load
2520   this.height(this.options_['height'], true);
2521
2522   // Wrap video tag in div (el/box) container
2523   if (tag.parentNode) {
2524     tag.parentNode.insertBefore(el, tag);
2525   }
2526   vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
2527
2528   return el;
2529 };
2530
2531 // /* Media Technology (tech)
2532 // ================================================================================ */
2533 // Load/Create an instance of playback technlogy including element and API methods
2534 // And append playback element in player div.
2535 vjs.Player.prototype.loadTech = function(techName, source){
2536
2537   // Pause and remove current playback technology
2538   if (this.tech) {
2539     this.unloadTech();
2540
2541   // If the first time loading, HTML5 tag will exist but won't be initialized
2542   // So we need to remove it if we're not loading HTML5
2543   } else if (techName !== 'Html5' && this.tag) {
2544     this.el_.removeChild(this.tag);
2545     this.tag.player = null;
2546     this.tag = null;
2547   }
2548
2549   this.techName = techName;
2550
2551   // Turn off API access because we're loading a new tech that might load asynchronously
2552   this.isReady_ = false;
2553
2554   var techReady = function(){
2555     this.player_.triggerReady();
2556
2557     // Manually track progress in cases where the browser/flash player doesn't report it.
2558     if (!this.features.progressEvents) {
2559       this.player_.manualProgressOn();
2560     }
2561
2562     // Manually track timeudpates in cases where the browser/flash player doesn't report it.
2563     if (!this.features.timeupdateEvents) {
2564       this.player_.manualTimeUpdatesOn();
2565     }
2566   };
2567
2568   // Grab tech-specific options from player options and add source and parent element to use.
2569   var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
2570
2571   if (source) {
2572     if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
2573       techOptions['startTime'] = this.cache_.currentTime;
2574     }
2575
2576     this.cache_.src = source.src;
2577   }
2578
2579   // Initialize tech instance
2580   this.tech = new window['videojs'][techName](this, techOptions);
2581
2582   this.tech.ready(techReady);
2583 };
2584
2585 vjs.Player.prototype.unloadTech = function(){
2586   this.isReady_ = false;
2587   this.tech.dispose();
2588
2589   // Turn off any manual progress or timeupdate tracking
2590   if (this.manualProgress) { this.manualProgressOff(); }
2591
2592   if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
2593
2594   this.tech = false;
2595 };
2596
2597 // There's many issues around changing the size of a Flash (or other plugin) object.
2598 // First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
2599 // Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
2600 // To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
2601 // reloadTech: function(betweenFn){
2602 //   vjs.log('unloadingTech')
2603 //   this.unloadTech();
2604 //   vjs.log('unloadedTech')
2605 //   if (betweenFn) { betweenFn.call(); }
2606 //   vjs.log('LoadingTech')
2607 //   this.loadTech(this.techName, { src: this.cache_.src })
2608 //   vjs.log('loadedTech')
2609 // },
2610
2611 /* Fallbacks for unsupported event types
2612 ================================================================================ */
2613 // Manually trigger progress events based on changes to the buffered amount
2614 // Many flash players and older HTML5 browsers don't send progress or progress-like events
2615 vjs.Player.prototype.manualProgressOn = function(){
2616   this.manualProgress = true;
2617
2618   // Trigger progress watching when a source begins loading
2619   this.trackProgress();
2620
2621   // Watch for a native progress event call on the tech element
2622   // In HTML5, some older versions don't support the progress event
2623   // So we're assuming they don't, and turning off manual progress if they do.
2624   // As opposed to doing user agent detection
2625   this.tech.one('progress', function(){
2626
2627     // Update known progress support for this playback technology
2628     this.features.progressEvents = true;
2629
2630     // Turn off manual progress tracking
2631     this.player_.manualProgressOff();
2632   });
2633 };
2634
2635 vjs.Player.prototype.manualProgressOff = function(){
2636   this.manualProgress = false;
2637   this.stopTrackingProgress();
2638 };
2639
2640 vjs.Player.prototype.trackProgress = function(){
2641
2642   this.progressInterval = setInterval(vjs.bind(this, function(){
2643     // Don't trigger unless buffered amount is greater than last time
2644     // log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
2645     /* TODO: update for multiple buffered regions */
2646     if (this.cache_.bufferEnd < this.buffered().end(0)) {
2647       this.trigger('progress');
2648     } else if (this.bufferedPercent() == 1) {
2649       this.stopTrackingProgress();
2650       this.trigger('progress'); // Last update
2651     }
2652   }), 500);
2653 };
2654 vjs.Player.prototype.stopTrackingProgress = function(){ clearInterval(this.progressInterval); };
2655
2656 /* Time Tracking -------------------------------------------------------------- */
2657 vjs.Player.prototype.manualTimeUpdatesOn = function(){
2658   this.manualTimeUpdates = true;
2659
2660   this.on('play', this.trackCurrentTime);
2661   this.on('pause', this.stopTrackingCurrentTime);
2662   // timeupdate is also called by .currentTime whenever current time is set
2663
2664   // Watch for native timeupdate event
2665   this.tech.one('timeupdate', function(){
2666     // Update known progress support for this playback technology
2667     this.features.timeupdateEvents = true;
2668     // Turn off manual progress tracking
2669     this.player_.manualTimeUpdatesOff();
2670   });
2671 };
2672
2673 vjs.Player.prototype.manualTimeUpdatesOff = function(){
2674   this.manualTimeUpdates = false;
2675   this.stopTrackingCurrentTime();
2676   this.off('play', this.trackCurrentTime);
2677   this.off('pause', this.stopTrackingCurrentTime);
2678 };
2679
2680 vjs.Player.prototype.trackCurrentTime = function(){
2681   if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
2682   this.currentTimeInterval = setInterval(vjs.bind(this, function(){
2683     this.trigger('timeupdate');
2684   }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
2685 };
2686
2687 // Turn off play progress tracking (when paused or dragging)
2688 vjs.Player.prototype.stopTrackingCurrentTime = function(){ clearInterval(this.currentTimeInterval); };
2689
2690 // /* Player event handlers (how the player reacts to certain events)
2691 // ================================================================================ */
2692 vjs.Player.prototype.onEnded = function(){
2693   if (this.options_['loop']) {
2694     this.currentTime(0);
2695     this.play();
2696   }
2697 };
2698
2699 vjs.Player.prototype.onPlay = function(){
2700   vjs.removeClass(this.el_, 'vjs-paused');
2701   vjs.addClass(this.el_, 'vjs-playing');
2702 };
2703
2704 vjs.Player.prototype.onFirstPlay = function(){
2705     //If the first starttime attribute is specified
2706     //then we will start at the given offset in seconds
2707     if(this.options_['starttime']){
2708       this.currentTime(this.options_['starttime']);
2709     }
2710 };
2711
2712 vjs.Player.prototype.onPause = function(){
2713   vjs.removeClass(this.el_, 'vjs-playing');
2714   vjs.addClass(this.el_, 'vjs-paused');
2715 };
2716
2717 vjs.Player.prototype.onProgress = function(){
2718   // Add custom event for when source is finished downloading.
2719   if (this.bufferedPercent() == 1) {
2720     this.trigger('loadedalldata');
2721   }
2722 };
2723
2724 // Update duration with durationchange event
2725 // Allows for cacheing value instead of asking player each time.
2726 vjs.Player.prototype.onDurationChange = function(){
2727   this.duration(this.techGet('duration'));
2728 };
2729
2730 vjs.Player.prototype.onError = function(e) {
2731   vjs.log('Video Error', e);
2732 };
2733
2734 vjs.Player.prototype.onFullscreenChange = function(e) {
2735   if (this.isFullScreen) {
2736     this.addClass('vjs-fullscreen');
2737   } else {
2738     this.removeClass('vjs-fullscreen');
2739   }
2740 };
2741
2742 // /* Player API
2743 // ================================================================================ */
2744
2745 /**
2746  * Object for cached values.
2747  * @private
2748  */
2749 vjs.Player.prototype.cache_;
2750
2751 vjs.Player.prototype.getCache = function(){
2752   return this.cache_;
2753 };
2754
2755 // Pass values to the playback tech
2756 vjs.Player.prototype.techCall = function(method, arg){
2757   // If it's not ready yet, call method when it is
2758   if (this.tech && this.tech.isReady_) {
2759     this.tech.ready(function(){
2760       this[method](arg);
2761     });
2762
2763   // Otherwise call method now
2764   } else {
2765     try {
2766       this.tech[method](arg);
2767     } catch(e) {
2768       vjs.log(e);
2769       throw e;
2770     }
2771   }
2772 };
2773
2774 // Get calls can't wait for the tech, and sometimes don't need to.
2775 vjs.Player.prototype.techGet = function(method){
2776
2777   // Make sure there is a tech
2778   // if (!this.tech) {
2779   //   return;
2780   // }
2781
2782   if (this.tech.isReady_) {
2783
2784     // Flash likes to die and reload when you hide or reposition it.
2785     // In these cases the object methods go away and we get errors.
2786     // When that happens we'll catch the errors and inform tech that it's not ready any more.
2787     try {
2788       return this.tech[method]();
2789     } catch(e) {
2790       // When building additional tech libs, an expected method may not be defined yet
2791       if (this.tech[method] === undefined) {
2792         vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);
2793       } else {
2794         // When a method isn't available on the object it throws a TypeError
2795         if (e.name == 'TypeError') {
2796           vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);
2797           this.tech.isReady_ = false;
2798         } else {
2799           vjs.log(e);
2800         }
2801       }
2802       throw e;
2803     }
2804   }
2805
2806   return;
2807 };
2808
2809 /**
2810  * Start media playback
2811  * http://dev.w3.org/html5/spec/video.html#dom-media-play
2812  * We're triggering the 'play' event here instead of relying on the
2813  * media element to allow using event.preventDefault() to stop
2814  * play from happening if desired. Usecase: preroll ads.
2815  */
2816 vjs.Player.prototype.play = function(){
2817   this.techCall('play');
2818   return this;
2819 };
2820
2821 // http://dev.w3.org/html5/spec/video.html#dom-media-pause
2822 vjs.Player.prototype.pause = function(){
2823   this.techCall('pause');
2824   return this;
2825 };
2826
2827 // http://dev.w3.org/html5/spec/video.html#dom-media-paused
2828 // The initial state of paused should be true (in Safari it's actually false)
2829 vjs.Player.prototype.paused = function(){
2830   return (this.techGet('paused') === false) ? false : true;
2831 };
2832
2833 // http://dev.w3.org/html5/spec/video.html#dom-media-currenttime
2834 vjs.Player.prototype.currentTime = function(seconds){
2835   if (seconds !== undefined) {
2836
2837     // Cache the last set value for smoother scrubbing.
2838     this.cache_.lastSetCurrentTime = seconds;
2839
2840     this.techCall('setCurrentTime', seconds);
2841
2842     // Improve the accuracy of manual timeupdates
2843     if (this.manualTimeUpdates) { this.trigger('timeupdate'); }
2844
2845     return this;
2846   }
2847
2848   // Cache last currentTime and return
2849   // Default to 0 seconds
2850   return this.cache_.currentTime = (this.techGet('currentTime') || 0);
2851 };
2852
2853 // http://dev.w3.org/html5/spec/video.html#dom-media-duration
2854 // Duration should return NaN if not available. ParseFloat will turn false-ish values to NaN.
2855 vjs.Player.prototype.duration = function(seconds){
2856   if (seconds !== undefined) {
2857
2858     // Cache the last set value for optimiized scrubbing (esp. Flash)
2859     this.cache_.duration = parseFloat(seconds);
2860
2861     return this;
2862   }
2863
2864   return this.cache_.duration;
2865 };
2866
2867 // Calculates how much time is left. Not in spec, but useful.
2868 vjs.Player.prototype.remainingTime = function(){
2869   return this.duration() - this.currentTime();
2870 };
2871
2872 // http://dev.w3.org/html5/spec/video.html#dom-media-buffered
2873 // Buffered returns a timerange object.
2874 // Kind of like an array of portions of the video that have been downloaded.
2875 // So far no browsers return more than one range (portion)
2876 vjs.Player.prototype.buffered = function(){
2877   var buffered = this.techGet('buffered'),
2878       start = 0,
2879       // Default end to 0 and store in values
2880       end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
2881
2882   if (buffered && buffered.length > 0 && buffered.end(0) !== end) {
2883     end = buffered.end(0);
2884     // Storing values allows them be overridden by setBufferedFromProgress
2885     this.cache_.bufferEnd = end;
2886   }
2887
2888   return vjs.createTimeRange(start, end);
2889 };
2890
2891 // Calculates amount of buffer is full. Not in spec but useful.
2892 vjs.Player.prototype.bufferedPercent = function(){
2893   return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
2894 };
2895
2896 // http://dev.w3.org/html5/spec/video.html#dom-media-volume
2897 vjs.Player.prototype.volume = function(percentAsDecimal){
2898   var vol;
2899
2900   if (percentAsDecimal !== undefined) {
2901     vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
2902     this.cache_.volume = vol;
2903     this.techCall('setVolume', vol);
2904     vjs.setLocalStorage('volume', vol);
2905     return this;
2906   }
2907
2908   // Default to 1 when returning current volume.
2909   vol = parseFloat(this.techGet('volume'));
2910   return (isNaN(vol)) ? 1 : vol;
2911 };
2912
2913 // http://dev.w3.org/html5/spec/video.html#attr-media-muted
2914 vjs.Player.prototype.muted = function(muted){
2915   if (muted !== undefined) {
2916     this.techCall('setMuted', muted);
2917     return this;
2918   }
2919   return this.techGet('muted') || false; // Default to false
2920 };
2921
2922 // Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
2923 vjs.Player.prototype.supportsFullScreen = function(){ return this.techGet('supportsFullScreen') || false; };
2924
2925 // Turn on fullscreen (or window) mode
2926 vjs.Player.prototype.requestFullScreen = function(){
2927   var requestFullScreen = vjs.support.requestFullScreen;
2928   this.isFullScreen = true;
2929
2930   if (requestFullScreen) {
2931     // the browser supports going fullscreen at the element level so we can
2932     // take the controls fullscreen as well as the video
2933
2934     // Trigger fullscreenchange event after change
2935     vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
2936       this.isFullScreen = document[requestFullScreen.isFullScreen];
2937
2938       // If cancelling fullscreen, remove event listener.
2939       if (this.isFullScreen === false) {
2940         vjs.off(document, requestFullScreen.eventName, arguments.callee);
2941       }
2942
2943     }));
2944
2945     // Flash and other plugins get reloaded when you take their parent to fullscreen.
2946     // To fix that we'll remove the tech, and reload it after the resize has finished.
2947     if (this.tech.features.fullscreenResize === false && this.options_['flash']['iFrameMode'] !== true) {
2948
2949       this.pause();
2950       this.unloadTech();
2951
2952       vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
2953         vjs.off(document, requestFullScreen.eventName, arguments.callee);
2954         this.loadTech(this.techName, { src: this.cache_.src });
2955       }));
2956
2957       this.el_[requestFullScreen.requestFn]();
2958
2959     } else {
2960       this.el_[requestFullScreen.requestFn]();
2961     }
2962
2963     this.trigger('fullscreenchange');
2964
2965   } else if (this.tech.supportsFullScreen()) {
2966     // we can't take the video.js controls fullscreen but we can go fullscreen
2967     // with native controls
2968
2969     this.techCall('enterFullScreen');
2970   } else {
2971     // fullscreen isn't supported so we'll just stretch the video element to
2972     // fill the viewport
2973
2974     this.enterFullWindow();
2975     this.trigger('fullscreenchange');
2976   }
2977
2978   return this;
2979 };
2980
2981 vjs.Player.prototype.cancelFullScreen = function(){
2982   var requestFullScreen = vjs.support.requestFullScreen;
2983
2984   this.isFullScreen = false;
2985
2986   // Check for browser element fullscreen support
2987   if (requestFullScreen) {
2988
2989    // Flash and other plugins get reloaded when you take their parent to fullscreen.
2990    // To fix that we'll remove the tech, and reload it after the resize has finished.
2991    if (this.tech.features.fullscreenResize === false && this.options_['flash']['iFrameMode'] !== true) {
2992
2993      this.pause();
2994      this.unloadTech();
2995
2996      vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
2997        vjs.off(document, requestFullScreen.eventName, arguments.callee);
2998        this.loadTech(this.techName, { src: this.cache_.src });
2999      }));
3000
3001      document[requestFullScreen.cancelFn]();
3002    } else {
3003      document[requestFullScreen.cancelFn]();
3004    }
3005
3006    this.trigger('fullscreenchange');
3007
3008   } else if (this.tech.supportsFullScreen()) {
3009    this.techCall('exitFullScreen');
3010   } else {
3011    this.exitFullWindow();
3012    this.trigger('fullscreenchange');
3013   }
3014
3015   return this;
3016 };
3017
3018 // When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
3019 vjs.Player.prototype.enterFullWindow = function(){
3020   this.isFullWindow = true;
3021
3022   // Storing original doc overflow value to return to when fullscreen is off
3023   this.docOrigOverflow = document.documentElement.style.overflow;
3024
3025   // Add listener for esc key to exit fullscreen
3026   vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
3027
3028   // Hide any scroll bars
3029   document.documentElement.style.overflow = 'hidden';
3030
3031   // Apply fullscreen styles
3032   vjs.addClass(document.body, 'vjs-full-window');
3033
3034   this.trigger('enterFullWindow');
3035 };
3036 vjs.Player.prototype.fullWindowOnEscKey = function(event){
3037   if (event.keyCode === 27) {
3038     if (this.isFullScreen === true) {
3039       this.cancelFullScreen();
3040     } else {
3041       this.exitFullWindow();
3042     }
3043   }
3044 };
3045
3046 vjs.Player.prototype.exitFullWindow = function(){
3047   this.isFullWindow = false;
3048   vjs.off(document, 'keydown', this.fullWindowOnEscKey);
3049
3050   // Unhide scroll bars.
3051   document.documentElement.style.overflow = this.docOrigOverflow;
3052
3053   // Remove fullscreen styles
3054   vjs.removeClass(document.body, 'vjs-full-window');
3055
3056   // Resize the box, controller, and poster to original sizes
3057   // this.positionAll();
3058   this.trigger('exitFullWindow');
3059 };
3060
3061 vjs.Player.prototype.selectSource = function(sources){
3062
3063   // Loop through each playback technology in the options order
3064   for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
3065     var techName = vjs.capitalize(j[i]),
3066         tech = window['videojs'][techName];
3067
3068     // Check if the browser supports this technology
3069     if (tech.isSupported()) {
3070       // Loop through each source object
3071       for (var a=0,b=sources;a<b.length;a++) {
3072         var source = b[a];
3073
3074         // Check if source can be played with this technology
3075         if (tech['canPlaySource'](source)) {
3076           return { source: source, tech: techName };
3077         }
3078       }
3079     }
3080   }
3081
3082   return false;
3083 };
3084
3085 // src is a pretty powerful function
3086 // If you pass it an array of source objects, it will find the best source to play and use that object.src
3087 //   If the new source requires a new playback technology, it will switch to that.
3088 // If you pass it an object, it will set the source to object.src
3089 // If you pass it anything else (url string) it will set the video source to that
3090 vjs.Player.prototype.src = function(source){
3091   // Case: Array of source objects to choose from and pick the best to play
3092   if (source instanceof Array) {
3093
3094     var sourceTech = this.selectSource(source),
3095         techName;
3096
3097     if (sourceTech) {
3098         source = sourceTech.source;
3099         techName = sourceTech.tech;
3100
3101       // If this technology is already loaded, set source
3102       if (techName == this.techName) {
3103         this.src(source); // Passing the source object
3104       // Otherwise load this technology with chosen source
3105       } else {
3106         this.loadTech(techName, source);
3107       }
3108     } else {
3109       this.el_.appendChild(vjs.createEl('p', {
3110         innerHTML: 'Sorry, no compatible source and playback technology were found for this video. Try using another browser like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
3111       }));
3112     }
3113
3114   // Case: Source object { src: '', type: '' ... }
3115   } else if (source instanceof Object) {
3116
3117     if (window['videojs'][this.techName]['canPlaySource'](source)) {
3118       this.src(source.src);
3119     } else {
3120       // Send through tech loop to check for a compatible technology.
3121       this.src([source]);
3122     }
3123
3124   // Case: URL String (http://myvideo...)
3125   } else {
3126     // Cache for getting last set source
3127     this.cache_.src = source;
3128
3129     if (!this.isReady_) {
3130       this.ready(function(){
3131         this.src(source);
3132       });
3133     } else {
3134       this.techCall('src', source);
3135       if (this.options_['preload'] == 'auto') {
3136         this.load();
3137       }
3138       if (this.options_['autoplay']) {
3139         this.play();
3140       }
3141     }
3142   }
3143   return this;
3144 };
3145
3146 // Begin loading the src data
3147 // http://dev.w3.org/html5/spec/video.html#dom-media-load
3148 vjs.Player.prototype.load = function(){
3149   this.techCall('load');
3150   return this;
3151 };
3152
3153 // http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
3154 vjs.Player.prototype.currentSrc = function(){
3155   return this.techGet('currentSrc') || this.cache_.src || '';
3156 };
3157
3158 // Attributes/Options
3159 vjs.Player.prototype.preload = function(value){
3160   if (value !== undefined) {
3161     this.techCall('setPreload', value);
3162     this.options_['preload'] = value;
3163     return this;
3164   }
3165   return this.techGet('preload');
3166 };
3167 vjs.Player.prototype.autoplay = function(value){
3168   if (value !== undefined) {
3169     this.techCall('setAutoplay', value);
3170     this.options_['autoplay'] = value;
3171     return this;
3172   }
3173   return this.techGet('autoplay', value);
3174 };
3175 vjs.Player.prototype.loop = function(value){
3176   if (value !== undefined) {
3177     this.techCall('setLoop', value);
3178     this.options_['loop'] = value;
3179     return this;
3180   }
3181   return this.techGet('loop');
3182 };
3183
3184 /**
3185  * The url of the poster image source.
3186  * @type {String}
3187  * @private
3188  */
3189 vjs.Player.prototype.poster_;
3190
3191 /**
3192  * Get or set the poster image source url.
3193  * @param  {String} src Poster image source URL
3194  * @return {String}    Poster image source URL or null
3195  */
3196 vjs.Player.prototype.poster = function(src){
3197   if (src !== undefined) {
3198     this.poster_ = src;
3199   }
3200   return this.poster_;
3201 };
3202
3203 /**
3204  * Whether or not the controls are showing
3205  * @type {Boolean}
3206  * @private
3207  */
3208 vjs.Player.prototype.controls_;
3209
3210 /**
3211  * Get or set whether or not the controls are showing.
3212  * @param  {Boolean} controls Set controls to showing or not
3213  * @return {Boolean}    Controls are showing
3214  */
3215 vjs.Player.prototype.controls = function(controls){
3216   if (controls !== undefined) {
3217     // Don't trigger a change event unless it actually changed
3218     if (this.controls_ !== controls) {
3219       this.controls_ = !!controls; // force boolean
3220       this.trigger('controlschange');
3221     }
3222   }
3223   return this.controls_;
3224 };
3225
3226 vjs.Player.prototype.error = function(){ return this.techGet('error'); };
3227 vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
3228
3229 // Methods to add support for
3230 // networkState: function(){ return this.techCall('networkState'); },
3231 // readyState: function(){ return this.techCall('readyState'); },
3232 // seeking: function(){ return this.techCall('seeking'); },
3233 // initialTime: function(){ return this.techCall('initialTime'); },
3234 // startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
3235 // played: function(){ return this.techCall('played'); },
3236 // seekable: function(){ return this.techCall('seekable'); },
3237 // videoTracks: function(){ return this.techCall('videoTracks'); },
3238 // audioTracks: function(){ return this.techCall('audioTracks'); },
3239 // videoWidth: function(){ return this.techCall('videoWidth'); },
3240 // videoHeight: function(){ return this.techCall('videoHeight'); },
3241 // defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
3242 // playbackRate: function(){ return this.techCall('playbackRate'); },
3243 // mediaGroup: function(){ return this.techCall('mediaGroup'); },
3244 // controller: function(){ return this.techCall('controller'); },
3245 // defaultMuted: function(){ return this.techCall('defaultMuted'); }
3246
3247 // TODO
3248 // currentSrcList: the array of sources including other formats and bitrates
3249 // playList: array of source lists in order of playback
3250
3251 // RequestFullscreen API
3252 (function(){
3253   var prefix, requestFS, div;
3254
3255   div = document.createElement('div');
3256
3257   requestFS = {};
3258
3259   // Current W3C Spec
3260   // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
3261   // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
3262   if (div.cancelFullscreen !== undefined) {
3263     requestFS.requestFn = 'requestFullscreen';
3264     requestFS.cancelFn = 'exitFullscreen';
3265     requestFS.eventName = 'fullscreenchange';
3266     requestFS.isFullScreen = 'fullScreen';
3267
3268   // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations
3269   // that use prefixes and vary slightly from the new W3C spec. Specifically,
3270   // using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.
3271   // Other browsers don't have any hints of which version they might follow yet,
3272   // so not going to try to predict by looping through all prefixes.
3273   } else {
3274
3275     if (document.mozCancelFullScreen) {
3276       prefix = 'moz';
3277       requestFS.isFullScreen = prefix + 'FullScreen';
3278     } else {
3279       prefix = 'webkit';
3280       requestFS.isFullScreen = prefix + 'IsFullScreen';
3281     }
3282
3283     if (div[prefix + 'RequestFullScreen']) {
3284       requestFS.requestFn = prefix + 'RequestFullScreen';
3285       requestFS.cancelFn = prefix + 'CancelFullScreen';
3286     }
3287     requestFS.eventName = prefix + 'fullscreenchange';
3288   }
3289
3290   if (document[requestFS.cancelFn]) {
3291     vjs.support.requestFullScreen = requestFS;
3292   }
3293
3294 })();
3295 /**
3296  * Container of main controls
3297  * @param {vjs.Player|Object} player
3298  * @param {Object=} options
3299  * @constructor
3300  */
3301 vjs.ControlBar = vjs.Component.extend({
3302   /** @constructor */
3303   init: function(player, options){
3304     vjs.Component.call(this, player, options);
3305
3306     if (!player.controls()) {
3307       this.disable();
3308     }
3309
3310     player.one('play', vjs.bind(this, function(){
3311       var touchstart,
3312         fadeIn = vjs.bind(this, this.fadeIn),
3313         fadeOut = vjs.bind(this, this.fadeOut);
3314
3315       this.fadeIn();
3316
3317       if ( !('ontouchstart' in window) ) {
3318         this.player_.on('mouseover', fadeIn);
3319         this.player_.on('mouseout', fadeOut);
3320         this.player_.on('pause', vjs.bind(this, this.lockShowing));
3321         this.player_.on('play', vjs.bind(this, this.unlockShowing));
3322       }
3323
3324       touchstart = false;
3325       this.player_.on('touchstart', function() {
3326         touchstart = true;
3327       });
3328       this.player_.on('touchmove', function() {
3329         touchstart = false;
3330       });
3331       this.player_.on('touchend', vjs.bind(this, function(event) {
3332         var idx;
3333         if (touchstart) {
3334           idx = this.el().className.search('fade-in');
3335           if (idx !== -1) {
3336             this.fadeOut();
3337           } else {
3338             this.fadeIn();
3339           }
3340         }
3341         touchstart = false;
3342
3343         if (!this.player_.paused()) {
3344           event.preventDefault();
3345         }
3346       }));
3347     }));
3348   }
3349 });
3350
3351 vjs.ControlBar.prototype.options_ = {
3352   loadEvent: 'play',
3353   children: {
3354     'playToggle': {},
3355     'currentTimeDisplay': {},
3356     'timeDivider': {},
3357     'durationDisplay': {},
3358     'remainingTimeDisplay': {},
3359     'progressControl': {},
3360     'fullscreenToggle': {},
3361     'volumeControl': {},
3362     'muteToggle': {}
3363     // 'volumeMenuButton': {}
3364   }
3365 };
3366
3367 vjs.ControlBar.prototype.createEl = function(){
3368   return vjs.createEl('div', {
3369     className: 'vjs-control-bar'
3370   });
3371 };
3372
3373 vjs.ControlBar.prototype.fadeIn = function(){
3374   vjs.Component.prototype.fadeIn.call(this);
3375   this.player_.trigger('controlsvisible');
3376 };
3377
3378 vjs.ControlBar.prototype.fadeOut = function(){
3379   vjs.Component.prototype.fadeOut.call(this);
3380   this.player_.trigger('controlshidden');
3381 };/**
3382  * Button to toggle between play and pause
3383  * @param {vjs.Player|Object} player
3384  * @param {Object=} options
3385  * @constructor
3386  */
3387 vjs.PlayToggle = vjs.Button.extend({
3388   /** @constructor */
3389   init: function(player, options){
3390     vjs.Button.call(this, player, options);
3391
3392     player.on('play', vjs.bind(this, this.onPlay));
3393     player.on('pause', vjs.bind(this, this.onPause));
3394   }
3395 });
3396
3397 vjs.PlayToggle.prototype.buttonText = 'Play';
3398
3399 vjs.PlayToggle.prototype.buildCSSClass = function(){
3400   return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);
3401 };
3402
3403   // OnClick - Toggle between play and pause
3404 vjs.PlayToggle.prototype.onClick = function(){
3405   if (this.player_.paused()) {
3406     this.player_.play();
3407   } else {
3408     this.player_.pause();
3409   }
3410 };
3411
3412   // OnPlay - Add the vjs-playing class to the element so it can change appearance
3413 vjs.PlayToggle.prototype.onPlay = function(){
3414   vjs.removeClass(this.el_, 'vjs-paused');
3415   vjs.addClass(this.el_, 'vjs-playing');
3416   this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to "Pause"
3417 };
3418
3419   // OnPause - Add the vjs-paused class to the element so it can change appearance
3420 vjs.PlayToggle.prototype.onPause = function(){
3421   vjs.removeClass(this.el_, 'vjs-playing');
3422   vjs.addClass(this.el_, 'vjs-paused');
3423   this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to "Play"
3424 };/**
3425  * Displays the current time
3426  * @param {vjs.Player|Object} player
3427  * @param {Object=} options
3428  * @constructor
3429  */
3430 vjs.CurrentTimeDisplay = vjs.Component.extend({
3431   /** @constructor */
3432   init: function(player, options){
3433     vjs.Component.call(this, player, options);
3434
3435     player.on('timeupdate', vjs.bind(this, this.updateContent));
3436   }
3437 });
3438
3439 vjs.CurrentTimeDisplay.prototype.createEl = function(){
3440   var el = vjs.Component.prototype.createEl.call(this, 'div', {
3441     className: 'vjs-current-time vjs-time-controls vjs-control'
3442   });
3443
3444   this.content = vjs.createEl('div', {
3445     className: 'vjs-current-time-display',
3446     innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', // label the current time for screen reader users
3447     'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
3448   });
3449
3450   el.appendChild(vjs.createEl('div').appendChild(this.content));
3451   return el;
3452 };
3453
3454 vjs.CurrentTimeDisplay.prototype.updateContent = function(){
3455   // Allows for smooth scrubbing, when player can't keep up.
3456   var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
3457   this.content.innerHTML = '<span class="vjs-control-text">Current Time </span>' + vjs.formatTime(time, this.player_.duration());
3458 };
3459
3460 /**
3461  * Displays the duration
3462  * @param {vjs.Player|Object} player
3463  * @param {Object=} options
3464  * @constructor
3465  */
3466 vjs.DurationDisplay = vjs.Component.extend({
3467   /** @constructor */
3468   init: function(player, options){
3469     vjs.Component.call(this, player, options);
3470
3471     player.on('timeupdate', vjs.bind(this, this.updateContent)); // this might need to be changes to 'durationchange' instead of 'timeupdate' eventually, however the durationchange event fires before this.player_.duration() is set, so the value cannot be written out using this method. Once the order of durationchange and this.player_.duration() being set is figured out, this can be updated.
3472   }
3473 });
3474
3475 vjs.DurationDisplay.prototype.createEl = function(){
3476   var el = vjs.Component.prototype.createEl.call(this, 'div', {
3477     className: 'vjs-duration vjs-time-controls vjs-control'
3478   });
3479
3480   this.content = vjs.createEl('div', {
3481     className: 'vjs-duration-display',
3482     innerHTML: '<span class="vjs-control-text">Duration Time </span>' + '0:00', // label the duration time for screen reader users
3483     'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
3484   });
3485
3486   el.appendChild(vjs.createEl('div').appendChild(this.content));
3487   return el;
3488 };
3489
3490 vjs.DurationDisplay.prototype.updateContent = function(){
3491   if (this.player_.duration()) {
3492       this.content.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(this.player_.duration()); // label the duration time for screen reader users
3493   }
3494 };
3495
3496 /**
3497  * Time Separator (Not used in main skin, but still available, and could be used as a 'spare element')
3498  * @param {vjs.Player|Object} player
3499  * @param {Object=} options
3500  * @constructor
3501  */
3502 vjs.TimeDivider = vjs.Component.extend({
3503   /** @constructor */
3504   init: function(player, options){
3505     vjs.Component.call(this, player, options);
3506   }
3507 });
3508
3509 vjs.TimeDivider.prototype.createEl = function(){
3510   return vjs.Component.prototype.createEl.call(this, 'div', {
3511     className: 'vjs-time-divider',
3512     innerHTML: '<div><span>/</span></div>'
3513   });
3514 };
3515
3516 /**
3517  * Displays the time left in the video
3518  * @param {vjs.Player|Object} player
3519  * @param {Object=} options
3520  * @constructor
3521  */
3522 vjs.RemainingTimeDisplay = vjs.Component.extend({
3523   /** @constructor */
3524   init: function(player, options){
3525     vjs.Component.call(this, player, options);
3526
3527     player.on('timeupdate', vjs.bind(this, this.updateContent));
3528   }
3529 });
3530
3531 vjs.RemainingTimeDisplay.prototype.createEl = function(){
3532   var el = vjs.Component.prototype.createEl.call(this, 'div', {
3533     className: 'vjs-remaining-time vjs-time-controls vjs-control'
3534   });
3535
3536   this.content = vjs.createEl('div', {
3537     className: 'vjs-remaining-time-display',
3538     innerHTML: '<span class="vjs-control-text">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users
3539     'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
3540   });
3541
3542   el.appendChild(vjs.createEl('div').appendChild(this.content));
3543   return el;
3544 };
3545
3546 vjs.RemainingTimeDisplay.prototype.updateContent = function(){
3547   if (this.player_.duration()) {
3548       if (this.player_.duration()) {
3549           this.content.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());
3550       }
3551   }
3552
3553   // Allows for smooth scrubbing, when player can't keep up.
3554   // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
3555   // this.content.innerHTML = vjs.formatTime(time, this.player_.duration());
3556 };/**
3557  * Toggle fullscreen video
3558  * @param {vjs.Player|Object} player
3559  * @param {Object=} options
3560  * @constructor
3561  */
3562 vjs.FullscreenToggle = vjs.Button.extend({
3563   /** @constructor */
3564   init: function(player, options){
3565     vjs.Button.call(this, player, options);
3566   }
3567 });
3568
3569 vjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';
3570
3571 vjs.FullscreenToggle.prototype.buildCSSClass = function(){
3572   return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);
3573 };
3574
3575 vjs.FullscreenToggle.prototype.onClick = function(){
3576   if (!this.player_.isFullScreen) {
3577     this.player_.requestFullScreen();
3578     this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to "Non-Fullscreen"
3579   } else {
3580     this.player_.cancelFullScreen();
3581     this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to "Fullscreen"
3582   }
3583 };/**
3584  * Seek, Load Progress, and Play Progress
3585  * @param {vjs.Player|Object} player
3586  * @param {Object=} options
3587  * @constructor
3588  */
3589 vjs.ProgressControl = vjs.Component.extend({
3590   /** @constructor */
3591   init: function(player, options){
3592     vjs.Component.call(this, player, options);
3593   }
3594 });
3595
3596 vjs.ProgressControl.prototype.options_ = {
3597   children: {
3598     'seekBar': {}
3599   }
3600 };
3601
3602 vjs.ProgressControl.prototype.createEl = function(){
3603   return vjs.Component.prototype.createEl.call(this, 'div', {
3604     className: 'vjs-progress-control vjs-control'
3605   });
3606 };
3607
3608 /**
3609  * Seek Bar and holder for the progress bars
3610  * @param {vjs.Player|Object} player
3611  * @param {Object=} options
3612  * @constructor
3613  */
3614 vjs.SeekBar = vjs.Slider.extend({
3615   /** @constructor */
3616   init: function(player, options){
3617     vjs.Slider.call(this, player, options);
3618     player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));
3619     player.ready(vjs.bind(this, this.updateARIAAttributes));
3620   }
3621 });
3622
3623 vjs.SeekBar.prototype.options_ = {
3624   children: {
3625     'loadProgressBar': {},
3626     'playProgressBar': {},
3627     'seekHandle': {}
3628   },
3629   'barName': 'playProgressBar',
3630   'handleName': 'seekHandle'
3631 };
3632
3633 vjs.SeekBar.prototype.playerEvent = 'timeupdate';
3634
3635 vjs.SeekBar.prototype.createEl = function(){
3636   return vjs.Slider.prototype.createEl.call(this, 'div', {
3637     className: 'vjs-progress-holder',
3638     'aria-label': 'video progress bar'
3639   });
3640 };
3641
3642 vjs.SeekBar.prototype.updateARIAAttributes = function(){
3643     // Allows for smooth scrubbing, when player can't keep up.
3644     var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
3645     this.el_.setAttribute('aria-valuenow',vjs.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)
3646     this.el_.setAttribute('aria-valuetext',vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
3647 };
3648
3649 vjs.SeekBar.prototype.getPercent = function(){
3650   return this.player_.currentTime() / this.player_.duration();
3651 };
3652
3653 vjs.SeekBar.prototype.onMouseDown = function(event){
3654   vjs.Slider.prototype.onMouseDown.call(this, event);
3655
3656   this.player_.scrubbing = true;
3657
3658   this.videoWasPlaying = !this.player_.paused();
3659   this.player_.pause();
3660 };
3661
3662 vjs.SeekBar.prototype.onMouseMove = function(event){
3663   var newTime = this.calculateDistance(event) * this.player_.duration();
3664
3665   // Don't let video end while scrubbing.
3666   if (newTime == this.player_.duration()) { newTime = newTime - 0.1; }
3667
3668   // Set new time (tell player to seek to new time)
3669   this.player_.currentTime(newTime);
3670 };
3671
3672 vjs.SeekBar.prototype.onMouseUp = function(event){
3673   vjs.Slider.prototype.onMouseUp.call(this, event);
3674
3675   this.player_.scrubbing = false;
3676   if (this.videoWasPlaying) {
3677     this.player_.play();
3678   }
3679 };
3680
3681 vjs.SeekBar.prototype.stepForward = function(){
3682   this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
3683 };
3684
3685 vjs.SeekBar.prototype.stepBack = function(){
3686   this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
3687 };
3688
3689
3690 /**
3691  * Shows load progres
3692  * @param {vjs.Player|Object} player
3693  * @param {Object=} options
3694  * @constructor
3695  */
3696 vjs.LoadProgressBar = vjs.Component.extend({
3697   /** @constructor */
3698   init: function(player, options){
3699     vjs.Component.call(this, player, options);
3700     player.on('progress', vjs.bind(this, this.update));
3701   }
3702 });
3703
3704 vjs.LoadProgressBar.prototype.createEl = function(){
3705   return vjs.Component.prototype.createEl.call(this, 'div', {
3706     className: 'vjs-load-progress',
3707     innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>'
3708   });
3709 };
3710
3711 vjs.LoadProgressBar.prototype.update = function(){
3712   if (this.el_.style) { this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%'; }
3713 };
3714
3715
3716 /**
3717  * Shows play progress
3718  * @param {vjs.Player|Object} player
3719  * @param {Object=} options
3720  * @constructor
3721  */
3722 vjs.PlayProgressBar = vjs.Component.extend({
3723   /** @constructor */
3724   init: function(player, options){
3725     vjs.Component.call(this, player, options);
3726   }
3727 });
3728
3729 vjs.PlayProgressBar.prototype.createEl = function(){
3730   return vjs.Component.prototype.createEl.call(this, 'div', {
3731     className: 'vjs-play-progress',
3732     innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
3733   });
3734 };
3735
3736 /**
3737  * SeekBar component includes play progress bar, and seek handle
3738  * Needed so it can determine seek position based on handle position/size
3739  * @param {vjs.Player|Object} player
3740  * @param {Object=} options
3741  * @constructor
3742  */
3743 vjs.SeekHandle = vjs.SliderHandle.extend();
3744
3745 /** @inheritDoc */
3746 vjs.SeekHandle.prototype.defaultValue = '00:00';
3747
3748 /** @inheritDoc */
3749 vjs.SeekHandle.prototype.createEl = function(){
3750   return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
3751     className: 'vjs-seek-handle'
3752   });
3753 };/**
3754  * Control the volume
3755  * @param {vjs.Player|Object} player
3756  * @param {Object=} options
3757  * @constructor
3758  */
3759 vjs.VolumeControl = vjs.Component.extend({
3760   /** @constructor */
3761   init: function(player, options){
3762     vjs.Component.call(this, player, options);
3763
3764     // hide volume controls when they're not supported by the current tech
3765     if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
3766       this.addClass('vjs-hidden');
3767     }
3768     player.on('loadstart', vjs.bind(this, function(){
3769       if (player.tech.features && player.tech.features.volumeControl === false) {
3770         this.addClass('vjs-hidden');
3771       } else {
3772         this.removeClass('vjs-hidden');
3773       }
3774     }));
3775   }
3776 });
3777
3778 vjs.VolumeControl.prototype.options_ = {
3779   children: {
3780     'volumeBar': {}
3781   }
3782 };
3783
3784 vjs.VolumeControl.prototype.createEl = function(){
3785   return vjs.Component.prototype.createEl.call(this, 'div', {
3786     className: 'vjs-volume-control vjs-control'
3787   });
3788 };
3789
3790 /**
3791  * Contains volume level
3792  * @param {vjs.Player|Object} player
3793  * @param {Object=} options
3794  * @constructor
3795  */
3796 vjs.VolumeBar = vjs.Slider.extend({
3797   /** @constructor */
3798   init: function(player, options){
3799     vjs.Slider.call(this, player, options);
3800     player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));
3801     player.ready(vjs.bind(this, this.updateARIAAttributes));
3802     setTimeout(vjs.bind(this, this.update), 0); // update when elements is in DOM
3803   }
3804 });
3805
3806 vjs.VolumeBar.prototype.updateARIAAttributes = function(){
3807   // Current value of volume bar as a percentage
3808   this.el_.setAttribute('aria-valuenow',vjs.round(this.player_.volume()*100, 2));
3809   this.el_.setAttribute('aria-valuetext',vjs.round(this.player_.volume()*100, 2)+'%');
3810 };
3811
3812 vjs.VolumeBar.prototype.options_ = {
3813   children: {
3814     'volumeLevel': {},
3815     'volumeHandle': {}
3816   },
3817   'barName': 'volumeLevel',
3818   'handleName': 'volumeHandle'
3819 };
3820
3821 vjs.VolumeBar.prototype.playerEvent = 'volumechange';
3822
3823 vjs.VolumeBar.prototype.createEl = function(){
3824   return vjs.Slider.prototype.createEl.call(this, 'div', {
3825     className: 'vjs-volume-bar',
3826     'aria-label': 'volume level'
3827   });
3828 };
3829
3830 vjs.VolumeBar.prototype.onMouseMove = function(event) {
3831   this.player_.volume(this.calculateDistance(event));
3832 };
3833
3834 vjs.VolumeBar.prototype.getPercent = function(){
3835   if (this.player_.muted()) {
3836     return 0;
3837   } else {
3838     return this.player_.volume();
3839   }
3840 };
3841
3842 vjs.VolumeBar.prototype.stepForward = function(){
3843   this.player_.volume(this.player_.volume() + 0.1);
3844 };
3845
3846 vjs.VolumeBar.prototype.stepBack = function(){
3847   this.player_.volume(this.player_.volume() - 0.1);
3848 };
3849
3850 /**
3851  * Shows volume level
3852  * @param {vjs.Player|Object} player
3853  * @param {Object=} options
3854  * @constructor
3855  */
3856 vjs.VolumeLevel = vjs.Component.extend({
3857   /** @constructor */
3858   init: function(player, options){
3859     vjs.Component.call(this, player, options);
3860   }
3861 });
3862
3863 vjs.VolumeLevel.prototype.createEl = function(){
3864   return vjs.Component.prototype.createEl.call(this, 'div', {
3865     className: 'vjs-volume-level',
3866     innerHTML: '<span class="vjs-control-text"></span>'
3867   });
3868 };
3869
3870 /**
3871  * Change volume level
3872  * @param {vjs.Player|Object} player
3873  * @param {Object=} options
3874  * @constructor
3875  */
3876  vjs.VolumeHandle = vjs.SliderHandle.extend();
3877
3878  /** @inheritDoc */
3879  vjs.VolumeHandle.prototype.defaultValue = '00:00';
3880
3881  /** @inheritDoc */
3882  vjs.VolumeHandle.prototype.createEl = function(){
3883    return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
3884      className: 'vjs-volume-handle'
3885    });
3886  };/**
3887  * Mute the audio
3888  * @param {vjs.Player|Object} player
3889  * @param {Object=} options
3890  * @constructor
3891  */
3892 vjs.MuteToggle = vjs.Button.extend({
3893   /** @constructor */
3894   init: function(player, options){
3895     vjs.Button.call(this, player, options);
3896
3897     player.on('volumechange', vjs.bind(this, this.update));
3898
3899     // hide mute toggle if the current tech doesn't support volume control
3900     if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
3901       this.addClass('vjs-hidden');
3902     }
3903     player.on('loadstart', vjs.bind(this, function(){
3904       if (player.tech.features && player.tech.features.volumeControl === false) {
3905         this.addClass('vjs-hidden');
3906       } else {
3907         this.removeClass('vjs-hidden');
3908       }
3909     }));
3910   }
3911 });
3912
3913 vjs.MuteToggle.prototype.createEl = function(){
3914   return vjs.Button.prototype.createEl.call(this, 'div', {
3915     className: 'vjs-mute-control vjs-control',
3916     innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
3917   });
3918 };
3919
3920 vjs.MuteToggle.prototype.onClick = function(){
3921   this.player_.muted( this.player_.muted() ? false : true );
3922 };
3923
3924 vjs.MuteToggle.prototype.update = function(){
3925   var vol = this.player_.volume(),
3926       level = 3;
3927
3928   if (vol === 0 || this.player_.muted()) {
3929     level = 0;
3930   } else if (vol < 0.33) {
3931     level = 1;
3932   } else if (vol < 0.67) {
3933     level = 2;
3934   }
3935
3936   // Don't rewrite the button text if the actual text doesn't change.
3937   // This causes unnecessary and confusing information for screen reader users.
3938   // This check is needed because this function gets called every time the volume level is changed.
3939   if(this.player_.muted()){
3940       if(this.el_.children[0].children[0].innerHTML!='Unmute'){
3941           this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to "Unmute"
3942       }
3943   } else {
3944       if(this.el_.children[0].children[0].innerHTML!='Mute'){
3945           this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to "Mute"
3946       }
3947   }
3948
3949   /* TODO improve muted icon classes */
3950   for (var i = 0; i < 4; i++) {
3951     vjs.removeClass(this.el_, 'vjs-vol-'+i);
3952   }
3953   vjs.addClass(this.el_, 'vjs-vol-'+level);
3954 };/**
3955  * Menu button with a popup for showing the volume slider.
3956  * @constructor
3957  */
3958 vjs.VolumeMenuButton = vjs.MenuButton.extend({
3959   /** @constructor */
3960   init: function(player, options){
3961     vjs.MenuButton.call(this, player, options);
3962
3963     // Same listeners as MuteToggle
3964     player.on('volumechange', vjs.bind(this, this.update));
3965
3966     // hide mute toggle if the current tech doesn't support volume control
3967     if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
3968       this.addClass('vjs-hidden');
3969     }
3970     player.on('loadstart', vjs.bind(this, function(){
3971       if (player.tech.features && player.tech.features.volumeControl === false) {
3972         this.addClass('vjs-hidden');
3973       } else {
3974         this.removeClass('vjs-hidden');
3975       }
3976     }));
3977     this.addClass('vjs-menu-button');
3978   }
3979 });
3980
3981 vjs.VolumeMenuButton.prototype.createMenu = function(){
3982   var menu = new vjs.Menu(this.player_, {
3983     contentElType: 'div'
3984   });
3985   var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({vertical: true}, this.options_.volumeBar));
3986   menu.addChild(vc);
3987   return menu;
3988 };
3989
3990 vjs.VolumeMenuButton.prototype.onClick = function(){
3991   vjs.MuteToggle.prototype.onClick.call(this);
3992   vjs.MenuButton.prototype.onClick.call(this);
3993 };
3994
3995 vjs.VolumeMenuButton.prototype.createEl = function(){
3996   return vjs.Button.prototype.createEl.call(this, 'div', {
3997     className: 'vjs-volume-menu-button vjs-menu-button vjs-control',
3998     innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
3999   });
4000 };
4001 vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
4002 /* Poster Image
4003 ================================================================================ */
4004 /**
4005  * Poster image. Shows before the video plays.
4006  * @param {vjs.Player|Object} player
4007  * @param {Object=} options
4008  * @constructor
4009  */
4010 vjs.PosterImage = vjs.Button.extend({
4011   /** @constructor */
4012   init: function(player, options){
4013     vjs.Button.call(this, player, options);
4014
4015     if (!player.poster() || !player.controls()) {
4016       this.hide();
4017     }
4018
4019     player.on('play', vjs.bind(this, this.hide));
4020   }
4021 });
4022
4023 vjs.PosterImage.prototype.createEl = function(){
4024   var el = vjs.createEl('div', {
4025         className: 'vjs-poster',
4026
4027         // Don't want poster to be tabbable.
4028         tabIndex: -1
4029       }),
4030       poster = this.player_.poster();
4031
4032   if (poster) {
4033     if ('backgroundSize' in el.style) {
4034       el.style.backgroundImage = 'url("' + poster + '")';
4035     } else {
4036       el.appendChild(vjs.createEl('img', { src: poster }));
4037     }
4038   }
4039
4040   return el;
4041 };
4042
4043 vjs.PosterImage.prototype.onClick = function(){
4044   this.player_.play();
4045 };
4046 /* Loading Spinner
4047 ================================================================================ */
4048 /**
4049  * Loading spinner for waiting events
4050  * @param {vjs.Player|Object} player
4051  * @param {Object=} options
4052  * @constructor
4053  */
4054 vjs.LoadingSpinner = vjs.Component.extend({
4055   /** @constructor */
4056   init: function(player, options){
4057     vjs.Component.call(this, player, options);
4058
4059     player.on('canplay', vjs.bind(this, this.hide));
4060     player.on('canplaythrough', vjs.bind(this, this.hide));
4061     player.on('playing', vjs.bind(this, this.hide));
4062     player.on('seeked', vjs.bind(this, this.hide));
4063
4064     player.on('seeking', vjs.bind(this, this.show));
4065
4066     // in some browsers seeking does not trigger the 'playing' event,
4067     // so we also need to trap 'seeked' if we are going to set a
4068     // 'seeking' event
4069     player.on('seeked', vjs.bind(this, this.hide));
4070
4071     player.on('error', vjs.bind(this, this.show));
4072
4073     // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
4074     // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing
4075     // player.on('stalled', vjs.bind(this, this.show));
4076
4077     player.on('waiting', vjs.bind(this, this.show));
4078   }
4079 });
4080
4081 vjs.LoadingSpinner.prototype.createEl = function(){
4082   return vjs.Component.prototype.createEl.call(this, 'div', {
4083     className: 'vjs-loading-spinner'
4084   });
4085 };
4086 /* Big Play Button
4087 ================================================================================ */
4088 /**
4089  * Initial play button. Shows before the video has played.
4090  * @param {vjs.Player|Object} player
4091  * @param {Object=} options
4092  * @constructor
4093  */
4094 vjs.BigPlayButton = vjs.Button.extend({
4095   /** @constructor */
4096   init: function(player, options){
4097     vjs.Button.call(this, player, options);
4098
4099     if (!player.controls()) {
4100       this.hide();
4101     }
4102
4103     player.on('play', vjs.bind(this, this.hide));
4104     // player.on('ended', vjs.bind(this, this.show));
4105   }
4106 });
4107
4108 vjs.BigPlayButton.prototype.createEl = function(){
4109   return vjs.Button.prototype.createEl.call(this, 'div', {
4110     className: 'vjs-big-play-button',
4111     innerHTML: '<span></span>',
4112     'aria-label': 'play video'
4113   });
4114 };
4115
4116 vjs.BigPlayButton.prototype.onClick = function(){
4117   // Go back to the beginning if big play button is showing at the end.
4118   // Have to check for current time otherwise it might throw a 'not ready' error.
4119   //if(this.player_.currentTime()) {
4120     //this.player_.currentTime(0);
4121   //}
4122   this.player_.play();
4123 };
4124 /**
4125  * @fileoverview Media Technology Controller - Base class for media playback technology controllers like Flash and HTML5
4126  */
4127
4128 /**
4129  * Base class for media (HTML5 Video, Flash) controllers
4130  * @param {vjs.Player|Object} player  Central player instance
4131  * @param {Object=} options Options object
4132  * @constructor
4133  */
4134 vjs.MediaTechController = vjs.Component.extend({
4135   /** @constructor */
4136   init: function(player, options, ready){
4137     vjs.Component.call(this, player, options, ready);
4138
4139     // Make playback element clickable
4140     // this.addEvent('click', this.proxy(this.onClick));
4141
4142     // player.triggerEvent('techready');
4143   }
4144 });
4145
4146 // destroy: function(){},
4147 // createElement: function(){},
4148
4149 /**
4150  * Handle a click on the media element. By default will play the media.
4151  *
4152  * On android browsers, having this toggle play state interferes with being
4153  * able to toggle the controls and toggling play state with the play button
4154  */
4155 vjs.MediaTechController.prototype.onClick = (function(){
4156   if (vjs.IS_ANDROID) {
4157     return function () {};
4158   } else {
4159     return function () {
4160       if (this.player_.controls()) {
4161         if (this.player_.paused()) {
4162           this.player_.play();
4163         } else {
4164           this.player_.pause();
4165         }
4166       }
4167     };
4168   }
4169 })();
4170
4171 vjs.MediaTechController.prototype.features = {
4172   volumeControl: true,
4173
4174   // Resizing plugins using request fullscreen reloads the plugin
4175   fullscreenResize: false,
4176
4177   // Optional events that we can manually mimic with timers
4178   // currently not triggered by video-js-swf
4179   progressEvents: false,
4180   timeupdateEvents: false
4181 };
4182
4183 vjs.media = {};
4184
4185 /**
4186  * List of default API methods for any MediaTechController
4187  * @type {String}
4188  */
4189 vjs.media.ApiMethods = 'play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted'.split(',');
4190 // Create placeholder methods for each that warn when a method isn't supported by the current playback technology
4191
4192 function createMethod(methodName){
4193   return function(){
4194     throw new Error('The "'+methodName+'" method is not available on the playback technology\'s API');
4195   };
4196 }
4197
4198 for (var i = vjs.media.ApiMethods.length - 1; i >= 0; i--) {
4199   var methodName = vjs.media.ApiMethods[i];
4200   vjs.MediaTechController.prototype[vjs.media.ApiMethods[i]] = createMethod(methodName);
4201 }
4202 /**
4203  * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
4204  */
4205
4206 /**
4207  * HTML5 Media Controller - Wrapper for HTML5 Media API
4208  * @param {vjs.Player|Object} player
4209  * @param {Object=} options
4210  * @param {Function=} ready
4211  * @constructor
4212  */
4213 vjs.Html5 = vjs.MediaTechController.extend({
4214   /** @constructor */
4215   init: function(player, options, ready){
4216     // volume cannot be changed from 1 on iOS
4217     this.features.volumeControl = vjs.Html5.canControlVolume();
4218
4219     // In iOS, if you move a video element in the DOM, it breaks video playback.
4220     this.features.movingMediaElementInDOM = !vjs.IS_IOS;
4221
4222     // HTML video is able to automatically resize when going to fullscreen
4223     this.features.fullscreenResize = true;
4224
4225     vjs.MediaTechController.call(this, player, options, ready);
4226
4227     var source = options['source'];
4228
4229     // If the element source is already set, we may have missed the loadstart event, and want to trigger it.
4230     // We don't want to set the source again and interrupt playback.
4231     if (source && this.el_.currentSrc == source.src) {
4232       player.trigger('loadstart');
4233
4234     // Otherwise set the source if one was provided.
4235     } else if (source) {
4236       this.el_.src = source.src;
4237     }
4238
4239     // Chrome and Safari both have issues with autoplay.
4240     // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
4241     // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
4242     // This fixes both issues. Need to wait for API, so it updates displays correctly
4243     player.ready(function(){
4244       if (this.options_['autoplay'] && this.paused()) {
4245         this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16.
4246         this.play();
4247       }
4248     });
4249
4250     this.on('click', this.onClick);
4251
4252     this.setupTriggers();
4253
4254     this.triggerReady();
4255   }
4256 });
4257
4258 vjs.Html5.prototype.dispose = function(){
4259   vjs.MediaTechController.prototype.dispose.call(this);
4260 };
4261
4262 vjs.Html5.prototype.createEl = function(){
4263   var player = this.player_,
4264       // If possible, reuse original tag for HTML5 playback technology element
4265       el = player.tag,
4266       newEl;
4267
4268   // Check if this browser supports moving the element into the box.
4269   // On the iPhone video will break if you move the element,
4270   // So we have to create a brand new element.
4271   if (!el || this.features.movingMediaElementInDOM === false) {
4272
4273     // If the original tag is still there, remove it.
4274     if (el) {
4275       player.el().removeChild(el);
4276       el = el.cloneNode(false);
4277     } else {
4278       el = vjs.createEl('video', {
4279         id:player.id() + '_html5_api',
4280         className:'vjs-tech'
4281       });
4282     }
4283     // associate the player with the new tag
4284     el['player'] = player;
4285
4286     vjs.insertFirst(el, player.el());
4287   }
4288
4289   // Update specific tag settings, in case they were overridden
4290   var attrs = ['autoplay','preload','loop','muted'];
4291   for (var i = attrs.length - 1; i >= 0; i--) {
4292     var attr = attrs[i];
4293     if (player.options_[attr] !== null) {
4294       el[attr] = player.options_[attr];
4295     }
4296   }
4297
4298   return el;
4299   // jenniisawesome = true;
4300 };
4301
4302 // Make video events trigger player events
4303 // May seem verbose here, but makes other APIs possible.
4304 vjs.Html5.prototype.setupTriggers = function(){
4305   for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
4306     vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this.player_, this.eventHandler));
4307   }
4308 };
4309 // Triggers removed using this.off when disposed
4310
4311 vjs.Html5.prototype.eventHandler = function(e){
4312   this.trigger(e);
4313
4314   // No need for media events to bubble up.
4315   e.stopPropagation();
4316 };
4317
4318
4319 vjs.Html5.prototype.play = function(){ this.el_.play(); };
4320 vjs.Html5.prototype.pause = function(){ this.el_.pause(); };
4321 vjs.Html5.prototype.paused = function(){ return this.el_.paused; };
4322
4323 vjs.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };
4324 vjs.Html5.prototype.setCurrentTime = function(seconds){
4325   try {
4326     this.el_.currentTime = seconds;
4327   } catch(e) {
4328     vjs.log(e, 'Video is not ready. (Video.js)');
4329     // this.warning(VideoJS.warnings.videoNotReady);
4330   }
4331 };
4332
4333 vjs.Html5.prototype.duration = function(){ return this.el_.duration || 0; };
4334 vjs.Html5.prototype.buffered = function(){ return this.el_.buffered; };
4335
4336 vjs.Html5.prototype.volume = function(){ return this.el_.volume; };
4337 vjs.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };
4338 vjs.Html5.prototype.muted = function(){ return this.el_.muted; };
4339 vjs.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };
4340
4341 vjs.Html5.prototype.width = function(){ return this.el_.offsetWidth; };
4342 vjs.Html5.prototype.height = function(){ return this.el_.offsetHeight; };
4343
4344 vjs.Html5.prototype.supportsFullScreen = function(){
4345   if (typeof this.el_.webkitEnterFullScreen == 'function') {
4346
4347     // Seems to be broken in Chromium/Chrome && Safari in Leopard
4348     if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {
4349       return true;
4350     }
4351   }
4352   return false;
4353 };
4354
4355 vjs.Html5.prototype.enterFullScreen = function(){
4356   var video = this.el_;
4357   if (video.paused && video.networkState <= video.HAVE_METADATA) {
4358     // attempt to prime the video element for programmatic access
4359     // this isn't necessary on the desktop but shouldn't hurt
4360     this.el_.play();
4361
4362     // playing and pausing synchronously during the transition to fullscreen
4363     // can get iOS ~6.1 devices into a play/pause loop
4364     setTimeout(function(){
4365       video.pause();
4366       video.webkitEnterFullScreen();
4367     }, 0);
4368   } else {
4369     video.webkitEnterFullScreen();
4370   }
4371 };
4372 vjs.Html5.prototype.exitFullScreen = function(){
4373   this.el_.webkitExitFullScreen();
4374 };
4375 vjs.Html5.prototype.src = function(src){ this.el_.src = src; };
4376 vjs.Html5.prototype.load = function(){ this.el_.load(); };
4377 vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
4378
4379 vjs.Html5.prototype.preload = function(){ return this.el_.preload; };
4380 vjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };
4381 vjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };
4382 vjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };
4383 vjs.Html5.prototype.loop = function(){ return this.el_.loop; };
4384 vjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };
4385
4386 vjs.Html5.prototype.error = function(){ return this.el_.error; };
4387   // networkState: function(){ return this.el_.networkState; },
4388   // readyState: function(){ return this.el_.readyState; },
4389 vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
4390   // initialTime: function(){ return this.el_.initialTime; },
4391   // startOffsetTime: function(){ return this.el_.startOffsetTime; },
4392   // played: function(){ return this.el_.played; },
4393   // seekable: function(){ return this.el_.seekable; },
4394 vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
4395   // videoTracks: function(){ return this.el_.videoTracks; },
4396   // audioTracks: function(){ return this.el_.audioTracks; },
4397   // videoWidth: function(){ return this.el_.videoWidth; },
4398   // videoHeight: function(){ return this.el_.videoHeight; },
4399   // textTracks: function(){ return this.el_.textTracks; },
4400   // defaultPlaybackRate: function(){ return this.el_.defaultPlaybackRate; },
4401   // playbackRate: function(){ return this.el_.playbackRate; },
4402   // mediaGroup: function(){ return this.el_.mediaGroup; },
4403   // controller: function(){ return this.el_.controller; },
4404 vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
4405
4406 /* HTML5 Support Testing ---------------------------------------------------- */
4407
4408 vjs.Html5.isSupported = function(){
4409   return !!document.createElement('video').canPlayType;
4410 };
4411
4412 vjs.Html5.canPlaySource = function(srcObj){
4413   return !!document.createElement('video').canPlayType(srcObj.type);
4414   // TODO: Check Type
4415   // If no Type, check ext
4416   // Check Media Type
4417 };
4418
4419 vjs.Html5.canControlVolume = function(){
4420   var volume =  vjs.TEST_VID.volume;
4421   vjs.TEST_VID.volume = (volume / 2) + 0.1;
4422   return volume !== vjs.TEST_VID.volume;
4423 };
4424
4425 // List of all HTML5 events (various uses).
4426 vjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');
4427
4428
4429 // HTML5 Feature detection and Device Fixes --------------------------------- //
4430
4431 // Android
4432 if (vjs.IS_ANDROID) {
4433
4434   // Override Android 2.2 and less canPlayType method which is broken
4435   if (vjs.ANDROID_VERSION < 3) {
4436     document.createElement('video').constructor.prototype.canPlayType = function(type){
4437       return (type && type.toLowerCase().indexOf('video/mp4') != -1) ? 'maybe' : '';
4438     };
4439   }
4440 }
4441 /**
4442  * @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API
4443  * https://github.com/zencoder/video-js-swf
4444  * Not using setupTriggers. Using global onEvent func to distribute events
4445  */
4446
4447 /**
4448  * HTML5 Media Controller - Wrapper for HTML5 Media API
4449  * @param {vjs.Player|Object} player
4450  * @param {Object=} options
4451  * @param {Function=} ready
4452  * @constructor
4453  */
4454 vjs.Flash = vjs.MediaTechController.extend({
4455   /** @constructor */
4456   init: function(player, options, ready){
4457     vjs.MediaTechController.call(this, player, options, ready);
4458
4459     var source = options['source'],
4460
4461         // Which element to embed in
4462         parentEl = options['parentEl'],
4463
4464         // Create a temporary element to be replaced by swf object
4465         placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),
4466
4467         // Generate ID for swf object
4468         objId = player.id()+'_flash_api',
4469
4470         // Store player options in local var for optimization
4471         // TODO: switch to using player methods instead of options
4472         // e.g. player.autoplay();
4473         playerOptions = player.options_,
4474
4475         // Merge default flashvars with ones passed in to init
4476         flashVars = vjs.obj.merge({
4477
4478           // SWF Callback Functions
4479           'readyFunction': 'videojs.Flash.onReady',
4480           'eventProxyFunction': 'videojs.Flash.onEvent',
4481           'errorEventProxyFunction': 'videojs.Flash.onError',
4482
4483           // Player Settings
4484           'autoplay': playerOptions.autoplay,
4485           'preload': playerOptions.preload,
4486           'loop': playerOptions.loop,
4487           'muted': playerOptions.muted
4488
4489         }, options['flashVars']),
4490
4491         // Merge default parames with ones passed in
4492         params = vjs.obj.merge({
4493           'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
4494           'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
4495         }, options['params']),
4496
4497         // Merge default attributes with ones passed in
4498         attributes = vjs.obj.merge({
4499           'id': objId,
4500           'name': objId, // Both ID and Name needed or swf to identifty itself
4501           'class': 'vjs-tech'
4502         }, options['attributes'])
4503     ;
4504
4505     // If source was supplied pass as a flash var.
4506     if (source) {
4507       flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
4508     }
4509
4510     // Add placeholder to player div
4511     vjs.insertFirst(placeHolder, parentEl);
4512
4513     // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
4514     // This allows resetting the playhead when we catch the reload
4515     if (options['startTime']) {
4516       this.ready(function(){
4517         this.load();
4518         this.play();
4519         this.currentTime(options['startTime']);
4520       });
4521     }
4522
4523     // Flash iFrame Mode
4524     // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
4525     // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
4526     // - Webkit when hiding the plugin
4527     // - Webkit and Firefox when using requestFullScreen on a parent element
4528     // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
4529     // Issues that remain include hiding the element and requestFullScreen in Firefox specifically
4530
4531     // There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.
4532     // Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.
4533     // I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.
4534     // Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe
4535     // In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.
4536     // The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.
4537
4538     // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
4539     // Firefox 9 throws a security error, unleess you call location.href right before doc.write.
4540     //    Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
4541     // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
4542
4543     if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
4544
4545       // Create iFrame with vjs-tech class so it's 100% width/height
4546       var iFrm = vjs.createEl('iframe', {
4547         'id': objId + '_iframe',
4548         'name': objId + '_iframe',
4549         'className': 'vjs-tech',
4550         'scrolling': 'no',
4551         'marginWidth': 0,
4552         'marginHeight': 0,
4553         'frameBorder': 0
4554       });
4555
4556       // Update ready function names in flash vars for iframe window
4557       flashVars['readyFunction'] = 'ready';
4558       flashVars['eventProxyFunction'] = 'events';
4559       flashVars['errorEventProxyFunction'] = 'errors';
4560
4561       // Tried multiple methods to get this to work in all browsers
4562
4563       // Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.
4564       // The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error
4565       // var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
4566       // (in onload)
4567       //  var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
4568       //  iDoc.body.appendChild(temp);
4569
4570       // Tried embedding the flash object through javascript in the iframe source.
4571       // This works in webkit but still triggers the firefox security error
4572       // iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
4573
4574       // Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe
4575       // We should add an option to host the iframe locally though, because it could help a lot of issues.
4576       // iFrm.src = "iframe.html";
4577
4578       // Wait until iFrame has loaded to write into it.
4579       vjs.on(iFrm, 'load', vjs.bind(this, function(){
4580
4581         var iDoc,
4582             iWin = iFrm.contentWindow;
4583
4584         // The one working method I found was to use the iframe's document.write() to create the swf object
4585         // This got around the security issue in all browsers except firefox.
4586         // I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
4587         // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
4588         // Plus Firefox 3.6 didn't work no matter what I tried.
4589         // if (vjs.USER_AGENT.match('Firefox')) {
4590         //   iWin.location.href = '';
4591         // }
4592
4593         // Get the iFrame's document depending on what the browser supports
4594         iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
4595
4596         // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
4597         // Even tried adding /. that was mentioned in a browser security writeup
4598         // document.domain = document.domain+'/.';
4599         // iDoc.domain = document.domain+'/.';
4600
4601         // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
4602         // iDoc.body.innerHTML = swfObjectHTML;
4603
4604         // Tried appending the object to the iframe doc's body. Security error in all browsers.
4605         // iDoc.body.appendChild(swfObject);
4606
4607         // Using document.write actually got around the security error that browsers were throwing.
4608         // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
4609         // Not sure why that's a security issue, but apparently it is.
4610         iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
4611
4612         // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
4613         // So far no issues with swf ready event being called before it's set on the window.
4614         iWin['player'] = this.player_;
4615
4616         // Create swf ready function for iFrame window
4617         iWin['ready'] = vjs.bind(this.player_, function(currSwf){
4618           var el = iDoc.getElementById(currSwf),
4619               player = this,
4620               tech = player.tech;
4621
4622           // Update reference to playback technology element
4623           tech.el_ = el;
4624
4625           // Now that the element is ready, make a click on the swf play the video
4626           vjs.on(el, 'click', tech.bind(tech.onClick));
4627
4628           // Make sure swf is actually ready. Sometimes the API isn't actually yet.
4629           vjs.Flash.checkReady(tech);
4630         });
4631
4632         // Create event listener for all swf events
4633         iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){
4634           var player = this;
4635           if (player && player.techName === 'flash') {
4636             player.trigger(eventName);
4637           }
4638         });
4639
4640         // Create error listener for all swf errors
4641         iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){
4642           vjs.log('Flash Error', eventName);
4643         });
4644
4645       }));
4646
4647       // Replace placeholder with iFrame (it will load now)
4648       placeHolder.parentNode.replaceChild(iFrm, placeHolder);
4649
4650     // If not using iFrame mode, embed as normal object
4651     } else {
4652       vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
4653     }
4654   }
4655 });
4656
4657 vjs.Flash.prototype.dispose = function(){
4658   vjs.MediaTechController.prototype.dispose.call(this);
4659 };
4660
4661 vjs.Flash.prototype.play = function(){
4662   this.el_.vjs_play();
4663 };
4664
4665 vjs.Flash.prototype.pause = function(){
4666   this.el_.vjs_pause();
4667 };
4668
4669 vjs.Flash.prototype.src = function(src){
4670   // Make sure source URL is abosolute.
4671   src = vjs.getAbsoluteURL(src);
4672
4673   this.el_.vjs_src(src);
4674
4675   // Currently the SWF doesn't autoplay if you load a source later.
4676   // e.g. Load player w/ no source, wait 2s, set src.
4677   if (this.player_.autoplay()) {
4678     var tech = this;
4679     setTimeout(function(){ tech.play(); }, 0);
4680   }
4681 };
4682
4683 vjs.Flash.prototype.load = function(){
4684   this.el_.vjs_load();
4685 };
4686
4687 vjs.Flash.prototype.poster = function(){
4688   this.el_.vjs_getProperty('poster');
4689 };
4690
4691 vjs.Flash.prototype.buffered = function(){
4692   return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));
4693 };
4694
4695 vjs.Flash.prototype.supportsFullScreen = function(){
4696   return false; // Flash does not allow fullscreen through javascript
4697 };
4698
4699 vjs.Flash.prototype.enterFullScreen = function(){
4700   return false;
4701 };
4702
4703
4704 // Create setters and getters for attributes
4705 var api = vjs.Flash.prototype,
4706     readWrite = 'preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
4707     readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
4708     // Overridden: buffered
4709
4710 /**
4711  * @this {*}
4712  */
4713 var createSetter = function(attr){
4714   var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
4715   api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };
4716 };
4717
4718 /**
4719  * @this {*}
4720  */
4721 var createGetter = function(attr){
4722   api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
4723 };
4724
4725 (function(){
4726   var i;
4727   // Create getter and setters for all read/write attributes
4728   for (i = 0; i < readWrite.length; i++) {
4729     createGetter(readWrite[i]);
4730     createSetter(readWrite[i]);
4731   }
4732
4733   // Create getters for read-only attributes
4734   for (i = 0; i < readOnly.length; i++) {
4735     createGetter(readOnly[i]);
4736   }
4737 })();
4738
4739 /* Flash Support Testing -------------------------------------------------------- */
4740
4741 vjs.Flash.isSupported = function(){
4742   return vjs.Flash.version()[0] >= 10;
4743   // return swfobject.hasFlashPlayerVersion('10');
4744 };
4745
4746 vjs.Flash.canPlaySource = function(srcObj){
4747   if (srcObj.type in vjs.Flash.formats) { return 'maybe'; }
4748 };
4749
4750 vjs.Flash.formats = {
4751   'video/flv': 'FLV',
4752   'video/x-flv': 'FLV',
4753   'video/mp4': 'MP4',
4754   'video/m4v': 'MP4'
4755 };
4756
4757 vjs.Flash['onReady'] = function(currSwf){
4758   var el = vjs.el(currSwf);
4759
4760   // Get player from box
4761   // On firefox reloads, el might already have a player
4762   var player = el['player'] || el.parentNode['player'],
4763       tech = player.tech;
4764
4765   // Reference player on tech element
4766   el['player'] = player;
4767
4768   // Update reference to playback technology element
4769   tech.el_ = el;
4770
4771   // Now that the element is ready, make a click on the swf play the video
4772   tech.on('click', tech.onClick);
4773
4774   vjs.Flash.checkReady(tech);
4775 };
4776
4777 // The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.
4778 // If it's not ready, we set a timeout to check again shortly.
4779 vjs.Flash.checkReady = function(tech){
4780
4781   // Check if API property exists
4782   if (tech.el().vjs_getProperty) {
4783
4784     // If so, tell tech it's ready
4785     tech.triggerReady();
4786
4787   // Otherwise wait longer.
4788   } else {
4789
4790     setTimeout(function(){
4791       vjs.Flash.checkReady(tech);
4792     }, 50);
4793
4794   }
4795 };
4796
4797 // Trigger events from the swf on the player
4798 vjs.Flash['onEvent'] = function(swfID, eventName){
4799   var player = vjs.el(swfID)['player'];
4800   player.trigger(eventName);
4801 };
4802
4803 // Log errors from the swf
4804 vjs.Flash['onError'] = function(swfID, err){
4805   var player = vjs.el(swfID)['player'];
4806   player.trigger('error');
4807   vjs.log('Flash Error', err, swfID);
4808 };
4809
4810 // Flash Version Check
4811 vjs.Flash.version = function(){
4812   var version = '0,0,0';
4813
4814   // IE
4815   try {
4816     version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
4817
4818   // other browsers
4819   } catch(e) {
4820     try {
4821       if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){
4822         version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
4823       }
4824     } catch(err) {}
4825   }
4826   return version.split(',');
4827 };
4828
4829 // Flash embedding method. Only used in non-iframe mode
4830 vjs.Flash.embed = function(swf, placeHolder, flashVars, params, attributes){
4831   var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),
4832
4833       // Get element by embedding code and retrieving created element
4834       obj = vjs.createEl('div', { innerHTML: code }).childNodes[0],
4835
4836       par = placeHolder.parentNode
4837   ;
4838
4839   placeHolder.parentNode.replaceChild(obj, placeHolder);
4840
4841   // IE6 seems to have an issue where it won't initialize the swf object after injecting it.
4842   // This is a dumb fix
4843   var newObj = par.childNodes[0];
4844   setTimeout(function(){
4845     newObj.style.display = 'block';
4846   }, 1000);
4847
4848   return obj;
4849
4850 };
4851
4852 vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
4853
4854   var objTag = '<object type="application/x-shockwave-flash"',
4855       flashVarsString = '',
4856       paramsString = '',
4857       attrsString = '';
4858
4859   // Convert flash vars to string
4860   if (flashVars) {
4861     vjs.obj.each(flashVars, function(key, val){
4862       flashVarsString += (key + '=' + val + '&amp;');
4863     });
4864   }
4865
4866   // Add swf, flashVars, and other default params
4867   params = vjs.obj.merge({
4868     'movie': swf,
4869     'flashvars': flashVarsString,
4870     'allowScriptAccess': 'always', // Required to talk to swf
4871     'allowNetworking': 'all' // All should be default, but having security issues.
4872   }, params);
4873
4874   // Create param tags string
4875   vjs.obj.each(params, function(key, val){
4876     paramsString += '<param name="'+key+'" value="'+val+'" />';
4877   });
4878
4879   attributes = vjs.obj.merge({
4880     // Add swf to attributes (need both for IE and Others to work)
4881     'data': swf,
4882
4883     // Default to 100% width/height
4884     'width': '100%',
4885     'height': '100%'
4886
4887   }, attributes);
4888
4889   // Create Attributes string
4890   vjs.obj.each(attributes, function(key, val){
4891     attrsString += (key + '="' + val + '" ');
4892   });
4893
4894   return objTag + attrsString + '>' + paramsString + '</object>';
4895 };
4896 /**
4897  * @constructor
4898  */
4899 vjs.MediaLoader = vjs.Component.extend({
4900   /** @constructor */
4901   init: function(player, options, ready){
4902     vjs.Component.call(this, player, options, ready);
4903
4904     // If there are no sources when the player is initialized,
4905     // load the first supported playback technology.
4906     if (!player.options_['sources'] || player.options_['sources'].length === 0) {
4907       for (var i=0,j=player.options_['techOrder']; i<j.length; i++) {
4908         var techName = vjs.capitalize(j[i]),
4909             tech = window['videojs'][techName];
4910
4911         // Check if the browser supports this technology
4912         if (tech && tech.isSupported()) {
4913           player.loadTech(techName);
4914           break;
4915         }
4916       }
4917     } else {
4918       // // Loop through playback technologies (HTML5, Flash) and check for support.
4919       // // Then load the best source.
4920       // // A few assumptions here:
4921       // //   All playback technologies respect preload false.
4922       player.src(player.options_['sources']);
4923     }
4924   }
4925 });/**
4926  * @fileoverview Text Tracks
4927  * Text tracks are tracks of timed text events.
4928  * Captions - text displayed over the video for the hearing impared
4929  * Subtitles - text displayed over the video for those who don't understand langauge in the video
4930  * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
4931  * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
4932  */
4933
4934 // Player Additions - Functions add to the player object for easier access to tracks
4935
4936 /**
4937  * List of associated text tracks
4938  * @type {Array}
4939  * @private
4940  */
4941 vjs.Player.prototype.textTracks_;
4942
4943 /**
4944  * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
4945  * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
4946  * @return {Array}           Array of track objects
4947  */
4948 vjs.Player.prototype.textTracks = function(){
4949   this.textTracks_ = this.textTracks_ || [];
4950   return this.textTracks_;
4951 };
4952
4953 /**
4954  * Add a text track
4955  * In addition to the W3C settings we allow adding additional info through options.
4956  * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
4957  * @param {String}  kind        Captions, subtitles, chapters, descriptions, or metadata
4958  * @param {String=} label       Optional label
4959  * @param {String=} language    Optional language
4960  * @param {Object=} options     Additional track options, like src
4961  */
4962 vjs.Player.prototype.addTextTrack = function(kind, label, language, options){
4963   var tracks = this.textTracks_ = this.textTracks_ || [];
4964   options = options || {};
4965
4966   options['kind'] = kind;
4967   options['label'] = label;
4968   options['language'] = language;
4969
4970   // HTML5 Spec says default to subtitles.
4971   // Uppercase first letter to match class names
4972   var Kind = vjs.capitalize(kind || 'subtitles');
4973
4974   // Create correct texttrack class. CaptionsTrack, etc.
4975   var track = new window['videojs'][Kind + 'Track'](this, options);
4976
4977   tracks.push(track);
4978
4979   // If track.dflt() is set, start showing immediately
4980   // TODO: Add a process to deterime the best track to show for the specific kind
4981   // Incase there are mulitple defaulted tracks of the same kind
4982   // Or the user has a set preference of a specific language that should override the default
4983   // if (track.dflt()) {
4984   //   this.ready(vjs.bind(track, track.show));
4985   // }
4986
4987   return track;
4988 };
4989
4990 /**
4991  * Add an array of text tracks. captions, subtitles, chapters, descriptions
4992  * Track objects will be stored in the player.textTracks() array
4993  * @param {Array} trackList Array of track elements or objects (fake track elements)
4994  */
4995 vjs.Player.prototype.addTextTracks = function(trackList){
4996   var trackObj;
4997
4998   for (var i = 0; i < trackList.length; i++) {
4999     trackObj = trackList[i];
5000     this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);
5001   }
5002
5003   return this;
5004 };
5005
5006 // Show a text track
5007 // disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
5008 vjs.Player.prototype.showTextTrack = function(id, disableSameKind){
5009   var tracks = this.textTracks_,
5010       i = 0,
5011       j = tracks.length,
5012       track, showTrack, kind;
5013
5014   // Find Track with same ID
5015   for (;i<j;i++) {
5016     track = tracks[i];
5017     if (track.id() === id) {
5018       track.show();
5019       showTrack = track;
5020
5021     // Disable tracks of the same kind
5022     } else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {
5023       track.disable();
5024     }
5025   }
5026
5027   // Get track kind from shown track or disableSameKind
5028   kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);
5029
5030   // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.
5031   if (kind) {
5032     this.trigger(kind+'trackchange');
5033   }
5034
5035   return this;
5036 };
5037
5038 /**
5039  * Track Class
5040  * Contains track methods for loading, showing, parsing cues of tracks
5041  * @param {vjs.Player|Object} player
5042  * @param {Object=} options
5043  * @constructor
5044  */
5045 vjs.TextTrack = vjs.Component.extend({
5046   /** @constructor */
5047   init: function(player, options){
5048     vjs.Component.call(this, player, options);
5049
5050     // Apply track info to track object
5051     // Options will often be a track element
5052
5053     // Build ID if one doesn't exist
5054     this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
5055     this.src_ = options['src'];
5056     // 'default' is a reserved keyword in js so we use an abbreviated version
5057     this.dflt_ = options['default'] || options['dflt'];
5058     this.title_ = options['title'];
5059     this.language_ = options['srclang'];
5060     this.label_ = options['label'];
5061     this.cues_ = [];
5062     this.activeCues_ = [];
5063     this.readyState_ = 0;
5064     this.mode_ = 0;
5065
5066     this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));
5067   }
5068 });
5069
5070 /**
5071  * Track kind value. Captions, subtitles, etc.
5072  * @private
5073  */
5074 vjs.TextTrack.prototype.kind_;
5075
5076 /**
5077  * Get the track kind value
5078  * @return {String}
5079  */
5080 vjs.TextTrack.prototype.kind = function(){
5081   return this.kind_;
5082 };
5083
5084 /**
5085  * Track src value
5086  * @private
5087  */
5088 vjs.TextTrack.prototype.src_;
5089
5090 /**
5091  * Get the track src value
5092  * @return {String}
5093  */
5094 vjs.TextTrack.prototype.src = function(){
5095   return this.src_;
5096 };
5097
5098 /**
5099  * Track default value
5100  * If default is used, subtitles/captions to start showing
5101  * @private
5102  */
5103 vjs.TextTrack.prototype.dflt_;
5104
5105 /**
5106  * Get the track default value
5107  * 'default' is a reserved keyword
5108  * @return {Boolean}
5109  */
5110 vjs.TextTrack.prototype.dflt = function(){
5111   return this.dflt_;
5112 };
5113
5114 /**
5115  * Track title value
5116  * @private
5117  */
5118 vjs.TextTrack.prototype.title_;
5119
5120 /**
5121  * Get the track title value
5122  * @return {String}
5123  */
5124 vjs.TextTrack.prototype.title = function(){
5125   return this.title_;
5126 };
5127
5128 /**
5129  * Language - two letter string to represent track language, e.g. 'en' for English
5130  * Spec def: readonly attribute DOMString language;
5131  * @private
5132  */
5133 vjs.TextTrack.prototype.language_;
5134
5135 /**
5136  * Get the track language value
5137  * @return {String}
5138  */
5139 vjs.TextTrack.prototype.language = function(){
5140   return this.language_;
5141 };
5142
5143 /**
5144  * Track label e.g. 'English'
5145  * Spec def: readonly attribute DOMString label;
5146  * @private
5147  */
5148 vjs.TextTrack.prototype.label_;
5149
5150 /**
5151  * Get the track label value
5152  * @return {String}
5153  */
5154 vjs.TextTrack.prototype.label = function(){
5155   return this.label_;
5156 };
5157
5158 /**
5159  * All cues of the track. Cues have a startTime, endTime, text, and other properties.
5160  * Spec def: readonly attribute TextTrackCueList cues;
5161  * @private
5162  */
5163 vjs.TextTrack.prototype.cues_;
5164
5165 /**
5166  * Get the track cues
5167  * @return {Array}
5168  */
5169 vjs.TextTrack.prototype.cues = function(){
5170   return this.cues_;
5171 };
5172
5173 /**
5174  * ActiveCues is all cues that are currently showing
5175  * Spec def: readonly attribute TextTrackCueList activeCues;
5176  * @private
5177  */
5178 vjs.TextTrack.prototype.activeCues_;
5179
5180 /**
5181  * Get the track active cues
5182  * @return {Array}
5183  */
5184 vjs.TextTrack.prototype.activeCues = function(){
5185   return this.activeCues_;
5186 };
5187
5188 /**
5189  * ReadyState describes if the text file has been loaded
5190  * const unsigned short NONE = 0;
5191  * const unsigned short LOADING = 1;
5192  * const unsigned short LOADED = 2;
5193  * const unsigned short ERROR = 3;
5194  * readonly attribute unsigned short readyState;
5195  * @private
5196  */
5197 vjs.TextTrack.prototype.readyState_;
5198
5199 /**
5200  * Get the track readyState
5201  * @return {Number}
5202  */
5203 vjs.TextTrack.prototype.readyState = function(){
5204   return this.readyState_;
5205 };
5206
5207 /**
5208  * Mode describes if the track is showing, hidden, or disabled
5209  * const unsigned short OFF = 0;
5210  * const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)
5211  * const unsigned short SHOWING = 2;
5212  * attribute unsigned short mode;
5213  * @private
5214  */
5215 vjs.TextTrack.prototype.mode_;
5216
5217 /**
5218  * Get the track mode
5219  * @return {Number}
5220  */
5221 vjs.TextTrack.prototype.mode = function(){
5222   return this.mode_;
5223 };
5224
5225 /**
5226  * Change the font size of the text track to make it larger when playing in fullscreen mode
5227  * and restore it to its normal size when not in fullscreen mode.
5228  */
5229 vjs.TextTrack.prototype.adjustFontSize = function(){
5230     if (this.player_.isFullScreen) {
5231         // Scale the font by the same factor as increasing the video width to the full screen window width.
5232         // Additionally, multiply that factor by 1.4, which is the default font size for
5233         // the caption track (from the CSS)
5234         this.el_.style.fontSize = screen.width / this.player_.width() * 1.4 * 100 + '%';
5235     } else {
5236         // Change the font size of the text track back to its original non-fullscreen size
5237         this.el_.style.fontSize = '';
5238     }
5239 };
5240
5241 /**
5242  * Create basic div to hold cue text
5243  * @return {Element}
5244  */
5245 vjs.TextTrack.prototype.createEl = function(){
5246   return vjs.Component.prototype.createEl.call(this, 'div', {
5247     className: 'vjs-' + this.kind_ + ' vjs-text-track'
5248   });
5249 };
5250
5251 /**
5252  * Show: Mode Showing (2)
5253  * Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
5254  * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
5255  * In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;
5256  * for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a non-visual fashion;
5257  * and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.
5258  * The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.
5259  * This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
5260  */
5261 vjs.TextTrack.prototype.show = function(){
5262   this.activate();
5263
5264   this.mode_ = 2;
5265
5266   // Show element.
5267   vjs.Component.prototype.show.call(this);
5268 };
5269
5270 /**
5271  * Hide: Mode Hidden (1)
5272  * Indicates that the text track is active, but that the user agent is not actively displaying the cues.
5273  * If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
5274  * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
5275  */
5276 vjs.TextTrack.prototype.hide = function(){
5277   // When hidden, cues are still triggered. Disable to stop triggering.
5278   this.activate();
5279
5280   this.mode_ = 1;
5281
5282   // Hide element.
5283   vjs.Component.prototype.hide.call(this);
5284 };
5285
5286 /**
5287  * Disable: Mode Off/Disable (0)
5288  * Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.
5289  * No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
5290  */
5291 vjs.TextTrack.prototype.disable = function(){
5292   // If showing, hide.
5293   if (this.mode_ == 2) { this.hide(); }
5294
5295   // Stop triggering cues
5296   this.deactivate();
5297
5298   // Switch Mode to Off
5299   this.mode_ = 0;
5300 };
5301
5302 /**
5303  * Turn on cue tracking. Tracks that are showing OR hidden are active.
5304  */
5305 vjs.TextTrack.prototype.activate = function(){
5306   // Load text file if it hasn't been yet.
5307   if (this.readyState_ === 0) { this.load(); }
5308
5309   // Only activate if not already active.
5310   if (this.mode_ === 0) {
5311     // Update current cue on timeupdate
5312     // Using unique ID for bind function so other tracks don't remove listener
5313     this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));
5314
5315     // Reset cue time on media end
5316     this.player_.on('ended', vjs.bind(this, this.reset, this.id_));
5317
5318     // Add to display
5319     if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {
5320       this.player_.getChild('textTrackDisplay').addChild(this);
5321     }
5322   }
5323 };
5324
5325 /**
5326  * Turn off cue tracking.
5327  */
5328 vjs.TextTrack.prototype.deactivate = function(){
5329   // Using unique ID for bind function so other tracks don't remove listener
5330   this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));
5331   this.player_.off('ended', vjs.bind(this, this.reset, this.id_));
5332   this.reset(); // Reset
5333
5334   // Remove from display
5335   this.player_.getChild('textTrackDisplay').removeChild(this);
5336 };
5337
5338 // A readiness state
5339 // One of the following:
5340 //
5341 // Not loaded
5342 // Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained.
5343 //
5344 // Loading
5345 // Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track.
5346 //
5347 // Loaded
5348 // Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object.
5349 //
5350 // Failed to load
5351 // Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.
5352 vjs.TextTrack.prototype.load = function(){
5353
5354   // Only load if not loaded yet.
5355   if (this.readyState_ === 0) {
5356     this.readyState_ = 1;
5357     vjs.get(this.src_, vjs.bind(this, this.parseCues), vjs.bind(this, this.onError));
5358   }
5359
5360 };
5361
5362 vjs.TextTrack.prototype.onError = function(err){
5363   this.error = err;
5364   this.readyState_ = 3;
5365   this.trigger('error');
5366 };
5367
5368 // Parse the WebVTT text format for cue times.
5369 // TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
5370 vjs.TextTrack.prototype.parseCues = function(srcContent) {
5371   var cue, time, text,
5372       lines = srcContent.split('\n'),
5373       line = '', id;
5374
5375   for (var i=1, j=lines.length; i<j; i++) {
5376     // Line 0 should be 'WEBVTT', so skipping i=0
5377
5378     line = vjs.trim(lines[i]); // Trim whitespace and linebreaks
5379
5380     if (line) { // Loop until a line with content
5381
5382       // First line could be an optional cue ID
5383       // Check if line has the time separator
5384       if (line.indexOf('-->') == -1) {
5385         id = line;
5386         // Advance to next line for timing.
5387         line = vjs.trim(lines[++i]);
5388       } else {
5389         id = this.cues_.length;
5390       }
5391
5392       // First line - Number
5393       cue = {
5394         id: id, // Cue Number
5395         index: this.cues_.length // Position in Array
5396       };
5397
5398       // Timing line
5399       time = line.split(' --> ');
5400       cue.startTime = this.parseCueTime(time[0]);
5401       cue.endTime = this.parseCueTime(time[1]);
5402
5403       // Additional lines - Cue Text
5404       text = [];
5405
5406       // Loop until a blank line or end of lines
5407       // Assumeing trim('') returns false for blank lines
5408       while (lines[++i] && (line = vjs.trim(lines[i]))) {
5409         text.push(line);
5410       }
5411
5412       cue.text = text.join('<br/>');
5413
5414       // Add this cue
5415       this.cues_.push(cue);
5416     }
5417   }
5418
5419   this.readyState_ = 2;
5420   this.trigger('loaded');
5421 };
5422
5423
5424 vjs.TextTrack.prototype.parseCueTime = function(timeText) {
5425   var parts = timeText.split(':'),
5426       time = 0,
5427       hours, minutes, other, seconds, ms;
5428
5429   // Check if optional hours place is included
5430   // 00:00:00.000 vs. 00:00.000
5431   if (parts.length == 3) {
5432     hours = parts[0];
5433     minutes = parts[1];
5434     other = parts[2];
5435   } else {
5436     hours = 0;
5437     minutes = parts[0];
5438     other = parts[1];
5439   }
5440
5441   // Break other (seconds, milliseconds, and flags) by spaces
5442   // TODO: Make additional cue layout settings work with flags
5443   other = other.split(/\s+/);
5444   // Remove seconds. Seconds is the first part before any spaces.
5445   seconds = other.splice(0,1)[0];
5446   // Could use either . or , for decimal
5447   seconds = seconds.split(/\.|,/);
5448   // Get milliseconds
5449   ms = parseFloat(seconds[1]);
5450   seconds = seconds[0];
5451
5452   // hours => seconds
5453   time += parseFloat(hours) * 3600;
5454   // minutes => seconds
5455   time += parseFloat(minutes) * 60;
5456   // Add seconds
5457   time += parseFloat(seconds);
5458   // Add milliseconds
5459   if (ms) { time += ms/1000; }
5460
5461   return time;
5462 };
5463
5464 // Update active cues whenever timeupdate events are triggered on the player.
5465 vjs.TextTrack.prototype.update = function(){
5466   if (this.cues_.length > 0) {
5467
5468     // Get curent player time
5469     var time = this.player_.currentTime();
5470
5471     // Check if the new time is outside the time box created by the the last update.
5472     if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
5473       var cues = this.cues_,
5474
5475           // Create a new time box for this state.
5476           newNextChange = this.player_.duration(), // Start at beginning of the timeline
5477           newPrevChange = 0, // Start at end
5478
5479           reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.
5480           newCues = [], // Store new active cues.
5481
5482           // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.
5483           firstActiveIndex, lastActiveIndex,
5484           cue, i; // Loop vars
5485
5486       // Check if time is going forwards or backwards (scrubbing/rewinding)
5487       // If we know the direction we can optimize the starting position and direction of the loop through the cues array.
5488       if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen
5489         // Forwards, so start at the index of the first active cue and loop forward
5490         i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;
5491       } else {
5492         // Backwards, so start at the index of the last active cue and loop backward
5493         reverse = true;
5494         i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;
5495       }
5496
5497       while (true) { // Loop until broken
5498         cue = cues[i];
5499
5500         // Cue ended at this point
5501         if (cue.endTime <= time) {
5502           newPrevChange = Math.max(newPrevChange, cue.endTime);
5503
5504           if (cue.active) {
5505             cue.active = false;
5506           }
5507
5508           // No earlier cues should have an active start time.
5509           // Nevermind. Assume first cue could have a duration the same as the video.
5510           // In that case we need to loop all the way back to the beginning.
5511           // if (reverse && cue.startTime) { break; }
5512
5513         // Cue hasn't started
5514         } else if (time < cue.startTime) {
5515           newNextChange = Math.min(newNextChange, cue.startTime);
5516
5517           if (cue.active) {
5518             cue.active = false;
5519           }
5520
5521           // No later cues should have an active start time.
5522           if (!reverse) { break; }
5523
5524         // Cue is current
5525         } else {
5526
5527           if (reverse) {
5528             // Add cue to front of array to keep in time order
5529             newCues.splice(0,0,cue);
5530
5531             // If in reverse, the first current cue is our lastActiveCue
5532             if (lastActiveIndex === undefined) { lastActiveIndex = i; }
5533             firstActiveIndex = i;
5534           } else {
5535             // Add cue to end of array
5536             newCues.push(cue);
5537
5538             // If forward, the first current cue is our firstActiveIndex
5539             if (firstActiveIndex === undefined) { firstActiveIndex = i; }
5540             lastActiveIndex = i;
5541           }
5542
5543           newNextChange = Math.min(newNextChange, cue.endTime);
5544           newPrevChange = Math.max(newPrevChange, cue.startTime);
5545
5546           cue.active = true;
5547         }
5548
5549         if (reverse) {
5550           // Reverse down the array of cues, break if at first
5551           if (i === 0) { break; } else { i--; }
5552         } else {
5553           // Walk up the array fo cues, break if at last
5554           if (i === cues.length - 1) { break; } else { i++; }
5555         }
5556
5557       }
5558
5559       this.activeCues_ = newCues;
5560       this.nextChange = newNextChange;
5561       this.prevChange = newPrevChange;
5562       this.firstActiveIndex = firstActiveIndex;
5563       this.lastActiveIndex = lastActiveIndex;
5564
5565       this.updateDisplay();
5566
5567       this.trigger('cuechange');
5568     }
5569   }
5570 };
5571
5572 // Add cue HTML to display
5573 vjs.TextTrack.prototype.updateDisplay = function(){
5574   var cues = this.activeCues_,
5575       html = '',
5576       i=0,j=cues.length;
5577
5578   for (;i<j;i++) {
5579     html += '<span class="vjs-tt-cue">'+cues[i].text+'</span>';
5580   }
5581
5582   this.el_.innerHTML = html;
5583 };
5584
5585 // Set all loop helper values back
5586 vjs.TextTrack.prototype.reset = function(){
5587   this.nextChange = 0;
5588   this.prevChange = this.player_.duration();
5589   this.firstActiveIndex = 0;
5590   this.lastActiveIndex = 0;
5591 };
5592
5593 // Create specific track types
5594 /**
5595  * @constructor
5596  */
5597 vjs.CaptionsTrack = vjs.TextTrack.extend();
5598 vjs.CaptionsTrack.prototype.kind_ = 'captions';
5599 // Exporting here because Track creation requires the track kind
5600 // to be available on global object. e.g. new window['videojs'][Kind + 'Track']
5601
5602 /**
5603  * @constructor
5604  */
5605 vjs.SubtitlesTrack = vjs.TextTrack.extend();
5606 vjs.SubtitlesTrack.prototype.kind_ = 'subtitles';
5607
5608 /**
5609  * @constructor
5610  */
5611 vjs.ChaptersTrack = vjs.TextTrack.extend();
5612 vjs.ChaptersTrack.prototype.kind_ = 'chapters';
5613
5614
5615 /* Text Track Display
5616 ============================================================================= */
5617 // Global container for both subtitle and captions text. Simple div container.
5618
5619 /**
5620  * @constructor
5621  */
5622 vjs.TextTrackDisplay = vjs.Component.extend({
5623   /** @constructor */
5624   init: function(player, options, ready){
5625     vjs.Component.call(this, player, options, ready);
5626
5627     // This used to be called during player init, but was causing an error
5628     // if a track should show by default and the display hadn't loaded yet.
5629     // Should probably be moved to an external track loader when we support
5630     // tracks that don't need a display.
5631     if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
5632       this.player_.addTextTracks(player.options_['tracks']);
5633     }
5634   }
5635 });
5636
5637 vjs.TextTrackDisplay.prototype.createEl = function(){
5638   return vjs.Component.prototype.createEl.call(this, 'div', {
5639     className: 'vjs-text-track-display'
5640   });
5641 };
5642
5643
5644 /* Text Track Menu Items
5645 ============================================================================= */
5646 /**
5647  * @constructor
5648  */
5649 vjs.TextTrackMenuItem = vjs.MenuItem.extend({
5650   /** @constructor */
5651   init: function(player, options){
5652     var track = this.track = options['track'];
5653
5654     // Modify options for parent MenuItem class's init.
5655     options['label'] = track.label();
5656     options['selected'] = track.dflt();
5657     vjs.MenuItem.call(this, player, options);
5658
5659     this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));
5660   }
5661 });
5662
5663 vjs.TextTrackMenuItem.prototype.onClick = function(){
5664   vjs.MenuItem.prototype.onClick.call(this);
5665   this.player_.showTextTrack(this.track.id_, this.track.kind());
5666 };
5667
5668 vjs.TextTrackMenuItem.prototype.update = function(){
5669   if (this.track.mode() == 2) {
5670     this.selected(true);
5671   } else {
5672     this.selected(false);
5673   }
5674 };
5675
5676 /**
5677  * @constructor
5678  */
5679 vjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({
5680   /** @constructor */
5681   init: function(player, options){
5682     // Create pseudo track info
5683     // Requires options['kind']
5684     options['track'] = {
5685       kind: function() { return options['kind']; },
5686       player: player,
5687       label: function(){ return options['kind'] + ' off'; },
5688       dflt: function(){ return false; },
5689       mode: function(){ return false; }
5690     };
5691     vjs.TextTrackMenuItem.call(this, player, options);
5692     this.selected(true);
5693   }
5694 });
5695
5696 vjs.OffTextTrackMenuItem.prototype.onClick = function(){
5697   vjs.TextTrackMenuItem.prototype.onClick.call(this);
5698   this.player_.showTextTrack(this.track.id_, this.track.kind());
5699 };
5700
5701 vjs.OffTextTrackMenuItem.prototype.update = function(){
5702   var tracks = this.player_.textTracks(),
5703       i=0, j=tracks.length, track,
5704       off = true;
5705
5706   for (;i<j;i++) {
5707     track = tracks[i];
5708     if (track.kind() == this.track.kind() && track.mode() == 2) {
5709       off = false;
5710     }
5711   }
5712
5713   if (off) {
5714     this.selected(true);
5715   } else {
5716     this.selected(false);
5717   }
5718 };
5719
5720 /* Captions Button
5721 ================================================================================ */
5722 /**
5723  * @constructor
5724  */
5725 vjs.TextTrackButton = vjs.MenuButton.extend({
5726   /** @constructor */
5727   init: function(player, options){
5728     vjs.MenuButton.call(this, player, options);
5729
5730     if (this.items.length <= 1) {
5731       this.hide();
5732     }
5733   }
5734 });
5735
5736 // vjs.TextTrackButton.prototype.buttonPressed = false;
5737
5738 // vjs.TextTrackButton.prototype.createMenu = function(){
5739 //   var menu = new vjs.Menu(this.player_);
5740
5741 //   // Add a title list item to the top
5742 //   // menu.el().appendChild(vjs.createEl('li', {
5743 //   //   className: 'vjs-menu-title',
5744 //   //   innerHTML: vjs.capitalize(this.kind_),
5745 //   //   tabindex: -1
5746 //   // }));
5747
5748 //   this.items = this.createItems();
5749
5750 //   // Add menu items to the menu
5751 //   for (var i = 0; i < this.items.length; i++) {
5752 //     menu.addItem(this.items[i]);
5753 //   }
5754
5755 //   // Add list to element
5756 //   this.addChild(menu);
5757
5758 //   return menu;
5759 // };
5760
5761 // Create a menu item for each text track
5762 vjs.TextTrackButton.prototype.createItems = function(){
5763   var items = [], track;
5764
5765   // Add an OFF menu item to turn all tracks off
5766   items.push(new vjs.OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
5767
5768   for (var i = 0; i < this.player_.textTracks().length; i++) {
5769     track = this.player_.textTracks()[i];
5770     if (track.kind() === this.kind_) {
5771       items.push(new vjs.TextTrackMenuItem(this.player_, {
5772         'track': track
5773       }));
5774     }
5775   }
5776
5777   return items;
5778 };
5779
5780 /**
5781  * @constructor
5782  */
5783 vjs.CaptionsButton = vjs.TextTrackButton.extend({
5784   /** @constructor */
5785   init: function(player, options, ready){
5786     vjs.TextTrackButton.call(this, player, options, ready);
5787     this.el_.setAttribute('aria-label','Captions Menu');
5788   }
5789 });
5790 vjs.CaptionsButton.prototype.kind_ = 'captions';
5791 vjs.CaptionsButton.prototype.buttonText = 'Captions';
5792 vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
5793
5794 /**
5795  * @constructor
5796  */
5797 vjs.SubtitlesButton = vjs.TextTrackButton.extend({
5798   /** @constructor */
5799   init: function(player, options, ready){
5800     vjs.TextTrackButton.call(this, player, options, ready);
5801     this.el_.setAttribute('aria-label','Subtitles Menu');
5802   }
5803 });
5804 vjs.SubtitlesButton.prototype.kind_ = 'subtitles';
5805 vjs.SubtitlesButton.prototype.buttonText = 'Subtitles';
5806 vjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';
5807
5808 // Chapters act much differently than other text tracks
5809 // Cues are navigation vs. other tracks of alternative languages
5810 /**
5811  * @constructor
5812  */
5813 vjs.ChaptersButton = vjs.TextTrackButton.extend({
5814   /** @constructor */
5815   init: function(player, options, ready){
5816     vjs.TextTrackButton.call(this, player, options, ready);
5817     this.el_.setAttribute('aria-label','Chapters Menu');
5818   }
5819 });
5820 vjs.ChaptersButton.prototype.kind_ = 'chapters';
5821 vjs.ChaptersButton.prototype.buttonText = 'Chapters';
5822 vjs.ChaptersButton.prototype.className = 'vjs-chapters-button';
5823
5824 // Create a menu item for each text track
5825 vjs.ChaptersButton.prototype.createItems = function(){
5826   var items = [], track;
5827
5828   for (var i = 0; i < this.player_.textTracks().length; i++) {
5829     track = this.player_.textTracks()[i];
5830     if (track.kind() === this.kind_) {
5831       items.push(new vjs.TextTrackMenuItem(this.player_, {
5832         'track': track
5833       }));
5834     }
5835   }
5836
5837   return items;
5838 };
5839
5840 vjs.ChaptersButton.prototype.createMenu = function(){
5841   var tracks = this.player_.textTracks(),
5842       i = 0,
5843       j = tracks.length,
5844       track, chaptersTrack,
5845       items = this.items = [];
5846
5847   for (;i<j;i++) {
5848     track = tracks[i];
5849     if (track.kind() == this.kind_ && track.dflt()) {
5850       if (track.readyState() < 2) {
5851         this.chaptersTrack = track;
5852         track.on('loaded', vjs.bind(this, this.createMenu));
5853         return;
5854       } else {
5855         chaptersTrack = track;
5856         break;
5857       }
5858     }
5859   }
5860
5861   var menu = this.menu = new vjs.Menu(this.player_);
5862
5863   menu.el_.appendChild(vjs.createEl('li', {
5864     className: 'vjs-menu-title',
5865     innerHTML: vjs.capitalize(this.kind_),
5866     tabindex: -1
5867   }));
5868
5869   if (chaptersTrack) {
5870     var cues = chaptersTrack.cues_, cue, mi;
5871     i = 0;
5872     j = cues.length;
5873
5874     for (;i<j;i++) {
5875       cue = cues[i];
5876
5877       mi = new vjs.ChaptersTrackMenuItem(this.player_, {
5878         'track': chaptersTrack,
5879         'cue': cue
5880       });
5881
5882       items.push(mi);
5883
5884       menu.addChild(mi);
5885     }
5886   }
5887
5888   if (this.items.length > 0) {
5889     this.show();
5890   }
5891
5892   return menu;
5893 };
5894
5895
5896 /**
5897  * @constructor
5898  */
5899 vjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({
5900   /** @constructor */
5901   init: function(player, options){
5902     var track = this.track = options['track'],
5903         cue = this.cue = options['cue'],
5904         currentTime = player.currentTime();
5905
5906     // Modify options for parent MenuItem class's init.
5907     options['label'] = cue.text;
5908     options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
5909     vjs.MenuItem.call(this, player, options);
5910
5911     track.on('cuechange', vjs.bind(this, this.update));
5912   }
5913 });
5914
5915 vjs.ChaptersTrackMenuItem.prototype.onClick = function(){
5916   vjs.MenuItem.prototype.onClick.call(this);
5917   this.player_.currentTime(this.cue.startTime);
5918   this.update(this.cue.startTime);
5919 };
5920
5921 vjs.ChaptersTrackMenuItem.prototype.update = function(){
5922   var cue = this.cue,
5923       currentTime = this.player_.currentTime();
5924
5925   // vjs.log(currentTime, cue.startTime);
5926   if (cue.startTime <= currentTime && currentTime < cue.endTime) {
5927     this.selected(true);
5928   } else {
5929     this.selected(false);
5930   }
5931 };
5932
5933 // Add Buttons to controlBar
5934 vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
5935   'subtitlesButton': {},
5936   'captionsButton': {},
5937   'chaptersButton': {}
5938 });
5939
5940 // vjs.Cue = vjs.Component.extend({
5941 //   /** @constructor */
5942 //   init: function(player, options){
5943 //     vjs.Component.call(this, player, options);
5944 //   }
5945 // });
5946 /**
5947  * @fileoverview Add JSON support
5948  * @suppress {undefinedVars}
5949  * (Compiler doesn't like JSON not being declared)
5950  */
5951
5952 /**
5953  * Javascript JSON implementation
5954  * (Parse Method Only)
5955  * https://github.com/douglascrockford/JSON-js/blob/master/json2.js
5956  * Only using for parse method when parsing data-setup attribute JSON.
5957  * @type {Object}
5958  * @suppress {undefinedVars}
5959  */
5960 vjs.JSON;
5961
5962 /**
5963  * @suppress {undefinedVars}
5964  */
5965 if (typeof window.JSON !== 'undefined' && window.JSON.parse === 'function') {
5966   vjs.JSON = window.JSON;
5967
5968 } else {
5969   vjs.JSON = {};
5970
5971   var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
5972
5973   vjs.JSON.parse = function (text, reviver) {
5974       var j;
5975
5976       function walk(holder, key) {
5977           var k, v, value = holder[key];
5978           if (value && typeof value === 'object') {
5979               for (k in value) {
5980                   if (Object.prototype.hasOwnProperty.call(value, k)) {
5981                       v = walk(value, k);
5982                       if (v !== undefined) {
5983                           value[k] = v;
5984                       } else {
5985                           delete value[k];
5986                       }
5987                   }
5988               }
5989           }
5990           return reviver.call(holder, key, value);
5991       }
5992       text = String(text);
5993       cx.lastIndex = 0;
5994       if (cx.test(text)) {
5995           text = text.replace(cx, function (a) {
5996               return '\\u' +
5997                   ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
5998           });
5999       }
6000
6001       if (/^[\],:{}\s]*$/
6002               .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
6003                   .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
6004                   .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
6005
6006           j = eval('(' + text + ')');
6007
6008           return typeof reviver === 'function' ?
6009               walk({'': j}, '') : j;
6010       }
6011
6012       throw new SyntaxError('JSON.parse(): invalid or malformed JSON data');
6013   };
6014 }
6015 /**
6016  * @fileoverview Functions for automatically setting up a player
6017  * based on the data-setup attribute of the video tag
6018  */
6019
6020 // Automatically set up any tags that have a data-setup attribute
6021 vjs.autoSetup = function(){
6022   var options, vid, player,
6023       vids = document.getElementsByTagName('video');
6024
6025   // Check if any media elements exist
6026   if (vids && vids.length > 0) {
6027
6028     for (var i=0,j=vids.length; i<j; i++) {
6029       vid = vids[i];
6030
6031       // Check if element exists, has getAttribute func.
6032       // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
6033       if (vid && vid.getAttribute) {
6034
6035         // Make sure this player hasn't already been set up.
6036         if (vid['player'] === undefined) {
6037           options = vid.getAttribute('data-setup');
6038
6039           // Check if data-setup attr exists.
6040           // We only auto-setup if they've added the data-setup attr.
6041           if (options !== null) {
6042
6043             // Parse options JSON
6044             // If empty string, make it a parsable json object.
6045             options = vjs.JSON.parse(options || '{}');
6046
6047             // Create new video.js instance.
6048             player = videojs(vid, options);
6049           }
6050         }
6051
6052       // If getAttribute isn't defined, we need to wait for the DOM.
6053       } else {
6054         vjs.autoSetupTimeout(1);
6055         break;
6056       }
6057     }
6058
6059   // No videos were found, so keep looping unless page is finisehd loading.
6060   } else if (!vjs.windowLoaded) {
6061     vjs.autoSetupTimeout(1);
6062   }
6063 };
6064
6065 // Pause to let the DOM keep processing
6066 vjs.autoSetupTimeout = function(wait){
6067   setTimeout(vjs.autoSetup, wait);
6068 };
6069
6070 vjs.one(window, 'load', function(){
6071   vjs.windowLoaded = true;
6072 });
6073
6074 // Run Auto-load players
6075 // You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
6076 vjs.autoSetupTimeout(1);
6077 vjs.plugin = function(name, init){
6078   vjs.Player.prototype[name] = init;
6079 };