Files
friendica/library/video-js/video.dev.js
2013-05-11 10:55:09 -06:00

6080 lines
183 KiB
JavaScript

/**
* @fileoverview Main function src.
*/
// HTML5 Shiv. Must be in <head> to support older browsers.
document.createElement('video');document.createElement('audio');
/**
* Doubles as the main function for users to create a player instance and also
* the main library object.
*
* @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
*/
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 = 'GENERATED_CDN_VSN';
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': { 'swf': vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/c/video-js.swf' },
// 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': {}
}
};
/**
* Global player list
* @type {Object}
*/
vjs.players = {};
// Set CDN Version of swf
if (vjs.CDN_VERSION != 'GENERATED_CDN_VSN') {
videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';
}
/**
* Core Object/Class for objects that use inheritance + contstructors
* @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
* @param {Object} props Functions and properties to be applied to the
* new object's prototype
* @return {vjs.CoreObject} Returns 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
* @return {vjs.CoreObject} Returns 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.
*/
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.
*/
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
*/
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}
*/
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) {
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
*/
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, recursively calls this function to bubble the event up the DOM.
if (parent && !event.isPropagationStopped()) {
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 {[type]} type [description]
* @param {Function} fn [description]
* @return {[type]}
*/
vjs.one = function(elem, type, fn) {
vjs.on(elem, type, function(){
vjs.off(elem, type, arguments.callee);
fn.apply(this, arguments);
});
};
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}
*/
vjs.createEl = function(tagName, properties){
var el = document.createElement(tagName || 'div');
for (var 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}
*/
vjs.capitalize = function(string){
return string.charAt(0).toUpperCase() + string.slice(1);
};
/**
* Object functions container
* @type {Object}
*/
vjs.obj = {};
/**
* Object.create shim for prototypal inheritance.
* https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
* @param {Object} obj Object to use as prototype
*/
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 {*}
*/
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}
*/
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.
*/
vjs.obj.deepMerge = function(obj1, obj2){
var key, val1, val2, objDef;
objDef = '[object Object]';
// 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
*/
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
*/
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}
*/
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}
*/
vjs.cache = {};
/**
* Unique ID for an element or function
* @type {Number}
*/
vjs.guid = 1;
/**
* Unique attribute name to store an element's guid in
* @type {String}
* @constant
*/
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}
*/
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}
*/
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
*/
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;
}
}
};
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
*/
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
*/
vjs.removeClass = function(element, classToRemove){
if (element.className.indexOf(classToRemove) == -1) { return; }
var classNames = element.className.split(' ');
// IE8 Does not support array.indexOf so using a for loop
for (var i = classNames.length - 1; i >= 0; i--) {
if (classNames[i] === classToRemove) {
classNames.splice(i,1);
}
}
// classNames.splice(classNames.indexOf(classToRemove),1);
element.className = classNames.join(' ');
};
/**
* Element for testing browser HTML5 video capabilities
* @type {Element}
* @constant
*/
vjs.TEST_VID = vjs.createEl('video');
/**
* Useragent for browser testing.
* @type {String}
* @constant
*/
vjs.USER_AGENT = navigator.userAgent;
/**
* Device is an iPhone
* @type {Boolean}
* @constant
*/
vjs.IS_IPHONE = !!vjs.USER_AGENT.match(/iPhone/i);
vjs.IS_IPAD = !!vjs.USER_AGENT.match(/iPad/i);
vjs.IS_IPOD = !!vjs.USER_AGENT.match(/iPod/i);
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 = !!vjs.USER_AGENT.match(/Android.*AppleWebKit/i);
vjs.ANDROID_VERSION = (function() {
var match = vjs.USER_AGENT.match(/Android (\d+)\./i);
if (match && match[1]) {
return match[1];
}
return null;
})();
vjs.IS_FIREFOX = function(){ return !!vjs.USER_AGENT.match('Firefox'); };
/**
* 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}
*/
vjs.getAttributeValues = function(tag){
var 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.
// Bookending with commas to allow for an easy string search.
var knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
if (tag && tag.attributes && tag.attributes.length > 0) {
var attrs = tag.attributes;
var attrName, attrVal;
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
*/
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
*/
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}
*/
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
*/
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
*/
vjs.formatTime = function(seconds, guide) {
guide = guide || seconds; // Default to using seconds as guide
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);
// 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
*/
vjs.trim = function(string){
return string.toString().replace(/^\s+/, '').replace(/\s+$/, '');
};
/**
* 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
*/
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
*/
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
*/
vjs.get = function(url, onSuccess, onError){
var local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
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.');
};
}
var request = new XMLHttpRequest();
try {
request.open('GET', url);
} catch(e) {
onError(e);
}
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);
}
}
};
/* Local Storage
================================================================================ */
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
*/
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
* @param {Object} player Main Player
* @param {Object=} options
* @constructor
*/
vjs.Component = vjs.CoreObject.extend({
/** @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(){
// 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_;
/**
* Return the component's player.
* @return {vjs.Player}
*/
vjs.Component.prototype.player = function(){
return this.player_;
};
/**
* Component 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);
};
/**
* Return the component's DOM element.
* @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_;
/**
* Return the component's 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_;
/**
* Return the component's ID.
* @return {String}
*/
vjs.Component.prototype.name = function(){
return this.name_;
};
/**
* Array of child components
* @type {Array}
* @private
*/
vjs.Component.prototype.children_;
/**
* Returns array of all child components.
* @return {Array}
*/
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 {Array}
*/
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 {Array}
*/
vjs.Component.prototype.getChild = function(name){
return this.childNameIndex_[name];
};
/**
* Adds a child component inside this component.
* @param {String|vjs.Component} child The class name or instance of a child to add.
* @param {Object=} options Optional options, including options to be passed to
* children of the child.
* @return {vjs.Component} The child component, because it might be created in this process.
* @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;
};
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());
}
};
/**
* Initialize default child components from options
*/
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();
}
});
}
};
vjs.Component.prototype.buildCSSClass = function(){
// Child classes can include a function that does:
// return 'CLASS NAME' + this._super();
return '';
};
/* Events
============================================================================= */
/**
* Add an event listener to this component's element. Context will be the component.
* @param {String} type Event type e.g. 'click'
* @param {Function} fn Event listener
* @return {vjs.Component}
*/
vjs.Component.prototype.on = function(type, fn){
vjs.on(this.el_, type, vjs.bind(this, fn));
return this;
};
/**
* Remove an event listener from the component's element
* @param {String=} type Optional event type. Without type it will remove all listeners.
* @param {Function=} fn Optional event listener. Without fn it will remove all listeners for a type.
* @return {vjs.Component}
*/
vjs.Component.prototype.off = function(type, fn){
vjs.off(this.el_, type, fn);
return this;
};
/**
* Add an event listener to be triggered only once and then removed
* @param {String} type Event type
* @param {Function} fn Event listener
* @return {vjs.Component}
*/
vjs.Component.prototype.one = function(type, fn) {
vjs.one(this.el_, type, vjs.bind(this, fn));
return this;
};
/**
* Trigger an event on an element
* @param {String} type Event type to trigger
* @param {Event|Object} event Event object to be passed to the listener
* @return {vjs.Component}
*/
vjs.Component.prototype.trigger = function(type, event){
vjs.trigger(this.el_, type, event);
return this;
};
/* Ready
================================================================================ */
/**
* Is the component loaded.
* @type {Boolean}
* @private
*/
vjs.Component.prototype.isReady_;
/**
* Trigger ready as soon as initialization is finished.
* Allows for delaying ready. Override on a sub class prototype.
* If you set this.isReadyOnInitFinish_ it will affect all components.
* Specially used when waiting for the Flash player to asynchrnously load.
* @type {Boolean}
* @private
*/
vjs.Component.prototype.isReadyOnInitFinish_ = true;
/**
* List of ready listeners
* @type {Array}
* @private
*/
vjs.Component.prototype.readyQueue_;
/**
* Bind a listener to the component's ready state.
* Different from event listeners in that if the ready event has already happend
* it will trigger the function immediately.
* @param {Function} fn Ready listener
* @return {vjs.Component}
*/
vjs.Component.prototype.ready = function(fn){
if (fn) {
if (this.isReady_) {
fn.call(this);
} else {
if (this.readyQueue_ === undefined) {
this.readyQueue_ = [];
}
this.readyQueue_.push(fn);
}
}
return this;
};
/**
* Trigger the ready listeners
* @return {vjs.Component}
*/
vjs.Component.prototype.triggerReady = function(){
this.isReady_ = true;
var readyQueue = this.readyQueue_;
if (readyQueue && readyQueue.length > 0) {
for (var i = 0, j = readyQueue.length; i < j; i++) {
readyQueue[i].call(this);
}
// Reset Ready Queue
this.readyQueue_ = [];
// Allow for using event listeners also, in case you want to do something everytime a source is ready.
this.trigger('ready');
}
};
/* Display
============================================================================= */
/**
* Add a CSS class name to the component's element
* @param {String} classToAdd Classname to add
* @return {vjs.Component}
*/
vjs.Component.prototype.addClass = function(classToAdd){
vjs.addClass(this.el_, classToAdd);
return this;
};
/**
* Remove a CSS class name from the component's element
* @param {String} classToRemove Classname to remove
* @return {vjs.Component}
*/
vjs.Component.prototype.removeClass = function(classToRemove){
vjs.removeClass(this.el_, classToRemove);
return this;
};
/**
* Show the component element if hidden
* @return {vjs.Component}
*/
vjs.Component.prototype.show = function(){
this.el_.style.display = 'block';
return this;
};
/**
* Hide the component element if hidden
* @return {vjs.Component}
*/
vjs.Component.prototype.hide = function(){
this.el_.style.display = 'none';
return this;
};
/**
* Fade a component in using CSS
* @return {vjs.Component}
*/
vjs.Component.prototype.fadeIn = function(){
this.removeClass('vjs-fade-out');
this.addClass('vjs-fade-in');
return this;
};
/**
* Fade a component out using CSS
* @return {vjs.Component}
*/
vjs.Component.prototype.fadeOut = function(){
this.removeClass('vjs-fade-in');
this.addClass('vjs-fade-out');
return this;
};
/**
* Lock an item in its visible state. To be used with fadeIn/fadeOut.
* @return {vjs.Component}
*/
vjs.Component.prototype.lockShowing = function(){
this.addClass('vjs-lock-showing');
return this;
};
/**
* Unlock an item to be hidden. To be used with fadeIn/fadeOut.
* @return {vjs.Component}
*/
vjs.Component.prototype.unlockShowing = function(){
this.removeClass('vjs-lock-showing');
return this;
};
/**
* Disable component by making it unshowable
*/
vjs.Component.prototype.disable = function(){
this.hide();
this.show = function(){};
this.fadeIn = function(){};
};
// TODO: Get enable working
// vjs.Component.prototype.enable = function(){
// this.fadeIn = vjs.Component.prototype.fadeIn;
// this.show = vjs.Component.prototype.show;
// };
/**
* If a value is provided it will change the width of the player to that value
* otherwise the width is returned
* http://dev.w3.org/html5/spec/dimension-attributes.html#attr-dim-height
* Video tag width/height only work in pixels. No percents.
* But allowing limited percents use. e.g. width() will return number+%, not computed width
* @param {Number|String=} num Optional width number
* @param {[type]} skipListeners Skip the 'resize' event trigger
* @return {vjs.Component|Number|String} Returns 'this' if dimension was set.
* Otherwise it returns the dimension.
*/
vjs.Component.prototype.width = function(num, skipListeners){
return this.dimension('width', num, skipListeners);
};
/**
* Get or set the height of the player
* @param {Number|String=} num Optional new player height
* @param {Boolean=} skipListeners Optional skip resize event trigger
* @return {vjs.Component|Number|String} The player, or the dimension
*/
vjs.Component.prototype.height = function(num, skipListeners){
return this.dimension('height', num, skipListeners);
};
/**
* Set both width and height at the same time.
* @param {Number|String} width
* @param {Number|String} height
* @return {vjs.Component} The player.
*/
vjs.Component.prototype.dimensions = function(width, height){
// Skip resize listeners on width for optimization
return this.width(width, true).height(height);
};
/**
* Get or set width or height.
* All for an integer, integer + 'px' or integer + '%';
* Known issue: hidden elements. Hidden elements officially have a width of 0.
* So we're defaulting to the style.width value and falling back to computedStyle
* which has the hidden element issue.
* Info, but probably not an efficient fix:
* http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
* @param {String=} widthOrHeight 'width' or 'height'
* @param {Number|String=} num New dimension
* @param {Boolean=} skipListeners Skip resize event trigger
* @return {vjs.Component|Number|String} Return the player if setting a dimension.
* Otherwise it returns the dimension.
*/
vjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){
if (num !== undefined) {
// Check if using css width/height (% or px) and adjust
if ((''+num).indexOf('%') !== -1 || (''+num).indexOf('px') !== -1) {
this.el_.style[widthOrHeight] = num;
} else if (num === 'auto') {
this.el_.style[widthOrHeight] = '';
} else {
this.el_.style[widthOrHeight] = num+'px';
}
// skipListeners allows us to avoid triggering the resize event when setting both width and height
if (!skipListeners) { this.trigger('resize'); }
// Return component
return this;
}
// Not setting a value, so getting it
// Make sure element exists
if (!this.el_) return 0;
// Get dimension value from style
var val = this.el_.style[widthOrHeight];
var pxIndex = val.indexOf('px');
if (pxIndex !== -1) {
// Return the pixel value with no 'px'
return parseInt(val.slice(0,pxIndex), 10);
// No px so using % or no style was set, so falling back to offsetWidth/height
// If component has display:none, offset will return 0
// TODO: handle display:none and no dimension style using px
} else {
return parseInt(this.el_['offset'+vjs.capitalize(widthOrHeight)], 10);
// ComputedStyle version.
// Only difference is if the element is hidden it will return
// the percent value (e.g. '100%'')
// instead of zero like offsetWidth returns.
// var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);
// var pxIndex = val.indexOf('px');
// if (pxIndex !== -1) {
// return val.slice(0, pxIndex);
// } else {
// return val;
// }
}
};
/* Button - Base class for all buttons
================================================================================ */
/**
* Base class for all buttons
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Button = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
var touchstart = false;
this.on('touchstart', function() {
touchstart = true;
});
this.on('touchmove', function() {
touchstart = false;
});
var self = this;
this.on('touchend', function(event) {
if (touchstart) {
self.onClick(event);
}
event.preventDefault();
event.stopPropagation();
});
this.on('click', this.onClick);
this.on('focus', this.onFocus);
this.on('blur', this.onBlur);
}
});
vjs.Button.prototype.createEl = function(type, props){
// Add standard Aria and Tabindex info
props = vjs.obj.merge({
className: this.buildCSSClass(),
innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + (this.buttonText || 'Need Text') + '</span></div>',
role: 'button',
'aria-live': 'polite', // let the screen reader user know that the text of the button may change
tabIndex: 0
}, props);
return vjs.Component.prototype.createEl.call(this, type, props);
};
vjs.Button.prototype.buildCSSClass = function(){
// TODO: Change vjs-control to vjs-button?
return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);
};
// Click - Override with specific functionality for button
vjs.Button.prototype.onClick = function(){};
// Focus - Add keyboard functionality to element
vjs.Button.prototype.onFocus = function(){
vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
// KeyPress (document level) - Trigger click when keys are pressed
vjs.Button.prototype.onKeyPress = function(event){
// Check for space bar (32) or enter (13) keys
if (event.which == 32 || event.which == 13) {
event.preventDefault();
this.onClick();
}
};
// Blur - Remove keyboard triggers
vjs.Button.prototype.onBlur = function(){
vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
/* Slider
================================================================================ */
/**
* Parent for seek bar and volume slider
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Slider = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
// Set property names to bar and handle to match with the child Slider class is looking for
this.bar = this.getChild(this.options_['barName']);
this.handle = this.getChild(this.options_['handleName']);
player.on(this.playerEvent, vjs.bind(this, this.update));
this.on('mousedown', this.onMouseDown);
this.on('touchstart', this.onMouseDown);
this.on('focus', this.onFocus);
this.on('blur', this.onBlur);
this.on('click', this.onClick);
this.player_.on('controlsvisible', vjs.bind(this, this.update));
// This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
// this.player_.one('timeupdate', vjs.bind(this, this.update));
player.ready(vjs.bind(this, this.update));
this.boundEvents = {};
}
});
vjs.Slider.prototype.createEl = function(type, props) {
props = props || {};
// Add the slider element class to all sub classes
props.className = props.className + ' vjs-slider';
props = vjs.obj.merge({
role: 'slider',
'aria-valuenow': 0,
'aria-valuemin': 0,
'aria-valuemax': 100,
tabIndex: 0
}, props);
return vjs.Component.prototype.createEl.call(this, type, props);
};
vjs.Slider.prototype.onMouseDown = function(event){
event.preventDefault();
vjs.blockTextSelection();
this.boundEvents.move = vjs.bind(this, this.onMouseMove);
this.boundEvents.end = vjs.bind(this, this.onMouseUp);
vjs.on(document, 'mousemove', this.boundEvents.move);
vjs.on(document, 'mouseup', this.boundEvents.end);
vjs.on(document, 'touchmove', this.boundEvents.move);
vjs.on(document, 'touchend', this.boundEvents.end);
this.onMouseMove(event);
};
vjs.Slider.prototype.onMouseUp = function() {
vjs.unblockTextSelection();
vjs.off(document, 'mousemove', this.boundEvents.move, false);
vjs.off(document, 'mouseup', this.boundEvents.end, false);
vjs.off(document, 'touchmove', this.boundEvents.move, false);
vjs.off(document, 'touchend', this.boundEvents.end, false);
this.update();
};
vjs.Slider.prototype.update = function(){
// In VolumeBar init we have a setTimeout for update that pops and update to the end of the
// execution stack. The player is destroyed before then update will cause an error
if (!this.el_) return;
// If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
// On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
// var progress = (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
var barProgress,
progress = this.getPercent(),
handle = this.handle,
bar = this.bar;
// Protect against no duration and other division issues
if (isNaN(progress)) { progress = 0; }
barProgress = progress;
// If there is a handle, we need to account for the handle in our calculation for progress bar
// so that it doesn't fall short of or extend past the handle.
if (handle) {
var box = this.el_,
boxWidth = box.offsetWidth,
handleWidth = handle.el().offsetWidth,
// The width of the handle in percent of the containing box
// In IE, widths may not be ready yet causing NaN
handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
// Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
// There is a margin of half the handle's width on both sides.
boxAdjustedPercent = 1 - handlePercent,
// Adjust the progress that we'll use to set widths to the new adjusted box width
adjustedProgress = progress * boxAdjustedPercent;
// The bar does reach the left side, so we need to account for this in the bar's width
barProgress = adjustedProgress + (handlePercent / 2);
// Move the handle from the left based on the adjected progress
handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';
}
// Set the new bar width
bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
};
vjs.Slider.prototype.calculateDistance = function(event){
var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;
el = this.el_;
box = vjs.findPosition(el);
boxW = boxH = el.offsetWidth;
handle = this.handle;
if (this.options_.vertical) {
boxY = box.top;
if (event.changedTouches) {
pageY = event.changedTouches[0].pageY;
} else {
pageY = event.pageY;
}
if (handle) {
var handleH = handle.el().offsetHeight;
// Adjusted X and Width, so handle doesn't go outside the bar
boxY = boxY + (handleH / 2);
boxH = boxH - handleH;
}
// Percent that the click is through the adjusted area
return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
} else {
boxX = box.left;
if (event.changedTouches) {
pageX = event.changedTouches[0].pageX;
} else {
pageX = event.pageX;
}
if (handle) {
var handleW = handle.el().offsetWidth;
// Adjusted X and Width, so handle doesn't go outside the bar
boxX = boxX + (handleW / 2);
boxW = boxW - handleW;
}
// Percent that the click is through the adjusted area
return Math.max(0, Math.min(1, (pageX - boxX) / boxW));
}
};
vjs.Slider.prototype.onFocus = function(){
vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
vjs.Slider.prototype.onKeyPress = function(event){
if (event.which == 37) { // Left Arrow
event.preventDefault();
this.stepBack();
} else if (event.which == 39) { // Right Arrow
event.preventDefault();
this.stepForward();
}
};
vjs.Slider.prototype.onBlur = function(){
vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
/**
* Listener for click events on slider, used to prevent clicks
* from bubbling up to parent elements like button menus.
* @param {Object} event Event object
*/
vjs.Slider.prototype.onClick = function(event){
event.stopImmediatePropagation();
event.preventDefault();
};
/**
* SeekBar Behavior includes play progress bar, and seek handle
* Needed so it can determine seek position based on handle position/size
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.SliderHandle = vjs.Component.extend();
/**
* Default value of the slider
* @type {Number}
*/
vjs.SliderHandle.prototype.defaultValue = 0;
/** @inheritDoc */
vjs.SliderHandle.prototype.createEl = function(type, props) {
props = props || {};
// Add the slider element class to all sub classes
props.className = props.className + ' vjs-slider-handle';
props = vjs.obj.merge({
innerHTML: '<span class="vjs-control-text">'+this.defaultValue+'</span>'
}, props);
return vjs.Component.prototype.createEl.call(this, 'div', props);
};
/* Menu
================================================================================ */
/**
* The base for text track and settings menu buttons.
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Menu = vjs.Component.extend();
/**
* Add a menu item to the menu
* @param {Object|String} component Component or component type to add
*/
vjs.Menu.prototype.addItem = function(component){
this.addChild(component);
component.on('click', vjs.bind(this, function(){
this.unlockShowing();
}));
};
/** @inheritDoc */
vjs.Menu.prototype.createEl = function(){
var contentElType = this.options().contentElType || 'ul';
this.contentEl_ = vjs.createEl(contentElType, {
className: 'vjs-menu-content'
});
var el = vjs.Component.prototype.createEl.call(this, 'div', {
append: this.contentEl_,
className: 'vjs-menu'
});
el.appendChild(this.contentEl_);
// Prevent clicks from bubbling up. Needed for Menu Buttons,
// where a click on the parent is significant
vjs.on(el, 'click', function(event){
event.preventDefault();
event.stopImmediatePropagation();
});
return el;
};
/**
* Menu item
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.MenuItem = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
this.selected(options['selected']);
}
});
/** @inheritDoc */
vjs.MenuItem.prototype.createEl = function(type, props){
return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({
className: 'vjs-menu-item',
innerHTML: this.options_['label']
}, props));
};
/** @inheritDoc */
vjs.MenuItem.prototype.onClick = function(){
this.selected(true);
};
/**
* Set this menu item as selected or not
* @param {Boolean} selected
*/
vjs.MenuItem.prototype.selected = function(selected){
if (selected) {
this.addClass('vjs-selected');
this.el_.setAttribute('aria-selected',true);
} else {
this.removeClass('vjs-selected');
this.el_.setAttribute('aria-selected',false);
}
};
/**
* A button class with a popup menu
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.MenuButton = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
this.menu = this.createMenu();
// Add list to element
this.addChild(this.menu);
// Automatically hide empty menu buttons
if (this.items && this.items.length === 0) {
this.hide();
}
this.on('keyup', this.onKeyPress);
this.el_.setAttribute('aria-haspopup', true);
this.el_.setAttribute('role', 'button');
}
});
/**
* Track the state of the menu button
* @type {Boolean}
*/
vjs.MenuButton.prototype.buttonPressed_ = false;
vjs.MenuButton.prototype.createMenu = function(){
var menu = new vjs.Menu(this.player_);
// Add a title list item to the top
if (this.options().title) {
menu.el().appendChild(vjs.createEl('li', {
className: 'vjs-menu-title',
innerHTML: vjs.capitalize(this.kind_),
tabindex: -1
}));
}
this.items = this.createItems();
if (this.items) {
// Add menu items to the menu
for (var i = 0; i < this.items.length; i++) {
menu.addItem(this.items[i]);
}
}
return menu;
};
/**
* Create the list of menu items. Specific to each subclass.
*/
vjs.MenuButton.prototype.createItems = function(){};
/** @inheritDoc */
vjs.MenuButton.prototype.buildCSSClass = function(){
return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);
};
// Focus - Add keyboard functionality to element
// This function is not needed anymore. Instead, the keyboard functionality is handled by
// treating the button as triggering a submenu. When the button is pressed, the submenu
// appears. Pressing the button again makes the submenu disappear.
vjs.MenuButton.prototype.onFocus = function(){};
// Can't turn off list display that we turned on with focus, because list would go away.
vjs.MenuButton.prototype.onBlur = function(){};
vjs.MenuButton.prototype.onClick = function(){
// When you click the button it adds focus, which will show the menu indefinitely.
// So we'll remove focus when the mouse leaves the button.
// Focus is needed for tab navigation.
this.one('mouseout', vjs.bind(this, function(){
this.menu.unlockShowing();
this.el_.blur();
}));
if (this.buttonPressed_){
this.unpressButton();
} else {
this.pressButton();
}
};
vjs.MenuButton.prototype.onKeyPress = function(event){
event.preventDefault();
// Check for space bar (32) or enter (13) keys
if (event.which == 32 || event.which == 13) {
if (this.buttonPressed_){
this.unpressButton();
} else {
this.pressButton();
}
// Check for escape (27) key
} else if (event.which == 27){
if (this.buttonPressed_){
this.unpressButton();
}
}
};
vjs.MenuButton.prototype.pressButton = function(){
this.buttonPressed_ = true;
this.menu.lockShowing();
this.el_.setAttribute('aria-pressed', true);
if (this.items && this.items.length > 0) {
this.items[0].el().focus(); // set the focus to the title of the submenu
}
};
vjs.MenuButton.prototype.unpressButton = function(){
this.buttonPressed_ = false;
this.menu.unlockShowing();
this.el_.setAttribute('aria-pressed', false);
};
/**
* Main player class. A player instance is returned by _V_(id);
* @param {Element} tag The original video tag used for configuring options
* @param {Object=} options Player options
* @param {Function=} ready Ready callback function
* @constructor
*/
vjs.Player = vjs.Component.extend({
/** @constructor */
init: function(tag, options, ready){
this.tag = tag; // Store the original tag used to set options
// Set Options
// The options argument overrides options set in the video tag
// which overrides globally set options.
// This latter part coincides with the load order
// (tag must exist before Player)
options = vjs.obj.merge(this.getTagSettings(tag), options);
// Cache for video property values.
this.cache_ = {};
// Set poster
this.poster_ = options['poster'];
// Set controls
this.controls_ = options['controls'];
// Use native controls for iOS and Android by default
// until controls are more stable on those devices.
if (options['customControlsOnMobile'] !== true && (vjs.IS_IOS || vjs.IS_ANDROID)) {
tag.controls = options['controls'];
this.controls_ = false;
} else {
// Original tag settings stored in options
// now remove immediately so native controls don't flash.
tag.controls = false;
}
// Run base component initializing with new options.
// Builds the element through createEl()
// Inits and embeds any child components in opts
vjs.Component.call(this, this, options, ready);
// Firstplay event implimentation. Not sold on the event yet.
// Could probably just check currentTime==0?
this.one('play', function(e){
var fpEvent = { type: 'firstplay', target: this.el_ };
// Using vjs.trigger so we can check if default was prevented
var keepGoing = vjs.trigger(this.el_, fpEvent);
if (!keepGoing) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
});
this.on('ended', this.onEnded);
this.on('play', this.onPlay);
this.on('firstplay', this.onFirstPlay);
this.on('pause', this.onPause);
this.on('progress', this.onProgress);
this.on('durationchange', this.onDurationChange);
this.on('error', this.onError);
this.on('fullscreenchange', this.onFullscreenChange);
// Make player easily findable by ID
vjs.players[this.id_] = this;
if (options['plugins']) {
vjs.obj.each(options['plugins'], function(key, val){
this[key](val);
}, this);
}
}
});
/**
* Player instance options, surfaced using vjs.options
* vjs.options = vjs.Player.prototype.options_
* Make changes in vjs.options, not here.
* All options should use string keys so they avoid
* renaming by closure compiler
* @type {Object}
* @private
*/
vjs.Player.prototype.options_ = vjs.options;
vjs.Player.prototype.dispose = function(){
// this.isReady_ = false;
// Kill reference to this player
vjs.players[this.id_] = null;
if (this.tag && this.tag['player']) { this.tag['player'] = null; }
if (this.el_ && this.el_['player']) { this.el_['player'] = null; }
// Ensure that tracking progress and time progress will stop and plater deleted
this.stopTrackingProgress();
this.stopTrackingCurrentTime();
if (this.tech) { this.tech.dispose(); }
// Component dispose
vjs.Component.prototype.dispose.call(this);
};
vjs.Player.prototype.getTagSettings = function(tag){
var options = {
'sources': [],
'tracks': []
};
vjs.obj.merge(options, vjs.getAttributeValues(tag));
// Get tag children settings
if (tag.hasChildNodes()) {
var child, childName,
children = tag.childNodes,
i = 0,
j = children.length;
for (; i < j; i++) {
child = children[i];
// Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
childName = child.nodeName.toLowerCase();
if (childName === 'source') {
options['sources'].push(vjs.getAttributeValues(child));
} else if (childName === 'track') {
options['tracks'].push(vjs.getAttributeValues(child));
}
}
}
return options;
};
vjs.Player.prototype.createEl = function(){
var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
var tag = this.tag;
// Remove width/height attrs from tag so CSS can make it 100% width/height
tag.removeAttribute('width');
tag.removeAttribute('height');
// Empty video tag sources and tracks so the built-in player doesn't use them also.
// This may not be fast enough to stop HTML5 browsers from reading the tags
// so we'll need to turn off any default tracks if we're manually doing
// captions and subtitles. videoElement.textTracks
if (tag.hasChildNodes()) {
var nrOfChildNodes = tag.childNodes.length;
for (var i=0,j=tag.childNodes;i<nrOfChildNodes;i++) {
if (j[0].nodeName.toLowerCase() == 'source' || j[0].nodeName.toLowerCase() == 'track') {
tag.removeChild(j[0]);
}
}
}
// Make sure tag ID exists
tag.id = tag.id || 'vjs_video_' + vjs.guid++;
// Give video tag ID and class to player div
// ID will now reference player box, not the video tag
el.id = tag.id;
el.className = tag.className;
// Update tag id/class for use as HTML5 playback tech
// Might think we should do this after embedding in container so .vjs-tech class
// doesn't flash 100% width/height, but class only applies with .video-js parent
tag.id += '_html5_api';
tag.className = 'vjs-tech';
// Make player findable on elements
tag['player'] = el['player'] = this;
// Default state of video is paused
this.addClass('vjs-paused');
// Make box use width/height of tag, or rely on default implementation
// Enforce with CSS since width/height attrs don't work on divs
this.width(this.options_['width'], true); // (true) Skip resize listener on load
this.height(this.options_['height'], true);
// Wrap video tag in div (el/box) container
if (tag.parentNode) {
tag.parentNode.insertBefore(el, tag);
}
vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
return el;
};
// /* Media Technology (tech)
// ================================================================================ */
// Load/Create an instance of playback technlogy including element and API methods
// And append playback element in player div.
vjs.Player.prototype.loadTech = function(techName, source){
// Pause and remove current playback technology
if (this.tech) {
this.unloadTech();
// If the first time loading, HTML5 tag will exist but won't be initialized
// So we need to remove it if we're not loading HTML5
} else if (techName !== 'Html5' && this.tag) {
this.el_.removeChild(this.tag);
this.tag.player = null;
this.tag = null;
}
this.techName = techName;
// Turn off API access because we're loading a new tech that might load asynchronously
this.isReady_ = false;
var techReady = function(){
this.player_.triggerReady();
// Manually track progress in cases where the browser/flash player doesn't report it.
if (!this.features.progressEvents) {
this.player_.manualProgressOn();
}
// Manually track timeudpates in cases where the browser/flash player doesn't report it.
if (!this.features.timeupdateEvents) {
this.player_.manualTimeUpdatesOn();
}
};
// Grab tech-specific options from player options and add source and parent element to use.
var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
if (source) {
if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
techOptions['startTime'] = this.cache_.currentTime;
}
this.cache_.src = source.src;
}
// Initialize tech instance
this.tech = new window['videojs'][techName](this, techOptions);
this.tech.ready(techReady);
};
vjs.Player.prototype.unloadTech = function(){
this.isReady_ = false;
this.tech.dispose();
// Turn off any manual progress or timeupdate tracking
if (this.manualProgress) { this.manualProgressOff(); }
if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
this.tech = false;
};
// There's many issues around changing the size of a Flash (or other plugin) object.
// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
// reloadTech: function(betweenFn){
// vjs.log('unloadingTech')
// this.unloadTech();
// vjs.log('unloadedTech')
// if (betweenFn) { betweenFn.call(); }
// vjs.log('LoadingTech')
// this.loadTech(this.techName, { src: this.cache_.src })
// vjs.log('loadedTech')
// },
/* Fallbacks for unsupported event types
================================================================================ */
// Manually trigger progress events based on changes to the buffered amount
// Many flash players and older HTML5 browsers don't send progress or progress-like events
vjs.Player.prototype.manualProgressOn = function(){
this.manualProgress = true;
// Trigger progress watching when a source begins loading
this.trackProgress();
// Watch for a native progress event call on the tech element
// In HTML5, some older versions don't support the progress event
// So we're assuming they don't, and turning off manual progress if they do.
// As opposed to doing user agent detection
this.tech.one('progress', function(){
// Update known progress support for this playback technology
this.features.progressEvents = true;
// Turn off manual progress tracking
this.player_.manualProgressOff();
});
};
vjs.Player.prototype.manualProgressOff = function(){
this.manualProgress = false;
this.stopTrackingProgress();
};
vjs.Player.prototype.trackProgress = function(){
this.progressInterval = setInterval(vjs.bind(this, function(){
// Don't trigger unless buffered amount is greater than last time
// log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
/* TODO: update for multiple buffered regions */
if (this.cache_.bufferEnd < this.buffered().end(0)) {
this.trigger('progress');
} else if (this.bufferedPercent() == 1) {
this.stopTrackingProgress();
this.trigger('progress'); // Last update
}
}), 500);
};
vjs.Player.prototype.stopTrackingProgress = function(){ clearInterval(this.progressInterval); };
/* Time Tracking -------------------------------------------------------------- */
vjs.Player.prototype.manualTimeUpdatesOn = function(){
this.manualTimeUpdates = true;
this.on('play', this.trackCurrentTime);
this.on('pause', this.stopTrackingCurrentTime);
// timeupdate is also called by .currentTime whenever current time is set
// Watch for native timeupdate event
this.tech.one('timeupdate', function(){
// Update known progress support for this playback technology
this.features.timeupdateEvents = true;
// Turn off manual progress tracking
this.player_.manualTimeUpdatesOff();
});
};
vjs.Player.prototype.manualTimeUpdatesOff = function(){
this.manualTimeUpdates = false;
this.stopTrackingCurrentTime();
this.off('play', this.trackCurrentTime);
this.off('pause', this.stopTrackingCurrentTime);
};
vjs.Player.prototype.trackCurrentTime = function(){
if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
this.currentTimeInterval = setInterval(vjs.bind(this, function(){
this.trigger('timeupdate');
}), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
};
// Turn off play progress tracking (when paused or dragging)
vjs.Player.prototype.stopTrackingCurrentTime = function(){ clearInterval(this.currentTimeInterval); };
// /* Player event handlers (how the player reacts to certain events)
// ================================================================================ */
vjs.Player.prototype.onEnded = function(){
if (this.options_['loop']) {
this.currentTime(0);
this.play();
}
};
vjs.Player.prototype.onPlay = function(){
vjs.removeClass(this.el_, 'vjs-paused');
vjs.addClass(this.el_, 'vjs-playing');
};
vjs.Player.prototype.onFirstPlay = function(){
//If the first starttime attribute is specified
//then we will start at the given offset in seconds
if(this.options_['starttime']){
this.currentTime(this.options_['starttime']);
}
};
vjs.Player.prototype.onPause = function(){
vjs.removeClass(this.el_, 'vjs-playing');
vjs.addClass(this.el_, 'vjs-paused');
};
vjs.Player.prototype.onProgress = function(){
// Add custom event for when source is finished downloading.
if (this.bufferedPercent() == 1) {
this.trigger('loadedalldata');
}
};
// Update duration with durationchange event
// Allows for cacheing value instead of asking player each time.
vjs.Player.prototype.onDurationChange = function(){
this.duration(this.techGet('duration'));
};
vjs.Player.prototype.onError = function(e) {
vjs.log('Video Error', e);
};
vjs.Player.prototype.onFullscreenChange = function(e) {
if (this.isFullScreen) {
this.addClass('vjs-fullscreen');
} else {
this.removeClass('vjs-fullscreen');
}
};
// /* Player API
// ================================================================================ */
/**
* Object for cached values.
* @private
*/
vjs.Player.prototype.cache_;
vjs.Player.prototype.getCache = function(){
return this.cache_;
};
// Pass values to the playback tech
vjs.Player.prototype.techCall = function(method, arg){
// If it's not ready yet, call method when it is
if (this.tech && this.tech.isReady_) {
this.tech.ready(function(){
this[method](arg);
});
// Otherwise call method now
} else {
try {
this.tech[method](arg);
} catch(e) {
vjs.log(e);
throw e;
}
}
};
// Get calls can't wait for the tech, and sometimes don't need to.
vjs.Player.prototype.techGet = function(method){
// Make sure there is a tech
// if (!this.tech) {
// return;
// }
if (this.tech.isReady_) {
// Flash likes to die and reload when you hide or reposition it.
// In these cases the object methods go away and we get errors.
// When that happens we'll catch the errors and inform tech that it's not ready any more.
try {
return this.tech[method]();
} catch(e) {
// When building additional tech libs, an expected method may not be defined yet
if (this.tech[method] === undefined) {
vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);
} else {
// When a method isn't available on the object it throws a TypeError
if (e.name == 'TypeError') {
vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);
this.tech.isReady_ = false;
} else {
vjs.log(e);
}
}
throw e;
}
}
return;
};
/**
* Start media playback
* http://dev.w3.org/html5/spec/video.html#dom-media-play
* We're triggering the 'play' event here instead of relying on the
* media element to allow using event.preventDefault() to stop
* play from happening if desired. Usecase: preroll ads.
*/
vjs.Player.prototype.play = function(){
this.techCall('play');
return this;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-pause
vjs.Player.prototype.pause = function(){
this.techCall('pause');
return this;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-paused
// The initial state of paused should be true (in Safari it's actually false)
vjs.Player.prototype.paused = function(){
return (this.techGet('paused') === false) ? false : true;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-currenttime
vjs.Player.prototype.currentTime = function(seconds){
if (seconds !== undefined) {
// Cache the last set value for smoother scrubbing.
this.cache_.lastSetCurrentTime = seconds;
this.techCall('setCurrentTime', seconds);
// Improve the accuracy of manual timeupdates
if (this.manualTimeUpdates) { this.trigger('timeupdate'); }
return this;
}
// Cache last currentTime and return
// Default to 0 seconds
return this.cache_.currentTime = (this.techGet('currentTime') || 0);
};
// http://dev.w3.org/html5/spec/video.html#dom-media-duration
// Duration should return NaN if not available. ParseFloat will turn false-ish values to NaN.
vjs.Player.prototype.duration = function(seconds){
if (seconds !== undefined) {
// Cache the last set value for optimiized scrubbing (esp. Flash)
this.cache_.duration = parseFloat(seconds);
return this;
}
return this.cache_.duration;
};
// Calculates how much time is left. Not in spec, but useful.
vjs.Player.prototype.remainingTime = function(){
return this.duration() - this.currentTime();
};
// http://dev.w3.org/html5/spec/video.html#dom-media-buffered
// Buffered returns a timerange object.
// Kind of like an array of portions of the video that have been downloaded.
// So far no browsers return more than one range (portion)
vjs.Player.prototype.buffered = function(){
var buffered = this.techGet('buffered'),
start = 0,
// Default end to 0 and store in values
end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
if (buffered && buffered.length > 0 && buffered.end(0) !== end) {
end = buffered.end(0);
// Storing values allows them be overridden by setBufferedFromProgress
this.cache_.bufferEnd = end;
}
return vjs.createTimeRange(start, end);
};
// Calculates amount of buffer is full. Not in spec but useful.
vjs.Player.prototype.bufferedPercent = function(){
return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-volume
vjs.Player.prototype.volume = function(percentAsDecimal){
var vol;
if (percentAsDecimal !== undefined) {
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
this.cache_.volume = vol;
this.techCall('setVolume', vol);
vjs.setLocalStorage('volume', vol);
return this;
}
// Default to 1 when returning current volume.
vol = parseFloat(this.techGet('volume'));
return (isNaN(vol)) ? 1 : vol;
};
// http://dev.w3.org/html5/spec/video.html#attr-media-muted
vjs.Player.prototype.muted = function(muted){
if (muted !== undefined) {
this.techCall('setMuted', muted);
return this;
}
return this.techGet('muted') || false; // Default to false
};
// Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
vjs.Player.prototype.supportsFullScreen = function(){ return this.techGet('supportsFullScreen') || false; };
// Turn on fullscreen (or window) mode
vjs.Player.prototype.requestFullScreen = function(){
var requestFullScreen = vjs.support.requestFullScreen;
this.isFullScreen = true;
if (requestFullScreen) {
// the browser supports going fullscreen at the element level so we can
// take the controls fullscreen as well as the video
// Trigger fullscreenchange event after change
vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
this.isFullScreen = document[requestFullScreen.isFullScreen];
// If cancelling fullscreen, remove event listener.
if (this.isFullScreen === false) {
vjs.off(document, requestFullScreen.eventName, arguments.callee);
}
}));
// Flash and other plugins get reloaded when you take their parent to fullscreen.
// To fix that we'll remove the tech, and reload it after the resize has finished.
if (this.tech.features.fullscreenResize === false && this.options_['flash']['iFrameMode'] !== true) {
this.pause();
this.unloadTech();
vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
vjs.off(document, requestFullScreen.eventName, arguments.callee);
this.loadTech(this.techName, { src: this.cache_.src });
}));
this.el_[requestFullScreen.requestFn]();
} else {
this.el_[requestFullScreen.requestFn]();
}
this.trigger('fullscreenchange');
} else if (this.tech.supportsFullScreen()) {
// we can't take the video.js controls fullscreen but we can go fullscreen
// with native controls
this.techCall('enterFullScreen');
} else {
// fullscreen isn't supported so we'll just stretch the video element to
// fill the viewport
this.enterFullWindow();
this.trigger('fullscreenchange');
}
return this;
};
vjs.Player.prototype.cancelFullScreen = function(){
var requestFullScreen = vjs.support.requestFullScreen;
this.isFullScreen = false;
// Check for browser element fullscreen support
if (requestFullScreen) {
// Flash and other plugins get reloaded when you take their parent to fullscreen.
// To fix that we'll remove the tech, and reload it after the resize has finished.
if (this.tech.features.fullscreenResize === false && this.options_['flash']['iFrameMode'] !== true) {
this.pause();
this.unloadTech();
vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(){
vjs.off(document, requestFullScreen.eventName, arguments.callee);
this.loadTech(this.techName, { src: this.cache_.src });
}));
document[requestFullScreen.cancelFn]();
} else {
document[requestFullScreen.cancelFn]();
}
this.trigger('fullscreenchange');
} else if (this.tech.supportsFullScreen()) {
this.techCall('exitFullScreen');
} else {
this.exitFullWindow();
this.trigger('fullscreenchange');
}
return this;
};
// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
vjs.Player.prototype.enterFullWindow = function(){
this.isFullWindow = true;
// Storing original doc overflow value to return to when fullscreen is off
this.docOrigOverflow = document.documentElement.style.overflow;
// Add listener for esc key to exit fullscreen
vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
// Hide any scroll bars
document.documentElement.style.overflow = 'hidden';
// Apply fullscreen styles
vjs.addClass(document.body, 'vjs-full-window');
this.trigger('enterFullWindow');
};
vjs.Player.prototype.fullWindowOnEscKey = function(event){
if (event.keyCode === 27) {
if (this.isFullScreen === true) {
this.cancelFullScreen();
} else {
this.exitFullWindow();
}
}
};
vjs.Player.prototype.exitFullWindow = function(){
this.isFullWindow = false;
vjs.off(document, 'keydown', this.fullWindowOnEscKey);
// Unhide scroll bars.
document.documentElement.style.overflow = this.docOrigOverflow;
// Remove fullscreen styles
vjs.removeClass(document.body, 'vjs-full-window');
// Resize the box, controller, and poster to original sizes
// this.positionAll();
this.trigger('exitFullWindow');
};
vjs.Player.prototype.selectSource = function(sources){
// Loop through each playback technology in the options order
for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
var techName = vjs.capitalize(j[i]),
tech = window['videojs'][techName];
// Check if the browser supports this technology
if (tech.isSupported()) {
// Loop through each source object
for (var a=0,b=sources;a<b.length;a++) {
var source = b[a];
// Check if source can be played with this technology
if (tech['canPlaySource'](source)) {
return { source: source, tech: techName };
}
}
}
}
return false;
};
// src is a pretty powerful function
// If you pass it an array of source objects, it will find the best source to play and use that object.src
// If the new source requires a new playback technology, it will switch to that.
// If you pass it an object, it will set the source to object.src
// If you pass it anything else (url string) it will set the video source to that
vjs.Player.prototype.src = function(source){
// Case: Array of source objects to choose from and pick the best to play
if (source instanceof Array) {
var sourceTech = this.selectSource(source),
techName;
if (sourceTech) {
source = sourceTech.source;
techName = sourceTech.tech;
// If this technology is already loaded, set source
if (techName == this.techName) {
this.src(source); // Passing the source object
// Otherwise load this technology with chosen source
} else {
this.loadTech(techName, source);
}
} else {
this.el_.appendChild(vjs.createEl('p', {
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>.'
}));
}
// Case: Source object { src: '', type: '' ... }
} else if (source instanceof Object) {
if (window['videojs'][this.techName]['canPlaySource'](source)) {
this.src(source.src);
} else {
// Send through tech loop to check for a compatible technology.
this.src([source]);
}
// Case: URL String (http://myvideo...)
} else {
// Cache for getting last set source
this.cache_.src = source;
if (!this.isReady_) {
this.ready(function(){
this.src(source);
});
} else {
this.techCall('src', source);
if (this.options_['preload'] == 'auto') {
this.load();
}
if (this.options_['autoplay']) {
this.play();
}
}
}
return this;
};
// Begin loading the src data
// http://dev.w3.org/html5/spec/video.html#dom-media-load
vjs.Player.prototype.load = function(){
this.techCall('load');
return this;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
vjs.Player.prototype.currentSrc = function(){
return this.techGet('currentSrc') || this.cache_.src || '';
};
// Attributes/Options
vjs.Player.prototype.preload = function(value){
if (value !== undefined) {
this.techCall('setPreload', value);
this.options_['preload'] = value;
return this;
}
return this.techGet('preload');
};
vjs.Player.prototype.autoplay = function(value){
if (value !== undefined) {
this.techCall('setAutoplay', value);
this.options_['autoplay'] = value;
return this;
}
return this.techGet('autoplay', value);
};
vjs.Player.prototype.loop = function(value){
if (value !== undefined) {
this.techCall('setLoop', value);
this.options_['loop'] = value;
return this;
}
return this.techGet('loop');
};
/**
* The url of the poster image source.
* @type {String}
* @private
*/
vjs.Player.prototype.poster_;
/**
* Get or set the poster image source url.
* @param {String} src Poster image source URL
* @return {String} Poster image source URL or null
*/
vjs.Player.prototype.poster = function(src){
if (src !== undefined) {
this.poster_ = src;
}
return this.poster_;
};
/**
* Whether or not the controls are showing
* @type {Boolean}
* @private
*/
vjs.Player.prototype.controls_;
/**
* Get or set whether or not the controls are showing.
* @param {Boolean} controls Set controls to showing or not
* @return {Boolean} Controls are showing
*/
vjs.Player.prototype.controls = function(controls){
if (controls !== undefined) {
// Don't trigger a change event unless it actually changed
if (this.controls_ !== controls) {
this.controls_ = !!controls; // force boolean
this.trigger('controlschange');
}
}
return this.controls_;
};
vjs.Player.prototype.error = function(){ return this.techGet('error'); };
vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
// Methods to add support for
// networkState: function(){ return this.techCall('networkState'); },
// readyState: function(){ return this.techCall('readyState'); },
// seeking: function(){ return this.techCall('seeking'); },
// initialTime: function(){ return this.techCall('initialTime'); },
// startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
// played: function(){ return this.techCall('played'); },
// seekable: function(){ return this.techCall('seekable'); },
// videoTracks: function(){ return this.techCall('videoTracks'); },
// audioTracks: function(){ return this.techCall('audioTracks'); },
// videoWidth: function(){ return this.techCall('videoWidth'); },
// videoHeight: function(){ return this.techCall('videoHeight'); },
// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
// playbackRate: function(){ return this.techCall('playbackRate'); },
// mediaGroup: function(){ return this.techCall('mediaGroup'); },
// controller: function(){ return this.techCall('controller'); },
// defaultMuted: function(){ return this.techCall('defaultMuted'); }
// TODO
// currentSrcList: the array of sources including other formats and bitrates
// playList: array of source lists in order of playback
// RequestFullscreen API
(function(){
var prefix, requestFS, div;
div = document.createElement('div');
requestFS = {};
// Current W3C Spec
// http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
// Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
if (div.cancelFullscreen !== undefined) {
requestFS.requestFn = 'requestFullscreen';
requestFS.cancelFn = 'exitFullscreen';
requestFS.eventName = 'fullscreenchange';
requestFS.isFullScreen = 'fullScreen';
// Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations
// that use prefixes and vary slightly from the new W3C spec. Specifically,
// using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.
// Other browsers don't have any hints of which version they might follow yet,
// so not going to try to predict by looping through all prefixes.
} else {
if (document.mozCancelFullScreen) {
prefix = 'moz';
requestFS.isFullScreen = prefix + 'FullScreen';
} else {
prefix = 'webkit';
requestFS.isFullScreen = prefix + 'IsFullScreen';
}
if (div[prefix + 'RequestFullScreen']) {
requestFS.requestFn = prefix + 'RequestFullScreen';
requestFS.cancelFn = prefix + 'CancelFullScreen';
}
requestFS.eventName = prefix + 'fullscreenchange';
}
if (document[requestFS.cancelFn]) {
vjs.support.requestFullScreen = requestFS;
}
})();
/**
* Container of main controls
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.ControlBar = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
if (!player.controls()) {
this.disable();
}
player.one('play', vjs.bind(this, function(){
var touchstart,
fadeIn = vjs.bind(this, this.fadeIn),
fadeOut = vjs.bind(this, this.fadeOut);
this.fadeIn();
if ( !('ontouchstart' in window) ) {
this.player_.on('mouseover', fadeIn);
this.player_.on('mouseout', fadeOut);
this.player_.on('pause', vjs.bind(this, this.lockShowing));
this.player_.on('play', vjs.bind(this, this.unlockShowing));
}
touchstart = false;
this.player_.on('touchstart', function() {
touchstart = true;
});
this.player_.on('touchmove', function() {
touchstart = false;
});
this.player_.on('touchend', vjs.bind(this, function(event) {
var idx;
if (touchstart) {
idx = this.el().className.search('fade-in');
if (idx !== -1) {
this.fadeOut();
} else {
this.fadeIn();
}
}
touchstart = false;
if (!this.player_.paused()) {
event.preventDefault();
}
}));
}));
}
});
vjs.ControlBar.prototype.options_ = {
loadEvent: 'play',
children: {
'playToggle': {},
'currentTimeDisplay': {},
'timeDivider': {},
'durationDisplay': {},
'remainingTimeDisplay': {},
'progressControl': {},
'fullscreenToggle': {},
'volumeControl': {},
'muteToggle': {}
// 'volumeMenuButton': {}
}
};
vjs.ControlBar.prototype.createEl = function(){
return vjs.createEl('div', {
className: 'vjs-control-bar'
});
};
vjs.ControlBar.prototype.fadeIn = function(){
vjs.Component.prototype.fadeIn.call(this);
this.player_.trigger('controlsvisible');
};
vjs.ControlBar.prototype.fadeOut = function(){
vjs.Component.prototype.fadeOut.call(this);
this.player_.trigger('controlshidden');
};/**
* Button to toggle between play and pause
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.PlayToggle = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
player.on('play', vjs.bind(this, this.onPlay));
player.on('pause', vjs.bind(this, this.onPause));
}
});
vjs.PlayToggle.prototype.buttonText = 'Play';
vjs.PlayToggle.prototype.buildCSSClass = function(){
return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);
};
// OnClick - Toggle between play and pause
vjs.PlayToggle.prototype.onClick = function(){
if (this.player_.paused()) {
this.player_.play();
} else {
this.player_.pause();
}
};
// OnPlay - Add the vjs-playing class to the element so it can change appearance
vjs.PlayToggle.prototype.onPlay = function(){
vjs.removeClass(this.el_, 'vjs-paused');
vjs.addClass(this.el_, 'vjs-playing');
this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to "Pause"
};
// OnPause - Add the vjs-paused class to the element so it can change appearance
vjs.PlayToggle.prototype.onPause = function(){
vjs.removeClass(this.el_, 'vjs-playing');
vjs.addClass(this.el_, 'vjs-paused');
this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to "Play"
};/**
* Displays the current time
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.CurrentTimeDisplay = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('timeupdate', vjs.bind(this, this.updateContent));
}
});
vjs.CurrentTimeDisplay.prototype.createEl = function(){
var el = vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-current-time vjs-time-controls vjs-control'
});
this.content = vjs.createEl('div', {
className: 'vjs-current-time-display',
innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', // label the current time for screen reader users
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
});
el.appendChild(vjs.createEl('div').appendChild(this.content));
return el;
};
vjs.CurrentTimeDisplay.prototype.updateContent = function(){
// Allows for smooth scrubbing, when player can't keep up.
var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
this.content.innerHTML = '<span class="vjs-control-text">Current Time </span>' + vjs.formatTime(time, this.player_.duration());
};
/**
* Displays the duration
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.DurationDisplay = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
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.
}
});
vjs.DurationDisplay.prototype.createEl = function(){
var el = vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-duration vjs-time-controls vjs-control'
});
this.content = vjs.createEl('div', {
className: 'vjs-duration-display',
innerHTML: '<span class="vjs-control-text">Duration Time </span>' + '0:00', // label the duration time for screen reader users
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
});
el.appendChild(vjs.createEl('div').appendChild(this.content));
return el;
};
vjs.DurationDisplay.prototype.updateContent = function(){
if (this.player_.duration()) {
this.content.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(this.player_.duration()); // label the duration time for screen reader users
}
};
/**
* Time Separator (Not used in main skin, but still available, and could be used as a 'spare element')
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.TimeDivider = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.TimeDivider.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-time-divider',
innerHTML: '<div><span>/</span></div>'
});
};
/**
* Displays the time left in the video
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.RemainingTimeDisplay = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('timeupdate', vjs.bind(this, this.updateContent));
}
});
vjs.RemainingTimeDisplay.prototype.createEl = function(){
var el = vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-remaining-time vjs-time-controls vjs-control'
});
this.content = vjs.createEl('div', {
className: 'vjs-remaining-time-display',
innerHTML: '<span class="vjs-control-text">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
});
el.appendChild(vjs.createEl('div').appendChild(this.content));
return el;
};
vjs.RemainingTimeDisplay.prototype.updateContent = function(){
if (this.player_.duration()) {
if (this.player_.duration()) {
this.content.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());
}
}
// Allows for smooth scrubbing, when player can't keep up.
// var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
// this.content.innerHTML = vjs.formatTime(time, this.player_.duration());
};/**
* Toggle fullscreen video
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.FullscreenToggle = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
}
});
vjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';
vjs.FullscreenToggle.prototype.buildCSSClass = function(){
return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);
};
vjs.FullscreenToggle.prototype.onClick = function(){
if (!this.player_.isFullScreen) {
this.player_.requestFullScreen();
this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to "Non-Fullscreen"
} else {
this.player_.cancelFullScreen();
this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to "Fullscreen"
}
};/**
* Seek, Load Progress, and Play Progress
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.ProgressControl = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.ProgressControl.prototype.options_ = {
children: {
'seekBar': {}
}
};
vjs.ProgressControl.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-progress-control vjs-control'
});
};
/**
* Seek Bar and holder for the progress bars
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.SeekBar = vjs.Slider.extend({
/** @constructor */
init: function(player, options){
vjs.Slider.call(this, player, options);
player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));
player.ready(vjs.bind(this, this.updateARIAAttributes));
}
});
vjs.SeekBar.prototype.options_ = {
children: {
'loadProgressBar': {},
'playProgressBar': {},
'seekHandle': {}
},
'barName': 'playProgressBar',
'handleName': 'seekHandle'
};
vjs.SeekBar.prototype.playerEvent = 'timeupdate';
vjs.SeekBar.prototype.createEl = function(){
return vjs.Slider.prototype.createEl.call(this, 'div', {
className: 'vjs-progress-holder',
'aria-label': 'video progress bar'
});
};
vjs.SeekBar.prototype.updateARIAAttributes = function(){
// Allows for smooth scrubbing, when player can't keep up.
var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
this.el_.setAttribute('aria-valuenow',vjs.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)
this.el_.setAttribute('aria-valuetext',vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
};
vjs.SeekBar.prototype.getPercent = function(){
return this.player_.currentTime() / this.player_.duration();
};
vjs.SeekBar.prototype.onMouseDown = function(event){
vjs.Slider.prototype.onMouseDown.call(this, event);
this.player_.scrubbing = true;
this.videoWasPlaying = !this.player_.paused();
this.player_.pause();
};
vjs.SeekBar.prototype.onMouseMove = function(event){
var newTime = this.calculateDistance(event) * this.player_.duration();
// Don't let video end while scrubbing.
if (newTime == this.player_.duration()) { newTime = newTime - 0.1; }
// Set new time (tell player to seek to new time)
this.player_.currentTime(newTime);
};
vjs.SeekBar.prototype.onMouseUp = function(event){
vjs.Slider.prototype.onMouseUp.call(this, event);
this.player_.scrubbing = false;
if (this.videoWasPlaying) {
this.player_.play();
}
};
vjs.SeekBar.prototype.stepForward = function(){
this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
};
vjs.SeekBar.prototype.stepBack = function(){
this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
};
/**
* Shows load progres
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.LoadProgressBar = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('progress', vjs.bind(this, this.update));
}
});
vjs.LoadProgressBar.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-load-progress',
innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>'
});
};
vjs.LoadProgressBar.prototype.update = function(){
if (this.el_.style) { this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%'; }
};
/**
* Shows play progress
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.PlayProgressBar = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.PlayProgressBar.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-play-progress',
innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
});
};
/**
* SeekBar component includes play progress bar, and seek handle
* Needed so it can determine seek position based on handle position/size
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.SeekHandle = vjs.SliderHandle.extend();
/** @inheritDoc */
vjs.SeekHandle.prototype.defaultValue = '00:00';
/** @inheritDoc */
vjs.SeekHandle.prototype.createEl = function(){
return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
className: 'vjs-seek-handle'
});
};/**
* Control the volume
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.VolumeControl = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
// hide volume controls when they're not supported by the current tech
if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
this.addClass('vjs-hidden');
}
player.on('loadstart', vjs.bind(this, function(){
if (player.tech.features && player.tech.features.volumeContr