mirror of
https://github.com/videojs/video.js.git
synced 2025-07-15 01:34:23 +02:00
Broke up Lib and Util into smaller libraries of functions
Broke out bind, guid, and element data functions from Lib Separated out more dom functions in to dom.js Broke out URL functions into url.js Removed setLocalStorage since it wasn't being used Moved browser tests out of lib Moved log functions into their own file Removed trim() since it wasn't being used Moved formatTime into its own file Moved round into its own file and renamed roundFloat() Moved capitalize into its own file and renamed as toTitleCase() Moved createTimeRange into its own file Removed Lib.arr.forEach infavor of the native forEach Removed Lib.obj.create in favor of native Object.create (ES6-sham) Removed obj.each in favor of native Object.getOwnPropertyNames().forEach() Removed obj.merge and copy. Using lodash.assign instead. Replaced Lib.obj.isPlain with lodash.isPlainObject Removed Lib.obj.isArray in favor of the native Array.isArray Also removed the lib.js tests file as all tests have been moved or removed. Removed Lib.isEmpty in favor of !Object.getOwnPropertyNames().length Switched Util.mergeOptions and deepMerge to use new mergeOptions() Moved Lib.TEST_VID to Html5.TEST_VID Removed Lib references everywhere. Woo! Attempting to fix sourcemap test errors by setting grunt-browserify version Switched to object.assign from lodash.assign Removed unused 'inherits' dependency Reorganzied test files and added '.test' to file names Combined js/core.js and js/video.js Moved events.js into the utils directory
This commit is contained in:
@ -30,6 +30,7 @@ CHANGELOG
|
|||||||
* @eXon began Tech 2.0 work, improved how tech events are handled by the player ([view](https://github.com/videojs/video.js/pull/2057))
|
* @eXon began Tech 2.0 work, improved how tech events are handled by the player ([view](https://github.com/videojs/video.js/pull/2057))
|
||||||
* @gkatsev added get and set global options methods ([view](https://github.com/videojs/video.js/pull/2115))
|
* @gkatsev added get and set global options methods ([view](https://github.com/videojs/video.js/pull/2115))
|
||||||
* @heff added support for fluid widths, aspect ratios, and metadata defaults ([view](https://github.com/videojs/video.js/pull/1952))
|
* @heff added support for fluid widths, aspect ratios, and metadata defaults ([view](https://github.com/videojs/video.js/pull/1952))
|
||||||
|
* @heff reorganized all utility functions in the codebase ([view](https://github.com/videojs/video.js/pull/2139))
|
||||||
|
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -24,8 +24,11 @@
|
|||||||
"style": "./dist/video-js.css",
|
"style": "./dist/video-js.css",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"global": "^4.3.0",
|
"global": "^4.3.0",
|
||||||
|
"lodash.clonedeep": "^3.0.0",
|
||||||
|
"lodash.isplainobject": "^3.0.2",
|
||||||
|
"lodash.merge": "^3.2.1",
|
||||||
|
"object.assign": "^2.0.1",
|
||||||
"safe-json-parse": "^4.0.0",
|
"safe-json-parse": "^4.0.0",
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"videojs-swf": "4.5.4",
|
"videojs-swf": "4.5.4",
|
||||||
"vtt.js": "git+https://github.com/gkatsev/vtt.js.git#shim-build"
|
"vtt.js": "git+https://github.com/gkatsev/vtt.js.git#shim-build"
|
||||||
},
|
},
|
||||||
@ -41,7 +44,7 @@
|
|||||||
"grunt": "^0.4.4",
|
"grunt": "^0.4.4",
|
||||||
"grunt-aws-s3": "^0.12.1",
|
"grunt-aws-s3": "^0.12.1",
|
||||||
"grunt-banner": "^0.3.1",
|
"grunt-banner": "^0.3.1",
|
||||||
"grunt-browserify": "^3.5.0",
|
"grunt-browserify": "3.5.1",
|
||||||
"grunt-cli": "~0.1.0",
|
"grunt-cli": "~0.1.0",
|
||||||
"grunt-contrib-clean": "~0.4.0a",
|
"grunt-contrib-clean": "~0.4.0a",
|
||||||
"grunt-contrib-concat": "^0.5.1",
|
"grunt-contrib-concat": "^0.5.1",
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import Component from './component';
|
import Component from './component';
|
||||||
import * as Lib from './lib';
|
import * as Dom from './utils/dom.js';
|
||||||
import * as Events from './events';
|
import * as Events from './utils/events.js';
|
||||||
|
import * as Fn from './utils/fn.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
import assign from 'object.assign';
|
||||||
|
|
||||||
/* Button - Base class for all buttons
|
/* Button - Base class for all buttons
|
||||||
================================================================================ */
|
================================================================================ */
|
||||||
@ -27,7 +29,7 @@ class Button extends Component {
|
|||||||
|
|
||||||
createEl(type, props) {
|
createEl(type, props) {
|
||||||
// Add standard Aria and Tabindex info
|
// Add standard Aria and Tabindex info
|
||||||
props = Lib.obj.merge({
|
props = assign({
|
||||||
className: this.buildCSSClass(),
|
className: this.buildCSSClass(),
|
||||||
'role': 'button',
|
'role': 'button',
|
||||||
'aria-live': 'polite', // let the screen reader user know that the text of the button may change
|
'aria-live': 'polite', // let the screen reader user know that the text of the button may change
|
||||||
@ -38,11 +40,11 @@ class Button extends Component {
|
|||||||
|
|
||||||
// if innerHTML hasn't been overridden (bigPlayButton), add content elements
|
// if innerHTML hasn't been overridden (bigPlayButton), add content elements
|
||||||
if (!props.innerHTML) {
|
if (!props.innerHTML) {
|
||||||
this.contentEl_ = Lib.createEl('div', {
|
this.contentEl_ = Dom.createEl('div', {
|
||||||
className: 'vjs-control-content'
|
className: 'vjs-control-content'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.controlText_ = Lib.createEl('span', {
|
this.controlText_ = Dom.createEl('span', {
|
||||||
className: 'vjs-control-text',
|
className: 'vjs-control-text',
|
||||||
innerHTML: this.localize(this.buttonText) || 'Need Text'
|
innerHTML: this.localize(this.buttonText) || 'Need Text'
|
||||||
});
|
});
|
||||||
@ -63,7 +65,7 @@ class Button extends Component {
|
|||||||
|
|
||||||
// Focus - Add keyboard functionality to element
|
// Focus - Add keyboard functionality to element
|
||||||
handleFocus() {
|
handleFocus() {
|
||||||
Events.on(document, 'keydown', Lib.bind(this, this.handleKeyPress));
|
Events.on(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyPress (document level) - Trigger click when keys are pressed
|
// KeyPress (document level) - Trigger click when keys are pressed
|
||||||
@ -77,7 +79,7 @@ class Button extends Component {
|
|||||||
|
|
||||||
// Blur - Remove keyboard triggers
|
// Blur - Remove keyboard triggers
|
||||||
handleBlur() {
|
handleBlur() {
|
||||||
Events.off(document, 'keydown', Lib.bind(this, this.handleKeyPress));
|
Events.off(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,15 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Lib from './lib.js';
|
|
||||||
import * as VjsUtil from './util.js';
|
|
||||||
import * as Events from './events.js';
|
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
import * as Dom from './utils/dom.js';
|
||||||
|
import * as Fn from './utils/fn.js';
|
||||||
|
import * as Guid from './utils/guid.js';
|
||||||
|
import * as Events from './utils/events.js';
|
||||||
|
import log from './utils/log.js';
|
||||||
|
import toTitleCase from './utils/to-title-case.js';
|
||||||
|
import assign from 'object.assign';
|
||||||
|
import mergeOptions from './utils/merge-options.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base UI Component class
|
* Base UI Component class
|
||||||
@ -48,7 +53,7 @@ class Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make a copy of prototype.options_ to protect against overriding global defaults
|
// Make a copy of prototype.options_ to protect against overriding global defaults
|
||||||
this.options_ = Lib.obj.copy(this.options_);
|
this.options_ = assign({}, this.options_);
|
||||||
|
|
||||||
// Updated options with supplied options
|
// Updated options with supplied options
|
||||||
options = this.options(options);
|
options = this.options(options);
|
||||||
@ -60,7 +65,8 @@ class Component {
|
|||||||
if (!this.id_) {
|
if (!this.id_) {
|
||||||
// Don't require the player ID function in the case of mock players
|
// Don't require the player ID function in the case of mock players
|
||||||
let id = player && player.id && player.id() || 'no_player';
|
let id = player && player.id && player.id() || 'no_player';
|
||||||
this.id_ = `${id}_component_${Lib.guid++}`;
|
|
||||||
|
this.id_ = `${id}_component_${Guid.newGUID()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.name_ = options.name || null;
|
this.name_ = options.name || null;
|
||||||
@ -124,7 +130,7 @@ class Component {
|
|||||||
this.el_.parentNode.removeChild(this.el_);
|
this.el_.parentNode.removeChild(this.el_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lib.removeData(this.el_);
|
Dom.removeData(this.el_);
|
||||||
this.el_ = null;
|
this.el_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +147,7 @@ class Component {
|
|||||||
* Deep merge of options objects
|
* Deep merge of options objects
|
||||||
*
|
*
|
||||||
* Whenever a property is an object on both options objects
|
* Whenever a property is an object on both options objects
|
||||||
* the two properties will be merged using Lib.obj.deepMerge.
|
* the two properties will be merged using mergeOptions.
|
||||||
*
|
*
|
||||||
* This is used for merging options for child components. We
|
* This is used for merging options for child components. We
|
||||||
* want it to be easy to override individual options on a child
|
* want it to be easy to override individual options on a child
|
||||||
@ -183,7 +189,7 @@ class Component {
|
|||||||
return this.options_;
|
return this.options_;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options_ = VjsUtil.mergeOptions(this.options_, obj);
|
this.options_ = mergeOptions(this.options_, obj);
|
||||||
return this.options_;
|
return this.options_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +212,7 @@ class Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
createEl(tagName, attributes) {
|
createEl(tagName, attributes) {
|
||||||
return Lib.createEl(tagName, attributes);
|
return Dom.createEl(tagName, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
localize(string) {
|
localize(string) {
|
||||||
@ -324,13 +330,13 @@ class Component {
|
|||||||
|
|
||||||
// Same as above, but true is deprecated so show a warning.
|
// Same as above, but true is deprecated so show a warning.
|
||||||
if (options === true) {
|
if (options === true) {
|
||||||
Lib.log.warn('Initializing a child component with `true` is deprecated. Children should be defined in an array when possible, but if necessary use an object instead of `true`.');
|
log.warn('Initializing a child component with `true` is deprecated. Children should be defined in an array when possible, but if necessary use an object instead of `true`.');
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no componentClass in options, assume componentClass is the name lowercased
|
// If no componentClass in options, assume componentClass is the name lowercased
|
||||||
// (e.g. playButton)
|
// (e.g. playButton)
|
||||||
let componentClassName = options.componentClass || Lib.capitalize(componentName);
|
let componentClassName = options.componentClass || toTitleCase(componentName);
|
||||||
|
|
||||||
// Set name through options
|
// Set name through options
|
||||||
options.name = componentName;
|
options.name = componentName;
|
||||||
@ -471,7 +477,7 @@ class Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Allow for an array of children details to passed in the options
|
// Allow for an array of children details to passed in the options
|
||||||
if (Lib.obj.isArray(children)) {
|
if (Array.isArray(children)) {
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
let child = children[i];
|
let child = children[i];
|
||||||
let name;
|
let name;
|
||||||
@ -490,7 +496,9 @@ class Component {
|
|||||||
handleAdd(name, opts);
|
handleAdd(name, opts);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Lib.obj.each(children, handleAdd);
|
Object.getOwnPropertyNames(children).forEach(function(name){
|
||||||
|
handleAdd(name, children[name]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -539,14 +547,14 @@ class Component {
|
|||||||
* @return {Component} self
|
* @return {Component} self
|
||||||
*/
|
*/
|
||||||
on(first, second, third) {
|
on(first, second, third) {
|
||||||
if (typeof first === 'string' || Lib.obj.isArray(first)) {
|
if (typeof first === 'string' || Array.isArray(first)) {
|
||||||
Events.on(this.el_, first, Lib.bind(this, second));
|
Events.on(this.el_, first, Fn.bind(this, second));
|
||||||
|
|
||||||
// Targeting another component or element
|
// Targeting another component or element
|
||||||
} else {
|
} else {
|
||||||
const target = first;
|
const target = first;
|
||||||
const type = second;
|
const type = second;
|
||||||
const fn = Lib.bind(this, third);
|
const fn = Fn.bind(this, third);
|
||||||
|
|
||||||
// When this component is disposed, remove the listener from the other component
|
// When this component is disposed, remove the listener from the other component
|
||||||
const removeOnDispose = () => this.off(target, type, fn);
|
const removeOnDispose = () => this.off(target, type, fn);
|
||||||
@ -603,13 +611,13 @@ class Component {
|
|||||||
* @return {Component}
|
* @return {Component}
|
||||||
*/
|
*/
|
||||||
off(first, second, third) {
|
off(first, second, third) {
|
||||||
if (!first || typeof first === 'string' || Lib.obj.isArray(first)) {
|
if (!first || typeof first === 'string' || Array.isArray(first)) {
|
||||||
Events.off(this.el_, first, second);
|
Events.off(this.el_, first, second);
|
||||||
} else {
|
} else {
|
||||||
const target = first;
|
const target = first;
|
||||||
const type = second;
|
const type = second;
|
||||||
// Ensure there's at least a guid, even if the function hasn't been used
|
// Ensure there's at least a guid, even if the function hasn't been used
|
||||||
const fn = Lib.bind(this, third);
|
const fn = Fn.bind(this, third);
|
||||||
|
|
||||||
// Remove the dispose listener on this component,
|
// Remove the dispose listener on this component,
|
||||||
// which was given the same guid as the event listener
|
// which was given the same guid as the event listener
|
||||||
@ -646,12 +654,12 @@ class Component {
|
|||||||
* @return {Component}
|
* @return {Component}
|
||||||
*/
|
*/
|
||||||
one(first, second, third) {
|
one(first, second, third) {
|
||||||
if (typeof first === 'string' || Lib.obj.isArray(first)) {
|
if (typeof first === 'string' || Array.isArray(first)) {
|
||||||
Events.one(this.el_, first, Lib.bind(this, second));
|
Events.one(this.el_, first, Fn.bind(this, second));
|
||||||
} else {
|
} else {
|
||||||
const target = first;
|
const target = first;
|
||||||
const type = second;
|
const type = second;
|
||||||
const fn = Lib.bind(this, third);
|
const fn = Fn.bind(this, third);
|
||||||
|
|
||||||
const newFunc = () => {
|
const newFunc = () => {
|
||||||
this.off(target, type, newFunc);
|
this.off(target, type, newFunc);
|
||||||
@ -733,7 +741,7 @@ class Component {
|
|||||||
* @return {Component}
|
* @return {Component}
|
||||||
*/
|
*/
|
||||||
hasClass(classToCheck) {
|
hasClass(classToCheck) {
|
||||||
return Lib.hasClass(this.el_, classToCheck);
|
return Dom.hasClass(this.el_, classToCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -743,7 +751,7 @@ class Component {
|
|||||||
* @return {Component}
|
* @return {Component}
|
||||||
*/
|
*/
|
||||||
addClass(classToAdd) {
|
addClass(classToAdd) {
|
||||||
Lib.addClass(this.el_, classToAdd);
|
Dom.addClass(this.el_, classToAdd);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -754,7 +762,7 @@ class Component {
|
|||||||
* @return {Component}
|
* @return {Component}
|
||||||
*/
|
*/
|
||||||
removeClass(classToRemove) {
|
removeClass(classToRemove) {
|
||||||
Lib.removeClass(this.el_, classToRemove);
|
Dom.removeClass(this.el_, classToRemove);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -909,13 +917,13 @@ class Component {
|
|||||||
// No px so using % or no style was set, so falling back to offsetWidth/height
|
// No px so using % or no style was set, so falling back to offsetWidth/height
|
||||||
// If component has display:none, offset will return 0
|
// If component has display:none, offset will return 0
|
||||||
// TODO: handle display:none and no dimension style using px
|
// TODO: handle display:none and no dimension style using px
|
||||||
return parseInt(this.el_['offset' + Lib.capitalize(widthOrHeight)], 10);
|
return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
|
||||||
|
|
||||||
// ComputedStyle version.
|
// ComputedStyle version.
|
||||||
// Only difference is if the element is hidden it will return
|
// Only difference is if the element is hidden it will return
|
||||||
// the percent value (e.g. '100%'')
|
// the percent value (e.g. '100%'')
|
||||||
// instead of zero like offsetWidth returns.
|
// instead of zero like offsetWidth returns.
|
||||||
// var val = Lib.getComputedStyleValue(this.el_, widthOrHeight);
|
// var val = Dom.getComputedStyleValue(this.el_, widthOrHeight);
|
||||||
// var pxIndex = val.indexOf('px');
|
// var pxIndex = val.indexOf('px');
|
||||||
|
|
||||||
// if (pxIndex !== -1) {
|
// if (pxIndex !== -1) {
|
||||||
@ -952,7 +960,8 @@ class Component {
|
|||||||
this.on('touchstart', function(event) {
|
this.on('touchstart', function(event) {
|
||||||
// If more than one finger, don't consider treating this as a click
|
// If more than one finger, don't consider treating this as a click
|
||||||
if (event.touches.length === 1) {
|
if (event.touches.length === 1) {
|
||||||
firstTouch = Lib.obj.copy(event.touches[0]);
|
// Copy the touches object to prevent modifying the original
|
||||||
|
firstTouch = assign({}, event.touches[0]);
|
||||||
// Record start time so we can detect a tap vs. "touch and hold"
|
// Record start time so we can detect a tap vs. "touch and hold"
|
||||||
touchStart = new Date().getTime();
|
touchStart = new Date().getTime();
|
||||||
// Reset couldBeTap tracking
|
// Reset couldBeTap tracking
|
||||||
@ -1001,7 +1010,7 @@ class Component {
|
|||||||
this.trigger('tap');
|
this.trigger('tap');
|
||||||
// It may be good to copy the touchend event object and change the
|
// It may be good to copy the touchend event object and change the
|
||||||
// type to tap, if the other event properties aren't exact after
|
// type to tap, if the other event properties aren't exact after
|
||||||
// Lib.fixEvent runs (e.g. event.target)
|
// Events.fixEvent runs (e.g. event.target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1037,7 +1046,7 @@ class Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// listener for reporting that the user is active
|
// listener for reporting that the user is active
|
||||||
const report = Lib.bind(this.player(), this.player().reportUserActivity);
|
const report = Fn.bind(this.player(), this.player().reportUserActivity);
|
||||||
|
|
||||||
let touchHolding;
|
let touchHolding;
|
||||||
|
|
||||||
@ -1069,7 +1078,7 @@ class Component {
|
|||||||
* @return {Number} Returns the timeout ID
|
* @return {Number} Returns the timeout ID
|
||||||
*/
|
*/
|
||||||
setTimeout(fn, timeout) {
|
setTimeout(fn, timeout) {
|
||||||
fn = Lib.bind(this, fn);
|
fn = Fn.bind(this, fn);
|
||||||
|
|
||||||
// window.setTimeout would be preferable here, but due to some bizarre issue with Sinon and/or Phantomjs, we can't.
|
// window.setTimeout would be preferable here, but due to some bizarre issue with Sinon and/or Phantomjs, we can't.
|
||||||
let timeoutId = window.setTimeout(fn, timeout);
|
let timeoutId = window.setTimeout(fn, timeout);
|
||||||
@ -1109,7 +1118,7 @@ class Component {
|
|||||||
* @return {Number} Returns the interval ID
|
* @return {Number} Returns the interval ID
|
||||||
*/
|
*/
|
||||||
setInterval(fn, interval) {
|
setInterval(fn, interval) {
|
||||||
fn = Lib.bind(this, fn);
|
fn = Fn.bind(this, fn);
|
||||||
|
|
||||||
let intervalId = window.setInterval(fn, interval);
|
let intervalId = window.setInterval(fn, interval);
|
||||||
|
|
||||||
@ -1156,7 +1165,7 @@ class Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (window && window.videojs && window.videojs[name]) {
|
if (window && window.videojs && window.videojs[name]) {
|
||||||
Lib.log.warn(`The ${name} component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)`);
|
log.warn(`The ${name} component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)`);
|
||||||
return window.videojs[name];
|
return window.videojs[name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1181,7 +1190,7 @@ class Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Inherit from this object's prototype
|
// Inherit from this object's prototype
|
||||||
subObj.prototype = Lib.obj.create(this.prototype);
|
subObj.prototype = Object.create(this.prototype);
|
||||||
// Reset the constructor property for subObj otherwise
|
// Reset the constructor property for subObj otherwise
|
||||||
// instances of subObj would have the constructor of the parent Object
|
// instances of subObj would have the constructor of the parent Object
|
||||||
subObj.prototype.constructor = subObj;
|
subObj.prototype.constructor = subObj;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import Component from '../component.js';
|
import Component from '../component.js';
|
||||||
import * as Lib from '../lib.js';
|
|
||||||
|
|
||||||
// Required children
|
// Required children
|
||||||
import PlayToggle from './play-toggle.js';
|
import PlayToggle from './play-toggle.js';
|
||||||
@ -29,7 +28,7 @@ import CustomControlSpacer from './spacer-controls/custom-control-spacer.js';
|
|||||||
*/
|
*/
|
||||||
class ControlBar extends Component {
|
class ControlBar extends Component {
|
||||||
createEl() {
|
createEl() {
|
||||||
return Lib.createEl('div', {
|
return super.createEl('div', {
|
||||||
className: 'vjs-control-bar'
|
className: 'vjs-control-bar'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Component from '../component';
|
import Component from '../component';
|
||||||
import * as Lib from '../lib';
|
import * as Dom from '../utils/dom.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the live indicator
|
* Displays the live indicator
|
||||||
@ -15,7 +15,7 @@ class LiveDisplay extends Component {
|
|||||||
className: 'vjs-live-control vjs-control'
|
className: 'vjs-live-control vjs-control'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contentEl_ = Lib.createEl('div', {
|
this.contentEl_ = Dom.createEl('div', {
|
||||||
className: 'vjs-live-display',
|
className: 'vjs-live-display',
|
||||||
innerHTML: `<span class="vjs-control-text">${this.localize('Stream Type')}</span>${this.localize('LIVE')}`,
|
innerHTML: `<span class="vjs-control-text">${this.localize('Stream Type')}</span>${this.localize('LIVE')}`,
|
||||||
'aria-live': 'off'
|
'aria-live': 'off'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Button from '../button';
|
import Button from '../button';
|
||||||
import Component from '../component';
|
import Component from '../component';
|
||||||
import * as Lib from '../lib';
|
import * as Dom from '../utils/dom.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A button component for muting the audio
|
* A button component for muting the audio
|
||||||
@ -68,9 +68,9 @@ class MuteToggle extends Button {
|
|||||||
|
|
||||||
/* TODO improve muted icon classes */
|
/* TODO improve muted icon classes */
|
||||||
for (var i = 0; i < 4; i++) {
|
for (var i = 0; i < 4; i++) {
|
||||||
Lib.removeClass(this.el_, `vjs-vol-${i}`);
|
Dom.removeClass(this.el_, `vjs-vol-${i}`);
|
||||||
}
|
}
|
||||||
Lib.addClass(this.el_, `vjs-vol-${level}`);
|
Dom.addClass(this.el_, `vjs-vol-${level}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import Button from '../button';
|
import Button from '../button';
|
||||||
import * as Lib from '../lib';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Button to toggle between play and pause
|
* Button to toggle between play and pause
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import MenuButton from '../../menu/menu-button.js';
|
import MenuButton from '../../menu/menu-button.js';
|
||||||
import Menu from '../../menu/menu.js';
|
import Menu from '../../menu/menu.js';
|
||||||
import PlaybackRateMenuItem from './playback-rate-menu-item.js';
|
import PlaybackRateMenuItem from './playback-rate-menu-item.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Dom from '../../utils/dom.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The component for controlling the playback rate
|
* The component for controlling the playback rate
|
||||||
@ -25,7 +25,7 @@ class PlaybackRateMenuButton extends MenuButton {
|
|||||||
createEl() {
|
createEl() {
|
||||||
let el = super.createEl();
|
let el = super.createEl();
|
||||||
|
|
||||||
this.labelEl_ = Lib.createEl('div', {
|
this.labelEl_ = Dom.createEl('div', {
|
||||||
className: 'vjs-playback-rate-value',
|
className: 'vjs-playback-rate-value',
|
||||||
innerHTML: 1.0
|
innerHTML: 1.0
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Component from '../../component.js';
|
import Component from '../../component.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Dom from '../../utils/dom.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows load progress
|
* Shows load progress
|
||||||
@ -44,7 +44,7 @@ class LoadProgressBar extends Component {
|
|||||||
let part = children[i];
|
let part = children[i];
|
||||||
|
|
||||||
if (!part) {
|
if (!part) {
|
||||||
part = this.el_.appendChild(Lib.createEl());
|
part = this.el_.appendChild(Dom.createEl());
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the percent based on the width of the progress bar (bufferedEnd)
|
// set the percent based on the width of the progress bar (bufferedEnd)
|
||||||
|
@ -2,7 +2,9 @@ import Slider from '../../slider/slider.js';
|
|||||||
import LoadProgressBar from './load-progress-bar.js';
|
import LoadProgressBar from './load-progress-bar.js';
|
||||||
import PlayProgressBar from './play-progress-bar.js';
|
import PlayProgressBar from './play-progress-bar.js';
|
||||||
import SeekHandle from './seek-handle.js';
|
import SeekHandle from './seek-handle.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Fn from '../../utils/fn.js';
|
||||||
|
import formatTime from '../../utils/format-time.js';
|
||||||
|
import roundFloat from '../../utils/round-float.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seek Bar and holder for the progress bars
|
* Seek Bar and holder for the progress bars
|
||||||
@ -16,7 +18,7 @@ class SeekBar extends Slider {
|
|||||||
constructor(player, options){
|
constructor(player, options){
|
||||||
super(player, options);
|
super(player, options);
|
||||||
this.on(player, 'timeupdate', this.updateARIAAttributes);
|
this.on(player, 'timeupdate', this.updateARIAAttributes);
|
||||||
player.ready(Lib.bind(this, this.updateARIAAttributes));
|
player.ready(Fn.bind(this, this.updateARIAAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
createEl() {
|
createEl() {
|
||||||
@ -29,8 +31,8 @@ class SeekBar extends Slider {
|
|||||||
updateARIAAttributes() {
|
updateARIAAttributes() {
|
||||||
// Allows for smooth scrubbing, when player can't keep up.
|
// Allows for smooth scrubbing, when player can't keep up.
|
||||||
let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||||
this.el_.setAttribute('aria-valuenow', Lib.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)
|
this.el_.setAttribute('aria-valuenow', roundFloat(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)
|
||||||
this.el_.setAttribute('aria-valuetext', Lib.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
|
this.el_.setAttribute('aria-valuetext', formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
|
||||||
}
|
}
|
||||||
|
|
||||||
getPercent() {
|
getPercent() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import SliderHandle from '../../slider/slider-handle.js';
|
import SliderHandle from '../../slider/slider-handle.js';
|
||||||
import * as Lib from '../../lib.js';
|
import formatTime from '../../utils/format-time.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Seek Handle shows the current position of the playhead during playback,
|
* The Seek Handle shows the current position of the playhead during playback,
|
||||||
@ -26,7 +26,7 @@ class SeekHandle extends SliderHandle {
|
|||||||
|
|
||||||
updateContent() {
|
updateContent() {
|
||||||
let time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
let time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||||
this.el_.innerHTML = `<span class="vjs-control-text">${Lib.formatTime(time, this.player_.duration())}</span>`;
|
this.el_.innerHTML = `<span class="vjs-control-text">${formatTime(time, this.player_.duration())}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@ import TextTrackButton from './text-track-button.js';
|
|||||||
import TextTrackMenuItem from './text-track-menu-item.js';
|
import TextTrackMenuItem from './text-track-menu-item.js';
|
||||||
import ChaptersTrackMenuItem from './chapters-track-menu-item.js';
|
import ChaptersTrackMenuItem from './chapters-track-menu-item.js';
|
||||||
import Menu from '../../menu/menu.js';
|
import Menu from '../../menu/menu.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Dom from '../../utils/dom.js';
|
||||||
|
import * as Fn from '../../utils/fn.js';
|
||||||
|
import toTitleCase from '../../utils/to-title-case.js';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
|
||||||
// Chapters act much differently than other text tracks
|
// Chapters act much differently than other text tracks
|
||||||
@ -53,7 +55,7 @@ class ChaptersButton extends TextTrackButton {
|
|||||||
track['mode'] = 'hidden';
|
track['mode'] = 'hidden';
|
||||||
/* jshint loopfunc:true */
|
/* jshint loopfunc:true */
|
||||||
// TODO see if we can figure out a better way of doing this https://github.com/videojs/video.js/issues/1864
|
// TODO see if we can figure out a better way of doing this https://github.com/videojs/video.js/issues/1864
|
||||||
window.setTimeout(Lib.bind(this, function() {
|
window.setTimeout(Fn.bind(this, function() {
|
||||||
this.createMenu();
|
this.createMenu();
|
||||||
}), 100);
|
}), 100);
|
||||||
/* jshint loopfunc:false */
|
/* jshint loopfunc:false */
|
||||||
@ -67,9 +69,9 @@ class ChaptersButton extends TextTrackButton {
|
|||||||
let menu = this.menu;
|
let menu = this.menu;
|
||||||
if (menu === undefined) {
|
if (menu === undefined) {
|
||||||
menu = new Menu(this.player_);
|
menu = new Menu(this.player_);
|
||||||
menu.contentEl().appendChild(Lib.createEl('li', {
|
menu.contentEl().appendChild(Dom.createEl('li', {
|
||||||
className: 'vjs-menu-title',
|
className: 'vjs-menu-title',
|
||||||
innerHTML: Lib.capitalize(this.kind_),
|
innerHTML: toTitleCase(this.kind_),
|
||||||
tabindex: -1
|
tabindex: -1
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import MenuItem from '../../menu/menu-item.js';
|
import MenuItem from '../../menu/menu-item.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Fn from '../../utils/fn.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
@ -18,7 +18,7 @@ class ChaptersTrackMenuItem extends MenuItem {
|
|||||||
|
|
||||||
this.track = track;
|
this.track = track;
|
||||||
this.cue = cue;
|
this.cue = cue;
|
||||||
track.addEventListener('cuechange', Lib.bind(this, this.update));
|
track.addEventListener('cuechange', Fn.bind(this, this.update));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick() {
|
handleClick() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import MenuButton from '../../menu/menu-button.js';
|
import MenuButton from '../../menu/menu-button.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Fn from '../../utils/fn.js';
|
||||||
|
|
||||||
import TextTrackMenuItem from './text-track-menu-item.js';
|
import TextTrackMenuItem from './text-track-menu-item.js';
|
||||||
import OffTextTrackMenuItem from './off-text-track-menu-item.js';
|
import OffTextTrackMenuItem from './off-text-track-menu-item.js';
|
||||||
|
|
||||||
@ -24,7 +23,7 @@ class TextTrackButton extends MenuButton {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let updateHandler = Lib.bind(this, this.update);
|
let updateHandler = Fn.bind(this, this.update);
|
||||||
tracks.addEventListener('removetrack', updateHandler);
|
tracks.addEventListener('removetrack', updateHandler);
|
||||||
tracks.addEventListener('addtrack', updateHandler);
|
tracks.addEventListener('addtrack', updateHandler);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import MenuItem from '../../menu/menu-item.js';
|
import MenuItem from '../../menu/menu-item.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Fn from '../../utils/fn.js';
|
||||||
|
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
@ -23,7 +22,7 @@ class TextTrackMenuItem extends MenuItem {
|
|||||||
this.track = track;
|
this.track = track;
|
||||||
|
|
||||||
if (tracks) {
|
if (tracks) {
|
||||||
let changeHandler = Lib.bind(this, this.handleTracksChange);
|
let changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||||
|
|
||||||
tracks.addEventListener('change', changeHandler);
|
tracks.addEventListener('change', changeHandler);
|
||||||
this.on('dispose', function() {
|
this.on('dispose', function() {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Component from '../../component.js';
|
import Component from '../../component.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Dom from '../../utils/dom.js';
|
||||||
|
import formatTime from '../../utils/format-time.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the current time
|
* Displays the current time
|
||||||
@ -20,7 +21,7 @@ class CurrentTimeDisplay extends Component {
|
|||||||
className: 'vjs-current-time vjs-time-control vjs-control'
|
className: 'vjs-current-time vjs-time-control vjs-control'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contentEl_ = Lib.createEl('div', {
|
this.contentEl_ = Dom.createEl('div', {
|
||||||
className: 'vjs-current-time-display',
|
className: 'vjs-current-time-display',
|
||||||
innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', // label the current time for screen reader users
|
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
|
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
|
||||||
@ -34,7 +35,7 @@ class CurrentTimeDisplay extends Component {
|
|||||||
// Allows for smooth scrubbing, when player can't keep up.
|
// Allows for smooth scrubbing, when player can't keep up.
|
||||||
let time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
let time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||||
let localizedText = this.localize('Current Time');
|
let localizedText = this.localize('Current Time');
|
||||||
let formattedTime = Lib.formatTime(time, this.player_.duration());
|
let formattedTime = formatTime(time, this.player_.duration());
|
||||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`;
|
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Component from '../../component.js';
|
import Component from '../../component.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Dom from '../../utils/dom.js';
|
||||||
|
import formatTime from '../../utils/format-time.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the duration
|
* Displays the duration
|
||||||
@ -25,7 +26,7 @@ class DurationDisplay extends Component {
|
|||||||
className: 'vjs-duration vjs-time-control vjs-control'
|
className: 'vjs-duration vjs-time-control vjs-control'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contentEl_ = Lib.createEl('div', {
|
this.contentEl_ = Dom.createEl('div', {
|
||||||
className: 'vjs-duration-display',
|
className: 'vjs-duration-display',
|
||||||
innerHTML: `<span class="vjs-control-text">${this.localize('Duration Time')}</span> 0:00`, // label the duration time for screen reader users
|
innerHTML: `<span class="vjs-control-text">${this.localize('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
|
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
|
||||||
@ -39,7 +40,7 @@ class DurationDisplay extends Component {
|
|||||||
let duration = this.player_.duration();
|
let duration = this.player_.duration();
|
||||||
if (duration) {
|
if (duration) {
|
||||||
let localizedText = this.localize('Duration Time');
|
let localizedText = this.localize('Duration Time');
|
||||||
let formattedTime = Lib.formatTime(duration);
|
let formattedTime = formatTime(duration);
|
||||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`; // label the duration time for screen reader users
|
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`; // label the duration time for screen reader users
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Component from '../../component.js';
|
import Component from '../../component.js';
|
||||||
import * as Lib from '../../lib';
|
import * as Dom from '../../utils/dom.js';
|
||||||
|
import formatTime from '../../utils/format-time.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the time left in the video
|
* Displays the time left in the video
|
||||||
@ -20,7 +21,7 @@ class RemainingTimeDisplay extends Component {
|
|||||||
className: 'vjs-remaining-time vjs-time-control vjs-control'
|
className: 'vjs-remaining-time vjs-time-control vjs-control'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contentEl_ = Lib.createEl('div', {
|
this.contentEl_ = Dom.createEl('div', {
|
||||||
className: 'vjs-remaining-time-display',
|
className: 'vjs-remaining-time-display',
|
||||||
innerHTML: `<span class="vjs-control-text">${this.localize('Remaining Time')}</span> -0:00`, // label the remaining time for screen reader users
|
innerHTML: `<span class="vjs-control-text">${this.localize('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
|
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
|
||||||
@ -33,7 +34,7 @@ class RemainingTimeDisplay extends Component {
|
|||||||
updateContent() {
|
updateContent() {
|
||||||
if (this.player_.duration()) {
|
if (this.player_.duration()) {
|
||||||
const localizedText = this.localize('Remaining Time');
|
const localizedText = this.localize('Remaining Time');
|
||||||
const formattedTime = Lib.formatTime(this.player_.remainingTime());
|
const formattedTime = formatTime(this.player_.remainingTime());
|
||||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> -${formattedTime}`;
|
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> -${formattedTime}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Slider from '../../slider/slider.js';
|
import Slider from '../../slider/slider.js';
|
||||||
import * as Lib from '../../lib.js';
|
import * as Fn from '../../utils/fn.js';
|
||||||
|
import roundFloat from '../../utils/round-float.js';
|
||||||
|
|
||||||
// Required children
|
// Required children
|
||||||
import VolumeHandle from './volume-handle.js';
|
import VolumeHandle from './volume-handle.js';
|
||||||
@ -17,7 +18,7 @@ class VolumeBar extends Slider {
|
|||||||
constructor(player, options){
|
constructor(player, options){
|
||||||
super(player, options);
|
super(player, options);
|
||||||
this.on(player, 'volumechange', this.updateARIAAttributes);
|
this.on(player, 'volumechange', this.updateARIAAttributes);
|
||||||
player.ready(Lib.bind(this, this.updateARIAAttributes));
|
player.ready(Fn.bind(this, this.updateARIAAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
createEl() {
|
createEl() {
|
||||||
@ -53,8 +54,8 @@ class VolumeBar extends Slider {
|
|||||||
|
|
||||||
updateARIAAttributes() {
|
updateARIAAttributes() {
|
||||||
// Current value of volume bar as a percentage
|
// Current value of volume bar as a percentage
|
||||||
this.el_.setAttribute('aria-valuenow', Lib.round(this.player_.volume()*100, 2));
|
this.el_.setAttribute('aria-valuenow', roundFloat(this.player_.volume()*100, 2));
|
||||||
this.el_.setAttribute('aria-valuetext', Lib.round(this.player_.volume()*100, 2)+'%');
|
this.el_.setAttribute('aria-valuetext', roundFloat(this.player_.volume()*100, 2)+'%');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import Component from '../../component.js';
|
import Component from '../../component.js';
|
||||||
import * as Lib from '../../lib.js';
|
|
||||||
|
|
||||||
// Required children
|
// Required children
|
||||||
import VolumeBar from './volume-bar.js';
|
import VolumeBar from './volume-bar.js';
|
||||||
|
@ -2,7 +2,6 @@ import Button from '../button.js';
|
|||||||
import Menu from '../menu/menu.js';
|
import Menu from '../menu/menu.js';
|
||||||
import MenuButton from '../menu/menu-button.js';
|
import MenuButton from '../menu/menu-button.js';
|
||||||
import MuteToggle from './mute-toggle.js';
|
import MuteToggle from './mute-toggle.js';
|
||||||
import * as Lib from '../lib.js';
|
|
||||||
import VolumeBar from './volume-control/volume-bar.js';
|
import VolumeBar from './volume-control/volume-bar.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,130 +0,0 @@
|
|||||||
import * as Lib from './lib';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Core Object/Class for objects that use inheritance + constructors
|
|
||||||
*
|
|
||||||
* To create a class that can be subclassed itself, extend the CoreObject class.
|
|
||||||
*
|
|
||||||
* var Animal = CoreObject.extend();
|
|
||||||
* var Horse = Animal.extend();
|
|
||||||
*
|
|
||||||
* The constructor can be defined through the init property of an object argument.
|
|
||||||
*
|
|
||||||
* var Animal = CoreObject.extend({
|
|
||||||
* init: function(name, sound){
|
|
||||||
* this.name = name;
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* Other methods and properties can be added the same way, or directly to the
|
|
||||||
* prototype.
|
|
||||||
*
|
|
||||||
* var Animal = CoreObject.extend({
|
|
||||||
* init: function(name){
|
|
||||||
* this.name = name;
|
|
||||||
* },
|
|
||||||
* getName: function(){
|
|
||||||
* return this.name;
|
|
||||||
* },
|
|
||||||
* sound: '...'
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* Animal.prototype.makeSound = function(){
|
|
||||||
* alert(this.sound);
|
|
||||||
* };
|
|
||||||
*
|
|
||||||
* To create an instance of a class, use the create method.
|
|
||||||
*
|
|
||||||
* var fluffy = Animal.create('Fluffy');
|
|
||||||
* fluffy.getName(); // -> Fluffy
|
|
||||||
*
|
|
||||||
* Methods and properties can be overridden in subclasses.
|
|
||||||
*
|
|
||||||
* var Horse = Animal.extend({
|
|
||||||
* sound: 'Neighhhhh!'
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* var horsey = Horse.create('Horsey');
|
|
||||||
* horsey.getName(); // -> Horsey
|
|
||||||
* horsey.makeSound(); // -> Alert: Neighhhhh!
|
|
||||||
*
|
|
||||||
* @class
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
var 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 flattened to something like
|
|
||||||
// `a = ...` and `this.prototype` would refer to the global object instead of
|
|
||||||
// CoreObject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new object that inherits from this Object
|
|
||||||
*
|
|
||||||
* var Animal = CoreObject.extend();
|
|
||||||
* var Horse = Animal.extend();
|
|
||||||
*
|
|
||||||
* @param {Object} props Functions and properties to be applied to the
|
|
||||||
* new object's prototype
|
|
||||||
* @return {CoreObject} An object that inherits from CoreObject
|
|
||||||
* @this {*}
|
|
||||||
*/
|
|
||||||
CoreObject.extend = function(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
|
|
||||||
let 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 constructor because the `this` in `this.init`
|
|
||||||
// would still refer to the Child and cause an infinite loop.
|
|
||||||
// We would instead have to do
|
|
||||||
// `ParentObject.prototype.init.apply(this, arguments);`
|
|
||||||
// Bleh. We're not creating a _super() function, so it's good to keep
|
|
||||||
// the parent constructor reference simple.
|
|
||||||
let subObj = function(){
|
|
||||||
init.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Inherit from this object's prototype
|
|
||||||
subObj.prototype = Lib.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 = CoreObject.extend;
|
|
||||||
// Make a function for creating instances
|
|
||||||
subObj.create = 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 instance of this Object class
|
|
||||||
*
|
|
||||||
* var myAnimal = Animal.create();
|
|
||||||
*
|
|
||||||
* @return {CoreObject} An instance of a CoreObject subclass
|
|
||||||
* @this {*}
|
|
||||||
*/
|
|
||||||
CoreObject.create = function(){
|
|
||||||
// Create a new object that inherits from this object's prototype
|
|
||||||
var inst = Lib.obj.create(this.prototype);
|
|
||||||
|
|
||||||
// Apply this constructor function to the new object
|
|
||||||
this.apply(inst, arguments);
|
|
||||||
|
|
||||||
// Return the new object
|
|
||||||
return inst;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CoreObject;
|
|
125
src/js/core.js
125
src/js/core.js
@ -1,125 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview Main function src.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Player from './player';
|
|
||||||
import Plugins from './plugins';
|
|
||||||
import Options from './options';
|
|
||||||
import * as Lib from './lib';
|
|
||||||
import * as VjsUtil from './util';
|
|
||||||
import CoreObject from './core-object';
|
|
||||||
import document from 'global/document';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Doubles as the main function for users to create a player instance and also
|
|
||||||
* the main library object.
|
|
||||||
*
|
|
||||||
* **ALIASES** videojs, _V_ (deprecated)
|
|
||||||
*
|
|
||||||
* The `vjs` function can be used to initialize or retrieve a player.
|
|
||||||
*
|
|
||||||
* var myPlayer = vjs('my_video_id');
|
|
||||||
*
|
|
||||||
* @param {String|Element} id Video element or video element ID
|
|
||||||
* @param {Object=} options Optional options object for config/settings
|
|
||||||
* @param {Function=} ready Optional ready callback
|
|
||||||
* @return {Player} A player instance
|
|
||||||
* @namespace
|
|
||||||
*/
|
|
||||||
var videojs = 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 (Player.players[id]) {
|
|
||||||
|
|
||||||
// If options or ready funtion are passed, warn
|
|
||||||
if (options) {
|
|
||||||
Lib.log.warn(`Player "${id}" is already initialised. Options will not be applied.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ready) {
|
|
||||||
Player.players[id].ready(ready);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Player.players[id];
|
|
||||||
|
|
||||||
// Otherwise get element for ID
|
|
||||||
} else {
|
|
||||||
tag = Lib.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 Player(tag, options, ready);
|
|
||||||
};
|
|
||||||
|
|
||||||
// CDN Version. Used to target right flash swf.
|
|
||||||
videojs.CDN_VERSION = '__VERSION_NO_PATCH__';
|
|
||||||
videojs.ACCESS_PROTOCOL = ('https:' === document.location.protocol ? 'https://' : 'http://');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Full player version
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
videojs['VERSION'] = '__VERSION__';
|
|
||||||
|
|
||||||
// Set CDN Version of swf
|
|
||||||
// The added (+) blocks the replace from changing this _VERSION_NO_PATCH_ string
|
|
||||||
if (videojs.CDN_VERSION !== '__VERSION_'+'NO_PATCH__') {
|
|
||||||
Options['flash']['swf'] = `${videojs.ACCESS_PROTOCOL}vjs.zencdn.net/${videojs.CDN_VERSION}/video-js.swf`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function for adding languages to the default options. Useful for
|
|
||||||
* amending multiple language support at runtime.
|
|
||||||
*
|
|
||||||
* Example: videojs.addLanguage('es', {'Hello':'Hola'});
|
|
||||||
*
|
|
||||||
* @param {String} code The language code or dictionary property
|
|
||||||
* @param {Object} data The data values to be translated
|
|
||||||
* @return {Object} The resulting global languages dictionary object
|
|
||||||
*/
|
|
||||||
videojs.addLanguage = function(code, data){
|
|
||||||
if(Options['languages'][code] !== undefined) {
|
|
||||||
Options['languages'][code] = VjsUtil.mergeOptions(Options['languages'][code], data);
|
|
||||||
} else {
|
|
||||||
Options['languages'][code] = data;
|
|
||||||
}
|
|
||||||
return Options['languages'];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom Universal Module Definition (UMD)
|
|
||||||
*
|
|
||||||
* Video.js will never be a non-browser lib so we can simplify UMD a bunch and
|
|
||||||
* still support requirejs and browserify. This also needs to be closure
|
|
||||||
* compiler compatible, so string keys are used.
|
|
||||||
*/
|
|
||||||
if (typeof define === 'function' && define['amd']) {
|
|
||||||
define('videojs', [], function(){ return videojs; });
|
|
||||||
|
|
||||||
// checking that module is an object too because of umdjs/umd#35
|
|
||||||
} else if (typeof exports === 'object' && typeof module === 'object') {
|
|
||||||
module['exports'] = videojs;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default videojs;
|
|
@ -1,5 +1,5 @@
|
|||||||
import Component from './component';
|
import Component from './component';
|
||||||
import * as Lib from './lib';
|
import * as Dom from './utils/dom.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display that an error has occurred making the video unplayable
|
* Display that an error has occurred making the video unplayable
|
||||||
@ -21,7 +21,7 @@ class ErrorDisplay extends Component {
|
|||||||
className: 'vjs-error-display'
|
className: 'vjs-error-display'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contentEl_ = Lib.createEl('div');
|
this.contentEl_ = Dom.createEl('div');
|
||||||
el.appendChild(this.contentEl_);
|
el.appendChild(this.contentEl_);
|
||||||
|
|
||||||
return el;
|
return el;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as Events from './events';
|
import * as Events from './utils/events.js';
|
||||||
import * as Lib from './lib';
|
|
||||||
|
|
||||||
var EventEmitter = function() {};
|
var EventEmitter = function() {};
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import * as Lib from './lib';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A combination of node inherits and babel's inherits (after transpile).
|
* A combination of node inherits and babel's inherits (after transpile).
|
||||||
* Both work the same but node adds `super_` to the subClass
|
* Both work the same but node adds `super_` to the subClass
|
||||||
|
901
src/js/lib.js
901
src/js/lib.js
@ -1,901 +0,0 @@
|
|||||||
import window from 'global/window';
|
|
||||||
import document from 'global/document';
|
|
||||||
let navigator = window.navigator;
|
|
||||||
|
|
||||||
let hasOwnProp = Object.prototype.hasOwnProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an element and applies properties.
|
|
||||||
* @param {String=} tagName Name of tag to be created.
|
|
||||||
* @param {Object=} properties Element properties to be applied.
|
|
||||||
* @return {Element}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var createEl = function(tagName='div', properties={}){
|
|
||||||
let el = document.createElement(tagName);
|
|
||||||
|
|
||||||
obj.each(properties, function(propName, val){
|
|
||||||
// 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, val);
|
|
||||||
} else {
|
|
||||||
el[propName] = val;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uppercase the first letter of a string
|
|
||||||
* @param {String} string String to be uppercased
|
|
||||||
* @return {String}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var capitalize = function(string){
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object functions container
|
|
||||||
* @type {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var obj = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object.create shim for prototypal inheritance
|
|
||||||
*
|
|
||||||
* https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
|
|
||||||
*
|
|
||||||
* @function
|
|
||||||
* @param {Object} obj Object to use as prototype
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
obj.create = Object.create || function(obj){
|
|
||||||
//Create a new function called 'F' which is just an empty object.
|
|
||||||
function F() {}
|
|
||||||
|
|
||||||
//the prototype of the 'F' function should point to the
|
|
||||||
//parameter of the anonymous function.
|
|
||||||
F.prototype = obj;
|
|
||||||
|
|
||||||
//create a new constructor function based off of the 'F' function.
|
|
||||||
return new F();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loop through each property in an object and call a function
|
|
||||||
* whose arguments are (key,value)
|
|
||||||
* @param {Object} obj Object of properties
|
|
||||||
* @param {Function} fn Function to be called on each property.
|
|
||||||
* @this {*}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
obj.each = function(obj, fn, context){
|
|
||||||
for (var key in obj) {
|
|
||||||
if (hasOwnProp.call(obj, key)) {
|
|
||||||
fn.call(context || this, key, obj[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge two objects together and return the original.
|
|
||||||
* @param {Object} obj1
|
|
||||||
* @param {Object} obj2
|
|
||||||
* @return {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
obj.merge = function(obj1, obj2){
|
|
||||||
if (!obj2) { return obj1; }
|
|
||||||
for (var key in obj2){
|
|
||||||
if (hasOwnProp.call(obj2, key)) {
|
|
||||||
obj1[key] = obj2[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj1;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge two objects, and merge any properties that are objects
|
|
||||||
* instead of just overwriting one. Uses to merge options hashes
|
|
||||||
* where deeper default settings are important.
|
|
||||||
* @param {Object} obj1 Object to override
|
|
||||||
* @param {Object} obj2 Overriding object
|
|
||||||
* @return {Object} New object. Obj1 and Obj2 will be untouched.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
obj.deepMerge = function(obj1, obj2){
|
|
||||||
var key, val1, val2;
|
|
||||||
|
|
||||||
// make a copy of obj1 so we're not overwriting original values.
|
|
||||||
// like prototype.options_ and all sub options objects
|
|
||||||
obj1 = 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 (obj.isPlain(val1) && obj.isPlain(val2)) {
|
|
||||||
obj1[key] = obj.deepMerge(val1, val2);
|
|
||||||
} else {
|
|
||||||
obj1[key] = obj2[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj1;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a copy of the supplied object
|
|
||||||
* @param {Object} obj Object to copy
|
|
||||||
* @return {Object} Copy of object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
obj.copy = function(objToCopy){
|
|
||||||
return obj.merge({}, objToCopy);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an object is plain, and not a dom node or any object sub-instance
|
|
||||||
* @param {Object} obj Object to check
|
|
||||||
* @return {Boolean} True if plain, false otherwise
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
obj.isPlain = function(obj){
|
|
||||||
return !!obj
|
|
||||||
&& typeof obj === 'object'
|
|
||||||
&& obj.toString() === '[object Object]'
|
|
||||||
&& obj.constructor === Object;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an object is Array
|
|
||||||
* Since instanceof Array will not work on arrays created in another frame we need to use Array.isArray, but since IE8 does not support Array.isArray we need this shim
|
|
||||||
* @param {Object} obj Object to check
|
|
||||||
* @return {Boolean} True if plain, false otherwise
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
obj.isArray = Array.isArray || function(arr) {
|
|
||||||
return Object.prototype.toString.call(arr) === '[object Array]';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind (a.k.a proxy or Context). A simple method for changing the context of a function
|
|
||||||
It also stores a unique id on the function so it can be easily removed from events
|
|
||||||
* @param {*} context The object to bind as scope
|
|
||||||
* @param {Function} fn The function to be bound to a scope
|
|
||||||
* @param {Number=} uid An optional unique ID for the function to be set
|
|
||||||
* @return {Function}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var bind = function(context, fn, uid) {
|
|
||||||
// Make sure the function has a unique ID
|
|
||||||
if (!fn.guid) { fn.guid = guid++; }
|
|
||||||
|
|
||||||
// Create the new function that changes the context
|
|
||||||
let 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 listeners are stored here.
|
|
||||||
* (also from jsninja.com, slightly modified and updated for closure compiler)
|
|
||||||
* @type {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var cache = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unique ID for an element or function
|
|
||||||
* @type {Number}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var guid = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unique attribute name to store an element's guid in
|
|
||||||
* @type {String}
|
|
||||||
* @constant
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var expando = 'vdata' + (new Date()).getTime();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the cache object where data for an element is stored
|
|
||||||
* @param {Element} el Element to store data for.
|
|
||||||
* @return {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var getData = function(el){
|
|
||||||
var id = el[expando];
|
|
||||||
if (!id) {
|
|
||||||
id = el[expando] = guid++;
|
|
||||||
}
|
|
||||||
if (!cache[id]) {
|
|
||||||
cache[id] = {};
|
|
||||||
}
|
|
||||||
return cache[id];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the cache object where data for an element is stored
|
|
||||||
* @param {Element} el Element to store data for.
|
|
||||||
* @return {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var hasData = function(el){
|
|
||||||
var id = el[expando];
|
|
||||||
return !(!id || isEmpty(cache[id]));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete data for the element from the cache and the guid attr from getElementById
|
|
||||||
* @param {Element} el Remove data for an element
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var removeData = function(el){
|
|
||||||
var id = el[expando];
|
|
||||||
if (!id) { return; }
|
|
||||||
// Remove all stored data
|
|
||||||
// Changed to = null
|
|
||||||
// http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
|
|
||||||
// cache[id] = null;
|
|
||||||
delete cache[id];
|
|
||||||
|
|
||||||
// Remove the expando property from the DOM node
|
|
||||||
try {
|
|
||||||
delete el[expando];
|
|
||||||
} catch(e) {
|
|
||||||
if (el.removeAttribute) {
|
|
||||||
el.removeAttribute(expando);
|
|
||||||
} else {
|
|
||||||
// IE doesn't appear to support removeAttribute on the document element
|
|
||||||
el[expando] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an object is empty
|
|
||||||
* @param {Object} obj The object to check for emptiness
|
|
||||||
* @return {Boolean}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var isEmpty = function(obj) {
|
|
||||||
for (var prop in obj) {
|
|
||||||
// Inlude null properties as empty.
|
|
||||||
if (obj[prop] !== null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an element has a CSS class
|
|
||||||
* @param {Element} element Element to check
|
|
||||||
* @param {String} classToCheck Classname to check
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var hasClass = function(element, classToCheck){
|
|
||||||
return ((' ' + element.className + ' ').indexOf(' ' + classToCheck + ' ') !== -1);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a CSS class name to an element
|
|
||||||
* @param {Element} element Element to add class name to
|
|
||||||
* @param {String} classToAdd Classname to add
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var addClass = function(element, classToAdd){
|
|
||||||
if (!hasClass(element, classToAdd)) {
|
|
||||||
element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a CSS class name from an element
|
|
||||||
* @param {Element} element Element to remove from class name
|
|
||||||
* @param {String} classToAdd Classname to remove
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var removeClass = function(element, classToRemove){
|
|
||||||
if (!hasClass(element, classToRemove)) {return;}
|
|
||||||
|
|
||||||
let classNames = element.className.split(' ');
|
|
||||||
|
|
||||||
// no arr.indexOf in ie8, and we don't want to add a big shim
|
|
||||||
for (let i = classNames.length - 1; i >= 0; i--) {
|
|
||||||
if (classNames[i] === classToRemove) {
|
|
||||||
classNames.splice(i,1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
element.className = classNames.join(' ');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Element for testing browser HTML5 video capabilities
|
|
||||||
* @type {Element}
|
|
||||||
* @constant
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var TEST_VID = createEl('video');
|
|
||||||
let track = document.createElement('track');
|
|
||||||
track.kind = 'captions';
|
|
||||||
track.srclang = 'en';
|
|
||||||
track.label = 'English';
|
|
||||||
TEST_VID.appendChild(track);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Useragent for browser testing.
|
|
||||||
* @type {String}
|
|
||||||
* @constant
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var USER_AGENT = navigator.userAgent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Device is an iPhone
|
|
||||||
* @type {Boolean}
|
|
||||||
* @constant
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var IS_IPHONE = (/iPhone/i).test(USER_AGENT);
|
|
||||||
var IS_IPAD = (/iPad/i).test(USER_AGENT);
|
|
||||||
var IS_IPOD = (/iPod/i).test(USER_AGENT);
|
|
||||||
var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
|
|
||||||
|
|
||||||
var IOS_VERSION = (function(){
|
|
||||||
var match = USER_AGENT.match(/OS (\d+)_/i);
|
|
||||||
if (match && match[1]) { return match[1]; }
|
|
||||||
})();
|
|
||||||
|
|
||||||
var IS_ANDROID = (/Android/i).test(USER_AGENT);
|
|
||||||
var ANDROID_VERSION = (function() {
|
|
||||||
// This matches Android Major.Minor.Patch versions
|
|
||||||
// ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
|
|
||||||
var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
|
|
||||||
major,
|
|
||||||
minor;
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
major = match[1] && parseFloat(match[1]);
|
|
||||||
minor = match[2] && parseFloat(match[2]);
|
|
||||||
|
|
||||||
if (major && minor) {
|
|
||||||
return parseFloat(match[1] + '.' + match[2]);
|
|
||||||
} else if (major) {
|
|
||||||
return major;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
|
|
||||||
var IS_OLD_ANDROID = IS_ANDROID && (/webkit/i).test(USER_AGENT) && ANDROID_VERSION < 2.3;
|
|
||||||
|
|
||||||
var IS_FIREFOX = (/Firefox/i).test(USER_AGENT);
|
|
||||||
var IS_CHROME = (/Chrome/i).test(USER_AGENT);
|
|
||||||
var IS_IE8 = (/MSIE\s8\.0/).test(USER_AGENT);
|
|
||||||
|
|
||||||
var TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
|
|
||||||
var BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in TEST_VID.style;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply attributes to an HTML element.
|
|
||||||
* @param {Element} el Target element.
|
|
||||||
* @param {Object=} attributes Element attributes to be applied.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var setElementAttributes = function(el, attributes){
|
|
||||||
obj.each(attributes, function(attrName, attrValue) {
|
|
||||||
if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
|
|
||||||
el.removeAttribute(attrName);
|
|
||||||
} else {
|
|
||||||
el.setAttribute(attrName, (attrValue === true ? '' : attrValue));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an element's attribute values, as defined on the HTML tag
|
|
||||||
* Attributes are not the same as properties. They're defined on the tag
|
|
||||||
* or with setAttribute (which shouldn't be used with HTML)
|
|
||||||
* This will return true or false for boolean attributes.
|
|
||||||
* @param {Element} tag Element from which to get tag attributes
|
|
||||||
* @return {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var getElementAttributes = function(tag){
|
|
||||||
var obj, knownBooleans, attrs, attrName, attrVal;
|
|
||||||
|
|
||||||
obj = {};
|
|
||||||
|
|
||||||
// known boolean attributes
|
|
||||||
// we can check for matching boolean properties, but older browsers
|
|
||||||
// won't know about HTML5 boolean attributes that we still read from
|
|
||||||
knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
|
|
||||||
|
|
||||||
if (tag && tag.attributes && tag.attributes.length > 0) {
|
|
||||||
attrs = tag.attributes;
|
|
||||||
|
|
||||||
for (var i = attrs.length - 1; i >= 0; i--) {
|
|
||||||
attrName = attrs[i].name;
|
|
||||||
attrVal = attrs[i].value;
|
|
||||||
|
|
||||||
// check for known booleans
|
|
||||||
// the matching element property will return a value for typeof
|
|
||||||
if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {
|
|
||||||
// the value of an included boolean attribute is typically an empty
|
|
||||||
// string ('') which would equal false if we just check for a false value.
|
|
||||||
// we also don't want support bad code like autoplay='false'
|
|
||||||
attrVal = (attrVal !== null) ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj[attrName] = attrVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the computed style value for an element
|
|
||||||
* From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
|
|
||||||
* @param {Element} el Element to get style value for
|
|
||||||
* @param {String} strCssRule Style name
|
|
||||||
* @return {String} Style value
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var 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
|
|
||||||
let upperCasedRule = strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1);
|
|
||||||
strValue = el[`client${upperCasedRule}`] + 'px';
|
|
||||||
}
|
|
||||||
return strValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert an element as the first child node of another
|
|
||||||
* @param {Element} child Element to insert
|
|
||||||
* @param {[type]} parent Element to insert child into
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var insertFirst = function(child, parent){
|
|
||||||
if (parent.firstChild) {
|
|
||||||
parent.insertBefore(child, parent.firstChild);
|
|
||||||
} else {
|
|
||||||
parent.appendChild(child);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object to hold browser support information
|
|
||||||
* @type {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var browser = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shorthand for document.getElementById()
|
|
||||||
* Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
|
|
||||||
* @param {String} id Element ID
|
|
||||||
* @return {Element} Element with supplied ID
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var el = function(id){
|
|
||||||
if (id.indexOf('#') === 0) {
|
|
||||||
id = id.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return document.getElementById(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format seconds as a time string, H:MM:SS or M:SS
|
|
||||||
* Supplying a guide (in seconds) will force a number of leading zeros
|
|
||||||
* to cover the length of the guide
|
|
||||||
* @param {Number} seconds Number of seconds to be turned into a string
|
|
||||||
* @param {Number} guide Number (in seconds) to model the string after
|
|
||||||
* @return {String} Time formatted as H:MM:SS or M:SS
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var formatTime = function(seconds, guide=seconds) {
|
|
||||||
let s = Math.floor(seconds % 60);
|
|
||||||
let m = Math.floor(seconds / 60 % 60);
|
|
||||||
let h = Math.floor(seconds / 3600);
|
|
||||||
const gm = Math.floor(guide / 60 % 60);
|
|
||||||
const gh = Math.floor(guide / 3600);
|
|
||||||
|
|
||||||
// handle invalid times
|
|
||||||
if (isNaN(seconds) || seconds === Infinity) {
|
|
||||||
// '-' is false for all relational operators (e.g. <, >=) so this setting
|
|
||||||
// will add the minimum number of fields specified by the guide
|
|
||||||
h = m = s = '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we need to show hours
|
|
||||||
h = (h > 0 || gh > 0) ? h + ':' : '';
|
|
||||||
|
|
||||||
// If hours are showing, we may need to add a leading zero.
|
|
||||||
// Always show at least one digit of minutes.
|
|
||||||
m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
|
|
||||||
|
|
||||||
// Check if leading zero is need for seconds
|
|
||||||
s = (s < 10) ? '0' + s : s;
|
|
||||||
|
|
||||||
return h + m + s;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attempt to block the ability to select text while dragging controls
|
|
||||||
var blockTextSelection = function(){
|
|
||||||
document.body.focus();
|
|
||||||
document.onselectstart = function () { return false; };
|
|
||||||
};
|
|
||||||
// Turn off text selection blocking
|
|
||||||
var unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trim whitespace from the ends of a string.
|
|
||||||
* @param {String} string String to trim
|
|
||||||
* @return {String} Trimmed string
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var trim = function(str){
|
|
||||||
return (str+'').replace(/^\s+|\s+$/g, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should round off a number to a decimal place
|
|
||||||
* @param {Number} num Number to round
|
|
||||||
* @param {Number} dec Number of decimal places to round to
|
|
||||||
* @return {Number} Rounded number
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var round = function(num, dec=0) {
|
|
||||||
return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should create a fake TimeRange object
|
|
||||||
* Mimics an HTML5 time range instance, which has functions that
|
|
||||||
* return the start and end times for a range
|
|
||||||
* TimeRanges are returned by the buffered() method
|
|
||||||
* @param {Number} start Start time in seconds
|
|
||||||
* @param {Number} end End time in seconds
|
|
||||||
* @return {Object} Fake TimeRange object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var createTimeRange = function(start, end){
|
|
||||||
return {
|
|
||||||
length: 1,
|
|
||||||
start: function() { return start; },
|
|
||||||
end: function() { return end; }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add to local storage (maybe removable)
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var setLocalStorage = function(key, value){
|
|
||||||
try {
|
|
||||||
// IE was throwing errors referencing the var anywhere without this
|
|
||||||
let localStorage = window.localStorage || false;
|
|
||||||
if (!localStorage) { return; }
|
|
||||||
localStorage[key] = value;
|
|
||||||
} catch(e) {
|
|
||||||
if (e.code === 22 || e.code === 1014) { // Webkit == 22 / Firefox == 1014
|
|
||||||
log('LocalStorage Full (VideoJS)', e);
|
|
||||||
} else {
|
|
||||||
if (e.code === 18) {
|
|
||||||
log('LocalStorage not allowed (VideoJS)', e);
|
|
||||||
} else {
|
|
||||||
log('LocalStorage Error (VideoJS)', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get absolute version of relative URL. Used to tell flash correct URL.
|
|
||||||
* http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
|
|
||||||
* @param {String} url URL to make absolute
|
|
||||||
* @return {String} Absolute URL
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var getAbsoluteURL = function(url){
|
|
||||||
// Check if absolute URL
|
|
||||||
if (!url.match(/^https?:\/\//)) {
|
|
||||||
// Convert to absolute URL. Flash hosted off-site needs an absolute URL.
|
|
||||||
url = createEl('div', {
|
|
||||||
innerHTML: `<a href="${url}">x</a>`
|
|
||||||
}).firstChild.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve and parse the elements of a URL
|
|
||||||
* @param {String} url The url to parse
|
|
||||||
* @return {Object} An object of url details
|
|
||||||
*/
|
|
||||||
var parseUrl = function(url) {
|
|
||||||
const props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
|
|
||||||
|
|
||||||
// add the url to an anchor and let the browser parse the URL
|
|
||||||
let a = createEl('a', { href: url });
|
|
||||||
|
|
||||||
// IE8 (and 9?) Fix
|
|
||||||
// ie8 doesn't parse the URL correctly until the anchor is actually
|
|
||||||
// added to the body, and an innerHTML is needed to trigger the parsing
|
|
||||||
let addToBody = (a.host === '' && a.protocol !== 'file:');
|
|
||||||
let div;
|
|
||||||
if (addToBody) {
|
|
||||||
div = createEl('div');
|
|
||||||
div.innerHTML = `<a href="${url}"></a>`;
|
|
||||||
a = div.firstChild;
|
|
||||||
// prevent the div from affecting layout
|
|
||||||
div.setAttribute('style', 'display:none; position:absolute;');
|
|
||||||
document.body.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the specific URL properties to a new object
|
|
||||||
// This is also needed for IE8 because the anchor loses its
|
|
||||||
// properties when it's removed from the dom
|
|
||||||
let details = {};
|
|
||||||
for (var i = 0; i < props.length; i++) {
|
|
||||||
details[props[i]] = a[props[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
// IE9 adds the port to the host property unlike everyone else. If
|
|
||||||
// a port identifier is added for standard ports, strip it.
|
|
||||||
if (details.protocol === 'http:') {
|
|
||||||
details.host = details.host.replace(/:80$/, '');
|
|
||||||
}
|
|
||||||
if (details.protocol === 'https:') {
|
|
||||||
details.host = details.host.replace(/:443$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addToBody) {
|
|
||||||
document.body.removeChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
return details;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log messages to the console and history based on the type of message
|
|
||||||
*
|
|
||||||
* @param {String} type The type of message, or `null` for `log`
|
|
||||||
* @param {[type]} args The args to be passed to the log
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function _logType(type, args){
|
|
||||||
// convert args to an array to get array functions
|
|
||||||
let argsArray = Array.prototype.slice.call(args);
|
|
||||||
// if there's no console then don't try to output messages
|
|
||||||
// they will still be stored in Lib.log.history
|
|
||||||
// Was setting these once outside of this function, but containing them
|
|
||||||
// in the function makes it easier to test cases where console doesn't exist
|
|
||||||
let noop = function(){};
|
|
||||||
|
|
||||||
let console = window['console'] || {
|
|
||||||
'log': noop,
|
|
||||||
'warn': noop,
|
|
||||||
'error': noop
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type) {
|
|
||||||
// add the type to the front of the message
|
|
||||||
argsArray.unshift(type.toUpperCase()+':');
|
|
||||||
} else {
|
|
||||||
// default to log with no prefix
|
|
||||||
type = 'log';
|
|
||||||
}
|
|
||||||
|
|
||||||
// add to history
|
|
||||||
log.history.push(argsArray);
|
|
||||||
|
|
||||||
// add console prefix after adding to history
|
|
||||||
argsArray.unshift('VIDEOJS:');
|
|
||||||
|
|
||||||
// call appropriate log function
|
|
||||||
if (console[type].apply) {
|
|
||||||
console[type].apply(console, argsArray);
|
|
||||||
} else {
|
|
||||||
// ie8 doesn't allow error.apply, but it will just join() the array anyway
|
|
||||||
console[type](argsArray.join(' '));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log plain debug messages
|
|
||||||
*/
|
|
||||||
var log = function(){
|
|
||||||
_logType(null, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keep a history of log messages
|
|
||||||
* @type {Array}
|
|
||||||
*/
|
|
||||||
log.history = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log error messages
|
|
||||||
*/
|
|
||||||
log.error = function(){
|
|
||||||
_logType('error', arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log warning messages
|
|
||||||
*/
|
|
||||||
log.warn = function(){
|
|
||||||
_logType('warn', arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Offset Left
|
|
||||||
// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
|
|
||||||
var findPosition = function(el) {
|
|
||||||
let box;
|
|
||||||
|
|
||||||
if (el.getBoundingClientRect && el.parentNode) {
|
|
||||||
box = el.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!box) {
|
|
||||||
return {
|
|
||||||
left: 0,
|
|
||||||
top: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const docEl = document.documentElement;
|
|
||||||
const body = document.body;
|
|
||||||
|
|
||||||
const clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
|
||||||
const scrollLeft = window.pageXOffset || body.scrollLeft;
|
|
||||||
const left = box.left + scrollLeft - clientLeft;
|
|
||||||
|
|
||||||
const clientTop = docEl.clientTop || body.clientTop || 0;
|
|
||||||
const scrollTop = window.pageYOffset || body.scrollTop;
|
|
||||||
const top = box.top + scrollTop - clientTop;
|
|
||||||
|
|
||||||
// Android sometimes returns slightly off decimal values, so need to round
|
|
||||||
return {
|
|
||||||
left: round(left),
|
|
||||||
top: round(top)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array functions container
|
|
||||||
* @type {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var arr = {};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Loops through an array and runs a function for each item inside it.
|
|
||||||
* @param {Array} array The array
|
|
||||||
* @param {Function} callback The function to be run for each item
|
|
||||||
* @param {*} thisArg The `this` binding of callback
|
|
||||||
* @returns {Array} The array
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
arr.forEach = function(array, callback, thisArg) {
|
|
||||||
thisArg = thisArg || this;
|
|
||||||
|
|
||||||
if (obj.isArray(array) && callback instanceof Function) {
|
|
||||||
for (var i = 0, len = array.length; i < len; ++i) {
|
|
||||||
callback.call(thisArg, array[i], i, array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the extension of the passed file name. It will return an empty string if you pass an invalid path
|
|
||||||
*
|
|
||||||
* @param {String} path The fileName path like '/path/to/file.mp4'
|
|
||||||
* @returns {String} The extension in lower case or an empty string if no extension could be found.
|
|
||||||
*/
|
|
||||||
var getFileExtension = function(path) {
|
|
||||||
if(typeof path === 'string'){
|
|
||||||
let splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
|
|
||||||
let pathParts = splitPathRe.exec(path);
|
|
||||||
|
|
||||||
if (pathParts) {
|
|
||||||
return pathParts.pop().toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
createEl,
|
|
||||||
capitalize,
|
|
||||||
obj,
|
|
||||||
isNaN,
|
|
||||||
bind,
|
|
||||||
cache,
|
|
||||||
guid,
|
|
||||||
expando,
|
|
||||||
getData,
|
|
||||||
hasData,
|
|
||||||
removeData,
|
|
||||||
isEmpty,
|
|
||||||
hasClass,
|
|
||||||
addClass,
|
|
||||||
removeClass,
|
|
||||||
TEST_VID,
|
|
||||||
USER_AGENT,
|
|
||||||
IS_IPHONE,
|
|
||||||
IS_IPAD,
|
|
||||||
IS_IPOD,
|
|
||||||
IS_IOS,
|
|
||||||
IOS_VERSION,
|
|
||||||
IS_ANDROID,
|
|
||||||
ANDROID_VERSION,
|
|
||||||
IS_OLD_ANDROID,
|
|
||||||
IS_FIREFOX,
|
|
||||||
IS_IE8,
|
|
||||||
IS_CHROME,
|
|
||||||
TOUCH_ENABLED,
|
|
||||||
BACKGROUND_SIZE_SUPPORTED,
|
|
||||||
setElementAttributes,
|
|
||||||
getElementAttributes,
|
|
||||||
getComputedDimension,
|
|
||||||
insertFirst,
|
|
||||||
browser,
|
|
||||||
el,
|
|
||||||
formatTime,
|
|
||||||
blockTextSelection,
|
|
||||||
unblockTextSelection,
|
|
||||||
trim,
|
|
||||||
round,
|
|
||||||
createTimeRange,
|
|
||||||
setLocalStorage,
|
|
||||||
getAbsoluteURL,
|
|
||||||
parseUrl,
|
|
||||||
log,
|
|
||||||
findPosition,
|
|
||||||
arr,
|
|
||||||
getFileExtension
|
|
||||||
};
|
|
@ -1,4 +1,4 @@
|
|||||||
import * as Lib from './lib';
|
import assign from 'object.assign';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom MediaError to mimic the HTML5 MediaError
|
* Custom MediaError to mimic the HTML5 MediaError
|
||||||
@ -11,7 +11,7 @@ let MediaError = function(code){
|
|||||||
// default code is zero, so this is a custom error
|
// default code is zero, so this is a custom error
|
||||||
this.message = code;
|
this.message = code;
|
||||||
} else if (typeof code === 'object') { // object
|
} else if (typeof code === 'object') { // object
|
||||||
Lib.obj.merge(this, code);
|
assign(this, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.message) {
|
if (!this.message) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import Button from '../button.js';
|
import Button from '../button.js';
|
||||||
import Menu from './menu.js';
|
import Menu from './menu.js';
|
||||||
import * as Lib from '../lib.js';
|
import * as Dom from '../utils/dom.js';
|
||||||
|
import * as Fn from '../utils/fn.js';
|
||||||
|
import toTitleCase from '../utils/to-title-case.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A button class with a popup menu
|
* A button class with a popup menu
|
||||||
@ -49,9 +51,9 @@ class MenuButton extends Button {
|
|||||||
|
|
||||||
// Add a title list item to the top
|
// Add a title list item to the top
|
||||||
if (this.options().title) {
|
if (this.options().title) {
|
||||||
menu.contentEl().appendChild(Lib.createEl('li', {
|
menu.contentEl().appendChild(Dom.createEl('li', {
|
||||||
className: 'vjs-menu-title',
|
className: 'vjs-menu-title',
|
||||||
innerHTML: Lib.capitalize(this.options().title),
|
innerHTML: toTitleCase(this.options().title),
|
||||||
tabindex: -1
|
tabindex: -1
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -91,7 +93,7 @@ class MenuButton extends Button {
|
|||||||
// When you click the button it adds focus, which will show the menu indefinitely.
|
// 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.
|
// So we'll remove focus when the mouse leaves the button.
|
||||||
// Focus is needed for tab navigation.
|
// Focus is needed for tab navigation.
|
||||||
this.one('mouseout', Lib.bind(this, function(){
|
this.one('mouseout', Fn.bind(this, function(){
|
||||||
this.menu.unlockShowing();
|
this.menu.unlockShowing();
|
||||||
this.el_.blur();
|
this.el_.blur();
|
||||||
}));
|
}));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Button from '../button.js';
|
import Button from '../button.js';
|
||||||
import * as Lib from '../lib.js';
|
import assign from 'object.assign';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The component for a menu item. `<li>`
|
* The component for a menu item. `<li>`
|
||||||
@ -18,7 +18,7 @@ class MenuItem extends Button {
|
|||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
createEl(type, props) {
|
createEl(type, props) {
|
||||||
return super.createEl('li', Lib.obj.merge({
|
return super.createEl('li', assign({
|
||||||
className: 'vjs-menu-item',
|
className: 'vjs-menu-item',
|
||||||
innerHTML: this.localize(this.options_['label'])
|
innerHTML: this.localize(this.options_['label'])
|
||||||
}, props));
|
}, props));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Component from '../component.js';
|
import Component from '../component.js';
|
||||||
import * as Lib from '../lib.js';
|
import * as Dom from '../utils/dom.js';
|
||||||
import * as Events from '../events.js';
|
import * as Fn from '../utils/fn.js';
|
||||||
|
import * as Events from '../utils/events.js';
|
||||||
|
|
||||||
/* Menu
|
/* Menu
|
||||||
================================================================================ */
|
================================================================================ */
|
||||||
@ -21,14 +22,14 @@ class Menu extends Component {
|
|||||||
*/
|
*/
|
||||||
addItem(component) {
|
addItem(component) {
|
||||||
this.addChild(component);
|
this.addChild(component);
|
||||||
component.on('click', Lib.bind(this, function(){
|
component.on('click', Fn.bind(this, function(){
|
||||||
this.unlockShowing();
|
this.unlockShowing();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
createEl() {
|
createEl() {
|
||||||
let contentElType = this.options().contentElType || 'ul';
|
let contentElType = this.options().contentElType || 'ul';
|
||||||
this.contentEl_ = Lib.createEl(contentElType, {
|
this.contentEl_ = Dom.createEl(contentElType, {
|
||||||
className: 'vjs-menu-content'
|
className: 'vjs-menu-content'
|
||||||
});
|
});
|
||||||
var el = super.createEl('div', {
|
var el = super.createEl('div', {
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
import Component from './component.js';
|
import Component from './component.js';
|
||||||
import * as Lib from './lib.js';
|
import * as Events from './utils/events.js';
|
||||||
import * as Events from './events.js';
|
import * as Dom from './utils/dom.js';
|
||||||
|
import * as Fn from './utils/fn.js';
|
||||||
|
import * as Guid from './utils/guid.js';
|
||||||
|
import * as browser from './utils/browser.js';
|
||||||
|
import log from './utils/log.js';
|
||||||
|
import toTitleCase from './utils/to-title-case.js';
|
||||||
|
import { createTimeRange } from './utils/time-ranges.js';
|
||||||
import FullscreenApi from './fullscreen-api.js';
|
import FullscreenApi from './fullscreen-api.js';
|
||||||
import MediaError from './media-error.js';
|
import MediaError from './media-error.js';
|
||||||
import Options from './options.js';
|
import Options from './options.js';
|
||||||
import safeParseTuple from 'safe-json-parse/tuple';
|
import safeParseTuple from 'safe-json-parse/tuple';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
import assign from 'object.assign';
|
||||||
|
import mergeOptions from './utils/merge-options.js';
|
||||||
|
|
||||||
// Include required child components
|
// Include required child components
|
||||||
import MediaLoader from './tech/loader.js';
|
import MediaLoader from './tech/loader.js';
|
||||||
@ -53,14 +61,14 @@ class Player extends Component {
|
|||||||
*/
|
*/
|
||||||
constructor(tag, options, ready){
|
constructor(tag, options, ready){
|
||||||
// Make sure tag ID exists
|
// Make sure tag ID exists
|
||||||
tag.id = tag.id || `vjs_video_${Lib.guid++}`;
|
tag.id = tag.id || `vjs_video_${Guid.newGUID()}`;
|
||||||
|
|
||||||
// Set Options
|
// Set Options
|
||||||
// The options argument overrides options set in the video tag
|
// The options argument overrides options set in the video tag
|
||||||
// which overrides globally set options.
|
// which overrides globally set options.
|
||||||
// This latter part coincides with the load order
|
// This latter part coincides with the load order
|
||||||
// (tag must exist before Player)
|
// (tag must exist before Player)
|
||||||
options = Lib.obj.merge(Player.getTagSettings(tag), options);
|
options = assign(Player.getTagSettings(tag), options);
|
||||||
|
|
||||||
// Delay the initialization of children because we need to set up
|
// Delay the initialization of children because we need to set up
|
||||||
// player properties first, and can't use `this` before `super()`
|
// player properties first, and can't use `this` before `super()`
|
||||||
@ -90,7 +98,7 @@ class Player extends Component {
|
|||||||
this.tag = tag; // Store the original tag used to set options
|
this.tag = tag; // Store the original tag used to set options
|
||||||
|
|
||||||
// Store the tag attributes used to restore html5 element
|
// Store the tag attributes used to restore html5 element
|
||||||
this.tagAttributes = tag && Lib.getElementAttributes(tag);
|
this.tagAttributes = tag && Dom.getElementAttributes(tag);
|
||||||
|
|
||||||
// Update Current Language
|
// Update Current Language
|
||||||
this.language_ = options['language'] || Options['language'];
|
this.language_ = options['language'] || Options['language'];
|
||||||
@ -122,8 +130,10 @@ class Player extends Component {
|
|||||||
|
|
||||||
// Load plugins
|
// Load plugins
|
||||||
if (options['plugins']) {
|
if (options['plugins']) {
|
||||||
Lib.obj.each(options['plugins'], function(key, val){
|
let plugins = options['plugins'];
|
||||||
this[key](val);
|
|
||||||
|
Object.getOwnPropertyNames(plugins).forEach(function(name){
|
||||||
|
this[name](plugins[name]);
|
||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +160,7 @@ class Player extends Component {
|
|||||||
|
|
||||||
// TODO: Make this smarter. Toggle user state between touching/mousing
|
// TODO: Make this smarter. Toggle user state between touching/mousing
|
||||||
// using events, since devices can have both touch and mouse events.
|
// using events, since devices can have both touch and mouse events.
|
||||||
// if (Lib.TOUCH_ENABLED) {
|
// if (browser.TOUCH_ENABLED) {
|
||||||
// this.addClass('vjs-touch-enabled');
|
// this.addClass('vjs-touch-enabled');
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@ -200,8 +210,9 @@ class Player extends Component {
|
|||||||
|
|
||||||
// Copy over all the attributes from the tag, including ID and class
|
// Copy over all the attributes from the tag, including ID and class
|
||||||
// ID will now reference player box, not the video tag
|
// ID will now reference player box, not the video tag
|
||||||
const attrs = Lib.getElementAttributes(tag);
|
const attrs = Dom.getElementAttributes(tag);
|
||||||
Lib.obj.each(attrs, function(attr) {
|
|
||||||
|
Object.getOwnPropertyNames(attrs).forEach(function(attr){
|
||||||
// workaround so we don't totally break IE7
|
// workaround so we don't totally break IE7
|
||||||
// http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
|
// http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
|
||||||
if (attr === 'class') {
|
if (attr === 'class') {
|
||||||
@ -234,7 +245,7 @@ class Player extends Component {
|
|||||||
this.fluid(this.options_['fluid']);
|
this.fluid(this.options_['fluid']);
|
||||||
this.aspectRatio(this.options_['aspectRatio']);
|
this.aspectRatio(this.options_['aspectRatio']);
|
||||||
|
|
||||||
// Lib.insertFirst seems to cause the networkState to flicker from 3 to 2, so
|
// insertFirst seems to cause the networkState to flicker from 3 to 2, so
|
||||||
// keep track of the original for later so we can know if the source originally failed
|
// keep track of the original for later so we can know if the source originally failed
|
||||||
tag.initNetworkState_ = tag.networkState;
|
tag.initNetworkState_ = tag.networkState;
|
||||||
|
|
||||||
@ -242,7 +253,7 @@ class Player extends Component {
|
|||||||
if (tag.parentNode) {
|
if (tag.parentNode) {
|
||||||
tag.parentNode.insertBefore(el, tag);
|
tag.parentNode.insertBefore(el, tag);
|
||||||
}
|
}
|
||||||
Lib.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
|
Dom.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
|
||||||
|
|
||||||
this.el_ = el;
|
this.el_ = el;
|
||||||
|
|
||||||
@ -271,7 +282,7 @@ class Player extends Component {
|
|||||||
let parsedVal = parseFloat(value);
|
let parsedVal = parseFloat(value);
|
||||||
|
|
||||||
if (isNaN(parsedVal)) {
|
if (isNaN(parsedVal)) {
|
||||||
Lib.log.error(`Improper value "${value}" supplied for for ${dimension}`);
|
log.error(`Improper value "${value}" supplied for for ${dimension}`);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,12 +407,12 @@ class Player extends Component {
|
|||||||
// Turn off API access because we're loading a new tech that might load asynchronously
|
// Turn off API access because we're loading a new tech that might load asynchronously
|
||||||
this.isReady_ = false;
|
this.isReady_ = false;
|
||||||
|
|
||||||
var techReady = Lib.bind(this, function() {
|
var techReady = Fn.bind(this, function() {
|
||||||
this.triggerReady();
|
this.triggerReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Grab tech-specific options from player options and add source and parent element to use.
|
// Grab tech-specific options from player options and add source and parent element to use.
|
||||||
var techOptions = Lib.obj.merge({
|
var techOptions = assign({
|
||||||
'source': source,
|
'source': source,
|
||||||
'playerId': this.id(),
|
'playerId': this.id(),
|
||||||
'textTracks': this.textTracks_
|
'textTracks': this.textTracks_
|
||||||
@ -462,7 +473,7 @@ class Player extends Component {
|
|||||||
// Add the tech element in the DOM if it was not already there
|
// Add the tech element in the DOM if it was not already there
|
||||||
// Make sure to not insert the original video element if using Html5
|
// Make sure to not insert the original video element if using Html5
|
||||||
if (this.tech.el().parentNode !== this.el() && (techName !== 'Html5' || !this.tag)) {
|
if (this.tech.el().parentNode !== this.el() && (techName !== 'Html5' || !this.tag)) {
|
||||||
Lib.insertFirst(this.tech.el(), this.el());
|
Dom.insertFirst(this.tech.el(), this.el());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get rid of the original video tag reference after the first tech is loaded
|
// Get rid of the original video tag reference after the first tech is loaded
|
||||||
@ -928,7 +939,7 @@ class Player extends Component {
|
|||||||
try {
|
try {
|
||||||
this.tech[method](arg);
|
this.tech[method](arg);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Lib.log(e);
|
log(e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -946,14 +957,14 @@ class Player extends Component {
|
|||||||
} catch(e) {
|
} catch(e) {
|
||||||
// When building additional tech libs, an expected method may not be defined yet
|
// When building additional tech libs, an expected method may not be defined yet
|
||||||
if (this.tech[method] === undefined) {
|
if (this.tech[method] === undefined) {
|
||||||
Lib.log(`Video.js: ${method} method not defined for ${this.techName} playback technology.`, e);
|
log(`Video.js: ${method} method not defined for ${this.techName} playback technology.`, e);
|
||||||
} else {
|
} else {
|
||||||
// When a method isn't available on the object it throws a TypeError
|
// When a method isn't available on the object it throws a TypeError
|
||||||
if (e.name === 'TypeError') {
|
if (e.name === 'TypeError') {
|
||||||
Lib.log(`Video.js: ${method} unavailable on ${this.techName} playback technology element.`, e);
|
log(`Video.js: ${method} unavailable on ${this.techName} playback technology element.`, e);
|
||||||
this.tech.isReady_ = false;
|
this.tech.isReady_ = false;
|
||||||
} else {
|
} else {
|
||||||
Lib.log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
@ -1120,7 +1131,7 @@ class Player extends Component {
|
|||||||
var buffered = this.techGet('buffered');
|
var buffered = this.techGet('buffered');
|
||||||
|
|
||||||
if (!buffered || !buffered.length) {
|
if (!buffered || !buffered.length) {
|
||||||
buffered = Lib.createTimeRange(0,0);
|
buffered = createTimeRange(0,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffered;
|
return buffered;
|
||||||
@ -1201,7 +1212,7 @@ class Player extends Component {
|
|||||||
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
|
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
|
||||||
this.cache_.volume = vol;
|
this.cache_.volume = vol;
|
||||||
this.techCall('setVolume', vol);
|
this.techCall('setVolume', vol);
|
||||||
Lib.setLocalStorage('volume', vol);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1268,7 +1279,7 @@ class Player extends Component {
|
|||||||
* @deprecated for lowercase 's' version
|
* @deprecated for lowercase 's' version
|
||||||
*/
|
*/
|
||||||
isFullScreen(isFS) {
|
isFullScreen(isFS) {
|
||||||
Lib.log.warn('player.isFullScreen() has been deprecated, use player.isFullscreen() with a lowercase "s")');
|
log.warn('player.isFullScreen() has been deprecated, use player.isFullscreen() with a lowercase "s")');
|
||||||
return this.isFullscreen(isFS);
|
return this.isFullscreen(isFS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1300,7 +1311,7 @@ class Player extends Component {
|
|||||||
// when canceling fullscreen. Otherwise if there's multiple
|
// when canceling fullscreen. Otherwise if there's multiple
|
||||||
// players on a page, they would all be reacting to the same fullscreen
|
// players on a page, they would all be reacting to the same fullscreen
|
||||||
// events
|
// events
|
||||||
Events.on(document, fsApi['fullscreenchange'], Lib.bind(this, function documentFullscreenChange(e){
|
Events.on(document, fsApi['fullscreenchange'], Fn.bind(this, function documentFullscreenChange(e){
|
||||||
this.isFullscreen(document[fsApi.fullscreenElement]);
|
this.isFullscreen(document[fsApi.fullscreenElement]);
|
||||||
|
|
||||||
// If cancelling fullscreen, remove event listener.
|
// If cancelling fullscreen, remove event listener.
|
||||||
@ -1332,7 +1343,7 @@ class Player extends Component {
|
|||||||
* @deprecated for lower case 's' version
|
* @deprecated for lower case 's' version
|
||||||
*/
|
*/
|
||||||
requestFullScreen() {
|
requestFullScreen() {
|
||||||
Lib.log.warn('player.requestFullScreen() has been deprecated, use player.requestFullscreen() with a lowercase "s")');
|
log.warn('player.requestFullScreen() has been deprecated, use player.requestFullscreen() with a lowercase "s")');
|
||||||
return this.requestFullscreen();
|
return this.requestFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1365,7 +1376,7 @@ class Player extends Component {
|
|||||||
* @deprecated for exitFullscreen
|
* @deprecated for exitFullscreen
|
||||||
*/
|
*/
|
||||||
cancelFullScreen() {
|
cancelFullScreen() {
|
||||||
Lib.log.warn('player.cancelFullScreen() has been deprecated, use player.exitFullscreen()');
|
log.warn('player.cancelFullScreen() has been deprecated, use player.exitFullscreen()');
|
||||||
return this.exitFullscreen();
|
return this.exitFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1377,13 +1388,13 @@ class Player extends Component {
|
|||||||
this.docOrigOverflow = document.documentElement.style.overflow;
|
this.docOrigOverflow = document.documentElement.style.overflow;
|
||||||
|
|
||||||
// Add listener for esc key to exit fullscreen
|
// Add listener for esc key to exit fullscreen
|
||||||
Events.on(document, 'keydown', Lib.bind(this, this.fullWindowOnEscKey));
|
Events.on(document, 'keydown', Fn.bind(this, this.fullWindowOnEscKey));
|
||||||
|
|
||||||
// Hide any scroll bars
|
// Hide any scroll bars
|
||||||
document.documentElement.style.overflow = 'hidden';
|
document.documentElement.style.overflow = 'hidden';
|
||||||
|
|
||||||
// Apply fullscreen styles
|
// Apply fullscreen styles
|
||||||
Lib.addClass(document.body, 'vjs-full-window');
|
Dom.addClass(document.body, 'vjs-full-window');
|
||||||
|
|
||||||
this.trigger('enterFullWindow');
|
this.trigger('enterFullWindow');
|
||||||
}
|
}
|
||||||
@ -1406,7 +1417,7 @@ class Player extends Component {
|
|||||||
document.documentElement.style.overflow = this.docOrigOverflow;
|
document.documentElement.style.overflow = this.docOrigOverflow;
|
||||||
|
|
||||||
// Remove fullscreen styles
|
// Remove fullscreen styles
|
||||||
Lib.removeClass(document.body, 'vjs-full-window');
|
Dom.removeClass(document.body, 'vjs-full-window');
|
||||||
|
|
||||||
// Resize the box, controller, and poster to original sizes
|
// Resize the box, controller, and poster to original sizes
|
||||||
// this.positionAll();
|
// this.positionAll();
|
||||||
@ -1416,12 +1427,12 @@ class Player extends Component {
|
|||||||
selectSource(sources) {
|
selectSource(sources) {
|
||||||
// Loop through each playback technology in the options order
|
// Loop through each playback technology in the options order
|
||||||
for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
|
for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
|
||||||
let techName = Lib.capitalize(j[i]);
|
let techName = toTitleCase(j[i]);
|
||||||
let tech = Component.getComponent(techName);
|
let tech = Component.getComponent(techName);
|
||||||
|
|
||||||
// Check if the current tech is defined before continuing
|
// Check if the current tech is defined before continuing
|
||||||
if (!tech) {
|
if (!tech) {
|
||||||
Lib.log.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
|
log.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1478,7 +1489,7 @@ class Player extends Component {
|
|||||||
let currentTech = Component.getComponent(this.techName);
|
let currentTech = Component.getComponent(this.techName);
|
||||||
|
|
||||||
// case: Array of source objects to choose from and pick the best to play
|
// case: Array of source objects to choose from and pick the best to play
|
||||||
if (Lib.obj.isArray(source)) {
|
if (Array.isArray(source)) {
|
||||||
this.sourceList_(source);
|
this.sourceList_(source);
|
||||||
|
|
||||||
// case: URL String (http://myvideo...)
|
// case: URL String (http://myvideo...)
|
||||||
@ -1782,7 +1793,7 @@ class Player extends Component {
|
|||||||
|
|
||||||
// log the name of the error type and any message
|
// log the name of the error type and any message
|
||||||
// ie8 just logs "[object object]" if you just log the error object
|
// ie8 just logs "[object object]" if you just log the error object
|
||||||
Lib.log.error(`(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_);
|
log.error(`(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -1848,7 +1859,7 @@ class Player extends Component {
|
|||||||
listenForUserActivity() {
|
listenForUserActivity() {
|
||||||
let mouseInProgress, lastMoveX, lastMoveY;
|
let mouseInProgress, lastMoveX, lastMoveY;
|
||||||
|
|
||||||
let handleActivity = Lib.bind(this, this.reportUserActivity);
|
let handleActivity = Fn.bind(this, this.reportUserActivity);
|
||||||
|
|
||||||
let handleMouseMove = function(e) {
|
let handleMouseMove = function(e) {
|
||||||
// #1068 - Prevent mousemove spamming
|
// #1068 - Prevent mousemove spamming
|
||||||
@ -2100,7 +2111,7 @@ class Player extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
let options = Lib.obj.deepMerge({}, this.options());
|
let options = mergeOptions(this.options());
|
||||||
let tracks = options.tracks;
|
let tracks = options.tracks;
|
||||||
|
|
||||||
options.tracks = [];
|
options.tracks = [];
|
||||||
@ -2109,7 +2120,7 @@ class Player extends Component {
|
|||||||
let track = tracks[i];
|
let track = tracks[i];
|
||||||
|
|
||||||
// deep merge tracks and null out player so no circular references
|
// deep merge tracks and null out player so no circular references
|
||||||
track = Lib.obj.deepMerge({}, track);
|
track = mergeOptions(track);
|
||||||
track.player = undefined;
|
track.player = undefined;
|
||||||
options.tracks[i] = track;
|
options.tracks[i] = track;
|
||||||
}
|
}
|
||||||
@ -2123,7 +2134,7 @@ class Player extends Component {
|
|||||||
'tracks': []
|
'tracks': []
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagOptions = Lib.getElementAttributes(tag);
|
const tagOptions = Dom.getElementAttributes(tag);
|
||||||
const dataSetup = tagOptions['data-setup'];
|
const dataSetup = tagOptions['data-setup'];
|
||||||
|
|
||||||
// Check if data-setup attr exists.
|
// Check if data-setup attr exists.
|
||||||
@ -2132,12 +2143,12 @@ class Player extends Component {
|
|||||||
// If empty string, make it a parsable json object.
|
// If empty string, make it a parsable json object.
|
||||||
const [err, data] = safeParseTuple(dataSetup || '{}');
|
const [err, data] = safeParseTuple(dataSetup || '{}');
|
||||||
if (err) {
|
if (err) {
|
||||||
Lib.log.error(err);
|
log.error(err);
|
||||||
}
|
}
|
||||||
Lib.obj.merge(tagOptions, data);
|
assign(tagOptions, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lib.obj.merge(baseOptions, tagOptions);
|
assign(baseOptions, tagOptions);
|
||||||
|
|
||||||
// Get tag children settings
|
// Get tag children settings
|
||||||
if (tag.hasChildNodes()) {
|
if (tag.hasChildNodes()) {
|
||||||
@ -2148,9 +2159,9 @@ class Player extends Component {
|
|||||||
// Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
|
// Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
|
||||||
const childName = child.nodeName.toLowerCase();
|
const childName = child.nodeName.toLowerCase();
|
||||||
if (childName === 'source') {
|
if (childName === 'source') {
|
||||||
baseOptions['sources'].push(Lib.getElementAttributes(child));
|
baseOptions['sources'].push(Dom.getElementAttributes(child));
|
||||||
} else if (childName === 'track') {
|
} else if (childName === 'track') {
|
||||||
baseOptions['tracks'].push(Lib.getElementAttributes(child));
|
baseOptions['tracks'].push(Dom.getElementAttributes(child));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import Button from './button';
|
import Button from './button';
|
||||||
import * as Lib from './lib';
|
import * as Fn from './utils/fn.js';
|
||||||
|
import * as Dom from './utils/dom.js';
|
||||||
|
import * as browser from './utils/browser.js';
|
||||||
|
|
||||||
/* Poster Image
|
/* Poster Image
|
||||||
================================================================================ */
|
================================================================================ */
|
||||||
@ -16,7 +18,7 @@ class PosterImage extends Button {
|
|||||||
super(player, options);
|
super(player, options);
|
||||||
|
|
||||||
this.update();
|
this.update();
|
||||||
player.on('posterchange', Lib.bind(this, this.update));
|
player.on('posterchange', Fn.bind(this, this.update));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,7 +34,7 @@ class PosterImage extends Button {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
createEl() {
|
createEl() {
|
||||||
let el = Lib.createEl('div', {
|
let el = Dom.createEl('div', {
|
||||||
className: 'vjs-poster',
|
className: 'vjs-poster',
|
||||||
|
|
||||||
// Don't want poster to be tabbable.
|
// Don't want poster to be tabbable.
|
||||||
@ -43,8 +45,8 @@ class PosterImage extends Button {
|
|||||||
// ratio, use a div with `background-size` when available. For browsers that
|
// ratio, use a div with `background-size` when available. For browsers that
|
||||||
// do not support `background-size` (e.g. IE8), fall back on using a regular
|
// do not support `background-size` (e.g. IE8), fall back on using a regular
|
||||||
// img element.
|
// img element.
|
||||||
if (!Lib.BACKGROUND_SIZE_SUPPORTED) {
|
if (!browser.BACKGROUND_SIZE_SUPPORTED) {
|
||||||
this.fallbackImg_ = Lib.createEl('img');
|
this.fallbackImg_ = Dom.createEl('img');
|
||||||
el.appendChild(this.fallbackImg_);
|
el.appendChild(this.fallbackImg_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as Events from './events';
|
import * as Events from './utils/events.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Component from '../component.js';
|
import Component from '../component.js';
|
||||||
import * as Lib from '../lib.js';
|
import assign from 'object.assign';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SeekBar Behavior includes play progress bar, and seek handle
|
* SeekBar Behavior includes play progress bar, and seek handle
|
||||||
@ -15,7 +15,7 @@ class SliderHandle extends Component {
|
|||||||
props = props || {};
|
props = props || {};
|
||||||
// Add the slider element class to all sub classes
|
// Add the slider element class to all sub classes
|
||||||
props.className = props.className + ' vjs-slider-handle';
|
props.className = props.className + ' vjs-slider-handle';
|
||||||
props = Lib.obj.merge({
|
props = assign({
|
||||||
innerHTML: `<span class="vjs-control-text">${this.defaultValue || 0}</span>`
|
innerHTML: `<span class="vjs-control-text">${this.defaultValue || 0}</span>`
|
||||||
}, props);
|
}, props);
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import Component from '../component.js';
|
import Component from '../component.js';
|
||||||
import * as Lib from '../lib.js';
|
import * as Dom from '../utils/dom.js';
|
||||||
|
import roundFloat from '../utils/round-float.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
import assign from 'object.assign';
|
||||||
|
|
||||||
/* Slider
|
/* Slider
|
||||||
================================================================================ */
|
================================================================================ */
|
||||||
@ -36,7 +38,7 @@ class Slider extends Component {
|
|||||||
createEl(type, props={}) {
|
createEl(type, props={}) {
|
||||||
// Add the slider element class to all sub classes
|
// Add the slider element class to all sub classes
|
||||||
props.className = props.className + ' vjs-slider';
|
props.className = props.className + ' vjs-slider';
|
||||||
props = Lib.obj.merge({
|
props = assign({
|
||||||
'role': 'slider',
|
'role': 'slider',
|
||||||
'aria-valuenow': 0,
|
'aria-valuenow': 0,
|
||||||
'aria-valuemin': 0,
|
'aria-valuemin': 0,
|
||||||
@ -49,7 +51,7 @@ class Slider extends Component {
|
|||||||
|
|
||||||
handleMouseDown(event) {
|
handleMouseDown(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
Lib.blockTextSelection();
|
Dom.blockTextSelection();
|
||||||
this.addClass('vjs-sliding');
|
this.addClass('vjs-sliding');
|
||||||
|
|
||||||
this.on(document, 'mousemove', this.handleMouseMove);
|
this.on(document, 'mousemove', this.handleMouseMove);
|
||||||
@ -64,7 +66,7 @@ class Slider extends Component {
|
|||||||
handleMouseMove() {}
|
handleMouseMove() {}
|
||||||
|
|
||||||
handleMouseUp() {
|
handleMouseUp() {
|
||||||
Lib.unblockTextSelection();
|
Dom.unblockTextSelection();
|
||||||
this.removeClass('vjs-sliding');
|
this.removeClass('vjs-sliding');
|
||||||
|
|
||||||
this.off(document, 'mousemove', this.handleMouseMove);
|
this.off(document, 'mousemove', this.handleMouseMove);
|
||||||
@ -102,7 +104,7 @@ class Slider extends Component {
|
|||||||
let barProgress = this.updateHandlePosition(progress);
|
let barProgress = this.updateHandlePosition(progress);
|
||||||
|
|
||||||
// Convert to a percentage for setting
|
// Convert to a percentage for setting
|
||||||
let percentage = Lib.round(barProgress * 100, 2) + '%';
|
let percentage = roundFloat(barProgress * 100, 2) + '%';
|
||||||
|
|
||||||
// Set the new bar width or height
|
// Set the new bar width or height
|
||||||
if (this.vertical()) {
|
if (this.vertical()) {
|
||||||
@ -145,7 +147,7 @@ class Slider extends Component {
|
|||||||
// The bar does reach the left side, so we need to account for this in the bar's width
|
// The bar does reach the left side, so we need to account for this in the bar's width
|
||||||
let barProgress = adjustedProgress + (handlePercent / 2);
|
let barProgress = adjustedProgress + (handlePercent / 2);
|
||||||
|
|
||||||
let percentage = Lib.round(adjustedProgress * 100, 2) + '%';
|
let percentage = roundFloat(adjustedProgress * 100, 2) + '%';
|
||||||
|
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
handle.el().style.bottom = percentage;
|
handle.el().style.bottom = percentage;
|
||||||
@ -158,7 +160,7 @@ class Slider extends Component {
|
|||||||
|
|
||||||
calculateDistance(event){
|
calculateDistance(event){
|
||||||
let el = this.el_;
|
let el = this.el_;
|
||||||
let box = Lib.findPosition(el);
|
let box = Dom.findPosition(el);
|
||||||
let boxW = el.offsetWidth;
|
let boxW = el.offsetWidth;
|
||||||
let boxH = el.offsetHeight;
|
let boxH = el.offsetHeight;
|
||||||
let handle = this.handle;
|
let handle = this.handle;
|
||||||
|
@ -5,10 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Tech from './tech';
|
import Tech from './tech';
|
||||||
import * as Lib from '../lib';
|
import * as Dom from '../utils/dom.js';
|
||||||
|
import * as Url from '../utils/url.js';
|
||||||
|
import { createTimeRange } from '../utils/time-ranges.js';
|
||||||
import FlashRtmpDecorator from './flash-rtmp';
|
import FlashRtmpDecorator from './flash-rtmp';
|
||||||
import Component from '../component';
|
import Component from '../component';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
import assign from 'object.assign';
|
||||||
|
|
||||||
let navigator = window.navigator;
|
let navigator = window.navigator;
|
||||||
/**
|
/**
|
||||||
@ -30,7 +33,7 @@ class Flash extends Tech {
|
|||||||
let objId = options.playerId+'_flash_api';
|
let objId = options.playerId+'_flash_api';
|
||||||
|
|
||||||
// Merge default flashvars with ones passed in to init
|
// Merge default flashvars with ones passed in to init
|
||||||
let flashVars = Lib.obj.merge({
|
let flashVars = assign({
|
||||||
|
|
||||||
// SWF Callback Functions
|
// SWF Callback Functions
|
||||||
'readyFunction': 'videojs.Flash.onReady',
|
'readyFunction': 'videojs.Flash.onReady',
|
||||||
@ -46,13 +49,13 @@ class Flash extends Tech {
|
|||||||
}, options.flashVars);
|
}, options.flashVars);
|
||||||
|
|
||||||
// Merge default parames with ones passed in
|
// Merge default parames with ones passed in
|
||||||
let params = Lib.obj.merge({
|
let params = assign({
|
||||||
'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
|
'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
|
||||||
'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
|
'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
|
||||||
}, options.params);
|
}, options.params);
|
||||||
|
|
||||||
// Merge default attributes with ones passed in
|
// Merge default attributes with ones passed in
|
||||||
let attributes = Lib.obj.merge({
|
let attributes = assign({
|
||||||
'id': objId,
|
'id': objId,
|
||||||
'name': objId, // Both ID and Name needed or swf to identify itself
|
'name': objId, // Both ID and Name needed or swf to identify itself
|
||||||
'class': 'vjs-tech'
|
'class': 'vjs-tech'
|
||||||
@ -104,7 +107,7 @@ class Flash extends Tech {
|
|||||||
|
|
||||||
setSrc(src) {
|
setSrc(src) {
|
||||||
// Make sure source URL is absolute.
|
// Make sure source URL is absolute.
|
||||||
src = Lib.getAbsoluteURL(src);
|
src = Url.getAbsoluteURL(src);
|
||||||
this.el_.vjs_src(src);
|
this.el_.vjs_src(src);
|
||||||
|
|
||||||
// Currently the SWF doesn't autoplay if you load a source later.
|
// Currently the SWF doesn't autoplay if you load a source later.
|
||||||
@ -150,7 +153,7 @@ class Flash extends Tech {
|
|||||||
setPoster() {}
|
setPoster() {}
|
||||||
|
|
||||||
buffered() {
|
buffered() {
|
||||||
return Lib.createTimeRange(0, this.el_.vjs_getProperty('buffered'));
|
return createTimeRange(0, this.el_.vjs_getProperty('buffered'));
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsFullScreen() {
|
supportsFullScreen() {
|
||||||
@ -215,7 +218,7 @@ Flash.nativeSourceHandler.canHandleSource = function(source){
|
|||||||
var type;
|
var type;
|
||||||
|
|
||||||
function guessMimeType(src) {
|
function guessMimeType(src) {
|
||||||
var ext = Lib.getFileExtension(src);
|
var ext = Url.getFileExtension(src);
|
||||||
if (ext) {
|
if (ext) {
|
||||||
return `video/${ext}`;
|
return `video/${ext}`;
|
||||||
}
|
}
|
||||||
@ -264,7 +267,7 @@ Flash.formats = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Flash.onReady = function(currSwf){
|
Flash.onReady = function(currSwf){
|
||||||
let el = Lib.el(currSwf);
|
let el = Dom.el(currSwf);
|
||||||
let tech = el && el.tech;
|
let tech = el && el.tech;
|
||||||
|
|
||||||
// if there is no el then the tech has been disposed
|
// if there is no el then the tech has been disposed
|
||||||
@ -297,13 +300,13 @@ Flash.checkReady = function(tech){
|
|||||||
|
|
||||||
// Trigger events from the swf on the player
|
// Trigger events from the swf on the player
|
||||||
Flash.onEvent = function(swfID, eventName){
|
Flash.onEvent = function(swfID, eventName){
|
||||||
let tech = Lib.el(swfID).tech;
|
let tech = Dom.el(swfID).tech;
|
||||||
tech.trigger(eventName);
|
tech.trigger(eventName);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Log errors from the swf
|
// Log errors from the swf
|
||||||
Flash.onError = function(swfID, err){
|
Flash.onError = function(swfID, err){
|
||||||
const tech = Lib.el(swfID).tech;
|
const tech = Dom.el(swfID).tech;
|
||||||
const msg = 'FLASH: '+err;
|
const msg = 'FLASH: '+err;
|
||||||
|
|
||||||
if (err === 'srcnotfound') {
|
if (err === 'srcnotfound') {
|
||||||
@ -339,7 +342,7 @@ Flash.embed = function(swf, flashVars, params, attributes){
|
|||||||
const code = Flash.getEmbedCode(swf, flashVars, params, attributes);
|
const code = Flash.getEmbedCode(swf, flashVars, params, attributes);
|
||||||
|
|
||||||
// Get element by embedding code and retrieving created element
|
// Get element by embedding code and retrieving created element
|
||||||
const obj = Lib.createEl('div', { innerHTML: code }).childNodes[0];
|
const obj = Dom.createEl('div', { innerHTML: code }).childNodes[0];
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
@ -352,13 +355,13 @@ Flash.getEmbedCode = function(swf, flashVars, params, attributes){
|
|||||||
|
|
||||||
// Convert flash vars to string
|
// Convert flash vars to string
|
||||||
if (flashVars) {
|
if (flashVars) {
|
||||||
Lib.obj.each(flashVars, function(key, val){
|
Object.getOwnPropertyNames(flashVars).forEach(function(key){
|
||||||
flashVarsString += `${key}=${val}&`;
|
flashVarsString += `${key}=${flashVars[key]}&`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add swf, flashVars, and other default params
|
// Add swf, flashVars, and other default params
|
||||||
params = Lib.obj.merge({
|
params = assign({
|
||||||
'movie': swf,
|
'movie': swf,
|
||||||
'flashvars': flashVarsString,
|
'flashvars': flashVarsString,
|
||||||
'allowScriptAccess': 'always', // Required to talk to swf
|
'allowScriptAccess': 'always', // Required to talk to swf
|
||||||
@ -366,11 +369,11 @@ Flash.getEmbedCode = function(swf, flashVars, params, attributes){
|
|||||||
}, params);
|
}, params);
|
||||||
|
|
||||||
// Create param tags string
|
// Create param tags string
|
||||||
Lib.obj.each(params, function(key, val){
|
Object.getOwnPropertyNames(params).forEach(function(key){
|
||||||
paramsString += `<param name="${key}" value="${val}" />`;
|
paramsString += `<param name="${key}" value="${params[key]}" />`;
|
||||||
});
|
});
|
||||||
|
|
||||||
attributes = Lib.obj.merge({
|
attributes = assign({
|
||||||
// Add swf to attributes (need both for IE and Others to work)
|
// Add swf to attributes (need both for IE and Others to work)
|
||||||
'data': swf,
|
'data': swf,
|
||||||
|
|
||||||
@ -381,8 +384,8 @@ Flash.getEmbedCode = function(swf, flashVars, params, attributes){
|
|||||||
}, attributes);
|
}, attributes);
|
||||||
|
|
||||||
// Create Attributes string
|
// Create Attributes string
|
||||||
Lib.obj.each(attributes, function(key, val){
|
Object.getOwnPropertyNames(attributes).forEach(function(key){
|
||||||
attrsString += `${key}="${val}" `;
|
attrsString += `${key}="${attributes[key]}" `;
|
||||||
});
|
});
|
||||||
|
|
||||||
return `${objTag}${attrsString}>${paramsString}</object>`;
|
return `${objTag}${attrsString}>${paramsString}</object>`;
|
||||||
|
@ -4,9 +4,15 @@
|
|||||||
|
|
||||||
import Tech from './tech.js';
|
import Tech from './tech.js';
|
||||||
import Component from '../component';
|
import Component from '../component';
|
||||||
import * as Lib from '../lib';
|
import * as Dom from '../utils/dom.js';
|
||||||
import * as VjsUtil from '../util';
|
import * as Url from '../utils/url.js';
|
||||||
|
import * as Fn from '../utils/fn.js';
|
||||||
|
import log from '../utils/log.js';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
import window from 'global/window';
|
||||||
|
import assign from 'object.assign';
|
||||||
|
import mergeOptions from '../utils/merge-options.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML5 Media Controller - Wrapper for HTML5 Media API
|
* HTML5 Media Controller - Wrapper for HTML5 Media API
|
||||||
@ -58,14 +64,14 @@ class Html5 extends Tech {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.featuresNativeTextTracks) {
|
if (this.featuresNativeTextTracks) {
|
||||||
this.on('loadstart', Lib.bind(this, this.hideCaptions));
|
this.on('loadstart', Fn.bind(this, this.hideCaptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if native controls should be used
|
// Determine if native controls should be used
|
||||||
// Our goal should be to get the custom controls on mobile solid everywhere
|
// Our goal should be to get the custom controls on mobile solid everywhere
|
||||||
// so we can remove this all together. Right now this will block custom
|
// so we can remove this all together. Right now this will block custom
|
||||||
// controls on touch enabled laptops like the Chrome Pixel
|
// controls on touch enabled laptops like the Chrome Pixel
|
||||||
if (Lib.TOUCH_ENABLED && options.nativeControlsForTouch === true) {
|
if (browser.TOUCH_ENABLED && options.nativeControlsForTouch === true) {
|
||||||
this.trigger('usenativecontrols');
|
this.trigger('usenativecontrols');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,17 +98,17 @@ class Html5 extends Tech {
|
|||||||
Html5.disposeMediaElement(el);
|
Html5.disposeMediaElement(el);
|
||||||
el = clone;
|
el = clone;
|
||||||
} else {
|
} else {
|
||||||
el = Lib.createEl('video');
|
el = document.createElement('video');
|
||||||
|
|
||||||
// determine if native controls should be used
|
// determine if native controls should be used
|
||||||
let tagAttributes = this.options_.tag && Lib.getElementAttributes(this.options_.tag);
|
let tagAttributes = this.options_.tag && Dom.getElementAttributes(this.options_.tag);
|
||||||
let attributes = VjsUtil.mergeOptions({}, tagAttributes);
|
let attributes = mergeOptions({}, tagAttributes);
|
||||||
if (!Lib.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
|
if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
|
||||||
delete attributes.controls;
|
delete attributes.controls;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lib.setElementAttributes(el,
|
Dom.setElementAttributes(el,
|
||||||
Lib.obj.merge(attributes, {
|
assign(attributes, {
|
||||||
id: this.options_.playerId + '_html5_api',
|
id: this.options_.playerId + '_html5_api',
|
||||||
class: 'vjs-tech'
|
class: 'vjs-tech'
|
||||||
})
|
})
|
||||||
@ -133,7 +139,7 @@ class Html5 extends Tech {
|
|||||||
if (typeof this.options_[attr] !== 'undefined') {
|
if (typeof this.options_[attr] !== 'undefined') {
|
||||||
overwriteAttrs[attr] = this.options_[attr];
|
overwriteAttrs[attr] = this.options_[attr];
|
||||||
}
|
}
|
||||||
Lib.setElementAttributes(el, overwriteAttrs);
|
Dom.setElementAttributes(el, overwriteAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return el;
|
return el;
|
||||||
@ -167,7 +173,7 @@ class Html5 extends Tech {
|
|||||||
try {
|
try {
|
||||||
this.el_.currentTime = seconds;
|
this.el_.currentTime = seconds;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Lib.log(e, 'Video is not ready. (Video.js)');
|
log(e, 'Video is not ready. (Video.js)');
|
||||||
// this.warning(VideoJS.warnings.videoNotReady);
|
// this.warning(VideoJS.warnings.videoNotReady);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,9 +193,9 @@ class Html5 extends Tech {
|
|||||||
|
|
||||||
supportsFullScreen() {
|
supportsFullScreen() {
|
||||||
if (typeof this.el_.webkitEnterFullScreen === 'function') {
|
if (typeof this.el_.webkitEnterFullScreen === 'function') {
|
||||||
|
let userAgent = window.navigator.userAgent;
|
||||||
// Seems to be broken in Chromium/Chrome && Safari in Leopard
|
// Seems to be broken in Chromium/Chrome && Safari in Leopard
|
||||||
if (/Android/.test(Lib.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(Lib.USER_AGENT)) {
|
if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,6 +369,19 @@ class Html5 extends Tech {
|
|||||||
|
|
||||||
/* HTML5 Support Testing ---------------------------------------------------- */
|
/* HTML5 Support Testing ---------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element for testing browser HTML5 video capabilities
|
||||||
|
* @type {Element}
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Html5.TEST_VID = document.createElement('video');
|
||||||
|
let track = document.createElement('track');
|
||||||
|
track.kind = 'captions';
|
||||||
|
track.srclang = 'en';
|
||||||
|
track.label = 'English';
|
||||||
|
Html5.TEST_VID.appendChild(track);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if HTML5 video is supported by this browser/device
|
* Check if HTML5 video is supported by this browser/device
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
@ -370,12 +389,12 @@ class Html5 extends Tech {
|
|||||||
Html5.isSupported = function(){
|
Html5.isSupported = function(){
|
||||||
// IE9 with no Media Player is a LIAR! (#984)
|
// IE9 with no Media Player is a LIAR! (#984)
|
||||||
try {
|
try {
|
||||||
Lib.TEST_VID['volume'] = 0.5;
|
Html5.TEST_VID['volume'] = 0.5;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!Lib.TEST_VID.canPlayType;
|
return !!Html5.TEST_VID.canPlayType;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add Source Handler pattern functions to this tech
|
// Add Source Handler pattern functions to this tech
|
||||||
@ -401,7 +420,7 @@ Html5.nativeSourceHandler.canHandleSource = function(source){
|
|||||||
// IE9 on Windows 7 without MediaPlayer throws an error here
|
// IE9 on Windows 7 without MediaPlayer throws an error here
|
||||||
// https://github.com/videojs/video.js/issues/519
|
// https://github.com/videojs/video.js/issues/519
|
||||||
try {
|
try {
|
||||||
return Lib.TEST_VID.canPlayType(type);
|
return Html5.TEST_VID.canPlayType(type);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -412,7 +431,7 @@ Html5.nativeSourceHandler.canHandleSource = function(source){
|
|||||||
return canPlayType(source.type);
|
return canPlayType(source.type);
|
||||||
} else if (source.src) {
|
} else if (source.src) {
|
||||||
// If no type, fall back to checking 'video/[EXTENSION]'
|
// If no type, fall back to checking 'video/[EXTENSION]'
|
||||||
ext = Lib.getFileExtension(source.src);
|
ext = Url.getFileExtension(source.src);
|
||||||
|
|
||||||
return canPlayType(`video/${ext}`);
|
return canPlayType(`video/${ext}`);
|
||||||
}
|
}
|
||||||
@ -447,9 +466,9 @@ Html5.registerSourceHandler(Html5.nativeSourceHandler);
|
|||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
Html5.canControlVolume = function(){
|
Html5.canControlVolume = function(){
|
||||||
var volume = Lib.TEST_VID.volume;
|
var volume = Html5.TEST_VID.volume;
|
||||||
Lib.TEST_VID.volume = (volume / 2) + 0.1;
|
Html5.TEST_VID.volume = (volume / 2) + 0.1;
|
||||||
return volume !== Lib.TEST_VID.volume;
|
return volume !== Html5.TEST_VID.volume;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -457,9 +476,9 @@ Html5.canControlVolume = function(){
|
|||||||
* @return {[type]} [description]
|
* @return {[type]} [description]
|
||||||
*/
|
*/
|
||||||
Html5.canControlPlaybackRate = function(){
|
Html5.canControlPlaybackRate = function(){
|
||||||
var playbackRate = Lib.TEST_VID.playbackRate;
|
var playbackRate = Html5.TEST_VID.playbackRate;
|
||||||
Lib.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
|
Html5.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
|
||||||
return playbackRate !== Lib.TEST_VID.playbackRate;
|
return playbackRate !== Html5.TEST_VID.playbackRate;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -474,11 +493,11 @@ Html5.supportsNativeTextTracks = function() {
|
|||||||
// Browsers with numeric modes include IE10 and older (<=2013) samsung android models.
|
// Browsers with numeric modes include IE10 and older (<=2013) samsung android models.
|
||||||
// Firefox isn't playing nice either with modifying the mode
|
// Firefox isn't playing nice either with modifying the mode
|
||||||
// TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862
|
// TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862
|
||||||
supportsTextTracks = !!Lib.TEST_VID.textTracks;
|
supportsTextTracks = !!Html5.TEST_VID.textTracks;
|
||||||
if (supportsTextTracks && Lib.TEST_VID.textTracks.length > 0) {
|
if (supportsTextTracks && Html5.TEST_VID.textTracks.length > 0) {
|
||||||
supportsTextTracks = typeof Lib.TEST_VID.textTracks[0]['mode'] !== 'number';
|
supportsTextTracks = typeof Html5.TEST_VID.textTracks[0]['mode'] !== 'number';
|
||||||
}
|
}
|
||||||
if (supportsTextTracks && Lib.IS_FIREFOX) {
|
if (supportsTextTracks && browser.IS_FIREFOX) {
|
||||||
supportsTextTracks = false;
|
supportsTextTracks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,7 +521,7 @@ Html5.prototype['featuresPlaybackRate'] = Html5.canControlPlaybackRate();
|
|||||||
* In iOS, if you move a video element in the DOM, it breaks video playback.
|
* In iOS, if you move a video element in the DOM, it breaks video playback.
|
||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
Html5.prototype['movingMediaElementInDOM'] = !Lib.IS_IOS;
|
Html5.prototype['movingMediaElementInDOM'] = !browser.IS_IOS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the the tech's fullscreen resize support status.
|
* Set the the tech's fullscreen resize support status.
|
||||||
@ -530,12 +549,12 @@ const mp4RE = /^video\/mp4/i;
|
|||||||
|
|
||||||
Html5.patchCanPlayType = function() {
|
Html5.patchCanPlayType = function() {
|
||||||
// Android 4.0 and above can play HLS to some extent but it reports being unable to do so
|
// Android 4.0 and above can play HLS to some extent but it reports being unable to do so
|
||||||
if (Lib.ANDROID_VERSION >= 4.0) {
|
if (browser.ANDROID_VERSION >= 4.0) {
|
||||||
if (!canPlayType) {
|
if (!canPlayType) {
|
||||||
canPlayType = Lib.TEST_VID.constructor.prototype.canPlayType;
|
canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lib.TEST_VID.constructor.prototype.canPlayType = function(type) {
|
Html5.TEST_VID.constructor.prototype.canPlayType = function(type) {
|
||||||
if (type && mpegurlRE.test(type)) {
|
if (type && mpegurlRE.test(type)) {
|
||||||
return 'maybe';
|
return 'maybe';
|
||||||
}
|
}
|
||||||
@ -544,12 +563,12 @@ Html5.patchCanPlayType = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Override Android 2.2 and less canPlayType method which is broken
|
// Override Android 2.2 and less canPlayType method which is broken
|
||||||
if (Lib.IS_OLD_ANDROID) {
|
if (browser.IS_OLD_ANDROID) {
|
||||||
if (!canPlayType) {
|
if (!canPlayType) {
|
||||||
canPlayType = Lib.TEST_VID.constructor.prototype.canPlayType;
|
canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lib.TEST_VID.constructor.prototype.canPlayType = function(type){
|
Html5.TEST_VID.constructor.prototype.canPlayType = function(type){
|
||||||
if (type && mp4RE.test(type)) {
|
if (type && mp4RE.test(type)) {
|
||||||
return 'maybe';
|
return 'maybe';
|
||||||
}
|
}
|
||||||
@ -559,8 +578,8 @@ Html5.patchCanPlayType = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Html5.unpatchCanPlayType = function() {
|
Html5.unpatchCanPlayType = function() {
|
||||||
var r = Lib.TEST_VID.constructor.prototype.canPlayType;
|
var r = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||||
Lib.TEST_VID.constructor.prototype.canPlayType = canPlayType;
|
Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
|
||||||
canPlayType = null;
|
canPlayType = null;
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Component from '../component';
|
import Component from '../component';
|
||||||
import * as Lib from '../lib';
|
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
import toTitleCase from '../utils/to-title-case.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Media Loader is the component that decides which playback technology to load
|
* The Media Loader is the component that decides which playback technology to load
|
||||||
@ -17,7 +17,7 @@ class MediaLoader extends Component {
|
|||||||
// load the first supported playback technology.
|
// load the first supported playback technology.
|
||||||
if (!player.options_['sources'] || player.options_['sources'].length === 0) {
|
if (!player.options_['sources'] || player.options_['sources'].length === 0) {
|
||||||
for (let i=0, j=player.options_['techOrder']; i<j.length; i++) {
|
for (let i=0, j=player.options_['techOrder']; i<j.length; i++) {
|
||||||
let techName = Lib.capitalize(j[i]);
|
let techName = toTitleCase(j[i]);
|
||||||
let tech = Component.getComponent(techName);
|
let tech = Component.getComponent(techName);
|
||||||
|
|
||||||
// Check if the browser supports this technology
|
// Check if the browser supports this technology
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
import Component from '../component';
|
import Component from '../component';
|
||||||
import TextTrack from '../tracks/text-track';
|
import TextTrack from '../tracks/text-track';
|
||||||
import TextTrackList from '../tracks/text-track-list';
|
import TextTrackList from '../tracks/text-track-list';
|
||||||
import * as Lib from '../lib';
|
import * as Fn from '../utils/fn.js';
|
||||||
|
import log from '../utils/log.js';
|
||||||
|
import { createTimeRange } from '../utils/time-ranges.js';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
@ -104,7 +106,7 @@ class Tech extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trackProgress() {
|
trackProgress() {
|
||||||
this.progressInterval = this.setInterval(Lib.bind(this, function(){
|
this.progressInterval = this.setInterval(Fn.bind(this, function(){
|
||||||
// Don't trigger unless buffered amount is greater than last time
|
// Don't trigger unless buffered amount is greater than last time
|
||||||
|
|
||||||
let bufferedPercent = this.bufferedPercent();
|
let bufferedPercent = this.bufferedPercent();
|
||||||
@ -136,7 +138,7 @@ class Tech extends Component {
|
|||||||
let buffered = this.buffered();
|
let buffered = this.buffered();
|
||||||
|
|
||||||
if (!buffered || !buffered.length) {
|
if (!buffered || !buffered.length) {
|
||||||
buffered = Lib.createTimeRange(0,0);
|
buffered = createTimeRange(0,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i=0; i<buffered.length; i++){
|
for (var i=0; i<buffered.length; i++){
|
||||||
@ -219,7 +221,7 @@ class Tech extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initTextTrackListeners() {
|
initTextTrackListeners() {
|
||||||
let textTrackListChanges = Lib.bind(this, function() {
|
let textTrackListChanges = Fn.bind(this, function() {
|
||||||
this.trigger('texttrackchange');
|
this.trigger('texttrackchange');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -230,7 +232,7 @@ class Tech extends Component {
|
|||||||
tracks.addEventListener('removetrack', textTrackListChanges);
|
tracks.addEventListener('removetrack', textTrackListChanges);
|
||||||
tracks.addEventListener('addtrack', textTrackListChanges);
|
tracks.addEventListener('addtrack', textTrackListChanges);
|
||||||
|
|
||||||
this.on('dispose', Lib.bind(this, function() {
|
this.on('dispose', Fn.bind(this, function() {
|
||||||
tracks.removeEventListener('removetrack', textTrackListChanges);
|
tracks.removeEventListener('removetrack', textTrackListChanges);
|
||||||
tracks.removeEventListener('addtrack', textTrackListChanges);
|
tracks.removeEventListener('addtrack', textTrackListChanges);
|
||||||
}));
|
}));
|
||||||
@ -250,7 +252,7 @@ class Tech extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let textTracksChanges = function() {
|
let textTracksChanges = function() {
|
||||||
let updateDisplay = Lib.bind(this, function() {
|
let updateDisplay = Fn.bind(this, function() {
|
||||||
this.trigger('texttrackchange');
|
this.trigger('texttrackchange');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -267,7 +269,7 @@ class Tech extends Component {
|
|||||||
|
|
||||||
tracks.addEventListener('change', textTracksChanges);
|
tracks.addEventListener('change', textTracksChanges);
|
||||||
|
|
||||||
this.on('dispose', Lib.bind(this, function() {
|
this.on('dispose', Fn.bind(this, function() {
|
||||||
tracks.removeEventListener('change', textTracksChanges);
|
tracks.removeEventListener('change', textTracksChanges);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -443,7 +445,7 @@ Tech.withSourceHandlers = function(_Tech){
|
|||||||
if (_Tech.nativeSourceHandler) {
|
if (_Tech.nativeSourceHandler) {
|
||||||
sh = _Tech.nativeSourceHandler;
|
sh = _Tech.nativeSourceHandler;
|
||||||
} else {
|
} else {
|
||||||
Lib.log.error('No source hander found for the current source.');
|
log.error('No source hander found for the current source.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as Lib from '../lib';
|
import * as browser from '../utils/browser.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -14,7 +14,7 @@ import document from 'global/document';
|
|||||||
let TextTrackCueList = function(cues) {
|
let TextTrackCueList = function(cues) {
|
||||||
let list = this;
|
let list = this;
|
||||||
|
|
||||||
if (Lib.IS_IE8) {
|
if (browser.IS_IE8) {
|
||||||
list = document.createElement('custom');
|
list = document.createElement('custom');
|
||||||
|
|
||||||
for (let prop in TextTrackCueList.prototype) {
|
for (let prop in TextTrackCueList.prototype) {
|
||||||
@ -30,7 +30,7 @@ let TextTrackCueList = function(cues) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Lib.IS_IE8) {
|
if (browser.IS_IE8) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import Component from '../component';
|
|||||||
import Menu from '../menu/menu.js';
|
import Menu from '../menu/menu.js';
|
||||||
import MenuItem from '../menu/menu-item.js';
|
import MenuItem from '../menu/menu-item.js';
|
||||||
import MenuButton from '../menu/menu-button.js';
|
import MenuButton from '../menu/menu-button.js';
|
||||||
import * as Lib from '../lib.js';
|
import * as Fn from '../utils/fn.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
|
||||||
@ -31,20 +31,20 @@ class TextTrackDisplay extends Component {
|
|||||||
constructor(player, options, ready){
|
constructor(player, options, ready){
|
||||||
super(player, options, ready);
|
super(player, options, ready);
|
||||||
|
|
||||||
player.on('loadstart', Lib.bind(this, this.toggleDisplay));
|
player.on('loadstart', Fn.bind(this, this.toggleDisplay));
|
||||||
player.on('texttrackchange', Lib.bind(this, this.toggleDisplay));
|
player.on('texttrackchange', Fn.bind(this, this.toggleDisplay));
|
||||||
|
|
||||||
// This used to be called during player init, but was causing an error
|
// This used to be called during player init, but was causing an error
|
||||||
// if a track should show by default and the display hadn't loaded yet.
|
// if a track should show by default and the display hadn't loaded yet.
|
||||||
// Should probably be moved to an external track loader when we support
|
// Should probably be moved to an external track loader when we support
|
||||||
// tracks that don't need a display.
|
// tracks that don't need a display.
|
||||||
player.ready(Lib.bind(this, function() {
|
player.ready(Fn.bind(this, function() {
|
||||||
if (player.tech && player.tech['featuresNativeTextTracks']) {
|
if (player.tech && player.tech['featuresNativeTextTracks']) {
|
||||||
this.hide();
|
this.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.on('fullscreenchange', Lib.bind(this, this.updateDisplay));
|
player.on('fullscreenchange', Fn.bind(this, this.updateDisplay));
|
||||||
|
|
||||||
let tracks = player.options_['tracks'] || [];
|
let tracks = player.options_['tracks'] || [];
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import EventEmitter from '../event-emitter';
|
import EventEmitter from '../event-emitter';
|
||||||
import * as Lib from '../lib';
|
import * as Fn from '../utils/fn.js';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -18,7 +19,7 @@ import document from 'global/document';
|
|||||||
let TextTrackList = function(tracks) {
|
let TextTrackList = function(tracks) {
|
||||||
let list = this;
|
let list = this;
|
||||||
|
|
||||||
if (Lib.IS_IE8) {
|
if (browser.IS_IE8) {
|
||||||
list = document.createElement('custom');
|
list = document.createElement('custom');
|
||||||
|
|
||||||
for (let prop in TextTrackList.prototype) {
|
for (let prop in TextTrackList.prototype) {
|
||||||
@ -39,12 +40,12 @@ let TextTrackList = function(tracks) {
|
|||||||
list.addTrack_(tracks[i]);
|
list.addTrack_(tracks[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Lib.IS_IE8) {
|
if (browser.IS_IE8) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TextTrackList.prototype = Lib.obj.create(EventEmitter.prototype);
|
TextTrackList.prototype = Object.create(EventEmitter.prototype);
|
||||||
TextTrackList.prototype.constructor = TextTrackList;
|
TextTrackList.prototype.constructor = TextTrackList;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -73,7 +74,7 @@ TextTrackList.prototype.addTrack_ = function(track) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
track.addEventListener('modechange', Lib.bind(this, function() {
|
track.addEventListener('modechange', Fn.bind(this, function() {
|
||||||
this.trigger('change');
|
this.trigger('change');
|
||||||
}));
|
}));
|
||||||
this.tracks_.push(track);
|
this.tracks_.push(track);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Component from '../component';
|
import Component from '../component';
|
||||||
import * as Lib from '../lib';
|
import * as Events from '../utils/events.js';
|
||||||
import * as Events from '../events';
|
import * as Fn from '../utils/fn.js';
|
||||||
|
import log from '../utils/log.js';
|
||||||
import safeParseTuple from 'safe-json-parse/tuple';
|
import safeParseTuple from 'safe-json-parse/tuple';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
|
||||||
@ -10,12 +11,12 @@ class TextTrackSettings extends Component {
|
|||||||
super(player, options);
|
super(player, options);
|
||||||
this.hide();
|
this.hide();
|
||||||
|
|
||||||
Events.on(this.el().querySelector('.vjs-done-button'), 'click', Lib.bind(this, function() {
|
Events.on(this.el().querySelector('.vjs-done-button'), 'click', Fn.bind(this, function() {
|
||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
this.hide();
|
this.hide();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Events.on(this.el().querySelector('.vjs-default-button'), 'click', Lib.bind(this, function() {
|
Events.on(this.el().querySelector('.vjs-default-button'), 'click', Fn.bind(this, function() {
|
||||||
this.el().querySelector('.vjs-fg-color > select').selectedIndex = 0;
|
this.el().querySelector('.vjs-fg-color > select').selectedIndex = 0;
|
||||||
this.el().querySelector('.vjs-bg-color > select').selectedIndex = 0;
|
this.el().querySelector('.vjs-bg-color > select').selectedIndex = 0;
|
||||||
this.el().querySelector('.window-color > select').selectedIndex = 0;
|
this.el().querySelector('.window-color > select').selectedIndex = 0;
|
||||||
@ -28,15 +29,15 @@ class TextTrackSettings extends Component {
|
|||||||
this.updateDisplay();
|
this.updateDisplay();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Events.on(this.el().querySelector('.vjs-fg-color > select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
Events.on(this.el().querySelector('.vjs-bg-color > select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
Events.on(this.el().querySelector('.window-color > select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
Events.on(this.el().querySelector('.vjs-text-opacity > select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
Events.on(this.el().querySelector('.vjs-bg-opacity > select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
Events.on(this.el().querySelector('.vjs-window-opacity > select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
Events.on(this.el().querySelector('.vjs-font-percent select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
Events.on(this.el().querySelector('.vjs-edge-style select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
Events.on(this.el().querySelector('.vjs-font-family select'), 'change', Lib.bind(this, this.updateDisplay));
|
Events.on(this.el().querySelector('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||||
|
|
||||||
if (player.options()['persistTextTrackSettings']) {
|
if (player.options()['persistTextTrackSettings']) {
|
||||||
this.restoreSettings();
|
this.restoreSettings();
|
||||||
@ -107,7 +108,7 @@ class TextTrackSettings extends Component {
|
|||||||
let [err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings'));
|
let [err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings'));
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
Lib.log.error(err);
|
log.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values) {
|
if (values) {
|
||||||
@ -122,7 +123,7 @@ class TextTrackSettings extends Component {
|
|||||||
|
|
||||||
let values = this.getValues();
|
let values = this.getValues();
|
||||||
try {
|
try {
|
||||||
if (!Lib.isEmpty(values)) {
|
if (Object.getOwnPropertyNames(values).length > 0) {
|
||||||
window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values));
|
window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values));
|
||||||
} else {
|
} else {
|
||||||
window.localStorage.removeItem('vjs-text-track-settings');
|
window.localStorage.removeItem('vjs-text-track-settings');
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import TextTrackCueList from './text-track-cue-list';
|
import TextTrackCueList from './text-track-cue-list';
|
||||||
import * as Lib from '../lib';
|
import * as Fn from '../utils/fn.js';
|
||||||
|
import * as Guid from '../utils/guid.js';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
import * as TextTrackEnum from './text-track-enums';
|
import * as TextTrackEnum from './text-track-enums';
|
||||||
|
import log from '../utils/log.js';
|
||||||
import EventEmitter from '../event-emitter';
|
import EventEmitter from '../event-emitter';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
@ -34,7 +37,7 @@ let TextTrack = function(options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tt = this;
|
let tt = this;
|
||||||
if (Lib.IS_IE8) {
|
if (browser.IS_IE8) {
|
||||||
tt = document.createElement('custom');
|
tt = document.createElement('custom');
|
||||||
|
|
||||||
for (let prop in TextTrack.prototype) {
|
for (let prop in TextTrack.prototype) {
|
||||||
@ -48,7 +51,7 @@ let TextTrack = function(options={}) {
|
|||||||
let kind = TextTrackEnum.TextTrackKind[options['kind']] || 'subtitles';
|
let kind = TextTrackEnum.TextTrackKind[options['kind']] || 'subtitles';
|
||||||
let label = options['label'] || '';
|
let label = options['label'] || '';
|
||||||
let language = options['language'] || options['srclang'] || '';
|
let language = options['language'] || options['srclang'] || '';
|
||||||
let id = options['id'] || 'vjs_text_track_' + Lib.guid++;
|
let id = options['id'] || 'vjs_text_track_' + Guid.newGUID();
|
||||||
|
|
||||||
if (kind === 'metadata' || kind === 'chapters') {
|
if (kind === 'metadata' || kind === 'chapters') {
|
||||||
mode = 'hidden';
|
mode = 'hidden';
|
||||||
@ -61,7 +64,7 @@ let TextTrack = function(options={}) {
|
|||||||
let activeCues = new TextTrackCueList(tt.activeCues_);
|
let activeCues = new TextTrackCueList(tt.activeCues_);
|
||||||
|
|
||||||
let changed = false;
|
let changed = false;
|
||||||
let timeupdateHandler = Lib.bind(tt, function() {
|
let timeupdateHandler = Fn.bind(tt, function() {
|
||||||
this['activeCues'];
|
this['activeCues'];
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this['trigger']('cuechange');
|
this['trigger']('cuechange');
|
||||||
@ -175,12 +178,12 @@ let TextTrack = function(options={}) {
|
|||||||
tt.loaded_ = true;
|
tt.loaded_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Lib.IS_IE8) {
|
if (browser.IS_IE8) {
|
||||||
return tt;
|
return tt;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TextTrack.prototype = Lib.obj.create(EventEmitter.prototype);
|
TextTrack.prototype = Object.create(EventEmitter.prototype);
|
||||||
TextTrack.prototype.constructor = TextTrack;
|
TextTrack.prototype.constructor = TextTrack;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -238,7 +241,7 @@ var parseCues = function(srcContent, track) {
|
|||||||
track.addCue(cue);
|
track.addCue(cue);
|
||||||
};
|
};
|
||||||
parser['onparsingerror'] = function(error) {
|
parser['onparsingerror'] = function(error) {
|
||||||
Lib.log.error(error);
|
log.error(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
parser['parse'](srcContent);
|
parser['parse'](srcContent);
|
||||||
@ -246,9 +249,9 @@ var parseCues = function(srcContent, track) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var loadTrack = function(src, track) {
|
var loadTrack = function(src, track) {
|
||||||
XHR(src, Lib.bind(this, function(err, response, responseBody){
|
XHR(src, Fn.bind(this, function(err, response, responseBody){
|
||||||
if (err) {
|
if (err) {
|
||||||
return Lib.log.error(err);
|
return log.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
import { obj } from './lib';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility functions namespace
|
|
||||||
* @namespace
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
var util = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge two options objects, recursively merging any plain object properties as
|
|
||||||
* well. Previously `deepMerge`
|
|
||||||
*
|
|
||||||
* @param {Object} obj1 Object to override values in
|
|
||||||
* @param {Object} obj2 Overriding object
|
|
||||||
* @return {Object} New object -- obj1 and obj2 will be untouched
|
|
||||||
*/
|
|
||||||
var mergeOptions = function(obj1, obj2){
|
|
||||||
var key, val1, val2;
|
|
||||||
|
|
||||||
// make a copy of obj1 so we're not overwriting original values.
|
|
||||||
// like prototype.options_ and all sub options objects
|
|
||||||
obj1 = obj.copy(obj1);
|
|
||||||
|
|
||||||
for (key in obj2){
|
|
||||||
if (obj2.hasOwnProperty(key)) {
|
|
||||||
val1 = obj1[key];
|
|
||||||
val2 = obj2[key];
|
|
||||||
|
|
||||||
// Check if both properties are pure objects and do a deep merge if so
|
|
||||||
if (obj.isPlain(val1) && obj.isPlain(val2)) {
|
|
||||||
obj1[key] = mergeOptions(val1, val2);
|
|
||||||
} else {
|
|
||||||
obj1[key] = obj2[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj1;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { mergeOptions };
|
|
53
src/js/utils/browser.js
Normal file
53
src/js/utils/browser.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import document from 'global/document';
|
||||||
|
import window from 'global/window';
|
||||||
|
|
||||||
|
const USER_AGENT = window.navigator.userAgent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device is an iPhone
|
||||||
|
* @type {Boolean}
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const IS_IPHONE = (/iPhone/i).test(USER_AGENT);
|
||||||
|
export const IS_IPAD = (/iPad/i).test(USER_AGENT);
|
||||||
|
export const IS_IPOD = (/iPod/i).test(USER_AGENT);
|
||||||
|
export const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
|
||||||
|
|
||||||
|
export const IOS_VERSION = (function(){
|
||||||
|
var match = USER_AGENT.match(/OS (\d+)_/i);
|
||||||
|
if (match && match[1]) { return match[1]; }
|
||||||
|
})();
|
||||||
|
|
||||||
|
export const IS_ANDROID = (/Android/i).test(USER_AGENT);
|
||||||
|
export const ANDROID_VERSION = (function() {
|
||||||
|
// This matches Android Major.Minor.Patch versions
|
||||||
|
// ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
|
||||||
|
var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
|
||||||
|
major,
|
||||||
|
minor;
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
major = match[1] && parseFloat(match[1]);
|
||||||
|
minor = match[2] && parseFloat(match[2]);
|
||||||
|
|
||||||
|
if (major && minor) {
|
||||||
|
return parseFloat(match[1] + '.' + match[2]);
|
||||||
|
} else if (major) {
|
||||||
|
return major;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
|
||||||
|
export const IS_OLD_ANDROID = IS_ANDROID && (/webkit/i).test(USER_AGENT) && ANDROID_VERSION < 2.3;
|
||||||
|
|
||||||
|
export const IS_FIREFOX = (/Firefox/i).test(USER_AGENT);
|
||||||
|
export const IS_CHROME = (/Chrome/i).test(USER_AGENT);
|
||||||
|
export const IS_IE8 = (/MSIE\s8\.0/).test(USER_AGENT);
|
||||||
|
|
||||||
|
export const TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
|
||||||
|
export const BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in document.createElement('video').style;
|
307
src/js/utils/dom.js
Normal file
307
src/js/utils/dom.js
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
import document from 'global/document';
|
||||||
|
import window from 'global/window';
|
||||||
|
import * as Guid from './guid.js';
|
||||||
|
import roundFloat from './round-float.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for document.getElementById()
|
||||||
|
* Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
|
||||||
|
* @param {String} id Element ID
|
||||||
|
* @return {Element} Element with supplied ID
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const el = function(id){
|
||||||
|
if (id.indexOf('#') === 0) {
|
||||||
|
id = id.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.getElementById(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an element and applies properties.
|
||||||
|
* @param {String=} tagName Name of tag to be created.
|
||||||
|
* @param {Object=} properties Element properties to be applied.
|
||||||
|
* @return {Element}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const createEl = function(tagName='div', properties={}){
|
||||||
|
let el = document.createElement(tagName);
|
||||||
|
|
||||||
|
Object.getOwnPropertyNames(properties).forEach(function(propName){
|
||||||
|
let val = 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, val);
|
||||||
|
} else {
|
||||||
|
el[propName] = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert an element as the first child node of another
|
||||||
|
* @param {Element} child Element to insert
|
||||||
|
* @param {[type]} parent Element to insert child into
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const insertFirst = function(child, parent){
|
||||||
|
if (parent.firstChild) {
|
||||||
|
parent.insertBefore(child, parent.firstChild);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(child);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element Data Store. Allows for binding data to an element without putting it directly on the element.
|
||||||
|
* Ex. Event listeners are stored here.
|
||||||
|
* (also from jsninja.com, slightly modified and updated for closure compiler)
|
||||||
|
* @type {Object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const cache = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique attribute name to store an element's guid in
|
||||||
|
* @type {String}
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const expando = 'vdata' + (new Date()).getTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cache object where data for an element is stored
|
||||||
|
* @param {Element} el Element to store data for.
|
||||||
|
* @return {Object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const getData = function(el){
|
||||||
|
var id = el[expando];
|
||||||
|
if (!id) {
|
||||||
|
id = el[expando] = Guid.newGUID();
|
||||||
|
}
|
||||||
|
if (!cache[id]) {
|
||||||
|
cache[id] = {};
|
||||||
|
}
|
||||||
|
return cache[id];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not an element has cached data
|
||||||
|
* @param {Element} el A dom element
|
||||||
|
* @return {Boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const hasData = function(el){
|
||||||
|
const id = el[expando];
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!Object.getOwnPropertyNames(cache[id]).length;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete data for the element from the cache and the guid attr from getElementById
|
||||||
|
* @param {Element} el Remove data for an element
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const removeData = function(el){
|
||||||
|
var id = el[expando];
|
||||||
|
if (!id) { return; }
|
||||||
|
// Remove all stored data
|
||||||
|
// Changed to = null
|
||||||
|
// http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
|
||||||
|
// cache[id] = null;
|
||||||
|
delete cache[id];
|
||||||
|
|
||||||
|
// Remove the expando property from the DOM node
|
||||||
|
try {
|
||||||
|
delete el[expando];
|
||||||
|
} catch(e) {
|
||||||
|
if (el.removeAttribute) {
|
||||||
|
el.removeAttribute(expando);
|
||||||
|
} else {
|
||||||
|
// IE doesn't appear to support removeAttribute on the document element
|
||||||
|
el[expando] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an element has a CSS class
|
||||||
|
* @param {Element} element Element to check
|
||||||
|
* @param {String} classToCheck Classname to check
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const hasClass = function(element, classToCheck){
|
||||||
|
return ((' ' + element.className + ' ').indexOf(' ' + classToCheck + ' ') !== -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a CSS class name to an element
|
||||||
|
* @param {Element} element Element to add class name to
|
||||||
|
* @param {String} classToAdd Classname to add
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const addClass = function(element, classToAdd){
|
||||||
|
if (!hasClass(element, classToAdd)) {
|
||||||
|
element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a CSS class name from an element
|
||||||
|
* @param {Element} element Element to remove from class name
|
||||||
|
* @param {String} classToAdd Classname to remove
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const removeClass = function(element, classToRemove){
|
||||||
|
if (!hasClass(element, classToRemove)) {return;}
|
||||||
|
|
||||||
|
let classNames = element.className.split(' ');
|
||||||
|
|
||||||
|
// no arr.indexOf in ie8, and we don't want to add a big shim
|
||||||
|
for (let i = classNames.length - 1; i >= 0; i--) {
|
||||||
|
if (classNames[i] === classToRemove) {
|
||||||
|
classNames.splice(i,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.className = classNames.join(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply attributes to an HTML element.
|
||||||
|
* @param {Element} el Target element.
|
||||||
|
* @param {Object=} attributes Element attributes to be applied.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const setElementAttributes = function(el, attributes){
|
||||||
|
Object.getOwnPropertyNames(attributes).forEach(function(attrName){
|
||||||
|
let attrValue = attributes[attrName];
|
||||||
|
|
||||||
|
if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
|
||||||
|
el.removeAttribute(attrName);
|
||||||
|
} else {
|
||||||
|
el.setAttribute(attrName, (attrValue === true ? '' : attrValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an element's attribute values, as defined on the HTML tag
|
||||||
|
* Attributes are not the same as properties. They're defined on the tag
|
||||||
|
* or with setAttribute (which shouldn't be used with HTML)
|
||||||
|
* This will return true or false for boolean attributes.
|
||||||
|
* @param {Element} tag Element from which to get tag attributes
|
||||||
|
* @return {Object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const getElementAttributes = function(tag){
|
||||||
|
var obj, knownBooleans, attrs, attrName, attrVal;
|
||||||
|
|
||||||
|
obj = {};
|
||||||
|
|
||||||
|
// known boolean attributes
|
||||||
|
// we can check for matching boolean properties, but older browsers
|
||||||
|
// won't know about HTML5 boolean attributes that we still read from
|
||||||
|
knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
|
||||||
|
|
||||||
|
if (tag && tag.attributes && tag.attributes.length > 0) {
|
||||||
|
attrs = tag.attributes;
|
||||||
|
|
||||||
|
for (var i = attrs.length - 1; i >= 0; i--) {
|
||||||
|
attrName = attrs[i].name;
|
||||||
|
attrVal = attrs[i].value;
|
||||||
|
|
||||||
|
// check for known booleans
|
||||||
|
// the matching element property will return a value for typeof
|
||||||
|
if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {
|
||||||
|
// the value of an included boolean attribute is typically an empty
|
||||||
|
// string ('') which would equal false if we just check for a false value.
|
||||||
|
// we also don't want support bad code like autoplay='false'
|
||||||
|
attrVal = (attrVal !== null) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[attrName] = attrVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the computed style value for an element
|
||||||
|
* From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
|
||||||
|
* @param {Element} el Element to get style value for
|
||||||
|
* @param {String} strCssRule Style name
|
||||||
|
* @return {String} Style value
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const 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
|
||||||
|
let upperCasedRule = strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1);
|
||||||
|
strValue = el[`client${upperCasedRule}`] + 'px';
|
||||||
|
}
|
||||||
|
return strValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attempt to block the ability to select text while dragging controls
|
||||||
|
export const blockTextSelection = function(){
|
||||||
|
document.body.focus();
|
||||||
|
document.onselectstart = function () { return false; };
|
||||||
|
};
|
||||||
|
// Turn off text selection blocking
|
||||||
|
export const unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };
|
||||||
|
|
||||||
|
// Offset Left
|
||||||
|
// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
|
||||||
|
export const findPosition = function(el) {
|
||||||
|
let box;
|
||||||
|
|
||||||
|
if (el.getBoundingClientRect && el.parentNode) {
|
||||||
|
box = el.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!box) {
|
||||||
|
return {
|
||||||
|
left: 0,
|
||||||
|
top: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const docEl = document.documentElement;
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
const clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||||
|
const scrollLeft = window.pageXOffset || body.scrollLeft;
|
||||||
|
const left = box.left + scrollLeft - clientLeft;
|
||||||
|
|
||||||
|
const clientTop = docEl.clientTop || body.clientTop || 0;
|
||||||
|
const scrollTop = window.pageYOffset || body.scrollTop;
|
||||||
|
const top = box.top + scrollTop - clientTop;
|
||||||
|
|
||||||
|
// Android sometimes returns slightly off decimal values, so need to round
|
||||||
|
return {
|
||||||
|
left: roundFloat(left),
|
||||||
|
top: roundFloat(top)
|
||||||
|
};
|
||||||
|
};
|
@ -5,17 +5,202 @@
|
|||||||
* robust as jquery's, so there's probably some differences.
|
* robust as jquery's, so there's probably some differences.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Lib from './lib';
|
import * as Dom from './dom.js';
|
||||||
|
import * as Guid from './guid.js';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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|Array} type Type of event to bind to.
|
||||||
|
* @param {Function} fn Event listener.
|
||||||
|
*/
|
||||||
|
export function on(elem, type, fn){
|
||||||
|
if (Array.isArray(type)) {
|
||||||
|
return _handleMultipleEvents(on, elem, type, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = Dom.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 = Guid.newGUID();
|
||||||
|
|
||||||
|
data.handlers[type].push(fn);
|
||||||
|
|
||||||
|
if (!data.dispatcher) {
|
||||||
|
data.disabled = false;
|
||||||
|
|
||||||
|
data.dispatcher = function (event){
|
||||||
|
|
||||||
|
if (data.disabled) return;
|
||||||
|
event = 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 (elem.addEventListener) {
|
||||||
|
elem.addEventListener(type, data.dispatcher, false);
|
||||||
|
} else if (elem.attachEvent) {
|
||||||
|
elem.attachEvent('on' + type, data.dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes event listeners from an element
|
||||||
|
* @param {Element|Object} elem Object to remove listeners from
|
||||||
|
* @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element.
|
||||||
|
* @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type.
|
||||||
|
*/
|
||||||
|
export function off(elem, type, fn) {
|
||||||
|
// Don't want to add a cache object through getData if not needed
|
||||||
|
if (!Dom.hasData(elem)) return;
|
||||||
|
|
||||||
|
let data = Dom.getData(elem);
|
||||||
|
|
||||||
|
// If no events exist, nothing to unbind
|
||||||
|
if (!data.handlers) { return; }
|
||||||
|
|
||||||
|
if (Array.isArray(type)) {
|
||||||
|
return _handleMultipleEvents(off, elem, type, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function
|
||||||
|
var removeType = function(t){
|
||||||
|
data.handlers[t] = [];
|
||||||
|
_cleanUpEvents(elem,t);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Are we removing all bound events?
|
||||||
|
if (!type) {
|
||||||
|
for (let 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 (let n = 0; n < handlers.length; n++) {
|
||||||
|
if (handlers[n].guid === fn.guid) {
|
||||||
|
handlers.splice(n--, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanUpEvents(elem, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger an event for an element
|
||||||
|
* @param {Element|Object} elem Element to trigger an event on
|
||||||
|
* @param {Event|Object|String} event A string (the type) or an event object with a type attribute
|
||||||
|
*/
|
||||||
|
export function trigger(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 = (Dom.hasData(elem)) ? Dom.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 = fixEvent(event);
|
||||||
|
|
||||||
|
// If the passed element has a dispatcher, executes the established handlers.
|
||||||
|
if (elemData.dispatcher) {
|
||||||
|
elemData.dispatcher.call(elem, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unless explicitly stopped or the event does not bubble (e.g. media events)
|
||||||
|
// recursively calls this function to bubble the event up the DOM.
|
||||||
|
if (parent && !event.isPropagationStopped() && event.bubbles !== false) {
|
||||||
|
trigger(parent, event);
|
||||||
|
|
||||||
|
// If at the top of the DOM, triggers the default action unless disabled.
|
||||||
|
} else if (!parent && !event.defaultPrevented) {
|
||||||
|
var targetData = Dom.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.defaultPrevented;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a listener only once for an event
|
||||||
|
* @param {Element|Object} elem Element or object to
|
||||||
|
* @param {String|Array} type
|
||||||
|
* @param {Function} fn
|
||||||
|
*/
|
||||||
|
export function one(elem, type, fn) {
|
||||||
|
if (Array.isArray(type)) {
|
||||||
|
return _handleMultipleEvents(one, elem, type, fn);
|
||||||
|
}
|
||||||
|
var func = function(){
|
||||||
|
off(elem, type, func);
|
||||||
|
fn.apply(this, arguments);
|
||||||
|
};
|
||||||
|
// copy the guid to the new function so it can removed using the original function's ID
|
||||||
|
func.guid = fn.guid = fn.guid || Guid.newGUID();
|
||||||
|
on(elem, type, func);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fix a native event to have standard property values
|
* Fix a native event to have standard property values
|
||||||
* @param {Object} event Event object to fix
|
* @param {Object} event Event object to fix
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
var fixEvent = function(event) {
|
export function fixEvent(event) {
|
||||||
|
|
||||||
function returnTrue() { return true; }
|
function returnTrue() { return true; }
|
||||||
function returnFalse() { return false; }
|
function returnFalse() { return false; }
|
||||||
@ -115,122 +300,7 @@ var fixEvent = function(event) {
|
|||||||
|
|
||||||
// Returns fixed-up instance
|
// Returns fixed-up instance
|
||||||
return event;
|
return event;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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|Array} type Type of event to bind to.
|
|
||||||
* @param {Function} fn Event listener.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var on = function(elem, type, fn){
|
|
||||||
if (Lib.obj.isArray(type)) {
|
|
||||||
return _handleMultipleEvents(on, elem, type, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = Lib.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 = Lib.guid++;
|
|
||||||
|
|
||||||
data.handlers[type].push(fn);
|
|
||||||
|
|
||||||
if (!data.dispatcher) {
|
|
||||||
data.disabled = false;
|
|
||||||
|
|
||||||
data.dispatcher = function (event){
|
|
||||||
|
|
||||||
if (data.disabled) return;
|
|
||||||
event = 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 (elem.addEventListener) {
|
|
||||||
elem.addEventListener(type, data.dispatcher, false);
|
|
||||||
} else if (elem.attachEvent) {
|
|
||||||
elem.attachEvent('on' + type, data.dispatcher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes event listeners from an element
|
|
||||||
* @param {Element|Object} elem Object to remove listeners from
|
|
||||||
* @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element.
|
|
||||||
* @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var off = function(elem, type, fn) {
|
|
||||||
// Don't want to add a cache object through getData if not needed
|
|
||||||
if (!Lib.hasData(elem)) return;
|
|
||||||
|
|
||||||
let data = Lib.getData(elem);
|
|
||||||
|
|
||||||
// If no events exist, nothing to unbind
|
|
||||||
if (!data.handlers) { return; }
|
|
||||||
|
|
||||||
if (Lib.obj.isArray(type)) {
|
|
||||||
return _handleMultipleEvents(off, elem, type, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility function
|
|
||||||
var removeType = function(t){
|
|
||||||
data.handlers[t] = [];
|
|
||||||
cleanUpEvents(elem,t);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Are we removing all bound events?
|
|
||||||
if (!type) {
|
|
||||||
for (let 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 (let n = 0; n < handlers.length; n++) {
|
|
||||||
if (handlers[n].guid === fn.guid) {
|
|
||||||
handlers.splice(n--, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanUpEvents(elem, type);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up the listener cache and dispatchers
|
* Clean up the listener cache and dispatchers
|
||||||
@ -238,8 +308,8 @@ var off = function(elem, type, fn) {
|
|||||||
* @param {String} type Type of event to clean up
|
* @param {String} type Type of event to clean up
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
var cleanUpEvents = function(elem, type) {
|
function _cleanUpEvents(elem, type) {
|
||||||
var data = Lib.getData(elem);
|
var data = Dom.getData(elem);
|
||||||
|
|
||||||
// Remove the events of a particular type if there are none left
|
// Remove the events of a particular type if there are none left
|
||||||
if (data.handlers[type].length === 0) {
|
if (data.handlers[type].length === 0) {
|
||||||
@ -256,7 +326,7 @@ var cleanUpEvents = function(elem, type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the events object if there are no types left
|
// Remove the events object if there are no types left
|
||||||
if (Lib.isEmpty(data.handlers)) {
|
if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
|
||||||
delete data.handlers;
|
delete data.handlers;
|
||||||
delete data.dispatcher;
|
delete data.dispatcher;
|
||||||
delete data.disabled;
|
delete data.disabled;
|
||||||
@ -267,83 +337,10 @@ var cleanUpEvents = function(elem, type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally remove the expando if there is no data left
|
// Finally remove the expando if there is no data left
|
||||||
if (Lib.isEmpty(data)) {
|
if (Object.getOwnPropertyNames(data).length <= 0) {
|
||||||
Lib.removeData(elem);
|
Dom.removeData(elem);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger an event for an element
|
|
||||||
* @param {Element|Object} elem Element to trigger an event on
|
|
||||||
* @param {Event|Object|String} event A string (the type) or an event object with a type attribute
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var 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 = (Lib.hasData(elem)) ? Lib.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 = fixEvent(event);
|
|
||||||
|
|
||||||
// If the passed element has a dispatcher, executes the established handlers.
|
|
||||||
if (elemData.dispatcher) {
|
|
||||||
elemData.dispatcher.call(elem, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unless explicitly stopped or the event does not bubble (e.g. media events)
|
|
||||||
// recursively calls this function to bubble the event up the DOM.
|
|
||||||
if (parent && !event.isPropagationStopped() && event.bubbles !== false) {
|
|
||||||
trigger(parent, event);
|
|
||||||
|
|
||||||
// If at the top of the DOM, triggers the default action unless disabled.
|
|
||||||
} else if (!parent && !event.defaultPrevented) {
|
|
||||||
var targetData = Lib.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.defaultPrevented;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger a listener only once for an event
|
|
||||||
* @param {Element|Object} elem Element or object to
|
|
||||||
* @param {String|Array} type
|
|
||||||
* @param {Function} fn
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var one = function(elem, type, fn) {
|
|
||||||
if (Lib.obj.isArray(type)) {
|
|
||||||
return _handleMultipleEvents(one, elem, type, fn);
|
|
||||||
}
|
|
||||||
var func = function(){
|
|
||||||
off(elem, type, func);
|
|
||||||
fn.apply(this, arguments);
|
|
||||||
};
|
|
||||||
// copy the guid to the new function so it can removed using the original function's ID
|
|
||||||
func.guid = fn.guid = fn.guid || Lib.guid++;
|
|
||||||
on(elem, type, func);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loops through an array of event types and calls the requested method for each type.
|
* Loops through an array of event types and calls the requested method for each type.
|
||||||
@ -353,10 +350,9 @@ var one = function(elem, type, fn) {
|
|||||||
* @param {Function} callback Event listener.
|
* @param {Function} callback Event listener.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function _handleMultipleEvents(fn, elem, type, callback) {
|
function _handleMultipleEvents(fn, elem, types, callback) {
|
||||||
Lib.arr.forEach(type, function(type) {
|
types.forEach(function(type) {
|
||||||
fn(elem, type, callback); //Call the event method for each one of the types
|
//Call the event method for each one of the types
|
||||||
|
fn(elem, type, callback);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { on, off, cleanUpEvents, fixEvent, one, trigger };
|
|
30
src/js/utils/fn.js
Normal file
30
src/js/utils/fn.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { newGUID } from './guid.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind (a.k.a proxy or Context). A simple method for changing the context of a function
|
||||||
|
It also stores a unique id on the function so it can be easily removed from events
|
||||||
|
* @param {*} context The object to bind as scope
|
||||||
|
* @param {Function} fn The function to be bound to a scope
|
||||||
|
* @param {Number=} uid An optional unique ID for the function to be set
|
||||||
|
* @return {Function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const bind = function(context, fn, uid) {
|
||||||
|
// Make sure the function has a unique ID
|
||||||
|
if (!fn.guid) { fn.guid = newGUID(); }
|
||||||
|
|
||||||
|
// Create the new function that changes the context
|
||||||
|
let 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;
|
||||||
|
};
|
37
src/js/utils/format-time.js
Normal file
37
src/js/utils/format-time.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Format seconds as a time string, H:MM:SS or M:SS
|
||||||
|
* Supplying a guide (in seconds) will force a number of leading zeros
|
||||||
|
* to cover the length of the guide
|
||||||
|
* @param {Number} seconds Number of seconds to be turned into a string
|
||||||
|
* @param {Number} guide Number (in seconds) to model the string after
|
||||||
|
* @return {String} Time formatted as H:MM:SS or M:SS
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function formatTime(seconds, guide=seconds) {
|
||||||
|
let s = Math.floor(seconds % 60);
|
||||||
|
let m = Math.floor(seconds / 60 % 60);
|
||||||
|
let h = Math.floor(seconds / 3600);
|
||||||
|
const gm = Math.floor(guide / 60 % 60);
|
||||||
|
const gh = Math.floor(guide / 3600);
|
||||||
|
|
||||||
|
// handle invalid times
|
||||||
|
if (isNaN(seconds) || seconds === Infinity) {
|
||||||
|
// '-' is false for all relational operators (e.g. <, >=) so this setting
|
||||||
|
// will add the minimum number of fields specified by the guide
|
||||||
|
h = m = s = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we need to show hours
|
||||||
|
h = (h > 0 || gh > 0) ? h + ':' : '';
|
||||||
|
|
||||||
|
// If hours are showing, we may need to add a leading zero.
|
||||||
|
// Always show at least one digit of minutes.
|
||||||
|
m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
|
||||||
|
|
||||||
|
// Check if leading zero is need for seconds
|
||||||
|
s = (s < 10) ? '0' + s : s;
|
||||||
|
|
||||||
|
return h + m + s;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default formatTime;
|
13
src/js/utils/guid.js
Normal file
13
src/js/utils/guid.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Unique ID for an element or function
|
||||||
|
* @type {Number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
let _guid = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next unique ID
|
||||||
|
*/
|
||||||
|
export function newGUID() {
|
||||||
|
return _guid++;
|
||||||
|
}
|
75
src/js/utils/log.js
Normal file
75
src/js/utils/log.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import window from 'global/window';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log plain debug messages
|
||||||
|
*/
|
||||||
|
const log = function(){
|
||||||
|
_logType(null, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep a history of log messages
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
log.history = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log error messages
|
||||||
|
*/
|
||||||
|
log.error = function(){
|
||||||
|
_logType('error', arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log warning messages
|
||||||
|
*/
|
||||||
|
log.warn = function(){
|
||||||
|
_logType('warn', arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log messages to the console and history based on the type of message
|
||||||
|
*
|
||||||
|
* @param {String} type The type of message, or `null` for `log`
|
||||||
|
* @param {[type]} args The args to be passed to the log
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _logType(type, args){
|
||||||
|
// convert args to an array to get array functions
|
||||||
|
let argsArray = Array.prototype.slice.call(args);
|
||||||
|
// if there's no console then don't try to output messages
|
||||||
|
// they will still be stored in log.history
|
||||||
|
// Was setting these once outside of this function, but containing them
|
||||||
|
// in the function makes it easier to test cases where console doesn't exist
|
||||||
|
let noop = function(){};
|
||||||
|
|
||||||
|
let console = window['console'] || {
|
||||||
|
'log': noop,
|
||||||
|
'warn': noop,
|
||||||
|
'error': noop
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
// add the type to the front of the message
|
||||||
|
argsArray.unshift(type.toUpperCase()+':');
|
||||||
|
} else {
|
||||||
|
// default to log with no prefix
|
||||||
|
type = 'log';
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to history
|
||||||
|
log.history.push(argsArray);
|
||||||
|
|
||||||
|
// add console prefix after adding to history
|
||||||
|
argsArray.unshift('VIDEOJS:');
|
||||||
|
|
||||||
|
// call appropriate log function
|
||||||
|
if (console[type].apply) {
|
||||||
|
console[type].apply(console, argsArray);
|
||||||
|
} else {
|
||||||
|
// ie8 doesn't allow error.apply, but it will just join() the array anyway
|
||||||
|
console[type](argsArray.join(' '));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default log;
|
33
src/js/utils/merge-options.js
Normal file
33
src/js/utils/merge-options.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import merge from 'lodash.merge';
|
||||||
|
import isPlainObject from 'lodash.isplainobject';
|
||||||
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two options objects, recursively merging any plain object properties as
|
||||||
|
* well. Previously `deepMerge`
|
||||||
|
*
|
||||||
|
* @param {Object} obj1 Object to override values in
|
||||||
|
* @param {Object} obj2 Overriding object
|
||||||
|
* @return {Object} New object -- obj1 and obj2 will be untouched
|
||||||
|
*/
|
||||||
|
export default function mergeOptions(obj1){
|
||||||
|
// Copy to ensure we're not modifying the defaults somewhere
|
||||||
|
obj1 = cloneDeep(obj1, function(value) {
|
||||||
|
if (!isPlainObject(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allow for infinite additional object args to merge
|
||||||
|
Array.prototype.slice.call(arguments, 1).forEach(function(argObj){
|
||||||
|
// Recursively merge only plain objects
|
||||||
|
// All other values will be directly copied
|
||||||
|
merge(obj1, argObj, function(a, b) {
|
||||||
|
if (!isPlainObject(a) || !isPlainObject(b)) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return obj1;
|
||||||
|
}
|
12
src/js/utils/round-float.js
Normal file
12
src/js/utils/round-float.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Should round off a number to a decimal place
|
||||||
|
* @param {Number} num Number to round
|
||||||
|
* @param {Number} dec Number of decimal places to round to
|
||||||
|
* @return {Number} Rounded number
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const roundFloat = function(num, dec=0) {
|
||||||
|
return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default roundFloat;
|
17
src/js/utils/time-ranges.js
Normal file
17
src/js/utils/time-ranges.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Should create a fake TimeRange object
|
||||||
|
* Mimics an HTML5 time range instance, which has functions that
|
||||||
|
* return the start and end times for a range
|
||||||
|
* TimeRanges are returned by the buffered() method
|
||||||
|
* @param {Number} start Start time in seconds
|
||||||
|
* @param {Number} end End time in seconds
|
||||||
|
* @return {Object} Fake TimeRange object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function createTimeRange(start, end){
|
||||||
|
return {
|
||||||
|
length: 1,
|
||||||
|
start: function() { return start; },
|
||||||
|
end: function() { return end; }
|
||||||
|
};
|
||||||
|
}
|
11
src/js/utils/to-title-case.js
Normal file
11
src/js/utils/to-title-case.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Uppercase the first letter of a string
|
||||||
|
* @param {String} string String to be uppercased
|
||||||
|
* @return {String}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function toTitleCase(string){
|
||||||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default toTitleCase;
|
89
src/js/utils/url.js
Normal file
89
src/js/utils/url.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import document from 'global/document';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve and parse the elements of a URL
|
||||||
|
* @param {String} url The url to parse
|
||||||
|
* @return {Object} An object of url details
|
||||||
|
*/
|
||||||
|
export const parseUrl = function(url) {
|
||||||
|
const props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
|
||||||
|
|
||||||
|
// add the url to an anchor and let the browser parse the URL
|
||||||
|
let a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
|
||||||
|
// IE8 (and 9?) Fix
|
||||||
|
// ie8 doesn't parse the URL correctly until the anchor is actually
|
||||||
|
// added to the body, and an innerHTML is needed to trigger the parsing
|
||||||
|
let addToBody = (a.host === '' && a.protocol !== 'file:');
|
||||||
|
let div;
|
||||||
|
if (addToBody) {
|
||||||
|
div = document.createElement('div');
|
||||||
|
div.innerHTML = `<a href="${url}"></a>`;
|
||||||
|
a = div.firstChild;
|
||||||
|
// prevent the div from affecting layout
|
||||||
|
div.setAttribute('style', 'display:none; position:absolute;');
|
||||||
|
document.body.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the specific URL properties to a new object
|
||||||
|
// This is also needed for IE8 because the anchor loses its
|
||||||
|
// properties when it's removed from the dom
|
||||||
|
let details = {};
|
||||||
|
for (var i = 0; i < props.length; i++) {
|
||||||
|
details[props[i]] = a[props[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// IE9 adds the port to the host property unlike everyone else. If
|
||||||
|
// a port identifier is added for standard ports, strip it.
|
||||||
|
if (details.protocol === 'http:') {
|
||||||
|
details.host = details.host.replace(/:80$/, '');
|
||||||
|
}
|
||||||
|
if (details.protocol === 'https:') {
|
||||||
|
details.host = details.host.replace(/:443$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addToBody) {
|
||||||
|
document.body.removeChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
return details;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get absolute version of relative URL. Used to tell flash correct URL.
|
||||||
|
* http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
|
||||||
|
* @param {String} url URL to make absolute
|
||||||
|
* @return {String} Absolute URL
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const getAbsoluteURL = function(url){
|
||||||
|
// Check if absolute URL
|
||||||
|
if (!url.match(/^https?:\/\//)) {
|
||||||
|
// Convert to absolute URL. Flash hosted off-site needs an absolute URL.
|
||||||
|
let div = document.createElement('div');
|
||||||
|
div.innerHTML = `<a href="${url}">x</a>`;
|
||||||
|
url = div.firstChild.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the extension of the passed file name. It will return an empty string if you pass an invalid path
|
||||||
|
*
|
||||||
|
* @param {String} path The fileName path like '/path/to/file.mp4'
|
||||||
|
* @returns {String} The extension in lower case or an empty string if no extension could be found.
|
||||||
|
*/
|
||||||
|
export const getFileExtension = function(path) {
|
||||||
|
if(typeof path === 'string'){
|
||||||
|
let splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
|
||||||
|
let pathParts = splitPathRe.exec(path);
|
||||||
|
|
||||||
|
if (pathParts) {
|
||||||
|
return pathParts.pop().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
142
src/js/video.js
142
src/js/video.js
@ -1,5 +1,5 @@
|
|||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
import assign from 'object.assign';
|
||||||
import MediaLoader from './tech/loader.js';
|
import MediaLoader from './tech/loader.js';
|
||||||
import Html5 from './tech/html5.js';
|
import Html5 from './tech/html5.js';
|
||||||
import Flash from './tech/flash.js';
|
import Flash from './tech/flash.js';
|
||||||
@ -9,23 +9,103 @@ import LoadingSpinner from './loading-spinner.js';
|
|||||||
import BigPlayButton from './big-play-button.js';
|
import BigPlayButton from './big-play-button.js';
|
||||||
import ControlBar from './control-bar/control-bar.js';
|
import ControlBar from './control-bar/control-bar.js';
|
||||||
import ErrorDisplay from './error-display.js';
|
import ErrorDisplay from './error-display.js';
|
||||||
|
|
||||||
import videojs from './core';
|
|
||||||
import * as setup from './setup';
|
import * as setup from './setup';
|
||||||
import Component from './component';
|
import Component from './component';
|
||||||
import * as Lib from './lib';
|
import Options from './options';
|
||||||
import * as Util from './util.js';
|
import * as Dom from './utils/dom.js';
|
||||||
|
import log from './utils/log.js';
|
||||||
|
import * as browser from './utils/browser.js';
|
||||||
import Player from './player';
|
import Player from './player';
|
||||||
import extendsFn from './extends.js';
|
import extendsFn from './extends.js';
|
||||||
import plugin from './plugins.js';
|
import plugin from './plugins.js';
|
||||||
import options from './options.js';
|
import options from './options.js';
|
||||||
|
import mergeOptions from '../../src/js/utils/merge-options.js';
|
||||||
|
|
||||||
|
// HTML5 Element Shim for IE8
|
||||||
if (typeof HTMLVideoElement === 'undefined') {
|
if (typeof HTMLVideoElement === 'undefined') {
|
||||||
document.createElement('video');
|
document.createElement('video');
|
||||||
document.createElement('audio');
|
document.createElement('audio');
|
||||||
document.createElement('track');
|
document.createElement('track');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doubles as the main function for users to create a player instance and also
|
||||||
|
* the main library object.
|
||||||
|
*
|
||||||
|
* **ALIASES** videojs, _V_ (deprecated)
|
||||||
|
*
|
||||||
|
* The `vjs` function can be used to initialize or retrieve a player.
|
||||||
|
*
|
||||||
|
* var myPlayer = vjs('my_video_id');
|
||||||
|
*
|
||||||
|
* @param {String|Element} id Video element or video element ID
|
||||||
|
* @param {Object=} options Optional options object for config/settings
|
||||||
|
* @param {Function=} ready Optional ready callback
|
||||||
|
* @return {Player} A player instance
|
||||||
|
* @namespace
|
||||||
|
*/
|
||||||
|
var videojs = 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 (Player.players[id]) {
|
||||||
|
|
||||||
|
// If options or ready funtion are passed, warn
|
||||||
|
if (options) {
|
||||||
|
log.warn(`Player "${id}" is already initialised. Options will not be applied.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ready) {
|
||||||
|
Player.players[id].ready(ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.players[id];
|
||||||
|
|
||||||
|
// Otherwise get element for ID
|
||||||
|
} else {
|
||||||
|
tag = Dom.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 Player(tag, options, ready);
|
||||||
|
};
|
||||||
|
|
||||||
|
// CDN Version. Used to target right flash swf.
|
||||||
|
videojs.CDN_VERSION = '__VERSION_NO_PATCH__';
|
||||||
|
videojs.ACCESS_PROTOCOL = ('https:' === document.location.protocol ? 'https://' : 'http://');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full player version
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
videojs['VERSION'] = '__VERSION__';
|
||||||
|
|
||||||
|
// Set CDN Version of swf
|
||||||
|
// The added (+) blocks the replace from changing this _VERSION_NO_PATCH_ string
|
||||||
|
if (videojs.CDN_VERSION !== '__VERSION_'+'NO_PATCH__') {
|
||||||
|
Options['flash']['swf'] = `${videojs.ACCESS_PROTOCOL}vjs.zencdn.net/${videojs.CDN_VERSION}/video-js.swf`;
|
||||||
|
}
|
||||||
|
|
||||||
// Run Auto-load players
|
// Run Auto-load players
|
||||||
// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
|
// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
|
||||||
setup.autoSetupTimeout(1, videojs);
|
setup.autoSetupTimeout(1, videojs);
|
||||||
@ -35,35 +115,71 @@ videojs.registerComponent = Component.registerComponent;
|
|||||||
|
|
||||||
// APIs that will be removed with 5.0, but need them to get tests passing
|
// APIs that will be removed with 5.0, but need them to get tests passing
|
||||||
// in ES6 transition
|
// in ES6 transition
|
||||||
videojs.TOUCH_ENABLED = Lib.TOUCH_ENABLED;
|
videojs.TOUCH_ENABLED = browser.TOUCH_ENABLED;
|
||||||
videojs.util = Util;
|
|
||||||
|
|
||||||
// Probably want to keep this one for 5.0?
|
// Probably want to keep this one for 5.0?
|
||||||
videojs.players = Player.players;
|
videojs.players = Player.players;
|
||||||
|
|
||||||
videojs.extends = extendsFn;
|
videojs.extends = extendsFn;
|
||||||
|
|
||||||
|
videojs.mergeOptions = mergeOptions;
|
||||||
|
|
||||||
videojs.getGlobalOptions = () => options;
|
videojs.getGlobalOptions = () => options;
|
||||||
videojs.setGlobalOptions = function(newOptions) {
|
videojs.setGlobalOptions = function(newOptions) {
|
||||||
Lib.util.mergeOptions(options, newOptions);
|
mergeOptions(options, newOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
videojs.plugin = plugin;
|
videojs.plugin = plugin;
|
||||||
|
|
||||||
// REMOVING: We probably should not include this in 5.0 thought it would make it
|
/**
|
||||||
// more backwards compatible
|
* Utility function for adding languages to the default options. Useful for
|
||||||
|
* amending multiple language support at runtime.
|
||||||
|
*
|
||||||
|
* Example: videojs.addLanguage('es', {'Hello':'Hola'});
|
||||||
|
*
|
||||||
|
* @param {String} code The language code or dictionary property
|
||||||
|
* @param {Object} data The data values to be translated
|
||||||
|
* @return {Object} The resulting global languages dictionary object
|
||||||
|
*/
|
||||||
|
videojs.addLanguage = function(code, data){
|
||||||
|
if(Options['languages'][code] !== undefined) {
|
||||||
|
Options['languages'][code] = mergeOptions(Options['languages'][code], data);
|
||||||
|
} else {
|
||||||
|
Options['languages'][code] = data;
|
||||||
|
}
|
||||||
|
return Options['languages'];
|
||||||
|
};
|
||||||
|
|
||||||
|
// REMOVING: We probably should add this to the migration plugin
|
||||||
// // Expose but deprecate the window[componentName] method for accessing components
|
// // Expose but deprecate the window[componentName] method for accessing components
|
||||||
// Lib.obj.each(Component.components, function(name, component){
|
// Object.getOwnPropertyNames(Component.components).forEach(function(name){
|
||||||
|
// let component = Component.components[name];
|
||||||
|
//
|
||||||
// // A deprecation warning as the constuctor
|
// // A deprecation warning as the constuctor
|
||||||
// module.exports[name] = function(player, options, ready){
|
// module.exports[name] = function(player, options, ready){
|
||||||
// Lib.log.warn('Using videojs.'+name+' to access the '+name+' component has been deprecated. Please use videojs.getComponent("componentName")');
|
// log.warn('Using videojs.'+name+' to access the '+name+' component has been deprecated. Please use videojs.getComponent("componentName")');
|
||||||
//
|
//
|
||||||
// return new Component(player, options, ready);
|
// return new Component(player, options, ready);
|
||||||
// };
|
// };
|
||||||
//
|
//
|
||||||
// // Allow the prototype and class methods to be accessible still this way
|
// // Allow the prototype and class methods to be accessible still this way
|
||||||
// // Though anything that attempts to override class methods will no longer work
|
// // Though anything that attempts to override class methods will no longer work
|
||||||
// Lib.obj.merge(module.exports[name], component);
|
// assign(module.exports[name], component);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Universal Module Definition (UMD)
|
||||||
|
*
|
||||||
|
* Video.js will never be a non-browser lib so we can simplify UMD a bunch and
|
||||||
|
* still support requirejs and browserify. This also needs to be closure
|
||||||
|
* compiler compatible, so string keys are used.
|
||||||
|
*/
|
||||||
|
if (typeof define === 'function' && define['amd']) {
|
||||||
|
define('videojs', [], function(){ return videojs; });
|
||||||
|
|
||||||
|
// checking that module is an object too because of umdjs/umd#35
|
||||||
|
} else if (typeof exports === 'object' && typeof module === 'object') {
|
||||||
|
module['exports'] = videojs;
|
||||||
|
}
|
||||||
|
|
||||||
export default videojs;
|
export default videojs;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as VjsUtils from './util';
|
import * as Url from './utils/url.js';
|
||||||
import * as Lib from './lib';
|
import log from './utils/log.js';
|
||||||
|
import mergeOptions from './utils/merge-options.js';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,7 +44,7 @@ var xhr = function(options, callback){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge with default options
|
// Merge with default options
|
||||||
VjsUtils.mergeOptions({
|
mergeOptions({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
timeout: 45 * 1000
|
timeout: 45 * 1000
|
||||||
}, options);
|
}, options);
|
||||||
@ -66,7 +67,7 @@ var xhr = function(options, callback){
|
|||||||
// Store a reference to the url on the request instance
|
// Store a reference to the url on the request instance
|
||||||
request.uri = options.uri;
|
request.uri = options.uri;
|
||||||
|
|
||||||
let urlInfo = Lib.parseUrl(options.uri);
|
let urlInfo = Url.parseUrl(options.uri);
|
||||||
let winLoc = window.location;
|
let winLoc = window.location;
|
||||||
|
|
||||||
let successHandler = function(){
|
let successHandler = function(){
|
||||||
|
@ -192,8 +192,7 @@ test('should export useful components to the public', function () {
|
|||||||
ok(videojs.getComponent('ChaptersButton'), 'ChaptersButton should be public');
|
ok(videojs.getComponent('ChaptersButton'), 'ChaptersButton should be public');
|
||||||
ok(videojs.getComponent('ChaptersTrackMenuItem'), 'ChaptersTrackMenuItem should be public');
|
ok(videojs.getComponent('ChaptersTrackMenuItem'), 'ChaptersTrackMenuItem should be public');
|
||||||
|
|
||||||
ok(videojs.util, 'util namespace should be public');
|
ok(videojs.mergeOptions, 'mergeOptions should be public');
|
||||||
ok(videojs.util.mergeOptions, 'mergeOptions should be public');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to initialize player twice on the same tag using string reference', function() {
|
test('should be able to initialize player twice on the same tag using string reference', function() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Component from '../../src/js/component.js';
|
import Component from '../../src/js/component.js';
|
||||||
import * as Lib from '../../src/js/lib.js';
|
import * as Dom from '../../src/js/utils/dom.js';
|
||||||
import * as Events from '../../src/js/events.js';
|
import * as Events from '../../src/js/utils/events.js';
|
||||||
|
import * as browser from '../../src/js/utils/browser.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
q.module('Component', {
|
q.module('Component', {
|
||||||
@ -164,8 +165,8 @@ test('should dispose of component and children', function(){
|
|||||||
|
|
||||||
// Add a listener
|
// Add a listener
|
||||||
comp.on('click', function(){ return true; });
|
comp.on('click', function(){ return true; });
|
||||||
var data = Lib.getData(comp.el());
|
var data = Dom.getData(comp.el());
|
||||||
var id = comp.el()[Lib.expando];
|
var id = comp.el()[Dom.expando];
|
||||||
|
|
||||||
var hasDisposed = false;
|
var hasDisposed = false;
|
||||||
var bubbles = null;
|
var bubbles = null;
|
||||||
@ -182,8 +183,8 @@ test('should dispose of component and children', function(){
|
|||||||
ok(!comp.el(), 'component element was deleted');
|
ok(!comp.el(), 'component element was deleted');
|
||||||
ok(!child.children(), 'child children were deleted');
|
ok(!child.children(), 'child children were deleted');
|
||||||
ok(!child.el(), 'child element was deleted');
|
ok(!child.el(), 'child element was deleted');
|
||||||
ok(!Lib.cache[id], 'listener cache nulled');
|
ok(!Dom.cache[id], 'listener cache nulled');
|
||||||
ok(Lib.isEmpty(data), 'original listener cache object was emptied');
|
ok(!Object.getOwnPropertyNames(data).length, 'original listener cache object was emptied');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should add and remove event listeners to element', function(){
|
test('should add and remove event listeners to element', function(){
|
||||||
@ -427,7 +428,7 @@ test('should change the width and height of a component', function(){
|
|||||||
comp.height('123px');
|
comp.height('123px');
|
||||||
|
|
||||||
ok(comp.width() === 500, 'percent values working');
|
ok(comp.width() === 500, 'percent values working');
|
||||||
var compStyle = Lib.getComputedDimension(el, 'width');
|
var compStyle = Dom.getComputedDimension(el, 'width');
|
||||||
ok(compStyle === comp.width() + 'px', 'matches computed style');
|
ok(compStyle === comp.width() + 'px', 'matches computed style');
|
||||||
ok(comp.height() === 123, 'px values working');
|
ok(comp.height() === 123, 'px values working');
|
||||||
|
|
||||||
@ -446,9 +447,9 @@ test('should use a defined content el for appending children', function(){
|
|||||||
|
|
||||||
CompWithContent.prototype.createEl = function(){
|
CompWithContent.prototype.createEl = function(){
|
||||||
// Create the main componenent element
|
// Create the main componenent element
|
||||||
var el = Lib.createEl('div');
|
var el = Dom.createEl('div');
|
||||||
// Create the element where children will be appended
|
// Create the element where children will be appended
|
||||||
this.contentEl_ = Lib.createEl('div', { 'id': 'contentEl' });
|
this.contentEl_ = Dom.createEl('div', { 'id': 'contentEl' });
|
||||||
el.appendChild(this.contentEl_);
|
el.appendChild(this.contentEl_);
|
||||||
return el;
|
return el;
|
||||||
};
|
};
|
||||||
@ -471,8 +472,8 @@ test('should emit a tap event', function(){
|
|||||||
expect(3);
|
expect(3);
|
||||||
|
|
||||||
// Fake touch support. Real touch support isn't needed for this test.
|
// Fake touch support. Real touch support isn't needed for this test.
|
||||||
var origTouch = Lib.TOUCH_ENABLED;
|
var origTouch = browser.TOUCH_ENABLED;
|
||||||
Lib.TOUCH_ENABLED = true;
|
browser.TOUCH_ENABLED = true;
|
||||||
|
|
||||||
var comp = new Component(getFakePlayer());
|
var comp = new Component(getFakePlayer());
|
||||||
var singleTouch = {};
|
var singleTouch = {};
|
||||||
@ -523,7 +524,7 @@ test('should emit a tap event', function(){
|
|||||||
comp.trigger('touchend');
|
comp.trigger('touchend');
|
||||||
|
|
||||||
// Reset to orignial value
|
// Reset to orignial value
|
||||||
Lib.TOUCH_ENABLED = origTouch;
|
browser.TOUCH_ENABLED = origTouch;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should provide timeout methods that automatically get cleared on component disposal', function() {
|
test('should provide timeout methods that automatically get cleared on component disposal', function() {
|
@ -1,60 +0,0 @@
|
|||||||
import CoreObject from '../../src/js/core-object.js';
|
|
||||||
|
|
||||||
q.module('Core Object');
|
|
||||||
|
|
||||||
test('should verify CoreObject extension', function(){
|
|
||||||
var TestObject = CoreObject.extend({
|
|
||||||
init: function(initOptions){
|
|
||||||
this['a'] = initOptions['a'];
|
|
||||||
},
|
|
||||||
testFn: function(){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var instance = new TestObject({ 'a': true });
|
|
||||||
|
|
||||||
ok(instance instanceof TestObject, 'New instance is instance of TestObject');
|
|
||||||
ok(instance instanceof CoreObject, 'New instance is instance of CoreObject');
|
|
||||||
ok(instance['a'], 'Init options are passed to init');
|
|
||||||
ok(instance.testFn(), 'Additional methods are applied to TestObject prototype');
|
|
||||||
|
|
||||||
// Two levels of inheritance
|
|
||||||
var TestChild = TestObject.extend({
|
|
||||||
init: function(initOptions){
|
|
||||||
TestObject.call(this, initOptions);
|
|
||||||
// TestObject.prototype.init.call(this, initOptions);
|
|
||||||
this['b'] = initOptions['b'];
|
|
||||||
},
|
|
||||||
testFn: function(){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var childInstance = new TestChild({ 'a': true, 'b': true });
|
|
||||||
|
|
||||||
ok(childInstance instanceof TestChild, 'New instance is instance of TestChild');
|
|
||||||
ok(childInstance instanceof TestObject, 'New instance is instance of TestObject');
|
|
||||||
ok(childInstance instanceof CoreObject, 'New instance is instance of CoreObject');
|
|
||||||
ok(childInstance['b'], 'Init options are passed to init');
|
|
||||||
ok(childInstance['a'], 'Init options are passed to super init');
|
|
||||||
ok(childInstance.testFn() === false, 'Methods can be overridden by extend');
|
|
||||||
ok(TestObject.prototype.testFn() === true, 'Prototype of parent not overridden');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should verify CoreObject create function', function(){
|
|
||||||
var TestObject = CoreObject.extend({
|
|
||||||
init: function(initOptions){
|
|
||||||
this['a'] = initOptions['a'];
|
|
||||||
},
|
|
||||||
testFn: function(){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var instance = TestObject.create({ 'a': true });
|
|
||||||
|
|
||||||
ok(instance instanceof TestObject, 'New instance is instance of TestObject');
|
|
||||||
ok(instance instanceof CoreObject, 'New instance is instance of CoreObject');
|
|
||||||
ok(instance['a'], 'Init options are passed to init');
|
|
||||||
ok(instance.testFn(), 'Additional methods are applied to TestObject prototype');
|
|
||||||
});
|
|
@ -1,4 +1,4 @@
|
|||||||
import * as Events from '../../src/js/events.js';
|
import * as Events from '../../src/js/utils/events.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
q.module('Events');
|
q.module('Events');
|
429
test/unit/lib.js
429
test/unit/lib.js
@ -1,429 +0,0 @@
|
|||||||
import * as Lib from '../../src/js/lib.js';
|
|
||||||
import window from 'global/window';
|
|
||||||
import document from 'global/document';
|
|
||||||
|
|
||||||
q.module('Lib', {
|
|
||||||
'setup': function() {
|
|
||||||
// Allow for stubbing createElement, should replace with sinon now
|
|
||||||
this.createElement = document.createElement;
|
|
||||||
},
|
|
||||||
'teardown': function() {
|
|
||||||
document.createElement = this.createElement;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create an element', function(){
|
|
||||||
var div = Lib.createEl();
|
|
||||||
var span = Lib.createEl('span', { 'data-test': 'asdf', innerHTML:'fdsa' });
|
|
||||||
ok(div.nodeName === 'DIV');
|
|
||||||
ok(span.nodeName === 'SPAN');
|
|
||||||
ok(span['data-test'] === 'asdf');
|
|
||||||
ok(span.innerHTML === 'fdsa');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should make a string start with an uppercase letter', function(){
|
|
||||||
var foo = Lib.capitalize('bar');
|
|
||||||
ok(foo === 'Bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should loop through each property on an object', function(){
|
|
||||||
var asdf = {
|
|
||||||
a: 1,
|
|
||||||
b: 2,
|
|
||||||
'c': 3
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add 3 to each value
|
|
||||||
Lib.obj.each(asdf, function(key, value){
|
|
||||||
asdf[key] = value + 3;
|
|
||||||
});
|
|
||||||
|
|
||||||
deepEqual(asdf,{a:4,b:5,'c':6});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should copy an object', function(){
|
|
||||||
var asdf = {
|
|
||||||
a: 1,
|
|
||||||
b: 2,
|
|
||||||
'c': 3
|
|
||||||
};
|
|
||||||
|
|
||||||
var fdsa = Lib.obj.copy(asdf);
|
|
||||||
|
|
||||||
deepEqual(asdf,fdsa);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should check if an object is an Array', function(){
|
|
||||||
var arr = ['a', 'b', 'c'];
|
|
||||||
ok(Lib.obj.isArray(arr) === true, 'Arr object is an Array');
|
|
||||||
|
|
||||||
var obj = {};
|
|
||||||
ok(Lib.obj.isArray(obj) === false, 'Obj is not an Array');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should check if an object is plain', function(){
|
|
||||||
var empty = {};
|
|
||||||
ok(Lib.obj.isPlain(empty) === true, 'Empty object is plain');
|
|
||||||
|
|
||||||
var node = document.createElement('div');
|
|
||||||
ok(Lib.obj.isPlain(node) === false, 'DOM node is not plain');
|
|
||||||
|
|
||||||
var fn = function(){};
|
|
||||||
ok(Lib.obj.isPlain(fn) === false, 'Function is not plain');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should add context to a function', function(){
|
|
||||||
var newContext = { test: 'obj'};
|
|
||||||
var asdf = function(){
|
|
||||||
ok(this === newContext);
|
|
||||||
};
|
|
||||||
var fdsa = Lib.bind(newContext, asdf);
|
|
||||||
|
|
||||||
fdsa();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should add and remove a class name on an element', function(){
|
|
||||||
var el = document.createElement('div');
|
|
||||||
Lib.addClass(el, 'test-class');
|
|
||||||
ok(el.className === 'test-class', 'class added');
|
|
||||||
Lib.addClass(el, 'test-class');
|
|
||||||
ok(el.className === 'test-class', 'same class not duplicated');
|
|
||||||
Lib.addClass(el, 'test-class2');
|
|
||||||
ok(el.className === 'test-class test-class2', 'added second class');
|
|
||||||
Lib.removeClass(el, 'test-class');
|
|
||||||
ok(el.className === 'test-class2', 'removed first class');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should read class names on an element', function(){
|
|
||||||
var el = document.createElement('div');
|
|
||||||
Lib.addClass(el, 'test-class1');
|
|
||||||
ok(Lib.hasClass(el, 'test-class1') === true, 'class detected');
|
|
||||||
ok(Lib.hasClass(el, 'test-class') === false, 'substring correctly not detected');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should get and remove data from an element', function(){
|
|
||||||
var el = document.createElement('div');
|
|
||||||
var data = Lib.getData(el);
|
|
||||||
var id = el[Lib.expando];
|
|
||||||
|
|
||||||
ok(typeof data === 'object', 'data object created');
|
|
||||||
|
|
||||||
// Add data
|
|
||||||
var testData = { asdf: 'fdsa' };
|
|
||||||
data.test = testData;
|
|
||||||
ok(Lib.getData(el).test === testData, 'data added');
|
|
||||||
|
|
||||||
// Remove all data
|
|
||||||
Lib.removeData(el);
|
|
||||||
|
|
||||||
ok(!Lib.cache[id], 'cached item nulled');
|
|
||||||
ok(el[Lib.expando] === null || el[Lib.expando] === undefined, 'element data id removed');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should read tag attributes from elements, including HTML5 in all browsers', function(){
|
|
||||||
var tags = '<video id="vid1" controls autoplay loop muted preload="none" src="http://google.com" poster="http://www2.videojs.com/img/video-js-html5-video-player.png" data-test="asdf" data-empty-string=""></video>';
|
|
||||||
tags += '<video id="vid2">';
|
|
||||||
// Not putting source and track inside video element because
|
|
||||||
// oldIE needs the HTML5 shim to read tags inside HTML5 tags.
|
|
||||||
// Still may not work in oldIE.
|
|
||||||
tags += '<source id="source" src="http://google.com" type="video/mp4" media="fdsa" title="test" >';
|
|
||||||
tags += '<track id="track" default src="http://google.com" kind="captions" srclang="en" label="testlabel" title="test" >';
|
|
||||||
tags += '</video>';
|
|
||||||
|
|
||||||
document.getElementById('qunit-fixture').innerHTML += tags;
|
|
||||||
|
|
||||||
var vid1Vals = Lib.getElementAttributes(document.getElementById('vid1'));
|
|
||||||
var vid2Vals = Lib.getElementAttributes(document.getElementById('vid2'));
|
|
||||||
var sourceVals = Lib.getElementAttributes(document.getElementById('source'));
|
|
||||||
var trackVals = Lib.getElementAttributes(document.getElementById('track'));
|
|
||||||
|
|
||||||
// was using deepEqual, but ie8 would send all properties as attributes
|
|
||||||
|
|
||||||
// vid1
|
|
||||||
equal(vid1Vals['autoplay'], true);
|
|
||||||
equal(vid1Vals['controls'], true);
|
|
||||||
equal(vid1Vals['data-test'], 'asdf');
|
|
||||||
equal(vid1Vals['data-empty-string'], '');
|
|
||||||
equal(vid1Vals['id'], 'vid1');
|
|
||||||
equal(vid1Vals['loop'], true);
|
|
||||||
equal(vid1Vals['muted'], true);
|
|
||||||
equal(vid1Vals['poster'], 'http://www2.videojs.com/img/video-js-html5-video-player.png');
|
|
||||||
equal(vid1Vals['preload'], 'none');
|
|
||||||
equal(vid1Vals['src'], 'http://google.com');
|
|
||||||
|
|
||||||
// vid2
|
|
||||||
equal(vid2Vals['id'], 'vid2');
|
|
||||||
|
|
||||||
// sourceVals
|
|
||||||
equal(sourceVals['title'], 'test');
|
|
||||||
equal(sourceVals['media'], 'fdsa');
|
|
||||||
equal(sourceVals['type'], 'video/mp4');
|
|
||||||
equal(sourceVals['src'], 'http://google.com');
|
|
||||||
equal(sourceVals['id'], 'source');
|
|
||||||
|
|
||||||
// trackVals
|
|
||||||
equal(trackVals['default'], true);
|
|
||||||
equal(trackVals['id'], 'track');
|
|
||||||
equal(trackVals['kind'], 'captions');
|
|
||||||
equal(trackVals['label'], 'testlabel');
|
|
||||||
equal(trackVals['src'], 'http://google.com');
|
|
||||||
equal(trackVals['srclang'], 'en');
|
|
||||||
equal(trackVals['title'], 'test');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should set element attributes from object', function(){
|
|
||||||
var el, vid1Vals;
|
|
||||||
|
|
||||||
el = document.createElement('div');
|
|
||||||
el.id = 'el1';
|
|
||||||
|
|
||||||
Lib.setElementAttributes(el, { controls: true, 'data-test': 'asdf' });
|
|
||||||
|
|
||||||
equal(el.getAttribute('id'), 'el1');
|
|
||||||
equal(el.getAttribute('controls'), '');
|
|
||||||
equal(el.getAttribute('data-test'), 'asdf');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should get the right style values for an element', function(){
|
|
||||||
var el = document.createElement('div');
|
|
||||||
var container = document.createElement('div');
|
|
||||||
var fixture = document.getElementById('qunit-fixture');
|
|
||||||
|
|
||||||
container.appendChild(el);
|
|
||||||
fixture.appendChild(container);
|
|
||||||
|
|
||||||
container.style.width = '1000px';
|
|
||||||
container.style.height = '1000px';
|
|
||||||
|
|
||||||
el.style.height = '100%';
|
|
||||||
el.style.width = '123px';
|
|
||||||
|
|
||||||
// integer px values may get translated int very-close floats in Chrome/OS X
|
|
||||||
// so round the dimensions to ignore this
|
|
||||||
equal(Math.round(parseFloat(Lib.getComputedDimension(el, 'height'))), 1000, 'the computed height is equal');
|
|
||||||
equal(Math.round(parseFloat(Lib.getComputedDimension(el, 'width'))), 123, 'the computed width is equal');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should insert an element first in another', function(){
|
|
||||||
var el1 = document.createElement('div');
|
|
||||||
var el2 = document.createElement('div');
|
|
||||||
var parent = document.createElement('div');
|
|
||||||
|
|
||||||
Lib.insertFirst(el1, parent);
|
|
||||||
ok(parent.firstChild === el1, 'inserts first into empty parent');
|
|
||||||
|
|
||||||
Lib.insertFirst(el2, parent);
|
|
||||||
ok(parent.firstChild === el2, 'inserts first into parent with child');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return the element with the ID', function(){
|
|
||||||
var el1 = document.createElement('div');
|
|
||||||
var el2 = document.createElement('div');
|
|
||||||
var fixture = document.getElementById('qunit-fixture');
|
|
||||||
|
|
||||||
fixture.appendChild(el1);
|
|
||||||
fixture.appendChild(el2);
|
|
||||||
|
|
||||||
el1.id = 'test_id1';
|
|
||||||
el2.id = 'test_id2';
|
|
||||||
|
|
||||||
ok(Lib.el('test_id1') === el1, 'found element for ID');
|
|
||||||
ok(Lib.el('#test_id2') === el2, 'found element for CSS ID');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should trim whitespace from a string', function(){
|
|
||||||
ok(Lib.trim(' asdf asdf asdf \t\n\r') === 'asdf asdf asdf');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should round a number', function(){
|
|
||||||
ok(Lib.round(1.01) === 1);
|
|
||||||
ok(Lib.round(1.5) === 2);
|
|
||||||
ok(Lib.round(1.55, 2) === 1.55);
|
|
||||||
ok(Lib.round(10.551, 2) === 10.55);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should format time as a string', function(){
|
|
||||||
ok(Lib.formatTime(1) === '0:01');
|
|
||||||
ok(Lib.formatTime(10) === '0:10');
|
|
||||||
ok(Lib.formatTime(60) === '1:00');
|
|
||||||
ok(Lib.formatTime(600) === '10:00');
|
|
||||||
ok(Lib.formatTime(3600) === '1:00:00');
|
|
||||||
ok(Lib.formatTime(36000) === '10:00:00');
|
|
||||||
ok(Lib.formatTime(360000) === '100:00:00');
|
|
||||||
|
|
||||||
// Using guide should provide extra leading zeros
|
|
||||||
ok(Lib.formatTime(1,1) === '0:01');
|
|
||||||
ok(Lib.formatTime(1,10) === '0:01');
|
|
||||||
ok(Lib.formatTime(1,60) === '0:01');
|
|
||||||
ok(Lib.formatTime(1,600) === '00:01');
|
|
||||||
ok(Lib.formatTime(1,3600) === '0:00:01');
|
|
||||||
// Don't do extra leading zeros for hours
|
|
||||||
ok(Lib.formatTime(1,36000) === '0:00:01');
|
|
||||||
ok(Lib.formatTime(1,360000) === '0:00:01');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should format invalid times as dashes', function(){
|
|
||||||
equal(Lib.formatTime(Infinity, 90), '-:-');
|
|
||||||
equal(Lib.formatTime(NaN), '-:-');
|
|
||||||
// equal(Lib.formatTime(NaN, 216000), '-:--:--');
|
|
||||||
equal(Lib.formatTime(10, Infinity), '0:00:10');
|
|
||||||
equal(Lib.formatTime(90, NaN), '1:30');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create a fake timerange', function(){
|
|
||||||
var tr = Lib.createTimeRange(0, 10);
|
|
||||||
ok(tr.start() === 0);
|
|
||||||
ok(tr.end() === 10);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should get an absolute URL', function(){
|
|
||||||
// Errors on compiled tests that don't use unit.html. Need a better solution.
|
|
||||||
// ok(Lib.getAbsoluteURL('unit.html') === window.location.href);
|
|
||||||
ok(Lib.getAbsoluteURL('http://asdf.com') === 'http://asdf.com');
|
|
||||||
ok(Lib.getAbsoluteURL('https://asdf.com/index.html') === 'https://asdf.com/index.html');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should parse the details of a url correctly', function(){
|
|
||||||
equal(Lib.parseUrl('#').protocol, window.location.protocol, 'parsed relative url protocol');
|
|
||||||
equal(Lib.parseUrl('#').host, window.location.host, 'parsed relative url host');
|
|
||||||
|
|
||||||
equal(Lib.parseUrl('http://example.com').protocol, 'http:', 'parsed example url protocol');
|
|
||||||
equal(Lib.parseUrl('http://example.com').hostname, 'example.com', 'parsed example url hostname');
|
|
||||||
|
|
||||||
equal(Lib.parseUrl('http://example.com:1234').port, '1234', 'parsed example url port');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should strip port from hosts using http or https', function() {
|
|
||||||
var url;
|
|
||||||
|
|
||||||
// attempts to create elements will return an anchor tag that
|
|
||||||
// misbehaves like IE9
|
|
||||||
document.createElement = function() {
|
|
||||||
return {
|
|
||||||
hostname: 'example.com',
|
|
||||||
host: 'example.com:80',
|
|
||||||
protocol: 'http:',
|
|
||||||
port: '80',
|
|
||||||
pathname: '/domain/relative/url',
|
|
||||||
hash: ''
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
url = Lib.parseUrl('/domain/relative/url');
|
|
||||||
ok(!(/.*:80$/).test(url.host), ':80 is not appended to the host');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test('Lib.findPosition should find top and left position', function() {
|
|
||||||
var d = document.createElement('div'),
|
|
||||||
position = Lib.findPosition(d);
|
|
||||||
d.style.top = '10px';
|
|
||||||
d.style.left = '20px';
|
|
||||||
d.style.position = 'absolute';
|
|
||||||
|
|
||||||
deepEqual(position, {left: 0, top: 0}, 'If element isn\'t in the DOM, we should get zeros');
|
|
||||||
|
|
||||||
document.body.appendChild(d);
|
|
||||||
position = Lib.findPosition(d);
|
|
||||||
deepEqual(position, {left: 20, top: 10}, 'The position was not correct');
|
|
||||||
|
|
||||||
d.getBoundingClientRect = null;
|
|
||||||
position = Lib.findPosition(d);
|
|
||||||
deepEqual(position, {left: 0, top: 0}, 'If there is no gBCR, we should get zeros');
|
|
||||||
});
|
|
||||||
|
|
||||||
// LOG TESTS
|
|
||||||
test('should confirm logging functions work', function() {
|
|
||||||
var console, log, error, warn, origConsole, origLog, origWarn, origError;
|
|
||||||
|
|
||||||
origConsole = window['console'];
|
|
||||||
// replace the native console for testing
|
|
||||||
// in ie8 console.log is apparently not a 'function' so sinon chokes on it
|
|
||||||
// https://github.com/cjohansen/Sinon.JS/issues/386
|
|
||||||
// instead we'll temporarily replace them with no-op functions
|
|
||||||
console = window['console'] = {
|
|
||||||
log: function(){},
|
|
||||||
warn: function(){},
|
|
||||||
error: function(){}
|
|
||||||
};
|
|
||||||
|
|
||||||
// stub the global log functions
|
|
||||||
log = sinon.stub(console, 'log');
|
|
||||||
error = sinon.stub(console, 'error');
|
|
||||||
warn = sinon.stub(console, 'warn');
|
|
||||||
|
|
||||||
Lib.log('log1', 'log2');
|
|
||||||
Lib.log.warn('warn1', 'warn2');
|
|
||||||
Lib.log.error('error1', 'error2');
|
|
||||||
|
|
||||||
ok(log.called, 'log was called');
|
|
||||||
equal(log.firstCall.args[0], 'VIDEOJS:');
|
|
||||||
equal(log.firstCall.args[1], 'log1');
|
|
||||||
equal(log.firstCall.args[2], 'log2');
|
|
||||||
|
|
||||||
ok(warn.called, 'warn was called');
|
|
||||||
equal(warn.firstCall.args[0], 'VIDEOJS:');
|
|
||||||
equal(warn.firstCall.args[1], 'WARN:');
|
|
||||||
equal(warn.firstCall.args[2], 'warn1');
|
|
||||||
equal(warn.firstCall.args[3], 'warn2');
|
|
||||||
|
|
||||||
ok(error.called, 'error was called');
|
|
||||||
equal(error.firstCall.args[0], 'VIDEOJS:');
|
|
||||||
equal(error.firstCall.args[1], 'ERROR:');
|
|
||||||
equal(error.firstCall.args[2], 'error1');
|
|
||||||
equal(error.firstCall.args[3], 'error2');
|
|
||||||
|
|
||||||
ok(Lib.log.history.length === 3, 'there should be three messages in the log history');
|
|
||||||
|
|
||||||
// tear down sinon
|
|
||||||
log.restore();
|
|
||||||
error.restore();
|
|
||||||
warn.restore();
|
|
||||||
|
|
||||||
// restore the native console
|
|
||||||
window['console'] = origConsole;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should loop through each element of an array', function() {
|
|
||||||
expect(10);
|
|
||||||
var a = [1, 2, 3];
|
|
||||||
var sum = 0;
|
|
||||||
var i = 0;
|
|
||||||
var thisArg = {};
|
|
||||||
|
|
||||||
Lib.arr.forEach(a, function(item, iterator, array) {
|
|
||||||
sum += item;
|
|
||||||
deepEqual(array, a, 'The array arg should match the original array');
|
|
||||||
equal(i++, iterator, 'The indexes should match');
|
|
||||||
equal(this, thisArg, 'The context should equal the thisArg');
|
|
||||||
|
|
||||||
if (this !== thisArg) {
|
|
||||||
ok(false, 'should allow setting the context');
|
|
||||||
}
|
|
||||||
}, thisArg);
|
|
||||||
|
|
||||||
ok(sum, 6);
|
|
||||||
});
|
|
||||||
|
|
||||||
//getFileExtension tests
|
|
||||||
test('should get the file extension of the passed path', function() {
|
|
||||||
equal(Lib.getFileExtension('/foo/bar/test.video.wgg'), 'wgg');
|
|
||||||
equal(Lib.getFileExtension('test./video.mp4'), 'mp4');
|
|
||||||
equal(Lib.getFileExtension('.bar/test.video.m4v'), 'm4v');
|
|
||||||
equal(Lib.getFileExtension('foo/.bar/test.video.flv'), 'flv');
|
|
||||||
equal(Lib.getFileExtension('foo/.bar/test.video.flv?foo=bar'), 'flv');
|
|
||||||
equal(Lib.getFileExtension('http://www.test.com/video.mp4'), 'mp4');
|
|
||||||
equal(Lib.getFileExtension('http://foo/bar/test.video.wgg'), 'wgg');
|
|
||||||
|
|
||||||
//edge cases
|
|
||||||
equal(Lib.getFileExtension('http://...'), '');
|
|
||||||
equal(Lib.getFileExtension('foo/.bar/testvideo'), '');
|
|
||||||
equal(Lib.getFileExtension(''), '');
|
|
||||||
equal(Lib.getFileExtension(null), '');
|
|
||||||
equal(Lib.getFileExtension(undefined), '');
|
|
||||||
|
|
||||||
//with capital letters
|
|
||||||
equal(Lib.getFileExtension('test.video.MP4'), 'mp4');
|
|
||||||
equal(Lib.getFileExtension('test.video.FLV'), 'flv');
|
|
||||||
});
|
|
@ -1,7 +1,9 @@
|
|||||||
import Player from '../../src/js/player.js';
|
import Player from '../../src/js/player.js';
|
||||||
import videojs from '../../src/js/core.js';
|
import videojs from '../../src/js/video.js';
|
||||||
import Options from '../../src/js/options.js';
|
import Options from '../../src/js/options.js';
|
||||||
import * as Lib from '../../src/js/lib.js';
|
import * as Dom from '../../src/js/utils/dom.js';
|
||||||
|
import * as browser from '../../src/js/utils/browser.js';
|
||||||
|
import log from '../../src/js/utils/log.js';
|
||||||
import MediaError from '../../src/js/media-error.js';
|
import MediaError from '../../src/js/media-error.js';
|
||||||
import Html5 from '../../src/js/tech/html5.js';
|
import Html5 from '../../src/js/tech/html5.js';
|
||||||
import TestHelpers from './test-helpers.js';
|
import TestHelpers from './test-helpers.js';
|
||||||
@ -133,7 +135,7 @@ test('should get tag, source, and track settings', function(){
|
|||||||
test('should asynchronously fire error events during source selection', function() {
|
test('should asynchronously fire error events during source selection', function() {
|
||||||
expect(2);
|
expect(2);
|
||||||
|
|
||||||
sinon.stub(Lib.log, 'error');
|
sinon.stub(log, 'error');
|
||||||
|
|
||||||
var player = TestHelpers.makePlayer({
|
var player = TestHelpers.makePlayer({
|
||||||
'techOrder': ['foo'],
|
'techOrder': ['foo'],
|
||||||
@ -150,7 +152,7 @@ test('should asynchronously fire error events during source selection', function
|
|||||||
this.clock.tick(1);
|
this.clock.tick(1);
|
||||||
|
|
||||||
player.dispose();
|
player.dispose();
|
||||||
Lib.log.error.restore();
|
log.error.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should set the width, height, and aspect ratio via a css class', function(){
|
test('should set the width, height, and aspect ratio via a css class', function(){
|
||||||
@ -389,15 +391,15 @@ test('should add a touch-enabled classname when touch is supported', function(){
|
|||||||
expect(1);
|
expect(1);
|
||||||
|
|
||||||
// Fake touch support. Real touch support isn't needed for this test.
|
// Fake touch support. Real touch support isn't needed for this test.
|
||||||
var origTouch = Lib.TOUCH_ENABLED;
|
var origTouch = browser.TOUCH_ENABLED;
|
||||||
Lib.TOUCH_ENABLED = true;
|
browser.TOUCH_ENABLED = true;
|
||||||
|
|
||||||
player = TestHelpers.makePlayer({});
|
player = TestHelpers.makePlayer({});
|
||||||
|
|
||||||
ok(player.el().className.indexOf('vjs-touch-enabled'), 'touch-enabled classname added');
|
ok(player.el().className.indexOf('vjs-touch-enabled'), 'touch-enabled classname added');
|
||||||
|
|
||||||
|
|
||||||
Lib.TOUCH_ENABLED = origTouch;
|
browser.TOUCH_ENABLED = origTouch;
|
||||||
player.dispose();
|
player.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -551,7 +553,7 @@ test('player should handle different error types', function(){
|
|||||||
var testMsg = 'test message';
|
var testMsg = 'test message';
|
||||||
|
|
||||||
// prevent error log messages in the console
|
// prevent error log messages in the console
|
||||||
sinon.stub(Lib.log, 'error');
|
sinon.stub(log, 'error');
|
||||||
|
|
||||||
// error code supplied
|
// error code supplied
|
||||||
function errCode(){
|
function errCode(){
|
||||||
@ -592,7 +594,7 @@ test('player should handle different error types', function(){
|
|||||||
ok(player.el().className.indexOf('vjs-error') >= 0, 'player does not have vjs-error classname');
|
ok(player.el().className.indexOf('vjs-error') >= 0, 'player does not have vjs-error classname');
|
||||||
|
|
||||||
// restore error logging
|
// restore error logging
|
||||||
Lib.log.error.restore();
|
log.error.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Data attributes on the video element should persist in the new wrapper element', function() {
|
test('Data attributes on the video element should persist in the new wrapper element', function() {
|
||||||
@ -612,7 +614,7 @@ test('should restore attributes from the original video tag when creating a new
|
|||||||
var tag, html5Mock, el;
|
var tag, html5Mock, el;
|
||||||
|
|
||||||
// simulate attributes stored from the original tag
|
// simulate attributes stored from the original tag
|
||||||
tag = Lib.createEl('video');
|
tag = Dom.createEl('video');
|
||||||
tag.setAttribute('preload', 'auto');
|
tag.setAttribute('preload', 'auto');
|
||||||
tag.setAttribute('autoplay', '');
|
tag.setAttribute('autoplay', '');
|
||||||
tag.setAttribute('webkit-playsinline', '');
|
tag.setAttribute('webkit-playsinline', '');
|
@ -1,12 +1,12 @@
|
|||||||
import PosterImage from '../../src/js/poster-image.js';
|
import PosterImage from '../../src/js/poster-image.js';
|
||||||
import * as Lib from '../../src/js/lib.js';
|
import * as browser from '../../src/js/utils/browser.js';
|
||||||
import TestHelpers from './test-helpers.js';
|
import TestHelpers from './test-helpers.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
q.module('PosterImage', {
|
q.module('PosterImage', {
|
||||||
'setup': function(){
|
'setup': function(){
|
||||||
// Store the original background support so we can test different vals
|
// Store the original background support so we can test different vals
|
||||||
this.origVal = Lib.BACKGROUND_SIZE_SUPPORTED;
|
this.origVal = browser.BACKGROUND_SIZE_SUPPORTED;
|
||||||
this.poster1 = 'http://example.com/poster.jpg';
|
this.poster1 = 'http://example.com/poster.jpg';
|
||||||
this.poster2 = 'http://example.com/UPDATED.jpg';
|
this.poster2 = 'http://example.com/UPDATED.jpg';
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ q.module('PosterImage', {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
'teardown': function(){
|
'teardown': function(){
|
||||||
Lib.BACKGROUND_SIZE_SUPPORTED = this.origVal;
|
browser.BACKGROUND_SIZE_SUPPORTED = this.origVal;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ test('should create and update a poster image', function(){
|
|||||||
return url.replace(new RegExp('\\"', 'g'),'');
|
return url.replace(new RegExp('\\"', 'g'),'');
|
||||||
}
|
}
|
||||||
|
|
||||||
Lib.BACKGROUND_SIZE_SUPPORTED = true;
|
browser.BACKGROUND_SIZE_SUPPORTED = true;
|
||||||
posterImage = new PosterImage(this.mockPlayer);
|
posterImage = new PosterImage(this.mockPlayer);
|
||||||
equal(normalizeUrl(posterImage.el().style.backgroundImage), 'url('+this.poster1+')', 'Background image used');
|
equal(normalizeUrl(posterImage.el().style.backgroundImage), 'url('+this.poster1+')', 'Background image used');
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ test('should create and update a poster image', function(){
|
|||||||
test('should create and update a fallback image in older browsers', function(){
|
test('should create and update a fallback image in older browsers', function(){
|
||||||
var posterImage;
|
var posterImage;
|
||||||
|
|
||||||
Lib.BACKGROUND_SIZE_SUPPORTED = false;
|
browser.BACKGROUND_SIZE_SUPPORTED = false;
|
||||||
posterImage = new PosterImage(this.mockPlayer);
|
posterImage = new PosterImage(this.mockPlayer);
|
||||||
equal(posterImage.fallbackImg_.src, this.poster1, 'Fallback image created');
|
equal(posterImage.fallbackImg_.src, this.poster1, 'Fallback image created');
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import Flash from '../../src/js/tech/flash.js';
|
import Flash from '../../../src/js/tech/flash.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
q.module('Flash');
|
q.module('Flash');
|
@ -1,7 +1,7 @@
|
|||||||
var player, tech, el;
|
var player, tech, el;
|
||||||
|
|
||||||
import Html5 from '../../src/js/tech/html5.js';
|
import Html5 from '../../../src/js/tech/html5.js';
|
||||||
import * as Lib from '../../src/js/lib.js';
|
import * as browser from '../../../src/js/utils/browser.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
q.module('HTML5', {
|
q.module('HTML5', {
|
||||||
@ -40,15 +40,15 @@ test('should detect whether the volume can be changed', function(){
|
|||||||
ok(true, 'your browser does not support this test, skipping it');
|
ok(true, 'your browser does not support this test, skipping it');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
testVid = Lib.TEST_VID;
|
testVid = Html5.TEST_VID;
|
||||||
ConstVolumeVideo = function(){
|
ConstVolumeVideo = function(){
|
||||||
this.volume = 1;
|
this.volume = 1;
|
||||||
this.__defineSetter__('volume', function(){});
|
this.__defineSetter__('volume', function(){});
|
||||||
};
|
};
|
||||||
Lib.TEST_VID = new ConstVolumeVideo();
|
Html5.TEST_VID = new ConstVolumeVideo();
|
||||||
|
|
||||||
ok(!Html5.canControlVolume());
|
ok(!Html5.canControlVolume());
|
||||||
Lib.TEST_VID = testVid;
|
Html5.TEST_VID = testVid;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test playbackRate', function() {
|
test('test playbackRate', function() {
|
||||||
@ -79,7 +79,7 @@ test('should remove the controls attribute when recreating the element', functio
|
|||||||
el = tech.createEl();
|
el = tech.createEl();
|
||||||
|
|
||||||
// On the iPhone controls are always true
|
// On the iPhone controls are always true
|
||||||
if (!Lib.IS_IPHONE) {
|
if (!browser.IS_IPHONE) {
|
||||||
ok(!el.controls, 'controls attribute is absent');
|
ok(!el.controls, 'controls attribute is absent');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,13 +90,13 @@ test('patchCanPlayType patches canplaytype with our function, conditionally', fu
|
|||||||
// the patch runs automatically so we need to first unpatch
|
// the patch runs automatically so we need to first unpatch
|
||||||
Html5.unpatchCanPlayType();
|
Html5.unpatchCanPlayType();
|
||||||
|
|
||||||
var oldAV = Lib.ANDROID_VERSION,
|
var oldAV = browser.ANDROID_VERSION,
|
||||||
video = document.createElement('video'),
|
video = document.createElement('video'),
|
||||||
canPlayType = Lib.TEST_VID.constructor.prototype.canPlayType,
|
canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType,
|
||||||
patchedCanPlayType,
|
patchedCanPlayType,
|
||||||
unpatchedCanPlayType;
|
unpatchedCanPlayType;
|
||||||
|
|
||||||
Lib.ANDROID_VERSION = 4.0;
|
browser.ANDROID_VERSION = 4.0;
|
||||||
Html5.patchCanPlayType();
|
Html5.patchCanPlayType();
|
||||||
|
|
||||||
notStrictEqual(video.canPlayType, canPlayType, 'original canPlayType and patched canPlayType should not be equal');
|
notStrictEqual(video.canPlayType, canPlayType, 'original canPlayType and patched canPlayType should not be equal');
|
||||||
@ -104,18 +104,18 @@ test('patchCanPlayType patches canplaytype with our function, conditionally', fu
|
|||||||
patchedCanPlayType = video.canPlayType;
|
patchedCanPlayType = video.canPlayType;
|
||||||
unpatchedCanPlayType = Html5.unpatchCanPlayType();
|
unpatchedCanPlayType = Html5.unpatchCanPlayType();
|
||||||
|
|
||||||
strictEqual(canPlayType, Lib.TEST_VID.constructor.prototype.canPlayType, 'original canPlayType and unpatched canPlayType should be equal');
|
strictEqual(canPlayType, Html5.TEST_VID.constructor.prototype.canPlayType, 'original canPlayType and unpatched canPlayType should be equal');
|
||||||
strictEqual(patchedCanPlayType, unpatchedCanPlayType, 'patched canPlayType and function returned from unpatch are equal');
|
strictEqual(patchedCanPlayType, unpatchedCanPlayType, 'patched canPlayType and function returned from unpatch are equal');
|
||||||
|
|
||||||
Lib.ANDROID_VERSION = oldAV;
|
browser.ANDROID_VERSION = oldAV;
|
||||||
Html5.unpatchCanPlayType();
|
Html5.unpatchCanPlayType();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return maybe for HLS urls on Android 4.0 or above', function() {
|
test('should return maybe for HLS urls on Android 4.0 or above', function() {
|
||||||
var oldAV = Lib.ANDROID_VERSION,
|
var oldAV = browser.ANDROID_VERSION,
|
||||||
video = document.createElement('video');
|
video = document.createElement('video');
|
||||||
|
|
||||||
Lib.ANDROID_VERSION = 4.0;
|
browser.ANDROID_VERSION = 4.0;
|
||||||
Html5.patchCanPlayType();
|
Html5.patchCanPlayType();
|
||||||
|
|
||||||
strictEqual(video.canPlayType('application/x-mpegurl'), 'maybe', 'android version 4.0 or above should be a maybe for x-mpegurl');
|
strictEqual(video.canPlayType('application/x-mpegurl'), 'maybe', 'android version 4.0 or above should be a maybe for x-mpegurl');
|
||||||
@ -123,20 +123,20 @@ test('should return maybe for HLS urls on Android 4.0 or above', function() {
|
|||||||
strictEqual(video.canPlayType('application/vnd.apple.mpegurl'), 'maybe', 'android version 4.0 or above should be a maybe for vnd.apple.mpegurl');
|
strictEqual(video.canPlayType('application/vnd.apple.mpegurl'), 'maybe', 'android version 4.0 or above should be a maybe for vnd.apple.mpegurl');
|
||||||
strictEqual(video.canPlayType('application/vnd.apple.mpegURL'), 'maybe', 'android version 4.0 or above should be a maybe for vnd.apple.mpegurl');
|
strictEqual(video.canPlayType('application/vnd.apple.mpegURL'), 'maybe', 'android version 4.0 or above should be a maybe for vnd.apple.mpegurl');
|
||||||
|
|
||||||
Lib.ANDROID_VERSION = oldAV;
|
browser.ANDROID_VERSION = oldAV;
|
||||||
Html5.unpatchCanPlayType();
|
Html5.unpatchCanPlayType();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return a maybe for mp4 on OLD ANDROID', function() {
|
test('should return a maybe for mp4 on OLD ANDROID', function() {
|
||||||
var isOldAndroid = Lib.IS_OLD_ANDROID,
|
var isOldAndroid = browser.IS_OLD_ANDROID,
|
||||||
video = document.createElement('video');
|
video = document.createElement('video');
|
||||||
|
|
||||||
Lib.IS_OLD_ANDROID = true;
|
browser.IS_OLD_ANDROID = true;
|
||||||
Html5.patchCanPlayType();
|
Html5.patchCanPlayType();
|
||||||
|
|
||||||
strictEqual(video.canPlayType('video/mp4'), 'maybe', 'old android should return a maybe for video/mp4');
|
strictEqual(video.canPlayType('video/mp4'), 'maybe', 'old android should return a maybe for video/mp4');
|
||||||
|
|
||||||
Lib.IS_OLD_ANDROID = isOldAndroid;
|
browser.IS_OLD_ANDROID = isOldAndroid;
|
||||||
Html5.unpatchCanPlayType();
|
Html5.unpatchCanPlayType();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,8 +154,8 @@ test('native source handler canHandleSource', function(){
|
|||||||
var result;
|
var result;
|
||||||
|
|
||||||
// Stub the test video canPlayType (used in canHandleSource) to control results
|
// Stub the test video canPlayType (used in canHandleSource) to control results
|
||||||
var origCPT = Lib.TEST_VID.canPlayType;
|
var origCPT = Html5.TEST_VID.canPlayType;
|
||||||
Lib.TEST_VID.canPlayType = function(type){
|
Html5.TEST_VID.canPlayType = function(type){
|
||||||
if (type === 'video/mp4') {
|
if (type === 'video/mp4') {
|
||||||
return 'maybe';
|
return 'maybe';
|
||||||
}
|
}
|
||||||
@ -176,5 +176,5 @@ test('native source handler canHandleSource', function(){
|
|||||||
equal(canHandleSource({ type: 'foo' }), '', 'Native source handler handled bad type');
|
equal(canHandleSource({ type: 'foo' }), '', 'Native source handler handled bad type');
|
||||||
|
|
||||||
// Reset test video canPlayType
|
// Reset test video canPlayType
|
||||||
Lib.TEST_VID.canPlayType = origCPT;
|
Html5.TEST_VID.canPlayType = origCPT;
|
||||||
});
|
});
|
@ -1,14 +1,12 @@
|
|||||||
// Fake a media playback tech controller so that player tests
|
// Fake a media playback tech controller so that player tests
|
||||||
// can run without HTML5 or Flash, of which PhantomJS supports neither.
|
// can run without HTML5 or Flash, of which PhantomJS supports neither.
|
||||||
|
|
||||||
import Tech from '../../src/js/tech/tech.js';
|
import Tech from '../../../src/js/tech/tech.js';
|
||||||
import * as Lib from '../../src/js/lib.js';
|
|
||||||
import Component from '../../src/js/component.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
class MediaFaker extends Tech {
|
class TechFaker extends Tech {
|
||||||
|
|
||||||
constructor(options, handleReady){
|
constructor(options, handleReady){
|
||||||
super(options, handleReady);
|
super(options, handleReady);
|
||||||
@ -53,5 +51,5 @@ class MediaFaker extends Tech {
|
|||||||
static canPlaySource(srcObj) { return srcObj.type !== 'video/unsupported-format'; }
|
static canPlaySource(srcObj) { return srcObj.type !== 'video/unsupported-format'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.registerComponent('MediaFaker', MediaFaker);
|
Tech.registerComponent('TechFaker', TechFaker);
|
||||||
module.exports = MediaFaker;
|
export default TechFaker;
|
@ -1,6 +1,6 @@
|
|||||||
var noop = function() {}, clock, oldTextTracks;
|
var noop = function() {}, clock, oldTextTracks;
|
||||||
|
|
||||||
import Tech from '../../src/js/tech/tech.js';
|
import Tech from '../../../src/js/tech/tech.js';
|
||||||
|
|
||||||
q.module('Media Tech', {
|
q.module('Media Tech', {
|
||||||
'setup': function() {
|
'setup': function() {
|
@ -1,5 +1,5 @@
|
|||||||
import Player from '../../src/js/player.js';
|
import Player from '../../src/js/player.js';
|
||||||
import MediaFaker from './mediafaker.js';
|
import TechFaker from './tech/tech-faker.js';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ var TestHelpers = {
|
|||||||
fixture.appendChild(videoTag);
|
fixture.appendChild(videoTag);
|
||||||
|
|
||||||
playerOptions = playerOptions || {};
|
playerOptions = playerOptions || {};
|
||||||
playerOptions['techOrder'] = playerOptions['techOrder'] || ['mediaFaker'];
|
playerOptions['techOrder'] = playerOptions['techOrder'] || ['techFaker'];
|
||||||
|
|
||||||
return player = new Player(videoTag, playerOptions);
|
return player = new Player(videoTag, playerOptions);
|
||||||
},
|
},
|
||||||
@ -39,4 +39,4 @@ var TestHelpers = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TestHelpers;
|
export default TestHelpers;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import TextTrackMenuItem from '../../../src/js/control-bar/text-track-controls/text-track-menu-item.js';
|
import TextTrackMenuItem from '../../../src/js/control-bar/text-track-controls/text-track-menu-item.js';
|
||||||
import TestHelpers from '../test-helpers.js';
|
import TestHelpers from '../test-helpers.js';
|
||||||
import * as Lib from '../../../src/js/lib.js';
|
import * as browser from '../../../src/js/utils/browser.js';
|
||||||
|
|
||||||
q.module('Text Track Controls');
|
q.module('Text Track Controls');
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ test('menu should update with removeRemoteTextTrack', function() {
|
|||||||
equal(player.textTracks().length, 1, 'textTracks contains one item');
|
equal(player.textTracks().length, 1, 'textTracks contains one item');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Lib.IS_IE8) {
|
if (!browser.IS_IE8) {
|
||||||
// This test doesn't work on IE8.
|
// This test doesn't work on IE8.
|
||||||
// However, this test tests a specific with iOS7 where the TextTrackList doesn't report track mode changes.
|
// However, this test tests a specific with iOS7 where the TextTrackList doesn't report track mode changes.
|
||||||
// TODO: figure out why this test doens't work on IE8. https://github.com/videojs/video.js/issues/1861
|
// TODO: figure out why this test doens't work on IE8. https://github.com/videojs/video.js/issues/1861
|
@ -1,6 +1,6 @@
|
|||||||
import TextTrackSettings from '../../../src/js/tracks/text-track-settings.js';
|
import TextTrackSettings from '../../../src/js/tracks/text-track-settings.js';
|
||||||
import TestHelpers from '../test-helpers.js';
|
import TestHelpers from '../test-helpers.js';
|
||||||
import * as Events from '../../../src/js/events.js';
|
import * as Events from '../../../src/js/utils/events.js';
|
||||||
import safeParseTuple from 'safe-json-parse/tuple';
|
import safeParseTuple from 'safe-json-parse/tuple';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
|
@ -8,7 +8,7 @@ import Flash from '../../../src/js/tech/flash.js';
|
|||||||
import Tech from '../../../src/js/tech/tech.js';
|
import Tech from '../../../src/js/tech/tech.js';
|
||||||
import Component from '../../../src/js/component.js';
|
import Component from '../../../src/js/component.js';
|
||||||
|
|
||||||
import * as Lib from '../../../src/js/lib.js';
|
import * as browser from '../../../src/js/utils/browser.js';
|
||||||
import TestHelpers from '../test-helpers.js';
|
import TestHelpers from '../test-helpers.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
@ -66,21 +66,8 @@ test('TextTrackDisplay initializes tracks on player ready', function() {
|
|||||||
equal(calls, 1, 'only a player.ready call was made');
|
equal(calls, 1, 'only a player.ready call was made');
|
||||||
});
|
});
|
||||||
|
|
||||||
// This is a bad test that breaks in Firefox because we disable FF for other reasons.
|
|
||||||
// test('html5 tech supports native text tracks if the video supports it', function() {
|
|
||||||
// var oldTestVid = Lib.TEST_VID;
|
|
||||||
//
|
|
||||||
// Lib.TEST_VID = {
|
|
||||||
// textTracks: []
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// ok(Html5.supportsNativeTextTracks(), 'if textTracks are available on video element, native text tracks are supported');
|
|
||||||
//
|
|
||||||
// Lib.TEST_VID = oldTestVid;
|
|
||||||
// });
|
|
||||||
|
|
||||||
test('listen to remove and add track events in native text tracks', function() {
|
test('listen to remove and add track events in native text tracks', function() {
|
||||||
var oldTestVid = Lib.TEST_VID,
|
var oldTestVid = Html5.TEST_VID,
|
||||||
player,
|
player,
|
||||||
options,
|
options,
|
||||||
oldTextTracks,
|
oldTextTracks,
|
||||||
@ -96,7 +83,7 @@ test('listen to remove and add track events in native text tracks', function() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Lib.TEST_VID = {
|
Html5.TEST_VID = {
|
||||||
textTracks: []
|
textTracks: []
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,7 +111,7 @@ test('listen to remove and add track events in native text tracks', function() {
|
|||||||
ok(events['removetrack'], 'removetrack listener was added');
|
ok(events['removetrack'], 'removetrack listener was added');
|
||||||
ok(events['addtrack'], 'addtrack listener was added');
|
ok(events['addtrack'], 'addtrack listener was added');
|
||||||
|
|
||||||
Lib.TEST_VID = oldTestVid;
|
Html5.TEST_VID = oldTestVid;
|
||||||
Html5.prototype.textTracks = oldTextTracks;
|
Html5.prototype.textTracks = oldTextTracks;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -210,8 +197,8 @@ test('update texttrack buttons on removetrack or addtrack', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('if native text tracks are not supported, create a texttrackdisplay', function() {
|
test('if native text tracks are not supported, create a texttrackdisplay', function() {
|
||||||
var oldTestVid = Lib.TEST_VID,
|
var oldTestVid = Html5.TEST_VID,
|
||||||
oldIsFirefox = Lib.IS_FIREFOX,
|
oldIsFirefox = browser.IS_FIREFOX,
|
||||||
oldTextTrackDisplay = Component.getComponent('TextTrackDisplay'),
|
oldTextTrackDisplay = Component.getComponent('TextTrackDisplay'),
|
||||||
called = false,
|
called = false,
|
||||||
player,
|
player,
|
||||||
@ -234,11 +221,11 @@ test('if native text tracks are not supported, create a texttrackdisplay', funct
|
|||||||
track.src = 'es.vtt';
|
track.src = 'es.vtt';
|
||||||
tag.appendChild(track);
|
tag.appendChild(track);
|
||||||
|
|
||||||
Lib.TEST_VID = {
|
Html5.TEST_VID = {
|
||||||
textTracks: []
|
textTracks: []
|
||||||
};
|
};
|
||||||
|
|
||||||
Lib.IS_FIREFOX = true;
|
browser.IS_FIREFOX = true;
|
||||||
Component.registerComponent('TextTrackDisplay', function() {
|
Component.registerComponent('TextTrackDisplay', function() {
|
||||||
called = true;
|
called = true;
|
||||||
});
|
});
|
||||||
@ -247,8 +234,8 @@ test('if native text tracks are not supported, create a texttrackdisplay', funct
|
|||||||
|
|
||||||
ok(called, 'text track display was created');
|
ok(called, 'text track display was created');
|
||||||
|
|
||||||
Lib.TEST_VID = oldTestVid;
|
Html5.TEST_VID = oldTestVid;
|
||||||
Lib.IS_FIREFOX = oldIsFirefox;
|
browser.IS_FIREFOX = oldIsFirefox;
|
||||||
Component.registerComponent('TextTrackDisplay', oldTextTrackDisplay);
|
Component.registerComponent('TextTrackDisplay', oldTextTrackDisplay);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -272,9 +259,9 @@ test('Player track methods call the tech', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('html5 tech supports native text tracks if the video supports it, unless mode is a number', function() {
|
test('html5 tech supports native text tracks if the video supports it, unless mode is a number', function() {
|
||||||
var oldTestVid = Lib.TEST_VID;
|
var oldTestVid = Html5.TEST_VID;
|
||||||
|
|
||||||
Lib.TEST_VID = {
|
Html5.TEST_VID = {
|
||||||
textTracks: [{
|
textTracks: [{
|
||||||
mode: 0
|
mode: 0
|
||||||
}]
|
}]
|
||||||
@ -282,23 +269,23 @@ test('html5 tech supports native text tracks if the video supports it, unless mo
|
|||||||
|
|
||||||
ok(!Html5.supportsNativeTextTracks(), 'native text tracks are not supported if mode is a number');
|
ok(!Html5.supportsNativeTextTracks(), 'native text tracks are not supported if mode is a number');
|
||||||
|
|
||||||
Lib.TEST_VID = oldTestVid;
|
Html5.TEST_VID = oldTestVid;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('html5 tech supports native text tracks if the video supports it, unless it is firefox', function() {
|
test('html5 tech supports native text tracks if the video supports it, unless it is firefox', function() {
|
||||||
var oldTestVid = Lib.TEST_VID,
|
var oldTestVid = Html5.TEST_VID,
|
||||||
oldIsFirefox = Lib.IS_FIREFOX;
|
oldIsFirefox = browser.IS_FIREFOX;
|
||||||
|
|
||||||
Lib.TEST_VID = {
|
Html5.TEST_VID = {
|
||||||
textTracks: []
|
textTracks: []
|
||||||
};
|
};
|
||||||
|
|
||||||
Lib.IS_FIREFOX = true;
|
browser.IS_FIREFOX = true;
|
||||||
|
|
||||||
ok(!Html5.supportsNativeTextTracks(), 'if textTracks are available on video element, native text tracks are supported');
|
ok(!Html5.supportsNativeTextTracks(), 'if textTracks are available on video element, native text tracks are supported');
|
||||||
|
|
||||||
Lib.TEST_VID = oldTestVid;
|
Html5.TEST_VID = oldTestVid;
|
||||||
Lib.IS_FIREFOX = oldIsFirefox;
|
browser.IS_FIREFOX = oldIsFirefox;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when switching techs, we should not get a new text track', function() {
|
test('when switching techs, we should not get a new text track', function() {
|
178
test/unit/utils/dom.test.js
Normal file
178
test/unit/utils/dom.test.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import document from 'global/document';
|
||||||
|
import * as Dom from '../../../src/js/utils/dom.js';
|
||||||
|
|
||||||
|
test('should return the element with the ID', function(){
|
||||||
|
var el1 = document.createElement('div');
|
||||||
|
var el2 = document.createElement('div');
|
||||||
|
var fixture = document.getElementById('qunit-fixture');
|
||||||
|
|
||||||
|
fixture.appendChild(el1);
|
||||||
|
fixture.appendChild(el2);
|
||||||
|
|
||||||
|
el1.id = 'test_id1';
|
||||||
|
el2.id = 'test_id2';
|
||||||
|
|
||||||
|
ok(Dom.el('test_id1') === el1, 'found element for ID');
|
||||||
|
ok(Dom.el('#test_id2') === el2, 'found element for CSS ID');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create an element', function(){
|
||||||
|
var div = Dom.createEl();
|
||||||
|
var span = Dom.createEl('span', { 'data-test': 'asdf', innerHTML:'fdsa' });
|
||||||
|
ok(div.nodeName === 'DIV');
|
||||||
|
ok(span.nodeName === 'SPAN');
|
||||||
|
ok(span['data-test'] === 'asdf');
|
||||||
|
ok(span.innerHTML === 'fdsa');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should insert an element first in another', function(){
|
||||||
|
var el1 = document.createElement('div');
|
||||||
|
var el2 = document.createElement('div');
|
||||||
|
var parent = document.createElement('div');
|
||||||
|
|
||||||
|
Dom.insertFirst(el1, parent);
|
||||||
|
ok(parent.firstChild === el1, 'inserts first into empty parent');
|
||||||
|
|
||||||
|
Dom.insertFirst(el2, parent);
|
||||||
|
ok(parent.firstChild === el2, 'inserts first into parent with child');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get and remove data from an element', function(){
|
||||||
|
var el = document.createElement('div');
|
||||||
|
var data = Dom.getData(el);
|
||||||
|
var id = el[Dom.expando];
|
||||||
|
|
||||||
|
ok(typeof data === 'object', 'data object created');
|
||||||
|
|
||||||
|
// Add data
|
||||||
|
var testData = { asdf: 'fdsa' };
|
||||||
|
data.test = testData;
|
||||||
|
ok(Dom.getData(el).test === testData, 'data added');
|
||||||
|
|
||||||
|
// Remove all data
|
||||||
|
Dom.removeData(el);
|
||||||
|
|
||||||
|
ok(!Dom.cache[id], 'cached item nulled');
|
||||||
|
ok(el[Dom.expando] === null || el[Dom.expando] === undefined, 'element data id removed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should add and remove a class name on an element', function(){
|
||||||
|
var el = document.createElement('div');
|
||||||
|
Dom.addClass(el, 'test-class');
|
||||||
|
ok(el.className === 'test-class', 'class added');
|
||||||
|
Dom.addClass(el, 'test-class');
|
||||||
|
ok(el.className === 'test-class', 'same class not duplicated');
|
||||||
|
Dom.addClass(el, 'test-class2');
|
||||||
|
ok(el.className === 'test-class test-class2', 'added second class');
|
||||||
|
Dom.removeClass(el, 'test-class');
|
||||||
|
ok(el.className === 'test-class2', 'removed first class');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should read class names on an element', function(){
|
||||||
|
var el = document.createElement('div');
|
||||||
|
Dom.addClass(el, 'test-class1');
|
||||||
|
ok(Dom.hasClass(el, 'test-class1') === true, 'class detected');
|
||||||
|
ok(Dom.hasClass(el, 'test-class') === false, 'substring correctly not detected');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set element attributes from object', function(){
|
||||||
|
var el, vid1Vals;
|
||||||
|
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.id = 'el1';
|
||||||
|
|
||||||
|
Dom.setElementAttributes(el, { controls: true, 'data-test': 'asdf' });
|
||||||
|
|
||||||
|
equal(el.getAttribute('id'), 'el1');
|
||||||
|
equal(el.getAttribute('controls'), '');
|
||||||
|
equal(el.getAttribute('data-test'), 'asdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should read tag attributes from elements, including HTML5 in all browsers', function(){
|
||||||
|
var tags = '<video id="vid1" controls autoplay loop muted preload="none" src="http://google.com" poster="http://www2.videojs.com/img/video-js-html5-video-player.png" data-test="asdf" data-empty-string=""></video>';
|
||||||
|
tags += '<video id="vid2">';
|
||||||
|
// Not putting source and track inside video element because
|
||||||
|
// oldIE needs the HTML5 shim to read tags inside HTML5 tags.
|
||||||
|
// Still may not work in oldIE.
|
||||||
|
tags += '<source id="source" src="http://google.com" type="video/mp4" media="fdsa" title="test" >';
|
||||||
|
tags += '<track id="track" default src="http://google.com" kind="captions" srclang="en" label="testlabel" title="test" >';
|
||||||
|
tags += '</video>';
|
||||||
|
|
||||||
|
document.getElementById('qunit-fixture').innerHTML += tags;
|
||||||
|
|
||||||
|
var vid1Vals = Dom.getElementAttributes(document.getElementById('vid1'));
|
||||||
|
var vid2Vals = Dom.getElementAttributes(document.getElementById('vid2'));
|
||||||
|
var sourceVals = Dom.getElementAttributes(document.getElementById('source'));
|
||||||
|
var trackVals = Dom.getElementAttributes(document.getElementById('track'));
|
||||||
|
|
||||||
|
// was using deepEqual, but ie8 would send all properties as attributes
|
||||||
|
|
||||||
|
// vid1
|
||||||
|
equal(vid1Vals['autoplay'], true);
|
||||||
|
equal(vid1Vals['controls'], true);
|
||||||
|
equal(vid1Vals['data-test'], 'asdf');
|
||||||
|
equal(vid1Vals['data-empty-string'], '');
|
||||||
|
equal(vid1Vals['id'], 'vid1');
|
||||||
|
equal(vid1Vals['loop'], true);
|
||||||
|
equal(vid1Vals['muted'], true);
|
||||||
|
equal(vid1Vals['poster'], 'http://www2.videojs.com/img/video-js-html5-video-player.png');
|
||||||
|
equal(vid1Vals['preload'], 'none');
|
||||||
|
equal(vid1Vals['src'], 'http://google.com');
|
||||||
|
|
||||||
|
// vid2
|
||||||
|
equal(vid2Vals['id'], 'vid2');
|
||||||
|
|
||||||
|
// sourceVals
|
||||||
|
equal(sourceVals['title'], 'test');
|
||||||
|
equal(sourceVals['media'], 'fdsa');
|
||||||
|
equal(sourceVals['type'], 'video/mp4');
|
||||||
|
equal(sourceVals['src'], 'http://google.com');
|
||||||
|
equal(sourceVals['id'], 'source');
|
||||||
|
|
||||||
|
// trackVals
|
||||||
|
equal(trackVals['default'], true);
|
||||||
|
equal(trackVals['id'], 'track');
|
||||||
|
equal(trackVals['kind'], 'captions');
|
||||||
|
equal(trackVals['label'], 'testlabel');
|
||||||
|
equal(trackVals['src'], 'http://google.com');
|
||||||
|
equal(trackVals['srclang'], 'en');
|
||||||
|
equal(trackVals['title'], 'test');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get the right style values for an element', function(){
|
||||||
|
var el = document.createElement('div');
|
||||||
|
var container = document.createElement('div');
|
||||||
|
var fixture = document.getElementById('qunit-fixture');
|
||||||
|
|
||||||
|
container.appendChild(el);
|
||||||
|
fixture.appendChild(container);
|
||||||
|
|
||||||
|
container.style.width = '1000px';
|
||||||
|
container.style.height = '1000px';
|
||||||
|
|
||||||
|
el.style.height = '100%';
|
||||||
|
el.style.width = '123px';
|
||||||
|
|
||||||
|
// integer px values may get translated int very-close floats in Chrome/OS X
|
||||||
|
// so round the dimensions to ignore this
|
||||||
|
equal(Math.round(parseFloat(Dom.getComputedDimension(el, 'height'))), 1000, 'the computed height is equal');
|
||||||
|
equal(Math.round(parseFloat(Dom.getComputedDimension(el, 'width'))), 123, 'the computed width is equal');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Dom.findPosition should find top and left position', function() {
|
||||||
|
const d = document.createElement('div');
|
||||||
|
let position = Dom.findPosition(d);
|
||||||
|
d.style.top = '10px';
|
||||||
|
d.style.left = '20px';
|
||||||
|
d.style.position = 'absolute';
|
||||||
|
|
||||||
|
deepEqual(position, {left: 0, top: 0}, 'If element isn\'t in the DOM, we should get zeros');
|
||||||
|
|
||||||
|
document.body.appendChild(d);
|
||||||
|
position = Dom.findPosition(d);
|
||||||
|
deepEqual(position, {left: 20, top: 10}, 'The position was not correct');
|
||||||
|
|
||||||
|
d.getBoundingClientRect = null;
|
||||||
|
position = Dom.findPosition(d);
|
||||||
|
deepEqual(position, {left: 0, top: 0}, 'If there is no gBCR, we should get zeros');
|
||||||
|
});
|
11
test/unit/utils/fn.test.js
Normal file
11
test/unit/utils/fn.test.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as Fn from '../../../src/js/utils/fn.js';
|
||||||
|
|
||||||
|
test('should add context to a function', function(){
|
||||||
|
var newContext = { test: 'obj'};
|
||||||
|
var asdf = function(){
|
||||||
|
ok(this === newContext);
|
||||||
|
};
|
||||||
|
var fdsa = Fn.bind(newContext, asdf);
|
||||||
|
|
||||||
|
fdsa();
|
||||||
|
});
|
29
test/unit/utils/format-time.test.js
Normal file
29
test/unit/utils/format-time.test.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import formatTime from '../../../src/js/utils/format-time.js';
|
||||||
|
|
||||||
|
test('should format time as a string', function(){
|
||||||
|
ok(formatTime(1) === '0:01');
|
||||||
|
ok(formatTime(10) === '0:10');
|
||||||
|
ok(formatTime(60) === '1:00');
|
||||||
|
ok(formatTime(600) === '10:00');
|
||||||
|
ok(formatTime(3600) === '1:00:00');
|
||||||
|
ok(formatTime(36000) === '10:00:00');
|
||||||
|
ok(formatTime(360000) === '100:00:00');
|
||||||
|
|
||||||
|
// Using guide should provide extra leading zeros
|
||||||
|
ok(formatTime(1,1) === '0:01');
|
||||||
|
ok(formatTime(1,10) === '0:01');
|
||||||
|
ok(formatTime(1,60) === '0:01');
|
||||||
|
ok(formatTime(1,600) === '00:01');
|
||||||
|
ok(formatTime(1,3600) === '0:00:01');
|
||||||
|
// Don't do extra leading zeros for hours
|
||||||
|
ok(formatTime(1,36000) === '0:00:01');
|
||||||
|
ok(formatTime(1,360000) === '0:00:01');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format invalid times as dashes', function(){
|
||||||
|
equal(formatTime(Infinity, 90), '-:-');
|
||||||
|
equal(formatTime(NaN), '-:-');
|
||||||
|
// equal(formatTime(NaN, 216000), '-:--:--');
|
||||||
|
equal(formatTime(10, Infinity), '0:00:10');
|
||||||
|
equal(formatTime(90, NaN), '1:30');
|
||||||
|
});
|
54
test/unit/utils/log.test.js
Normal file
54
test/unit/utils/log.test.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import log from '../../../src/js/utils/log.js';
|
||||||
|
import window from 'global/window';
|
||||||
|
|
||||||
|
test('should confirm logging functions work', function() {
|
||||||
|
let origConsole = window['console'];
|
||||||
|
// replace the native console for testing
|
||||||
|
// in ie8 console.log is apparently not a 'function' so sinon chokes on it
|
||||||
|
// https://github.com/cjohansen/Sinon.JS/issues/386
|
||||||
|
// instead we'll temporarily replace them with no-op functions
|
||||||
|
let console = window['console'] = {
|
||||||
|
log: function(){},
|
||||||
|
warn: function(){},
|
||||||
|
error: function(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
// stub the global log functions
|
||||||
|
let logStub = sinon.stub(console, 'log');
|
||||||
|
let errorStub = sinon.stub(console, 'error');
|
||||||
|
let warnStub = sinon.stub(console, 'warn');
|
||||||
|
|
||||||
|
// Reset the log history
|
||||||
|
log.history = [];
|
||||||
|
|
||||||
|
log('log1', 'log2');
|
||||||
|
log.warn('warn1', 'warn2');
|
||||||
|
log.error('error1', 'error2');
|
||||||
|
|
||||||
|
ok(logStub.called, 'log was called');
|
||||||
|
equal(logStub.firstCall.args[0], 'VIDEOJS:');
|
||||||
|
equal(logStub.firstCall.args[1], 'log1');
|
||||||
|
equal(logStub.firstCall.args[2], 'log2');
|
||||||
|
|
||||||
|
ok(warnStub.called, 'warn was called');
|
||||||
|
equal(warnStub.firstCall.args[0], 'VIDEOJS:');
|
||||||
|
equal(warnStub.firstCall.args[1], 'WARN:');
|
||||||
|
equal(warnStub.firstCall.args[2], 'warn1');
|
||||||
|
equal(warnStub.firstCall.args[3], 'warn2');
|
||||||
|
|
||||||
|
ok(errorStub.called, 'error was called');
|
||||||
|
equal(errorStub.firstCall.args[0], 'VIDEOJS:');
|
||||||
|
equal(errorStub.firstCall.args[1], 'ERROR:');
|
||||||
|
equal(errorStub.firstCall.args[2], 'error1');
|
||||||
|
equal(errorStub.firstCall.args[3], 'error2');
|
||||||
|
|
||||||
|
equal(log.history.length, 3, 'there should be three messages in the log history');
|
||||||
|
|
||||||
|
// tear down sinon
|
||||||
|
logStub.restore();
|
||||||
|
errorStub.restore();
|
||||||
|
warnStub.restore();
|
||||||
|
|
||||||
|
// restore the native console
|
||||||
|
window['console'] = origConsole;
|
||||||
|
});
|
@ -1,6 +1,4 @@
|
|||||||
import * as Util from '../../src/js/util.js';
|
import mergeOptions from '../../../src/js/utils/merge-options.js';
|
||||||
|
|
||||||
q.module('Util');
|
|
||||||
|
|
||||||
test('should merge options objects', function(){
|
test('should merge options objects', function(){
|
||||||
var ob1, ob2, ob3;
|
var ob1, ob2, ob3;
|
||||||
@ -20,7 +18,7 @@ test('should merge options objects', function(){
|
|||||||
d: true
|
d: true
|
||||||
};
|
};
|
||||||
|
|
||||||
ob3 = Util.mergeOptions(ob1, ob2);
|
ob3 = mergeOptions(ob1, ob2);
|
||||||
|
|
||||||
deepEqual(ob3, {
|
deepEqual(ob3, {
|
||||||
a: false,
|
a: false,
|
8
test/unit/utils/round-float.test.js
Normal file
8
test/unit/utils/round-float.test.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import roundFloat from '../../../src/js/utils/round-float.js';
|
||||||
|
|
||||||
|
test('should round a number', function(){
|
||||||
|
ok(roundFloat(1.01) === 1);
|
||||||
|
ok(roundFloat(1.5) === 2);
|
||||||
|
ok(roundFloat(1.55, 2) === 1.55);
|
||||||
|
ok(roundFloat(10.551, 2) === 10.55);
|
||||||
|
});
|
7
test/unit/utils/time-ranges.test.js
Normal file
7
test/unit/utils/time-ranges.test.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
|
||||||
|
|
||||||
|
test('should create a fake timerange', function(){
|
||||||
|
var tr = createTimeRange(0, 10);
|
||||||
|
ok(tr.start() === 0);
|
||||||
|
ok(tr.end() === 10);
|
||||||
|
});
|
6
test/unit/utils/to-title-case.test.js
Normal file
6
test/unit/utils/to-title-case.test.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import toTitleCase from '../../../src/js/utils/to-title-case.js';
|
||||||
|
|
||||||
|
test('should make a string start with an uppercase letter', function(){
|
||||||
|
var foo = toTitleCase('bar');
|
||||||
|
ok(foo === 'Bar');
|
||||||
|
});
|
65
test/unit/utils/url.test.js
Normal file
65
test/unit/utils/url.test.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import document from 'global/document';
|
||||||
|
import window from 'global/window';
|
||||||
|
import * as Url from '../../../src/js/utils/url.js';
|
||||||
|
|
||||||
|
test('should parse the details of a url correctly', function(){
|
||||||
|
equal(Url.parseUrl('#').protocol, window.location.protocol, 'parsed relative url protocol');
|
||||||
|
equal(Url.parseUrl('#').host, window.location.host, 'parsed relative url host');
|
||||||
|
|
||||||
|
equal(Url.parseUrl('http://example.com').protocol, 'http:', 'parsed example url protocol');
|
||||||
|
equal(Url.parseUrl('http://example.com').hostname, 'example.com', 'parsed example url hostname');
|
||||||
|
|
||||||
|
equal(Url.parseUrl('http://example.com:1234').port, '1234', 'parsed example url port');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should strip port from hosts using http or https', function() {
|
||||||
|
var url;
|
||||||
|
var origDocCreate = document.createElement;
|
||||||
|
|
||||||
|
// attempts to create elements will return an anchor tag that
|
||||||
|
// misbehaves like IE9
|
||||||
|
document.createElement = function() {
|
||||||
|
return {
|
||||||
|
hostname: 'example.com',
|
||||||
|
host: 'example.com:80',
|
||||||
|
protocol: 'http:',
|
||||||
|
port: '80',
|
||||||
|
pathname: '/domain/relative/url',
|
||||||
|
hash: ''
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
url = Url.parseUrl('/domain/relative/url');
|
||||||
|
ok(!(/.*:80$/).test(url.host), ':80 is not appended to the host');
|
||||||
|
|
||||||
|
document.createElement = origDocCreate;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get an absolute URL', function(){
|
||||||
|
// Errors on compiled tests that don't use unit.html. Need a better solution.
|
||||||
|
// ok(Url.getAbsoluteURL('unit.html') === window.location.href);
|
||||||
|
ok(Url.getAbsoluteURL('http://asdf.com') === 'http://asdf.com');
|
||||||
|
ok(Url.getAbsoluteURL('https://asdf.com/index.html') === 'https://asdf.com/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
//getFileExtension tests
|
||||||
|
test('should get the file extension of the passed path', function() {
|
||||||
|
equal(Url.getFileExtension('/foo/bar/test.video.wgg'), 'wgg');
|
||||||
|
equal(Url.getFileExtension('test./video.mp4'), 'mp4');
|
||||||
|
equal(Url.getFileExtension('.bar/test.video.m4v'), 'm4v');
|
||||||
|
equal(Url.getFileExtension('foo/.bar/test.video.flv'), 'flv');
|
||||||
|
equal(Url.getFileExtension('foo/.bar/test.video.flv?foo=bar'), 'flv');
|
||||||
|
equal(Url.getFileExtension('http://www.test.com/video.mp4'), 'mp4');
|
||||||
|
equal(Url.getFileExtension('http://foo/bar/test.video.wgg'), 'wgg');
|
||||||
|
|
||||||
|
//edge cases
|
||||||
|
equal(Url.getFileExtension('http://...'), '');
|
||||||
|
equal(Url.getFileExtension('foo/.bar/testvideo'), '');
|
||||||
|
equal(Url.getFileExtension(''), '');
|
||||||
|
equal(Url.getFileExtension(null), '');
|
||||||
|
equal(Url.getFileExtension(undefined), '');
|
||||||
|
|
||||||
|
//with capital letters
|
||||||
|
equal(Url.getFileExtension('test.video.MP4'), 'mp4');
|
||||||
|
equal(Url.getFileExtension('test.video.FLV'), 'flv');
|
||||||
|
});
|
@ -1,23 +0,0 @@
|
|||||||
import videojs from '../../src/js/video.js';
|
|
||||||
import TestHelpers from './test-helpers.js';
|
|
||||||
|
|
||||||
q.module('video.js');
|
|
||||||
|
|
||||||
test('should expose plugin registry function', function() {
|
|
||||||
var pluginName, pluginFunction, player;
|
|
||||||
|
|
||||||
pluginName = 'foo';
|
|
||||||
pluginFunction = function(options) {
|
|
||||||
console.log(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
ok(videojs.plugin, 'should exist');
|
|
||||||
|
|
||||||
videojs.plugin(pluginName, pluginFunction);
|
|
||||||
|
|
||||||
player = TestHelpers.makePlayer();
|
|
||||||
|
|
||||||
ok(player.foo, 'should exist');
|
|
||||||
equal(player.foo, pluginFunction, 'should be equal');
|
|
||||||
|
|
||||||
});
|
|
@ -1,10 +1,10 @@
|
|||||||
import videojs from '../../src/js/core.js';
|
import videojs from '../../src/js/video.js';
|
||||||
|
import TestHelpers from './test-helpers.js';
|
||||||
import Player from '../../src/js/player.js';
|
import Player from '../../src/js/player.js';
|
||||||
import * as Lib from '../../src/js/lib.js';
|
|
||||||
import Options from '../../src/js/options.js';
|
import Options from '../../src/js/options.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
q.module('Core');
|
q.module('video.js');
|
||||||
|
|
||||||
test('should create a video tag and have access children in old IE', function(){
|
test('should create a video tag and have access children in old IE', function(){
|
||||||
var fixture = document.getElementById('qunit-fixture');
|
var fixture = document.getElementById('qunit-fixture');
|
||||||
@ -44,4 +44,23 @@ test('should add the value to the languages object', function() {
|
|||||||
ok(Options['languages'][code], 'should exist');
|
ok(Options['languages'][code], 'should exist');
|
||||||
equal(Options['languages'][code], data, 'should match');
|
equal(Options['languages'][code], data, 'should match');
|
||||||
deepEqual(result[code], Options['languages'][code], 'should also match');
|
deepEqual(result[code], Options['languages'][code], 'should also match');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('should expose plugin registry function', function() {
|
||||||
|
var pluginName, pluginFunction, player;
|
||||||
|
|
||||||
|
pluginName = 'foo';
|
||||||
|
pluginFunction = function(options) {
|
||||||
|
console.log(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
ok(videojs.plugin, 'should exist');
|
||||||
|
|
||||||
|
videojs.plugin(pluginName, pluginFunction);
|
||||||
|
|
||||||
|
player = TestHelpers.makePlayer();
|
||||||
|
|
||||||
|
ok(player.foo, 'should exist');
|
||||||
|
equal(player.foo, pluginFunction, 'should be equal');
|
||||||
|
});
|
Reference in New Issue
Block a user