UNPKG

hdjs

Version:
1,760 lines (1,529 loc) 215 kB
/** * @fileoverview Main function src. */ // HTML5 Shiv. Must be in <head> to support older browsers. document.createElement('video'); document.createElement('audio'); document.createElement('track'); /** * Doubles as the main function for users to create a player instance and also * the main library object. * * **ALIASES** videojs, _V_ (deprecated) * * The `vjs` function can be used to initialize or retrieve a player. * * var myPlayer = vjs('my_video_id'); * * @param {String|Element} id Video element or video element ID * @param {Object=} options Optional options object for config/settings * @param {Function=} ready Optional ready callback * @return {vjs.Player} A player instance * @namespace */ var vjs = function(id, options, ready){ var tag; // Element of ID // Allow for element or ID to be passed in // String ID if (typeof id === 'string') { // Adjust for jQuery ID syntax if (id.indexOf('#') === 0) { id = id.slice(1); } // If a player instance has already been created for this ID return it. if (vjs.players[id]) { return vjs.players[id]; // Otherwise get element for ID } else { tag = vjs.el(id); } // ID is a media element } else { tag = id; } // Check for a useable element if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns } // Element may have a player attr referring to an already created player instance. // If not, set up a new player and return the instance. return tag['player'] || new vjs.Player(tag, options, ready); }; // Extended name, also available externally, window.videojs var videojs = vjs; window.videojs = window.vjs = vjs; // CDN Version. Used to target right flash swf. vjs.CDN_VERSION = '4.3'; vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://'); /** * Global Player instance options, surfaced from vjs.Player.prototype.options_ * vjs.options = vjs.Player.prototype.options_ * All options should use string keys so they avoid * renaming by closure compiler * @type {Object} */ vjs.options = { // Default order of fallback technology 'techOrder': ['html5','flash'], // techOrder: ['flash','html5'], 'html5': {}, 'flash': {}, // Default of web browser is 300x150. Should rely on source width/height. 'width': 300, 'height': 150, // defaultVolume: 0.85, 'defaultVolume': 0.00, // The freakin seaguls are driving me crazy! // Included control sets 'children': { 'mediaLoader': {}, 'posterImage': {}, 'textTrackDisplay': {}, 'loadingSpinner': {}, 'bigPlayButton': {}, 'controlBar': {} }, // Default message to show when a video cannot be played. 'notSupportedMessage': '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>.' }; // Set CDN Version of swf // The added (+) blocks the replace from changing this 4.3 string if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') { videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf'; } /** * Global player list * @type {Object} */ vjs.players = {}; /** * Core Object/Class for objects that use inheritance + contstructors * * To create a class that can be subclassed itself, extend the CoreObject class. * * var Animal = CoreObject.extend(); * var Horse = Animal.extend(); * * The constructor can be defined through the init property of an object argument. * * var Animal = CoreObject.extend({ * init: function(name, sound){ * this.name = name; * } * }); * * Other methods and properties can be added the same way, or directly to the * prototype. * * var Animal = CoreObject.extend({ * init: function(name){ * this.name = name; * }, * getName: function(){ * return this.name; * }, * sound: '...' * }); * * Animal.prototype.makeSound = function(){ * alert(this.sound); * }; * * To create an instance of a class, use the create method. * * var fluffy = Animal.create('Fluffy'); * fluffy.getName(); // -> Fluffy * * Methods and properties can be overridden in subclasses. * * var Horse = Animal.extend({ * sound: 'Neighhhhh!' * }); * * var horsey = Horse.create('Horsey'); * horsey.getName(); // -> Horsey * horsey.makeSound(); // -> Alert: Neighhhhh! * * @class * @constructor */ vjs.CoreObject = vjs['CoreObject'] = function(){}; // Manually exporting vjs['CoreObject'] here for Closure Compiler // because of the use of the extend/create class methods // If we didn't do this, those functions would get flattend to something like // `a = ...` and `this.prototype` would refer to the global object instead of // CoreObject /** * Create a new object that inherits from this Object * * var Animal = CoreObject.extend(); * var Horse = Animal.extend(); * * @param {Object} props Functions and properties to be applied to the * new object's prototype * @return {vjs.CoreObject} An object that inherits from CoreObject * @this {*} */ vjs.CoreObject.extend = function(props){ var init, subObj; props = props || {}; // Set up the constructor using the supplied init method // or using the init of the parent object // Make sure to check the unobfuscated version for external libs init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){}; // In Resig's simple class inheritance (previously used) the constructor // is a function that calls `this.init.apply(arguments)` // However that would prevent us from using `ParentObject.call(this);` // in a Child constuctor because the `this` in `this.init` // would still refer to the Child and cause an inifinite loop. // We would instead have to do // `ParentObject.prototype.init.apply(this, argumnents);` // Bleh. We're not creating a _super() function, so it's good to keep // the parent constructor reference simple. subObj = function(){ init.apply(this, arguments); }; // Inherit from this object's prototype subObj.prototype = vjs.obj.create(this.prototype); // Reset the constructor property for subObj otherwise // instances of subObj would have the constructor of the parent Object subObj.prototype.constructor = subObj; // Make the class extendable subObj.extend = vjs.CoreObject.extend; // Make a function for creating instances subObj.create = vjs.CoreObject.create; // Extend subObj's prototype with functions and other properties from props for (var name in props) { if (props.hasOwnProperty(name)) { subObj.prototype[name] = props[name]; } } return subObj; }; /** * Create a new instace of this Object class * * var myAnimal = Animal.create(); * * @return {vjs.CoreObject} An instance of a CoreObject subclass * @this {*} */ vjs.CoreObject.create = function(){ // Create a new object that inherits from this object's prototype var inst = vjs.obj.create(this.prototype); // Apply this constructor function to the new object this.apply(inst, arguments); // Return the new object return inst; }; /** * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) * This should work very similarly to jQuery's events, however it's based off the book version which isn't as * robust as jquery's, so there's probably some differences. */ /** * Add an event listener to element * It stores the handler function in a separate cache object * and adds a generic handler to the element's event, * along with a unique id (guid) to the element. * @param {Element|Object} elem Element or object to bind listeners to * @param {String} type Type of event to bind to. * @param {Function} fn Event listener. * @private */ vjs.on = function(elem, type, fn){ var data = vjs.getData(elem); // We need a place to store all our handler data if (!data.handlers) data.handlers = {}; if (!data.handlers[type]) data.handlers[type] = []; if (!fn.guid) fn.guid = vjs.guid++; data.handlers[type].push(fn); if (!data.dispatcher) { data.disabled = false; data.dispatcher = function (event){ if (data.disabled) return; event = vjs.fixEvent(event); var handlers = data.handlers[event.type]; if (handlers) { // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. var handlersCopy = handlers.slice(0); for (var m = 0, n = handlersCopy.length; m < n; m++) { if (event.isImmediatePropagationStopped()) { break; } else { handlersCopy[m].call(elem, event); } } } }; } if (data.handlers[type].length == 1) { if (document.addEventListener) { elem.addEventListener(type, data.dispatcher, false); } else if (document.attachEvent) { elem.attachEvent('on' + type, data.dispatcher); } } }; /** * Removes event listeners from an element * @param {Element|Object} elem Object to remove listeners from * @param {String=} type Type of listener to remove. Don't include to remove all events from element. * @param {Function} fn Specific listener to remove. Don't incldue to remove listeners for an event type. * @private */ vjs.off = function(elem, type, fn) { // Don't want to add a cache object through getData if not needed if (!vjs.hasData(elem)) return; var data = vjs.getData(elem); // If no events exist, nothing to unbind if (!data.handlers) { return; } // Utility function var removeType = function(t){ data.handlers[t] = []; vjs.cleanUpEvents(elem,t); }; // Are we removing all bound events? if (!type) { for (var t in data.handlers) removeType(t); return; } var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind if (!handlers) return; // If no listener was provided, remove all listeners for type if (!fn) { removeType(type); return; } // We're only removing a single handler if (fn.guid) { for (var n = 0; n < handlers.length; n++) { if (handlers[n].guid === fn.guid) { handlers.splice(n--, 1); } } } vjs.cleanUpEvents(elem, type); }; /** * Clean up the listener cache and dispatchers * @param {Element|Object} elem Element to clean up * @param {String} type Type of event to clean up * @private */ vjs.cleanUpEvents = function(elem, type) { var data = vjs.getData(elem); // Remove the events of a particular type if there are none left if (data.handlers[type].length === 0) { delete data.handlers[type]; // data.handlers[type] = null; // Setting to null was causing an error with data.handlers // Remove the meta-handler from the element if (document.removeEventListener) { elem.removeEventListener(type, data.dispatcher, false); } else if (document.detachEvent) { elem.detachEvent('on' + type, data.dispatcher); } } // Remove the events object if there are no types left if (vjs.isEmpty(data.handlers)) { delete data.handlers; delete data.dispatcher; delete data.disabled; // data.handlers = null; // data.dispatcher = null; // data.disabled = null; } // Finally remove the expando if there is no data left if (vjs.isEmpty(data)) { vjs.removeData(elem); } }; /** * Fix a native event to have standard property values * @param {Object} event Event object to fix * @return {Object} * @private */ vjs.fixEvent = function(event) { function returnTrue() { return true; } function returnFalse() { return false; } // Test if fixing up is needed // Used to check if !event.stopPropagation instead of isPropagationStopped // But native events return true for stopPropagation, but don't have // other expected methods like isPropagationStopped. Seems to be a problem // with the Javascript Ninja code. So we're just overriding all events now. if (!event || !event.isPropagationStopped) { var old = event || window.event; event = {}; // Clone the old object so that we can modify the values event = {}; // IE8 Doesn't like when you mess with native event properties // Firefox returns false for event.hasOwnProperty('type') and other props // which makes copying more difficult. // TODO: Probably best to create a whitelist of event props for (var key in old) { // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y if (key !== 'layerX' && key !== 'layerY') { event[key] = old[key]; } } // The event occurred on this element if (!event.target) { event.target = event.srcElement || document; } // Handle which other element the event is related to event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; // Stop the default browser action event.preventDefault = function () { if (old.preventDefault) { old.preventDefault(); } event.returnValue = false; event.isDefaultPrevented = returnTrue; }; event.isDefaultPrevented = returnFalse; // Stop the event from bubbling event.stopPropagation = function () { if (old.stopPropagation) { old.stopPropagation(); } event.cancelBubble = true; event.isPropagationStopped = returnTrue; }; event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers event.stopImmediatePropagation = function () { if (old.stopImmediatePropagation) { old.stopImmediatePropagation(); } event.isImmediatePropagationStopped = returnTrue; event.stopPropagation(); }; event.isImmediatePropagationStopped = returnFalse; // Handle mouse position if (event.clientX != null) { var doc = document.documentElement, body = document.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } // Handle key presses event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: // 0 == left; 1 == middle; 2 == right if (event.button != null) { event.button = (event.button & 1 ? 0 : (event.button & 4 ? 1 : (event.button & 2 ? 2 : 0))); } } // Returns fixed-up instance return event; }; /** * Trigger an event for an element * @param {Element|Object} elem Element to trigger an event on * @param {String} event Type of event to trigger * @private */ vjs.trigger = function(elem, event) { // Fetches element data and a reference to the parent (for bubbling). // Don't want to add a data object to cache for every parent, // so checking hasData first. var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {}; var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, // handler; // If an event name was passed as a string, creates an event out of it if (typeof event === 'string') { event = { type:event, target:elem }; } // Normalizes the event properties. event = vjs.fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. if (elemData.dispatcher) { elemData.dispatcher.call(elem, event); } // Unless explicitly stopped or the event does not bubble (e.g. media events) // recursively calls this function to bubble the event up the DOM. if (parent && !event.isPropagationStopped() && event.bubbles !== false) { vjs.trigger(parent, event); // If at the top of the DOM, triggers the default action unless disabled. } else if (!parent && !event.isDefaultPrevented()) { var targetData = vjs.getData(event.target); // Checks if the target has a default action for this event. if (event.target[event.type]) { // Temporarily disables event dispatching on the target as we have already executed the handler. targetData.disabled = true; // Executes the default action. if (typeof event.target[event.type] === 'function') { event.target[event.type](); } // Re-enables event dispatching. targetData.disabled = false; } } // Inform the triggerer if the default was prevented by returning false return !event.isDefaultPrevented(); /* Original version of js ninja events wasn't complete. * We've since updated to the latest version, but keeping this around * for now just in case. */ // // Added in attion to book. Book code was broke. // event = typeof event === 'object' ? // event[vjs.expando] ? // event : // new vjs.Event(type, event) : // new vjs.Event(type); // event.type = type; // if (handler) { // handler.call(elem, event); // } // // Clean up the event in case it is being reused // event.result = undefined; // event.target = elem; }; /** * Trigger a listener only once for an event * @param {Element|Object} elem Element or object to * @param {String} type * @param {Function} fn * @private */ vjs.one = function(elem, type, fn) { var func = function(){ vjs.off(elem, type, func); fn.apply(this, arguments); }; func.guid = fn.guid = fn.guid || vjs.guid++; vjs.on(elem, type, func); }; var hasOwnProp = Object.prototype.hasOwnProperty; /** * Creates an element and applies properties. * @param {String=} tagName Name of tag to be created. * @param {Object=} properties Element properties to be applied. * @return {Element} * @private */ vjs.createEl = function(tagName, properties){ var el, propName; el = document.createElement(tagName || 'div'); for (propName in properties){ if (hasOwnProp.call(properties, propName)) { //el[propName] = properties[propName]; // Not remembering why we were checking for dash // but using setAttribute means you have to use getAttribute // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin. // The additional check for "role" is because the default method for adding attributes does not // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs. // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem. if (propName.indexOf('aria-') !== -1 || propName=='role') { el.setAttribute(propName, properties[propName]); } else { el[propName] = properties[propName]; } } } return el; }; /** * Uppercase the first letter of a string * @param {String} string String to be uppercased * @return {String} * @private */ vjs.capitalize = function(string){ return string.charAt(0).toUpperCase() + string.slice(1); }; /** * Object functions container * @type {Object} * @private */ vjs.obj = {}; /** * Object.create shim for prototypal inheritance * * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create * * @function * @param {Object} obj Object to use as prototype * @private */ vjs.obj.create = Object.create || function(obj){ //Create a new function called 'F' which is just an empty object. function F() {} //the prototype of the 'F' function should point to the //parameter of the anonymous function. F.prototype = obj; //create a new constructor function based off of the 'F' function. return new F(); }; /** * Loop through each property in an object and call a function * whose arguments are (key,value) * @param {Object} obj Object of properties * @param {Function} fn Function to be called on each property. * @this {*} * @private */ vjs.obj.each = function(obj, fn, context){ for (var key in obj) { if (hasOwnProp.call(obj, key)) { fn.call(context || this, key, obj[key]); } } }; /** * Merge two objects together and return the original. * @param {Object} obj1 * @param {Object} obj2 * @return {Object} * @private */ vjs.obj.merge = function(obj1, obj2){ if (!obj2) { return obj1; } for (var key in obj2){ if (hasOwnProp.call(obj2, key)) { obj1[key] = obj2[key]; } } return obj1; }; /** * Merge two objects, and merge any properties that are objects * instead of just overwriting one. Uses to merge options hashes * where deeper default settings are important. * @param {Object} obj1 Object to override * @param {Object} obj2 Overriding object * @return {Object} New object. Obj1 and Obj2 will be untouched. * @private */ vjs.obj.deepMerge = function(obj1, obj2){ var key, val1, val2; // make a copy of obj1 so we're not ovewriting original values. // like prototype.options_ and all sub options objects obj1 = vjs.obj.copy(obj1); for (key in obj2){ if (hasOwnProp.call(obj2, key)) { val1 = obj1[key]; val2 = obj2[key]; // Check if both properties are pure objects and do a deep merge if so if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) { obj1[key] = vjs.obj.deepMerge(val1, val2); } else { obj1[key] = obj2[key]; } } } return obj1; }; /** * Make a copy of the supplied object * @param {Object} obj Object to copy * @return {Object} Copy of object * @private */ vjs.obj.copy = function(obj){ return vjs.obj.merge({}, obj); }; /** * Check if an object is plain, and not a dom node or any object sub-instance * @param {Object} obj Object to check * @return {Boolean} True if plain, false otherwise * @private */ vjs.obj.isPlain = function(obj){ return !!obj && typeof obj === 'object' && obj.toString() === '[object Object]' && obj.constructor === Object; }; /** * Bind (a.k.a proxy or Context). A simple method for changing the context of a function It also stores a unique id on the function so it can be easily removed from events * @param {*} context The object to bind as scope * @param {Function} fn The function to be bound to a scope * @param {Number=} uid An optional unique ID for the function to be set * @return {Function} * @private */ vjs.bind = function(context, fn, uid) { // Make sure the function has a unique ID if (!fn.guid) { fn.guid = vjs.guid++; } // Create the new function that changes the context var ret = function() { return fn.apply(context, arguments); }; // Allow for the ability to individualize this function // Needed in the case where multiple objects might share the same prototype // IF both items add an event listener with the same function, then you try to remove just one // it will remove both because they both have the same guid. // when using this, you need to use the bind method when you remove the listener as well. // currently used in text tracks ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid; return ret; }; /** * Element Data Store. Allows for binding data to an element without putting it directly on the element. * Ex. Event listneres are stored here. * (also from jsninja.com, slightly modified and updated for closure compiler) * @type {Object} * @private */ vjs.cache = {}; /** * Unique ID for an element or function * @type {Number} * @private */ vjs.guid = 1; /** * Unique attribute name to store an element's guid in * @type {String} * @constant * @private */ vjs.expando = 'vdata' + (new Date()).getTime(); /** * Returns the cache object where data for an element is stored * @param {Element} el Element to store data for. * @return {Object} * @private */ vjs.getData = function(el){ var id = el[vjs.expando]; if (!id) { id = el[vjs.expando] = vjs.guid++; vjs.cache[id] = {}; } return vjs.cache[id]; }; /** * Returns the cache object where data for an element is stored * @param {Element} el Element to store data for. * @return {Object} * @private */ vjs.hasData = function(el){ var id = el[vjs.expando]; return !(!id || vjs.isEmpty(vjs.cache[id])); }; /** * Delete data for the element from the cache and the guid attr from getElementById * @param {Element} el Remove data for an element * @private */ vjs.removeData = function(el){ var id = el[vjs.expando]; if (!id) { return; } // Remove all stored data // Changed to = null // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/ // vjs.cache[id] = null; delete vjs.cache[id]; // Remove the expando property from the DOM node try { delete el[vjs.expando]; } catch(e) { if (el.removeAttribute) { el.removeAttribute(vjs.expando); } else { // IE doesn't appear to support removeAttribute on the document element el[vjs.expando] = null; } } }; /** * Check if an object is empty * @param {Object} obj The object to check for emptiness * @return {Boolean} * @private */ vjs.isEmpty = function(obj) { for (var prop in obj) { // Inlude null properties as empty. if (obj[prop] !== null) { return false; } } return true; }; /** * Add a CSS class name to an element * @param {Element} element Element to add class name to * @param {String} classToAdd Classname to add * @private */ vjs.addClass = function(element, classToAdd){ if ((' '+element.className+' ').indexOf(' '+classToAdd+' ') == -1) { element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd; } }; /** * Remove a CSS class name from an element * @param {Element} element Element to remove from class name * @param {String} classToAdd Classname to remove * @private */ vjs.removeClass = function(element, classToRemove){ var classNames, i; if (element.className.indexOf(classToRemove) == -1) { return; } classNames = element.className.split(' '); // no arr.indexOf in ie8, and we don't want to add a big shim for (i = classNames.length - 1; i >= 0; i--) { if (classNames[i] === classToRemove) { classNames.splice(i,1); } } element.className = classNames.join(' '); }; /** * Element for testing browser HTML5 video capabilities * @type {Element} * @constant * @private */ vjs.TEST_VID = vjs.createEl('video'); /** * Useragent for browser testing. * @type {String} * @constant * @private */ vjs.USER_AGENT = navigator.userAgent; /** * Device is an iPhone * @type {Boolean} * @constant * @private */ vjs.IS_IPHONE = (/iPhone/i).test(vjs.USER_AGENT); vjs.IS_IPAD = (/iPad/i).test(vjs.USER_AGENT); vjs.IS_IPOD = (/iPod/i).test(vjs.USER_AGENT); vjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD; vjs.IOS_VERSION = (function(){ var match = vjs.USER_AGENT.match(/OS (\d+)_/i); if (match && match[1]) { return match[1]; } })(); vjs.IS_ANDROID = (/Android/i).test(vjs.USER_AGENT); vjs.ANDROID_VERSION = (function() { // This matches Android Major.Minor.Patch versions // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned var match = vjs.USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i), major, minor; if (!match) { return null; } major = match[1] && parseFloat(match[1]); minor = match[2] && parseFloat(match[2]); if (major && minor) { return parseFloat(match[1] + '.' + match[2]); } else if (major) { return major; } else { return null; } })(); // Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser vjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.ANDROID_VERSION < 2.3; vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT); vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT); vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch); /** * Get an element's attribute values, as defined on the HTML tag * Attributs are not the same as properties. They're defined on the tag * or with setAttribute (which shouldn't be used with HTML) * This will return true or false for boolean attributes. * @param {Element} tag Element from which to get tag attributes * @return {Object} * @private */ vjs.getAttributeValues = function(tag){ var obj, knownBooleans, attrs, attrName, attrVal; obj = {}; // known boolean attributes // we can check for matching boolean properties, but older browsers // won't know about HTML5 boolean attributes that we still read from knownBooleans = ','+'autoplay,controls,loop,muted,default'+','; if (tag && tag.attributes && tag.attributes.length > 0) { attrs = tag.attributes; for (var i = attrs.length - 1; i >= 0; i--) { attrName = attrs[i].name; attrVal = attrs[i].value; // check for known booleans // the matching element property will return a value for typeof if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) { // the value of an included boolean attribute is typically an empty // string ('') which would equal false if we just check for a false value. // we also don't want support bad code like autoplay='false' attrVal = (attrVal !== null) ? true : false; } obj[attrName] = attrVal; } } return obj; }; /** * Get the computed style value for an element * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/ * @param {Element} el Element to get style value for * @param {String} strCssRule Style name * @return {String} Style value * @private */ vjs.getComputedDimension = function(el, strCssRule){ var strValue = ''; if(document.defaultView && document.defaultView.getComputedStyle){ strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule); } else if(el.currentStyle){ // IE8 Width/Height support strValue = el['client'+strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1)] + 'px'; } return strValue; }; /** * Insert an element as the first child node of another * @param {Element} child Element to insert * @param {[type]} parent Element to insert child into * @private */ vjs.insertFirst = function(child, parent){ if (parent.firstChild) { parent.insertBefore(child, parent.firstChild); } else { parent.appendChild(child); } }; /** * Object to hold browser support information * @type {Object} * @private */ vjs.support = {}; /** * Shorthand for document.getElementById() * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs. * @param {String} id Element ID * @return {Element} Element with supplied ID * @private */ vjs.el = function(id){ if (id.indexOf('#') === 0) { id = id.slice(1); } return document.getElementById(id); }; /** * Format seconds as a time string, H:MM:SS or M:SS * Supplying a guide (in seconds) will force a number of leading zeros * to cover the length of the guide * @param {Number} seconds Number of seconds to be turned into a string * @param {Number} guide Number (in seconds) to model the string after * @return {String} Time formatted as H:MM:SS or M:SS * @private */ vjs.formatTime = function(seconds, guide) { // Default to using seconds as guide guide = guide || seconds; var s = Math.floor(seconds % 60), m = Math.floor(seconds / 60 % 60), h = Math.floor(seconds / 3600), gm = Math.floor(guide / 60 % 60), gh = Math.floor(guide / 3600); // handle invalid times if (isNaN(seconds) || seconds === Infinity) { // '-' is false for all relational operators (e.g. <, >=) so this setting // will add the minimum number of fields specified by the guide h = m = s = '-'; } // Check if we need to show hours h = (h > 0 || gh > 0) ? h + ':' : ''; // If hours are showing, we may need to add a leading zero. // Always show at least one digit of minutes. m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':'; // Check if leading zero is need for seconds s = (s < 10) ? '0' + s : s; return h + m + s; }; // Attempt to block the ability to select text while dragging controls vjs.blockTextSelection = function(){ document.body.focus(); document.onselectstart = function () { return false; }; }; // Turn off text selection blocking vjs.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; }; /** * Trim whitespace from the ends of a string. * @param {String} string String to trim * @return {String} Trimmed string * @private */ vjs.trim = function(str){ return (str+'').replace(/^\s+|\s+$/g, ''); }; /** * Should round off a number to a decimal place * @param {Number} num Number to round * @param {Number} dec Number of decimal places to round to * @return {Number} Rounded number * @private */ vjs.round = function(num, dec) { if (!dec) { dec = 0; } return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec); }; /** * Should create a fake TimeRange object * Mimics an HTML5 time range instance, which has functions that * return the start and end times for a range * TimeRanges are returned by the buffered() method * @param {Number} start Start time in seconds * @param {Number} end End time in seconds * @return {Object} Fake TimeRange object * @private */ vjs.createTimeRange = function(start, end){ return { length: 1, start: function() { return start; }, end: function() { return end; } }; }; /** * Simple http request for retrieving external files (e.g. text tracks) * @param {String} url URL of resource * @param {Function=} onSuccess Success callback * @param {Function=} onError Error callback * @private */ vjs.get = function(url, onSuccess, onError){ var local, request; if (typeof XMLHttpRequest === 'undefined') { window.XMLHttpRequest = function () { try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {} try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {} try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {} throw new Error('This browser does not support XMLHttpRequest.'); }; } request = new XMLHttpRequest(); try { request.open('GET', url); } catch(e) { onError(e); } local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1)); request.onreadystatechange = function() { if (request.readyState === 4) { if (request.status === 200 || local && request.status === 0) { onSuccess(request.responseText); } else { if (onError) { onError(); } } } }; try { request.send(); } catch(e) { if (onError) { onError(e); } } }; /** * Add to local storage (may removeable) * @private */ vjs.setLocalStorage = function(key, value){ try { // IE was throwing errors referencing the var anywhere without this var localStorage = window.localStorage || false; if (!localStorage) { return; } localStorage[key] = value; } catch(e) { if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014 vjs.log('LocalStorage Full (VideoJS)', e); } else { if (e.code == 18) { vjs.log('LocalStorage not allowed (VideoJS)', e); } else { vjs.log('LocalStorage Error (VideoJS)', e); } } } }; /** * Get abosolute version of relative URL. Used to tell flash correct URL. * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue * @param {String} url URL to make absolute * @return {String} Absolute URL * @private */ vjs.getAbsoluteURL = function(url){ // Check if absolute URL if (!url.match(/^https?:\/\//)) { // Convert to absolute URL. Flash hosted off-site needs an absolute URL. url = vjs.createEl('div', { innerHTML: '<a href="'+url+'">x</a>' }).firstChild.href; } return url; }; // usage: log('inside coolFunc',this,arguments); // http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/ vjs.log = function(){ vjs.log.history = vjs.log.history || []; // store logs to an array for reference vjs.log.history.push(arguments); if(window.console){ window.console.log(Array.prototype.slice.call(arguments)); } }; // Offset Left // getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/ vjs.findPosition = function(el) { var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top; if (el.getBoundingClientRect && el.parentNode) { box = el.getBoundingClientRect(); } if (!box) { return { left: 0, top: 0 }; } docEl = document.documentElement; body = document.body; clientLeft = docEl.clientLeft || body.clientLeft || 0; scrollLeft = window.pageXOffset || body.scrollLeft; left = box.left + scrollLeft - clientLeft; clientTop = docEl.clientTop || body.clientTop || 0; scrollTop = window.pageYOffset || body.scrollTop; top = box.top + scrollTop - clientTop; return { left: left, top: top }; }; /** * @fileoverview Player Component - Base class for all UI objects * */ /** * Base UI Component class * * Components are embeddable UI objects that are represented by both a * javascript object and an element in the DOM. They can be children of other * components, and can have many children themselves. * * // adding a button to the player * var button = player.addChild('button'); * button.el(); // -> button element * * <div class="video-js"> * <div class="vjs-button">Button</div> * </div> * * Components are also event emitters. * * button.on('click', function(){ * console.log('Button Clicked!'); * }); * * button.trigger('customevent'); * * @param {Object} player Main Player * @param {Object=} options * @class * @constructor * @extends vjs.CoreObject */ vjs.Component = vjs.CoreObject.extend({ /** * the constructor funciton for the class * * @constructor */ init: function(player, options, ready){ this.player_ = player; // Make a copy of prototype.options_ to protect against overriding global defaults this.options_ = vjs.obj.copy(this.options_); // Updated options with supplied options options = this.options(options); // Get ID from options, element, or create using player ID and unique ID this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ ); this.name_ = options['name'] || null; // Create element if one wasn't provided in options this.el_ = options['el'] || this.createEl(); this.children_ = []; this.childIndex_ = {}; this.childNameIndex_ = {}; // Add any child components in options this.initChildren(); this.ready(ready); // Don't want to trigger ready here or it will before init is actually // finished for all children that run this constructor } }); /** * Dispose of the component and all child components */ vjs.Component.prototype.dispose = function(){ this.trigger('dispose'); // Dispose all children. if (this.children_) { for (var i = this.children_.length - 1; i >= 0; i--) { if (this.children_[i].dispose) { this.children_[i].dispose(); } } } // Delete child references this.children_ = null; this.childIndex_ = null; this.childNameIndex_ = null; // Remove all event listeners. this.off(); // Remove element from DOM if (this.el_.parentNode) { this.el_.parentNode.removeChild(this.el_); } vjs.removeData(this.el_); this.el_ = null; }; /** * Reference to main player instance * * @type {vjs.Player} * @private */ vjs.Component.prototype.player_ = true; /** * Return the component's player * * @return {vjs.Player} */ vjs.Component.prototype.player = function(){ return this.player_; }; /** * The component's options object * * @type {Object} * @private */ vjs.Component.prototype.options_; /** * Deep merge of options objects * * Whenever a property is an object on both options objects * the two properties will be merged using vjs.obj.deepMerge. * * This is used for merging options for child components. We * want it to be easy to override individual options on a child * component without having to rewrite all the other default options. * * Parent.prototype.options_ = { * children: { * 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' }, * 'childTwo': {}, * 'childThree': {} * } * } * newOptions = { * children: { * 'childOne': { 'foo': 'baz', 'abc': '123' } * 'childTwo': null, * 'childFour': {} * } * } * * this.options(newOptions); * * RESULT * * { * children: { * 'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' }, * 'childTwo': null, // Disabled. Won't be initialized. * 'childThree': {}, * 'childFour': {} * } * } * * @param {Object} obj Object whose values will be overwritten * @return {Object} NEW merged object. Does not return obj1. */ vjs.Component.prototype.options = function(obj){ if (obj === undefined) return this.options_; return this.options_ = vjs.obj.deepMerge(this.options_, obj); }; /** * The DOM element for the component * * @type {Element} * @private */ vjs.Component.prototype.el_; /** * Create the component's DOM element * * @param {String=} tagName Element's node type. e.g. 'div' * @param {Object=} attributes An object of element attributes that should be set on the element * @return {Element} */ vjs.Component.prototype.createEl = function(tagName, attributes){ return vjs.createEl(tagName, attributes); }; /** * Get the component's DOM element * * var domEl = myComponent.el(); * * @return {Element} */ vjs.Component.prototype.el = function(){ return this.el_; }; /** * An optional element where, if defined, children will be inserted instead of * directly in `el_` * * @type {Element} * @private */ vjs.Component.prototype.contentEl_; /** * Return the component's DOM element for embedding content. * Will either be el_ or a new element defined in createEl. * * @return {Element} */ vjs.Component.prototype.contentEl = function(){ return this.contentEl_ || this.el_; }; /** * The ID for the component * * @type {String} * @private */ vjs.Component.prototype.id_; /** * Get the component's ID * * var id = myComponent.id(); * * @return {String} */ vjs.Component.prototype.id = function(){ return this.id_; }; /** * The name for the component. Often used to reference the component. * * @type {String} * @private */ vjs.Component.prototype.name_; /** * Get the component's name. The name is often used to reference the component. * * var name = myComponent.name(); * * @return {String} */ vjs.Component.prototype.name = function(){ return this.name_; }; /** * Array of child components * * @type {Array} * @private */ vjs.Component.prototype.children_; /** * Get an array of all child components * * var kids = myComponent.children(); * * @return {Array} The children */ vjs.Component.prototype.children = function(){ return this.children_; }; /** * Object of child components by ID * * @type {Object} * @private */ vjs.Component.prototype.childIndex_; /** * Returns a child component with the provided ID * * @return {vjs.Component} */ vjs.Component.prototype.getChildById = function(id){ return this.childIndex_[id]; }; /** * Object of child components by name * * @type {Object} * @private */ vjs.Component.prototype.childNameIndex_; /** * Returns a child component with the provided ID * * @return {vjs.Component} */ vjs.Component.prototype.getChild = function(name){ return this.childNameIndex_[name]; }; /** * Adds a child component inside this component * * myComponent.el(); * // -> <div class='my-component'></div> * myComonent.children(); * // [empty array] * * var myButton = myComponent.addChild('MyButton'); * // -> <div class='my-component'><div class="my-button">myButton<div></div> * // -> myButton === myComonent.children()[0]; * * Pass in options for child constructors and options for children of the child * * var myButton = myComponent.addChild('MyButton', { * text: 'Press Me', * children: { * buttonChildExample: { * buttonChildOption: true * } * } * }); * * @param {String|vjs.Component} child The class name or instance of a child to add * @param {Object=} options Options, including options to be passed to children of the child. * @return {vjs.Component} The child component (created by this process if a string was used) * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility} */ vjs.Component.prototype.addChild = function(child, options){ var component, componentClass, componentName, componentId; // If string, create new component with options if (typeof child === 'string') { componentName = child; // Make sure options is at least an empty object to protect against errors options = options || {}; // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.) componentClass = options['componentClass'] || vjs.capitalize(componentName); // Set name through options options['name'] = componentName; // Create a new object & element for this controls set // If there's no .player_, this is a player // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly. // Every class should be exported, so this should never be a problem here. component = new window['videojs'][componentClass](this.player_ || this, options); // child is a component instance } else { component = child; } this.children_.push(component); if (typeof component.id === 'function') { this.childIndex_[component.id()] = component; } // If a name wasn't used to create the component, check if we can use the // name function of the component componentName = componentName || (component.name && component.name()); if (componentName) { this.childNameIndex_[componentName] = component; } // Add the UI object's element to the container div (box) // Having an element is not required if (typeof component['el'] === 'function' && component['el']()) { this.contentEl().appendChild(component['el']()); } // Return so it can stored on parent object if desired. return component; }; /** * Remove a child component from this component's list of children, and the * child component's element from this component's element * * @param {vjs.Component} component Component to remove */ vjs.Component.prototype.removeChild = function(component){ if (typeof component === 'string') { component = this.getChild(component); } if (!component || !this.children_) return; var childFound = false; for (var i = this.children_.length - 1; i >= 0; i--) { if (this.children_[i] === component) { childFound = true; this.children_.splice(i,1); break; } } if (!childFound) return; this.childIndex_[component.id] = null; this.childNameIndex_[component.name] = null; var compEl = component.el(); if (compEl && compEl.parentNode === this.contentEl()) { this.contentEl().removeChild(component.el()); } }; /** * Add and initialize default child components from options * * // when an instance of MyComponent is created, all children in options * // will be added to the instance by their name strings and options * MyComponent.prototype.options_.children = { * myChildComponent: { * myChildOption: true * } * } */ vjs.Component.prototype.initChildren = function(){ var options = this.options_; if (options && options['children']) { var self = this; // Loop through components and add them to the player vjs.obj.each(options['children'], function(name, opts){ // Allow for disabling default components // e.g. vjs.options['children']['posterImage'] = false if (opts === false) return; // Allow waiting to add components until a specific event is called var tempAdd = function(){ // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy. self[name] = self.addChild(name, opts); }; if (opts['loadEvent']) { // this.one(opts.loadEvent, tempAdd) } else { tempAdd(); } }); } }; /** * Allows sub components to stack CSS class names * * @return {String} The constructed