PageSourceSearch

https://www.sunpln.net/wp-content/themes/shangpintongxinshebei/js/videojs/video.js

js sunpln.net collected 2026-10-08 04:16:49 UTC 145,550 bytes, 4,569 lines download raw bytes

1/*!
2Video.js - HTML5 Video Player
3Version 3.2.0
4
5LGPL v3 LICENSE INFO
6This file is part of Video.js. Copyright 2011 Zencoder, Inc.
7
8Video.js is free software: you can redistribute it and/or modify
9it under the terms of the GNU Lesser General Public License as published by
10the Free Software Foundation, either version 3 of the License, or
11(at your option) any later version.
12
13Video.js is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16GNU Lesser General Public License for more details.
17
18You should have received a copy of the GNU Lesser General Public License
19along with Video.js.  If not, see <http://www.gnu.org/licenses/>.
20*/
21
22// Self-executing function to prevent global vars and help with minification
23;(function(window, undefined){
24  var document = window.document;// HTML5 Shiv. Must be in <head> to support older browsers.
25document.createElement("video");document.createElement("audio");
26
27var VideoJS = function(id, addOptions, ready){
28  var tag; // Element of ID
29
30  // Allow for element or ID to be passed in
31  // String ID
32  if (typeof id == "string") {
33
34    // Adjust for jQuery ID syntax
35    if (id.indexOf("#") === 0) {
36      id = id.slice(1);
37    }
38
39    // If a player instance has already been created for this ID return it.
40    if (_V_.players[id]) {
41      return _V_.players[id];
42
43    // Otherwise get element for ID
44    } else {
45      tag = _V_.el(id)
46    }
47
48  // ID is a media element
49  } else {
50    tag = id;
51  }
52
53  // Check for a useable element
54  if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also
55    throw new TypeError("The element or ID supplied is not valid. (VideoJS)"); // Returns
56  }
57
58  // Element may have a player attr referring to an already created player instance.
59  // If not, set up a new player and return the instance.
60  return tag.player || new _V_.Player(tag, addOptions, ready);
61},
62
63// Shortcut
64_V_ = VideoJS,
65
66// CDN Version. Used to target right flash swf.
67CDN_VERSION = "3.2";
68
69VideoJS.players = {};
70
71VideoJS.options = {
72
73  // Default order of fallback technology
74  techOrder: ["html5","flash"],
75  // techOrder: ["flash","html5"],
76
77  html5: {},
78  flash: { swf: "http://vjs.zencdn.net/c/video-js.swf" },
79
80  // Default of web browser is 300x150. Should rely on source width/height.
81  width: "auto",
82  height: "auto",
83  
84  // defaultVolume: 0.85,
85  defaultVolume: 0.00, // The freakin seaguls are driving me crazy!
86
87  // Included control sets
88  components: {
89    "posterImage": {},
90    "textTrackDisplay": {},
91    "loadingSpinner": {},
92    "bigPlayButton": {},
93    "controlBar": {}
94  }
95
96  // components: [
97  //   "poster",
98  //   "loadingSpinner",
99  //   "bigPlayButton",
100  //   { name: "controlBar", options: {
101  //     components: [
102  //       "playToggle",
103  //       "fullscreenToggle",
104  //       "currentTimeDisplay",
105  //       "timeDivider",
106  //       "durationDisplay",
107  //       "remainingTimeDisplay",
108  //       { name: "progressControl", options: {
109  //         components: [
110  //           { name: "seekBar", options: {
111  //             components: [
112  //               "loadProgressBar",
113  //               "playProgressBar",
114  //               "seekHandle"
115  //             ]}
116  //           }
117  //         ]}
118  //       },
119  //       { name: "volumeControl", options: {
120  //         components: [
121  //           { name: "volumeBar", options: {
122  //             components: [
123  //               "volumeLevel",
124  //               "volumeHandle"
125  //             ]}
126  //           }
127  //         ]}
128  //       },
129  //       "muteToggle"
130  //     ]
131  //   }},
132  //   "subtitlesDisplay"/*, "replay"*/
133  // ]
134};
135
136// Set CDN Version of swf
137if (CDN_VERSION != "GENERATED_CDN_VSN") {
138  _V_.options.flash.swf = "http://vjs.zencdn.net/"+CDN_VERSION+"/video-js.swf"
139}_V_.merge = function(obj1, obj2, safe){
140  // Make sure second object exists
141  if (!obj2) { obj2 = {}; };
142
143  for (var attrname in obj2){
144    if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; }
145  }
146  return obj1;
147};
148_V_.extend = function(obj){ this.merge(this, obj, true); };
149
150_V_.extend({
151  tech: {}, // Holder for playback technology settings
152  controlSets: {}, // Holder for control set definitions
153
154  // Device Checks
155  isIE: function(){ return !+"\v1"; },
156  isFF: function(){ return !!_V_.ua.match("Firefox") },
157  isIPad: function(){ return navigator.userAgent.match(/iPad/i) !== null; },
158  isIPhone: function(){ return navigator.userAgent.match(/iPhone/i) !== null; },
159  isIOS: function(){ return VideoJS.isIPhone() || VideoJS.isIPad(); },
160  iOSVersion: function() {
161    var match = navigator.userAgent.match(/OS (\d+)_/i);
162    if (match && match[1]) { return match[1]; }
163  },
164  isAndroid: function(){ return navigator.userAgent.match(/Android.*AppleWebKit/i) !== null; },
165  androidVersion: function() {
166    var match = navigator.userAgent.match(/Android (\d+)\./i);
167    if (match && match[1]) { return match[1]; }
168  },
169
170  testVid: document.createElement("video"),
171  ua: navigator.userAgent,
172  support: {},
173
174  each: function(arr, fn){
175    if (!arr || arr.length === 0) { return; }
176    for (var i=0,j=arr.length; i<j; i++) {
177      fn.call(this, arr[i], i);
178    }
179  },
180
181  eachProp: function(obj, fn){
182    if (!obj) { return; }
183    for (var name in obj) {
184      if (obj.hasOwnProperty(name)) {
185        fn.call(this, name, obj[name]);
186      }
187    }
188  },
189
190  el: function(id){ return document.getElementById(id); },
191  createElement: function(tagName, attributes){
192    var el = document.createElement(tagName),
193        attrname;
194    for (attrname in attributes){
195      if (attributes.hasOwnProperty(attrname)) {
196        if (attrname.indexOf("-") !== -1) {
197          el.setAttribute(attrname, attributes[attrname]);
198        } else {
199          el[attrname] = attributes[attrname];
200        }
201      }
202    }
203    return el;
204  },
205
206  insertFirst: function(node, parent){
207    if (parent.firstChild) {
208      parent.insertBefore(node, parent.firstChild);
209    } else {
210      parent.appendChild(node);
211    }
212  },
213
214  addClass: function(element, classToAdd){
215    if ((" "+element.className+" ").indexOf(" "+classToAdd+" ") == -1) {
216      element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd;
217    }
218  },
219
220  removeClass: function(element, classToRemove){
221    if (element.className.indexOf(classToRemove) == -1) { return; }
222    var classNames = element.className.split(" ");
223    classNames.splice(classNames.indexOf(classToRemove),1);
224    element.className = classNames.join(" ");
225  },
226  
227  remove: function(item, array){
228    if (!array) return;
229    var i = array.indexOf(item);
230    if (i != -1) { 
231      return array.splice(i, 1)
232    };
233  },
234
235  // Attempt to block the ability to select text while dragging controls
236  blockTextSelection: function(){
237    document.body.focus();
238    document.onselectstart = function () { return false; };
239  },
240  // Turn off text selection blocking
241  unblockTextSelection: function(){ document.onselectstart = function () { return true; }; },
242
243  // Return seconds as H:MM:SS or M:SS
244  // Supplying a guide (in seconds) will include enough leading zeros to cover the length of the guide
245  formatTime: function(seconds, guide) {
246    guide = guide || seconds; // Default to using seconds as guide
247    var s = Math.floor(seconds % 60),
248        m = Math.floor(seconds / 60 % 60),
249        h = Math.floor(seconds / 3600),
250        gm = Math.floor(guide / 60 % 60),
251        gh = Math.floor(guide / 3600);
252
253    // Check if we need to show hours
254    h = (h > 0 || gh > 0) ? h + ":" : "";
255
256    // If hours are showing, we may need to add a leading zero.
257    // Always show at least one digit of minutes.
258    m = (((h || gm >= 10) && m < 10) ? "0" + m : m) + ":";
259
260    // Check if leading zero is need for seconds
261    s = (s < 10) ? "0" + s : s;
262
263    return h + m + s;
264  },
265
266  uc: function(string){
267    return string.charAt(0).toUpperCase() + string.slice(1);
268  },
269
270  // Return the relative horizonal position of an event as a value from 0-1
271  getRelativePosition: function(x, relativeElement){
272    return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth));
273  },
274  
275  getComputedStyleValue: function(element, style){
276    return window.getComputedStyle(element, null).getPropertyValue(style);
277  },
278
279  trim: function(string){ return string.toString().replace(/^\s+/, "").replace(/\s+$/, ""); },
280  round: function(num, dec) {
281    if (!dec) { dec = 0; }
282    return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
283  },
284
285  isEmpty: function(object) {
286    for (var prop in object) {
287      return false;
288    }
289    return true;
290  },
291
292  // Mimic HTML5 TimeRange Spec.
293  createTimeRange: function(start, end){
294    return {
295      length: 1,
296      start: function() { return start; },
297      end: function() { return end; }
298    };
299  },
300
301  /* Element Data Store. Allows for binding data to an element without putting it directly on the element.
302     Ex. Event listneres are stored here.
303     (also from jsninja.com)
304  ================================================================================ */
305  cache: {}, // Where the data is stored
306  guid: 1, // Unique ID for the element
307  expando: "vdata" + (new Date).getTime(), // Unique attribute to store element's guid in
308
309  // Returns the cache object where data for the element is stored
310  getData: function(elem){
311    var id = elem[_V_.expando];
312    if (!id) {
313      id = elem[_V_.expando] = _V_.guid++;
314      _V_.cache[id] = {};
315    }
316    return _V_.cache[id];
317  },
318  // Delete data for the element from the cache and the guid attr from element
319  removeData: function(elem){
320    var id = elem[_V_.expando];
321    if (!id) { return; }
322    // Remove all stored data
323    delete _V_.cache[id];
324    // Remove the expando property from the DOM node
325    try {
326      delete elem[_V_.expando];
327    } catch(e) {
328      if (elem.removeAttribute) {
329        elem.removeAttribute(_V_.expando);
330      } else {
331        // IE doesn't appear to support removeAttribute on the document element
332        elem[_V_.expando] = null;
333      }
334    }
335  },
336
337  /* Proxy (a.k.a Bind or Context). A simple method for changing the context of a function
338     It also stores a unique id on the function so it can be easily removed from events
339  ================================================================================ */
340  proxy: function(context, fn, uid) {
341    // Make sure the function has a unique ID
342    if (!fn.guid) { fn.guid = _V_.guid++; }
343
344    // Create the new function that changes the context
345    var ret = function() {
346      return fn.apply(context, arguments);
347    }
348
349    // Allow for the ability to individualize this function
350    // Needed in the case where multiple objects might share the same prototype
351    // IF both items add an event listener with the same function, then you try to remove just one
352    // it will remove both because they both have the same guid.
353    // when using this, you need to use the proxy method when you remove the listener as well.
354    ret.guid = (uid) ? uid + "_" + fn.guid : fn.guid;
355
356    return ret;
357  },
358
359  get: function(url, onSuccess, onError){
360    // if (netscape.security.PrivilegeManager.enablePrivilege) {
361    //   netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
362    // }
363
364    var local = (url.indexOf("file:") == 0 || (window.location.href.indexOf("file:") == 0 && url.indexOf("http:") == -1));
365
366    if (typeof XMLHttpRequest == "undefined") {
367      XMLHttpRequest = function () {
368        try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
369        try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {}
370        try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {}
371        throw new Error("This browser does not support XMLHttpRequest.");
372      };
373    }
374
375    var request = new XMLHttpRequest();
376
377    try {
378      request.open("GET", url);
379    } catch(e) {
380      _V_.log("VideoJS XMLHttpRequest (open)", e);
381      // onError(e);
382      return false;
383    }
384
385    request.onreadystatechange = _V_.proxy(this, function() {
386      if (request.readyState == 4) {
387        if (request.status == 200 || local && request.status == 0) {
388          onSuccess(request.responseText);
389        } else {
390          if (onError) {
391            onError();
392          }
393        }
394      }
395    });
396
397    try {
398      request.send();
399    } catch(e) {
400      _V_.log("VideoJS XMLHttpRequest (send)", e);
401      if (onError) {
402        onError(e);
403      }
404    }
405  },
406
407  /* Local Storage
408  ================================================================================ */
409  setLocalStorage: function(key, value){
410    // IE was throwing errors referencing the var anywhere without this
411    var localStorage = window.localStorage || false;
412    if (!localStorage) { return; }
413    try {
414      localStorage[key] = value;
415    } catch(e) {
416      if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
417        _V_.log("LocalStorage Full (VideoJS)", e);
418      } else {
419        _V_.log("LocalStorage Error (VideoJS)", e);
420      }
421    }
422  },
423
424  // Get abosolute version of relative URL. Used to tell flash correct URL.
425  // http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
426  getAbsoluteURL: function(url){
427
428    // Check if absolute URL
429    if (!url.match(/^https?:\/\//)) {
430      // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
431      url = _V_.createElement('div', {
432        innerHTML: '<a href="'+url+'">x</a>'
433      }).firstChild.href;
434    }
435
436    return url;
437  }
438
439});
440
441// usage: log('inside coolFunc', this, arguments);
442// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
443_V_.log = function(){
444  _V_.log.history = _V_.log.history || [];// store logs to an array for reference
445  _V_.log.history.push(arguments);
446  if(window.console) {
447    arguments.callee = arguments.callee.caller;
448    var newarr = [].slice.call(arguments);
449    (typeof console.log === 'object' ? _V_.log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr));
450  }
451};
452
453// make it safe to use console.log always
454(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try
455{console.log();return window.console;}catch(err){return window.console={};}})());
456
457// Offset Left
458// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
459if ("getBoundingClientRect" in document.documentElement) {
460  _V_.findPosX = function(el) {
461    var box;
462
463    try {
464      box = el.getBoundingClientRect();
465    } catch(e) {}
466
467    if (!box) { return 0; }
468
469    var docEl = document.documentElement,
470        body = document.body,
471        clientLeft = docEl.clientLeft || body.clientLeft || 0,
472        scrollLeft = window.pageXOffset || body.scrollLeft,
473        left = box.left + scrollLeft - clientLeft;
474
475    return left;
476  };
477} else {
478  _V_.findPosX = function(el) {
479    var curleft = el.offsetLeft;
480    // _V_.log(obj.className, obj.offsetLeft)
481    while(el = obj.offsetParent) {
482      if (el.className.indexOf("video-js") == -1) {
483        // _V_.log(el.offsetParent, "OFFSETLEFT", el.offsetLeft)
484        // _V_.log("-webkit-full-screen", el.webkitMatchesSelector("-webkit-full-screen"));
485        // _V_.log("-webkit-full-screen", el.querySelectorAll(".video-js:-webkit-full-screen"));
486      } else {
487      }
488      curleft += el.offsetLeft;
489    }
490    return curleft;
491  };
492}// Using John Resig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/
493// (function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; _V_.Class = function(){}; _V_.Class.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function Class() { if ( !initializing && this.init ) this.init.apply(this, arguments); } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class;};})();
494(function(){
495  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
496  _V_.Class = function(){};
497  _V_.Class.extend = function(prop) {
498    var _super = this.prototype;
499    initializing = true;
500    var prototype = new this();
501    initializing = false;
502    for (var name in prop) {
503      prototype[name] = typeof prop[name] == "function" &&
504        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
505        (function(name, fn){
506          return function() {
507            var tmp = this._super;
508            this._super = _super[name];
509            var ret = fn.apply(this, arguments);
510            this._super = tmp;
511            return ret;
512          };
513        })(name, prop[name]) :
514        prop[name];
515    }
516    function Class() {
517      if ( !initializing && this.init ) {
518        return this.init.apply(this, arguments);
519
520      // Attempting to recreate accessing function form of class.
521      } else if (!initializing) {
522        return arguments.callee.prototype.init()
523      }
524    }
525    Class.prototype = prototype;
526    Class.constructor = Class;
527    Class.extend = arguments.callee;
528    return Class;
529  };
530})();
531
532/* Player Component- Base class for all UI objects
533================================================================================ */
534_V_.Component = _V_.Class.extend({
535
536  init: function(player, options){
537    this.player = player;
538
539    // Allow for overridding default component options
540    options = this.options = _V_.merge(this.options || {}, options);
541
542    // Create element if one wasn't provided in options
543    if (options.el) {
544      this.el = options.el;
545    } else {
546      this.el = this.createElement();
547    }
548
549    // Add any components in options
550    this.initComponents();
551  },
552
553  destroy: function(){},
554
555  createElement: function(type, attrs){
556    return _V_.createElement(type || "div", attrs);
557  },
558
559  buildCSSClass: function(){
560    // Child classes can include a function that does:
561    // return "CLASS NAME" + this._super();
562    return "";
563  },
564
565  initComponents: function(){
566    var options = this.options;
567    if (options && options.components) {
568      // Loop through components and add them to the player
569      this.eachProp(options.components, function(name, opts){
570
571        // Allow waiting to add components until a specific event is called
572        var tempAdd = this.proxy(function(){
573          // Set property name on player. Could cause conflicts with other prop n
573ames, but it's worth making refs easy.
574          this[name] = this.addComponent(name, opts);
575        });
576
577        if (opts.loadEvent) {
578          this.one(opts.loadEvent, tempAdd)
579        } else {
580          tempAdd();
581        }
582      });
583    }
584  },
585
586  // Add child components to this component.
587  // Will generate a new child component and then append child component's element to this component's element.
588  // Takes either the name of the UI component class, or an object that contains a name, UI Class, and options.
589  addComponent: function(name, options){
590    var component, componentClass;
591
592    // If string, create new component with options
593    if (typeof name == "string") {
594
595      // Make sure options is at least an empty object to protect against errors
596      options = options || {};
597
598      // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
599      componentClass = options.componentClass || _V_.uc(name);
600
601      // Create a new object & element for this controls set
602      // If there's no .player, this is a player
603      component = new _V_[componentClass](this.player || this, options);
604
605    } else {
606      component = name;
607    }
608
609    // Add the UI object's element to the container div (box)
610    this.el.appendChild(component.el);
611
612    // Return so it can stored on parent object if desired.
613    return component;
614  },
615
616  removeComponent: function(component){
617    this.el.removeChild(component.el);
618  },
619
620  /* Display
621  ================================================================================ */
622  show: function(){
623    this.el.style.display = "block";
624  },
625
626  hide: function(){
627    this.el.style.display = "none";
628  },
629  
630  fadeIn: function(){
631    this.removeClass("vjs-fade-out");
632    this.addClass("vjs-fade-in");
633  },
634
635  fadeOut: function(){
636    this.removeClass("vjs-fade-in");
637    this.addClass("vjs-fade-out");
638  },
639
640  lockShowing: function(){
641    var style = this.el.style;
642    style.display = "block";
643    style.opacity = 1;
644    style.visiblity = "visible";
645  },
646
647  unlockShowing: function(){
648    var style = this.el.style;
649    style.display = "";
650    style.opacity = "";
651    style.visiblity = "";
652  },
653
654  addClass: function(classToAdd){
655    _V_.addClass(this.el, classToAdd);
656  },
657
658  removeClass: function(classToRemove){
659    _V_.removeClass(this.el, classToRemove);
660  },
661
662  /* Events
663  ================================================================================ */
664  addEvent: function(type, fn, uid){
665    return _V_.addEvent(this.el, type, _V_.proxy(this, fn));
666  },
667  removeEvent: function(type, fn){
668    return _V_.removeEvent(this.el, type, fn);
669  },
670  triggerEvent: function(type, e){
671    return _V_.triggerEvent(this.el, type, e);
672  },
673  one: function(type, fn) {
674    _V_.one(this.el, type, _V_.proxy(this, fn));
675  },
676
677  /* Ready - Trigger functions when component is ready
678  ================================================================================ */
679  ready: function(fn){
680    if (!fn) return this;
681
682    if (this.isReady) {
683      fn.call(this);
684    } else {
685      if (this.readyQueue === undefined) {
686        this.readyQueue = [];
687      }
688      this.readyQueue.push(fn);
689    }
690
691    return this;
692  },
693
694  triggerReady: function(){
695    this.isReady = true;
696    if (this.readyQueue && this.readyQueue.length > 0) {
697      // Call all functions in ready queue
698      this.each(this.readyQueue, function(fn){
699        fn.call(this);
700      });
701
702      // Reset Ready Queue
703      this.readyQueue = [];
704
705      // Allow for using event listeners also, in case you want to do something everytime a source is ready.
706      this.triggerEvent("ready");
707    }
708  },
709
710  /* Utility
711  ================================================================================ */
712  each: function(arr, fn){ _V_.each.call(this, arr, fn); },
713
714  eachProp: function(obj, fn){ _V_.eachProp.call(this, obj, fn); },
715
716  extend: function(obj){ _V_.merge(this, obj) },
717
718  // More easily attach 'this' to functions
719  proxy: function(fn, uid){  return _V_.proxy(this, fn, uid); }
720
721});/* Control - Base class for all control elements
722================================================================================ */
723_V_.Control = _V_.Component.extend({
724
725  buildCSSClass: function(){
726    return "vjs-control " + this._super();
727  }
728
729});
730
731/* Control Bar
732================================================================================ */
733_V_.ControlBar = _V_.Component.extend({
734
735  options: {
736    loadEvent: "play",
737    components: {
738      "playToggle": {},
739      "fullscreenToggle": {},
740      "currentTimeDisplay": {},
741      "timeDivider": {},
742      "durationDisplay": {},
743      "remainingTimeDisplay": {},
744      "progressControl": {},
745      "volumeControl": {},
746      "muteToggle": {}
747    }
748  },
749
750  init: function(player, options){
751    this._super(player, options);
752
753    player.addEvent("play", this.proxy(function(){
754      this.fadeIn();
755      this.player.addEvent("mouseover", this.proxy(this.fadeIn));
756      this.player.addEvent("mouseout", this.proxy(this.fadeOut));
757    }));
758
759  },
760
761  createElement: function(){
762    return _V_.createElement("div", {
763      className: "vjs-controls"
764    });
765  },
766
767  fadeIn: function(){
768    this._super();
769    this.player.triggerEvent("controlsvisible");
770  },
771
772  fadeOut: function(){
773    this._super();
774    this.player.triggerEvent("controlshidden");
775  },
776
777  lockShowing: function(){
778    this.el.style.opacity = "1";
779  }
780
781});
782
783/* Button - Base class for all buttons
784================================================================================ */
785_V_.Button = _V_.Control.extend({
786
787  init: function(player, options){
788    this._super(player, options);
789
790    this.addEvent("click", this.onClick);
791    this.addEvent("focus", this.onFocus);
792    this.addEvent("blur", this.onBlur);
793  },
794
795  createElement: function(type, attrs){
796    // Add standard Aria and Tabindex info
797    attrs = _V_.merge({
798      className: this.buildCSSClass(),
799      innerHTML: '<div><span class="vjs-control-text">' + (this.buttonText || "Need Text") + '</span></div>',
800      role: "button",
801      tabIndex: 0
802    }, attrs);
803
804    return this._super(type, attrs);
805  },
806
807  // Click - Override with specific functionality for button
808  onClick: function(){},
809
810  // Focus - Add keyboard functionality to element
811  onFocus: function(){
812    _V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
813  },
814
815  // KeyPress (document level) - Trigger click when keys are pressed
816  onKeyPress: function(event){
817    // Check for space bar (32) or enter (13) keys
818    if (event.which == 32 || event.which == 13) {
819      event.preventDefault();
820      this.onClick();
821    }
822  },
823
824  // Blur - Remove keyboard triggers
825  onBlur: function(){
826    _V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
827  }
828
829});
830
831/* Play Button
832================================================================================ */
833_V_.PlayButton = _V_.Button.extend({
834
835  buttonText: "Play",
836
837  buildCSSClass: function(){
838    return "vjs-play-button " + this._super();
839  },
840
841  onClick: function(){
842    this.player.play();
843  }
844
845});
846
847/* Pause Button
848================================================================================ */
849_V_.PauseButton = _V_.Button.extend({
850
851  buttonText: "Pause",
852
853  buildCSSClass: function(){
854    return "vjs-pause-button " + this._super();
855  },
856
857  onClick: function(){
858    this.player.pause();
859  }
860
861});
862
863/* Play Toggle - Play or Pause Media
864================================================================================ */
865_V_.PlayToggle = _V_.Button.extend({
866
867  buttonText: "Play",
868
869  init: function(player, options){
870    this._super(player, options);
871
872    player.addEvent("play", _V_.proxy(this, this.onPlay));
873    player.addEvent("pause", _V_.proxy(this, this.onPause));
874  },
875
876  buildCSSClass: function(){
877    return "vjs-play-control " + this._super();
878  },
879
880  // OnClick - Toggle between play and pause
881  onClick: function(){
882    if (this.player.paused()) {
883      this.player.play();
884    } else {
885      this.player.pause();
886    }
887  },
888
889  // OnPlay - Add the vjs-playing class to the element so it can change appearance
890  onPlay: function(){
891    _V_.removeClass(this.el, "vjs-paused");
892    _V_.addClass(this.el, "vjs-playing");
893  },
894
895  // OnPause - Add the vjs-paused class to the element so it can change appearance
896  onPause: function(){
897    _V_.removeClass(this.el, "vjs-playing");
898    _V_.addClass(this.el, "vjs-paused");
899  }
900
901});
902
903
904/* Fullscreen Toggle Behaviors
905================================================================================ */
906_V_.FullscreenToggle = _V_.Button.extend({
907
908  buttonText: "Fullscreen",
909
910  buildCSSClass: function(){
911    return "vjs-fullscreen-control " + this._super();
912  },
913
914  onClick: function(){
915    if (!this.player.isFullScreen) {
916      this.player.requestFullScreen();
917    } else {
918      this.player.cancelFullScreen();
919    }
920  }
921
922});
923
924/* Big Play Button
925================================================================================ */
926_V_.BigPlayButton = _V_.Button.extend({
927  init: function(player, options){
928    this._super(player, options);
929
930    player.addEvent("play", _V_.proxy(this, this.hide));
931    player.addEvent("ended", _V_.proxy(this, this.show));
932  },
933
934  createElement: function(){
935    return this._super("div", {
936      className: "vjs-big-play-button",
937      innerHTML: "<span></span>"
938    });
939  },
940
941  onClick: function(){
942    // Go back to the beginning if big play button is showing at the end.
943    // Have to check for current time otherwise it might throw a 'not ready' error.
944    if(this.player.currentTime()) {
945      this.player.currentTime(0);
946    }
947    this.player.play();
948  }
949});
950
951/* Loading Spinner
952================================================================================ */
953_V_.LoadingSpinner = _V_.Component.extend({
954  init: function(player, options){
955    this._super(player, options);
956
957    player.addEvent("canplay", _V_.proxy(this, this.hide));
958    player.addEvent("canplaythrough", _V_.proxy(this, this.hide));
959    player.addEvent("playing", _V_.proxy(this, this.hide));
960
961    player.addEvent("seeking", _V_.proxy(this, this.show));
962    player.addEvent("error", _V_.proxy(this, this.show));
963
964    // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
965    // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-s
965howing
966    // player.addEvent("stalled", _V_.proxy(this, this.show));
967
968    player.addEvent("waiting", _V_.proxy(this, this.show));
969  },
970
971  createElement: function(){
972
973    var classNameSpinner, innerHtmlSpinner;
974
975    if ( typeof this.player.el.style.WebkitBorderRadius == "string"
976         || typeof this.player.el.style.MozBorderRadius == "string"
977         || typeof this.player.el.style.KhtmlBorderRadius == "string"
978         || typeof this.player.el.style.borderRadius == "string")
979      {
980        classNameSpinner = "vjs-loading-spinner";
981        innerHtmlSpinner = "<div class='ball1'></div><div class='ball2'></div><div class='ball3'></div><div class='ball4'></div><div class='ball5'></div><div class='ball6'></div><div class='ball7'></div><div class='ball8'></div>";
982      } else {
983        classNameSpinner = "vjs-loading-spinner-fallback";
984        innerHtmlSpinner = "";
985      }
986
987    return this._super("div", {
988      className: classNameSpinner,
989      innerHTML: innerHtmlSpinner
990    });
991  }
992});
993
994/* Time
995================================================================================ */
996_V_.CurrentTimeDisplay = _V_.Component.extend({
997
998  init: function(player, options){
999    this._super(player, options);
1000
1001    player.addEvent("timeupdate", _V_.proxy(this, this.updateContent));
1002  },
1003
1004  createElement: function(){
1005    var el = this._super("div", {
1006      className: "vjs-current-time vjs-time-controls vjs-control"
1007    });
1008
1009    this.content = _V_.createElement("div", {
1010      className: "vjs-current-time-display",
1011      innerHTML: '0:00'
1012    });
1013
1014    el.appendChild(_V_.createElement("div").appendChild(this.content));
1015    return el;
1016  },
1017
1018  updateContent: function(){
1019    // Allows for smooth scrubbing, when player can't keep up.
1020    var time = (this.player.scrubbing) ? this.player.values.currentTime : this.player.currentTime();
1021    this.content.innerHTML = _V_.formatTime(time, this.player.duration());
1022  }
1023
1024});
1025
1026_V_.DurationDisplay = _V_.Component.extend({
1027
1028  init: function(player, options){
1029    this._super(player, options);
1030
1031    player.addEvent("timeupdate", _V_.proxy(this, this.updateContent));
1032  },
1033
1034  createElement: function(){
1035    var el = this._super("div", {
1036      className: "vjs-duration vjs-time-controls vjs-control"
1037    });
1038
1039    this.content = _V_.createElement("div", {
1040      className: "vjs-duration-display",
1041      innerHTML: '0:00'
1042    });
1043
1044    el.appendChild(_V_.createElement("div").appendChild(this.content));
1045    return el;
1046  },
1047
1048  updateContent: function(){
1049    if (this.player.duration()) { this.content.innerHTML = _V_.formatTime(this.player.duration()); }
1050  }
1051
1052});
1053
1054// Time Separator (Not used in main skin, but still available, and could be used as a 'spare element')
1055_V_.TimeDivider = _V_.Component.extend({
1056
1057  createElement: function(){
1058    return this._super("div", {
1059      className: "vjs-time-divider",
1060      innerHTML: '<div><span>/</span></div>'
1061    });
1062  }
1063
1064});
1065
1066_V_.RemainingTimeDisplay = _V_.Component.extend({
1067
1068  init: function(player, options){
1069    this._super(player, options);
1070
1071    player.addEvent("timeupdate", _V_.proxy(this, this.updateContent));
1072  },
1073
1074  createElement: function(){
1075    var el = this._super("div", {
1076      className: "vjs-remaining-time vjs-time-controls vjs-control"
1077    });
1078
1079    this.content = _V_.createElement("div", {
1080      className: "vjs-remaining-time-display",
1081      innerHTML: '-0:00'
1082    });
1083
1084    el.appendChild(_V_.createElement("div").appendChild(this.content));
1085    return el;
1086  },
1087
1088  updateContent: function(){
1089    if (this.player.duration()) { this.content.innerHTML = "-"+_V_.formatTime(this.player.remainingTime()); }
1090
1091    // Allows for smooth scrubbing, when player can't keep up.
1092    // var time = (this.player.scrubbing) ? this.player.values.currentTime : this.player.currentTime();
1093    // this.content.innerHTML = _V_.formatTime(time, this.player.duration());
1094  }
1095
1096});
1097
1098/* Slider - Parent for seek bar and volume slider
1099================================================================================ */
1100_V_.Slider = _V_.Component.extend({
1101
1102  init: function(player, options){
1103    this._super(player, options);
1104
1105    player.addEvent(this.playerEvent, _V_.proxy(this, this.update));
1106
1107    this.addEvent("mousedown", this.onMouseDown);
1108    this.addEvent("focus", this.onFocus);
1109    this.addEvent("blur", this.onBlur);
1110
1111    this.player.addEvent("controlsvisible", this.proxy(this.update));
1112
1113    // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
1114    // this.player.one("timeupdate", this.proxy(this.update));
1115
1116    this.update();
1117  },
1118
1119  createElement: function(type, attrs) {
1120    attrs = _V_.merge({
1121      role: "slider",
1122      "aria-valuenow": 0,
1123      "aria-valuemin": 0,
1124      "aria-valuemax": 100,
1125      tabIndex: 0
1126    }, attrs);
1127
1128    return this._super(type, attrs);
1129  },
1130
1131  onMouseDown: function(event){
1132    event.preventDefault();
1133    _V_.blockTextSelection();
1134
1135    _V_.addEvent(document, "mousemove", _V_.proxy(this, this.onMouseMove));
1136    _V_.addEvent(document, "mouseup", _V_.proxy(this, this.onMouseUp));
1137
1138    this.onMouseMove(event);
1139  },
1140
1141  onMouseUp: function(event) {
1142    _V_.unblockTextSelection();
1143    _V_.removeEvent(document, "mousemove", this.onMouseMove, false);
1144    _V_.removeEvent(document, "mouseup", this.onMouseUp, false);
1145
1146    this.update();
1147  },
1148
1149  update: function(){
1150    // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
1151    // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
1152    // var progress =  (this.player.scrubbing) ? this.player.values.currentTime / this.player.duration() : this.player.currentTime() / this.player.duration();
1153
1154    var barProgress,
1155        progress = this.getPercent();
1156        handle = this.handle,
1157        bar = this.bar;
1158
1159    // Protect against no duration and other division issues
1160    if (isNaN(progress)) { progress = 0; }
1161
1162    barProgress = progress;
1163
1164    // If there is a handle, we need to account for the handle in our 
1164calculation for progress bar
1165    // so that it doesn't fall short of or extend past the handle.
1166    if (handle) {
1167
1168      var box = this.el,
1169          boxWidth = box.offsetWidth,
1170
1171          handleWidth = handle.el.offsetWidth,
1172
1173          // The width of the handle in percent of the containing box
1174          // In IE, widths may not be ready yet causing NaN
1175          handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
1176
1177          // Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
1178          // There is a margin of half the handle's width on both sides.
1179          boxAdjustedPercent = 1 - handlePercent;
1180
1181          // Adjust the progress that we'll use to set widths to the new adjusted box width
1182          adjustedProgress = progress * boxAdjustedPercent,
1183
1184          // The bar does reach the left side, so we need to account for this in the bar's width
1185          barProgress = adjustedProgress + (handlePercent / 2);
1186
1187      // Move the handle from the left based on the adjected progress
1188      handle.el.style.left = _V_.round(adjustedProgress * 100, 2) + "%";
1189    }
1190
1191    // Set the new bar width
1192    bar.el.style.width = _V_.round(barProgress * 100, 2) + "%";
1193  },
1194
1195  calculateDistance: function(event){
1196    var box = this.el,
1197        boxX = _V_.findPosX(box),
1198        boxW = box.offsetWidth,
1199        handle = this.handle;
1200
1201    if (handle) {
1202      var handleW = handle.el.offsetWidth;
1203
1204      // Adjusted X and Width, so handle doesn't go outside the bar
1205      boxX = boxX + (handleW / 2);
1206      boxW = boxW - handleW;
1207    }
1208
1209    // Percent that the click is through the adjusted area
1210    return Math.max(0, Math.min(1, (event.pageX - boxX) / boxW));
1211  },
1212
1213  onFocus: function(event){
1214    _V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
1215  },
1216
1217  onKeyPress: function(event){
1218    if (event.which == 37) { // Left Arrow
1219      event.preventDefault();
1220      this.stepBack();
1221    } else if (event.which == 39) { // Right Arrow
1222      event.preventDefault();
1223      this.stepForward();
1224    }
1225  },
1226
1227  onBlur: function(event){
1228    _V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
1229  }
1230});
1231
1232
1233/* Progress
1234================================================================================ */
1235
1236// Progress Control: Seek, Load Progress, and Play Progress
1237_V_.ProgressControl = _V_.Component.extend({
1238
1239  options: {
1240    components: {
1241      "seekBar": {}
1242    }
1243  },
1244
1245  createElement: function(){
1246    return this._super("div", {
1247      className: "vjs-progress-control vjs-control"
1248    });
1249  }
1250
1251});
1252
1253// Seek Bar and holder for the progress bars
1254_V_.SeekBar = _V_.Slider.extend({
1255
1256  options: {
1257    components: {
1258      "loadProgressBar": {},
1259
1260      // Set property names to bar and handle to match with the parent Slider class is looking for
1261      "bar": { componentClass: "PlayProgressBar" },
1262      "handle": { componentClass: "SeekHandle" }
1263    }
1264  },
1265
1266  playerEvent: "timeupdate",
1267
1268  init: function(player, options){
1269    this._super(player, options);
1270  },
1271
1272  createElement: function(){
1273    return this._super("div", {
1274      className: "vjs-progress-holder"
1275    });
1276  },
1277
1278  getPercent: function(){
1279    return this.player.currentTime() / this.player.duration();
1280  },
1281
1282  onMouseDown: function(event){
1283    this._super(event);
1284
1285    this.player.scrubbing = true;
1286
1287    this.videoWasPlaying = !this.player.paused();
1288    this.player.pause();
1289  },
1290
1291  onMouseMove: function(event){
1292    var newTime = this.calculateDistance(event) * this.player.duration();
1293
1294    // Don't let video end while scrubbing.
1295    if (newTime == this.player.duration()) { newTime = newTime - 0.1; }
1296
1297    // Set new time (tell player to seek to new time)
1298    this.player.currentTime(newTime);
1299  },
1300
1301  onMouseUp: function(event){
1302    this._super(event);
1303
1304    this.player.scrubbing = false;
1305    if (this.videoWasPlaying) {
1306      this.player.play();
1307    }
1308  },
1309
1310  stepForward: function(){
1311    this.player.currentTime(this.player.currentTime() + 1);
1312  },
1313
1314  stepBack: function(){
1315    this.player.currentTime(this.player.currentTime() - 1);
1316  }
1317
1318});
1319
1320// Load Progress Bar
1321_V_.LoadProgressBar = _V_.Component.extend({
1322
1323  init: function(player, options){
1324    this._super(player, options);
1325    player.addEvent("progress", _V_.proxy(this, this.update));
1326  },
1327
1328  createElement: function(){
1329    return this._super("div", {
1330      className: "vjs-load-progress",
1331      innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>'
1332    });
1333  },
1334
1335  update: function(){
1336    if (this.el.style) { this.el.style.width = _V_.round(this.player.bufferedPercent() * 100, 2) + "%"; }
1337  }
1338
1339});
1340
1341// Play Progress Bar
1342_V_.PlayProgressBar = _V_.Component.extend({
1343
1344  createElement: function(){
1345    return this._super("div", {
1346      className: "vjs-play-progress",
1347      innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
1348    });
1349  }
1350
1351});
1352
1353// Seek Handle
1354// SeekBar Behavior includes play progress bar, and seek handle
1355// Needed so it can determine seek position based on handle position/size
1356_V_.SeekHandle = _V_.Component.extend({
1357
1358  createElement: function(){
1359    return this._super("div", {
1360      className: "vjs-seek-handle",
1361      innerHTML: '<span class="vjs-control-text">00:00</span>'
1362    });
1363  }
1364
1365});
1366
1367/* Volume Scrubber
1368================================================================================ */
1369// Progress Control: Seek, Load Progress, and Play Progress
1370_V_.VolumeControl = _V_.Component.extend({
1371
1372  options: {
1373    components: {
1374      "volumeBar": {}
1375    }
1376  },
1377
1378  createElement: function(){
1379    return this._super("div", {
1380      className: "vjs-volume-control vjs-control"
1381    });
1382  }
1383
1384});
1385
1386_V_.VolumeBar = _V_.Slider.extend({
1387
1388  options: {
1389    components: {
1390      "bar": { componentClass: "VolumeLevel" },
1391      "handle": { componentClass: "VolumeHandle" }
1392    }
1393  },
1394
1395  playerEvent: "volumechange",
1396
1397  createElement: function(){
1398    return this._super("div", {
1399      className: "vjs-volume-bar"
1400    });
1401  },
1402
1403  onMouseMove: function(event) {
1404    this.player.volume(this.calculateDistance(event));
1405  },
1406
1407  getPercent: function(){
1408   return this.player.volume();
1409  },
1410
1411  stepForward: function(){
1412    this.player.volume(this.player.volume() + 0.1);
1413  },
1414
1415  stepBack: function(){
1416    this.player.volume(this.player.volume() - 0.1);
1417  }
1418});
1419
1420_V_.VolumeLevel = _V_.Component.extend({
1421
1422  createElement: function(){
1423    return this._super("div", {
1424      className: "vjs-volume-level",
1425      innerHTML: '<span class="vjs-control-text"></span>'
1426    });
1427  }
1428
1429});
1430
1431_V_.VolumeHandle = _V_.Component.extend({
1432
1433  createElement: function(){
1434    return this._super("div", {
1435      className: "vjs-volume-handle",
1436      innerHTML: '<span class="vjs-control-text"></span>'
1437      // tabindex: 0,
1438      // role: "slider", "aria-valuenow": 0, "aria-valuemin": 0, "aria-valuemax": 100
1439    });
1440  }
1441
1442});
1443
1444_V_.MuteToggle = _V_.Button.extend({
1445
1446  init: function(player, options){
1447    this._super(player, options);
1448
1449    player.addEvent("volumechange", _V_.proxy(this, this.update));
1450  },
1451
1452  createElement: function(){
1453    return this._super("div", {
1454      className: "vjs-mute-control vjs-control",
1455      innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
1456    });
1457  },
1458
1459  onClick: function(event){
1460    this.player.muted( this.player.muted() ? false : true );
1461  },
1462
1463  update: function(event){
1464    var vol = this.player.volume(),
1465        level = 3;
1466
1467    if (vol == 0 || this.player.muted()) {
1468      level = 0;
1469    } else if (vol < 0.33) {
1470      level = 1;
1471    } else if (vol < 0.67) {
1472      level = 2;
1473    }
1474
1475    /* TODO improve muted icon classes */
1476    _V_.each.call(this, [0,1,2,3], function(i){
1477      _V_.removeClass(this.el, "vjs-vol-"+i);
1478    });
1479    _V_.addClass(this.el, "vjs-vol-"+level);
1480  }
1481
1482});
1483
1484
1485/* Poster Image
1486================================================================================ */
1487_V_.PosterImage = _V_.Button.extend({
1488  init: function(player, options){
1489    this._super(player, options);
1490
1491    if (!this.player.options.poster) {
1492      this.hide();
1493    }
1494
1495    player.addEvent("play", _V_.proxy(this, this.hide));
1496  },
1497
1498  createElement: function(){
1499    return _V_.createElement("img", {
1500      className: "vjs-poster",
1501      src: this.player.options.poster,
1502
1503      // Don't want poster to be tabbable.
1504      tabIndex: -1
1505    });
1506  },
1507
1508  onClick: function(){
1509    this.player.play();
1510  }
1511});
1512
1513/* Menu
1514================================================================================ */
1515// The base for text track and settings menu buttons.
1516_V_.Menu = _V_.Component.extend({
1517
1518  init: function(player, options){
1519    this._super(player, options);
1520  },
1521
1522  addItem: function(component){
1523    this.addComponent(component);
1524    component.addEvent("click", this.proxy(function(){
1525      this.unlockShowing();
1526    }));
1527  },
1528
1529  createElement: function(){
1530    return this._super("ul", {
1531      className: "vjs-menu"
1532    });
1533  }
1534
1535});
1536
1537_V_.MenuItem = _V_.Button.extend({
1538
1539  init: function(player, options){
1540    this._super(player, options);
1541
1542    if (options.selected) {
1543      this.addClass("vjs-selected");
1544    }
1545  },
1546
1547  createElement: function(type, attrs){
1548    return this._super("li", _V_.merge({
1549      className: "vjs-menu-item",
1550      innerHTML: this.options.label
1551    }, attrs));
1552  },
1553
1554  onClick: function(){
1555    this.selected(true);
1556  },
1557
1558  selected: function(selected){
1559    if (selected) {
1560      this.addClass("vjs-selected");
1561    } else {
1562      this.removeClass("vjs-selected")
1563    }
1564  }
1565
1566});// ECMA-262 is the standard for javascript.
1567// The following methods are impelemented EXACTLY as described in the standard (according to Mozilla Docs), and do not override the default method if one exists.
1568// This may conflict with other libraries that modify the array prototype, but those libs should update to use the standard.
1569
1570// [].indexOf
1571// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
1572if (!Array.prototype.indexOf) {
1573    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
1574        "use strict";
1575        if (this === void 0 || this === null) {
1576            throw new TypeError();
1577        }
1578        var t = Object(this);
1579        var len = t.length >>> 0;
1580        if (len === 0) {
1581            return -1;
1582        }
1583        var n = 0;
1584        if (arguments.length > 0) {
1585            n = Number(arguments[1]);
1586            if (n !== n) { // shortcut for verifying if it's NaN
1587                n = 0;
1588            } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
1589                n = (n > 0 || -1) * Math.floor(Math.abs(n));
1590            }
1591        }
1592        if (n >= len) {
1593            return -1;
1594        }
1595        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
1596        for (; k < len; k++) {
1597            if (k in t && t[k] === searchElement) {
1598                return k;
1599            }
1600        }
1601        return -1;
1602    }
1603}
1604
1605// NOT NEEDED YET
1606// [].lastIndexOf
1607// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
1608// if (!Array.prototype.lastIndexOf)
1609// {
1610//   Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/)
1611//   {
1612//     "use strict";
1613// 
1614//     if (this === void 0 || this === null)
1615//       throw new TypeError();
1616// 
1617//     var t = Object(this);
1618//     var len = t.length >>> 0;
1619//     if (len === 0)
1620//       return -1;
1621// 
1622//     var n = len;
1623//     if (arguments.length > 1)
1624//     {
1625//       n = Number(arguments[1]);
1626//       if (n !== n)
1627//         n = 0;
1628//       else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0))
1629//         n = (n > 0 || -1) * Math.floor(Math.abs(n));
1630//     }
1631// 
1632//     var k = n >= 0
1633//           ? Math.min(n, len - 1)
1634//           : len - Math.abs(n);
1635// 
1636//     for (; k >= 0; k--)
1637//     {
1638//       if (k in t && t[k] === searchElement)
1639//         return k;
1640//     }
1641//     return -1;
1642//   };
1643// }
1644
1645
1646// NOT NEEDED YET
1647// Array forEach per ECMA standard https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
1648// Production steps of ECMA-262, Edition 5, 15.4.4.18
1649// Reference: http://es5.github.com/#x15.4.4.18
1650// if ( !Array.prototype.forEach ) {
1651// 
1652//   Array.prototype.forEach = function( callback, thisArg ) {
1653// 
1654//     var T, k;
1655// 
1656//     if ( this == null ) {
1657//       throw new TypeError( " this is null or not defined" );
1658//     }
1659// 
1660//     // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
1661//     var O = Object(this);
1662// 
1663//     // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
1664//     // 3. Let len be ToUint32(lenValue).
1665//     var len = O.length >>> 0;
1666// 
1667//     // 4. If IsCallable(callback) is false, throw a TypeError exception.
1668//     // See: http://es5.github.com/#x9.11
1669//     if ( {}.toString.call(callback) != "[object Function]" ) {
1670//       throw new TypeError( callback + " is not a function" );
1671//     }
1672// 
1673//     // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
1674//     if ( thisArg ) {
1675//       T = thisArg;
1676//     }
1677// 
1678//     // 6. Let k be 0
1679//     k = 0;
1680// 
1681//     // 7. Repeat, while k < len
1682//     while( k < len ) {
1683// 
1684//       var kValue;
1685// 
1686//       // a. Let Pk be ToString(k).
1687//       //   This is implicit for LHS operands of the in operator
1688//       // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
1689//       //   This step can be combined with c
1690//       // c. If kPresent is true, then
1691//       if ( k in O ) {
1692// 
1693//         // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
1694//         kValue = O[ Pk ];
1695// 
1696//         // ii. Call the Call internal method of callback with T as the this value and
1697//         // argument list containing kValue, k, and O.
1698//         callback.call( T, kValue, k, O );
1699//       }
1700//       // d. Increase k by 1.
1701//       k++;
1702//     }
1703//     // 8. return undefined
1704//   };
1705// }
1706
1707
1708// NOT NEEDED YET
1709// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
1710// Production steps of ECMA-262, Edition 5, 15.4.4.19
1711// Reference: http://es5.github.com/#x15.4.4.19
1712// if (!Array.prototype.map) {
1713//   Array.prototype.map = function(callback, thisArg) {
1714// 
1715//     var T, A, k;
1716// 
1717//     if (this == null) {
1718//       throw new TypeError(" this is null or not defined");
1719//     }
1720// 
1721//     // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
1722//     var O = Object(this);
1723// 
1724//     // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
1725//     // 3. Let len be ToUint32(lenValue).
1726//     var len = O.length >>> 0;
1727// 
1728//     // 4. If IsCallable(callback) is false, throw a TypeError exception.
1729//     // See: http://es5.github.com/#x9.11
1730//     if ({}.toString.call(callback) != "[object Function]") {
1731//       throw new TypeError(callback + " is not a function");
1732//     }
1733// 
1734//     // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
1735//     if (thisArg) {
1736//       T = thisArg;
1737//     }
1738// 
1739//     // 6. Let A be a new array created as if by the expression new Array(len) where Array is
1740//     // the standard built-in constructor with that name and len is the value of len.
1741//     A = new Array(len);
1742// 
1743//     // 7. Let k be 0
1744//     k = 0;
1745// 
1746//     // 8. Repeat, while k < len
1747//     while(k < len) {
1748// 
1749//       var kValue, mappedValue;
1750// 
1751//       // a. Let Pk be ToString(k).
1752//       //   This is implicit for LHS operands of the in operator
1753//       // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
1754//       //   This step can be combined with c
1755//       // c. If kPresent is true, then
1756//       if (k in O) {
1757// 
1758//         // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
1759//         kValue = O[ k ];
1760// 
1761//         // ii. Let mappedValue be the result of calling the Call internal method of callback
1762//         // with T as the this value and argument list containing kValue, k, and O.
1763//         mappedValue = callback.call(T, kValue, k, O);
1764// 
1765//         // iii. Call the DefineOwnProperty internal method of A with arguments
1766//         // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true},
1767//         // and false.
1768// 
1769//         // In browsers that support Object.defineProperty, use the following:
1770//         // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
1771// 
1772//         // For best browser support, use the following:
1773//         A[ k ] = mappedValue;
1774//       }
1775//       // d. Increase k by 1.
1776//       k++;
1777//     }
1778// 
1779//     // 9. return A
1780//     return A;
1781//   };      
1782// }
1783// Event System (J.Resig - Secrets of a JS Ninja http://jsninja.com/ [Go read it, really])
1784// (Book version isn't completely usable, so fixed some things and borrowed from jQuery where it's working)
1785// 
1786// This should work very similarly to jQuery's events, however it's based off the book version which isn't as
1787// robust as jquery's, so there's probably some differences.
1788// 
1789// When you add an event listener using _V_.addEvent, 
1790//   it stores the handler function in seperate cache object, 
1791//   and adds a generic handler to the element's event,
1792//   along with a unique id (guid) to the element.
1793
1794_V_.extend({
1795
1796  // Add an event listener to element
1797  // It stores the handler function in a separate cache object
1798  // and adds a generic handler to the element's event,
1799  // along with a unique id (guid) to the element.
1800  addEvent: function(elem, type, fn){
1801    var data = _V_.getData(elem), handlers;
1802
1803    // We only need to generate one handler per element
1804    if (data && !data.handler) {
1805      // Our new meta-handler that fixes the event object and the context
1806      data.handler = function(event){
1807        event = _V_.fixEvent(event);
1808        var handlers = _V_.getData(elem).events[event.type];
1809        // Go through and call all the real bound handlers
1810        if (handlers) {
1811          
1812          // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
1813          var handlersCopy = [];
1814          _V_.each(handlers, function(handler, i){
1815            handlersCopy[i] = handler;
1816          })
1817          
1818          for (var i = 0, l = handlersCopy.length; i < l; i++) {
1819            handlersCopy[i].call(elem, event);
1820          }
1821        }
1822      };
1823    }
1824
1825    // We need a place to store all our event data
1826    if (!data.events) { data.events = {}; }
1827
1828    // And a place to store the handlers for this event type
1829    handlers = data.events[type];
1830
1831    if (!handlers) {
1832      handlers = data.events[ type ] = [];
1833
1834      // Attach our meta-handler to the element, since one doesn't exist
1835      if (document.addEventListener) {
1836        elem.addEventListener(type, data.handler, false);
1837      } else if (document.attachEvent) {
1838        elem.attachEvent("on" + type, data.handler);
1839      }
1840    }
1841
1842    if (!fn.guid) { fn.guid = _V_.guid++; }
1843
1844    handlers.push(fn);
1845  },
1846
1847  removeEvent: function(elem, type, fn) {
1848    var data = _V_.getData(elem), handlers;
1849    // If no events exist, nothing to unbind
1850    if (!data.events) { return; }
1851
1852    // Are we removing all bound events?
1853    if (!type) {
1854      for (type in data.events) {
1855        _V_.cleanUpEvents(elem, type);
1856      }
1857      return;
1858    }
1859
1860    // And a place to store the handlers for this event type
1861    handlers = data.events[type];
1862
1863    // If no handlers exist, nothing to unbind
1864    if (!handlers) { return; }
1865
1866    // See if we're only removing a single handler
1867    if (fn && fn.guid) {
1868      for (var i = 0; i < handlers.length; i++) {
1869        // We found a match (don't stop here, there could be a couple bound)
1870        if (handlers[i].guid === fn.guid) {
1871          // Remove the handler from the array of handlers
1872          handlers.splice(i--, 1);
1873        }
1874      }
1875    }
1876
1877    _V_.cleanUpEvents(elem, type);
1878  },
1879
1880  cleanUpEvents: function(elem, type) {
1881    var data = _V_.getData(elem);
1882    // Remove the events of a particular type if there are none left
1883
1884    if (data.events[type].length === 0) {
1885      delete data.events[type];
1886
1887      // Remove the meta-handler from the element
1888      if (document.removeEventListener) {
1889        elem.removeEventListener(type, data.handler, false);
1890      } else if (document.detachEvent) {
1891        elem.detachEvent("on" + type, data.handler);
1892      }
1893    }
1894
1895    // Remove the events object if there are no types left
1896    if (_V_.isEmpty(data.events)) {
1897      delete data.events;
1898      delete data.handler;
1899    }
1900
1901    // Finally remove the expando if there is no data left
1902    if (_V_.isEmpty(data)) {
1903      _V_.removeData(elem);
1904    }
1905  },
1906
1907  fixEvent: function(event) {
1908    if (event[_V_.expando]) { return event; }
1909    // store a copy of the original event object
1910    // and "clone" to set read-only properties
1911    var originalEvent = event;
1912    event = new _V_.Event(originalEvent);
1913
1914    for ( var i = _V_.Event.props.length, prop; i; ) {
1915      prop = _V_.Event.props[ --i ];
1916      event[prop] = originalEvent[prop];
1917    }
1918
1919    // Fix target property, if necessary
1920    if (!event.target) { event.target = event.srcElement || document; }
1921
1922    // check if target is a textnode (safari)
1923    if (event.target.nodeType === 3) { event.target = event.target.parentNode; }
1924
1925    // Add relatedTarget, if necessary
1926    if (!event.relatedTarget && event.fromElement) {
1927      event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
1928    }
1929
1930    // Calculate pageX/Y if missing and clientX/Y available
1931    if ( event.pageX == null && event.clientX != null ) {
1932      var eventDocument = event.target.ownerDocument || document,
1933        doc = eventDocument.documentElement,
1934        body = eventDocument.body;
1935
1936      event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
1937      event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
1938    }
1939
1940    // Add which for key events
1941    if (event.which == null && (event.charCode != null || event.keyCode != null)) {
1942      event.which = event.charCode != null ? event.charCode : event.keyCode;
1943    }
1944
1945    // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
1946    if ( !event.metaKey && event.ctrlKey ) {
1947      event.metaKey = event.ctrlKey;
1948    }
1949
1950    // Add which for click: 1 === left; 2 === middle; 3 === right
1951    // Note: button is not normalized, so don't use it
1952    if ( !event.which && event.button !== undefined ) {
1953      event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
1954    }
1955
1956    return event;
1957  },
1958
1959  triggerEvent: function(elem, event) {
1960    var data = _V_.getData(elem),
1961        parent = elem.parentNode || elem.ownerDocument,
1962        type = event.type || event,
1963        handler;
1964
1965    if (data) { handler = data.handler }
1966
1967    // Added in attion to book. Book code was broke.
1968    event = typeof event === "object" ?
1969      event[_V_.expando] ? 
1970        event :
1971        new _V_.Event(type, event) :
1972      new _V_.Event(type);
1973
1974    event.type = type;
1975    if (handler) {
1976      handler.call(elem, event);
1977    }
1978
1979    // Clean up the event in case it is being reused
1980    event.result = undefined;
1981    event.target = elem;
1982
1983    // Bubble the event up the tree to the document,
1984    // Unless it's been explicitly stopped
1985    // if (parent && !event.isPropagationStopped()) {
1986    //   _V_.triggerEvent(parent, event);
1987    // 
1988    // // We're at the top document so trigger the default action
1989    // } else if (!parent && !event.isDefaultPrevented()) {
1990    //   // log(type);
1991    //   var targetData = _V_.getData(event.target);
1992    //   // log(targetData);
1993    //   var targetHandler = targetData.handler;
1994    //   // log("2");
1995    //   if (event.target[event.type]) {
1996    //     // Temporarily disable the bound handler,
1997    //     // don't want to execute it twice
1998    //     if (targetHandler) {
1999    //       targetData.handler = function(){};
2000    //     }
2001    // 
2002    //     // Trigger the native event (click, focus, blur)
2003    //     event.target[event.type]();
2004    // 
2005    //     // Restore the handler
2006    //     if (targetHandler) {
2007    //       targetData.handler = targetHandler;
2008    //     }
2009    //   }
2010    // }
2011  },
2012  
2013  one: function(elem, type, fn) {
2014    _V_.addEvent(elem, type, function(){
2015      _V_.removeEvent(elem, type, arguments.callee)
2016      fn.apply(this, arguments);
2017    });
2018  }
2019});
2020
2021// Custom Event object for standardizing event objects between browsers.
2022_V_.Event = function(src, props){
2023  // Event object
2024  if (src && src.type) {
2025    this.originalEvent = src;
2026    this.type = src.type;
2027
2028    // Events bubbling up the document may have been marked as prevented
2029    // by a handler lower down the tree; reflect the correct value.
2030    this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
2031      src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
2032
2033  // Event type
2034  } else {
2035    this.type = src;
2036  }
2037
2038  // Put explicitly provided properties onto the event object
2039  if (props) { _V_.merge(this, props); }
2040
2041  this.timeStamp = (new Date).getTime();
2042
2043  // Mark it as fixed
2044  this[_V_.expando] = true;
2045};
2046
2047_V_.Event.prototype = {
2048  preventDefault: function() {
2049    this.isDefaultPrevented = returnTrue;
2050
2051    var e = this.originalEvent;
2052    if (!e) { return; }
2053
2054    // if preventDefault exists run it on the original event
2055    if (e.preventDefault) { 
2056      e.preventDefault();
2057    // otherwise set the returnValue property of the original event to false (IE)
2058    } else {
2059      e.returnValue = false;
2060    }
2061  },
2062  stopPropagation: function() {
2063    this.isPropagationStopped = returnTrue;
2064
2065    var e = this.originalEvent;
2066    if (!e) { return; }
2067    // if stopPropagation exists run it on the original event
2068    if (e.stopPropagation) { e.stopPropagation(); }
2069    // otherwise set the cancelBubble property of the original event to true (IE)
2070    e.cancelBubble = true;
2071  },
2072  stopImmediatePropagation: function() {
2073    this.isImmediatePropagationStopped = returnTrue;
2074    this.stopPropagation();
2075  },
2076  isDefaultPrevented: returnFalse,
2077  isPropagationStopped: returnFalse,
2078  isImmediatePropagationStopped: returnFalse
2079};
2080_V_.Event.props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" ");
2081
2082function returnTrue(){ return true; }
2083function returnFalse(){ return false; }
2084
2085// Javascript JSON implementation
2086// (Parse Method Only)
2087// https://github.com/douglascrockford/JSON-js/blob/master/json2.js
2088
2089var JSON;
2090if (!JSON) { JSON = {}; }
2091
2092(function(){
2093  var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
2094
2095  if (typeof JSON.parse !== 'function') {
2096      JSON.parse = function (text, reviver) {
2097          var j;
2098
2099          function walk(holder, key) {
2100              var k, v, value = holder[key];
2101              if (value && typeof value === 'object') {
2102                  for (k in value) {
2103                      if (Object.prototype.hasOwnProperty.call(value, k)) {
2104                          v = walk(value, k);
2105                          if (v !== undefined) {
2106                              value[k] = v;
2107                          } else {
2108                              delete value[k];
2109                          }
2110                      }
2111                  }
2112              }
2113              return reviver.call(holder, key, value);
2114          }
2115          text = String(text);
2116          cx.lastIndex = 0;
2117          if (cx.test(text)) {
2118              text = text.replace(cx, function (a) {
2119                  return '\\u' +
2120                      ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
2121              });
2122          }
2123
2124          if (/^[\],:{}\s]*$/
2125                  .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
2126                      .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
2127                      .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
2128
2129              j = eval('(' + text + ')');
2130
2131              return typeof reviver === 'function' ?
2132                  walk({'': j}, '') : j;
2133          }
2134
2135          throw new SyntaxError('JSON.parse');
2136      };
2137  }
2138}());
2139/* UI Component- Base class for all UI objects
2140================================================================================ */
2141_V_.Player = _V_.Component.extend({
2142
2143  init: function(tag, addOptions, ready){
2144
2145    this.tag = tag; // Store the original tag used to set options
2146
2147    var el = this.el = _V_.createElement("div"), // Div to contain video and controls
2148        options = this.options = {},
2149        width = options.width = tag.getAttribute('width'),
2150        height = options.height = tag.getAttribute('height'),
2151
2152        // Browsers default to 300x150 if there's no width/height or video size data.
2153        initWidth = width || 300,
2154        initHeight = height || 150;
2155
2156    // Make player findable on elements
2157    tag.player = el.player = this;
2158
2159    // Add callback to ready queue
2160    this.ready(ready);
2161
2162    // Wrap video tag in div (el/box) container
2163    tag.parentNode.insertBefore(el, tag);
2164    el.appendChild(tag); // Breaks iPhone, fixed in HTML5 setup.
2165
2166    // Give video tag properties to box
2167    el.id = this.id = tag.id; // ID will now reference box, not the video tag
2168    el.className = tag.className;
2169    // Update tag id/class for use as HTML5 playback tech
2170    tag.id += "_html5_api";
2171    tag.className = "vjs-tech";
2172
2173    // Make player easily findable by ID
2174    _V_.players[el.id] = this;
2175
2176    // Make box use width/height of tag, or default 300x150
2177    el.setAttribute("width", initWidth);
2178    el.setAttribute("height", initHeight);
2179    // Enforce with CSS since width/height attrs don't work on divs
2180    el.style.width = initWidth+"px";
2181    el.style.height = initHeight+"px";
2182    // Remove width/height attrs from tag so CSS can make it 100% width/height
2183    tag.removeAttribute("width");
2184    tag.removeAttribute("height");
2185
2186    // Set Options
2187    _V_.merge(options, _V_.options); // Copy Global Defaults
2188    _V_.merge(options, this.getVideoTagSettings()); // Override with Video Tag Options
2189    _V_.merge(options, addOptions); // Override/extend with options from setup call
2190
2191    // Store controls setting, and then remove immediately so native controls don't flash.
2192    tag.removeAttribute("controls");
2193
2194    // Poster will be handled by a manual <img>
2195    tag.removeAttribute("poster");
2196
2197    // Empty video tag sources and tracks so the built in player doesn't use them also.
2198    if (tag.hasChildNodes()) {
2199      for (var i=0,j=tag.childNodes;i<j.length;i++) {
2200        if (j[i].nodeName == "SOURCE" || j[i].nodeName == "TRACK") {
2201          tag.removeChild(j[i]);
2202        }
2203      }
2204    }
2205
2206    // Cache for video property values.
2207    this.values = {};
2208
2209    this.addClass("vjs-paused");
2210
2211    this.addEvent("ended", this.onEnded);
2212    this.addEvent("play", this.onPlay);
2213    this.addEvent("pause", this.onPause);
2214    this.addEvent("progress", this.onProgress);
2215    this.addEvent("error", this.onError);
2216
2217    // When the API is ready, loop through the components and add to the player.
2218    if (options.controls) {
2219      this.ready(function(){
2220        this.initComponents();
2221      });
2222    }
2223
2224    // Tracks defined in tracks.js
2225    this.textTracks = [];
2226    if (options.tracks && options.tracks.length > 0) {
2227      this.addTextTracks(options.tracks);
2228    }
2229
2230    // If there are no sources when the player is initialized,
2231    // load the first supported playback technology.
2232    if (!options.sources || options.sources.length == 0) {
2233      for (var i=0,j=options.techOrder; i<j.length; i++) {
2234        var techName = j[i],
2235            tech = _V_[techName];
2236
2237        // Check if the browser supports this technology
2238        if (tech.isSupported()) {
2239          this.loadTech(techName);
2240          break;
2241        }
2242      }
2243    } else {
2244      // Loop through playback technologies (HTML5, Flash) and check for support. Then load the best source.
2245      // A few assumptions here:
2246      //   All playback technologies respect preload false.
2247      this.src(options.sources);
2248    }
2249  },
2250
2251  // Cache for video property values.
2252  values: {},
2253
2254  destroy: function(){
2255    // Ensure that tracking progress and time progress will stop and plater deleted
2256    this.stopTrackingProgress();
2257    this.stopTrackingCurrentTime();
2258    _V_.players[this.id] = null;
2259    delete _V_.players[this.id];
2260    this.tech.destroy();
2261    this.el.parentNode.removeChild(this.el);
2262  },
2263
2264  createElement: function(type, options){},
2265
2266  getVideoTagSettings: function(){
2267    var options = {
2268      sources: [],
2269      tracks: []
2270    };
2271
2272    options.src = this.tag.getAttribute("src");
2273    options.controls = this.tag.getAttribute("controls") !== null;
2274    options.poster = this.tag.getAttribute("poster");
2275    options.preload = this.tag.getAttribute("preload");
2276    options.autoplay = this.tag.getAttribute("autoplay") !== null; // hasAttribute not IE <8 compatible
2277    options.loop = this.tag.getAttribute("loop") !== null;
2278    options.muted = this.tag.getAttribute("muted") !== null;
2279
2280    if (this.tag.hasChildNodes()) {
2281      for (var c,i=0,j=this.tag.childNodes;i<j.length;i++) {
2282        c = j[i];
2283        if (c.nodeName == "SOURCE") {
2284          options.sources.push({
2285            src: c.getAttribute('src'),
2286            type: c.getAttribute('type'),
2287            media: c.getAttribute('media'),
2288            title: c.getAttribute('title')
2289          });
2290        }
2291        if (c.nodeName == "TRACK") {
2292          options.tracks.push({
2293            src: c.getAttribute("src"),
2294            kind: c.getAttribute("kind"),
2295            srclang: c.getAttribute("srclang"),
2296            label: c.getAttribute("label"),
2297            'default': c.getAttribute("default") !== null,
2298            title: c.getAttribute("title")
2299          });
2300        }
2301      }
2302    }
2303    return options;
2304  },
2305
2306  /* PLayback Technology (tech)
2307  ================================================================================ */
2308  // Load/Create an instance of playback technlogy including element and API methods
2309  // And append playback element in player div.
2310  loadTech: function(techName, source){
2311
2312    // Pause and remove current playback technology
2313    if (this.tech) {
2314      this.unloadTech();
2315
2316    // If the first time loading, HTML5 tag will exist but won't be initialized
2317    // So we need to remove it if we're not loading HTML5
2318    } else if (techName != "html5" && this.tag) {
2319      this.el.removeChild(this.tag);
2320      this.tag = false;
2321    }
2322
2323    this.techName = techName;
2324
2325    // Turn off API access because we're loading a new tech that might load asynchronously
2326    this.isReady = false;
2327
2328    var techReady = function(){
2329      this.player.triggerReady();
2330
2331      // Manually track progress in cases where the browser/flash player doesn't report it.
2332      if (!this.support.progressEvent) {
2333        this.player.manualProgressOn();
2334      }
2335
2336      // Manually track timeudpates in cases where the browser/flash player doesn't report it.
2337      if (!this.support.timeupdateEvent) {
2338        this.player.manualTimeUpdatesOn();
2339      }
2340    }
2341
2342    // Grab tech-specific options from player options and add source and parent element to use.
2343    var techOptions = _V_.merge({ source: source, parentEl: this.el }, this.options[techName])
2344
2345    if (source) {
2346      if (source.src == this.values.src && this.values.currentTime > 0) {
2347        techOptions.startTime = this.values.currentTime;
2348      }
2349
2350      this.values.src = source.src;
2351    }
2352
2353    // Initialize tech instance
2354    this.tech = new _V_[techName](this, techOptions);
2355    this.tech.ready(techReady);
2356  },
2357
2358  unloadTech: function(){
2359    this.tech.destroy();
2360
2361    // Turn off any manual progress or timeupdate tracking
2362    if (this.manualProgress) { this.manualProgressOff(); }
2363
2364    if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
2365
2366    this.tech = false;
2367  },
2368
2369  // There's many issues around changing the size of a Flash (or other plugin) object.
2370  // First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
2371  // Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
2372  // To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
2373  // reloadTech: function(betweenFn){
2374  //   _V_.log("unloadingTech")
2375  //   this.unloadTech();
2376  //   _V_.log("unloadedTech")
2377  //   if (betweenFn) { betweenFn.call(); }
2378  //   _V_.log("LoadingTech")
2379  //   this.loadTech(this.techName, { src: this.values.src })
2380  //   _V_.log("loadedTech")
2381  // },
2382
2383  /* Fallbacks for unsupported event types
2384  ================================================================================ */
2385  // Manually trigger progress events based on changes to the buffered amount
2386  // Many flash players and older HTML5 browsers don't send progress or progress-like events
2387  manualProgressOn: function(){
2388    this.manualProgress = true;
2389
2390    // Trigger progress watching when a source begins loading
2391    this.trackProgress();
2392
2393    // Watch for a native progress event call on the tech element
2394    // In HTML5, some older versions don't support the progress event
2395    // So we're assuming they don't, and turning off manual progress if they do.
2396    this.tech.addEvent("progress", function(){
2397
2398      // Remove this listener from the element
2399      this.removeEvent("progress", arguments.callee);
2400
2401      // Update known progress support for this playback technology
2402      this.support.progressEvent = true;
2403
2404      // Turn off manual progress tracking
2405      this.player.manualProgressOff();
2406    });
2407  },
2408
2409  manualProgressOff: function(){
2410    this.manualProgress = false;
2411    this.stopTrackingProgress();
2412  },
2413
2414  trackProgress: function(){
2415    this.progressInterval = setInterval(_V_.proxy(this, function(){
2416      // Don't trigger unless buffered amount is greater than last time
2417      // log(this.values.bufferEnd, this.buffered().end(0), this.duration())
2418      /* TODO: update for multiple buffered regions */
2419      if (this.values.bufferEnd < this.buffered().end(0)) {
2420        this.triggerEvent("progress");
2421      } else if (this.bufferedPercent() == 1) {
2422        this.stopTrackingProgress();
2423        this.triggerEvent("progress"); // Last update
2424      }
2425    }), 500);
2426  },
2427  stopTrackingProgress: function(){ clearInterval(this.progressInterval); },
2428
2429  /* Time Tracking -------------------------------------------------------------- */
2430  manualTimeUpdatesOn: function(){
2431    this.manualTimeUpdates = true;
2432
2433    this.addEvent("play", this.trackCurrentTime);
2434    this.addEvent("pause", this.stopTrackingCurrentTime);
2435    // timeupdate is also called by .currentTime whenever current time is set
2436
2437    // Watch for native timeupdate event
2438    this.tech.addEvent("timeupdate", function(){
2439
2440      // Remove this listener from the element
2441      this.removeEvent("timeupdate", arguments.callee);
2442
2443      // Update known progress support for this playback technology
2444      this.support.timeupdateEvent = true;
2445
2446      // Turn off manual progress tracking
2447      this.player.manualTimeUpdatesOff();
2448    });
2449  },
2450
2451  manualTimeUpdatesOff: function(){
2452    this.manualTimeUpdates = false;
2453    this.stopTrackingCurrentTime();
2454    this.removeEvent("play", this.trackCurrentTime);
2455    this.removeEvent("pause", this.stopTrackingCurrentTime);
2456  },
2457
2458  trackCurrentTime: function(){
2459    if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
2460    this.currentTimeInterval = setInterval(_V_.proxy(this, function(){
2461      this.triggerEvent("timeupdate");
2462    }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
2463  },
2464
2465  // Turn off play progress tracking (when paused or dragging)
2466  stopTrackingCurrentTime: function(){ clearInterval(this.currentTimeInterval); },
2467
2468  /* Player event handlers (how the player reacts to certain events)
2469  ================================================================================ */
2470  onEnded: function(){
2471    if (this.options.loop) {
2472      this.currentTime(0);
2473      this.play();
2474    } else {
2475      this.pause();
2476      this.currentTime(0);
2477      this.pause();
2478    }
2479  },
2480
2481  onPlay: function(){
2482    _V_.removeClass(this.el, "vjs-paused");
2483    _V_.addClass(this.el, "vjs-playing");
2484  },
2485
2486  onPause: function(){
2487    _V_.removeClass(this.el, "vjs-playing");
2488    _V_.addClass(this.el, "vjs-paused");
2489  },
2490
2491  onProgress: function(){
2492    // Add custom event for when source is finished downloading.
2493    if (this.bufferedPercent() == 1) {
2494      this.triggerEvent("loadedalldata");
2495    }
2496  },
2497
2498  onError: function(e) {
2499    _V_.log("Video Error", e);
2500  },
2501
2502/* Player API
2503================================================================================ */
2504
2505  // Pass values to the playback tech
2506  techCall: function(method, arg){
2507
2508    // If it's not ready yet, call method when it is
2509    if (!this.tech.isReady) {
2510      this.tech.ready(function(){
2511        this[method](arg);
2512      });
2513
2514    // Otherwise call method now
2515    } else {
2516      try {
2517        this.tech[method](arg);
2518      } catch(e) {
2519        _V_.log(e);
2520      }
2521    }
2522  },
2523
2524  // Get calls can't wait for the tech, and sometimes don't need to.
2525  techGet: function(method){
2526
2527    // Make sure tech is ready
2528    if (this.tech.isReady) {
2529
2530      // Flash likes to die and reload when you hide or reposition it.
2531      // In these cases the object methods go away and we get errors.
2532      // When that happens we'll catch the errors and inform tech that it's not ready any more.
2533      try {
2534        return this.tech[method]();
2535      } catch(e) {
2536
2537        // When building additional tech libs, an expected method may not be defined yet
2538        if (this.tech[method] === undefined) {
2539          _V_.log("Video.js: " + method + " method not defined for "+this.techName+" playback technology.", e);
2540
2541        } else {
2542
2543          // When a method isn't available on the object it throws a TypeError
2544          if (e.name == "TypeError") {
2545            _V_.log("Video.js: " + method + " unavailable on "+this.techName+" playback technology element.", e);
2546            this.tech.isReady = false;
2547
2548          } else {
2549            _V_.log(e);
2550          }
2551        }
2552      }
2553    }
2554
2555    return;
2556  },
2557
2558  // Method for calling methods on the current playback technology
2559  // techCall: function(method, arg){
2560  // 
2561  //   // if (this.isReady) {
2562  //   //   
2563  //   // } else {
2564  //   //   _V_.log("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]", arguments.callee.caller.arguments.callee.caller.arguments.callee.caller)
2565  //   //   return false;
2566  //   //   // throw new Error("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]");
2567  //   // }
2568  // },
2569
2570  // http://dev.w3.org/html5/spec/video.html#dom-media-play
2571  play: function(){
2572    this.techCall("play");
2573    return this;
2574  },
2575
2576  // http://dev.w3.org/html5/spec/video.html#dom-media-pause
2577  pause: function(){
2578    this.techCall("pause");
2579    return this;
2580  },
2581  
2582  // http://dev.w3.org/html5/spec/video.html#dom-media-paused
2583  // The initial state of paused should be true (in Safari it's actually false)
2584  paused: function(){
2585    return (this.techGet("paused") === false) ? false : true;
2586  },
2587
2588  // http://dev.w3.org/html5/spec/video.html#dom-media-currenttime
2589  currentTime: function(seconds){
2590    if (seconds !== undefined) {
2591
2592      // Cache the last set value for smoother scrubbing.
2593      this.values.lastSetCurrentTime = seconds;
2594
2595      this.techCall("setCurrentTime", seconds);
2596
2597      // Improve the accuracy of manual timeupdates
2598      if (this.manualTimeUpdates) { this.triggerEvent("timeupdate"); }
2599
2600      return this;
2601    }
2602
2603    // Cache last currentTime and return
2604    // Default to 0 seconds
2605    return this.values.currentTime = (this.techGet("currentTime") || 0);
2606  },
2607
2608  // http://dev.w3.org/html5/spec/video.html#dom-media-duration
2609  // Duration should return NaN if not available. ParseFloat will turn false-ish values to NaN.
2610  duration: function(){
2611    return parseFloat(this.techGet("duration"));
2612  },
2613
2614  // Calculates how much time is left. Not in spec, but useful.
2615  remainingTime: function(){
2616    return this.duration() - this.currentTime();
2617  },
2618
2619  // http://dev.w3.org/html5/spec/video.html#dom-media-buffered
2620  // Buffered returns a timerange object. Kind of like an array of portions of the video that have been downloaded.
2621  // So far no browsers return more than one range (portion)
2622  buffered: function(){
2623    var buffered = this.techGet("buffered"),
2624        start = 0,
2625        end = this.values.bufferEnd = this.values.bufferEnd || 0, // Default end to 0 and store in values
2626        timeRange;
2627
2628    if (buffered && buffered.length > 0 && buffered.end(0) !== end) {
2629      end = buffered.end(0);
2630      // Storing values allows them be overridden by setBufferedFromProgress
2631      this.values.bufferEnd = end;
2632    }
2633
2634    return _V_.createTimeRange(start, end);
2635  },
2636
2637  // Calculates amount of buffer is full. Not in spec but useful.
2638  bufferedPercent: function(){
2639    return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
2640  },
2641
2642  // http://dev.w3.org/html5/spec/video.html#dom-media-volume
2643  volume: function(percentAsDecimal){
2644    var vol;
2645
2646    if (percentAsDecimal !== undefined) {
2647      vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
2648      this.values.volume = vol;
2649      this.techCall("setVolume", vol);
2650      _V_.setLocalStorage("volume", vol);
2651      return this;
2652    }
2653
2654    // Default to 1 when returning current volume.
2655    vol = parseFloat(this.techGet("volume"));
2656    return (isNaN(vol)) ? 1 : vol;
2657  },
2658
2659  // http://dev.w3.org/html5/spec/video.html#attr-media-muted
2660  muted: function(muted){
2661    if (muted !== undefined) {
2662      this.techCall("setMuted", muted);
2663      return this;
2664    }
2665    return this.techGet("muted") || false; // Default to false
2666  },
2667
2668  // http://dev.w3.org/html5/spec/dimension-attributes.html#attr-dim-height
2669  // Video tag width/height only work in pixels. No percents.
2670  // We could potentially allow percents but won't for now until we can do testing around it.
2671  width: function(width, skipListeners){
2672    if (width !== undefined) {
2673      this.el.width = width;
2674      this.el.style.width = width+"px";
2675
2676      // skipListeners allows us to avoid triggering the resize event when setting both width and height
2677      if (!skipListeners) { this.triggerEvent("resize"); }
2678      return this;
2679    }
2680    return parseInt(this.el.getAttribute("width"));
2681  },
2682  height: function(height){
2683    if (height !== undefined) {
2684      this.el.height = height;
2685      this.el.style.height = height+"px";
2686      this.triggerEvent("resize");
2687      return this;
2688    }
2689    return parseInt(this.el.getAttribute("height"));
2690  },
2691  // Set both width and height at the same time.
2692  size: function(width, height){
2693    // Skip resize listeners on width for optimization
2694    return this.width(width, true).height(height);
2695  },
2696
2697  // Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
2698  supportsFullScreen: function(){ return this.techGet("supportsFullScreen") || false; },
2699
2700  // Turn on fullscreen (or window) mode
2701  requestFullScreen: function(){
2702    var requestFullScreen = _V_.support.requestFullScreen;
2703
2704    this.isFullScreen = true;
2705
2706    // Check for browser element fullscreen support
2707    if (requestFullScreen) {
2708
2709      // Trigger fullscreenchange event after change
2710      _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){
2711        this.isFullScreen = document[requestFullScreen.isFullScreen];
2712
2713        // If cancelling fullscreen, remove event listener.
2714        if (this.isFullScreen == false) {
2715          _V_.removeEvent(document, requestFullScreen.eventName, arguments.callee);
2716        }
2717
2718        this.triggerEvent("fullscreenchange");
2719      }));
2720
2721      // Flash and other plugins get reloaded when you take their parent to fullscreen.
2722      // To fix that we'll remove the tech, and reload it after the resize has finished.
2723      if (this.tech.support.fullscreenResize === false && this.options.flash.iFrameMode != true) {
2724
2725        this.pause();
2726        this.unloadTech();
2727
2728        _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){
2729          _V_.removeEvent(document, requestFullScreen.eventName, arguments.callee);
2730          this.loadTech(this.techName, { src: this.values.src });
2731        }));
2732
2733        this.el[requestFullScreen.requestFn]();
2734
2735      } else {
2736        this.el[requestFullScreen.requestFn]();
2737      }
2738
2739    } else if (this.tech.supportsFullScreen()) {
2740      this.triggerEvent("fullscreenchange");
2741      this.techCall("enterFullScreen");
2742
2743    } else {
2744      this.triggerEvent("fullscreenchange");
2745      this.enterFullWindow();
2746    }
2747
2748     return this;
2749   },
2750
2751   cancelFullScreen: function(){
2752    var requestFullScreen = _V_.support.requestFullScreen;
2753
2754    this.isFullScreen = false;
2755
2756    // Check for browser element fullscreen support
2757    if (requestFullScreen) {
2758
2759     // Flash and other plugins get reloaded when you take their parent to fullscreen.
2760     // To fix that we'll remove the tech, and reload it after the resize has finished.
2761     if (this.tech.support.fullscreenResize === false && this.options.flash.iFrameMode != true) {
2762
2763       this.pause();
2764       this.unloadTech();
2765
2766       _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){
2767         _V_.removeEvent(document, requestFullScreen.eventName, arguments.callee);
2768         this.loadTech(this.techName, { src: this.values.src })
2769       }));
2770
2771       document[requestFullScreen.cancelFn]();
2772
2773     } else {
2774       document[requestFullScreen.cancelFn]();
2775     }
2776
2777    } else if (this.tech.supportsFullScreen()) {
2778     this.techCall("exitFullScreen");
2779     this.triggerEvent("fullscreenchange");
2780
2781    } else {
2782     this.exitFullWindow();
2783     this.triggerEvent("fullscreenchange");
2784    }
2785
2786    return this;
2787  },
2788
2789  // When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
2790  enterFullWindow: function(){
2791    this.isFullWindow = true;
2792
2793    // Storing original doc overflow value to return to when fullscreen is off
2794    this.docOrigOverflow = document.documentElement.style.overflow;
2795
2796    // Add listener for esc key to exit fullscreen
2797    _V_.addEvent(document, "keydown", _V_.proxy(this, this.fullWindowOnEscKey));
2798
2799    // Hide any scroll bars
2800    document.documentElement.style.overflow = 'hidden';
2801
2802    // Apply fullscreen styles
2803    _V_.addClass(document.body, "vjs-full-window");
2804    _V_.addClass(this.el, "vjs-fullscreen");
2805
2806    this.triggerEvent("enterFullWindow");
2807  },
2808  fullWindowOnEscKey: function(event){
2809    if (event.keyCode == 27) {
2810      if (this.isFullScreen == true) {
2811        this.cancelFullScreen();
2812      } else {
2813        this.exitFullWindow();
2814      }
2815    }
2816  },
2817
2818  exitFullWindow: function(){
2819    this.isFullWindow = false;
2820    _V_.removeEvent(document, "keydown", this.fullWindowOnEscKey);
2821
2822    // Unhide scroll bars.
2823    document.documentElement.style.overflow = this.docOrigOverflow;
2824
2825    // Remove fullscreen styles
2826    _V_.removeClass(document.body, "vjs-full-window");
2827    _V_.removeClass(this.el, "vjs-fullscreen");
2828
2829    // Resize the box, controller, and poster to original sizes
2830    // this.positionAll();
2831    this.triggerEvent("exitFullWindow");
2832  },
2833
2834  selectSource: function(sources){
2835
2836    // Loop through each playback technology in the options order
2837    for (var i=0,j=this.options.techOrder;i<j.length;i++) {
2838      var techName = j[i],
2839          tech = _V_[techName];
2840          // tech = _V_.tech[techName];
2841
2842      // Check if the browser supports this technology
2843      if (tech.isSupported()) {
2844
2845        // Loop through each source object
2846        for (var a=0,b=sources;a<b.length;a++) {
2847          var source = b[a];
2848
2849          // Check if source can be played with this technology
2850          if (tech.canPlaySource.call(this, source)) {
2851
2852            return { source: source, tech: techName };
2853
2854          }
2855        }
2856      }
2857    }
2858
2859    return false;
2860  },
2861
2862  // src is a pretty powerful function
2863  // If you pass it an array of source objects, it will find the best source to play and use that object.src
2864  //   If the new source requires a new playback technology, it will switch to that.
2865  // If you pass it an object, it will set the source to object.src
2866  // If you pass it anything else (url string) it will set the video source to that
2867  src: function(source){
2868    // Case: Array of source objects to choose from and pick the best to play
2869    if (source instanceof Array) {
2870
2871      var sourceTech = this.selectSource(source),
2872          source,
2873          techName;
2874
2875      if (sourceTech) {
2876          source = sourceTech.source;
2877          techName = sourceTech.tech;
2878
2879        // If this technology is already loaded, set source
2880        if (techName == this.techName) {
2881          this.src(source); // Passing the source object
2882
2883        // Otherwise load this technology with chosen source
2884        } else {
2885          this.loadTech(techName, source);
2886        }
2887      } else {
2888        _V_.log("No compatible source and playback technology were found.")
2889      }
2890
2891    // Case: Source object { src: "", type: "" ... }
2892    } else if (source instanceof Object) {
2893
2894      if (_V_[this.techName].canPlaySource(source)) {
2895        this.src(source.src);
2896      } else {
2897        // Send through tech loop to check for a compatible technology.
2898        this.src([source]);
2899      }
2900
2901    // Case: URL String (http://myvideo...)
2902    } else {
2903      // Cache for getting last set source
2904      this.values.src = source;
2905
2906      if (!this.isReady) {
2907        this.ready(function(){
2908          this.src(source);
2909        });
2910      } else {
2911        this.techCall("src", source);
2912        if (this.options.preload == "auto") {
2913          this.load();
2914        }
2915        if (this.options.autoplay) {
2916          this.play();
2917        }
2918      }
2919    }
2920    return this;
2921  },
2922
2923  // Begin loading the src data
2924  // http://dev.w3.org/html5/spec/video.html#dom-media-load
2925  load: function(){
2926    this.techCall("load");
2927    return this;
2928  },
2929
2930  // http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
2931  currentSrc: function(){
2932    return this.techGet("currentSrc") || this.values.src || "";
2933  },
2934
2935  // Attributes/Options
2936  preload: function(value){
2937    if (value !== undefined) {
2938      this.techCall("setPreload", value);
2939      this.options.preload = value;
2940      return this;
2941    }
2942    return this.techGet("preload");
2943  },
2944  autoplay: function(value){
2945    if (value !== undefined) {
2946      this.techCall("setAutoplay", value);
2947      this.options.autoplay = value;
2948      return this;
2949    }
2950    return this.techGet("autoplay", value);
2951  },
2952  loop: function(value){
2953    if (value !== undefined) {
2954      this.techCall("setLoop", value);
2955      this.options.loop = value;
2956      return this;
2957    }
2958    return this.techGet("loop");
2959  },
2960
2961  controls: function(){ return this.options.controls; },
2962  poster: function(){ return this.techGet("poster"); },
2963  error: function(){ return this.techGet("error"); },
2964  ended: function(){ return this.techGet("ended"); }
2965
2966  // Methods to add support for
2967  // networkState: function(){ return this.techCall("networkState"); },
2968  // readyState: function(){ return this.techCall("readyState"); },
2969  // seeking: function(){ return this.techCall("seeking"); },
2970  // initialTime: function(){ return this.techCall("initialTime"); },
2971  // startOffsetTime: function(){ return this.techCall("startOffsetTime"); },
2972  // played: function(){ return this.techCall("played"); },
2973  // seekable: function(){ return this.techCall("seekable"); },
2974  // videoTracks: function(){ return this.techCall("videoTracks"); },
2975  // audioTracks: function(){ return this.techCall("audioTracks"); },
2976  // videoWidth: function(){ return this.techCall("videoWidth"); },
2977  // videoHeight: function(){ return this.techCall("videoHeight"); },
2978  // defaultPlaybackRate: function(){ return this.techCall("defaultPlaybackRate"); },
2979  // playbackRate: function(){ return this.techCall("playbackRate"); },
2980  // mediaGroup: function(){ return this.techCall("mediaGroup"); },
2981  // controller: function(){ return this.techCall("controller"); },
2982  // defaultMuted: function(){ return this.techCall("defaultMuted"); }
2983});
2984
2985// RequestFullscreen API
2986(function(){
2987  var requestFn,
2988      cancelFn,
2989      eventName,
2990      isFullScreen,
2991      playerProto = _V_.Player.prototype;
2992
2993  // Current W3C Spec
2994  // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
2995  // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
2996  if (document.cancelFullscreen !== undefined) {
2997    requestFn = "requestFullscreen";
2998    cancelFn = "exitFullscreen";
2999    eventName = "fullscreenchange";
3000    isFullScreen = "fullScreen";
3001
3002  // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementaitons
3003  // that use prefixes and vary slightly from the new W3C spec. Specifically, using 'exit' instead of 'cancel',
3004  // and lowercasing the 'S' in Fullscreen.
3005  // Other browsers don't have any hints of which version they might follow yet, so not going to try to predict by loopeing through all prefixes.
3006  } else {
3007
3008    _V_.each(["moz", "webkit"], function(prefix){
3009
3010      // https://github.com/zencoder/video-js/pull/128
3011      if ((prefix != "moz" || document.mozFullScreenEnabled) && document[prefix + "CancelFullScreen"] !== undefined) {
3012        requestFn = prefix + "RequestFullScreen";
3013        cancelFn = prefix + "CancelFullScreen";
3014        eventName = prefix + "fullscreenchange";
3015
3016        if (prefix == "webkit") {
3017          isFullScreen = prefix + "IsFullScreen";
3018        } else {
3019          isFullScreen = prefix + "FullScreen";
3020        }
3021      }
3022
3023    });
3024
3025  }
3026
3027  if (requestFn) {
3028    _V_.support.requestFullScreen = {
3029      requestFn: requestFn,
3030      cancelFn: cancelFn,
3031      eventName: eventName,
3032      isFullScreen: isFullScreen
3033    };
3034  }
3035
3036})();/* Playback Technology - Base class for playback technologies
3037================================================================================ */
3038_V_.PlaybackTech = _V_.Component.extend({
3039  init: function(player, options){
3040    // this._super(player, options);
3041
3042    // Make playback element clickable
3043    // _V_.addEvent(this.el, "click", _V_.proxy(this, _V_.PlayToggle.prototype.onClick));
3044
3045    // this.addEvent("click", this.proxy(this.onClick));
3046
3047    // player.triggerEvent("techready");
3048  },
3049  // destroy: function(){},
3050  // createElement: function(){},
3051  onClick: function(){
3052    if (this.player.options.controls) {
3053      _V_.PlayToggle.prototype.onClick.call(this);
3054    }
3055  }
3056});
3057
3058// Create placeholder methods for each that warn when a method isn't supported by the current playback technology
3059_V_.apiMethods = "play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,set
3059Loop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted".split(",");
3060_V_.each(_V_.apiMethods, function(methodName){
3061  _V_.PlaybackTech.prototype[methodName] = function(){
3062    throw new Error("The '"+methodName+"' method is not available on the playback technology's API");
3063  }
3064});
3065
3066/* HTML5 Playback Technology - Wrapper for HTML5 Media API
3067================================================================================ */
3068_V_.html5 = _V_.PlaybackTech.extend({
3069
3070  init: function(player, options, ready){
3071    this.player = player;
3072    this.el = this.createElement();
3073    this.ready(ready);
3074
3075    this.addEvent("click", this.proxy(this.onClick));
3076
3077    var source = options.source;
3078
3079    // If the element source is already set, we may have missed the loadstart event, and want to trigger it.
3080    // We don't want to set the source again and interrupt playback.
3081    if (source && this.el.currentSrc == source.src) {
3082      player.triggerEvent("loadstart");
3083
3084    // Otherwise set the source if one was provided.
3085    } else if (source) {
3086      this.el.src = source.src;
3087    }
3088
3089    // Chrome and Safari both have issues with autoplay.
3090    // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
3091    // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
3092    // This fixes both issues. Need to wait for API, so it updates displays correctly
3093    player.ready(function(){
3094      if (this.options.autoplay && this.paused()) {
3095        this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16.
3096        this.play();
3097      }
3098    });
3099
3100    this.setupTriggers();
3101
3102    this.triggerReady();
3103  },
3104
3105  destroy: function(){
3106    this.player.tag = false;
3107    this.removeTriggers();
3108    this.el.parentNode.removeChild(this.el);
3109  },
3110
3111  createElement: function(){
3112    var html5 = _V_.html5,
3113        player = this.player,
3114
3115        // If possible, reuse original tag for HTML5 playback technology element
3116        el = player.tag,
3117        newEl;
3118
3119    // Check if this browser supports moving the element into the box.
3120    // On the iPhone video will break if you move the element,
3121    // So we have to create a brand new element.
3122    if (!el || this.support.movingElementInDOM === false) {
3123
3124      // If the original tag is still there, remove it.
3125      if (el) {
3126        player.el.removeChild(el);
3127      }
3128
3129      newEl = _V_.createElement("video", {
3130        id: el.id || player.el.id + "_html5_api",
3131        className: el.className || "vjs-tech"
3132      });
3133
3134      el = newEl;
3135      _V_.insertFirst(el, player.el);
3136    }
3137
3138    // Update tag settings, in case they were overridden
3139    _V_.each(["autoplay","preload","loop","muted"], function(attr){ // ,"poster"
3140      if (player.options[attr] !== null) {
3141        el[attr] = player.options[attr];
3142      }
3143    }, this);
3144
3145    return el;
3146  },
3147
3148  // Make video events trigger player events
3149  // May seem verbose here, but makes other APIs possible.
3150  setupTriggers: function(){
3151    _V_.each.call(this, _V_.html5.events, function(type){
3152      _V_.addEvent(this.el, type, _V_.proxy(this.player, this.eventHandler));
3153    });
3154  },
3155  removeTriggers: function(){
3156    _V_.each.call(this, _V_.html5.events, function(type){
3157      _V_.removeEvent(this.el, type, _V_.proxy(this.player, this.eventHandler));
3158    });
3159  },
3160  eventHandler: function(e){
3161    e.stopPropagation();
3162    this.triggerEvent(e);
3163  },
3164
3165  play: function(){ this.el.play(); },
3166  pause: function(){ this.el.pause(); },
3167  paused: function(){ return this.el.paused; },
3168
3169  currentTime: function(){ return this.el.currentTime; },
3170  setCurrentTime: function(seconds){
3171    try {
3172      this.el.currentTime = seconds;
3173      } catch(e) {
3174        _V_.log(e, "Video isn't ready. (VideoJS)");
3175      // this.warning(VideoJS.warnings.videoNotReady);
3176    }
3177  },
3178
3179  duration: function(){ return this.el.duration || 0; },
3180  buffered: function(){ return this.el.buffered; },
3181
3182  volume: function(){ return this.el.volume; },
3183  setVolume: function(percentAsDecimal){ this.el.volume = percentAsDecimal; },
3184  muted: function(){ return this.el.muted; },
3185  setMuted: function(muted){ this.el.muted = muted },
3186
3187  width: function(){ return this.el.offsetWidth; },
3188  height: function(){ return this.el.offsetHeight; },
3189
3190  supportsFullScreen: function(){
3191    if (typeof this.el.webkitEnterFullScreen == 'function') {
3192
3193      // Seems to be broken in Chromium/Chrome && Safari in Leopard
3194      if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) {
3195        return true;
3196      }
3197    }
3198    return false;
3199  },
3200
3201  enterFullScreen: function(){
3202      try {
3203        this.el.webkitEnterFullScreen();
3204      } catch (e) {
3205        if (e.code == 11) {
3206          // this.warning(VideoJS.warnings.videoNotReady);
3207          _V_.log("VideoJS: Video not ready.")
3208        }
3209      }
3210  },
3211  src: function(src){ this.el.src = src; },
3212  load: function(){ this.el.load(); },
3213  currentSrc: function(){ return this.el.currentSrc; },
3214
3215  preload: function(){ return this.el.preload; },
3216  setPreload: function(val){ this.el.preload = val; },
3217  autoplay: function(){ return this.el.autoplay; },
3218  setAutoplay: function(val){ this.el.autoplay = val; },
3219  loop: function(){ return this.el.loop; },
3220  setLoop: function(val){ this.el.loop = val; },
3221
3222  error: function(){ return this.el.error; },
3223  // networkState: function(){ return this.el.networkState; },
3224  // readyState: function(){ return this.el.readyState; },
3225  seeking: function(){ return this.el.seeking; },
3226  // initialTime: function(){ return this.el.initialTime; },
3227  // startOffsetTime: function(){ return this.el.startOffsetTime; },
3228  // played: function(){ return this.el.played; },
3229  // seekable: function(){ return this.el.seekable; },
3230  ended: function(){ return this.el.ended; },
3231  // videoTracks: function(){ return this.el.videoTracks; },
3232  // audioTracks: function(){ return this.el.audioTracks; },
3233  // videoWidth: function(){ return this.el.videoWidth; },
3234  // videoHeight: function(){ return this.el.videoHeight; },
3235  // textTracks: function(){ return this.el.textTracks; },
3236  // defaultPlaybackRate: function(){ return this.el.defaultPlaybackRate; },
3237  // playbackRate: function(){ return this.el.playbackRate; },
3238  // mediaGroup: function(){ return this.el.mediaGroup; },
3239  // controller: function(){ return this.el.controller; },
3240  controls: function(){ return this.player.options.controls; },
3241  defaultMuted: function(){ return this.el.defaultMuted; }
3242});
3243
3244/* HTML5 Support Testing -------------------------------------------------------- */
3245
3246_V_.html5.isSupported = function(){
3247  return !!document.createElement("video").canPlayType;
3248};
3249
3250_V_.html5.canPlaySource = function(srcObj){
3251  return !!document.createElement("video").canPlayType(srcObj.type);
3252  // TODO: Check Type
3253  // If no Type, check ext
3254  // Check Media Type
3255};
3256
3257// List of all HTML5 events (various uses).
3258_V_.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(",");
3259
3260/* HTML5 Device Fixes ---------------------------------------------------------- */
3261
3262_V_.html5.prototype.support = {
3263
3264  // Support for tech specific full screen. (webkitEnterFullScreen, not requestFullscreen)
3265  // http://developer.apple.com/library/safari/#documentation/AudioVideo/Reference/HTMLVideoElementClassReference/HTMLVideoElement/HTMLVideoElement.html
3266  // Seems to be broken in Chromium/Chrome && Safari in Leopard
3267  fullscreen: (typeof _V_.testVid.webkitEnterFullScreen !== undefined) ? (!_V_.ua.match("Chrome") && !_V_.ua.match("Mac OS X 10.5") ? true : false) : false,
3268
3269  // In iOS, if you move a video element in the DOM, it breaks video playback.
3270  movingElementInDOM: !_V_.isIOS()
3271
3272};
3273
3274// Android
3275if (_V_.isAndroid()) {
3276
3277  // Override Android 2.2 and less canPlayType method which is broken
3278  if (_V_.androidVersion() < 3) {
3279    document.createElement("video").constructor.prototype.canPlayType = function(type){
3280      return (type && type.toLowerCase().indexOf("video/mp4") != -1) ? "maybe" : "";
3281    };
3282  }
3283}
3284
3285
3286/* VideoJS-SWF - Custom Flash Player with HTML5-ish API - https://github.com/zencoder/video-js-swf
3287================================================================================ */
3288_V_.flash = _V_.PlaybackTech.extend({
3289
3290  init: function(player, options){
3291    this.player = player;
3292
3293    var source = options.source,
3294
3295        // Which element to embed in
3296        parentEl = options.parentEl,
3297
3298        // Create a temporary element to be replaced by swf object
3299        placeHolder = this.el = _V_.createElement("div", { id: parentEl.id + "_temp_flash" }),
3300
3301        // Generate ID for swf object
3302        objId = player.el.id+"_flash_api",
3303
3304        // Store player options in local var for optimization
3305        playerOptions = player.options,
3306
3307        // Merge default flashvars with ones passed in to init
3308        flashVars = _V_.merge({
3309
3310          // SWF Callback Functions
3311          readyFunction: "_V_.flash.onReady",
3312          eventProxyFunction: "_V_.flash.onEvent",
3313          errorEventProxyFunction: "_V_.flash.onError",
3314
3315          // Player Settings
3316          autoplay: playerOptions.autoplay,
3317          preload: playerOptions.preload,
3318          loop: playerOptions.loop,
3319          muted: playerOptions.muted
3320
3321        }, options.flashVars),
3322
3323        // Merge default parames with ones passed in
3324        params = _V_.merge({
3325          wmode: "opaque", // Opaque is needed to overlay controls, but can affect playback performance
3326          bgcolor: "#000000" // Using bgcolor prevents a white flash when the object is loading
3327        }, options.params),
3328
3329        // Merge default attributes with ones passed in
3330        attributes = _V_.merge({
3331          id: objId,
3332          name: objId, // Both ID and Name needed or swf to identifty itself
3333          'class': 'vjs-tech'
3334        }, options.attributes)
3335    ;
3336
3337    // If source was supplied pass as a flash var.
3338    if (source) {
3339      flashVars.src = encodeURIComponent(_V_.getAbsoluteURL(source.src));
3340    }
3341
3342    // Add placeholder to player div
3343    _V_.insertFirst(placeHolder, parentEl);
3344
3345    // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
3346    // This allows resetting the playhead when we catch the reload
3347    if (options.startTime) {
3348      this.ready(function(){
3349        this.load();
3350        this.play();
3351        this.currentTime(options.startTime);
3352      });
3353    }
3354
3355    // Flash iFrame Mode
3356    // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
3357    // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
3358    // - Webkit when hiding the plugin
3359    // - Webkit and Firefox when using requestFullScreen on a parent element
3360    // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
3361    // Issues that remain include hiding the element and requestFullScreen in Firefox specifically
3362
3363    // There's on particularly annoying issue with this method which is that Firefox throws a 
3363security error on an offsite Flash object loaded into a dynamically created iFrame.
3364    // Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.
3365    // I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.
3366    // Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe
3367    // In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.
3368    // The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.
3369
3370    // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
3371    // Firefox 9 throws a security error, unleess you call location.href right before doc.write.
3372    //    Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
3373    // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
3374
3375    if (options.iFrameMode == true && !_V_.isFF) {
3376
3377      // Create iFrame with vjs-tech class so it's 100% width/height
3378      var iFrm = _V_.createElement("iframe", {
3379        id: objId + "_iframe",
3380        name: objId + "_iframe",
3381        className: "vjs-tech",
3382        scrolling: "no",
3383        marginWidth: 0,
3384        marginHeight: 0,
3385        frameBorder: 0
3386      });
3387
3388      // Update ready function names in flash vars for iframe window
3389      flashVars.readyFunction = "ready";
3390      flashVars.eventProxyFunction = "events";
3391      flashVars.errorEventProxyFunction = "errors";
3392
3393      // Tried multiple methods to get this to work in all browsers
3394
3395      // Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.
3396      // The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error
3397      // var newObj = _V_.flash.embed(options.swf, placeHolder, flashVars, params, attributes);
3398      // (in onload)
3399      //  var temp = _V_.createElement("a", { id:"asdf", innerHTML: "asdf" } );
3400      //  iDoc.body.appendChild(temp);
3401
3402      // Tried embedding the flash object through javascript in the iframe source.
3403      // This works in webkit but still triggers the firefox security error
3404      // iFrm.src = "javascript: document.write('"+_V_.flash.getEmbedCode(options.swf, flashVars, params, attributes)+"');";
3405
3406      // Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe
3407      // We should add an option to host the iframe locally though, because it could help a lot of issues.
3408      // iFrm.src = "iframe.html";
3409
3410      // Wait until iFrame has loaded to write into it.
3411      _V_.addEvent(iFrm, "load", _V_.proxy(this, function(){
3412
3413        var iDoc, objTag, swfLoc,
3414            iWin = iFrm.contentWindow,
3415            varString = "";
3416
3417
3418        // The one working method I found was to use the iframe's document.write() to create the swf object
3419        // This got around the security issue in all browsers except firefox.
3420        // I did find a hack where if I call the iframe's window.location.href="", it would get around the security error
3421        // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
3422        // Plus Firefox 3.6 didn't work no matter what I tried.
3423        // if (_V_.ua.match("Firefox")) {
3424        //   iWin.location.href = "";
3425        // }
3426
3427        // Get the iFrame's document depending on what the browser supports
3428        iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
3429
3430        // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
3431        // Even tried adding /. that was mentioned in a browser security writeup
3432        // document.domain = document.domain+"/.";
3433        // iDoc.domain = document.domain+"/.";
3434
3435        // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
3436        // iDoc.body.innerHTML = swfObjectHTML;
3437
3438        // Tried appending the object to the iframe doc's body. Security error in all browsers.
3439        // iDoc.body.appendChild(swfObject);
3440
3441        // Using document.write actually got around the security error that browsers were throwing.
3442        // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
3443        // Not sure why that's a security issue, but apparently it is.
3444        iDoc.write(_V_.flash.getEmbedCode(options.swf, flashVars, params, attributes));
3445
3446        // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
3447        // So far no issues with swf ready event being called before it's set on the window.
3448        iWin.player = this.player;
3449
3450        // Create swf ready function for iFrame window
3451        iWin.ready = _V_.proxy(this.player, function(currSwf){
3452          var el = iDoc.getElementById(currSwf),
3453              player = this,
3454              tech = player.tech;
3455
3456          // Update reference to playback technology element
3457          tech.el = el;
3458
3459          // Now that the element is ready, make a click on the swf play the video
3460          _V_.addEvent(el, "click", tech.proxy(tech.onClick));
3461
3462          // Make sure swf is actually ready. Sometimes the API isn't actually yet.
3463          _V_.flash.checkReady(tech);
3464        });
3465
3466        // Create event listener for all swf events
3467        iWin.events = _V_.proxy(this.player, function(swfID, eventName, other){
3468          var player = this;
3469          if (player && player.techName == "flash") {
3470            player.triggerEvent(eventName);
3471          }
3472        });
3473
3474        // Create error listener for all swf errors
3475        iWin.errors = _V_.proxy(this.player, function(swfID, eventName){
3476          _V_.log("Flash Error", eventName);
3477        });
3478
3479      }));
3480
3481      // Replace placeholder with iFrame (it will load now)
3482      placeHolder.parentNode.replaceChild(iFrm, placeHolder);
3483
3484    // If not using iFrame mode, embed as normal object
3485    } else {
3486      _V_.flash.embed(options.swf, placeHolder, flashVars, params, attributes);
3487    }
3488  },
3489
3490  destroy: function(){
3491    this.el.parentNode.removeChild(this.el);
3492  },
3493
3494  // setupTriggers: function(){}, // Using global onEvent func to distribute events
3495
3496  play: function(){ this.el.vjs_play(); },
3497  pause: function(){ this.el.vjs_pause(); },
3498  src: function(src){
3499    // Make sure source URL is abosolute.
3500    src = _V_.getAbsoluteURL(src);
3501
3502    this.el.vjs_src(src);
3503
3504    // Currently the SWF doesn't autoplay if you load a source later.
3505    // e.g. Load player w/ no source, wait 2s, set src.
3506    if (this.player.autoplay()) {
3507      var tech = this;
3508      setTimeout(function(){ tech.play(); }, 0);
3509    }
3510  },
3511  load: function(){ this.el.vjs_load(); },
3512  poster: function(){ this.el.vjs_getProperty("poster"); },
3513
3514  buffered: function(){
3515    return _V_.createTimeRange(0, this.el.vjs_getProperty("buffered"));
3516  },
3517
3518  supportsFullScreen: function(){
3519    return false; // Flash does not allow fullscreen through javascript
3520  },
3521  enterFullScreen: function(){
3522    return false;
3523  }
3524});
3525
3526// Create setters and getters for attributes
3527(function(){
3528
3529  var api = _V_.flash.prototype,
3530      readWrite = "preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted".split(","),
3531      readOnly = "error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks".split(","),
3532      callOnly = "load,play,pause".split(",");
3533      // Overridden: buffered
3534
3535      createSetter = function(attr){
3536        var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
3537        api["set"+attrUpper] = function(val){ return this.el.vjs_setProperty(attr, val); };
3538      },
3539
3540      createGetter = function(attr){
3541        api[attr] = function(){ return this.el.vjs_getProperty(attr); };
3542      }
3543  ;
3544
3545  // Create getter and setters for all read/write attributes
3546  _V_.each(readWrite, function(attr){
3547    createGetter(attr);
3548    createSetter(attr);
3549  });
3550
3551  // Create getters for read-only attributes
3552  _V_.each(readOnly, function(attr){
3553    createGetter(attr);
3554  });
3555
3556})();
3557
3558/* Flash Support Testing -------------------------------------------------------- */
3559
3560_V_.flash.isSupported = function(){
3561  return _V_.flash.version()[0] >= 10;
3562  // return swfobject.hasFlashPlayerVersion("10");
3563};
3564
3565_V_.flash.canPlaySource = function(srcObj){
3566  if (srcObj.type in _V_.flash.prototype.support.formats) { return "maybe"; }
3567};
3568
3569_V_.flash.prototype.support = {
3570  formats: {
3571    "video/flv": "FLV",
3572    "video/x-flv": "FLV",
3573    "video/mp4": "MP4",
3574    "video/m4v": "MP4"
3575  },
3576
3577  // Optional events that we can manually mimic with timers
3578  progressEvent: false,
3579  timeupdateEvent: false,
3580
3581  // Resizing plugins using request fullscreen reloads the plugin
3582  fullscreenResize: false,
3583
3584  // Resizing plugins in Firefox always reloads the plugin (e.g. full window mode)
3585  parentResize: !(_V_.ua.match("Firefox"))
3586};
3587
3588_V_.flash.onReady = function(currSwf){
3589
3590  var el = _V_.el(currSwf);
3591
3592  // Get player from box
3593  // On firefox reloads, el might already have a player
3594  var player = el.player || el.parentNode.player,
3595      tech = player.tech;
3596
3597  // Reference player on tech element
3598  el.player = player;
3599
3600  // Update reference to playback technology element
3601  tech.el = el;
3602
3603  // Now that the element is ready, make a click on the swf play the video
3604  tech.addEvent("click", tech.onClick);
3605
3606  _V_.flash.checkReady(tech);
3607};
3608
3609// The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.
3610// If it's not ready, we set a timeout to check again shortly.
3611_V_.flash.checkReady = function(tech){
3612
3613  // Check if API property exists
3614  if (tech.el.vjs_getProperty) {
3615
3616    // If so, tell tech it's ready
3617    tech.triggerReady();
3618
3619  // Otherwise wait longer.
3620  } else {
3621
3622    setTimeout(function(){
3623      _V_.flash.checkReady(tech);
3624    }, 50);
3625
3626  }
3627};
3628
3629// Trigger events from the swf on the player
3630_V_.flash.onEvent = function(swfID, eventName){
3631  var player = _V_.el(swfID).player;
3632  player.triggerEvent(eventName);
3633};
3634
3635// Log errors from the swf
3636_V_.flash.onError = function(swfID, err){
3637  var player = _V_.el(swfID).player;
3638  player.triggerEvent("error");
3639  _V_.log("Flash Error", err, swfID);
3640};
3641
3642// Flash Version Check
3643_V_.flash.version = function(){
3644  var version = '0,0,0'
3645
3646  // IE
3647  try {
3648    version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
3649
3650  // other browsers
3651  } catch(e) {
3652    try {
3653      if (navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){
3654        version = (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g, ",").match(/^,?(.+),?$/)[1];
3655      }
3656    } catch(e) {}
3657  }
3658  return version.split(",");
3659}
3660
3661// Flash embedding method. Only used in non-iframe mode
3662_V_.flash.embed = function(swf, placeHolder, flashVars, params, attributes){
3663  var code = _V_.flash.getEmbedCode(swf, flashVars, params, attributes),
3664
3665      // Get element by embedding code and retrieving created element
3666      obj = _V_.createElement("div", { innerHTML: code }).childNodes[0],
3667
3668      par = placeHolder.parentNode
3669  ;
3670
3671  placeHolder.parentNode.replaceChild(obj, placeHolder);
3672
3673  // IE6 seems to have an issue where it won't initialize the swf object after injecting it.
3674  // This is a dumb temporary fix
3675  if (_V_.isIE()) {
3676    var newObj = par.childNodes[0];
3677    setTimeout(function(){
3678      newObj.style.display = "block";
3679    }, 1000);
3680  }
3681
3682  return obj;
3683
3684};
3685
3686_V_.flash.getEmbedCode = function(swf, flashVars, params, attributes){
3687
3688  var objTag = '<object type="application/x-shockwave-flash"',
3689      flashVarsString = '',
3690      paramsString = ''
3691      attrsString = '';
3692
3693  // Convert flash vars to string
3694  if (flashVars) {
3695    _V_.eachProp(flashVars, function(key, val){
3696      flashVarsString += (key + "=" + val + "&amp;");
3697    });
3698  }
3699
3700  // Add swf, flashVars, and other default params
3701  params = _V_.merge({
3702    movie: swf,
3703    flashvars: flashVarsString,
3704    allowScriptAccess: "always", // Required to talk to swf
3705    allowNetworking: "all" // All should be default, but having security issues.
3706  }, params);
3707
3708  // Create param tags string
3709  _V_.eachProp(params, function(key, val){
3710    paramsString += '<param name="'+key+'" value="'+val+'" />';
3711  });
3712
3713  attributes = _V_.merge({
3714    // Add swf to attributes (need both for IE and Others to work)
3715    data: swf,
3716
3717    // Default to 100% width/height
3718    width: "100%",
3719    height: "100%"
3720
3721  }, attributes);
3722
3723  // Create Attributes string
3724  _V_.eachProp(attributes, function(key, val){
3725    attrsString += (key + '="' + val + '" ');
3726  });
3727
3728  return objTag + attrsString + '>' + paramsString + '</object>';
3729}
3730// TEXT TRACKS
3731// Text tracks are tracks of timed text events.
3732//    Captions - text displayed over the video for the hearing impared
3733//    Subtitles - text displayed over the video for those who don't understand langauge in the video
3734//    Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
3735//    Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
3736
3737// Player Track Functions - Functions add to the player object for easier access to tracks
3738_V_.merge(_V_.Player.prototype, {
3739
3740  // Add an array of text tracks. captions, subtitles, chapters, descriptions
3741  // Track objects will be stored in the player.textTracks array
3742  addTextTracks: function(trackObjects){
3743    var tracks = this.textTracks = (this.textTracks) ? this.textTracks : [],
3744        i = 0, j = trackObjects.length, track, Kind;
3745
3746    for (;i<j;i++) {
3747      // HTML5 Spec says default to subtitles.
3748      // Uppercase (uc) first letter to match class names
3749      Kind = _V_.uc(trackObjects[i].kind || "subtitles");
3750
3751      // Create correct texttrack class. CaptionsTrack, etc.
3752      track = new _V_[Kind + "Track"](this, trackObjects[i]);
3753
3754      tracks.push(track);
3755
3756      // If track.default is set, start showing immediately
3757      // TODO: Add a process to deterime the best track to show for the specific kind
3758      // Incase there are mulitple defaulted tracks of the same kind
3759      // Or the user has a set preference of a specific language that should override the default
3760      if (track['default']) {
3761        this.ready(_V_.proxy(track, track.show));
3762      }
3763    }
3764
3765    // Return the track so it can be appended to the display component
3766    return this;
3767  },
3768
3769  // Show a text track
3770  // disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
3771  showTextTrack: function(id, disableSameKind){
3772    var tracks = this.textTracks,
3773        i = 0,
3774        j = tracks.length,
3775        track, showTrack, kind;
3776
3777    // Find Track with same ID
3778    for (;i<j;i++) {
3779      track = tracks[i];
3780      if (track.id === id) {
3781        track.show();
3782        showTrack = track;
3783
3784      // Disable tracks of the same kind
3785      } else if (disableSameKind && track.kind == disableSameKind && track.mode > 0) {
3786        track.disable();
3787      }
3788    }
3789
3790    // Get track kind from shown track or disableSameKind
3791    kind = (showTrack) ? showTrack.kind : ((disableSameKind) ? disableSameKind : false);
3792
3793    // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.
3794    if (kind) {
3795      this.triggerEvent(kind+"trackchange");
3796    }
3797
3798    return this;
3799  }
3800
3801});
3802
3803// Track Class
3804// Contains track methods for loading, showing, parsing cues of tracks
3805_V_.Track = _V_.Component.extend({
3806
3807  init: function(player, options){
3808    this._super(player, options);
3809
3810    // Apply track info to track object
3811    // Options will often be a track element
3812    _V_.merge(this, {
3813      // Build ID if one doesn't exist
3814      id: options.id || ("vjs_" + options.kind + "_" + options.language + "_" + _V_.guid++),
3815
3816      src: options.src,
3817
3818      // If default is used, subtitles/captions to start showing
3819      "default": options["default"], // 'default' is reserved-ish
3820      title: options.title,
3821
3822      // Language - two letter string to represent track language, e.g. "en" for English
3823      // readonly attribute DOMString language;
3824      language: options.srclang,
3825
3826      // Track label e.g. "English"
3827      // readonly attribute DOMString label;
3828      label: options.label,
3829
3830      // All cues of the track. Cues have a startTime, endTime, text, and other properties.
3831      // readonly attribute TextTrackCueList cues;
3832      cues: [],
3833
3834      // ActiveCues is all cues that are currently showing
3835      // readonly attribute TextTrackCueList activeCues;
3836      activeCues: [],
3837
3838      // ReadyState describes if the text file has been loaded
3839      // const unsigned short NONE = 0;
3840      // const unsigned short LOADING = 1;
3841      // const unsigned short LOADED = 2;
3842      // const unsigned short ERROR = 3;
3843      // readonly attribute unsigned short readyState;
3844      readyState: 0,
3845
3846      // Mode describes if the track is showing, hidden, or disabled
3847      // const unsigned short OFF = 0;
3848      // const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)
3849      // const unsigned short SHOWING = 2;
3850      // attribute unsigned short mode;
3851      mode: 0
3852    });
3853  },
3854
3855  // Create basic div to hold cue text
3856  createElement: function(){
3857    return this._super("div", {
3858      className: "vjs-" + this.kind + " vjs-text-track"
3859    });
3860  },
3861
3862  // Show: Mode Showing (2)
3863  // 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.
3864  // The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
3865  // In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;
3866  // for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a 
3866non-visual fashion;
3867  // 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.
3868  // 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.
3869  // This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
3870  show: function(){
3871    this.activate();
3872
3873    this.mode = 2;
3874
3875    // Show element.
3876    this._super();
3877  },
3878
3879  // Hide: Mode Hidden (1)
3880  // Indicates that the text track is active, but that the user agent is not actively displaying the cues.
3881  // If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
3882  // The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
3883  hide: function(){
3884    // When hidden, cues are still triggered. Disable to stop triggering.
3885    this.activate();
3886
3887    this.mode = 1;
3888
3889    // Hide element.
3890    this._super();
3891  },
3892
3893  // Disable: Mode Off/Disable (0)
3894  // 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.
3895  // No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
3896  disable: function(){
3897    // If showing, hide.
3898    if (this.mode == 2) { this.hide(); }
3899
3900    // Stop triggering cues
3901    this.deactivate();
3902
3903    // Switch Mode to Off
3904    this.mode = 0;
3905  },
3906
3907  // Turn on cue tracking. Tracks that are showing OR hidden are active.
3908  activate: function(){
3909    // Load text file if it hasn't been yet.
3910    if (this.readyState == 0) { this.load(); }
3911
3912    // Only activate if not already active.
3913    if (this.mode == 0) {
3914      // Update current cue on timeupdate
3915      // Using unique ID for proxy function so other tracks don't remove listener
3916      this.player.addEvent("timeupdate", this.proxy(this.update, this.id));
3917
3918      // Reset cue time on media end
3919      this.player.addEvent("ended", this.proxy(this.reset, this.id));
3920
3921      // Add to display
3922      if (this.kind == "captions" || this.kind == "subtitles") {
3923        this.player.textTrackDisplay.addComponent(this);
3924      }
3925    }
3926  },
3927
3928  // Turn off cue tracking.
3929  deactivate: function(){
3930    // Using unique ID for proxy function so other tracks don't remove listener
3931    this.player.removeEvent("timeupdate", this.proxy(this.update, this.id));
3932    this.player.removeEvent("ended", this.proxy(this.reset, this.id));
3933    this.reset(); // Reset
3934
3935    // Remove from display
3936    this.player.textTrackDisplay.removeComponent(this);
3937  },
3938
3939  // A readiness state
3940  // One of the following:
3941  //
3942  // Not loaded
3943  // 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.
3944  //
3945  // Loading
3946  // 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.
3947  //
3948  // Loaded
3949  // 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.
3950  //
3951  // Failed to load
3952  // 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.
3953  load: function(){
3954
3955    // Only load if not loaded yet.
3956    if (this.readyState == 0) {
3957      this.readyState = 1;
3958      _V_.get(this.src, this.proxy(this.parseCues), this.proxy(this.onError));
3959    }
3960
3961  },
3962
3963  onError: function(err){
3964    this.error = err;
3965    this.readyState = 3;
3966    this.triggerEvent("error");
3967  },
3968
3969  // Parse the WebVTT text format for cue times.
3970  // TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
3971  parseCues: function(srcContent) {
3972    var cue, time, text,
3973        lines = srcContent.split("\n"),
3974        line = "", id;
3975
3976    for (var i=1, j=lines.length; i<j; i++) {
3977      // Line 0 should be 'WEBVTT', so skipping i=0
3978
3979      line = _V_.trim(lines[i]);
3979 // Trim whitespace and linebreaks
3980
3981      if (line) { // Loop until a line with content
3982
3983        // First line could be an optional cue ID
3984        // Check if line has the time separator
3985        if (line.indexOf("-->") == -1) {
3986          id = line;
3987          // Advance to next line for timing.
3988          line = _V_.trim(lines[++i]);
3989        } else {
3990          id = this.cues.length;
3991        }
3992
3993        // First line - Number
3994        cue = {
3995          id: id, // Cue Number
3996          index: this.cues.length // Position in Array
3997        };
3998
3999        // Timing line
4000        time = line.split(" --> ");
4001        cue.startTime = this.parseCueTime(time[0]);
4002        cue.endTime = this.parseCueTime(time[1]);
4003
4004        // Additional lines - Cue Text
4005        text = [];
4006
4007        // Loop until a blank line or end of lines
4008        // Assumeing trim("") returns false for blank lines
4009        while (lines[++i] && (line = _V_.trim(lines[i]))) {
4010          text.push(line);
4011        }
4012
4013        cue.text = text.join('<br/>');
4014
4015        // Add this cue
4016        this.cues.push(cue);
4017      }
4018    }
4019
4020    this.readyState = 2;
4021    this.triggerEvent("loaded");
4022  },
4023
4024  parseCueTime: function(timeText) {
4025    var parts = timeText.split(':'),
4026        time = 0,
4027        hours, minutes, other, seconds, ms, flags;
4028
4029    // Check if optional hours place is included
4030    // 00:00:00.000 vs. 00:00.000
4031    if (parts.length == 3) {
4032      hours = parts[0];
4033      minutes = parts[1];
4034      other = parts[2];
4035    } else {
4036      hours = 0;
4037      minutes = parts[0];
4038      other = parts[1];
4039    }
4040
4041    // Break other (seconds, milliseconds, and flags) by spaces
4042    // TODO: Make additional cue layout settings work with flags
4043    other = other.split(/\s+/)
4044    // Remove seconds. Seconds is the first part before any spaces.
4045    seconds = other.splice(0,1)[0];
4046    // Could use either . or , for decimal
4047    seconds = seconds.split(/\.|,/);
4048    // Get milliseconds
4049    ms = parseFloat(seconds[1]);
4050    seconds = seconds[0];
4051
4052    // hours => seconds
4053    time += parseFloat(hours) * 3600;
4054    // minutes => seconds
4055    time += parseFloat(minutes) * 60;
4056    // Add seconds
4057    time += parseFloat(seconds);
4058    // Add milliseconds
4059    if (ms) { time += ms/1000; }
4060
4061    return time;
4062  },
4063
4064  // Update active cues whenever timeupdate events are triggered on the player.
4065  update: function(){
4066    if (this.cues.length > 0) {
4067
4068      // Get curent player time
4069      var time = this.player.currentTime();
4070
4071      // Check if the new time is outside the time box created by the the last update.
4072      if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
4073        var cues = this.cues,
4074
4075            // Create a new time box for this state.
4076            newNextChange = this.player.duration(), // Start at beginning of the timeline
4077            newPrevChange = 0, // Start at end
4078
4079            reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.
4080            newCues = [], // Store new active cues.
4081
4082            // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.
4083            firstActiveIndex, lastActiveIndex,
4084
4085            html = "", // Create cue text HTML to add to the display
4086            cue, i, j; // Loop vars
4087
4088        // Check if time is going forwards or backwards (scrubbing/rewinding)
4089        // If we know the direction we can optimize the starting position and direction of the loop through the cues array.
4090        if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen
4091          // Forwards, so start at the index of the first active cue and loop forward
4092          i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;
4093        } else {
4094          // Backwards, so start at the index of the last active cue and loop backward
4095          reverse = true;
4096          i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;
4097        }
4098
4099        while (true) { // Loop until broken
4100          cue = cues[i];
4101
4102          // Cue ended at this point
4103          if (cue.endTime <= time) {
4104            newPrevChange = Math.max(newPrevChange, cue.endTime);
4105
4106            if (cue.active) {
4107              cue.active = false;
4108            }
4109
4110            // No earlier cues should have an active start time.
4111            // Nevermind. Assume first cue could have a duration the same as the video.
4112            // In that case we need to loop all the way back to the beginning.
4113            // if (reverse && cue.startTime) { break; }
4114
4115          // Cue hasn't started
4116          } else if (time < cue.startTime) {
4117            newNextChange = Math.min(newNextChange, cue.startTime);
4118
4119            if (cue.active) {
4120              cue.active = false;
4121            }
4122
4123            // No later cues should have an active start time.
4124            if (!reverse) { break; }
4125
4126          // Cue is current
4127          } else {
4128
4129            if (reverse) {
4130              // Add cue to front of array to keep in time order
4131              newCues.splice(0,0,cue);
4132
4133              // If in reverse, the first current cue is our lastActiveCue
4134              if (lastActiveIndex === undefined) { lastActiveIndex = i; }
4135              firstActiveIndex = i;
4136            } else {
4137              // Add cue to end of array
4138              newCues.push(cue);
4139
4140              // If forward, the first current cue is our firstActiveIndex
4141              if (firstActiveIndex === undefined) { firstActiveIndex = i; }
4142              lastActiveIndex = i;
4143            }
4144
4145            newNextChange = Math.min(newNextChange, cue.endTime);
4146            newPrevChange = Math.max(newPrevChange, cue.startTime);
4147
4148            cue.active = true;
4149          }
4150
4151          if (reverse) {
4152            // Reverse down the array of cues, break if at first
4153            if (i === 0) { break; } else { i--; }
4154          } else {
4155            // Walk up the array fo cues, break if at last
4156            if (i === cues.length - 1) { break; } else { i++; }
4157          }
4158
4159        }
4160
4161        this.activeCues = newCues;
4162        this.nextChange = newNextChange;
4163        this.prevChange = newPrevChange;
4164        this.firstActiveIndex = firstActiveIndex;
4165        this.lastActiveIndex = lastActiveIndex;
4166
4167        this.updateDisplay();
4168
4169        this.triggerEvent("cuechange");
4170      }
4171    }
4172  },
4173
4174  // Add cue HTML to display
4175  updateDisplay: function(){
4176    var cues = this.activeCues,
4177        html = "",
4178        i=0,j=cues.length;
4179
4180    for (;i<j;i++) {
4181      html += "<span class='vjs-tt-cue'>"+cues[i].text+"</span>";
4182    }
4183
4184    this.el.innerHTML = html;
4185  },
4186
4187  // Set all loop helper values back
4188  reset: function(){
4189    this.nextChange = 0;
4190    this.prevChange = this.player.duration();
4191    this.firstActiveIndex = 0;
4192    this.lastActiveIndex = 0;
4193  }
4194
4195});
4196
4197// Create specific track types
4198_V_.CaptionsTrack = _V_.Track.extend({
4199  kind: "captions"
4200});
4201
4202_V_.SubtitlesTrack = _V_.Track.extend({
4203  kind: "subtitles"
4204});
4205
4206_V_.ChaptersTrack = _V_.Track.extend({
4207  kind: "chapters"
4208});
4209
4210
4211/* Text Track Display
4212================================================================================ */
4213// Global container for both subtitle and captions text. Simple div container.
4214_V_.TextTrackDisplay = _V_.Component.extend({
4215
4216  createElement: function(){
4217    return this._super("div", {
4218      className: "vjs-text-track-display"
4219    });
4220  }
4221
4222});
4223
4224/* Text Track Menu Items
4225================================================================================ */
4226_V_.TextTrackMenuItem = _V_.MenuItem.extend({
4227
4228  init: function(player, options){
4229    var track = this.track = options.track;
4230
4231    // Modify options for parent MenuItem class's init.
4232    options.label = track.label;
4233    options.selected = track["default"];
4234    this._super(player, options);
4235
4236    this.player.addEvent(track.kind + "trackchange", _V_.proxy(this, this.update));
4237  },
4238
4239  onClick: function(){
4240    this._super();
4241    this.player.showTextTrack(this.track.id, this.track.kind);
4242  },
4243
4244  update: function(){
4245    if (this.track.mode == 2) {
4246      this.selected(true);
4247    } else {
4248      this.selected(false);
4249    }
4250  }
4251
4252});
4253
4254_V_.OffTextTrackMenuItem = _V_.TextTrackMenuItem.extend({
4255
4256  init: function(player, options){
4257    // Create pseudo track info
4258    // Requires options.kind
4259    options.track = { kind: options.kind, player: player, label: "Off" }
4260    this._super(player, options);
4261  },
4262
4263  onClick: function(){
4264    this._super();
4265    this.player.showTextTrack(this.track.id, this.track.kind);
4266  },
4267
4268  update: function(){
4269    var tracks = this.player.textTracks,
4270        i=0, j=tracks.length, track,
4271        off = true;
4272
4273    for (;i<j;i++) {
4274      track = tracks[i];
4275      if (track.kind == this.track.kind && track.mode == 2) {
4276        off = false;
4277      }
4278    }
4279
4280    if (off) {
4281      this.selected(true);
4282    } else {
4283      this.selected(false);
4284    }
4285  }
4286
4287});
4288
4289/* Captions Button
4290================================================================================ */
4291_V_.TextTrackButton = _V_.Button.extend({
4292
4293  init: function(player, options){
4294    this._super(player, options);
4295
4296    this.menu = this.createMenu();
4297
4298    if (this.items.length === 0) {
4299      this.hide();
4300    }
4301  },
4302
4303  createMenu: function(){
4304    var menu = new _V_.Menu(this.player);
4305
4306    // Add a title list item to the top
4307    menu.el.appendChild(_V_.createElement("li", {
4308      className: "vjs-menu-title",
4309      innerHTML: _V_.uc(this.kind)
4310    }));
4311
4312    // Add an OFF menu item to turn all tracks off
4313    menu.addItem(new _V_.OffTextTrackMenuItem(this.player, { kind: this.kind }))
4314
4315    this.items = this.createItems();
4316
4317    // Add menu items to the menu
4318    this.each(this.items, function(item){
4319      menu.addItem(item);
4320    });
4321
4322    // Add list to element
4323    this.addComponent(menu);
4324
4325    return menu;
4326  },
4327
4328  // Create a menu item for each text track
4329  createItems: function(){
4330    var items = [];
4331    this.each(this.player.textTracks, function(track){
4332      if (track.kind === this.kind) {
4333        items.push(new _V_.TextTrackMenuItem(this.player, {
4334          track: track
4335        }));
4336      }
4337    });
4338    return items;
4339  },
4340
4341  buildCSSClass: function(){
4342    return this.className + " vjs-menu-button " + this._super();
4343  },
4344
4345  // Focus - Add keyboard functionality to element
4346  onFocus: function(){
4347    // Show the menu, and keep showing when the menu items are in focus
4348    this.menu.lockShowing();
4349    // this.menu.el.style.display = "block";
4350
4351    // When tabbing through, the menu should hide when focus goes from the last menu item to the next tabbed element.
4352    _V_.one(this.menu.el.childNodes[this.menu.el.childNodes.length - 1], "blur", this.proxy(function(){
4353      this.menu.unlockShowing();
4354    }));
4355  },
4356  // Can't turn off list display that we turned on with focus, because list would go away.
4357  onBlur: function(){},
4358
4359  onClick: function(){
4360    // When you click the button it adds focus, which will show the menu indefinitely.
4361    // So we'll remove focus when the mouse leaves the button.
4362    // Focus is needed for tab navigation.
4363    this.one("mouseout", this.proxy(function(){
4364      this.menu.unlockShowing();
4365      this.el.blur();
4366    }));
4367  }
4368
4369});
4370
4371_V_.CaptionsButton = _V_.TextTrackButton.extend({
4372  kind: "captions",
4373  buttonText: "Captions",
4374  className: "vjs-captions-button"
4375});
4376
4377_V_.SubtitlesButton = _V_.TextTrackButton.extend({
4378  kind: "subtitles",
4379  buttonText: "Subtitles",
4380  className: "vjs-subtitles-button"
4381});
4382
4383// Chapters act much differently than other text tracks
4384// Cues are navigation vs. other tracks of alternative languages
4385_V_.ChaptersButton = _V_.TextTrackButton.extend({
4386  kind: "chapters",
4387  buttonText: "Chapters",
4388  className: "vjs-chapters-button",
4389
4390  // Create a menu item for each text track
4391  createItems: function(chaptersTrack){
4392    var items = [];
4393
4394    this.each(this.player.textTracks, function(track){
4395      if (track.kind === this.kind) {
4396        items.push(new _V_.TextTrackMenuItem(this.player, {
4397          track: track
4398        }));
4399      }
4400    });
4401
4402    return items;
4403  },
4404
4405  createMenu: function(){
4406    var tracks = this.player.textTracks,
4407        i = 0,
4408        j = tracks.length,
4409        track, chaptersTrack,
4410        items = this.items = [];
4411
4412    for (;i<j;i++) {
4413      track = tracks[i];
4414      if (track.kind == this.kind && track["default"]) {
4415        if (track.readyState < 2) {
4416          this.chaptersTrack = track;
4417          track.addEvent("loaded", this.proxy(this.createMenu));
4418          return;
4419        } else {
4420          chaptersTrack = track;
4421          break;
4422        }
4423      }
4424    }
4425
4426    var menu = this.menu = new _V_.Menu(this.player);
4427
4428    menu.el.appendChild(_V_.createElement("li", {
4429      className: "vjs-menu-title",
4430      innerHTML: _V_.uc(this.kind)
4431    }));
4432
4433    if (chaptersTrack) {
4434      var cues = chaptersTrack.cues,
4435          i = 0, j = cues.length, cue, mi;
4436
4437      for (;i<j;i++) {
4438        cue = cues[i];
4439
4440        mi = new _V_.ChaptersTrackMenuItem(this.player, {
4441          track: chaptersTrack,
4442          cue: cue
4443        });
4444
4445        items.push(mi);
4446
4447        menu.addComponent(mi);
4448      }
4449    }
4450
4451    // Add list to element
4452    this.addComponent(menu);
4453
4454    if (this.items.length > 0) {
4455      this.show();
4456    }
4457
4458    return menu;
4459  }
4460
4461});
4462
4463_V_.ChaptersTrackMenuItem = _V_.MenuItem.extend({
4464
4465  init: function(player, options){
4466    var track = this.track = options.track,
4467        cue = this.cue = options.cue,
4468        currentTime = player.currentTime();
4469
4470    // Modify options for parent MenuItem class's init.
4471    options.label = cue.text;
4472    options.selected = (cue.startTime <= currentTime && currentTime < cue.endTime);
4473    this._super(player, options);
4474
4475    track.addEvent("cuechange", _V_.proxy(this, this.update));
4476  },
4477
4478  onClick: function(){
4479    this._super();
4480    this.player.currentTime(this.cue.startTime);
4481    this.update(this.cue.startTime);
4482  },
4483
4484  update: function(time){
4485    var cue = this.cue,
4486        currentTime = this.player.currentTime();
4487
4488    // _V_.log(currentTime, cue.startTime);
4489    if (cue.startTime <= currentTime && currentTime < cue.endTime) {
4490      this.selected(true);
4491    } else {
4492      this.selected(false);
4493    }
4494  }
4495
4496});
4497
4498// Add Buttons to controlBar
4499_V_.merge(_V_.ControlBar.prototype.options.components, {
4500  "subtitlesButton": {},
4501  "captionsButton": {},
4502  "chaptersButton": {}
4503});
4504
4505// _V_.Cue = _V_.Component.extend({
4506//   init: function(player, options){
4507//     this._super(player, options);
4508//   }
4509// });// Automatically set up any tags that have a data-setup attribute
4510_V_.autoSetup = function(){
4511  var options, vid, player,
4512      vids = document.getElementsByTagName("video");
4513
4514  // Check if any media elements exist
4515  if (vids && vids.length > 0) {
4516
4517    for (var i=0,j=vids.length; i<j; i++) {
4518      vid = vids[i];
4519
4520      // Check if element exists, has getAttribute func.
4521      // IE seems to consider typeof el.getAttribute == "object" instead of "function" like expected, at least when loading the player immediately.
4522      if (vid && vid.getAttribute) {
4523
4524        // Make sure this player hasn't already been set up.
4525        if (vid.player === undefined) {
4526          options = vid.getAttribute("data-setup");
4527
4528          // Check if data-setup attr exists. 
4529          // We only auto-setup if they've added the data-setup attr.
4530          if (options !== null) {
4531
4532            // Parse options JSON
4533            // If empty string, make it a parsable json object.
4534            options = JSON.parse(options || "{}");
4535
4536            // Create new video.js instance.
4537            player = _V_(vid, options);
4538          }
4539        }
4540
4541      // If getAttribute isn't defined, we need to wait for the DOM.
4542      } else {
4543        _V_.autoSetupTimeout(1);
4544        break;
4545      }
4546    }
4547
4548  // No videos were found, so keep looping unless page is finisehd loading.
4549  } else if (!_V_.windowLoaded) {
4550    _V_.autoSetupTimeout(1);
4551  }
4552};
4553
4554// Pause to let the DOM keep processing
4555_V_.autoSetupTimeout = function(wait){
4556  setTimeout(_V_.autoSetup, wait);
4557};
4558
4559_V_.addEvent(window, "load", function(){
4560  _V_.windowLoaded = true;
4561});
4562
4563// Run Auto-load players
4564_V_.autoSetup();
4565// Expose to global
4566window.VideoJS = window._V_ = VideoJS;
4567
4568// End self-executing function
4569})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.