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