source: extensions/piwigo_videojs/video-js/video.dev.js @ 24676

Last change on this file since 24676 was 24676, checked in by ddtddt, 11 years ago

[extensions] - piwigo_videojs - add file for translate

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