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