2 Video.js - HTML5 Video Player
6 This file is part of Video.js. Copyright 2011 Zencoder, Inc.
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.
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.
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/>.
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");
27 var VideoJS = function(id
, addOptions
, ready
){
28 var tag
; // Element of ID
30 // Allow for element or ID to be passed in
32 if (typeof id
== "string") {
34 // Adjust for jQuery ID syntax
35 if (id
.indexOf("#") === 0) {
39 // If a player instance has already been created for this ID return it.
40 if (_V_
.players
[id
]) {
41 return _V_
.players
[id
];
43 // Otherwise get element for ID
48 // ID is a media element
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
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
);
66 // CDN Version. Used to target right flash swf.
73 // Default order of fallback technology
74 techOrder
: ["html5","flash"],
75 // techOrder: ["flash","html5"],
78 flash
: { swf
: "http://vjs.zencdn.net/c/video-js.swf" },
80 // Default of web browser is 300x150. Should rely on source width/height.
84 // defaultVolume: 0.85,
85 defaultVolume
: 0.00, // The freakin seaguls are driving me crazy!
87 // Included control sets
93 "subtitlesDisplay": {}
100 // { name: "controlBar", options: {
103 // "fullscreenToggle",
104 // "currentTimeDisplay",
106 // "durationDisplay",
107 // "remainingTimeDisplay",
108 // { name: "progressControl", options: {
110 // { name: "seekBar", options: {
112 // "loadProgressBar",
113 // "playProgressBar",
119 // { name: "volumeControl", options: {
121 // { name: "volumeBar", options: {
132 // "subtitlesDisplay"/*, "replay"*/
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"
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");
146 // Check if any media elements exist
147 if (vids
&& vids
.length
> 0) {
149 for (var i
=0,j
=vids
.length
; i
<j
; i
++) {
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
) {
156 // Make sure this player hasn't already been set up.
157 if (vid
.player
=== undefined) {
158 options
= vid
.getAttribute("data-setup");
160 // Check if data-setup attr exists.
161 // We only auto-setup if they've added the data-setup attr.
162 if (options
!== null) {
164 // Parse options JSON
165 // If empty string, make it a parsable json object.
166 options
= JSON
.parse(options
|| "{}");
168 // Create new video.js instance.
169 player
= _V_(vid
, options
);
173 // If getAttribute isn't defined, we need to wait for the DOM.
175 _V_
.autoSetupTimeout(1);
180 // No videos were found, so keep looping unless page is finisehd loading.
181 } else if (!_V_
.windowLoaded
) {
182 _V_
.autoSetupTimeout(1);
186 // Pause to let the DOM keep processing
187 _V_
.autoSetupTimeout = function(wait
){
188 setTimeout(_V_
.autoSetup
, wait
);
190 _V_
.merge = function(obj1
, obj2
, safe
){
191 // Make sure second object exists
192 if (!obj2
) { obj2
= {}; };
194 for (var attrname
in obj2
){
195 if (obj2
.hasOwnProperty(attrname
) && (!safe
|| !obj1
.hasOwnProperty(attrname
))) { obj1
[attrname
]=obj2
[attrname
]; }
199 _V_
.extend = function(obj
){ this.merge(this, obj
, true); };
202 tech
: {}, // Holder for playback technology settings
203 controlSets
: {}, // Holder for control set definitions
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]; }
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]; }
221 testVid
: document
.createElement("video"),
222 ua
: navigator
.userAgent
,
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
);
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
]);
241 el: function(id
){ return document
.getElementById(id
); },
242 createElement: function(tagName
, attributes
){
243 var el
= document
.createElement(tagName
),
245 for (attrname
in attributes
){
246 if (attributes
.hasOwnProperty(attrname
)) {
247 if (attrname
.indexOf("-") !== -1) {
248 el
.setAttribute(attrname
, attributes
[attrname
]);
250 el
[attrname
] = attributes
[attrname
];
257 insertFirst: function(node
, parent
){
258 if (parent
.firstChild
) {
259 parent
.insertBefore(node
, parent
.firstChild
);
261 parent
.appendChild(node
);
265 addClass: function(element
, classToAdd
){
266 if ((" "+element
.className
+" ").indexOf(" "+classToAdd
+" ") == -1) {
267 element
.className
= element
.className
=== "" ? classToAdd
: element
.className
+ " " + classToAdd
;
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(" ");
278 remove: function(item
, array
){
280 var i
= array
.indexOf(item
);
282 return array
.splice(i
, 1)
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; };
291 // Turn off text selection blocking
292 unblockTextSelection: function(){ document
.onselectstart = function () { return true; }; },
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);
304 // Check if we need to show hours
305 h
= (h
> 0 || gh
> 0) ? h
+ ":" : "";
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
) + ":";
311 // Check if leading zero is need for seconds
312 s
= (s
< 10) ? "0" + s
: s
;
317 capitalize: function(string
){
318 return string
.charAt(0).toUpperCase() + string
.slice(1);
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
));
326 getComputedStyleValue: function(element
, style
){
327 return window
.getComputedStyle(element
, null).getPropertyValue(style
);
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
);
336 isEmpty: function(object
) {
337 for (var prop
in object
) {
343 // Mimic HTML5 TimeRange Spec.
344 createTimeRange: function(start
, end
){
347 start: function() { return start
; },
348 end: function() { return end
; }
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
360 // Returns the cache object where data for the element is stored
361 getData: function(elem
){
362 var id
= elem
[_V_
.expando
];
364 id
= elem
[_V_
.expando
] = _V_
.guid
++;
367 return _V_
.cache
[id
];
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
];
373 // Remove all stored data
374 delete _V_
.cache
[id
];
375 // Remove the expando property from the DOM node
377 delete elem
[_V_
.expando
];
379 if (elem
.removeAttribute
) {
380 elem
.removeAttribute(_V_
.expando
);
382 // IE doesn't appear to support removeAttribute on the document element
383 elem
[_V_
.expando
] = null;
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
);
399 // Give the new function the same ID
400 // (so that they are equivalent and can be easily removed)
406 get: function(url
, onSuccess
, onError
){
407 // if (netscape.security.PrivilegeManager.enablePrivilege) {
408 // netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
411 var local
= (url
.indexOf("file:") == 0 || (window
.location
.href
.indexOf("file:") == 0 && url
.indexOf("http:") == -1));
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.");
422 var request
= new XMLHttpRequest();
425 request
.open("GET", url
);
427 _V_
.log("VideoJS XMLHttpRequest (open)", e
);
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
);
447 _V_
.log("VideoJS XMLHttpRequest (send)", e
);
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; }
461 localStorage
[key
] = value
;
463 if (e
.code
== 22 || e
.code
== 1014) { // Webkit == 22 / Firefox == 1014
464 _V_
.log("LocalStorage Full (VideoJS)", e
);
466 _V_
.log("LocalStorage Error (VideoJS)", e
);
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
);
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
));
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
={};}})());
490 // getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
491 if ("getBoundingClientRect" in document
.documentElement
) {
492 _V_
.findPosX = function(el
) {
496 box
= el
.getBoundingClientRect();
499 if (!box
) { return 0; }
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
;
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"));
520 curleft
+= el
.offsetLeft
;
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;};})();
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;
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
]) ?
539 var tmp
= this._super
;
540 this._super
= _super
[name
];
541 var ret
= fn
.apply(this, arguments
);
545 })(name
, prop
[name
]) :
549 if ( !initializing
&& this.init
) {
550 return this.init
.apply(this, arguments
);
552 // Attempting to recreate accessing function form of class.
553 } else if (!initializing
) {
554 return arguments
.callee
.prototype.init()
557 Class
.prototype = prototype;
558 Class
.constructor = Class
;
559 Class
.extend
= arguments
.callee
;
564 /* Player Component- Base class for all UI objects
565 ================================================================================ */
566 _V_
.Component
= _V_
.Class
.extend({
568 init: function(player
, options
){
569 this.player
= player
;
571 // Allow for overridding default component options
572 options
= this.options
= _V_
.merge(this.options
|| {}, options
);
574 // Create element if one wasn't provided in options
576 this.el
= options
.el
;
578 this.el
= this.createElement();
581 // Add any components in options
582 this.initComponents();
585 destroy: function(){},
587 createElement: function(type
, attrs
){
588 return _V_
.createElement(type
|| "div", attrs
);
591 buildCSSClass: function(){
592 // Child classes can include a function that does:
593 // return "CLASS NAME" + this._super();
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
){
603 // Allow waiting to add components until a specific event is called
604 var tempAdd
= this.proxy(function(){
605 this.addComponent(name
, opts
);
608 if (opts
.loadEvent
) {
609 this.one(opts
.loadEvent
, tempAdd
)
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
;
623 // Make sure options is at least an empty object to protect against errors
624 options
= options
|| {};
626 // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
627 componentClass
= options
.componentClass
|| _V_
.capitalize(name
);
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
);
633 // Add the UI object's element to the container div (box)
634 this.el
.appendChild(component
.el
);
636 // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
637 this[name
] = component
;
641 ================================================================================ */
643 this.el
.style
.display
= "block";
647 this.el
.style
.display
= "none";
651 this.removeClass("vjs-fade-out");
652 this.addClass("vjs-fade-in");
656 this.removeClass("vjs-fade-in");
657 this.addClass("vjs-fade-out");
660 addClass: function(classToAdd
){
661 _V_
.addClass(this.el
, classToAdd
);
664 removeClass: function(classToRemove
){
665 _V_
.removeClass(this.el
, classToRemove
);
669 ================================================================================ */
670 addEvent: function(type
, fn
){
671 return _V_
.addEvent(this.el
, type
, _V_
.proxy(this, fn
));
673 removeEvent: function(type
, fn
){
674 return _V_
.removeEvent(this.el
, type
, fn
);
676 triggerEvent: function(type
, e
){
677 return _V_
.triggerEvent(this.el
, type
, e
);
679 one: function(type
, fn
) {
680 _V_
.one
.call(this, this.el
, type
, fn
);
683 /* Ready - Trigger functions when component is ready
684 ================================================================================ */
686 if (!fn
) return this;
691 if (this.readyQueue
=== undefined) {
692 this.readyQueue
= [];
694 this.readyQueue
.push(fn
);
700 triggerReady: function(){
702 if (this.readyQueue
&& this.readyQueue
.length
> 0) {
703 // Call all functions in ready queue
704 this.each(this.readyQueue
, function(fn
){
709 this.readyQueue
= [];
714 ================================================================================ */
715 each: function(arr
, fn
){ _V_
.each
.call(this, arr
, fn
); },
717 eachProp: function(obj
, fn
){ _V_
.eachProp
.call(this, obj
, fn
); },
719 extend: function(obj
){ _V_
.merge(this, obj
) },
721 // More easily attach 'this' to functions
722 proxy: function(fn
){ return _V_
.proxy(this, fn
); }
724 });/* Control - Base class for all control elements
725 ================================================================================ */
726 _V_
.Control
= _V_
.Component
.extend({
728 buildCSSClass: function(){
729 return "vjs-control " + this._super();
734 /* Button - Base class for all buttons
735 ================================================================================ */
736 _V_
.Button
= _V_
.Control
.extend({
738 init: function(player
, options
){
739 this._super(player
, options
);
741 this.addEvent("click", this.onClick
);
742 this.addEvent("focus", this.onFocus
);
743 this.addEvent("blur", this.onBlur
);
746 createElement: function(type
, attrs
){
747 // Add standard Aria and Tabindex info
749 className
: this.buildCSSClass(),
750 innerHTML
: '<div><span class="vjs-control-text">' + (this.buttonText
|| "Need Text") + '</span></div>',
755 return this._super(type
, attrs
);
758 // Click - Override with specific functionality for button
759 onClick: function(){},
761 // Focus - Add keyboard functionality to element
763 _V_
.addEvent(document
, "keyup", _V_
.proxy(this, this.onKeyPress
));
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();
775 // Blur - Remove keyboard triggers
777 _V_
.removeEvent(document
, "keyup", _V_
.proxy(this, this.onKeyPress
));
783 ================================================================================ */
784 _V_
.PlayButton
= _V_
.Button
.extend({
788 buildCSSClass: function(){
789 return "vjs-play-button " + this._super();
799 ================================================================================ */
800 _V_
.PauseButton
= _V_
.Button
.extend({
804 buildCSSClass: function(){
805 return "vjs-pause-button " + this._super();
814 /* Play Toggle - Play or Pause Media
815 ================================================================================ */
816 _V_
.PlayToggle
= _V_
.Button
.extend({
820 init: function(player
, options
){
821 this._super(player
, options
);
823 player
.addEvent("play", _V_
.proxy(this, this.onPlay
));
824 player
.addEvent("pause", _V_
.proxy(this, this.onPause
));
827 buildCSSClass: function(){
828 return "vjs-play-control " + this._super();
831 // OnClick - Toggle between play and pause
833 if (this.player
.paused()) {
840 // OnPlay - Add the vjs-playing class to the element so it can change appearance
842 _V_
.removeClass(this.el
, "vjs-paused");
843 _V_
.addClass(this.el
, "vjs-playing");
846 // OnPause - Add the vjs-paused class to the element so it can change appearance
848 _V_
.removeClass(this.el
, "vjs-playing");
849 _V_
.addClass(this.el
, "vjs-paused");
855 /* Fullscreen Toggle Behaviors
856 ================================================================================ */
857 _V_
.FullscreenToggle
= _V_
.Button
.extend({
859 buttonText
: "Fullscreen",
861 buildCSSClass: function(){
862 return "vjs-fullscreen-control " + this._super();
866 if (!this.player
.isFullScreen
) {
867 this.player
.requestFullScreen();
869 this.player
.cancelFullScreen();
876 ================================================================================ */
877 _V_
.BigPlayButton
= _V_
.Button
.extend({
878 init: function(player
, options
){
879 this._super(player
, options
);
881 player
.addEvent("play", _V_
.proxy(this, this.hide
));
882 player
.addEvent("ended", _V_
.proxy(this, this.show
));
885 createElement: function(){
886 return this._super("div", {
887 className
: "vjs-big-play-button",
888 innerHTML
: "<span></span>"
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);
903 ================================================================================ */
904 _V_
.LoadingSpinner
= _V_
.Component
.extend({
905 init: function(player
, options
){
906 this._super(player
, options
);
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
));
912 player
.addEvent("seeking", _V_
.proxy(this, this.show
));
913 player
.addEvent("error", _V_
.proxy(this, this.show
));
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));
919 player
.addEvent("waiting", _V_
.proxy(this, this.show
));
922 createElement: function(){
924 var classNameSpinner
, innerHtmlSpinner
;
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")
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>";
934 classNameSpinner
= "vjs-loading-spinner-fallback";
935 innerHtmlSpinner
= "";
938 return this._super("div", {
939 className
: classNameSpinner
,
940 innerHTML
: innerHtmlSpinner
946 ================================================================================ */
947 _V_
.ControlBar
= _V_
.Component
.extend({
953 "fullscreenToggle": {},
954 "currentTimeDisplay": {},
956 "durationDisplay": {},
957 "remainingTimeDisplay": {},
958 "progressControl": {},
964 init: function(player
, options
){
965 this._super(player
, options
);
967 player
.addEvent("play", this.proxy(function(){
969 this.player
.addEvent("mouseover", this.proxy(this.fadeIn
));
970 this.player
.addEvent("mouseout", this.proxy(this.fadeOut
));
974 createElement: function(){
975 return _V_
.createElement("div", {
976 className
: "vjs-controls"
982 this.player
.triggerEvent("controlsvisible");
987 this.player
.triggerEvent("controlshidden");
992 ================================================================================ */
993 _V_
.CurrentTimeDisplay
= _V_
.Component
.extend({
995 init: function(player
, options
){
996 this._super(player
, options
);
998 player
.addEvent("timeupdate", _V_
.proxy(this, this.updateContent
));
1001 createElement: function(){
1002 var el
= this._super("div", {
1003 className
: "vjs-current-time vjs-time-controls vjs-control"
1006 this.content
= _V_
.createElement("div", {
1007 className
: "vjs-current-time-display",
1011 el
.appendChild(_V_
.createElement("div").appendChild(this.content
));
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());
1023 _V_
.DurationDisplay
= _V_
.Component
.extend({
1025 init: function(player
, options
){
1026 this._super(player
, options
);
1028 player
.addEvent("timeupdate", _V_
.proxy(this, this.updateContent
));
1031 createElement: function(){
1032 var el
= this._super("div", {
1033 className
: "vjs-duration vjs-time-controls vjs-control"
1036 this.content
= _V_
.createElement("div", {
1037 className
: "vjs-duration-display",
1041 el
.appendChild(_V_
.createElement("div").appendChild(this.content
));
1045 updateContent: function(){
1046 if (this.player
.duration()) { this.content
.innerHTML
= _V_
.formatTime(this.player
.duration()); }
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({
1054 createElement: function(){
1055 return this._super("div", {
1056 className
: "vjs-time-divider",
1057 innerHTML
: '<div><span>/</span></div>'
1063 _V_
.RemainingTimeDisplay
= _V_
.Component
.extend({
1065 init: function(player
, options
){
1066 this._super(player
, options
);
1068 player
.addEvent("timeupdate", _V_
.proxy(this, this.updateContent
));
1071 createElement: function(){
1072 var el
= this._super("div", {
1073 className
: "vjs-remaining-time vjs-time-controls vjs-control"
1076 this.content
= _V_
.createElement("div", {
1077 className
: "vjs-remaining-time-display",
1081 el
.appendChild(_V_
.createElement("div").appendChild(this.content
));
1085 updateContent: function(){
1086 if (this.player
.duration()) { this.content
.innerHTML
= "-"+_V_
.formatTime(this.player
.remainingTime()); }
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());
1095 /* Slider - Parent for seek bar and volume slider
1096 ================================================================================ */
1097 _V_
.Slider
= _V_
.Component
.extend({
1099 init: function(player
, options
){
1100 this._super(player
, options
);
1102 player
.addEvent(this.playerEvent
, _V_
.proxy(this, this.update
));
1104 this.addEvent("mousedown", this.onMouseDown
);
1105 this.addEvent("focus", this.onFocus
);
1106 this.addEvent("blur", this.onBlur
);
1108 this.player
.addEvent("controlsvisible", this.proxy(this.update
));
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));
1116 createElement: function(type
, attrs
) {
1121 "aria-valuemax": 100,
1125 return this._super(type
, attrs
);
1128 onMouseDown: function(event
){
1129 event
.preventDefault();
1130 _V_
.blockTextSelection();
1132 _V_
.addEvent(document
, "mousemove", _V_
.proxy(this, this.onMouseMove
));
1133 _V_
.addEvent(document
, "mouseup", _V_
.proxy(this, this.onMouseUp
));
1135 this.onMouseMove(event
);
1138 onMouseUp: function(event
) {
1139 _V_
.unblockTextSelection();
1140 _V_
.removeEvent(document
, "mousemove", this.onMouseMove
, false);
1141 _V_
.removeEvent(document
, "mouseup", this.onMouseUp
, false);
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();
1152 progress
= this.getPercent();
1153 handle
= this.handle
,
1156 // Protect against no duration and other division issues
1157 if (isNaN(progress
)) { progress
= 0; }
1159 barProgress
= progress
;
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.
1166 boxWidth
= box
.offsetWidth
,
1168 handleWidth
= handle
.el
.offsetWidth
,
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,
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
;
1178 // Adjust the progress that we'll use to set widths to the new adjusted box width
1179 adjustedProgress
= progress
* boxAdjustedPercent
,
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);
1184 // Move the handle from the left based on the adjected progress
1185 handle
.el
.style
.left
= _V_
.round(adjustedProgress
* 100, 2) + "%";
1188 // Set the new bar width
1189 bar
.el
.style
.width
= _V_
.round(barProgress
* 100, 2) + "%";
1192 calculateDistance: function(event
){
1194 boxX
= _V_
.findPosX(box
),
1195 boxW
= box
.offsetWidth
,
1196 handle
= this.handle
;
1199 var handleW
= handle
.el
.offsetWidth
;
1201 // Adjusted X and Width, so handle doesn't go outside the bar
1202 boxX
= boxX
+ (handleW
/ 2);
1203 boxW
= boxW
- handleW
;
1206 // Percent that the click is through the adjusted area
1207 return Math
.max(0, Math
.min(1, (event
.pageX
- boxX
) / boxW
));
1210 onFocus: function(event
){
1211 _V_
.addEvent(document
, "keyup", _V_
.proxy(this, this.onKeyPress
));
1214 onKeyPress: function(event
){
1215 if (event
.which
== 37) { // Left Arrow
1216 event
.preventDefault();
1218 } else if (event
.which
== 39) { // Right Arrow
1219 event
.preventDefault();
1224 onBlur: function(event
){
1225 _V_
.removeEvent(document
, "keyup", _V_
.proxy(this, this.onKeyPress
));
1231 ================================================================================ */
1233 // Progress Control: Seek, Load Progress, and Play Progress
1234 _V_
.ProgressControl
= _V_
.Component
.extend({
1242 createElement: function(){
1243 return this._super("div", {
1244 className
: "vjs-progress-control vjs-control"
1250 // Seek Bar and holder for the progress bars
1251 _V_
.SeekBar
= _V_
.Slider
.extend({
1255 "loadProgressBar": {},
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" }
1263 playerEvent
: "timeupdate",
1265 init: function(player
, options
){
1266 this._super(player
, options
);
1269 createElement: function(){
1270 return this._super("div", {
1271 className
: "vjs-progress-holder"
1275 getPercent: function(){
1276 return this.player
.currentTime() / this.player
.duration();
1279 onMouseDown: function(event
){
1282 this.player
.scrubbing
= true;
1284 this.videoWasPlaying
= !this.player
.paused();
1285 this.player
.pause();
1288 onMouseMove: function(event
){
1289 var newTime
= this.calculateDistance(event
) * this.player
.duration();
1291 // Don't let video end while scrubbing.
1292 if (newTime
== this.player
.duration()) { newTime
= newTime
- 0.1; }
1294 // Set new time (tell player to seek to new time)
1295 this.player
.currentTime(newTime
);
1298 onMouseUp: function(event
){
1301 this.player
.scrubbing
= false;
1302 if (this.videoWasPlaying
) {
1307 stepForward: function(){
1308 this.player
.currentTime(this.player
.currentTime() + 1);
1311 stepBack: function(){
1312 this.player
.currentTime(this.player
.currentTime() - 1);
1317 // Load Progress Bar
1318 _V_
.LoadProgressBar
= _V_
.Component
.extend({
1320 init: function(player
, options
){
1321 this._super(player
, options
);
1322 player
.addEvent("progress", _V_
.proxy(this, this.update
));
1325 createElement: function(){
1326 return this._super("div", {
1327 className
: "vjs-load-progress",
1328 innerHTML
: '<span class="vjs-control-text">Loaded: 0%</span>'
1333 if (this.el
.style
) { this.el
.style
.width
= _V_
.round(this.player
.bufferedPercent() * 100, 2) + "%"; }
1338 // Play Progress Bar
1339 _V_
.PlayProgressBar
= _V_
.Component
.extend({
1341 createElement: function(){
1342 return this._super("div", {
1343 className
: "vjs-play-progress",
1344 innerHTML
: '<span class="vjs-control-text">Progress: 0%</span>'
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({
1355 createElement: function(){
1356 return this._super("div", {
1357 className
: "vjs-seek-handle",
1358 innerHTML
: '<span class="vjs-control-text">00:00</span>'
1365 ================================================================================ */
1366 // Progress Control: Seek, Load Progress, and Play Progress
1367 _V_
.VolumeControl
= _V_
.Component
.extend({
1375 createElement: function(){
1376 return this._super("div", {
1377 className
: "vjs-volume-control vjs-control"
1383 _V_
.VolumeBar
= _V_
.Slider
.extend({
1387 "bar": { componentClass
: "VolumeLevel" },
1388 "handle": { componentClass
: "VolumeHandle" }
1392 playerEvent
: "volumechange",
1394 createElement: function(){
1395 return this._super("div", {
1396 className
: "vjs-volume-bar"
1400 onMouseMove: function(event
) {
1401 this.player
.volume(this.calculateDistance(event
));
1404 getPercent: function(){
1405 return this.player
.volume();
1408 stepForward: function(){
1409 this.player
.volume(this.player
.volume() + 0.1);
1412 stepBack: function(){
1413 this.player
.volume(this.player
.volume() - 0.1);
1417 _V_
.VolumeLevel
= _V_
.Component
.extend({
1419 createElement: function(){
1420 return this._super("div", {
1421 className
: "vjs-volume-level",
1422 innerHTML
: '<span class="vjs-control-text"></span>'
1428 _V_
.VolumeHandle
= _V_
.Component
.extend({
1430 createElement: function(){
1431 return this._super("div", {
1432 className
: "vjs-volume-handle",
1433 innerHTML
: '<span class="vjs-control-text"></span>'
1435 // role: "slider", "aria-valuenow": 0, "aria-valuemin": 0, "aria-valuemax": 100
1441 _V_
.MuteToggle
= _V_
.Button
.extend({
1443 init: function(player
, options
){
1444 this._super(player
, options
);
1446 player
.addEvent("volumechange", _V_
.proxy(this, this.update
));
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>'
1456 onClick: function(event
){
1457 this.player
.muted( this.player
.muted() ? false : true );
1460 update: function(event
){
1461 var vol
= this.player
.volume(),
1464 if (vol
== 0 || this.player
.muted()) {
1466 } else if (vol
< 0.33) {
1468 } else if (vol
< 0.67) {
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
);
1476 _V_
.addClass(this.el
, "vjs-vol-"+level
);
1483 ================================================================================ */
1484 _V_
.Poster
= _V_
.Button
.extend({
1485 init: function(player
, options
){
1486 this._super(player
, options
);
1488 if (!this.player
.options
.poster
) {
1492 player
.addEvent("play", _V_
.proxy(this, this.hide
));
1495 createElement: function(){
1496 return _V_
.createElement("img", {
1497 className
: "vjs-poster",
1498 src
: this.player
.options
.poster
,
1500 // Don't want poster to be tabbable.
1505 onClick: function(){
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.
1518 // Base class for all track displays. Should not be instantiated on its own.
1519 _V_
.TextTrackDisplay
= _V_
.Component
.extend({
1521 init: function(player
, options
){
1522 this._super(player
, options
);
1524 player
.addEvent(this.trackType
+ "update", _V_
.proxy(this, this.update
));
1527 createElement: function(){
1528 return this._super("div", {
1529 className
: "vjs-" + this.trackType
1534 this.el
.innerHTML
= this.player
.textTrackValue(this.trackType
);
1539 _V_
.SubtitlesDisplay
= _V_
.TextTrackDisplay
.extend({
1541 trackType
: "subtitles"
1545 _V_
.CaptionsDisplay
= _V_
.TextTrackDisplay
.extend({
1547 trackType
: "captions"
1551 _V_
.ChaptersDisplay
= _V_
.TextTrackDisplay
.extend({
1553 trackType
: "chapters"
1557 _V_
.DescriptionsDisplay
= _V_
.TextTrackDisplay
.extend({
1559 trackType
: "descriptions"
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.
1566 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
1567 if (!Array
.prototype.indexOf
) {
1568 Array
.prototype.indexOf = function (searchElement
/*, fromIndex */ ) {
1570 if (this === void 0 || this === null) {
1571 throw new TypeError();
1573 var t
= Object(this);
1574 var len
= t
.length
>>> 0;
1579 if (arguments
.length
> 0) {
1580 n
= Number(arguments
[1]);
1581 if (n
!== n
) { // shortcut for verifying if it's NaN
1583 } else if (n
!== 0 && n
!== (1 / 0) && n
!== -(1 / 0)) {
1584 n
= (n
> 0 || -1) * Math
.floor(Math
.abs(n
));
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
) {
1602 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
1603 // if (!Array.prototype.lastIndexOf)
1605 // Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/)
1609 // if (this === void 0 || this === null)
1610 // throw new TypeError();
1612 // var t = Object(this);
1613 // var len = t.length >>> 0;
1618 // if (arguments.length > 1)
1620 // n = Number(arguments[1]);
1623 // else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0))
1624 // n = (n > 0 || -1) * Math.floor(Math.abs(n));
1628 // ? Math.min(n, len - 1)
1629 // : len - Math.abs(n);
1631 // for (; k >= 0; k--)
1633 // if (k in t && t[k] === searchElement)
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 ) {
1647 // Array.prototype.forEach = function( callback, thisArg ) {
1651 // if ( this == null ) {
1652 // throw new TypeError( " this is null or not defined" );
1655 // // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
1656 // var O = Object(this);
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;
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" );
1668 // // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
1676 // // 7. Repeat, while k < len
1677 // while( k < len ) {
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
1688 // // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
1689 // kValue = O[ Pk ];
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 );
1695 // // d. Increase k by 1.
1698 // // 8. return undefined
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) {
1712 // if (this == null) {
1713 // throw new TypeError(" this is null or not defined");
1716 // // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
1717 // var O = Object(this);
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;
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");
1729 // // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
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);
1741 // // 8. Repeat, while k < len
1744 // var kValue, mappedValue;
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
1753 // // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
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);
1760 // // iii. Call the DefineOwnProperty internal method of A with arguments
1761 // // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true},
1764 // // In browsers that support Object.defineProperty, use the following:
1765 // // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
1767 // // For best browser support, use the following:
1768 // A[ k ] = mappedValue;
1770 // // d. Increase k by 1.
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)
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.
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.
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
;
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
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
;
1813 for (var i
= 0, l
= handlersCopy
.length
; i
< l
; i
++) {
1814 handlersCopy
[i
].call(elem
, event
);
1820 // We need a place to store all our event data
1821 if (!data
.events
) { data
.events
= {}; }
1823 // And a place to store the handlers for this event type
1824 handlers
= data
.events
[type
];
1827 handlers
= data
.events
[ type
] = [];
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
);
1837 if (!fn
.guid
) { fn
.guid
= _V_
.guid
++; }
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; }
1847 // Are we removing all bound events?
1849 for (type
in data
.events
) {
1850 _V_
.cleanUpEvents(elem
, type
);
1855 // And a place to store the handlers for this event type
1856 handlers
= data
.events
[type
];
1858 // If no handlers exist, nothing to unbind
1859 if (!handlers
) { return; }
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);
1872 _V_
.cleanUpEvents(elem
, type
);
1875 cleanUpEvents: function(elem
, type
) {
1876 var data
= _V_
.getData(elem
);
1877 // Remove the events of a particular type if there are none left
1879 if (data
.events
[type
].length
=== 0) {
1880 delete data
.events
[type
];
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
);
1890 // Remove the events object if there are no types left
1891 if (_V_
.isEmpty(data
.events
)) {
1893 delete data
.handler
;
1896 // Finally remove the expando if there is no data left
1897 if (_V_
.isEmpty(data
)) {
1898 _V_
.removeData(elem
);
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
);
1909 for ( var i
= _V_
.Event
.props
.length
, prop
; i
; ) {
1910 prop
= _V_
.Event
.props
[ --i
];
1911 event
[prop
] = originalEvent
[prop
];
1914 // Fix target property, if necessary
1915 if (!event
.target
) { event
.target
= event
.srcElement
|| document
; }
1917 // check if target is a textnode (safari)
1918 if (event
.target
.nodeType
=== 3) { event
.target
= event
.target
.parentNode
; }
1920 // Add relatedTarget, if necessary
1921 if (!event
.relatedTarget
&& event
.fromElement
) {
1922 event
.relatedTarget
= event
.fromElement
=== event
.target
? event
.toElement
: event
.fromElement
;
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
;
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);
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
;
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
;
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 ) ));
1954 triggerEvent: function(elem
, event
) {
1955 var data
= _V_
.getData(elem
),
1956 parent
= elem
.parentNode
|| elem
.ownerDocument
,
1957 type
= event
.type
|| event
,
1960 if (data
) { handler
= data
.handler
}
1962 // Added in attion to book. Book code was broke.
1963 event
= typeof event
=== "object" ?
1964 event
[_V_
.expando
] ?
1966 new _V_
.Event(type
, event
) :
1967 new _V_
.Event(type
);
1971 handler
.call(elem
, event
);
1974 // Clean up the event in case it is being reused
1975 event
.result
= undefined;
1976 event
.target
= elem
;
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);
1983 // // We're at the top document so trigger the default action
1984 // } else if (!parent && !event.isDefaultPrevented()) {
1986 // var targetData = _V_.getData(event.target);
1987 // // log(targetData);
1988 // var targetHandler = targetData.handler;
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(){};
1997 // // Trigger the native event (click, focus, blur)
1998 // event.target[event.type]();
2000 // // Restore the handler
2001 // if (targetHandler) {
2002 // targetData.handler = targetHandler;
2008 one: function(elem
, type
, fn
) {
2009 _V_
.addEvent(elem
, type
, function(){
2010 _V_
.removeEvent(elem
, type
, arguments
.callee
)
2011 fn
.apply(this, arguments
);
2016 // Custom Event object for standardizing event objects between browsers.
2017 _V_
.Event = function(src
, props
){
2019 if (src
&& src
.type
) {
2020 this.originalEvent
= src
;
2021 this.type
= src
.type
;
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
;
2033 // Put explicitly provided properties onto the event object
2034 if (props
) { _V_
.merge(this, props
); }
2036 this.timeStamp
= (new Date
).getTime();
2039 this[_V_
.expando
] = true;
2042 _V_
.Event
.prototype = {
2043 preventDefault: function() {
2044 this.isDefaultPrevented
= returnTrue
;
2046 var e
= this.originalEvent
;
2049 // if preventDefault exists run it on the original event
2050 if (e
.preventDefault
) {
2052 // otherwise set the returnValue property of the original event to false (IE)
2054 e
.returnValue
= false;
2057 stopPropagation: function() {
2058 this.isPropagationStopped
= returnTrue
;
2060 var e
= this.originalEvent
;
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;
2067 stopImmediatePropagation: function() {
2068 this.isImmediatePropagationStopped
= returnTrue
;
2069 this.stopPropagation();
2071 isDefaultPrevented
: returnFalse
,
2072 isPropagationStopped
: returnFalse
,
2073 isImmediatePropagationStopped
: returnFalse
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(" ");
2077 function returnTrue(){ return true; }
2078 function returnFalse(){ return false; }
2080 // Javascript JSON implementation
2081 // (Parse Method Only)
2082 // https://github.com/douglascrockford/JSON-js/blob/master/json2.js
2085 if (!JSON
) { JSON
= {}; }
2088 var cx
= /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
2090 if (typeof JSON
.parse
!== 'function') {
2091 JSON
.parse = function (text
, reviver
) {
2094 function walk(holder
, key
) {
2095 var k
, v
, value
= holder
[key
];
2096 if (value
&& typeof value
=== 'object') {
2098 if (Object
.prototype.hasOwnProperty
.call(value
, k
)) {
2100 if (v
!== undefined) {
2108 return reviver
.call(holder
, key
, value
);
2110 text
= String(text
);
2112 if (cx
.test(text
)) {
2113 text
= text
.replace(cx
, function (a
) {
2115 ('0000' + a
.charCodeAt(0).toString(16)).slice(-4);
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, ''))) {
2124 j
= eval('(' + text
+ ')');
2126 return typeof reviver
=== 'function' ?
2127 walk({'': j
}, '') : j
;
2130 throw new SyntaxError('JSON.parse');
2134 /* UI Component- Base class for all UI objects
2135 ================================================================================ */
2136 _V_
.Player
= _V_
.Component
.extend({
2138 init: function(tag
, addOptions
, ready
){
2140 this.tag
= tag
; // Store the original tag used to set options
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'),
2147 // Browsers default to 300x150 if there's no width/height or video size data.
2148 initWidth
= width
|| 300,
2149 initHeight
= height
|| 150;
2151 // Make player findable on elements
2152 tag
.player
= el
.player
= this;
2154 // Add callback to ready queue
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.
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";
2168 // Make player easily findable by ID
2169 _V_
.players
[el
.id
] = this;
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");
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
2186 // Store controls setting, and then remove immediately so native controls don't flash.
2187 tag
.removeAttribute("controls");
2189 // Poster will be handled by a manual <img>
2190 tag
.removeAttribute("poster");
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
]);
2201 // Holder for playback tech components
2204 // Cache for video property values.
2207 this.addClass("vjs-paused");
2209 this.addEvent("ended", this.onEnded
);
2210 this.addEvent("play", this.onPlay
);
2211 this.addEvent("pause", this.onPause
);
2212 this.addEvent("error", this.onError
);
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();
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
];
2228 // Check if the browser supports this technology
2229 if (tech
.isSupported()) {
2230 this.loadTech(techName
);
2235 // Loop through playback technologies (HTML5, Flash) and check for support
2236 // Then load the best source.
2237 this.src(options
.sources
);
2241 // Cache for video property values.
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
]
2251 createElement: function(type
, options
){
2255 getVideoTagSettings: function(){
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;
2269 if (this.tag
.hasChildNodes()) {
2270 for (var c
,i
=0,j
=this.tag
.childNodes
;i
<j
.length
;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')
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")
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
){
2302 // Pause and remove current playback technology
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
);
2313 this.techName
= techName
;
2315 // Turn off API access because we're loading a new tech that might load asynchronously
2316 this.isReady
= false;
2318 var techReady = function(){
2319 this.player
.triggerReady();
2321 // Manually track progress in cases where the browser/flash player doesn't report it.
2322 if (!this.support
.progressEvent
) {
2323 this.player
.manualProgressOn();
2326 // Manually track timeudpates in cases where the browser/flash player doesn't report it.
2327 if (!this.support
.timeupdateEvent
) {
2328 this.player
.manualTimeUpdatesOn();
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
])
2336 if (source
.src
== this.values
.src
&& this.values
.currentTime
> 0) {
2337 techOptions
.startTime
= this.values
.currentTime
;
2340 this.values
.src
= source
.src
;
2343 // Initialize tech instance
2344 this.tech
= new _V_
[techName
](this, techOptions
);
2345 this.tech
.ready(techReady
);
2348 unloadTech: function(){
2349 this.tech
.destroy();
2351 // Turn off any manual progress or timeupdate tracking
2352 if (this.manualProgress
) { this.manualProgressOff(); }
2354 if (this.manualTimeUpdates
) { this.manualTimeUpdatesOff(); }
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")
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;
2380 // Trigger progress watching when a source begins loading
2381 this.trackProgress();
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(){
2388 // Remove this listener from the element
2389 this.removeEvent("progress", arguments
.callee
);
2391 // Update known progress support for this playback technology
2392 this.support
.progressEvent
= true;
2394 // Turn off manual progress tracking
2395 this.player
.manualProgressOff();
2399 manualProgressOff: function(){
2400 this.manualProgress
= false;
2401 this.stopTrackingProgress();
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
2417 stopTrackingProgress: function(){ clearInterval(this.progressInterval
); },
2419 /* Time Tracking -------------------------------------------------------------- */
2420 manualTimeUpdatesOn: function(){
2421 this.manualTimeUpdates
= true;
2423 this.addEvent("play", this.trackCurrentTime
);
2424 this.addEvent("pause", this.stopTrackingCurrentTime
);
2425 // timeupdate is also called by .currentTime whenever current time is set
2427 // Watch for native timeupdate event
2428 this.tech
.addEvent("timeupdate", function(){
2430 // Remove this listener from the element
2431 this.removeEvent("timeupdate", arguments
.callee
);
2433 // Update known progress support for this playback technology
2434 this.support
.timeupdateEvent
= true;
2436 // Turn off manual progress tracking
2437 this.player
.manualTimeUpdatesOff();
2441 manualTimeUpdatesOff: function(){
2442 this.manualTimeUpdates
= false;
2443 this.stopTrackingCurrentTime();
2444 this.removeEvent("play", this.trackCurrentTime
);
2445 this.removeEvent("pause", this.stopTrackingCurrentTime
);
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
2455 // Turn off play progress tracking (when paused or dragging)
2456 stopTrackingCurrentTime: function(){ clearInterval(this.currentTimeInterval
); },
2458 /* Player event handlers (how the player reacts to certain events)
2459 ================================================================================ */
2460 onEnded: function(){
2461 if (this.options
.loop
) {
2462 this.currentTime(0);
2466 this.currentTime(0);
2472 _V_
.removeClass(this.el
, "vjs-paused");
2473 _V_
.addClass(this.el
, "vjs-playing");
2476 onPause: function(){
2477 _V_
.removeClass(this.el
, "vjs-playing");
2478 _V_
.addClass(this.el
, "vjs-paused");
2481 onError: function(e
) {
2482 _V_
.log("Video Error", e
);
2486 ================================================================================ */
2488 apiCall: function(method
, arg
){
2490 return this.tech
[method
](arg
);
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
)
2494 // throw new Error("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]");
2499 this.apiCall("play"); return this;
2502 this.apiCall("pause"); return this;
2505 return this.apiCall("paused");
2508 currentTime: function(seconds
){
2509 if (seconds
!== undefined) {
2511 // Cache the last set value for smoother scrubbing.
2512 this.values
.lastSetCurrentTime
= seconds
;
2514 this.apiCall("setCurrentTime", seconds
);
2516 if (this.manualTimeUpdates
) {
2517 this.triggerEvent("timeupdate");
2522 // Cache last currentTime and return
2523 return this.values
.currentTime
= this.apiCall("currentTime");
2525 duration: function(){
2526 return this.apiCall("duration");
2528 remainingTime: function(){
2529 return this.duration() - this.currentTime();
2532 buffered: function(){
2533 var buffered
= this.apiCall("buffered"),
2534 start
= 0, end
= this.values
.bufferEnd
= this.values
.bufferEnd
|| 0,
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
;
2543 return _V_
.createTimeRange(start
, end
);
2546 // Calculates amount of buffer is full
2547 bufferedPercent: function(){
2548 return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
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
);
2559 // if (this.values.volume) { return this.values.volume; }
2560 return this.apiCall("volume");
2562 muted: function(muted
){
2563 if (muted
!== undefined) {
2564 this.apiCall("setMuted", muted
);
2567 return this.apiCall("muted");
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"); }
2577 return parseInt(this.el
.getAttribute("width"));
2579 height: function(height
){
2580 if (height
!== undefined) {
2581 this.el
.height
= height
;
2582 this.el
.style
.height
= height
+"px";
2583 this.triggerEvent("resize");
2586 return parseInt(this.el
.getAttribute("height"));
2588 size: function(width
, height
){
2589 // Skip resize listeners on width for optimization
2590 return this.width(width
, true).height(height
);
2593 supportsFullScreen: function(){ return this.apiCall("supportsFullScreen"); },
2595 // Turn on fullscreen (or window) mode
2596 requestFullScreen: function(){
2597 var requestFullScreen
= _V_
.support
.requestFullScreen
;
2599 this.isFullScreen
= true;
2601 // Check for browser element fullscreen support
2602 if (requestFullScreen
) {
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) {
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
});
2616 this.el
[requestFullScreen
.requestFn
]();
2619 this.el
[requestFullScreen
.requestFn
]();
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
];
2627 } else if (this.tech
.supportsFullScreen()) {
2628 this.apiCall("enterFullScreen");
2631 this.enterFullWindow();
2634 this.triggerEvent("fullscreenchange");
2639 cancelFullScreen: function(){
2640 var requestFullScreen
= _V_
.support
.requestFullScreen
;
2642 // Check for browser element fullscreen support
2643 if (requestFullScreen
) {
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) {
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
})
2657 document
[requestFullScreen
.cancelFn
]();
2660 document
[requestFullScreen
.cancelFn
]();
2663 } else if (this.tech
.supportsFullScreen()) {
2664 this.apiCall("exitFullScreen");
2667 this.exitFullWindow();
2670 this.isFullScreen
= false;
2671 this.triggerEvent("fullscreenchange");
2676 enterFullWindow: function(){
2677 this.isFullWindow
= true;
2679 // Storing original doc overflow value to return to when fullscreen is off
2680 this.docOrigOverflow
= document
.documentElement
.style
.overflow
;
2682 // Add listener for esc key to exit fullscreen
2683 _V_
.addEvent(document
, "keydown", _V_
.proxy(this, this.fullWindowOnEscKey
));
2685 // Hide any scroll bars
2686 document
.documentElement
.style
.overflow
= 'hidden';
2688 // Apply fullscreen styles
2689 _V_
.addClass(document
.body
, "vjs-full-window");
2690 _V_
.addClass(this.el
, "vjs-fullscreen");
2692 this.triggerEvent("enterFullWindow");
2695 fullWindowOnEscKey: function(event
){
2696 if (event
.keyCode
== 27) {
2697 if (this.isFullScreen
== true) {
2698 this.cancelFullScreen();
2700 this.exitFullWindow();
2705 exitFullWindow: function(){
2706 this.isFullWindow
= false;
2707 _V_
.removeEvent(document
, "keydown", this.fullWindowOnEscKey
);
2709 // Unhide scroll bars.
2710 document
.documentElement
.style
.overflow
= this.docOrigOverflow
;
2712 // Remove fullscreen styles
2713 _V_
.removeClass(document
.body
, "vjs-full-window");
2714 _V_
.removeClass(this.el
, "vjs-fullscreen");
2716 // Resize the box, controller, and poster to original sizes
2717 // this.positionAll();
2718 this.triggerEvent("exitFullWindow");
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
) {
2730 var sources
= source
;
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];
2739 // Check if the browser supports this technology
2740 if (tech
.isSupported()) {
2742 // Loop through each source object
2743 for (var a
=0,b
=sources
;a
<b
.length
;a
++) {
2746 // Check if source can be played with this technology
2747 if (tech
.canPlaySource
.call(this, source
)) {
2749 // If this technology is already loaded, set source
2750 if (techName
== this.techName
) {
2751 this.src(source
); // Passing the source object
2753 // Otherwise load this technology with chosen source
2755 this.loadTech(techName
, source
);
2758 break techLoop
; // Break both loops
2764 // Case: Source object { src: "", type: "" ... }
2765 } else if (source
instanceof Object
) {
2766 if (_V_
[this.techName
].canPlaySource(source
)) {
2767 this.src(source
.src
);
2769 // Send through tech loop to check for a compatible technology.
2772 // Case: URL String (http://myvideo...)
2774 // Cache for getting last set source
2775 this.values
.src
= source
;
2777 if (!this.isReady
) {
2778 this.ready(function(){
2782 this.apiCall("src", source
);
2783 if (this.options
.preload
== "auto") {
2786 if (this.options
.autoplay
) {
2794 // Begin loading the src data
2796 this.apiCall("load");
2799 currentSrc: function(){
2800 return this.apiCall("currentSrc");
2803 textTrackValue: function(kind
, value
){
2804 if (value
!== undefined) {
2805 this.values
[kind
] = value
;
2806 this.triggerEvent(kind
+"update");
2809 return this.values
[kind
];
2812 // Attributes/Options
2813 preload: function(value
){
2814 if (value
!== undefined) {
2815 this.apiCall("setPreload", value
);
2816 this.options
.preload
= value
;
2819 return this.apiCall("preload", value
);
2821 autoplay: function(value
){
2822 if (value
!== undefined) {
2823 this.apiCall("setAutoplay", value
);
2824 this.options
.autoplay
= value
;
2827 return this.apiCall("autoplay", value
);
2829 loop: function(value
){
2830 if (value
!== undefined) {
2831 this.apiCall("setLoop", value
);
2832 this.options
.loop
= value
;
2835 return this.apiCall("loop", value
);
2838 controls: function(){ return this.options
.controls
; },
2839 textTracks: function(){ return this.options
.tracks
; },
2840 poster: function(){ return this.apiCall("poster"); },
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"); }
2863 // RequestFullscreen API
2869 playerProto
= _V_
.Player
.prototype;
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";
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.
2886 _V_
.each(["moz", "webkit"], function(prefix
){
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";
2894 if (prefix
== "webkit") {
2895 isFullScreen
= prefix
+ "IsFullScreen";
2898 isFullScreen
= prefix
+ "FullScreen";
2907 _V_
.support
.requestFullScreen
= {
2908 requestFn
: requestFn
,
2910 eventName
: eventName
,
2911 isFullScreen
: isFullScreen
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);
2921 // Make playback element clickable
2922 // _V_.addEvent(this.el, "click", _V_.proxy(this, _V_.PlayToggle.prototype.onClick));
2924 // this.addEvent("click", this.proxy(this.onClick));
2926 // player.triggerEvent("techready");
2928 // destroy: function(){},
2929 // createElement: function(){},
2930 onClick: function(){
2931 if (this.player
.options
.controls
) {
2932 _V_
.PlayToggle
.prototype.onClick
.call(this);
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");
2945 /* HTML5 Playback Technology - Wrapper for HTML5 Media API
2946 ================================================================================ */
2947 _V_
.html5
= _V_
.PlaybackTech
.extend({
2949 init: function(player
, options
, ready
){
2950 this.player
= player
;
2951 this.el
= this.createElement();
2954 this.addEvent("click", this.proxy(this.onClick
));
2956 var source
= options
.source
;
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");
2963 // Otherwise set the source if one was provided.
2964 } else if (source
) {
2965 this.el
.src
= source
.src
;
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.
2979 this.setupTriggers();
2981 this.triggerReady();
2984 destroy: function(){
2985 this.player
.tag
= false;
2986 this.removeTriggers();
2987 this.el
.parentNode
.removeChild(this.el
);
2990 createElement: function(){
2991 var html5
= _V_
.html5
,
2992 player
= this.player
,
2994 // If possible, reuse original tag for HTML5 playback technology element
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) {
3003 // If the original tag is still there, remove it.
3005 player
.el
.removeChild(el
);
3008 newEl
= _V_
.createElement("video", {
3009 id
: el
.id
|| player
.el
.id
+ "_html5_api",
3010 className
: el
.className
|| "vjs-tech"
3014 _V_
.insertFirst(el
, player
.el
);
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
];
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
));
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
));
3037 eventHandler: function(e
){
3038 e
.stopPropagation();
3039 this.triggerEvent(e
);
3042 play: function(){ this.el
.play(); },
3043 pause: function(){ this.el
.pause(); },
3044 paused: function(){ return this.el
.paused
; },
3046 currentTime: function(){ return this.el
.currentTime
; },
3047 setCurrentTime: function(seconds
){
3049 this.el
.currentTime
= seconds
;
3051 _V_
.log(e
, "Video isn't ready. (VideoJS)");
3052 // this.warning(VideoJS.warnings.videoNotReady);
3056 duration: function(){ return this.el
.duration
|| 0; },
3057 buffered: function(){ return this.el
.buffered
; },
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
},
3064 width: function(){ return this.el
.offsetWidth
; },
3065 height: function(){ return this.el
.offsetHeight
; },
3067 supportsFullScreen: function(){
3068 if (typeof this.el
.webkitEnterFullScreen
== 'function') {
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")) {
3078 enterFullScreen: function(){
3080 this.el
.webkitEnterFullScreen();
3083 // this.warning(VideoJS.warnings.videoNotReady);
3084 _V_
.log("VideoJS: Video not ready.")
3088 src: function(src
){ this.el
.src
= src
; },
3089 load: function(){ this.el
.load(); },
3090 currentSrc: function(){ return this.el
.currentSrc
; },
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
; },
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
; }
3121 /* HTML5 Support Testing -------------------------------------------------------- */
3123 _V_
.html5
.isSupported = function(){
3124 return !!document
.createElement("video").canPlayType
;
3127 _V_
.html5
.canPlaySource = function(srcObj
){
3128 return !!document
.createElement("video").canPlayType(srcObj
.type
);
3130 // If no Type, check ext
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(",");
3137 /* HTML5 Device Fixes ---------------------------------------------------------- */
3139 _V_
.html5
.prototype.support
= {
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,
3146 // In iOS, if you move a video element in the DOM, it breaks video playback.
3147 movingElementInDOM
: !_V_
.isIOS()
3152 if (_V_
.isAndroid()) {
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" : "";
3163 /* VideoJS-SWF - Custom Flash Player with HTML5-ish API - https://github.com/zencoder/video-js-swf
3164 ================================================================================ */
3165 _V_
.flash
= _V_
.PlaybackTech
.extend({
3167 init: function(player
, options
){
3168 this.player
= player
;
3170 var source
= options
.source
,
3172 // Which element to embed in
3173 parentEl
= options
.parentEl
,
3175 // Create a temporary element to be replaced by swf object
3176 placeHolder
= this.el
= _V_
.createElement("div", { id
: parentEl
.id
+ "_temp_flash" }),
3178 // Generate ID for swf object
3179 objId
= player
.el
.id
+"_flash_api",
3181 // Store player options in local var for optimization
3182 playerOptions
= player
.options
,
3184 // Merge default flashvars with ones passed in to init
3185 flashVars
= _V_
.merge({
3187 // SWF Callback Functions
3188 readyFunction
: "_V_.flash.onReady",
3189 eventProxyFunction
: "_V_.flash.onEvent",
3190 errorEventProxyFunction
: "_V_.flash.onError",
3193 autoplay
: playerOptions
.autoplay
,
3194 preload
: playerOptions
.preload
,
3195 loop
: playerOptions
.loop
,
3196 muted
: playerOptions
.muted
3198 }, options
.flashVars
),
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
3206 // Merge default attributes with ones passed in
3207 attributes
= _V_
.merge({
3209 name
: objId
, // Both ID and Name needed or swf to identifty itself
3211 }, options
.attributes
)
3214 // If source was supplied pass as a flash var.
3216 flashVars
.src
= encodeURIComponent(source
.src
);
3219 // Add placeholder to player div
3220 _V_
.insertFirst(placeHolder
, parentEl
);
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(){
3228 this.currentTime(options
.startTime
);
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
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.
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.
3252 if (options
.iFrameMode
== true && !_V_
.isFF
) {
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",
3265 // Update ready function names in flash vars for iframe window
3266 flashVars
.readyFunction
= "ready";
3267 flashVars
.eventProxyFunction
= "events";
3268 flashVars
.errorEventProxyFunction
= "errors";
3270 // Tried multiple methods to get this to work in all browsers
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);
3276 // var temp = _V_.createElement("a", { id:"asdf", innerHTML: "asdf" } );
3277 // iDoc.body.appendChild(temp);
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)+"');";
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";
3287 // Wait until iFrame has loaded to write into it.
3288 _V_
.addEvent(iFrm
, "load", _V_
.proxy(this, function(){
3290 var iDoc
, objTag
, swfLoc
,
3291 iWin
= iFrm
.contentWindow
,
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 = "";
3304 // Get the iFrame's document depending on what the browser supports
3305 iDoc
= iFrm
.contentDocument
? iFrm
.contentDocument
: iFrm
.contentWindow
.document
;
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+"/.";
3312 // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
3313 // iDoc.body.innerHTML = swfObjectHTML;
3315 // Tried appending the object to the iframe doc's body. Security error in all browsers.
3316 // iDoc.body.appendChild(swfObject);
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
));
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
;
3327 // Create swf ready function for iFrame window
3328 iWin
.ready
= _V_
.proxy(this.player
, function(currSwf
){
3329 var el
= iDoc
.getElementById(currSwf
),
3333 // Update reference to playback technology element
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
));
3339 // Make sure swf is actually ready. Sometimes the API isn't actually yet.
3340 _V_
.flash
.checkReady(tech
);
3343 // Create event listener for all swf events
3344 iWin
.events
= _V_
.proxy(this.player
, function(swfID
, eventName
, other
){
3346 if (player
&& player
.techName
== "flash") {
3347 player
.triggerEvent(eventName
);
3351 // Create error listener for all swf errors
3352 iWin
.errors
= _V_
.proxy(this.player
, function(swfID
, eventName
){
3353 _V_
.log("Flash Error", eventName
);
3358 // Replace placeholder with iFrame (it will load now)
3359 placeHolder
.parentNode
.replaceChild(iFrm
, placeHolder
);
3361 // If not using iFrame mode, embed as normal object
3363 _V_
.flash
.embed(options
.swf
, placeHolder
, flashVars
, params
, attributes
);
3367 destroy: function(){
3368 this.el
.parentNode
.removeChild(this.el
);
3371 // setupTriggers: function(){}, // Using global onEvent func to distribute events
3373 play: function(){ this.el
.vjs_play(); },
3374 pause: function(){ this.el
.vjs_pause(); },
3376 this.el
.vjs_src(src
);
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
) {
3382 setTimeout(function(){ tech
.play(); }, 0);
3385 load: function(){ this.el
.vjs_load(); },
3386 poster: function(){ this.el
.vjs_getProperty("poster"); },
3388 buffered: function(){
3389 return _V_
.createTimeRange(0, this.el
.vjs_getProperty("buffered"));
3392 supportsFullScreen: function(){
3393 return false; // Flash does not allow fullscreen through javascript
3395 enterFullScreen: function(){
3400 // Create setters and getters for attributes
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
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
); };
3414 createGetter = function(attr
){
3415 api
[attr
] = function(){ return this.el
.vjs_getProperty(attr
); };
3419 // Create getter and setters for all read/write attributes
3420 _V_
.each(readWrite
, function(attr
){
3425 // Create getters for read-only attributes
3426 _V_
.each(readOnly
, function(attr
){
3432 /* Flash Support Testing -------------------------------------------------------- */
3434 _V_
.flash
.isSupported = function(){
3435 return _V_
.flash
.version()[0] >= 10;
3436 // return swfobject.hasFlashPlayerVersion("10");
3439 _V_
.flash
.canPlaySource = function(srcObj
){
3440 if (srcObj
.type
in _V_
.flash
.prototype.support
.formats
) { return "maybe"; }
3443 _V_
.flash
.prototype.support
= {
3446 "video/x-flv": "FLV",
3451 // Optional events that we can manually mimic with timers
3452 progressEvent
: false,
3453 timeupdateEvent
: false,
3455 // Resizing plugins using request fullscreen reloads the plugin
3456 fullscreenResize
: false,
3458 // Resizing plugins in Firefox always reloads the plugin (e.g. full window mode)
3459 parentResize
: !(_V_
.ua
.match("Firefox"))
3462 _V_
.flash
.onReady = function(currSwf
){
3464 var el
= _V_
.el(currSwf
);
3466 // Get player from box
3467 // On firefox reloads, el might already have a player
3468 var player
= el
.player
|| el
.parentNode
.player
,
3471 // Reference player on tech element
3474 // Update reference to playback technology element
3477 // Now that the element is ready, make a click on the swf play the video
3478 tech
.addEvent("click", tech
.onClick
);
3480 _V_
.flash
.checkReady(tech
);
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
){
3487 // Check if API property exists
3488 if (tech
.el
.vjs_getProperty
) {
3490 // If so, tell tech it's ready
3491 tech
.triggerReady();
3493 // Otherwise wait longer.
3496 setTimeout(function(){
3497 _V_
.flash
.checkReady(tech
);
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
);
3509 // Log errors from the swf
3510 _V_
.flash
.onError = function(swfID
, err
){
3511 _V_
.log("Flash Error", err
, swfID
);
3514 // Flash Version Check
3515 _V_
.flash
.version = function(){
3516 var version
= '0,0,0'
3520 version
= new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
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];
3530 return version
.split(",");
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
),
3537 // Get element by embedding code and retrieving created element
3538 obj
= _V_
.createElement("div", { innerHTML
: code
}).childNodes
[0],
3540 par
= placeHolder
.parentNode
3543 placeHolder
.parentNode
.replaceChild(obj
, placeHolder
);
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
3548 var newObj
= par
.childNodes
[0];
3549 setTimeout(function(){
3550 newObj
.style
.display
= "block";
3558 _V_
.flash
.getEmbedCode = function(swf
, flashVars
, params
, attributes
){
3560 var objTag
= '<object type="application/x-shockwave-flash"',
3561 flashVarsString
= '',
3565 // Convert flash vars to string
3567 _V_
.eachProp(flashVars
, function(key
, val
){
3568 flashVarsString
+= (key
+ "=" + val
+ "&");
3572 // Add swf, flashVars, and other default params
3573 params
= _V_
.merge({
3575 flashvars
: flashVarsString
,
3576 allowScriptAccess
: "always", // Required to talk to swf
3577 allowNetworking
: "all" // All should be default, but having security issues.
3580 // Create param tags string
3581 _V_
.eachProp(params
, function(key
, val
){
3582 paramsString
+= '<param name="'+key
+'" value="'+val
+'" />';
3585 attributes
= _V_
.merge({
3586 // Add swf to attributes (need both for IE and Others to work)
3589 // Default to 100% width/height
3595 // Create Attributes string
3596 _V_
.eachProp(attributes
, function(key
, val
){
3597 attrsString
+= (key
+ '="' + val
+ '" ');
3600 return objTag
+ attrsString
+ '>' + paramsString
+ '</object>';
3602 _V_
.Track = function(attributes
, player
){
3603 // Store reference to the parent player
3604 this.player
= player
;
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
;
3614 this.currentCue
= false;
3615 this.lastCueIndex
= 0;
3617 // Update current cue on timeupdate
3618 player
.addEvent("timeupdate", _V_
.proxy(this, this.update
));
3620 // Reset cue time on media end
3621 player
.addEvent("ended", _V_
.proxy(this, function() { this.lastCueIndex
= 0; }));
3624 _V_
.get(attributes
.src
, _V_
.proxy(this, this.parseCues
));
3627 _V_
.Track
.prototype = {
3629 parseCues: function(srcContent
) {
3630 var cue
, time
, text
,
3631 lines
= srcContent
.split("\n"),
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
3638 // First line - Number
3640 id
: line
, // Cue Number
3641 index
: this.cues
.length
// Position in Array
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]);
3650 // Additional lines - Cue 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; }
3657 cue
.text
= text
.join('<br/>');
3660 this.cues
.push(cue
);
3665 parseCueTime: function(timeText
) {
3666 var parts
= timeText
.split(':'),
3669 time
+= parseFloat(parts
[0])*60*60;
3670 // minutes => seconds
3671 time
+= parseFloat(parts
[1])*60;
3673 var seconds
= parts
[2].split(/\.|,/); // Either . or ,
3674 time
+= parseFloat(seconds
[0]);
3676 ms
= parseFloat(seconds
[1]);
3677 if (ms
) { time
+= ms
/1000; }
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
) {
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
) {
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("");
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
);
3734 _V_
.addEvent(window
, "load", function(){
3735 _V_
.windowLoaded
= true;
3738 // Run Auto-load players
3741 window
.VideoJS
= window
._V_
= VideoJS
;
3743 // End self-executing function