PageSourceSearch

https://collections.stormking.org/assets/videojs/video.js

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