github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/libs/video-js/video.dev.js (about)

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