2 * @fileoverview Main function src.
5 // HTML5 Shiv. Must be in <head> to support older browsers.
6 document.createElement('video');document.createElement('audio');
9 * Doubles as the main function for users to create a player instance and also
10 * the main library object.
12 * @param {String|Element} id Video element or video element ID
13 * @param {Object=} options Optional options object for config/settings
14 * @param {Function=} ready Optional ready callback
15 * @return {vjs.Player} A player instance
17 var vjs = function(id, options, ready){
18 var tag; // Element of ID
20 // Allow for element or ID to be passed in
22 if (typeof id === 'string') {
24 // Adjust for jQuery ID syntax
25 if (id.indexOf('#') === 0) {
29 // If a player instance has already been created for this ID return it.
30 if (vjs.players[id]) {
31 return vjs.players[id];
33 // Otherwise get element for ID
38 // ID is a media element
43 // Check for a useable element
44 if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also
45 throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
48 // Element may have a player attr referring to an already created player instance.
49 // If not, set up a new player and return the instance.
50 return tag['player'] || new vjs.Player(tag, options, ready);
53 // Extended name, also available externally, window.videojs
55 window.videojs = window.vjs = vjs;
57 // CDN Version. Used to target right flash swf.
58 vjs.CDN_VERSION = 'GENERATED_CDN_VSN';
59 vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
62 * Global Player instance options, surfaced from vjs.Player.prototype.options_
63 * vjs.options = vjs.Player.prototype.options_
64 * All options should use string keys so they avoid
65 * renaming by closure compiler
69 // Default order of fallback technology
70 'techOrder': ['html5','flash'],
71 // techOrder: ['flash','html5'],
74 'flash': { 'swf': vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/c/video-js.swf' },
76 // Default of web browser is 300x150. Should rely on source width/height.
79 // defaultVolume: 0.85,
80 'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!
82 // Included control sets
86 'textTrackDisplay': {},
100 // Set CDN Version of swf
101 if (vjs.CDN_VERSION != 'GENERATED_CDN_VSN') {
102 videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';
105 * Core Object/Class for objects that use inheritance + contstructors
108 vjs.CoreObject = vjs['CoreObject'] = function(){};
109 // Manually exporting vjs['CoreObject'] here for Closure Compiler
110 // because of the use of the extend/create class methods
111 // If we didn't do this, those functions would get flattend to something like
112 // `a = ...` and `this.prototype` would refer to the global object instead of
116 * Create a new object that inherits from this Object
117 * @param {Object} props Functions and properties to be applied to the
118 * new object's prototype
119 * @return {vjs.CoreObject} Returns an object that inherits from CoreObject
122 vjs.CoreObject.extend = function(props){
126 // Set up the constructor using the supplied init method
127 // or using the init of the parent object
128 // Make sure to check the unobfuscated version for external libs
129 init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){};
130 // In Resig's simple class inheritance (previously used) the constructor
131 // is a function that calls `this.init.apply(arguments)`
132 // However that would prevent us from using `ParentObject.call(this);`
133 // in a Child constuctor because the `this` in `this.init`
134 // would still refer to the Child and cause an inifinite loop.
135 // We would instead have to do
136 // `ParentObject.prototype.init.apply(this, argumnents);`
137 // Bleh. We're not creating a _super() function, so it's good to keep
138 // the parent constructor reference simple.
140 init.apply(this, arguments);
143 // Inherit from this object's prototype
144 subObj.prototype = vjs.obj.create(this.prototype);
145 // Reset the constructor property for subObj otherwise
146 // instances of subObj would have the constructor of the parent Object
147 subObj.prototype.constructor = subObj;
149 // Make the class extendable
150 subObj.extend = vjs.CoreObject.extend;
151 // Make a function for creating instances
152 subObj.create = vjs.CoreObject.create;
154 // Extend subObj's prototype with functions and other properties from props
155 for (var name in props) {
156 if (props.hasOwnProperty(name)) {
157 subObj.prototype[name] = props[name];
165 * Create a new instace of this Object class
166 * @return {vjs.CoreObject} Returns an instance of a CoreObject subclass
169 vjs.CoreObject.create = function(){
170 // Create a new object that inherits from this object's prototype
171 var inst = vjs.obj.create(this.prototype);
173 // Apply this constructor function to the new object
174 this.apply(inst, arguments);
176 // Return the new object
180 * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
181 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
182 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
183 * robust as jquery's, so there's probably some differences.
187 * Add an event listener to element
188 * It stores the handler function in a separate cache object
189 * and adds a generic handler to the element's event,
190 * along with a unique id (guid) to the element.
191 * @param {Element|Object} elem Element or object to bind listeners to
192 * @param {String} type Type of event to bind to.
193 * @param {Function} fn Event listener.
195 vjs.on = function(elem, type, fn){
196 var data = vjs.getData(elem);
198 // We need a place to store all our handler data
199 if (!data.handlers) data.handlers = {};
201 if (!data.handlers[type]) data.handlers[type] = [];
203 if (!fn.guid) fn.guid = vjs.guid++;
205 data.handlers[type].push(fn);
207 if (!data.dispatcher) {
208 data.disabled = false;
210 data.dispatcher = function (event){
212 if (data.disabled) return;
213 event = vjs.fixEvent(event);
215 var handlers = data.handlers[event.type];
218 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
219 var handlersCopy = handlers.slice(0);
221 for (var m = 0, n = handlersCopy.length; m < n; m++) {
222 if (event.isImmediatePropagationStopped()) {
225 handlersCopy[m].call(elem, event);
232 if (data.handlers[type].length == 1) {
233 if (document.addEventListener) {
234 elem.addEventListener(type, data.dispatcher, false);
235 } else if (document.attachEvent) {
236 elem.attachEvent('on' + type, data.dispatcher);
242 * Removes event listeners from an element
243 * @param {Element|Object} elem Object to remove listeners from
244 * @param {String=} type Type of listener to remove. Don't include to remove all events from element.
245 * @param {Function} fn Specific listener to remove. Don't incldue to remove listeners for an event type.
247 vjs.off = function(elem, type, fn) {
248 // Don't want to add a cache object through getData if not needed
249 if (!vjs.hasData(elem)) return;
251 var data = vjs.getData(elem);
253 // If no events exist, nothing to unbind
254 if (!data.handlers) { return; }
257 var removeType = function(t){
258 data.handlers[t] = [];
259 vjs.cleanUpEvents(elem,t);
262 // Are we removing all bound events?
264 for (var t in data.handlers) removeType(t);
268 var handlers = data.handlers[type];
270 // If no handlers exist, nothing to unbind
271 if (!handlers) return;
273 // If no listener was provided, remove all listeners for type
279 // We're only removing a single handler
281 for (var n = 0; n < handlers.length; n++) {
282 if (handlers[n].guid === fn.guid) {
283 handlers.splice(n--, 1);
288 vjs.cleanUpEvents(elem, type);
292 * Clean up the listener cache and dispatchers
293 * @param {Element|Object} elem Element to clean up
294 * @param {String} type Type of event to clean up
296 vjs.cleanUpEvents = function(elem, type) {
297 var data = vjs.getData(elem);
299 // Remove the events of a particular type if there are none left
300 if (data.handlers[type].length === 0) {
301 delete data.handlers[type];
302 // data.handlers[type] = null;
303 // Setting to null was causing an error with data.handlers
305 // Remove the meta-handler from the element
306 if (document.removeEventListener) {
307 elem.removeEventListener(type, data.dispatcher, false);
308 } else if (document.detachEvent) {
309 elem.detachEvent('on' + type, data.dispatcher);
313 // Remove the events object if there are no types left
314 if (vjs.isEmpty(data.handlers)) {
315 delete data.handlers;
316 delete data.dispatcher;
317 delete data.disabled;
319 // data.handlers = null;
320 // data.dispatcher = null;
321 // data.disabled = null;
324 // Finally remove the expando if there is no data left
325 if (vjs.isEmpty(data)) {
326 vjs.removeData(elem);
331 * Fix a native event to have standard property values
332 * @param {Object} event Event object to fix
335 vjs.fixEvent = function(event) {
337 function returnTrue() { return true; }
338 function returnFalse() { return false; }
340 // Test if fixing up is needed
341 // Used to check if !event.stopPropagation instead of isPropagationStopped
342 // But native events return true for stopPropagation, but don't have
343 // other expected methods like isPropagationStopped. Seems to be a problem
344 // with the Javascript Ninja code. So we're just overriding all events now.
345 if (!event || !event.isPropagationStopped) {
346 var old = event || window.event;
349 // Clone the old object so that we can modify the values event = {};
350 // IE8 Doesn't like when you mess with native event properties
351 // Firefox returns false for event.hasOwnProperty('type') and other props
352 // which makes copying more difficult.
353 // TODO: Probably best to create a whitelist of event props
354 for (var key in old) {
355 event[key] = old[key];
358 // The event occurred on this element
360 event.target = event.srcElement || document;
363 // Handle which other element the event is related to
364 event.relatedTarget = event.fromElement === event.target ?
368 // Stop the default browser action
369 event.preventDefault = function () {
370 if (old.preventDefault) {
371 old.preventDefault();
373 event.returnValue = false;
374 event.isDefaultPrevented = returnTrue;
377 event.isDefaultPrevented = returnFalse;
379 // Stop the event from bubbling
380 event.stopPropagation = function () {
381 if (old.stopPropagation) {
382 old.stopPropagation();
384 event.cancelBubble = true;
385 event.isPropagationStopped = returnTrue;
388 event.isPropagationStopped = returnFalse;
390 // Stop the event from bubbling and executing other handlers
391 event.stopImmediatePropagation = function () {
392 if (old.stopImmediatePropagation) {
393 old.stopImmediatePropagation();
395 event.isImmediatePropagationStopped = returnTrue;
396 event.stopPropagation();
399 event.isImmediatePropagationStopped = returnFalse;
401 // Handle mouse position
402 if (event.clientX != null) {
403 var doc = document.documentElement, body = document.body;
405 event.pageX = event.clientX +
406 (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
407 (doc && doc.clientLeft || body && body.clientLeft || 0);
408 event.pageY = event.clientY +
409 (doc && doc.scrollTop || body && body.scrollTop || 0) -
410 (doc && doc.clientTop || body && body.clientTop || 0);
413 // Handle key presses
414 event.which = event.charCode || event.keyCode;
416 // Fix button for mouse clicks:
417 // 0 == left; 1 == middle; 2 == right
418 if (event.button != null) {
419 event.button = (event.button & 1 ? 0 :
420 (event.button & 4 ? 1 :
421 (event.button & 2 ? 2 : 0)));
425 // Returns fixed-up instance
430 * Trigger an event for an element
431 * @param {Element|Object} elem Element to trigger an event on
432 * @param {String} event Type of event to trigger
434 vjs.trigger = function(elem, event) {
435 // Fetches element data and a reference to the parent (for bubbling).
436 // Don't want to add a data object to cache for every parent,
437 // so checking hasData first.
438 var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {};
439 var parent = elem.parentNode || elem.ownerDocument;
440 // type = event.type || event,
443 // If an event name was passed as a string, creates an event out of it
444 if (typeof event === 'string') {
445 event = { type:event, target:elem };
447 // Normalizes the event properties.
448 event = vjs.fixEvent(event);
450 // If the passed element has a dispatcher, executes the established handlers.
451 if (elemData.dispatcher) {
452 elemData.dispatcher.call(elem, event);
455 // Unless explicitly stopped, recursively calls this function to bubble the event up the DOM.
456 if (parent && !event.isPropagationStopped()) {
457 vjs.trigger(parent, event);
459 // If at the top of the DOM, triggers the default action unless disabled.
460 } else if (!parent && !event.isDefaultPrevented()) {
461 var targetData = vjs.getData(event.target);
463 // Checks if the target has a default action for this event.
464 if (event.target[event.type]) {
465 // Temporarily disables event dispatching on the target as we have already executed the handler.
466 targetData.disabled = true;
467 // Executes the default action.
468 if (typeof event.target[event.type] === 'function') {
469 event.target[event.type]();
471 // Re-enables event dispatching.
472 targetData.disabled = false;
476 // Inform the triggerer if the default was prevented by returning false
477 return !event.isDefaultPrevented();
478 /* Original version of js ninja events wasn't complete.
479 * We've since updated to the latest version, but keeping this around
480 * for now just in case.
482 // // Added in attion to book. Book code was broke.
483 // event = typeof event === 'object' ?
484 // event[vjs.expando] ?
486 // new vjs.Event(type, event) :
487 // new vjs.Event(type);
489 // event.type = type;
491 // handler.call(elem, event);
494 // // Clean up the event in case it is being reused
495 // event.result = undefined;
496 // event.target = elem;
500 * Trigger a listener only once for an event
501 * @param {Element|Object} elem Element or object to
502 * @param {[type]} type [description]
503 * @param {Function} fn [description]
506 vjs.one = function(elem, type, fn) {
507 vjs.on(elem, type, function(){
508 vjs.off(elem, type, arguments.callee);
509 fn.apply(this, arguments);
512 var hasOwnProp = Object.prototype.hasOwnProperty;
515 * Creates an element and applies properties.
516 * @param {String=} tagName Name of tag to be created.
517 * @param {Object=} properties Element properties to be applied.
520 vjs.createEl = function(tagName, properties){
521 var el = document.createElement(tagName || 'div');
523 for (var propName in properties){
524 if (hasOwnProp.call(properties, propName)) {
525 //el[propName] = properties[propName];
526 // Not remembering why we were checking for dash
527 // but using setAttribute means you have to use getAttribute
529 // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.
530 // The additional check for "role" is because the default method for adding attributes does not
531 // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
532 // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
533 // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
535 if (propName.indexOf('aria-') !== -1 || propName=='role') {
536 el.setAttribute(propName, properties[propName]);
538 el[propName] = properties[propName];
546 * Uppercase the first letter of a string
547 * @param {String} string String to be uppercased
550 vjs.capitalize = function(string){
551 return string.charAt(0).toUpperCase() + string.slice(1);
555 * Object functions container
561 * Object.create shim for prototypal inheritance.
562 * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
563 * @param {Object} obj Object to use as prototype
565 vjs.obj.create = Object.create || function(obj){
566 //Create a new function called 'F' which is just an empty object.
569 //the prototype of the 'F' function should point to the
570 //parameter of the anonymous function.
573 //create a new constructor function based off of the 'F' function.
578 * Loop through each property in an object and call a function
579 * whose arguments are (key,value)
580 * @param {Object} obj Object of properties
581 * @param {Function} fn Function to be called on each property.
584 vjs.obj.each = function(obj, fn, context){
585 for (var key in obj) {
586 if (hasOwnProp.call(obj, key)) {
587 fn.call(context || this, key, obj[key]);
593 * Merge two objects together and return the original.
594 * @param {Object} obj1
595 * @param {Object} obj2
598 vjs.obj.merge = function(obj1, obj2){
599 if (!obj2) { return obj1; }
600 for (var key in obj2){
601 if (hasOwnProp.call(obj2, key)) {
602 obj1[key] = obj2[key];
609 * Merge two objects, and merge any properties that are objects
610 * instead of just overwriting one. Uses to merge options hashes
611 * where deeper default settings are important.
612 * @param {Object} obj1 Object to override
613 * @param {Object} obj2 Overriding object
614 * @return {Object} New object. Obj1 and Obj2 will be untouched.
616 vjs.obj.deepMerge = function(obj1, obj2){
617 var key, val1, val2, objDef;
618 objDef = '[object Object]';
620 // Make a copy of obj1 so we're not ovewriting original values.
621 // like prototype.options_ and all sub options objects
622 obj1 = vjs.obj.copy(obj1);
625 if (hasOwnProp.call(obj2, key)) {
629 // Check if both properties are pure objects and do a deep merge if so
630 if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {
631 obj1[key] = vjs.obj.deepMerge(val1, val2);
633 obj1[key] = obj2[key];
641 * Make a copy of the supplied object
642 * @param {Object} obj Object to copy
643 * @return {Object} Copy of object
645 vjs.obj.copy = function(obj){
646 return vjs.obj.merge({}, obj);
650 * Check if an object is plain, and not a dom node or any object sub-instance
651 * @param {Object} obj Object to check
652 * @return {Boolean} True if plain, false otherwise
654 vjs.obj.isPlain = function(obj){
656 && typeof obj === 'object'
657 && obj.toString() === '[object Object]'
658 && obj.constructor === Object;
662 * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
663 It also stores a unique id on the function so it can be easily removed from events
664 * @param {*} context The object to bind as scope
665 * @param {Function} fn The function to be bound to a scope
666 * @param {Number=} uid An optional unique ID for the function to be set
669 vjs.bind = function(context, fn, uid) {
670 // Make sure the function has a unique ID
671 if (!fn.guid) { fn.guid = vjs.guid++; }
673 // Create the new function that changes the context
674 var ret = function() {
675 return fn.apply(context, arguments);
678 // Allow for the ability to individualize this function
679 // Needed in the case where multiple objects might share the same prototype
680 // IF both items add an event listener with the same function, then you try to remove just one
681 // it will remove both because they both have the same guid.
682 // when using this, you need to use the bind method when you remove the listener as well.
683 // currently used in text tracks
684 ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid;
690 * Element Data Store. Allows for binding data to an element without putting it directly on the element.
691 * Ex. Event listneres are stored here.
692 * (also from jsninja.com, slightly modified and updated for closure compiler)
698 * Unique ID for an element or function
704 * Unique attribute name to store an element's guid in
708 vjs.expando = 'vdata' + (new Date()).getTime();
711 * Returns the cache object where data for an element is stored
712 * @param {Element} el Element to store data for.
715 vjs.getData = function(el){
716 var id = el[vjs.expando];
718 id = el[vjs.expando] = vjs.guid++;
721 return vjs.cache[id];
725 * Returns the cache object where data for an element is stored
726 * @param {Element} el Element to store data for.
729 vjs.hasData = function(el){
730 var id = el[vjs.expando];
731 return !(!id || vjs.isEmpty(vjs.cache[id]));
735 * Delete data for the element from the cache and the guid attr from getElementById
736 * @param {Element} el Remove data for an element
738 vjs.removeData = function(el){
739 var id = el[vjs.expando];
741 // Remove all stored data
743 // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
744 // vjs.cache[id] = null;
745 delete vjs.cache[id];
747 // Remove the expando property from the DOM node
749 delete el[vjs.expando];
751 if (el.removeAttribute) {
752 el.removeAttribute(vjs.expando);
754 // IE doesn't appear to support removeAttribute on the document element
755 el[vjs.expando] = null;
760 vjs.isEmpty = function(obj) {
761 for (var prop in obj) {
762 // Inlude null properties as empty.
763 if (obj[prop] !== null) {
771 * Add a CSS class name to an element
772 * @param {Element} element Element to add class name to
773 * @param {String} classToAdd Classname to add
775 vjs.addClass = function(element, classToAdd){
776 if ((' '+element.className+' ').indexOf(' '+classToAdd+' ') == -1) {
777 element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;
782 * Remove a CSS class name from an element
783 * @param {Element} element Element to remove from class name
784 * @param {String} classToAdd Classname to remove
786 vjs.removeClass = function(element, classToRemove){
787 if (element.className.indexOf(classToRemove) == -1) { return; }
788 var classNames = element.className.split(' ');
789 // IE8 Does not support array.indexOf so using a for loop
790 for (var i = classNames.length - 1; i >= 0; i--) {
791 if (classNames[i] === classToRemove) {
792 classNames.splice(i,1);
795 // classNames.splice(classNames.indexOf(classToRemove),1);
796 element.className = classNames.join(' ');
800 * Element for testing browser HTML5 video capabilities
804 vjs.TEST_VID = vjs.createEl('video');
807 * Useragent for browser testing.
811 vjs.USER_AGENT = navigator.userAgent;
814 * Device is an iPhone
818 vjs.IS_IPHONE = !!vjs.USER_AGENT.match(/iPhone/i);
819 vjs.IS_IPAD = !!vjs.USER_AGENT.match(/iPad/i);
820 vjs.IS_IPOD = !!vjs.USER_AGENT.match(/iPod/i);
821 vjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD;
823 vjs.IOS_VERSION = (function(){
824 var match = vjs.USER_AGENT.match(/OS (\d+)_/i);
825 if (match && match[1]) { return match[1]; }
828 vjs.IS_ANDROID = !!vjs.USER_AGENT.match(/Android.*AppleWebKit/i);
829 vjs.ANDROID_VERSION = (function() {
830 var match = vjs.USER_AGENT.match(/Android (\d+)\./i);
831 if (match && match[1]) {
837 vjs.IS_FIREFOX = function(){ return !!vjs.USER_AGENT.match('Firefox'); };
841 * Get an element's attribute values, as defined on the HTML tag
842 * Attributs are not the same as properties. They're defined on the tag
843 * or with setAttribute (which shouldn't be used with HTML)
844 * This will return true or false for boolean attributes.
845 * @param {Element} tag Element from which to get tag attributes
848 vjs.getAttributeValues = function(tag){
851 // Known boolean attributes
852 // We can check for matching boolean properties, but older browsers
853 // won't know about HTML5 boolean attributes that we still read from.
854 // Bookending with commas to allow for an easy string search.
855 var knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
857 if (tag && tag.attributes && tag.attributes.length > 0) {
858 var attrs = tag.attributes;
859 var attrName, attrVal;
861 for (var i = attrs.length - 1; i >= 0; i--) {
862 attrName = attrs[i].name;
863 attrVal = attrs[i].value;
865 // Check for known booleans
866 // The matching element property will return a value for typeof
867 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {
868 // The value of an included boolean attribute is typically an empty string ('')
869 // which would equal false if we just check for a false value.
870 // We also don't want support bad code like autoplay='false'
871 attrVal = (attrVal !== null) ? true : false;
874 obj[attrName] = attrVal;
882 * Get the computed style value for an element
883 * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
884 * @param {Element} el Element to get style value for
885 * @param {String} strCssRule Style name
886 * @return {String} Style value
888 vjs.getComputedDimension = function(el, strCssRule){
890 if(document.defaultView && document.defaultView.getComputedStyle){
891 strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule);
893 } else if(el.currentStyle){
894 // IE8 Width/Height support
895 strValue = el['client'+strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1)] + 'px';
901 * Insert an element as the first child node of another
902 * @param {Element} child Element to insert
903 * @param {[type]} parent Element to insert child into
905 vjs.insertFirst = function(child, parent){
906 if (parent.firstChild) {
907 parent.insertBefore(child, parent.firstChild);
909 parent.appendChild(child);
914 * Object to hold browser support information
920 * Shorthand for document.getElementById()
921 * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
922 * @param {String} id Element ID
923 * @return {Element} Element with supplied ID
925 vjs.el = function(id){
926 if (id.indexOf('#') === 0) {
930 return document.getElementById(id);
934 * Format seconds as a time string, H:MM:SS or M:SS
935 * Supplying a guide (in seconds) will force a number of leading zeros
936 * to cover the length of the guide
937 * @param {Number} seconds Number of seconds to be turned into a string
938 * @param {Number} guide Number (in seconds) to model the string after
939 * @return {String} Time formatted as H:MM:SS or M:SS
941 vjs.formatTime = function(seconds, guide) {
942 guide = guide || seconds; // Default to using seconds as guide
943 var s = Math.floor(seconds % 60),
944 m = Math.floor(seconds / 60 % 60),
945 h = Math.floor(seconds / 3600),
946 gm = Math.floor(guide / 60 % 60),
947 gh = Math.floor(guide / 3600);
949 // Check if we need to show hours
950 h = (h > 0 || gh > 0) ? h + ':' : '';
952 // If hours are showing, we may need to add a leading zero.
953 // Always show at least one digit of minutes.
954 m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
956 // Check if leading zero is need for seconds
957 s = (s < 10) ? '0' + s : s;
962 // Attempt to block the ability to select text while dragging controls
963 vjs.blockTextSelection = function(){
964 document.body.focus();
965 document.onselectstart = function () { return false; };
967 // Turn off text selection blocking
968 vjs.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };
971 * Trim whitespace from the ends of a string.
972 * @param {String} string String to trim
973 * @return {String} Trimmed string
975 vjs.trim = function(string){
976 return string.toString().replace(/^\s+/, '').replace(/\s+$/, '');
980 * Should round off a number to a decimal place
981 * @param {Number} num Number to round
982 * @param {Number} dec Number of decimal places to round to
983 * @return {Number} Rounded number
985 vjs.round = function(num, dec) {
986 if (!dec) { dec = 0; }
987 return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
991 * Should create a fake TimeRange object
992 * Mimics an HTML5 time range instance, which has functions that
993 * return the start and end times for a range
994 * TimeRanges are returned by the buffered() method
995 * @param {Number} start Start time in seconds
996 * @param {Number} end End time in seconds
997 * @return {Object} Fake TimeRange object
999 vjs.createTimeRange = function(start, end){
1002 start: function() { return start; },
1003 end: function() { return end; }
1008 * Simple http request for retrieving external files (e.g. text tracks)
1009 * @param {String} url URL of resource
1010 * @param {Function=} onSuccess Success callback
1011 * @param {Function=} onError Error callback
1013 vjs.get = function(url, onSuccess, onError){
1014 var local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
1016 if (typeof XMLHttpRequest === 'undefined') {
1017 window.XMLHttpRequest = function () {
1018 try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}
1019 try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}
1020 try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {}
1021 throw new Error('This browser does not support XMLHttpRequest.');
1025 var request = new XMLHttpRequest();
1028 request.open('GET', url);
1033 request.onreadystatechange = function() {
1034 if (request.readyState === 4) {
1035 if (request.status === 200 || local && request.status === 0) {
1036 onSuccess(request.responseText);
1055 ================================================================================ */
1056 vjs.setLocalStorage = function(key, value){
1058 // IE was throwing errors referencing the var anywhere without this
1059 var localStorage = window.localStorage || false;
1060 if (!localStorage) { return; }
1061 localStorage[key] = value;
1063 if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
1064 vjs.log('LocalStorage Full (VideoJS)', e);
1067 vjs.log('LocalStorage not allowed (VideoJS)', e);
1069 vjs.log('LocalStorage Error (VideoJS)', e);
1076 * Get abosolute version of relative URL. Used to tell flash correct URL.
1077 * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
1078 * @param {String} url URL to make absolute
1079 * @return {String} Absolute URL
1081 vjs.getAbsoluteURL = function(url){
1083 // Check if absolute URL
1084 if (!url.match(/^https?:\/\//)) {
1085 // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
1086 url = vjs.createEl('div', {
1087 innerHTML: '<a href="'+url+'">x</a>'
1094 // usage: log('inside coolFunc',this,arguments);
1095 // http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
1096 vjs.log = function(){
1097 vjs.log.history = vjs.log.history || []; // store logs to an array for reference
1098 vjs.log.history.push(arguments);
1100 window.console.log(Array.prototype.slice.call(arguments));
1105 // getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
1106 vjs.findPosition = function(el) {
1107 var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
1109 if (el.getBoundingClientRect && el.parentNode) {
1110 box = el.getBoundingClientRect();
1120 docEl = document.documentElement;
1121 body = document.body;
1123 clientLeft = docEl.clientLeft || body.clientLeft || 0;
1124 scrollLeft = window.pageXOffset || body.scrollLeft;
1125 left = box.left + scrollLeft - clientLeft;
1127 clientTop = docEl.clientTop || body.clientTop || 0;
1128 scrollTop = window.pageYOffset || body.scrollTop;
1129 top = box.top + scrollTop - clientTop;
1137 * @fileoverview Player Component - Base class for all UI objects
1142 * Base UI Component class
1143 * @param {Object} player Main Player
1144 * @param {Object=} options
1147 vjs.Component = vjs.CoreObject.extend({
1149 init: function(player, options, ready){
1150 this.player_ = player;
1152 // Make a copy of prototype.options_ to protect against overriding global defaults
1153 this.options_ = vjs.obj.copy(this.options_);
1155 // Updated options with supplied options
1156 options = this.options(options);
1158 // Get ID from options, element, or create using player ID and unique ID
1159 this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );
1161 this.name_ = options['name'] || null;
1163 // Create element if one wasn't provided in options
1164 this.el_ = options['el'] || this.createEl();
1166 this.children_ = [];
1167 this.childIndex_ = {};
1168 this.childNameIndex_ = {};
1170 // Add any child components in options
1171 this.initChildren();
1174 // Don't want to trigger ready here or it will before init is actually
1175 // finished for all children that run this constructor
1180 * Dispose of the component and all child components.
1182 vjs.Component.prototype.dispose = function(){
1183 // Dispose all children.
1184 if (this.children_) {
1185 for (var i = this.children_.length - 1; i >= 0; i--) {
1186 if (this.children_[i].dispose) {
1187 this.children_[i].dispose();
1192 // Delete child references
1193 this.children_ = null;
1194 this.childIndex_ = null;
1195 this.childNameIndex_ = null;
1197 // Remove all event listeners.
1200 // Remove element from DOM
1201 if (this.el_.parentNode) {
1202 this.el_.parentNode.removeChild(this.el_);
1205 vjs.removeData(this.el_);
1210 * Reference to main player instance.
1211 * @type {vjs.Player}
1214 vjs.Component.prototype.player_;
1217 * Return the component's player.
1218 * @return {vjs.Player}
1220 vjs.Component.prototype.player = function(){
1221 return this.player_;
1225 * Component options object.
1229 vjs.Component.prototype.options_;
1232 * Deep merge of options objects
1233 * Whenever a property is an object on both options objects
1234 * the two properties will be merged using vjs.obj.deepMerge.
1236 * This is used for merging options for child components. We
1237 * want it to be easy to override individual options on a child
1238 * component without having to rewrite all the other default options.
1240 * Parent.prototype.options_ = {
1242 * 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
1249 * 'childOne': { 'foo': 'baz', 'abc': '123' }
1255 * this.options(newOptions);
1261 * 'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },
1262 * 'childTwo': null, // Disabled. Won't be initialized.
1268 * @param {Object} obj Object whose values will be overwritten
1269 * @return {Object} NEW merged object. Does not return obj1.
1271 vjs.Component.prototype.options = function(obj){
1272 if (obj === undefined) return this.options_;
1274 return this.options_ = vjs.obj.deepMerge(this.options_, obj);
1278 * The DOM element for the component.
1282 vjs.Component.prototype.el_;
1285 * Create the component's DOM element.
1286 * @param {String=} tagName Element's node type. e.g. 'div'
1287 * @param {Object=} attributes An object of element attributes that should be set on the element.
1290 vjs.Component.prototype.createEl = function(tagName, attributes){
1291 return vjs.createEl(tagName, attributes);
1295 * Return the component's DOM element.
1298 vjs.Component.prototype.el = function(){
1303 * An optional element where, if defined, children will be inserted
1304 * instead of directly in el_
1308 vjs.Component.prototype.contentEl_;
1311 * Return the component's DOM element for embedding content.
1312 * will either be el_ or a new element defined in createEl
1315 vjs.Component.prototype.contentEl = function(){
1316 return this.contentEl_ || this.el_;
1320 * The ID for the component.
1324 vjs.Component.prototype.id_;
1327 * Return the component's ID.
1330 vjs.Component.prototype.id = function(){
1335 * The name for the component. Often used to reference the component.
1339 vjs.Component.prototype.name_;
1342 * Return the component's ID.
1345 vjs.Component.prototype.name = function(){
1350 * Array of child components
1354 vjs.Component.prototype.children_;
1357 * Returns array of all child components.
1360 vjs.Component.prototype.children = function(){
1361 return this.children_;
1365 * Object of child components by ID
1369 vjs.Component.prototype.childIndex_;
1372 * Returns a child component with the provided ID.
1375 vjs.Component.prototype.getChildById = function(id){
1376 return this.childIndex_[id];
1380 * Object of child components by Name
1384 vjs.Component.prototype.childNameIndex_;
1387 * Returns a child component with the provided ID.
1390 vjs.Component.prototype.getChild = function(name){
1391 return this.childNameIndex_[name];
1395 * Adds a child component inside this component.
1396 * @param {String|vjs.Component} child The class name or instance of a child to add.
1397 * @param {Object=} options Optional options, including options to be passed to
1398 * children of the child.
1399 * @return {vjs.Component} The child component, because it might be created in this process.
1400 * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}
1402 vjs.Component.prototype.addChild = function(child, options){
1403 var component, componentClass, componentName, componentId;
1405 // If string, create new component with options
1406 if (typeof child === 'string') {
1408 componentName = child;
1410 // Make sure options is at least an empty object to protect against errors
1411 options = options || {};
1413 // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
1414 componentClass = options['componentClass'] || vjs.capitalize(componentName);
1416 // Set name through options
1417 options['name'] = componentName;
1419 // Create a new object & element for this controls set
1420 // If there's no .player_, this is a player
1421 // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly.
1422 // Every class should be exported, so this should never be a problem here.
1423 component = new window['videojs'][componentClass](this.player_ || this, options);
1425 // child is a component instance
1430 this.children_.push(component);
1432 if (typeof component.id === 'function') {
1433 this.childIndex_[component.id()] = component;
1436 // If a name wasn't used to create the component, check if we can use the
1437 // name function of the component
1438 componentName = componentName || (component.name && component.name());
1440 if (componentName) {
1441 this.childNameIndex_[componentName] = component;
1444 // Add the UI object's element to the container div (box)
1445 // Having an element is not required
1446 if (typeof component['el'] === 'function' && component['el']()) {
1447 this.contentEl().appendChild(component['el']());
1450 // Return so it can stored on parent object if desired.
1454 vjs.Component.prototype.removeChild = function(component){
1455 if (typeof component === 'string') {
1456 component = this.getChild(component);
1459 if (!component || !this.children_) return;
1461 var childFound = false;
1462 for (var i = this.children_.length - 1; i >= 0; i--) {
1463 if (this.children_[i] === component) {
1465 this.children_.splice(i,1);
1470 if (!childFound) return;
1472 this.childIndex_[component.id] = null;
1473 this.childNameIndex_[component.name] = null;
1475 var compEl = component.el();
1476 if (compEl && compEl.parentNode === this.contentEl()) {
1477 this.contentEl().removeChild(component.el());
1482 * Initialize default child components from options
1484 vjs.Component.prototype.initChildren = function(){
1485 var options = this.options_;
1487 if (options && options['children']) {
1490 // Loop through components and add them to the player
1491 vjs.obj.each(options['children'], function(name, opts){
1492 // Allow for disabling default components
1493 // e.g. vjs.options['children']['posterImage'] = false
1494 if (opts === false) return;
1496 // Allow waiting to add components until a specific event is called
1497 var tempAdd = function(){
1498 // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
1499 self[name] = self.addChild(name, opts);
1502 if (opts['loadEvent']) {
1503 // this.one(opts.loadEvent, tempAdd)
1511 vjs.Component.prototype.buildCSSClass = function(){
1512 // Child classes can include a function that does:
1513 // return 'CLASS NAME' + this._super();
1518 ============================================================================= */
1521 * Add an event listener to this component's element. Context will be the component.
1522 * @param {String} type Event type e.g. 'click'
1523 * @param {Function} fn Event listener
1524 * @return {vjs.Component}
1526 vjs.Component.prototype.on = function(type, fn){
1527 vjs.on(this.el_, type, vjs.bind(this, fn));
1532 * Remove an event listener from the component's element
1533 * @param {String=} type Optional event type. Without type it will remove all listeners.
1534 * @param {Function=} fn Optional event listener. Without fn it will remove all listeners for a type.
1535 * @return {vjs.Component}
1537 vjs.Component.prototype.off = function(type, fn){
1538 vjs.off(this.el_, type, fn);
1543 * Add an event listener to be triggered only once and then removed
1544 * @param {String} type Event type
1545 * @param {Function} fn Event listener
1546 * @return {vjs.Component}
1548 vjs.Component.prototype.one = function(type, fn) {
1549 vjs.one(this.el_, type, vjs.bind(this, fn));
1554 * Trigger an event on an element
1555 * @param {String} type Event type to trigger
1556 * @param {Event|Object} event Event object to be passed to the listener
1557 * @return {vjs.Component}
1559 vjs.Component.prototype.trigger = function(type, event){
1560 vjs.trigger(this.el_, type, event);
1565 ================================================================================ */
1567 * Is the component loaded.
1571 vjs.Component.prototype.isReady_;
1574 * Trigger ready as soon as initialization is finished.
1575 * Allows for delaying ready. Override on a sub class prototype.
1576 * If you set this.isReadyOnInitFinish_ it will affect all components.
1577 * Specially used when waiting for the Flash player to asynchrnously load.
1581 vjs.Component.prototype.isReadyOnInitFinish_ = true;
1584 * List of ready listeners
1588 vjs.Component.prototype.readyQueue_;
1591 * Bind a listener to the component's ready state.
1592 * Different from event listeners in that if the ready event has already happend
1593 * it will trigger the function immediately.
1594 * @param {Function} fn Ready listener
1595 * @return {vjs.Component}
1597 vjs.Component.prototype.ready = function(fn){
1599 if (this.isReady_) {
1602 if (this.readyQueue_ === undefined) {
1603 this.readyQueue_ = [];
1605 this.readyQueue_.push(fn);
1612 * Trigger the ready listeners
1613 * @return {vjs.Component}
1615 vjs.Component.prototype.triggerReady = function(){
1616 this.isReady_ = true;
1618 var readyQueue = this.readyQueue_;
1620 if (readyQueue && readyQueue.length > 0) {
1622 for (var i = 0, j = readyQueue.length; i < j; i++) {
1623 readyQueue[i].call(this);
1626 // Reset Ready Queue
1627 this.readyQueue_ = [];
1629 // Allow for using event listeners also, in case you want to do something everytime a source is ready.
1630 this.trigger('ready');
1635 ============================================================================= */
1638 * Add a CSS class name to the component's element
1639 * @param {String} classToAdd Classname to add
1640 * @return {vjs.Component}
1642 vjs.Component.prototype.addClass = function(classToAdd){
1643 vjs.addClass(this.el_, classToAdd);
1648 * Remove a CSS class name from the component's element
1649 * @param {String} classToRemove Classname to remove
1650 * @return {vjs.Component}
1652 vjs.Component.prototype.removeClass = function(classToRemove){
1653 vjs.removeClass(this.el_, classToRemove);
1658 * Show the component element if hidden
1659 * @return {vjs.Component}
1661 vjs.Component.prototype.show = function(){
1662 this.el_.style.display = 'block';
1667 * Hide the component element if hidden
1668 * @return {vjs.Component}
1670 vjs.Component.prototype.hide = function(){
1671 this.el_.style.display = 'none';
1676 * Fade a component in using CSS
1677 * @return {vjs.Component}
1679 vjs.Component.prototype.fadeIn = function(){
1680 this.removeClass('vjs-fade-out');
1681 this.addClass('vjs-fade-in');
1686 * Fade a component out using CSS
1687 * @return {vjs.Component}
1689 vjs.Component.prototype.fadeOut = function(){
1690 this.removeClass('vjs-fade-in');
1691 this.addClass('vjs-fade-out');
1696 * Lock an item in its visible state. To be used with fadeIn/fadeOut.
1697 * @return {vjs.Component}
1699 vjs.Component.prototype.lockShowing = function(){
1700 this.addClass('vjs-lock-showing');
1705 * Unlock an item to be hidden. To be used with fadeIn/fadeOut.
1706 * @return {vjs.Component}
1708 vjs.Component.prototype.unlockShowing = function(){
1709 this.removeClass('vjs-lock-showing');
1714 * Disable component by making it unshowable
1716 vjs.Component.prototype.disable = function(){
1718 this.show = function(){};
1719 this.fadeIn = function(){};
1722 // TODO: Get enable working
1723 // vjs.Component.prototype.enable = function(){
1724 // this.fadeIn = vjs.Component.prototype.fadeIn;
1725 // this.show = vjs.Component.prototype.show;
1729 * If a value is provided it will change the width of the player to that value
1730 * otherwise the width is returned
1731 * http://dev.w3.org/html5/spec/dimension-attributes.html#attr-dim-height
1732 * Video tag width/height only work in pixels. No percents.
1733 * But allowing limited percents use. e.g. width() will return number+%, not computed width
1734 * @param {Number|String=} num Optional width number
1735 * @param {[type]} skipListeners Skip the 'resize' event trigger
1736 * @return {vjs.Component|Number|String} Returns 'this' if dimension was set.
1737 * Otherwise it returns the dimension.
1739 vjs.Component.prototype.width = function(num, skipListeners){
1740 return this.dimension('width', num, skipListeners);
1744 * Get or set the height of the player
1745 * @param {Number|String=} num Optional new player height
1746 * @param {Boolean=} skipListeners Optional skip resize event trigger
1747 * @return {vjs.Component|Number|String} The player, or the dimension
1749 vjs.Component.prototype.height = function(num, skipListeners){
1750 return this.dimension('height', num, skipListeners);
1754 * Set both width and height at the same time.
1755 * @param {Number|String} width
1756 * @param {Number|String} height
1757 * @return {vjs.Component} The player.
1759 vjs.Component.prototype.dimensions = function(width, height){
1760 // Skip resize listeners on width for optimization
1761 return this.width(width, true).height(height);
1765 * Get or set width or height.
1766 * All for an integer, integer + 'px' or integer + '%';
1767 * Known issue: hidden elements. Hidden elements officially have a width of 0.
1768 * So we're defaulting to the style.width value and falling back to computedStyle
1769 * which has the hidden element issue.
1770 * Info, but probably not an efficient fix:
1771 * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
1772 * @param {String=} widthOrHeight 'width' or 'height'
1773 * @param {Number|String=} num New dimension
1774 * @param {Boolean=} skipListeners Skip resize event trigger
1775 * @return {vjs.Component|Number|String} Return the player if setting a dimension.
1776 * Otherwise it returns the dimension.
1778 vjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){
1779 if (num !== undefined) {
1781 // Check if using css width/height (% or px) and adjust
1782 if ((''+num).indexOf('%') !== -1 || (''+num).indexOf('px') !== -1) {
1783 this.el_.style[widthOrHeight] = num;
1784 } else if (num === 'auto') {
1785 this.el_.style[widthOrHeight] = '';
1787 this.el_.style[widthOrHeight] = num+'px';
1790 // skipListeners allows us to avoid triggering the resize event when setting both width and height
1791 if (!skipListeners) { this.trigger('resize'); }
1797 // Not setting a value, so getting it
1798 // Make sure element exists
1799 if (!this.el_) return 0;
1801 // Get dimension value from style
1802 var val = this.el_.style[widthOrHeight];
1803 var pxIndex = val.indexOf('px');
1804 if (pxIndex !== -1) {
1805 // Return the pixel value with no 'px'
1806 return parseInt(val.slice(0,pxIndex), 10);
1808 // No px so using % or no style was set, so falling back to offsetWidth/height
1809 // If component has display:none, offset will return 0
1810 // TODO: handle display:none and no dimension style using px
1813 return parseInt(this.el_['offset'+vjs.capitalize(widthOrHeight)], 10);
1815 // ComputedStyle version.
1816 // Only difference is if the element is hidden it will return
1817 // the percent value (e.g. '100%'')
1818 // instead of zero like offsetWidth returns.
1819 // var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);
1820 // var pxIndex = val.indexOf('px');
1822 // if (pxIndex !== -1) {
1823 // return val.slice(0, pxIndex);
1829 /* Button - Base class for all buttons
1830 ================================================================================ */
1832 * Base class for all buttons
1833 * @param {vjs.Player|Object} player
1834 * @param {Object=} options
1837 vjs.Button = vjs.Component.extend({
1839 init: function(player, options){
1840 vjs.Component.call(this, player, options);
1842 var touchstart = false;
1843 this.on('touchstart', function() {
1846 this.on('touchmove', function() {
1850 this.on('touchend', function(event) {
1852 self.onClick(event);
1854 event.preventDefault();
1855 event.stopPropagation();
1858 this.on('click', this.onClick);
1859 this.on('focus', this.onFocus);
1860 this.on('blur', this.onBlur);
1864 vjs.Button.prototype.createEl = function(type, props){
1865 // Add standard Aria and Tabindex info
1866 props = vjs.obj.merge({
1867 className: this.buildCSSClass(),
1868 innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + (this.buttonText || 'Need Text') + '</span></div>',
1870 'aria-live': 'polite', // let the screen reader user know that the text of the button may change
1874 return vjs.Component.prototype.createEl.call(this, type, props);
1877 vjs.Button.prototype.buildCSSClass = function(){
1878 // TODO: Change vjs-control to vjs-button?
1879 return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);
1882 // Click - Override with specific functionality for button
1883 vjs.Button.prototype.onClick = function(){};
1885 // Focus - Add keyboard functionality to element
1886 vjs.Button.prototype.onFocus = function(){
1887 vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
1890 // KeyPress (document level) - Trigger click when keys are pressed
1891 vjs.Button.prototype.onKeyPress = function(event){
1892 // Check for space bar (32) or enter (13) keys
1893 if (event.which == 32 || event.which == 13) {
1894 event.preventDefault();
1899 // Blur - Remove keyboard triggers
1900 vjs.Button.prototype.onBlur = function(){
1901 vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
1904 ================================================================================ */
1906 * Parent for seek bar and volume slider
1907 * @param {vjs.Player|Object} player
1908 * @param {Object=} options
1911 vjs.Slider = vjs.Component.extend({
1913 init: function(player, options){
1914 vjs.Component.call(this, player, options);
1916 // Set property names to bar and handle to match with the child Slider class is looking for
1917 this.bar = this.getChild(this.options_['barName']);
1918 this.handle = this.getChild(this.options_['handleName']);
1920 player.on(this.playerEvent, vjs.bind(this, this.update));
1922 this.on('mousedown', this.onMouseDown);
1923 this.on('touchstart', this.onMouseDown);
1924 this.on('focus', this.onFocus);
1925 this.on('blur', this.onBlur);
1926 this.on('click', this.onClick);
1928 this.player_.on('controlsvisible', vjs.bind(this, this.update));
1930 // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
1931 // this.player_.one('timeupdate', vjs.bind(this, this.update));
1933 player.ready(vjs.bind(this, this.update));
1935 this.boundEvents = {};
1939 vjs.Slider.prototype.createEl = function(type, props) {
1940 props = props || {};
1941 // Add the slider element class to all sub classes
1942 props.className = props.className + ' vjs-slider';
1943 props = vjs.obj.merge({
1947 'aria-valuemax': 100,
1951 return vjs.Component.prototype.createEl.call(this, type, props);
1954 vjs.Slider.prototype.onMouseDown = function(event){
1955 event.preventDefault();
1956 vjs.blockTextSelection();
1958 this.boundEvents.move = vjs.bind(this, this.onMouseMove);
1959 this.boundEvents.end = vjs.bind(this, this.onMouseUp);
1961 vjs.on(document, 'mousemove', this.boundEvents.move);
1962 vjs.on(document, 'mouseup', this.boundEvents.end);
1963 vjs.on(document, 'touchmove', this.boundEvents.move);
1964 vjs.on(document, 'touchend', this.boundEvents.end);
1966 this.onMouseMove(event);
1969 vjs.Slider.prototype.onMouseUp = function() {
1970 vjs.unblockTextSelection();
1971 vjs.off(document, 'mousemove', this.boundEvents.move, false);
1972 vjs.off(document, 'mouseup', this.boundEvents.end, false);
1973 vjs.off(document, 'touchmove', this.boundEvents.move, false);
1974 vjs.off(document, 'touchend', this.boundEvents.end, false);
1979 vjs.Slider.prototype.update = function(){
1980 // In VolumeBar init we have a setTimeout for update that pops and update to the end of the
1981 // execution stack. The player is destroyed before then update will cause an error
1982 if (!this.el_) return;
1984 // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
1985 // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
1986 // var progress = (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
1989 progress = this.getPercent(),
1990 handle = this.handle,
1993 // Protect against no duration and other division issues
1994 if (isNaN(progress)) { progress = 0; }
1996 barProgress = progress;
1998 // If there is a handle, we need to account for the handle in our calculation for progress bar
1999 // so that it doesn't fall short of or extend past the handle.
2003 boxWidth = box.offsetWidth,
2005 handleWidth = handle.el().offsetWidth,
2007 // The width of the handle in percent of the containing box
2008 // In IE, widths may not be ready yet causing NaN
2009 handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
2011 // Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
2012 // There is a margin of half the handle's width on both sides.
2013 boxAdjustedPercent = 1 - handlePercent,
2015 // Adjust the progress that we'll use to set widths to the new adjusted box width
2016 adjustedProgress = progress * boxAdjustedPercent;
2018 // The bar does reach the left side, so we need to account for this in the bar's width
2019 barProgress = adjustedProgress + (handlePercent / 2);
2021 // Move the handle from the left based on the adjected progress
2022 handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';
2025 // Set the new bar width
2026 bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
2029 vjs.Slider.prototype.calculateDistance = function(event){
2030 var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;
2033 box = vjs.findPosition(el);
2034 boxW = boxH = el.offsetWidth;
2035 handle = this.handle;
2037 if (this.options_.vertical) {
2040 if (event.changedTouches) {
2041 pageY = event.changedTouches[0].pageY;
2043 pageY = event.pageY;
2047 var handleH = handle.el().offsetHeight;
2048 // Adjusted X and Width, so handle doesn't go outside the bar
2049 boxY = boxY + (handleH / 2);
2050 boxH = boxH - handleH;
2053 // Percent that the click is through the adjusted area
2054 return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
2059 if (event.changedTouches) {
2060 pageX = event.changedTouches[0].pageX;
2062 pageX = event.pageX;
2066 var handleW = handle.el().offsetWidth;
2068 // Adjusted X and Width, so handle doesn't go outside the bar
2069 boxX = boxX + (handleW / 2);
2070 boxW = boxW - handleW;
2073 // Percent that the click is through the adjusted area
2074 return Math.max(0, Math.min(1, (pageX - boxX) / boxW));
2078 vjs.Slider.prototype.onFocus = function(){
2079 vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
2082 vjs.Slider.prototype.onKeyPress = function(event){
2083 if (event.which == 37) { // Left Arrow
2084 event.preventDefault();
2086 } else if (event.which == 39) { // Right Arrow
2087 event.preventDefault();
2092 vjs.Slider.prototype.onBlur = function(){
2093 vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
2097 * Listener for click events on slider, used to prevent clicks
2098 * from bubbling up to parent elements like button menus.
2099 * @param {Object} event Event object
2101 vjs.Slider.prototype.onClick = function(event){
2102 event.stopImmediatePropagation();
2103 event.preventDefault();
2107 * SeekBar Behavior includes play progress bar, and seek handle
2108 * Needed so it can determine seek position based on handle position/size
2109 * @param {vjs.Player|Object} player
2110 * @param {Object=} options
2113 vjs.SliderHandle = vjs.Component.extend();
2116 * Default value of the slider
2119 vjs.SliderHandle.prototype.defaultValue = 0;
2122 vjs.SliderHandle.prototype.createEl = function(type, props) {
2123 props = props || {};
2124 // Add the slider element class to all sub classes
2125 props.className = props.className + ' vjs-slider-handle';
2126 props = vjs.obj.merge({
2127 innerHTML: '<span class="vjs-control-text">'+this.defaultValue+'</span>'
2130 return vjs.Component.prototype.createEl.call(this, 'div', props);
2133 ================================================================================ */
2135 * The base for text track and settings menu buttons.
2136 * @param {vjs.Player|Object} player
2137 * @param {Object=} options
2140 vjs.Menu = vjs.Component.extend();
2143 * Add a menu item to the menu
2144 * @param {Object|String} component Component or component type to add
2146 vjs.Menu.prototype.addItem = function(component){
2147 this.addChild(component);
2148 component.on('click', vjs.bind(this, function(){
2149 this.unlockShowing();
2154 vjs.Menu.prototype.createEl = function(){
2155 var contentElType = this.options().contentElType || 'ul';
2156 this.contentEl_ = vjs.createEl(contentElType, {
2157 className: 'vjs-menu-content'
2159 var el = vjs.Component.prototype.createEl.call(this, 'div', {
2160 append: this.contentEl_,
2161 className: 'vjs-menu'
2163 el.appendChild(this.contentEl_);
2165 // Prevent clicks from bubbling up. Needed for Menu Buttons,
2166 // where a click on the parent is significant
2167 vjs.on(el, 'click', function(event){
2168 event.preventDefault();
2169 event.stopImmediatePropagation();
2177 * @param {vjs.Player|Object} player
2178 * @param {Object=} options
2181 vjs.MenuItem = vjs.Button.extend({
2183 init: function(player, options){
2184 vjs.Button.call(this, player, options);
2185 this.selected(options['selected']);
2190 vjs.MenuItem.prototype.createEl = function(type, props){
2191 return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({
2192 className: 'vjs-menu-item',
2193 innerHTML: this.options_['label']
2198 vjs.MenuItem.prototype.onClick = function(){
2199 this.selected(true);
2203 * Set this menu item as selected or not
2204 * @param {Boolean} selected
2206 vjs.MenuItem.prototype.selected = function(selected){
2208 this.addClass('vjs-selected');
2209 this.el_.setAttribute('aria-selected',true);
2211 this.removeClass('vjs-selected');
2212 this.el_.setAttribute('aria-selected',false);
2218 * A button class with a popup menu
2219 * @param {vjs.Player|Object} player
2220 * @param {Object=} options
2223 vjs.MenuButton = vjs.Button.extend({
2225 init: function(player, options){
2226 vjs.Button.call(this, player, options);
2228 this.menu = this.createMenu();
2230 // Add list to element
2231 this.addChild(this.menu);
2233 // Automatically hide empty menu buttons
2234 if (this.items && this.items.length === 0) {
2238 this.on('keyup', this.onKeyPress);
2239 this.el_.setAttribute('aria-haspopup', true);
2240 this.el_.setAttribute('role', 'button');
2245 * Track the state of the menu button
2248 vjs.MenuButton.prototype.buttonPressed_ = false;
2250 vjs.MenuButton.prototype.createMenu = function(){
2251 var menu = new vjs.Menu(this.player_);
2253 // Add a title list item to the top
2254 if (this.options().title) {
2255 menu.el().appendChild(vjs.createEl('li', {
2256 className: 'vjs-menu-title',
2257 innerHTML: vjs.capitalize(this.kind_),
2262 this.items = this.createItems();
2265 // Add menu items to the menu
2266 for (var i = 0; i < this.items.length; i++) {
2267 menu.addItem(this.items[i]);
2275 * Create the list of menu items. Specific to each subclass.
2277 vjs.MenuButton.prototype.createItems = function(){};
2280 vjs.MenuButton.prototype.buildCSSClass = function(){
2281 return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);
2284 // Focus - Add keyboard functionality to element
2285 // This function is not needed anymore. Instead, the keyboard functionality is handled by
2286 // treating the button as triggering a submenu. When the button is pressed, the submenu
2287 // appears. Pressing the button again makes the submenu disappear.
2288 vjs.MenuButton.prototype.onFocus = function(){};
2289 // Can't turn off list display that we turned on with focus, because list would go away.
2290 vjs.MenuButton.prototype.onBlur = function(){};
2292 vjs.MenuButton.prototype.onClick = function(){
2293 // When you click the button it adds focus, which will show the menu indefinitely.
2294 // So we'll remove focus when the mouse leaves the button.
2295 // Focus is needed for tab navigation.
2296 this.one('mouseout', vjs.bind(this, function(){
2297 this.menu.unlockShowing();
2300 if (this.buttonPressed_){
2301 this.unpressButton();
2307 vjs.MenuButton.prototype.onKeyPress = function(event){
2308 event.preventDefault();
2310 // Check for space bar (32) or enter (13) keys
2311 if (event.which == 32 || event.which == 13) {
2312 if (this.buttonPressed_){
2313 this.unpressButton();
2317 // Check for escape (27) key
2318 } else if (event.which == 27){
2319 if (this.buttonPressed_){
2320 this.unpressButton();
2325 vjs.MenuButton.prototype.pressButton = function(){
2326 this.buttonPressed_ = true;
2327 this.menu.lockShowing();
2328 this.el_.setAttribute('aria-pressed', true);
2329 if (this.items && this.items.length > 0) {
2330 this.items[0].el().focus(); // set the focus to the title of the submenu
2334 vjs.MenuButton.prototype.unpressButton = function(){
2335 this.buttonPressed_ = false;
2336 this.menu.unlockShowing();
2337 this.el_.setAttribute('aria-pressed', false);
2341 * Main player class. A player instance is returned by _V_(id);
2342 * @param {Element} tag The original video tag used for configuring options
2343 * @param {Object=} options Player options
2344 * @param {Function=} ready Ready callback function
2347 vjs.Player = vjs.Component.extend({
2349 init: function(tag, options, ready){
2350 this.tag = tag; // Store the original tag used to set options
2353 // The options argument overrides options set in the video tag
2354 // which overrides globally set options.
2355 // This latter part coincides with the load order
2356 // (tag must exist before Player)
2357 options = vjs.obj.merge(this.getTagSettings(tag), options);
2359 // Cache for video property values.
2363 this.poster_ = options['poster'];
2365 this.controls_ = options['controls'];
2366 // Use native controls for iOS and Android by default
2367 // until controls are more stable on those devices.
2368 if (options['customControlsOnMobile'] !== true && (vjs.IS_IOS || vjs.IS_ANDROID)) {
2369 tag.controls = options['controls'];
2370 this.controls_ = false;
2372 // Original tag settings stored in options
2373 // now remove immediately so native controls don't flash.
2374 tag.controls = false;
2377 // Run base component initializing with new options.
2378 // Builds the element through createEl()
2379 // Inits and embeds any child components in opts
2380 vjs.Component.call(this, this, options, ready);
2382 // Firstplay event implimentation. Not sold on the event yet.
2383 // Could probably just check currentTime==0?
2384 this.one('play', function(e){
2385 var fpEvent = { type: 'firstplay', target: this.el_ };
2386 // Using vjs.trigger so we can check if default was prevented
2387 var keepGoing = vjs.trigger(this.el_, fpEvent);
2391 e.stopPropagation();
2392 e.stopImmediatePropagation();
2396 this.on('ended', this.onEnded);
2397 this.on('play', this.onPlay);
2398 this.on('firstplay', this.onFirstPlay);
2399 this.on('pause', this.onPause);
2400 this.on('progress', this.onProgress);
2401 this.on('durationchange', this.onDurationChange);
2402 this.on('error', this.onError);
2403 this.on('fullscreenchange', this.onFullscreenChange);
2405 // Make player easily findable by ID
2406 vjs.players[this.id_] = this;
2408 if (options['plugins']) {
2409 vjs.obj.each(options['plugins'], function(key, val){
2417 * Player instance options, surfaced using vjs.options
2418 * vjs.options = vjs.Player.prototype.options_
2419 * Make changes in vjs.options, not here.
2420 * All options should use string keys so they avoid
2421 * renaming by closure compiler
2425 vjs.Player.prototype.options_ = vjs.options;
2427 vjs.Player.prototype.dispose = function(){
2428 // this.isReady_ = false;
2430 // Kill reference to this player
2431 vjs.players[this.id_] = null;
2432 if (this.tag && this.tag['player']) { this.tag['player'] = null; }
2433 if (this.el_ && this.el_['player']) { this.el_['player'] = null; }
2435 // Ensure that tracking progress and time progress will stop and plater deleted
2436 this.stopTrackingProgress();
2437 this.stopTrackingCurrentTime();
2439 if (this.tech) { this.tech.dispose(); }
2441 // Component dispose
2442 vjs.Component.prototype.dispose.call(this);
2445 vjs.Player.prototype.getTagSettings = function(tag){
2451 vjs.obj.merge(options, vjs.getAttributeValues(tag));
2453 // Get tag children settings
2454 if (tag.hasChildNodes()) {
2455 var child, childName,
2456 children = tag.childNodes,
2458 j = children.length;
2460 for (; i < j; i++) {
2461 child = children[i];
2462 // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
2463 childName = child.nodeName.toLowerCase();
2465 if (childName === 'source') {
2466 options['sources'].push(vjs.getAttributeValues(child));
2468 } else if (childName === 'track') {
2469 options['tracks'].push(vjs.getAttributeValues(child));
2478 vjs.Player.prototype.createEl = function(){
2479 var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
2482 // Remove width/height attrs from tag so CSS can make it 100% width/height
2483 tag.removeAttribute('width');
2484 tag.removeAttribute('height');
2485 // Empty video tag sources and tracks so the built-in player doesn't use them also.
2486 // This may not be fast enough to stop HTML5 browsers from reading the tags
2487 // so we'll need to turn off any default tracks if we're manually doing
2488 // captions and subtitles. videoElement.textTracks
2489 if (tag.hasChildNodes()) {
2490 var nrOfChildNodes = tag.childNodes.length;
2491 for (var i=0,j=tag.childNodes;i<nrOfChildNodes;i++) {
2492 if (j[0].nodeName.toLowerCase() == 'source' || j[0].nodeName.toLowerCase() == 'track') {
2493 tag.removeChild(j[0]);
2498 // Make sure tag ID exists
2499 tag.id = tag.id || 'vjs_video_' + vjs.guid++;
2501 // Give video tag ID and class to player div
2502 // ID will now reference player box, not the video tag
2504 el.className = tag.className;
2506 // Update tag id/class for use as HTML5 playback tech
2507 // Might think we should do this after embedding in container so .vjs-tech class
2508 // doesn't flash 100% width/height, but class only applies with .video-js parent
2509 tag.id += '_html5_api';
2510 tag.className = 'vjs-tech';
2512 // Make player findable on elements
2513 tag['player'] = el['player'] = this;
2514 // Default state of video is paused
2515 this.addClass('vjs-paused');
2517 // Make box use width/height of tag, or rely on default implementation
2518 // Enforce with CSS since width/height attrs don't work on divs
2519 this.width(this.options_['width'], true); // (true) Skip resize listener on load
2520 this.height(this.options_['height'], true);
2522 // Wrap video tag in div (el/box) container
2523 if (tag.parentNode) {
2524 tag.parentNode.insertBefore(el, tag);
2526 vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
2531 // /* Media Technology (tech)
2532 // ================================================================================ */
2533 // Load/Create an instance of playback technlogy including element and API methods
2534 // And append playback element in player div.
2535 vjs.Player.prototype.loadTech = function(techName, source){
2537 // Pause and remove current playback technology
2541 // If the first time loading, HTML5 tag will exist but won't be initialized
2542 // So we need to remove it if we're not loading HTML5
2543 } else if (techName !== 'Html5' && this.tag) {
2544 this.el_.removeChild(this.tag);
2545 this.tag.player = null;
2549 this.techName = techName;
2551 // Turn off API access because we're loading a new tech that might load asynchronously
2552 this.isReady_ = false;
2554 var techReady = function(){
2555 this.player_.triggerReady();
2557 // Manually track progress in cases where the browser/flash player doesn't report it.
2558 if (!this.features.progressEvents) {
2559 this.player_.manualProgressOn();
2562 // Manually track timeudpates in cases where the browser/flash player doesn't report it.
2563 if (!this.features.timeupdateEvents) {
2564 this.player_.manualTimeUpdatesOn();
2568 // Grab tech-specific options from player options and add source and parent element to use.
2569 var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
2572 if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
2573 techOptions['startTime'] = this.cache_.currentTime;
2576 this.cache_.src = source.src;
2579 // Initialize tech instance
2580 this.tech = new window['videojs'][techName](this, techOptions);
2582 this.tech.ready(techReady);
2585 vjs.Player.prototype.unloadTech = function(){
2586 this.isReady_ = false;
2587 this.tech.dispose();
2589 // Turn off any manual progress or timeupdate tracking
2590 if (this.manualProgress) { this.manualProgressOff(); }
2592 if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
2597 // There's many issues around changing the size of a Flash (or other plugin) object.
2598 // First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
2599 // Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
2600 // To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
2601 // reloadTech: function(betweenFn){
2602 // vjs.log('unloadingTech')
2603 // this.unloadTech();
2604 // vjs.log('unloadedTech')
2605 // if (betweenFn) { betweenFn.call(); }
2606 // vjs.log('LoadingTech')
2607 // this.loadTech(this.techName, { src: this.cache_.src })
2608 // vjs.log('loadedTech')
2611 /* Fallbacks for unsupported event types
2612 ================================================================================ */
2613 // Manually trigger progress events based on changes to the buffered amount
2614 // Many flash players and older HTML5 browsers don't send progress or progress-like events
2615 vjs.Player.prototype.manualProgressOn = function(){
2616 this.manualProgress = true;
2618 // Trigger progress watching when a source begins loading
2619 this.trackProgress();
2621 // Watch for a native progress event call on the tech element
2622 // In HTML5, some older versions don't support the progress event
2623 // So we're assuming they don't, and turning off manual progress if they do.
2624 // As opposed to doing user agent detection
2625 this.tech.one('progress', function(){
2627 // Update known progress support for this playback technology
2628 this.features.progressEvents = true;
2630 // Turn off manual progress tracking
2631 this.player_.manualProgressOff();
2635 vjs.Player.prototype.manualProgressOff = function(){
2636 this.manualProgress = false;
2637 this.stopTrackingProgress();
2640 vjs.Player.prototype.trackProgress = function(){
2642 this.progressInterval = setInterval(vjs.bind(this, function(){
2643 // Don't trigger unless buffered amount is greater than last time
2644 // log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
2645 /* TODO: update for multiple buffered regions */
2646 if (this.cache_.bufferEnd < this.buffered().end(0)) {
2647 this.trigger('progress');
2648 } else if (this.bufferedPercent() == 1) {
2649 this.stopTrackingProgress();
2650 this.trigger('progress'); // Last update
2654 vjs.Player.prototype.stopTrackingProgress = function(){ clearInterval(this.progressInterval); };
2656 /* Time Tracking -------------------------------------------------------------- */
2657 vjs.Player.prototype.manualTimeUpdatesOn = function(){
2658 this.manualTimeUpdates = true;
2660 this.on('play', this.trackCurrentTime);
2661 this.on('pause', this.stopTrackingCurrentTime);
2662 // timeupdate is also called by .currentTime whenever current time is set
2664 // Watch for native timeupdate event
2665 this.tech.one('timeupdate', function(){
2666 // Update known progress support for this playback technology
2667 this.features.timeupdateEvents = true;
2668 // Turn off manual progress tracking
2669 this.player_.manualTimeUpdatesOff();
2673 vjs.Player.prototype.manualTimeUpdatesOff = function(){
2674 this.manualTimeUpdates = false;
2675 this.stopTrackingCurrentTime();
2676 this.off('play', this.trackCurrentTime);
2677 this.off('pause', this.stopTrackingCurrentTime);
2680 vjs.Player.prototype.trackCurrentTime = function(){
2681 if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
2682 this.currentTimeInterval = setInterval(vjs.bind(this, function(){
2683 this.trigger('timeupdate');
2684 }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
2687 // Turn off play progress tracking (when paused or dragging)
2688 vjs.Player.prototype.stopTrackingCurrentTime = function(){ clearInterval(this.currentTimeInterval); };
2690 // /* Player event handlers (how the player reacts to certain events)
2691 // ================================================================================ */
2692 vjs.Player.prototype.onEnded = function(){
2693 if (this.options_['loop']) {
2694 this.currentTime(0);
2699 vjs.Player.prototype.onPlay = function(){
2700 vjs.removeClass(this.el_, 'vjs-paused');
2701 vjs.addClass(this.el_, 'vjs-playing');
2704 vjs.Player.prototype.onFirstPlay = function(){
2705 //If the first starttime attribute is specified
2706 //then we will start at the given offset in seconds
2707 if(this.options_['starttime']){
2708 this.currentTime(this.options_['starttime']);
2712 vjs.Player.prototype.onPause = function(){
2713 vjs.removeClass(this.el_, 'vjs-playing');
2714 vjs.addClass(this.el_, 'vjs-paused');
2717 vjs.Player.prototype.onProgress = function(){
2718 // Add custom event for when source is finished downloading.
2719 if (this.bufferedPercent() == 1) {
2720 this.trigger('loadedalldata');
2724 // Update duration with durationchange event
2725 // Allows for cacheing value instead of asking player each time.
2726 vjs.Player.prototype.onDurationChange = function(){
2727 this.duration(this.techGet('duration'));
2730 vjs.Player.prototype.onError = function(e) {
2731 vjs.log('Video Error', e);
2734 vjs.Player.prototype.onFullscreenChange = function(e) {
2735 if (this.isFullScreen) {
2736 this.addClass('vjs-fullscreen');
2738 this.removeClass('vjs-fullscreen');
2743 // ================================================================================ */
2746 * Object for cached values.
2749 vjs.Player.prototype.cache_;
2751 vjs.Player.prototype.getCache = function(){
2755 // Pass values to the playback tech
2756 vjs.Player.prototype.techCall = function(method, arg){
2757 // If it's not ready yet, call method when it is
2758 if (this.tech && this.tech.isReady_) {
2759 this.tech.ready(function(){
2763 // Otherwise call method now
2766 this.tech[method](arg);
2774 // Get calls can't wait for the tech, and sometimes don't need to.
2775 vjs.Player.prototype.techGet = function(method){
2777 // Make sure there is a tech
2778 // if (!this.tech) {
2782 if (this.tech.isReady_) {
2784 // Flash likes to die and reload when you hide or reposition it.
2785 // In these cases the object methods go away and we get errors.
2786 // When that happens we'll catch the errors and inform tech that it's not ready any more.
2788 return this.tech[method]();
2790 // When building additional tech libs, an expected method may not be defined yet
2791 if (this.tech[method] === undefined) {
2792 vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);
2794 // When a method isn't available on the object it throws a TypeError
2795 if (e.name == 'TypeError') {
2796 vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);
2797 this.tech.isReady_ = false;
2810 * Start media playback
2811 * http://dev.w3.org/html5/spec/video.html#dom-media-play
2812 * We're triggering the 'play' event here instead of relying on the
2813 * media element to allow using event.preventDefault() to stop
2814 * play from happening if desired. Usecase: preroll ads.
2816 vjs.Player.prototype.play = function(){
2817 this.techCall('play');
2821 // http://dev.w3.org/html5/spec/video.html#dom-media-pause
2822 vjs.Player.prototype.pause = function(){
2823 this.techCall('pause');
2827 // http://dev.w3.org/html5/spec/video.html#dom-media-paused
2828 // The initial state of paused should be true (in Safari it's actually false)
2829 vjs.Player.prototype.paused = function(){
2830 return (this.techGet('paused') === false) ? false : true;
2833 // http://dev.w3.org/html5/spec/video.html#dom-media-currenttime
2834 vjs.Player.prototype.currentTime = function(seconds){
2835 if (seconds !== undefined) {
2837 // Cache the last set value for smoother scrubbing.
2838 this.cache_.lastSetCurrentTime = seconds;
2840 this.techCall('setCurrentTime', seconds);
2842 // Improve the accuracy of manual timeupdates
2843 if (this.manualTimeUpdates) { this.trigger('timeupdate'); }
2848 // Cache last currentTime and return
2849 // Default to 0 seconds
2850 return this.cache_.currentTime = (this.techGet('currentTime') || 0);
2853 // http://dev.w3.org/html5/spec/video.html#dom-media-duration
2854 // Duration should return NaN if not available. ParseFloat will turn false-ish values to NaN.
2855 vjs.Player.prototype.duration = function(seconds){
2856 if (seconds !== undefined) {
2858 // Cache the last set value for optimiized scrubbing (esp. Flash)
2859 this.cache_.duration = parseFloat(seconds);
2864 return this.cache_.duration;
2867 // Calculates how much time is left. Not in spec, but useful.
2868 vjs.Player.prototype.remainingTime = function(){
2869 return this.duration() - this.currentTime();
2872 // http://dev.w3.org/html5/spec/video.html#dom-media-buffered
2873 // Buffered returns a timerange object.
2874 // Kind of like an array of portions of the video that have been downloaded.
2875 // So far no browsers return more than one range (portion)
2876 vjs.Player.prototype.buffered = function(){
2877 var buffered = this.techGet('buffered'),
2879 // Default end to 0 and store in values
2880 end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
2882 if (buffered && buffered.length > 0 && buffered.end(0) !== end) {
2883 end = buffered.end(0);
2884 // Storing values allows them be overridden by setBufferedFromProgress
2885 this.cache_.bufferEnd = end;
2888 return vjs.createTimeRange(start, end);
2891 // Calculates amount of buffer is full. Not in spec but useful.
2892 vjs.Player.prototype.bufferedPercent = function(){
2893 return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
2896 // http://dev.w3.org/html5/spec/video.html#dom-media-volume
2897 vjs.Player.prototype.volume = function(percentAsDecimal){
2900 if (percentAsDecimal !== undefined) {
2901 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
2902 this.cache_.volume = vol;
2903 this.techCall('setVolume', vol);
2904 vjs.setLocalStorage('volume', vol);
2908 // Default to 1 when returning current volume.
2909 vol = parseFloat(this.techGet('volume'));
2910 return (isNaN(vol)) ? 1 : vol;
2913 // http://dev.w3.org/html5/spec/video.html#attr-media-muted
2914 vjs.Player.prototype.muted = function(muted){
2915 if (muted !== undefined) {
2916 this.techCall('setMuted', muted);
2919 return this.techGet('muted') || false; // Default to false
2922 // Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
2923 vjs.Player.prototype.supportsFullScreen = function(){ return this.techGet('supportsFullScreen') || false; };
2925 // Turn on fullscreen (or window) mode
2926 vjs.Player.prototype.requestFullScreen = function(){
2927 var requestFullScreen = vjs.support.requestFullScreen;
2928 this.isFullScreen = true;
2930 if (requestFullScreen) {
2931 // the browser supports going fullscreen at the element level so we can
2932 // take the controls fullscreen as well as the video
2934 // Trigger fullscreenchange event after change
2935 vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
2936 this.isFullScreen = document[requestFullScreen.isFullScreen];
2938 // If cancelling fullscreen, remove event listener.
2939 if (this.isFullScreen === false) {
2940 vjs.off(document, requestFullScreen.eventName, arguments.callee);
2945 // Flash and other plugins get reloaded when you take their parent to fullscreen.
2946 // To fix that we'll remove the tech, and reload it after the resize has finished.
2947 if (this.tech.features.fullscreenResize === false && this.options_['flash']['iFrameMode'] !== true) {
2952 vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
2953 vjs.off(document, requestFullScreen.eventName, arguments.callee);
2954 this.loadTech(this.techName, { src: this.cache_.src });
2957 this.el_[requestFullScreen.requestFn]();
2960 this.el_[requestFullScreen.requestFn]();
2963 this.trigger('fullscreenchange');
2965 } else if (this.tech.supportsFullScreen()) {
2966 // we can't take the video.js controls fullscreen but we can go fullscreen
2967 // with native controls
2969 this.techCall('enterFullScreen');
2971 // fullscreen isn't supported so we'll just stretch the video element to
2972 // fill the viewport
2974 this.enterFullWindow();
2975 this.trigger('fullscreenchange');
2981 vjs.Player.prototype.cancelFullScreen = function(){
2982 var requestFullScreen = vjs.support.requestFullScreen;
2984 this.isFullScreen = false;
2986 // Check for browser element fullscreen support
2987 if (requestFullScreen) {
2989 // Flash and other plugins get reloaded when you take their parent to fullscreen.
2990 // To fix that we'll remove the tech, and reload it after the resize has finished.
2991 if (this.tech.features.fullscreenResize === false && this.options_['flash']['iFrameMode'] !== true) {
2996 vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
2997 vjs.off(document, requestFullScreen.eventName, arguments.callee);
2998 this.loadTech(this.techName, { src: this.cache_.src });
3001 document[requestFullScreen.cancelFn]();
3003 document[requestFullScreen.cancelFn]();
3006 this.trigger('fullscreenchange');
3008 } else if (this.tech.supportsFullScreen()) {
3009 this.techCall('exitFullScreen');
3011 this.exitFullWindow();
3012 this.trigger('fullscreenchange');
3018 // When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
3019 vjs.Player.prototype.enterFullWindow = function(){
3020 this.isFullWindow = true;
3022 // Storing original doc overflow value to return to when fullscreen is off
3023 this.docOrigOverflow = document.documentElement.style.overflow;
3025 // Add listener for esc key to exit fullscreen
3026 vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
3028 // Hide any scroll bars
3029 document.documentElement.style.overflow = 'hidden';
3031 // Apply fullscreen styles
3032 vjs.addClass(document.body, 'vjs-full-window');
3034 this.trigger('enterFullWindow');
3036 vjs.Player.prototype.fullWindowOnEscKey = function(event){
3037 if (event.keyCode === 27) {
3038 if (this.isFullScreen === true) {
3039 this.cancelFullScreen();
3041 this.exitFullWindow();
3046 vjs.Player.prototype.exitFullWindow = function(){
3047 this.isFullWindow = false;
3048 vjs.off(document, 'keydown', this.fullWindowOnEscKey);
3050 // Unhide scroll bars.
3051 document.documentElement.style.overflow = this.docOrigOverflow;
3053 // Remove fullscreen styles
3054 vjs.removeClass(document.body, 'vjs-full-window');
3056 // Resize the box, controller, and poster to original sizes
3057 // this.positionAll();
3058 this.trigger('exitFullWindow');
3061 vjs.Player.prototype.selectSource = function(sources){
3063 // Loop through each playback technology in the options order
3064 for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
3065 var techName = vjs.capitalize(j[i]),
3066 tech = window['videojs'][techName];
3068 // Check if the browser supports this technology
3069 if (tech.isSupported()) {
3070 // Loop through each source object
3071 for (var a=0,b=sources;a<b.length;a++) {
3074 // Check if source can be played with this technology
3075 if (tech['canPlaySource'](source)) {
3076 return { source: source, tech: techName };
3085 // src is a pretty powerful function
3086 // If you pass it an array of source objects, it will find the best source to play and use that object.src
3087 // If the new source requires a new playback technology, it will switch to that.
3088 // If you pass it an object, it will set the source to object.src
3089 // If you pass it anything else (url string) it will set the video source to that
3090 vjs.Player.prototype.src = function(source){
3091 // Case: Array of source objects to choose from and pick the best to play
3092 if (source instanceof Array) {
3094 var sourceTech = this.selectSource(source),
3098 source = sourceTech.source;
3099 techName = sourceTech.tech;
3101 // If this technology is already loaded, set source
3102 if (techName == this.techName) {
3103 this.src(source); // Passing the source object
3104 // Otherwise load this technology with chosen source
3106 this.loadTech(techName, source);
3109 this.el_.appendChild(vjs.createEl('p', {
3110 innerHTML: 'Sorry, no compatible source and playback technology were found for this video. Try using another browser like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
3114 // Case: Source object { src: '', type: '' ... }
3115 } else if (source instanceof Object) {
3117 if (window['videojs'][this.techName]['canPlaySource'](source)) {
3118 this.src(source.src);
3120 // Send through tech loop to check for a compatible technology.
3124 // Case: URL String (http://myvideo...)
3126 // Cache for getting last set source
3127 this.cache_.src = source;
3129 if (!this.isReady_) {
3130 this.ready(function(){
3134 this.techCall('src', source);
3135 if (this.options_['preload'] == 'auto') {
3138 if (this.options_['autoplay']) {
3146 // Begin loading the src data
3147 // http://dev.w3.org/html5/spec/video.html#dom-media-load
3148 vjs.Player.prototype.load = function(){
3149 this.techCall('load');
3153 // http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
3154 vjs.Player.prototype.currentSrc = function(){
3155 return this.techGet('currentSrc') || this.cache_.src || '';
3158 // Attributes/Options
3159 vjs.Player.prototype.preload = function(value){
3160 if (value !== undefined) {
3161 this.techCall('setPreload', value);
3162 this.options_['preload'] = value;
3165 return this.techGet('preload');
3167 vjs.Player.prototype.autoplay = function(value){
3168 if (value !== undefined) {
3169 this.techCall('setAutoplay', value);
3170 this.options_['autoplay'] = value;
3173 return this.techGet('autoplay', value);
3175 vjs.Player.prototype.loop = function(value){
3176 if (value !== undefined) {
3177 this.techCall('setLoop', value);
3178 this.options_['loop'] = value;
3181 return this.techGet('loop');
3185 * The url of the poster image source.
3189 vjs.Player.prototype.poster_;
3192 * Get or set the poster image source url.
3193 * @param {String} src Poster image source URL
3194 * @return {String} Poster image source URL or null
3196 vjs.Player.prototype.poster = function(src){
3197 if (src !== undefined) {
3200 return this.poster_;
3204 * Whether or not the controls are showing
3208 vjs.Player.prototype.controls_;
3211 * Get or set whether or not the controls are showing.
3212 * @param {Boolean} controls Set controls to showing or not
3213 * @return {Boolean} Controls are showing
3215 vjs.Player.prototype.controls = function(controls){
3216 if (controls !== undefined) {
3217 // Don't trigger a change event unless it actually changed
3218 if (this.controls_ !== controls) {
3219 this.controls_ = !!controls; // force boolean
3220 this.trigger('controlschange');
3223 return this.controls_;
3226 vjs.Player.prototype.error = function(){ return this.techGet('error'); };
3227 vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
3229 // Methods to add support for
3230 // networkState: function(){ return this.techCall('networkState'); },
3231 // readyState: function(){ return this.techCall('readyState'); },
3232 // seeking: function(){ return this.techCall('seeking'); },
3233 // initialTime: function(){ return this.techCall('initialTime'); },
3234 // startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
3235 // played: function(){ return this.techCall('played'); },
3236 // seekable: function(){ return this.techCall('seekable'); },
3237 // videoTracks: function(){ return this.techCall('videoTracks'); },
3238 // audioTracks: function(){ return this.techCall('audioTracks'); },
3239 // videoWidth: function(){ return this.techCall('videoWidth'); },
3240 // videoHeight: function(){ return this.techCall('videoHeight'); },
3241 // defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
3242 // playbackRate: function(){ return this.techCall('playbackRate'); },
3243 // mediaGroup: function(){ return this.techCall('mediaGroup'); },
3244 // controller: function(){ return this.techCall('controller'); },
3245 // defaultMuted: function(){ return this.techCall('defaultMuted'); }
3248 // currentSrcList: the array of sources including other formats and bitrates
3249 // playList: array of source lists in order of playback
3251 // RequestFullscreen API
3253 var prefix, requestFS, div;
3255 div = document.createElement('div');
3260 // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
3261 // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
3262 if (div.cancelFullscreen !== undefined) {
3263 requestFS.requestFn = 'requestFullscreen';
3264 requestFS.cancelFn = 'exitFullscreen';
3265 requestFS.eventName = 'fullscreenchange';
3266 requestFS.isFullScreen = 'fullScreen';
3268 // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations
3269 // that use prefixes and vary slightly from the new W3C spec. Specifically,
3270 // using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.
3271 // Other browsers don't have any hints of which version they might follow yet,
3272 // so not going to try to predict by looping through all prefixes.
3275 if (document.mozCancelFullScreen) {
3277 requestFS.isFullScreen = prefix + 'FullScreen';
3280 requestFS.isFullScreen = prefix + 'IsFullScreen';
3283 if (div[prefix + 'RequestFullScreen']) {
3284 requestFS.requestFn = prefix + 'RequestFullScreen';
3285 requestFS.cancelFn = prefix + 'CancelFullScreen';
3287 requestFS.eventName = prefix + 'fullscreenchange';
3290 if (document[requestFS.cancelFn]) {
3291 vjs.support.requestFullScreen = requestFS;
3296 * Container of main controls
3297 * @param {vjs.Player|Object} player
3298 * @param {Object=} options
3301 vjs.ControlBar = vjs.Component.extend({
3303 init: function(player, options){
3304 vjs.Component.call(this, player, options);
3306 if (!player.controls()) {
3310 player.one('play', vjs.bind(this, function(){
3312 fadeIn = vjs.bind(this, this.fadeIn),
3313 fadeOut = vjs.bind(this, this.fadeOut);
3317 if ( !('ontouchstart' in window) ) {
3318 this.player_.on('mouseover', fadeIn);
3319 this.player_.on('mouseout', fadeOut);
3320 this.player_.on('pause', vjs.bind(this, this.lockShowing));
3321 this.player_.on('play', vjs.bind(this, this.unlockShowing));
3325 this.player_.on('touchstart', function() {
3328 this.player_.on('touchmove', function() {
3331 this.player_.on('touchend', vjs.bind(this, function(event) {
3334 idx = this.el().className.search('fade-in');
3343 if (!this.player_.paused()) {
3344 event.preventDefault();
3351 vjs.ControlBar.prototype.options_ = {
3355 'currentTimeDisplay': {},
3357 'durationDisplay': {},
3358 'remainingTimeDisplay': {},
3359 'progressControl': {},
3360 'fullscreenToggle': {},
3361 'volumeControl': {},
3363 // 'volumeMenuButton': {}
3367 vjs.ControlBar.prototype.createEl = function(){
3368 return vjs.createEl('div', {
3369 className: 'vjs-control-bar'
3373 vjs.ControlBar.prototype.fadeIn = function(){
3374 vjs.Component.prototype.fadeIn.call(this);
3375 this.player_.trigger('controlsvisible');
3378 vjs.ControlBar.prototype.fadeOut = function(){
3379 vjs.Component.prototype.fadeOut.call(this);
3380 this.player_.trigger('controlshidden');
3382 * Button to toggle between play and pause
3383 * @param {vjs.Player|Object} player
3384 * @param {Object=} options
3387 vjs.PlayToggle = vjs.Button.extend({
3389 init: function(player, options){
3390 vjs.Button.call(this, player, options);
3392 player.on('play', vjs.bind(this, this.onPlay));
3393 player.on('pause', vjs.bind(this, this.onPause));
3397 vjs.PlayToggle.prototype.buttonText = 'Play';
3399 vjs.PlayToggle.prototype.buildCSSClass = function(){
3400 return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);
3403 // OnClick - Toggle between play and pause
3404 vjs.PlayToggle.prototype.onClick = function(){
3405 if (this.player_.paused()) {
3406 this.player_.play();
3408 this.player_.pause();
3412 // OnPlay - Add the vjs-playing class to the element so it can change appearance
3413 vjs.PlayToggle.prototype.onPlay = function(){
3414 vjs.removeClass(this.el_, 'vjs-paused');
3415 vjs.addClass(this.el_, 'vjs-playing');
3416 this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to "Pause"
3419 // OnPause - Add the vjs-paused class to the element so it can change appearance
3420 vjs.PlayToggle.prototype.onPause = function(){
3421 vjs.removeClass(this.el_, 'vjs-playing');
3422 vjs.addClass(this.el_, 'vjs-paused');
3423 this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to "Play"
3425 * Displays the current time
3426 * @param {vjs.Player|Object} player
3427 * @param {Object=} options
3430 vjs.CurrentTimeDisplay = vjs.Component.extend({
3432 init: function(player, options){
3433 vjs.Component.call(this, player, options);
3435 player.on('timeupdate', vjs.bind(this, this.updateContent));
3439 vjs.CurrentTimeDisplay.prototype.createEl = function(){
3440 var el = vjs.Component.prototype.createEl.call(this, 'div', {
3441 className: 'vjs-current-time vjs-time-controls vjs-control'
3444 this.content = vjs.createEl('div', {
3445 className: 'vjs-current-time-display',
3446 innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', // label the current time for screen reader users
3447 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
3450 el.appendChild(vjs.createEl('div').appendChild(this.content));
3454 vjs.CurrentTimeDisplay.prototype.updateContent = function(){
3455 // Allows for smooth scrubbing, when player can't keep up.
3456 var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
3457 this.content.innerHTML = '<span class="vjs-control-text">Current Time </span>' + vjs.formatTime(time, this.player_.duration());
3461 * Displays the duration
3462 * @param {vjs.Player|Object} player
3463 * @param {Object=} options
3466 vjs.DurationDisplay = vjs.Component.extend({
3468 init: function(player, options){
3469 vjs.Component.call(this, player, options);
3471 player.on('timeupdate', vjs.bind(this, this.updateContent)); // this might need to be changes to 'durationchange' instead of 'timeupdate' eventually, however the durationchange event fires before this.player_.duration() is set, so the value cannot be written out using this method. Once the order of durationchange and this.player_.duration() being set is figured out, this can be updated.
3475 vjs.DurationDisplay.prototype.createEl = function(){
3476 var el = vjs.Component.prototype.createEl.call(this, 'div', {
3477 className: 'vjs-duration vjs-time-controls vjs-control'
3480 this.content = vjs.createEl('div', {
3481 className: 'vjs-duration-display',
3482 innerHTML: '<span class="vjs-control-text">Duration Time </span>' + '0:00', // label the duration time for screen reader users
3483 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
3486 el.appendChild(vjs.createEl('div').appendChild(this.content));
3490 vjs.DurationDisplay.prototype.updateContent = function(){
3491 if (this.player_.duration()) {
3492 this.content.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(this.player_.duration()); // label the duration time for screen reader users
3497 * Time Separator (Not used in main skin, but still available, and could be used as a 'spare element')
3498 * @param {vjs.Player|Object} player
3499 * @param {Object=} options
3502 vjs.TimeDivider = vjs.Component.extend({
3504 init: function(player, options){
3505 vjs.Component.call(this, player, options);
3509 vjs.TimeDivider.prototype.createEl = function(){
3510 return vjs.Component.prototype.createEl.call(this, 'div', {
3511 className: 'vjs-time-divider',
3512 innerHTML: '<div><span>/</span></div>'
3517 * Displays the time left in the video
3518 * @param {vjs.Player|Object} player
3519 * @param {Object=} options
3522 vjs.RemainingTimeDisplay = vjs.Component.extend({
3524 init: function(player, options){
3525 vjs.Component.call(this, player, options);
3527 player.on('timeupdate', vjs.bind(this, this.updateContent));
3531 vjs.RemainingTimeDisplay.prototype.createEl = function(){
3532 var el = vjs.Component.prototype.createEl.call(this, 'div', {
3533 className: 'vjs-remaining-time vjs-time-controls vjs-control'
3536 this.content = vjs.createEl('div', {
3537 className: 'vjs-remaining-time-display',
3538 innerHTML: '<span class="vjs-control-text">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users
3539 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
3542 el.appendChild(vjs.createEl('div').appendChild(this.content));
3546 vjs.RemainingTimeDisplay.prototype.updateContent = function(){
3547 if (this.player_.duration()) {
3548 if (this.player_.duration()) {
3549 this.content.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());
3553 // Allows for smooth scrubbing, when player can't keep up.
3554 // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
3555 // this.content.innerHTML = vjs.formatTime(time, this.player_.duration());
3557 * Toggle fullscreen video
3558 * @param {vjs.Player|Object} player
3559 * @param {Object=} options
3562 vjs.FullscreenToggle = vjs.Button.extend({
3564 init: function(player, options){
3565 vjs.Button.call(this, player, options);
3569 vjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';
3571 vjs.FullscreenToggle.prototype.buildCSSClass = function(){
3572 return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);
3575 vjs.FullscreenToggle.prototype.onClick = function(){
3576 if (!this.player_.isFullScreen) {
3577 this.player_.requestFullScreen();
3578 this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to "Non-Fullscreen"
3580 this.player_.cancelFullScreen();
3581 this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to "Fullscreen"
3584 * Seek, Load Progress, and Play Progress
3585 * @param {vjs.Player|Object} player
3586 * @param {Object=} options
3589 vjs.ProgressControl = vjs.Component.extend({
3591 init: function(player, options){
3592 vjs.Component.call(this, player, options);
3596 vjs.ProgressControl.prototype.options_ = {
3602 vjs.ProgressControl.prototype.createEl = function(){
3603 return vjs.Component.prototype.createEl.call(this, 'div', {
3604 className: 'vjs-progress-control vjs-control'
3609 * Seek Bar and holder for the progress bars
3610 * @param {vjs.Player|Object} player
3611 * @param {Object=} options
3614 vjs.SeekBar = vjs.Slider.extend({
3616 init: function(player, options){
3617 vjs.Slider.call(this, player, options);
3618 player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));
3619 player.ready(vjs.bind(this, this.updateARIAAttributes));
3623 vjs.SeekBar.prototype.options_ = {
3625 'loadProgressBar': {},
3626 'playProgressBar': {},
3629 'barName': 'playProgressBar',
3630 'handleName': 'seekHandle'
3633 vjs.SeekBar.prototype.playerEvent = 'timeupdate';
3635 vjs.SeekBar.prototype.createEl = function(){
3636 return vjs.Slider.prototype.createEl.call(this, 'div', {
3637 className: 'vjs-progress-holder',
3638 'aria-label': 'video progress bar'
3642 vjs.SeekBar.prototype.updateARIAAttributes = function(){
3643 // Allows for smooth scrubbing, when player can't keep up.
3644 var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
3645 this.el_.setAttribute('aria-valuenow',vjs.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)
3646 this.el_.setAttribute('aria-valuetext',vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
3649 vjs.SeekBar.prototype.getPercent = function(){
3650 return this.player_.currentTime() / this.player_.duration();
3653 vjs.SeekBar.prototype.onMouseDown = function(event){
3654 vjs.Slider.prototype.onMouseDown.call(this, event);
3656 this.player_.scrubbing = true;
3658 this.videoWasPlaying = !this.player_.paused();
3659 this.player_.pause();
3662 vjs.SeekBar.prototype.onMouseMove = function(event){
3663 var newTime = this.calculateDistance(event) * this.player_.duration();
3665 // Don't let video end while scrubbing.
3666 if (newTime == this.player_.duration()) { newTime = newTime - 0.1; }
3668 // Set new time (tell player to seek to new time)
3669 this.player_.currentTime(newTime);
3672 vjs.SeekBar.prototype.onMouseUp = function(event){
3673 vjs.Slider.prototype.onMouseUp.call(this, event);
3675 this.player_.scrubbing = false;
3676 if (this.videoWasPlaying) {
3677 this.player_.play();
3681 vjs.SeekBar.prototype.stepForward = function(){
3682 this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
3685 vjs.SeekBar.prototype.stepBack = function(){
3686 this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
3691 * Shows load progres
3692 * @param {vjs.Player|Object} player
3693 * @param {Object=} options
3696 vjs.LoadProgressBar = vjs.Component.extend({
3698 init: function(player, options){
3699 vjs.Component.call(this, player, options);
3700 player.on('progress', vjs.bind(this, this.update));
3704 vjs.LoadProgressBar.prototype.createEl = function(){
3705 return vjs.Component.prototype.createEl.call(this, 'div', {
3706 className: 'vjs-load-progress',
3707 innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>'
3711 vjs.LoadProgressBar.prototype.update = function(){
3712 if (this.el_.style) { this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%'; }
3717 * Shows play progress
3718 * @param {vjs.Player|Object} player
3719 * @param {Object=} options
3722 vjs.PlayProgressBar = vjs.Component.extend({
3724 init: function(player, options){
3725 vjs.Component.call(this, player, options);
3729 vjs.PlayProgressBar.prototype.createEl = function(){
3730 return vjs.Component.prototype.createEl.call(this, 'div', {
3731 className: 'vjs-play-progress',
3732 innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
3737 * SeekBar component includes play progress bar, and seek handle
3738 * Needed so it can determine seek position based on handle position/size
3739 * @param {vjs.Player|Object} player
3740 * @param {Object=} options
3743 vjs.SeekHandle = vjs.SliderHandle.extend();
3746 vjs.SeekHandle.prototype.defaultValue = '00:00';
3749 vjs.SeekHandle.prototype.createEl = function(){
3750 return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
3751 className: 'vjs-seek-handle'
3754 * Control the volume
3755 * @param {vjs.Player|Object} player
3756 * @param {Object=} options
3759 vjs.VolumeControl = vjs.Component.extend({
3761 init: function(player, options){
3762 vjs.Component.call(this, player, options);
3764 // hide volume controls when they're not supported by the current tech
3765 if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
3766 this.addClass('vjs-hidden');
3768 player.on('loadstart', vjs.bind(this, function(){
3769 if (player.tech.features && player.tech.features.volumeControl === false) {
3770 this.addClass('vjs-hidden');
3772 this.removeClass('vjs-hidden');
3778 vjs.VolumeControl.prototype.options_ = {
3784 vjs.VolumeControl.prototype.createEl = function(){
3785 return vjs.Component.prototype.createEl.call(this, 'div', {
3786 className: 'vjs-volume-control vjs-control'
3791 * Contains volume level
3792 * @param {vjs.Player|Object} player
3793 * @param {Object=} options
3796 vjs.VolumeBar = vjs.Slider.extend({
3798 init: function(player, options){
3799 vjs.Slider.call(this, player, options);
3800 player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));
3801 player.ready(vjs.bind(this, this.updateARIAAttributes));
3802 setTimeout(vjs.bind(this, this.update), 0); // update when elements is in DOM
3806 vjs.VolumeBar.prototype.updateARIAAttributes = function(){
3807 // Current value of volume bar as a percentage
3808 this.el_.setAttribute('aria-valuenow',vjs.round(this.player_.volume()*100, 2));
3809 this.el_.setAttribute('aria-valuetext',vjs.round(this.player_.volume()*100, 2)+'%');
3812 vjs.VolumeBar.prototype.options_ = {
3817 'barName': 'volumeLevel',
3818 'handleName': 'volumeHandle'
3821 vjs.VolumeBar.prototype.playerEvent = 'volumechange';
3823 vjs.VolumeBar.prototype.createEl = function(){
3824 return vjs.Slider.prototype.createEl.call(this, 'div', {
3825 className: 'vjs-volume-bar',
3826 'aria-label': 'volume level'
3830 vjs.VolumeBar.prototype.onMouseMove = function(event) {
3831 this.player_.volume(this.calculateDistance(event));
3834 vjs.VolumeBar.prototype.getPercent = function(){
3835 if (this.player_.muted()) {
3838 return this.player_.volume();
3842 vjs.VolumeBar.prototype.stepForward = function(){
3843 this.player_.volume(this.player_.volume() + 0.1);
3846 vjs.VolumeBar.prototype.stepBack = function(){
3847 this.player_.volume(this.player_.volume() - 0.1);
3851 * Shows volume level
3852 * @param {vjs.Player|Object} player
3853 * @param {Object=} options
3856 vjs.VolumeLevel = vjs.Component.extend({
3858 init: function(player, options){
3859 vjs.Component.call(this, player, options);
3863 vjs.VolumeLevel.prototype.createEl = function(){
3864 return vjs.Component.prototype.createEl.call(this, 'div', {
3865 className: 'vjs-volume-level',
3866 innerHTML: '<span class="vjs-control-text"></span>'
3871 * Change volume level
3872 * @param {vjs.Player|Object} player
3873 * @param {Object=} options
3876 vjs.VolumeHandle = vjs.SliderHandle.extend();
3879 vjs.VolumeHandle.prototype.defaultValue = '00:00';
3882 vjs.VolumeHandle.prototype.createEl = function(){
3883 return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
3884 className: 'vjs-volume-handle'
3888 * @param {vjs.Player|Object} player
3889 * @param {Object=} options
3892 vjs.MuteToggle = vjs.Button.extend({
3894 init: function(player, options){
3895 vjs.Button.call(this, player, options);
3897 player.on('volumechange', vjs.bind(this, this.update));
3899 // hide mute toggle if the current tech doesn't support volume control
3900 if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
3901 this.addClass('vjs-hidden');
3903 player.on('loadstart', vjs.bind(this, function(){
3904 if (player.tech.features && player.tech.features.volumeControl === false) {
3905 this.addClass('vjs-hidden');
3907 this.removeClass('vjs-hidden');
3913 vjs.MuteToggle.prototype.createEl = function(){
3914 return vjs.Button.prototype.createEl.call(this, 'div', {
3915 className: 'vjs-mute-control vjs-control',
3916 innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
3920 vjs.MuteToggle.prototype.onClick = function(){
3921 this.player_.muted( this.player_.muted() ? false : true );
3924 vjs.MuteToggle.prototype.update = function(){
3925 var vol = this.player_.volume(),
3928 if (vol === 0 || this.player_.muted()) {
3930 } else if (vol < 0.33) {
3932 } else if (vol < 0.67) {
3936 // Don't rewrite the button text if the actual text doesn't change.
3937 // This causes unnecessary and confusing information for screen reader users.
3938 // This check is needed because this function gets called every time the volume level is changed.
3939 if(this.player_.muted()){
3940 if(this.el_.children[0].children[0].innerHTML!='Unmute'){
3941 this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to "Unmute"
3944 if(this.el_.children[0].children[0].innerHTML!='Mute'){
3945 this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to "Mute"
3949 /* TODO improve muted icon classes */
3950 for (var i = 0; i < 4; i++) {
3951 vjs.removeClass(this.el_, 'vjs-vol-'+i);
3953 vjs.addClass(this.el_, 'vjs-vol-'+level);
3955 * Menu button with a popup for showing the volume slider.
3958 vjs.VolumeMenuButton = vjs.MenuButton.extend({
3960 init: function(player, options){
3961 vjs.MenuButton.call(this, player, options);
3963 // Same listeners as MuteToggle
3964 player.on('volumechange', vjs.bind(this, this.update));
3966 // hide mute toggle if the current tech doesn't support volume control
3967 if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
3968 this.addClass('vjs-hidden');
3970 player.on('loadstart', vjs.bind(this, function(){
3971 if (player.tech.features && player.tech.features.volumeControl === false) {
3972 this.addClass('vjs-hidden');
3974 this.removeClass('vjs-hidden');
3977 this.addClass('vjs-menu-button');
3981 vjs.VolumeMenuButton.prototype.createMenu = function(){
3982 var menu = new vjs.Menu(this.player_, {
3983 contentElType: 'div'
3985 var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({vertical: true}, this.options_.volumeBar));
3990 vjs.VolumeMenuButton.prototype.onClick = function(){
3991 vjs.MuteToggle.prototype.onClick.call(this);
3992 vjs.MenuButton.prototype.onClick.call(this);
3995 vjs.VolumeMenuButton.prototype.createEl = function(){
3996 return vjs.Button.prototype.createEl.call(this, 'div', {
3997 className: 'vjs-volume-menu-button vjs-menu-button vjs-control',
3998 innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
4001 vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
4003 ================================================================================ */
4005 * Poster image. Shows before the video plays.
4006 * @param {vjs.Player|Object} player
4007 * @param {Object=} options
4010 vjs.PosterImage = vjs.Button.extend({
4012 init: function(player, options){
4013 vjs.Button.call(this, player, options);
4015 if (!player.poster() || !player.controls()) {
4019 player.on('play', vjs.bind(this, this.hide));
4023 vjs.PosterImage.prototype.createEl = function(){
4024 var el = vjs.createEl('div', {
4025 className: 'vjs-poster',
4027 // Don't want poster to be tabbable.
4030 poster = this.player_.poster();
4033 if ('backgroundSize' in el.style) {
4034 el.style.backgroundImage = 'url("' + poster + '")';
4036 el.appendChild(vjs.createEl('img', { src: poster }));
4043 vjs.PosterImage.prototype.onClick = function(){
4044 this.player_.play();
4047 ================================================================================ */
4049 * Loading spinner for waiting events
4050 * @param {vjs.Player|Object} player
4051 * @param {Object=} options
4054 vjs.LoadingSpinner = vjs.Component.extend({
4056 init: function(player, options){
4057 vjs.Component.call(this, player, options);
4059 player.on('canplay', vjs.bind(this, this.hide));
4060 player.on('canplaythrough', vjs.bind(this, this.hide));
4061 player.on('playing', vjs.bind(this, this.hide));
4062 player.on('seeked', vjs.bind(this, this.hide));
4064 player.on('seeking', vjs.bind(this, this.show));
4066 // in some browsers seeking does not trigger the 'playing' event,
4067 // so we also need to trap 'seeked' if we are going to set a
4069 player.on('seeked', vjs.bind(this, this.hide));
4071 player.on('error', vjs.bind(this, this.show));
4073 // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
4074 // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing
4075 // player.on('stalled', vjs.bind(this, this.show));
4077 player.on('waiting', vjs.bind(this, this.show));
4081 vjs.LoadingSpinner.prototype.createEl = function(){
4082 return vjs.Component.prototype.createEl.call(this, 'div', {
4083 className: 'vjs-loading-spinner'
4087 ================================================================================ */
4089 * Initial play button. Shows before the video has played.
4090 * @param {vjs.Player|Object} player
4091 * @param {Object=} options
4094 vjs.BigPlayButton = vjs.Button.extend({
4096 init: function(player, options){
4097 vjs.Button.call(this, player, options);
4099 if (!player.controls()) {
4103 player.on('play', vjs.bind(this, this.hide));
4104 // player.on('ended', vjs.bind(this, this.show));
4108 vjs.BigPlayButton.prototype.createEl = function(){
4109 return vjs.Button.prototype.createEl.call(this, 'div', {
4110 className: 'vjs-big-play-button',
4111 innerHTML: '<span></span>',
4112 'aria-label': 'play video'
4116 vjs.BigPlayButton.prototype.onClick = function(){
4117 // Go back to the beginning if big play button is showing at the end.
4118 // Have to check for current time otherwise it might throw a 'not ready' error.
4119 //if(this.player_.currentTime()) {
4120 //this.player_.currentTime(0);
4122 this.player_.play();
4125 * @fileoverview Media Technology Controller - Base class for media playback technology controllers like Flash and HTML5
4129 * Base class for media (HTML5 Video, Flash) controllers
4130 * @param {vjs.Player|Object} player Central player instance
4131 * @param {Object=} options Options object
4134 vjs.MediaTechController = vjs.Component.extend({
4136 init: function(player, options, ready){
4137 vjs.Component.call(this, player, options, ready);
4139 // Make playback element clickable
4140 // this.addEvent('click', this.proxy(this.onClick));
4142 // player.triggerEvent('techready');
4146 // destroy: function(){},
4147 // createElement: function(){},
4150 * Handle a click on the media element. By default will play the media.
4152 * On android browsers, having this toggle play state interferes with being
4153 * able to toggle the controls and toggling play state with the play button
4155 vjs.MediaTechController.prototype.onClick = (function(){
4156 if (vjs.IS_ANDROID) {
4157 return function () {};
4159 return function () {
4160 if (this.player_.controls()) {
4161 if (this.player_.paused()) {
4162 this.player_.play();
4164 this.player_.pause();
4171 vjs.MediaTechController.prototype.features = {
4172 volumeControl: true,
4174 // Resizing plugins using request fullscreen reloads the plugin
4175 fullscreenResize: false,
4177 // Optional events that we can manually mimic with timers
4178 // currently not triggered by video-js-swf
4179 progressEvents: false,
4180 timeupdateEvents: false
4186 * List of default API methods for any MediaTechController
4189 vjs.media.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(',');
4190 // Create placeholder methods for each that warn when a method isn't supported by the current playback technology
4192 function createMethod(methodName){
4194 throw new Error('The "'+methodName+'" method is not available on the playback technology\'s API');
4198 for (var i = vjs.media.ApiMethods.length - 1; i >= 0; i--) {
4199 var methodName = vjs.media.ApiMethods[i];
4200 vjs.MediaTechController.prototype[vjs.media.ApiMethods[i]] = createMethod(methodName);
4203 * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
4207 * HTML5 Media Controller - Wrapper for HTML5 Media API
4208 * @param {vjs.Player|Object} player
4209 * @param {Object=} options
4210 * @param {Function=} ready
4213 vjs.Html5 = vjs.MediaTechController.extend({
4215 init: function(player, options, ready){
4216 // volume cannot be changed from 1 on iOS
4217 this.features.volumeControl = vjs.Html5.canControlVolume();
4219 // In iOS, if you move a video element in the DOM, it breaks video playback.
4220 this.features.movingMediaElementInDOM = !vjs.IS_IOS;
4222 // HTML video is able to automatically resize when going to fullscreen
4223 this.features.fullscreenResize = true;
4225 vjs.MediaTechController.call(this, player, options, ready);
4227 var source = options['source'];
4229 // If the element source is already set, we may have missed the loadstart event, and want to trigger it.
4230 // We don't want to set the source again and interrupt playback.
4231 if (source && this.el_.currentSrc == source.src) {
4232 player.trigger('loadstart');
4234 // Otherwise set the source if one was provided.
4235 } else if (source) {
4236 this.el_.src = source.src;
4239 // Chrome and Safari both have issues with autoplay.
4240 // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
4241 // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
4242 // This fixes both issues. Need to wait for API, so it updates displays correctly
4243 player.ready(function(){
4244 if (this.options_['autoplay'] && this.paused()) {
4245 this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16.
4250 this.on('click', this.onClick);
4252 this.setupTriggers();
4254 this.triggerReady();
4258 vjs.Html5.prototype.dispose = function(){
4259 vjs.MediaTechController.prototype.dispose.call(this);
4262 vjs.Html5.prototype.createEl = function(){
4263 var player = this.player_,
4264 // If possible, reuse original tag for HTML5 playback technology element
4268 // Check if this browser supports moving the element into the box.
4269 // On the iPhone video will break if you move the element,
4270 // So we have to create a brand new element.
4271 if (!el || this.features.movingMediaElementInDOM === false) {
4273 // If the original tag is still there, remove it.
4275 player.el().removeChild(el);
4276 el = el.cloneNode(false);
4278 el = vjs.createEl('video', {
4279 id:player.id() + '_html5_api',
4280 className:'vjs-tech'
4283 // associate the player with the new tag
4284 el['player'] = player;
4286 vjs.insertFirst(el, player.el());
4289 // Update specific tag settings, in case they were overridden
4290 var attrs = ['autoplay','preload','loop','muted'];
4291 for (var i = attrs.length - 1; i >= 0; i--) {
4292 var attr = attrs[i];
4293 if (player.options_[attr] !== null) {
4294 el[attr] = player.options_[attr];
4299 // jenniisawesome = true;
4302 // Make video events trigger player events
4303 // May seem verbose here, but makes other APIs possible.
4304 vjs.Html5.prototype.setupTriggers = function(){
4305 for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
4306 vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this.player_, this.eventHandler));
4309 // Triggers removed using this.off when disposed
4311 vjs.Html5.prototype.eventHandler = function(e){
4314 // No need for media events to bubble up.
4315 e.stopPropagation();
4319 vjs.Html5.prototype.play = function(){ this.el_.play(); };
4320 vjs.Html5.prototype.pause = function(){ this.el_.pause(); };
4321 vjs.Html5.prototype.paused = function(){ return this.el_.paused; };
4323 vjs.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };
4324 vjs.Html5.prototype.setCurrentTime = function(seconds){
4326 this.el_.currentTime = seconds;
4328 vjs.log(e, 'Video is not ready. (Video.js)');
4329 // this.warning(VideoJS.warnings.videoNotReady);
4333 vjs.Html5.prototype.duration = function(){ return this.el_.duration || 0; };
4334 vjs.Html5.prototype.buffered = function(){ return this.el_.buffered; };
4336 vjs.Html5.prototype.volume = function(){ return this.el_.volume; };
4337 vjs.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };
4338 vjs.Html5.prototype.muted = function(){ return this.el_.muted; };
4339 vjs.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };
4341 vjs.Html5.prototype.width = function(){ return this.el_.offsetWidth; };
4342 vjs.Html5.prototype.height = function(){ return this.el_.offsetHeight; };
4344 vjs.Html5.prototype.supportsFullScreen = function(){
4345 if (typeof this.el_.webkitEnterFullScreen == 'function') {
4347 // Seems to be broken in Chromium/Chrome && Safari in Leopard
4348 if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {
4355 vjs.Html5.prototype.enterFullScreen = function(){
4356 var video = this.el_;
4357 if (video.paused && video.networkState <= video.HAVE_METADATA) {
4358 // attempt to prime the video element for programmatic access
4359 // this isn't necessary on the desktop but shouldn't hurt
4362 // playing and pausing synchronously during the transition to fullscreen
4363 // can get iOS ~6.1 devices into a play/pause loop
4364 setTimeout(function(){
4366 video.webkitEnterFullScreen();
4369 video.webkitEnterFullScreen();
4372 vjs.Html5.prototype.exitFullScreen = function(){
4373 this.el_.webkitExitFullScreen();
4375 vjs.Html5.prototype.src = function(src){ this.el_.src = src; };
4376 vjs.Html5.prototype.load = function(){ this.el_.load(); };
4377 vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
4379 vjs.Html5.prototype.preload = function(){ return this.el_.preload; };
4380 vjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };
4381 vjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };
4382 vjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };
4383 vjs.Html5.prototype.loop = function(){ return this.el_.loop; };
4384 vjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };
4386 vjs.Html5.prototype.error = function(){ return this.el_.error; };
4387 // networkState: function(){ return this.el_.networkState; },
4388 // readyState: function(){ return this.el_.readyState; },
4389 vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
4390 // initialTime: function(){ return this.el_.initialTime; },
4391 // startOffsetTime: function(){ return this.el_.startOffsetTime; },
4392 // played: function(){ return this.el_.played; },
4393 // seekable: function(){ return this.el_.seekable; },
4394 vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
4395 // videoTracks: function(){ return this.el_.videoTracks; },
4396 // audioTracks: function(){ return this.el_.audioTracks; },
4397 // videoWidth: function(){ return this.el_.videoWidth; },
4398 // videoHeight: function(){ return this.el_.videoHeight; },
4399 // textTracks: function(){ return this.el_.textTracks; },
4400 // defaultPlaybackRate: function(){ return this.el_.defaultPlaybackRate; },
4401 // playbackRate: function(){ return this.el_.playbackRate; },
4402 // mediaGroup: function(){ return this.el_.mediaGroup; },
4403 // controller: function(){ return this.el_.controller; },
4404 vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
4406 /* HTML5 Support Testing ---------------------------------------------------- */
4408 vjs.Html5.isSupported = function(){
4409 return !!document.createElement('video').canPlayType;
4412 vjs.Html5.canPlaySource = function(srcObj){
4413 return !!document.createElement('video').canPlayType(srcObj.type);
4415 // If no Type, check ext
4419 vjs.Html5.canControlVolume = function(){
4420 var volume = vjs.TEST_VID.volume;
4421 vjs.TEST_VID.volume = (volume / 2) + 0.1;
4422 return volume !== vjs.TEST_VID.volume;
4425 // List of all HTML5 events (various uses).
4426 vjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');
4429 // HTML5 Feature detection and Device Fixes --------------------------------- //
4432 if (vjs.IS_ANDROID) {
4434 // Override Android 2.2 and less canPlayType method which is broken
4435 if (vjs.ANDROID_VERSION < 3) {
4436 document.createElement('video').constructor.prototype.canPlayType = function(type){
4437 return (type && type.toLowerCase().indexOf('video/mp4') != -1) ? 'maybe' : '';
4442 * @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API
4443 * https://github.com/zencoder/video-js-swf
4444 * Not using setupTriggers. Using global onEvent func to distribute events
4448 * HTML5 Media Controller - Wrapper for HTML5 Media API
4449 * @param {vjs.Player|Object} player
4450 * @param {Object=} options
4451 * @param {Function=} ready
4454 vjs.Flash = vjs.MediaTechController.extend({
4456 init: function(player, options, ready){
4457 vjs.MediaTechController.call(this, player, options, ready);
4459 var source = options['source'],
4461 // Which element to embed in
4462 parentEl = options['parentEl'],
4464 // Create a temporary element to be replaced by swf object
4465 placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),
4467 // Generate ID for swf object
4468 objId = player.id()+'_flash_api',
4470 // Store player options in local var for optimization
4471 // TODO: switch to using player methods instead of options
4472 // e.g. player.autoplay();
4473 playerOptions = player.options_,
4475 // Merge default flashvars with ones passed in to init
4476 flashVars = vjs.obj.merge({
4478 // SWF Callback Functions
4479 'readyFunction': 'videojs.Flash.onReady',
4480 'eventProxyFunction': 'videojs.Flash.onEvent',
4481 'errorEventProxyFunction': 'videojs.Flash.onError',
4484 'autoplay': playerOptions.autoplay,
4485 'preload': playerOptions.preload,
4486 'loop': playerOptions.loop,
4487 'muted': playerOptions.muted
4489 }, options['flashVars']),
4491 // Merge default parames with ones passed in
4492 params = vjs.obj.merge({
4493 'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
4494 'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
4495 }, options['params']),
4497 // Merge default attributes with ones passed in
4498 attributes = vjs.obj.merge({
4500 'name': objId, // Both ID and Name needed or swf to identifty itself
4502 }, options['attributes'])
4505 // If source was supplied pass as a flash var.
4507 flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
4510 // Add placeholder to player div
4511 vjs.insertFirst(placeHolder, parentEl);
4513 // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
4514 // This allows resetting the playhead when we catch the reload
4515 if (options['startTime']) {
4516 this.ready(function(){
4519 this.currentTime(options['startTime']);
4523 // Flash iFrame Mode
4524 // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
4525 // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
4526 // - Webkit when hiding the plugin
4527 // - Webkit and Firefox when using requestFullScreen on a parent element
4528 // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
4529 // Issues that remain include hiding the element and requestFullScreen in Firefox specifically
4531 // 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.
4532 // 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.
4533 // 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.
4534 // 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
4535 // 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.
4536 // 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.
4538 // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
4539 // Firefox 9 throws a security error, unleess you call location.href right before doc.write.
4540 // Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
4541 // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
4543 if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
4545 // Create iFrame with vjs-tech class so it's 100% width/height
4546 var iFrm = vjs.createEl('iframe', {
4547 'id': objId + '_iframe',
4548 'name': objId + '_iframe',
4549 'className': 'vjs-tech',
4556 // Update ready function names in flash vars for iframe window
4557 flashVars['readyFunction'] = 'ready';
4558 flashVars['eventProxyFunction'] = 'events';
4559 flashVars['errorEventProxyFunction'] = 'errors';
4561 // Tried multiple methods to get this to work in all browsers
4563 // 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.
4564 // 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
4565 // var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
4567 // var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
4568 // iDoc.body.appendChild(temp);
4570 // Tried embedding the flash object through javascript in the iframe source.
4571 // This works in webkit but still triggers the firefox security error
4572 // iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
4574 // 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
4575 // We should add an option to host the iframe locally though, because it could help a lot of issues.
4576 // iFrm.src = "iframe.html";
4578 // Wait until iFrame has loaded to write into it.
4579 vjs.on(iFrm, 'load', vjs.bind(this, function(){
4582 iWin = iFrm.contentWindow;
4584 // The one working method I found was to use the iframe's document.write() to create the swf object
4585 // This got around the security issue in all browsers except firefox.
4586 // I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
4587 // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
4588 // Plus Firefox 3.6 didn't work no matter what I tried.
4589 // if (vjs.USER_AGENT.match('Firefox')) {
4590 // iWin.location.href = '';
4593 // Get the iFrame's document depending on what the browser supports
4594 iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
4596 // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
4597 // Even tried adding /. that was mentioned in a browser security writeup
4598 // document.domain = document.domain+'/.';
4599 // iDoc.domain = document.domain+'/.';
4601 // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
4602 // iDoc.body.innerHTML = swfObjectHTML;
4604 // Tried appending the object to the iframe doc's body. Security error in all browsers.
4605 // iDoc.body.appendChild(swfObject);
4607 // Using document.write actually got around the security error that browsers were throwing.
4608 // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
4609 // Not sure why that's a security issue, but apparently it is.
4610 iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
4612 // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
4613 // So far no issues with swf ready event being called before it's set on the window.
4614 iWin['player'] = this.player_;
4616 // Create swf ready function for iFrame window
4617 iWin['ready'] = vjs.bind(this.player_, function(currSwf){
4618 var el = iDoc.getElementById(currSwf),
4622 // Update reference to playback technology element
4625 // Now that the element is ready, make a click on the swf play the video
4626 vjs.on(el, 'click', tech.bind(tech.onClick));
4628 // Make sure swf is actually ready. Sometimes the API isn't actually yet.
4629 vjs.Flash.checkReady(tech);
4632 // Create event listener for all swf events
4633 iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){
4635 if (player && player.techName === 'flash') {
4636 player.trigger(eventName);
4640 // Create error listener for all swf errors
4641 iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){
4642 vjs.log('Flash Error', eventName);
4647 // Replace placeholder with iFrame (it will load now)
4648 placeHolder.parentNode.replaceChild(iFrm, placeHolder);
4650 // If not using iFrame mode, embed as normal object
4652 vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
4657 vjs.Flash.prototype.dispose = function(){
4658 vjs.MediaTechController.prototype.dispose.call(this);
4661 vjs.Flash.prototype.play = function(){
4662 this.el_.vjs_play();
4665 vjs.Flash.prototype.pause = function(){
4666 this.el_.vjs_pause();
4669 vjs.Flash.prototype.src = function(src){
4670 // Make sure source URL is abosolute.
4671 src = vjs.getAbsoluteURL(src);
4673 this.el_.vjs_src(src);
4675 // Currently the SWF doesn't autoplay if you load a source later.
4676 // e.g. Load player w/ no source, wait 2s, set src.
4677 if (this.player_.autoplay()) {
4679 setTimeout(function(){ tech.play(); }, 0);
4683 vjs.Flash.prototype.load = function(){
4684 this.el_.vjs_load();
4687 vjs.Flash.prototype.poster = function(){
4688 this.el_.vjs_getProperty('poster');
4691 vjs.Flash.prototype.buffered = function(){
4692 return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));
4695 vjs.Flash.prototype.supportsFullScreen = function(){
4696 return false; // Flash does not allow fullscreen through javascript
4699 vjs.Flash.prototype.enterFullScreen = function(){
4704 // Create setters and getters for attributes
4705 var api = vjs.Flash.prototype,
4706 readWrite = 'preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
4707 readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
4708 // Overridden: buffered
4713 var createSetter = function(attr){
4714 var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
4715 api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };
4721 var createGetter = function(attr){
4722 api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
4727 // Create getter and setters for all read/write attributes
4728 for (i = 0; i < readWrite.length; i++) {
4729 createGetter(readWrite[i]);
4730 createSetter(readWrite[i]);
4733 // Create getters for read-only attributes
4734 for (i = 0; i < readOnly.length; i++) {
4735 createGetter(readOnly[i]);
4739 /* Flash Support Testing -------------------------------------------------------- */
4741 vjs.Flash.isSupported = function(){
4742 return vjs.Flash.version()[0] >= 10;
4743 // return swfobject.hasFlashPlayerVersion('10');
4746 vjs.Flash.canPlaySource = function(srcObj){
4747 if (srcObj.type in vjs.Flash.formats) { return 'maybe'; }
4750 vjs.Flash.formats = {
4752 'video/x-flv': 'FLV',
4757 vjs.Flash['onReady'] = function(currSwf){
4758 var el = vjs.el(currSwf);
4760 // Get player from box
4761 // On firefox reloads, el might already have a player
4762 var player = el['player'] || el.parentNode['player'],
4765 // Reference player on tech element
4766 el['player'] = player;
4768 // Update reference to playback technology element
4771 // Now that the element is ready, make a click on the swf play the video
4772 tech.on('click', tech.onClick);
4774 vjs.Flash.checkReady(tech);
4777 // The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.
4778 // If it's not ready, we set a timeout to check again shortly.
4779 vjs.Flash.checkReady = function(tech){
4781 // Check if API property exists
4782 if (tech.el().vjs_getProperty) {
4784 // If so, tell tech it's ready
4785 tech.triggerReady();
4787 // Otherwise wait longer.
4790 setTimeout(function(){
4791 vjs.Flash.checkReady(tech);
4797 // Trigger events from the swf on the player
4798 vjs.Flash['onEvent'] = function(swfID, eventName){
4799 var player = vjs.el(swfID)['player'];
4800 player.trigger(eventName);
4803 // Log errors from the swf
4804 vjs.Flash['onError'] = function(swfID, err){
4805 var player = vjs.el(swfID)['player'];
4806 player.trigger('error');
4807 vjs.log('Flash Error', err, swfID);
4810 // Flash Version Check
4811 vjs.Flash.version = function(){
4812 var version = '0,0,0';
4816 version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
4821 if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){
4822 version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
4826 return version.split(',');
4829 // Flash embedding method. Only used in non-iframe mode
4830 vjs.Flash.embed = function(swf, placeHolder, flashVars, params, attributes){
4831 var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),
4833 // Get element by embedding code and retrieving created element
4834 obj = vjs.createEl('div', { innerHTML: code }).childNodes[0],
4836 par = placeHolder.parentNode
4839 placeHolder.parentNode.replaceChild(obj, placeHolder);
4841 // IE6 seems to have an issue where it won't initialize the swf object after injecting it.
4842 // This is a dumb fix
4843 var newObj = par.childNodes[0];
4844 setTimeout(function(){
4845 newObj.style.display = 'block';
4852 vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
4854 var objTag = '<object type="application/x-shockwave-flash"',
4855 flashVarsString = '',
4859 // Convert flash vars to string
4861 vjs.obj.each(flashVars, function(key, val){
4862 flashVarsString += (key + '=' + val + '&');
4866 // Add swf, flashVars, and other default params
4867 params = vjs.obj.merge({
4869 'flashvars': flashVarsString,
4870 'allowScriptAccess': 'always', // Required to talk to swf
4871 'allowNetworking': 'all' // All should be default, but having security issues.
4874 // Create param tags string
4875 vjs.obj.each(params, function(key, val){
4876 paramsString += '<param name="'+key+'" value="'+val+'" />';
4879 attributes = vjs.obj.merge({
4880 // Add swf to attributes (need both for IE and Others to work)
4883 // Default to 100% width/height
4889 // Create Attributes string
4890 vjs.obj.each(attributes, function(key, val){
4891 attrsString += (key + '="' + val + '" ');
4894 return objTag + attrsString + '>' + paramsString + '</object>';
4899 vjs.MediaLoader = vjs.Component.extend({
4901 init: function(player, options, ready){
4902 vjs.Component.call(this, player, options, ready);
4904 // If there are no sources when the player is initialized,
4905 // load the first supported playback technology.
4906 if (!player.options_['sources'] || player.options_['sources'].length === 0) {
4907 for (var i=0,j=player.options_['techOrder']; i<j.length; i++) {
4908 var techName = vjs.capitalize(j[i]),
4909 tech = window['videojs'][techName];
4911 // Check if the browser supports this technology
4912 if (tech && tech.isSupported()) {
4913 player.loadTech(techName);
4918 // // Loop through playback technologies (HTML5, Flash) and check for support.
4919 // // Then load the best source.
4920 // // A few assumptions here:
4921 // // All playback technologies respect preload false.
4922 player.src(player.options_['sources']);
4926 * @fileoverview Text Tracks
4927 * Text tracks are tracks of timed text events.
4928 * Captions - text displayed over the video for the hearing impared
4929 * Subtitles - text displayed over the video for those who don't understand langauge in the video
4930 * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
4931 * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
4934 // Player Additions - Functions add to the player object for easier access to tracks
4937 * List of associated text tracks
4941 vjs.Player.prototype.textTracks_;
4944 * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
4945 * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
4946 * @return {Array} Array of track objects
4948 vjs.Player.prototype.textTracks = function(){
4949 this.textTracks_ = this.textTracks_ || [];
4950 return this.textTracks_;
4955 * In addition to the W3C settings we allow adding additional info through options.
4956 * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
4957 * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata
4958 * @param {String=} label Optional label
4959 * @param {String=} language Optional language
4960 * @param {Object=} options Additional track options, like src
4962 vjs.Player.prototype.addTextTrack = function(kind, label, language, options){
4963 var tracks = this.textTracks_ = this.textTracks_ || [];
4964 options = options || {};
4966 options['kind'] = kind;
4967 options['label'] = label;
4968 options['language'] = language;
4970 // HTML5 Spec says default to subtitles.
4971 // Uppercase first letter to match class names
4972 var Kind = vjs.capitalize(kind || 'subtitles');
4974 // Create correct texttrack class. CaptionsTrack, etc.
4975 var track = new window['videojs'][Kind + 'Track'](this, options);
4979 // If track.dflt() is set, start showing immediately
4980 // TODO: Add a process to deterime the best track to show for the specific kind
4981 // Incase there are mulitple defaulted tracks of the same kind
4982 // Or the user has a set preference of a specific language that should override the default
4983 // if (track.dflt()) {
4984 // this.ready(vjs.bind(track, track.show));
4991 * Add an array of text tracks. captions, subtitles, chapters, descriptions
4992 * Track objects will be stored in the player.textTracks() array
4993 * @param {Array} trackList Array of track elements or objects (fake track elements)
4995 vjs.Player.prototype.addTextTracks = function(trackList){
4998 for (var i = 0; i < trackList.length; i++) {
4999 trackObj = trackList[i];
5000 this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);
5006 // Show a text track
5007 // disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
5008 vjs.Player.prototype.showTextTrack = function(id, disableSameKind){
5009 var tracks = this.textTracks_,
5012 track, showTrack, kind;
5014 // Find Track with same ID
5017 if (track.id() === id) {
5021 // Disable tracks of the same kind
5022 } else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {
5027 // Get track kind from shown track or disableSameKind
5028 kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);
5030 // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.
5032 this.trigger(kind+'trackchange');
5040 * Contains track methods for loading, showing, parsing cues of tracks
5041 * @param {vjs.Player|Object} player
5042 * @param {Object=} options
5045 vjs.TextTrack = vjs.Component.extend({
5047 init: function(player, options){
5048 vjs.Component.call(this, player, options);
5050 // Apply track info to track object
5051 // Options will often be a track element
5053 // Build ID if one doesn't exist
5054 this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
5055 this.src_ = options['src'];
5056 // 'default' is a reserved keyword in js so we use an abbreviated version
5057 this.dflt_ = options['default'] || options['dflt'];
5058 this.title_ = options['title'];
5059 this.language_ = options['srclang'];
5060 this.label_ = options['label'];
5062 this.activeCues_ = [];
5063 this.readyState_ = 0;
5066 this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));
5071 * Track kind value. Captions, subtitles, etc.
5074 vjs.TextTrack.prototype.kind_;
5077 * Get the track kind value
5080 vjs.TextTrack.prototype.kind = function(){
5088 vjs.TextTrack.prototype.src_;
5091 * Get the track src value
5094 vjs.TextTrack.prototype.src = function(){
5099 * Track default value
5100 * If default is used, subtitles/captions to start showing
5103 vjs.TextTrack.prototype.dflt_;
5106 * Get the track default value
5107 * 'default' is a reserved keyword
5110 vjs.TextTrack.prototype.dflt = function(){
5118 vjs.TextTrack.prototype.title_;
5121 * Get the track title value
5124 vjs.TextTrack.prototype.title = function(){
5129 * Language - two letter string to represent track language, e.g. 'en' for English
5130 * Spec def: readonly attribute DOMString language;
5133 vjs.TextTrack.prototype.language_;
5136 * Get the track language value
5139 vjs.TextTrack.prototype.language = function(){
5140 return this.language_;
5144 * Track label e.g. 'English'
5145 * Spec def: readonly attribute DOMString label;
5148 vjs.TextTrack.prototype.label_;
5151 * Get the track label value
5154 vjs.TextTrack.prototype.label = function(){
5159 * All cues of the track. Cues have a startTime, endTime, text, and other properties.
5160 * Spec def: readonly attribute TextTrackCueList cues;
5163 vjs.TextTrack.prototype.cues_;
5166 * Get the track cues
5169 vjs.TextTrack.prototype.cues = function(){
5174 * ActiveCues is all cues that are currently showing
5175 * Spec def: readonly attribute TextTrackCueList activeCues;
5178 vjs.TextTrack.prototype.activeCues_;
5181 * Get the track active cues
5184 vjs.TextTrack.prototype.activeCues = function(){
5185 return this.activeCues_;
5189 * ReadyState describes if the text file has been loaded
5190 * const unsigned short NONE = 0;
5191 * const unsigned short LOADING = 1;
5192 * const unsigned short LOADED = 2;
5193 * const unsigned short ERROR = 3;
5194 * readonly attribute unsigned short readyState;
5197 vjs.TextTrack.prototype.readyState_;
5200 * Get the track readyState
5203 vjs.TextTrack.prototype.readyState = function(){
5204 return this.readyState_;
5208 * Mode describes if the track is showing, hidden, or disabled
5209 * const unsigned short OFF = 0;
5210 * const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)
5211 * const unsigned short SHOWING = 2;
5212 * attribute unsigned short mode;
5215 vjs.TextTrack.prototype.mode_;
5218 * Get the track mode
5221 vjs.TextTrack.prototype.mode = function(){
5226 * Change the font size of the text track to make it larger when playing in fullscreen mode
5227 * and restore it to its normal size when not in fullscreen mode.
5229 vjs.TextTrack.prototype.adjustFontSize = function(){
5230 if (this.player_.isFullScreen) {
5231 // Scale the font by the same factor as increasing the video width to the full screen window width.
5232 // Additionally, multiply that factor by 1.4, which is the default font size for
5233 // the caption track (from the CSS)
5234 this.el_.style.fontSize = screen.width / this.player_.width() * 1.4 * 100 + '%';
5236 // Change the font size of the text track back to its original non-fullscreen size
5237 this.el_.style.fontSize = '';
5242 * Create basic div to hold cue text
5245 vjs.TextTrack.prototype.createEl = function(){
5246 return vjs.Component.prototype.createEl.call(this, 'div', {
5247 className: 'vjs-' + this.kind_ + ' vjs-text-track'
5252 * Show: Mode Showing (2)
5253 * Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
5254 * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
5255 * In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;
5256 * for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a non-visual fashion;
5257 * and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.
5258 * The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.
5259 * This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
5261 vjs.TextTrack.prototype.show = function(){
5267 vjs.Component.prototype.show.call(this);
5271 * Hide: Mode Hidden (1)
5272 * Indicates that the text track is active, but that the user agent is not actively displaying the cues.
5273 * If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
5274 * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
5276 vjs.TextTrack.prototype.hide = function(){
5277 // When hidden, cues are still triggered. Disable to stop triggering.
5283 vjs.Component.prototype.hide.call(this);
5287 * Disable: Mode Off/Disable (0)
5288 * Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.
5289 * No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
5291 vjs.TextTrack.prototype.disable = function(){
5292 // If showing, hide.
5293 if (this.mode_ == 2) { this.hide(); }
5295 // Stop triggering cues
5298 // Switch Mode to Off
5303 * Turn on cue tracking. Tracks that are showing OR hidden are active.
5305 vjs.TextTrack.prototype.activate = function(){
5306 // Load text file if it hasn't been yet.
5307 if (this.readyState_ === 0) { this.load(); }
5309 // Only activate if not already active.
5310 if (this.mode_ === 0) {
5311 // Update current cue on timeupdate
5312 // Using unique ID for bind function so other tracks don't remove listener
5313 this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));
5315 // Reset cue time on media end
5316 this.player_.on('ended', vjs.bind(this, this.reset, this.id_));
5319 if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {
5320 this.player_.getChild('textTrackDisplay').addChild(this);
5326 * Turn off cue tracking.
5328 vjs.TextTrack.prototype.deactivate = function(){
5329 // Using unique ID for bind function so other tracks don't remove listener
5330 this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));
5331 this.player_.off('ended', vjs.bind(this, this.reset, this.id_));
5332 this.reset(); // Reset
5334 // Remove from display
5335 this.player_.getChild('textTrackDisplay').removeChild(this);
5338 // A readiness state
5339 // One of the following:
5342 // Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained.
5345 // Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track.
5348 // Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object.
5351 // Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.
5352 vjs.TextTrack.prototype.load = function(){
5354 // Only load if not loaded yet.
5355 if (this.readyState_ === 0) {
5356 this.readyState_ = 1;
5357 vjs.get(this.src_, vjs.bind(this, this.parseCues), vjs.bind(this, this.onError));
5362 vjs.TextTrack.prototype.onError = function(err){
5364 this.readyState_ = 3;
5365 this.trigger('error');
5368 // Parse the WebVTT text format for cue times.
5369 // TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
5370 vjs.TextTrack.prototype.parseCues = function(srcContent) {
5371 var cue, time, text,
5372 lines = srcContent.split('\n'),
5375 for (var i=1, j=lines.length; i<j; i++) {
5376 // Line 0 should be 'WEBVTT', so skipping i=0
5378 line = vjs.trim(lines[i]); // Trim whitespace and linebreaks
5380 if (line) { // Loop until a line with content
5382 // First line could be an optional cue ID
5383 // Check if line has the time separator
5384 if (line.indexOf('-->') == -1) {
5386 // Advance to next line for timing.
5387 line = vjs.trim(lines[++i]);
5389 id = this.cues_.length;
5392 // First line - Number
5394 id: id, // Cue Number
5395 index: this.cues_.length // Position in Array
5399 time = line.split(' --> ');
5400 cue.startTime = this.parseCueTime(time[0]);
5401 cue.endTime = this.parseCueTime(time[1]);
5403 // Additional lines - Cue Text
5406 // Loop until a blank line or end of lines
5407 // Assumeing trim('') returns false for blank lines
5408 while (lines[++i] && (line = vjs.trim(lines[i]))) {
5412 cue.text = text.join('<br/>');
5415 this.cues_.push(cue);
5419 this.readyState_ = 2;
5420 this.trigger('loaded');
5424 vjs.TextTrack.prototype.parseCueTime = function(timeText) {
5425 var parts = timeText.split(':'),
5427 hours, minutes, other, seconds, ms;
5429 // Check if optional hours place is included
5430 // 00:00:00.000 vs. 00:00.000
5431 if (parts.length == 3) {
5441 // Break other (seconds, milliseconds, and flags) by spaces
5442 // TODO: Make additional cue layout settings work with flags
5443 other = other.split(/\s+/);
5444 // Remove seconds. Seconds is the first part before any spaces.
5445 seconds = other.splice(0,1)[0];
5446 // Could use either . or , for decimal
5447 seconds = seconds.split(/\.|,/);
5449 ms = parseFloat(seconds[1]);
5450 seconds = seconds[0];
5453 time += parseFloat(hours) * 3600;
5454 // minutes => seconds
5455 time += parseFloat(minutes) * 60;
5457 time += parseFloat(seconds);
5459 if (ms) { time += ms/1000; }
5464 // Update active cues whenever timeupdate events are triggered on the player.
5465 vjs.TextTrack.prototype.update = function(){
5466 if (this.cues_.length > 0) {
5468 // Get curent player time
5469 var time = this.player_.currentTime();
5471 // Check if the new time is outside the time box created by the the last update.
5472 if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
5473 var cues = this.cues_,
5475 // Create a new time box for this state.
5476 newNextChange = this.player_.duration(), // Start at beginning of the timeline
5477 newPrevChange = 0, // Start at end
5479 reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.
5480 newCues = [], // Store new active cues.
5482 // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.
5483 firstActiveIndex, lastActiveIndex,
5484 cue, i; // Loop vars
5486 // Check if time is going forwards or backwards (scrubbing/rewinding)
5487 // If we know the direction we can optimize the starting position and direction of the loop through the cues array.
5488 if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen
5489 // Forwards, so start at the index of the first active cue and loop forward
5490 i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;
5492 // Backwards, so start at the index of the last active cue and loop backward
5494 i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;
5497 while (true) { // Loop until broken
5500 // Cue ended at this point
5501 if (cue.endTime <= time) {
5502 newPrevChange = Math.max(newPrevChange, cue.endTime);
5508 // No earlier cues should have an active start time.
5509 // Nevermind. Assume first cue could have a duration the same as the video.
5510 // In that case we need to loop all the way back to the beginning.
5511 // if (reverse && cue.startTime) { break; }
5513 // Cue hasn't started
5514 } else if (time < cue.startTime) {
5515 newNextChange = Math.min(newNextChange, cue.startTime);
5521 // No later cues should have an active start time.
5522 if (!reverse) { break; }
5528 // Add cue to front of array to keep in time order
5529 newCues.splice(0,0,cue);
5531 // If in reverse, the first current cue is our lastActiveCue
5532 if (lastActiveIndex === undefined) { lastActiveIndex = i; }
5533 firstActiveIndex = i;
5535 // Add cue to end of array
5538 // If forward, the first current cue is our firstActiveIndex
5539 if (firstActiveIndex === undefined) { firstActiveIndex = i; }
5540 lastActiveIndex = i;
5543 newNextChange = Math.min(newNextChange, cue.endTime);
5544 newPrevChange = Math.max(newPrevChange, cue.startTime);
5550 // Reverse down the array of cues, break if at first
5551 if (i === 0) { break; } else { i--; }
5553 // Walk up the array fo cues, break if at last
5554 if (i === cues.length - 1) { break; } else { i++; }
5559 this.activeCues_ = newCues;
5560 this.nextChange = newNextChange;
5561 this.prevChange = newPrevChange;
5562 this.firstActiveIndex = firstActiveIndex;
5563 this.lastActiveIndex = lastActiveIndex;
5565 this.updateDisplay();
5567 this.trigger('cuechange');
5572 // Add cue HTML to display
5573 vjs.TextTrack.prototype.updateDisplay = function(){
5574 var cues = this.activeCues_,
5579 html += '<span class="vjs-tt-cue">'+cues[i].text+'</span>';
5582 this.el_.innerHTML = html;
5585 // Set all loop helper values back
5586 vjs.TextTrack.prototype.reset = function(){
5587 this.nextChange = 0;
5588 this.prevChange = this.player_.duration();
5589 this.firstActiveIndex = 0;
5590 this.lastActiveIndex = 0;
5593 // Create specific track types
5597 vjs.CaptionsTrack = vjs.TextTrack.extend();
5598 vjs.CaptionsTrack.prototype.kind_ = 'captions';
5599 // Exporting here because Track creation requires the track kind
5600 // to be available on global object. e.g. new window['videojs'][Kind + 'Track']
5605 vjs.SubtitlesTrack = vjs.TextTrack.extend();
5606 vjs.SubtitlesTrack.prototype.kind_ = 'subtitles';
5611 vjs.ChaptersTrack = vjs.TextTrack.extend();
5612 vjs.ChaptersTrack.prototype.kind_ = 'chapters';
5615 /* Text Track Display
5616 ============================================================================= */
5617 // Global container for both subtitle and captions text. Simple div container.
5622 vjs.TextTrackDisplay = vjs.Component.extend({
5624 init: function(player, options, ready){
5625 vjs.Component.call(this, player, options, ready);
5627 // This used to be called during player init, but was causing an error
5628 // if a track should show by default and the display hadn't loaded yet.
5629 // Should probably be moved to an external track loader when we support
5630 // tracks that don't need a display.
5631 if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
5632 this.player_.addTextTracks(player.options_['tracks']);
5637 vjs.TextTrackDisplay.prototype.createEl = function(){
5638 return vjs.Component.prototype.createEl.call(this, 'div', {
5639 className: 'vjs-text-track-display'
5644 /* Text Track Menu Items
5645 ============================================================================= */
5649 vjs.TextTrackMenuItem = vjs.MenuItem.extend({
5651 init: function(player, options){
5652 var track = this.track = options['track'];
5654 // Modify options for parent MenuItem class's init.
5655 options['label'] = track.label();
5656 options['selected'] = track.dflt();
5657 vjs.MenuItem.call(this, player, options);
5659 this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));
5663 vjs.TextTrackMenuItem.prototype.onClick = function(){
5664 vjs.MenuItem.prototype.onClick.call(this);
5665 this.player_.showTextTrack(this.track.id_, this.track.kind());
5668 vjs.TextTrackMenuItem.prototype.update = function(){
5669 if (this.track.mode() == 2) {
5670 this.selected(true);
5672 this.selected(false);
5679 vjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({
5681 init: function(player, options){
5682 // Create pseudo track info
5683 // Requires options['kind']
5684 options['track'] = {
5685 kind: function() { return options['kind']; },
5687 label: function(){ return options['kind'] + ' off'; },
5688 dflt: function(){ return false; },
5689 mode: function(){ return false; }
5691 vjs.TextTrackMenuItem.call(this, player, options);
5692 this.selected(true);
5696 vjs.OffTextTrackMenuItem.prototype.onClick = function(){
5697 vjs.TextTrackMenuItem.prototype.onClick.call(this);
5698 this.player_.showTextTrack(this.track.id_, this.track.kind());
5701 vjs.OffTextTrackMenuItem.prototype.update = function(){
5702 var tracks = this.player_.textTracks(),
5703 i=0, j=tracks.length, track,
5708 if (track.kind() == this.track.kind() && track.mode() == 2) {
5714 this.selected(true);
5716 this.selected(false);
5721 ================================================================================ */
5725 vjs.TextTrackButton = vjs.MenuButton.extend({
5727 init: function(player, options){
5728 vjs.MenuButton.call(this, player, options);
5730 if (this.items.length <= 1) {
5736 // vjs.TextTrackButton.prototype.buttonPressed = false;
5738 // vjs.TextTrackButton.prototype.createMenu = function(){
5739 // var menu = new vjs.Menu(this.player_);
5741 // // Add a title list item to the top
5742 // // menu.el().appendChild(vjs.createEl('li', {
5743 // // className: 'vjs-menu-title',
5744 // // innerHTML: vjs.capitalize(this.kind_),
5748 // this.items = this.createItems();
5750 // // Add menu items to the menu
5751 // for (var i = 0; i < this.items.length; i++) {
5752 // menu.addItem(this.items[i]);
5755 // // Add list to element
5756 // this.addChild(menu);
5761 // Create a menu item for each text track
5762 vjs.TextTrackButton.prototype.createItems = function(){
5763 var items = [], track;
5765 // Add an OFF menu item to turn all tracks off
5766 items.push(new vjs.OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
5768 for (var i = 0; i < this.player_.textTracks().length; i++) {
5769 track = this.player_.textTracks()[i];
5770 if (track.kind() === this.kind_) {
5771 items.push(new vjs.TextTrackMenuItem(this.player_, {
5783 vjs.CaptionsButton = vjs.TextTrackButton.extend({
5785 init: function(player, options, ready){
5786 vjs.TextTrackButton.call(this, player, options, ready);
5787 this.el_.setAttribute('aria-label','Captions Menu');
5790 vjs.CaptionsButton.prototype.kind_ = 'captions';
5791 vjs.CaptionsButton.prototype.buttonText = 'Captions';
5792 vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
5797 vjs.SubtitlesButton = vjs.TextTrackButton.extend({
5799 init: function(player, options, ready){
5800 vjs.TextTrackButton.call(this, player, options, ready);
5801 this.el_.setAttribute('aria-label','Subtitles Menu');
5804 vjs.SubtitlesButton.prototype.kind_ = 'subtitles';
5805 vjs.SubtitlesButton.prototype.buttonText = 'Subtitles';
5806 vjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';
5808 // Chapters act much differently than other text tracks
5809 // Cues are navigation vs. other tracks of alternative languages
5813 vjs.ChaptersButton = vjs.TextTrackButton.extend({
5815 init: function(player, options, ready){
5816 vjs.TextTrackButton.call(this, player, options, ready);
5817 this.el_.setAttribute('aria-label','Chapters Menu');
5820 vjs.ChaptersButton.prototype.kind_ = 'chapters';
5821 vjs.ChaptersButton.prototype.buttonText = 'Chapters';
5822 vjs.ChaptersButton.prototype.className = 'vjs-chapters-button';
5824 // Create a menu item for each text track
5825 vjs.ChaptersButton.prototype.createItems = function(){
5826 var items = [], track;
5828 for (var i = 0; i < this.player_.textTracks().length; i++) {
5829 track = this.player_.textTracks()[i];
5830 if (track.kind() === this.kind_) {
5831 items.push(new vjs.TextTrackMenuItem(this.player_, {
5840 vjs.ChaptersButton.prototype.createMenu = function(){
5841 var tracks = this.player_.textTracks(),
5844 track, chaptersTrack,
5845 items = this.items = [];
5849 if (track.kind() == this.kind_ && track.dflt()) {
5850 if (track.readyState() < 2) {
5851 this.chaptersTrack = track;
5852 track.on('loaded', vjs.bind(this, this.createMenu));
5855 chaptersTrack = track;
5861 var menu = this.menu = new vjs.Menu(this.player_);
5863 menu.el_.appendChild(vjs.createEl('li', {
5864 className: 'vjs-menu-title',
5865 innerHTML: vjs.capitalize(this.kind_),
5869 if (chaptersTrack) {
5870 var cues = chaptersTrack.cues_, cue, mi;
5877 mi = new vjs.ChaptersTrackMenuItem(this.player_, {
5878 'track': chaptersTrack,
5888 if (this.items.length > 0) {
5899 vjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({
5901 init: function(player, options){
5902 var track = this.track = options['track'],
5903 cue = this.cue = options['cue'],
5904 currentTime = player.currentTime();
5906 // Modify options for parent MenuItem class's init.
5907 options['label'] = cue.text;
5908 options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
5909 vjs.MenuItem.call(this, player, options);
5911 track.on('cuechange', vjs.bind(this, this.update));
5915 vjs.ChaptersTrackMenuItem.prototype.onClick = function(){
5916 vjs.MenuItem.prototype.onClick.call(this);
5917 this.player_.currentTime(this.cue.startTime);
5918 this.update(this.cue.startTime);
5921 vjs.ChaptersTrackMenuItem.prototype.update = function(){
5923 currentTime = this.player_.currentTime();
5925 // vjs.log(currentTime, cue.startTime);
5926 if (cue.startTime <= currentTime && currentTime < cue.endTime) {
5927 this.selected(true);
5929 this.selected(false);
5933 // Add Buttons to controlBar
5934 vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
5935 'subtitlesButton': {},
5936 'captionsButton': {},
5937 'chaptersButton': {}
5940 // vjs.Cue = vjs.Component.extend({
5941 // /** @constructor */
5942 // init: function(player, options){
5943 // vjs.Component.call(this, player, options);
5947 * @fileoverview Add JSON support
5948 * @suppress {undefinedVars}
5949 * (Compiler doesn't like JSON not being declared)
5953 * Javascript JSON implementation
5954 * (Parse Method Only)
5955 * https://github.com/douglascrockford/JSON-js/blob/master/json2.js
5956 * Only using for parse method when parsing data-setup attribute JSON.
5958 * @suppress {undefinedVars}
5963 * @suppress {undefinedVars}
5965 if (typeof window.JSON !== 'undefined' && window.JSON.parse === 'function') {
5966 vjs.JSON = window.JSON;
5971 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
5973 vjs.JSON.parse = function (text, reviver) {
5976 function walk(holder, key) {
5977 var k, v, value = holder[key];
5978 if (value && typeof value === 'object') {
5980 if (Object.prototype.hasOwnProperty.call(value, k)) {
5982 if (v !== undefined) {
5990 return reviver.call(holder, key, value);
5992 text = String(text);
5994 if (cx.test(text)) {
5995 text = text.replace(cx, function (a) {
5997 ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
6002 .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
6003 .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
6004 .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
6006 j = eval('(' + text + ')');
6008 return typeof reviver === 'function' ?
6009 walk({'': j}, '') : j;
6012 throw new SyntaxError('JSON.parse(): invalid or malformed JSON data');
6016 * @fileoverview Functions for automatically setting up a player
6017 * based on the data-setup attribute of the video tag
6020 // Automatically set up any tags that have a data-setup attribute
6021 vjs.autoSetup = function(){
6022 var options, vid, player,
6023 vids = document.getElementsByTagName('video');
6025 // Check if any media elements exist
6026 if (vids && vids.length > 0) {
6028 for (var i=0,j=vids.length; i<j; i++) {
6031 // Check if element exists, has getAttribute func.
6032 // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
6033 if (vid && vid.getAttribute) {
6035 // Make sure this player hasn't already been set up.
6036 if (vid['player'] === undefined) {
6037 options = vid.getAttribute('data-setup');
6039 // Check if data-setup attr exists.
6040 // We only auto-setup if they've added the data-setup attr.
6041 if (options !== null) {
6043 // Parse options JSON
6044 // If empty string, make it a parsable json object.
6045 options = vjs.JSON.parse(options || '{}');
6047 // Create new video.js instance.
6048 player = videojs(vid, options);
6052 // If getAttribute isn't defined, we need to wait for the DOM.
6054 vjs.autoSetupTimeout(1);
6059 // No videos were found, so keep looping unless page is finisehd loading.
6060 } else if (!vjs.windowLoaded) {
6061 vjs.autoSetupTimeout(1);
6065 // Pause to let the DOM keep processing
6066 vjs.autoSetupTimeout = function(wait){
6067 setTimeout(vjs.autoSetup, wait);
6070 vjs.one(window, 'load', function(){
6071 vjs.windowLoaded = true;
6074 // Run Auto-load players
6075 // You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
6076 vjs.autoSetupTimeout(1);
6077 vjs.plugin = function(name, init){
6078 vjs.Player.prototype[name] = init;