1
0
mirror of https://github.com/videojs/video.js.git synced 2025-03-25 22:01:11 +02:00

Close GH-425: Backing out goog.inherits and adding back the previous inheritance API. fixes #415.

This commit is contained in:
Steve Heffernan 2013-04-09 13:43:35 -07:00
parent 3a32f44f23
commit 2138d4f2a0
23 changed files with 1239 additions and 1079 deletions

View File

@ -32,12 +32,12 @@ module.exports = function(grunt) {
},
minify: {
source:{
src: ['build/files/combined.video.js'],
src: ['build/files/combined.video.js', 'build/compiler/goog.base.js', 'src/js/exports.js'],
externs: ['src/js/media.flash.externs.js'],
dest: 'build/files/minified.video.js'
},
tests: {
src: ['build/files/combined.video.js', 'test/unit/*.js', '!test/unit/api.js'],
src: ['build/files/combined.video.js', 'build/compiler/goog.base.js', 'src/js/exports.js', 'test/unit/*.js', '!test/unit/api.js'],
externs: ['src/js/media.flash.externs.js', 'test/qunit/qunit-externs.js'],
dest: 'build/files/test.minified.video.js'
}

View File

@ -15,21 +15,27 @@
// ADD NEW SOURCE FILES HERE
var sourceFiles = [
"src/js/goog.base.js",
"src/js/core.js",
"src/js/core-object.js",
"src/js/events.js",
"src/js/lib.js",
"src/js/component.js",
"src/js/button.js",
"src/js/slider.js",
"src/js/menu.js",
"src/js/player.js",
"src/js/controls.js",
"src/js/poster.js",
"src/js/loading-spinner.js",
"src/js/big-play-button.js",
"src/js/media.js",
"src/js/media.html5.js",
"src/js/media.flash.js",
"src/js/media.loader.js",
"src/js/tracks.js",
"src/js/json.js",
"src/js/setup.js",
"src/js/plugins.js",
"src/js/exports.js"
"src/js/plugins.js"
];
// Allow overriding the default project root

34
src/js/big-play-button.js Normal file
View File

@ -0,0 +1,34 @@
/* Big Play Button
================================================================================ */
/**
* Initial play button. Shows before the video has played.
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.BigPlayButton = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
player.on('play', vjs.bind(this, this.hide));
// player.on('ended', vjs.bind(this, this.show));
}
});
vjs.BigPlayButton.prototype.createEl = function(){
return vjs.Button.prototype.createEl.call(this, 'div', {
className: 'vjs-big-play-button',
innerHTML: '<span></span>',
'aria-label': 'play video'
});
};
vjs.BigPlayButton.prototype.onClick = function(){
// Go back to the beginning if big play button is showing at the end.
// Have to check for current time otherwise it might throw a 'not ready' error.
//if(this.player_.currentTime()) {
//this.player_.currentTime(0);
//}
this.player_.play();
};

74
src/js/button.js Normal file
View File

@ -0,0 +1,74 @@
/* Button - Base class for all buttons
================================================================================ */
/**
* Base class for all buttons
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Button = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
var touchstart = false;
this.on('touchstart', function() {
touchstart = true;
});
this.on('touchmove', function() {
touchstart = false;
});
var self = this;
this.on('touchend', function(event) {
if (touchstart) {
self.onClick(event);
}
event.preventDefault();
event.stopPropagation();
});
this.on('click', this.onClick);
this.on('focus', this.onFocus);
this.on('blur', this.onBlur);
}
});
vjs.Button.prototype.createEl = function(type, props){
// Add standard Aria and Tabindex info
props = vjs.obj.merge({
className: this.buildCSSClass(),
innerHTML: '<div><span class="vjs-control-text">' + (this.buttonText || 'Need Text') + '</span></div>',
role: 'button',
'aria-live': 'polite', // let the screen reader user know that the text of the button may change
tabIndex: 0
}, props);
return vjs.Component.prototype.createEl.call(this, type, props);
};
vjs.Button.prototype.buildCSSClass = function(){
// TODO: Change vjs-control to vjs-button?
return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);
};
// Click - Override with specific functionality for button
vjs.Button.prototype.onClick = function(){};
// Focus - Add keyboard functionality to element
vjs.Button.prototype.onFocus = function(){
vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
// KeyPress (document level) - Trigger click when keys are pressed
vjs.Button.prototype.onKeyPress = function(event){
// Check for space bar (32) or enter (13) keys
if (event.which == 32 || event.which == 13) {
event.preventDefault();
this.onClick();
}
};
// Blur - Remove keyboard triggers
vjs.Button.prototype.onBlur = function(){
vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
};

View File

@ -9,34 +9,37 @@
* @param {Object=} options
* @constructor
*/
vjs.Component = function(player, options, ready){
this.player_ = player;
vjs.Component = vjs.CoreObject.extend({
/** @constructor */
init: function(player, options, ready){
this.player_ = player;
// Make a copy of prototype.options_ to protect against overriding global defaults
this.options_ = vjs.obj.copy(this.options_);
// Make a copy of prototype.options_ to protect against overriding global defaults
this.options_ = vjs.obj.copy(this.options_);
// Updated options with supplied options
options = this.options(options);
// Updated options with supplied options
options = this.options(options);
// Get ID from options, element, or create using player ID and unique ID
this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );
// Get ID from options, element, or create using player ID and unique ID
this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );
this.name_ = options['name'] || null;
this.name_ = options['name'] || null;
// Create element if one wasn't provided in options
this.el_ = options['el'] || this.createEl();
// Create element if one wasn't provided in options
this.el_ = options['el'] || this.createEl();
this.children_ = [];
this.childIndex_ = {};
this.childNameIndex_ = {};
this.children_ = [];
this.childIndex_ = {};
this.childNameIndex_ = {};
// Add any child components in options
this.initChildren();
// Add any child components in options
this.initChildren();
this.ready(ready);
// Don't want to trigger ready here or it will before init is actually
// finished for all children that run this constructor
};
this.ready(ready);
// Don't want to trigger ready here or it will before init is actually
// finished for all children that run this constructor
}
});
/**
* Dispose of the component and all child components.
@ -334,7 +337,6 @@ vjs.Component.prototype.initChildren = function(){
// Loop through components and add them to the player
vjs.obj.each(options['children'], function(name, opts){
// Allow for disabling default components
// e.g. vjs.options['children']['posterImage'] = false
if (opts === false) return;

830
src/js/controls.js vendored

File diff suppressed because it is too large Load Diff

74
src/js/core-object.js Normal file
View File

@ -0,0 +1,74 @@
/**
* Core Object/Class for objects that use inheritance + contstructors
* @constructor
*/
vjs.CoreObject = vjs['CoreObject'] = function(){};
// Manually exporting vjs['CoreObject'] here for Closure Compiler
// because of the use of the extend/create class methods
// If we didn't do this, those functions would get flattend to something like
// `a = ...` and `this.prototype` would refer to the global object instead of
// CoreObject
/**
* Create a new object that inherits from this Object
* @param {Object} props Functions and properties to be applied to the
* new object's prototype
* @return {vjs.CoreObject} Returns an object that inherits from CoreObject
* @this {*}
*/
vjs.CoreObject.extend = function(props){
var init, subObj;
props = props || {};
// Set up the constructor using the supplied init method
// or using the init of the parent object
init = props.init || this.prototype.init || function(){};
// In Resig's simple class inheritance (previously used) the constructor
// is a function that calls `this.init.apply(arguments)`
// However that would prevent us from using `ParentObject.call(this);`
// in a Child constuctor because the `this` in `this.init`
// would still refer to the Child and cause an inifinite loop.
// We would instead have to do
// `ParentObject.prototype.init.apply(this, argumnents);`
// Bleh. We're not creating a _super() function, so it's good to keep
// the parent constructor reference simple.
subObj = function(){
init.apply(this, arguments);
};
// Inherit from this object's prototype
subObj.prototype = vjs.obj.create(this.prototype);
// Reset the constructor property for subObj otherwise
// instances of subObj would have the constructor of the parent Object
subObj.prototype.constructor = subObj;
// Make the class extendable
subObj.extend = vjs.CoreObject.extend;
// Make a function for creating instances
subObj.create = vjs.CoreObject.create;
// Extend subObj's prototype with functions and other properties from props
for (var name in props) {
if (props.hasOwnProperty(name)) {
subObj.prototype[name] = props[name];
}
}
return subObj;
};
/**
* Create a new instace of this Object class
* @return {vjs.CoreObject} Returns an instance of a CoreObject subclass
* @this {*}
*/
vjs.CoreObject.create = function(){
// Create a new object that inherits from this object's prototype
var inst = vjs.obj.create(this.prototype);
// Apply this constructor function to the new object
this.apply(inst, arguments);
// Return the new object
return inst;
};

View File

@ -33,6 +33,9 @@ goog.exportSymbol('videojs.options', vjs.options);
// Allow external components to use global cache
goog.exportSymbol('videojs.cache', vjs.cache);
// goog.exportSymbol('videojs.CoreObject', vjs.CoreObject);
// goog.exportProperty(vjs.CoreObject, 'create', vjs.CoreObject.create);
goog.exportSymbol('videojs.Component', vjs.Component);
goog.exportProperty(vjs.Component.prototype, 'dispose', vjs.Component.prototype.dispose);
goog.exportProperty(vjs.Component.prototype, 'createEl', vjs.Component.prototype.createEl);
@ -57,11 +60,8 @@ goog.exportProperty(vjs.Player.prototype, 'dispose', vjs.Player.prototype.dispos
goog.exportSymbol('videojs.MediaLoader', vjs.MediaLoader);
goog.exportSymbol('videojs.TextTrackDisplay', vjs.TextTrackDisplay);
goog.exportSymbol('videojs.Control', vjs.Control);
goog.exportSymbol('videojs.ControlBar', vjs.ControlBar);
goog.exportSymbol('videojs.Button', vjs.Button);
goog.exportSymbol('videojs.PlayButton', vjs.PlayButton);
goog.exportSymbol('videojs.PauseButton', vjs.PauseButton);
goog.exportSymbol('videojs.PlayToggle', vjs.PlayToggle);
goog.exportSymbol('videojs.FullscreenToggle', vjs.FullscreenToggle);
goog.exportSymbol('videojs.BigPlayButton', vjs.BigPlayButton);

View File

@ -46,6 +46,23 @@ vjs.capitalize = function(string){
*/
vjs.obj = {};
/**
* Object.create shim for prototypal inheritance.
* https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
* @param {Object} obj Object to use as prototype
*/
vjs.obj.create = Object.create || function(obj){
//Create a new function called 'F' which is just an empty object.
function F() {}
//the prototype of the 'F' function should point to the
//parameter of the anonymous function.
F.prototype = obj;
//create a new constructor function based off of the 'F' function.
return new F();
};
/**
* Loop through each property in an object and call a function
* whose arguments are (key,value)

55
src/js/loading-spinner.js Normal file
View File

@ -0,0 +1,55 @@
/* Loading Spinner
================================================================================ */
/**
* Loading spinner for waiting events
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.LoadingSpinner = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('canplay', vjs.bind(this, this.hide));
player.on('canplaythrough', vjs.bind(this, this.hide));
player.on('playing', vjs.bind(this, this.hide));
player.on('seeked', vjs.bind(this, this.hide));
player.on('seeking', vjs.bind(this, this.show));
// in some browsers seeking does not trigger the 'playing' event,
// so we also need to trap 'seeked' if we are going to set a
// 'seeking' event
player.on('seeked', vjs.bind(this, this.hide));
player.on('error', vjs.bind(this, this.show));
// Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
// Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing
// player.on('stalled', vjs.bind(this, this.show));
player.on('waiting', vjs.bind(this, this.show));
}
});
vjs.LoadingSpinner.prototype.createEl = function(){
var classNameSpinner, innerHtmlSpinner;
if ( typeof this.player_.el().style.WebkitBorderRadius == 'string'
|| typeof this.player_.el().style.MozBorderRadius == 'string'
|| typeof this.player_.el().style.KhtmlBorderRadius == 'string'
|| typeof this.player_.el().style.borderRadius == 'string')
{
classNameSpinner = 'vjs-loading-spinner';
innerHtmlSpinner = '<div class="ball1"></div><div class="ball2"></div><div class="ball3"></div><div class="ball4"></div><div class="ball5"></div><div class="ball6"></div><div class="ball7"></div><div class="ball8"></div>';
} else {
classNameSpinner = 'vjs-loading-spinner-fallback';
innerHtmlSpinner = '';
}
return vjs.Component.prototype.createEl.call(this, 'div', {
className: classNameSpinner,
innerHTML: innerHtmlSpinner
});
};

View File

@ -11,209 +11,211 @@
* @param {Function=} ready
* @constructor
*/
vjs.Flash = function(player, options, ready){
goog.base(this, player, options, ready);
vjs.Flash = vjs.MediaTechController.extend({
/** @constructor */
init: function(player, options, ready){
vjs.MediaTechController.call(this, player, options, ready);
var source = options['source'],
var source = options['source'],
// Which element to embed in
parentEl = options['parentEl'],
// Which element to embed in
parentEl = options['parentEl'],
// Create a temporary element to be replaced by swf object
placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),
// Create a temporary element to be replaced by swf object
placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),
// Generate ID for swf object
objId = player.id()+'_flash_api',
// Generate ID for swf object
objId = player.id()+'_flash_api',
// Store player options in local var for optimization
// TODO: switch to using player methods instead of options
// e.g. player.autoplay();
playerOptions = player.options_,
// Store player options in local var for optimization
// TODO: switch to using player methods instead of options
// e.g. player.autoplay();
playerOptions = player.options_,
// Merge default flashvars with ones passed in to init
flashVars = vjs.obj.merge({
// Merge default flashvars with ones passed in to init
flashVars = vjs.obj.merge({
// SWF Callback Functions
'readyFunction': 'videojs.Flash.onReady',
'eventProxyFunction': 'videojs.Flash.onEvent',
'errorEventProxyFunction': 'videojs.Flash.onError',
// SWF Callback Functions
'readyFunction': 'videojs.Flash.onReady',
'eventProxyFunction': 'videojs.Flash.onEvent',
'errorEventProxyFunction': 'videojs.Flash.onError',
// Player Settings
'autoplay': playerOptions.autoplay,
'preload': playerOptions.preload,
'loop': playerOptions.loop,
'muted': playerOptions.muted
// Player Settings
'autoplay': playerOptions.autoplay,
'preload': playerOptions.preload,
'loop': playerOptions.loop,
'muted': playerOptions.muted
}, options['flashVars']),
}, options['flashVars']),
// Merge default parames with ones passed in
params = vjs.obj.merge({
'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
}, options['params']),
// Merge default parames with ones passed in
params = vjs.obj.merge({
'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
}, options['params']),
// Merge default attributes with ones passed in
attributes = vjs.obj.merge({
'id': objId,
'name': objId, // Both ID and Name needed or swf to identifty itself
'class': 'vjs-tech'
}, options['attributes'])
;
// Merge default attributes with ones passed in
attributes = vjs.obj.merge({
'id': objId,
'name': objId, // Both ID and Name needed or swf to identifty itself
'class': 'vjs-tech'
}, options['attributes'])
;
// If source was supplied pass as a flash var.
if (source) {
flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
}
// If source was supplied pass as a flash var.
if (source) {
flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
}
// Add placeholder to player div
vjs.insertFirst(placeHolder, parentEl);
// Add placeholder to player div
vjs.insertFirst(placeHolder, parentEl);
// Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
// This allows resetting the playhead when we catch the reload
if (options['startTime']) {
this.ready(function(){
this.load();
this.play();
this.currentTime(options['startTime']);
});
}
// Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
// This allows resetting the playhead when we catch the reload
if (options['startTime']) {
this.ready(function(){
this.load();
this.play();
this.currentTime(options['startTime']);
});
}
// Flash iFrame Mode
// In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
// - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
// - Webkit when hiding the plugin
// - Webkit and Firefox when using requestFullScreen on a parent element
// Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
// Issues that remain include hiding the element and requestFullScreen in Firefox specifically
// Flash iFrame Mode
// In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
// - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
// - Webkit when hiding the plugin
// - Webkit and Firefox when using requestFullScreen on a parent element
// Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
// Issues that remain include hiding the element and requestFullScreen in Firefox specifically
// There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.
// Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.
// I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.
// Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe
// In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.
// The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.
// There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.
// Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.
// I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.
// Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe
// In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.
// The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.
// NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
// Firefox 9 throws a security error, unleess you call location.href right before doc.write.
// Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
// Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
// NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
// Firefox 9 throws a security error, unleess you call location.href right before doc.write.
// Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
// Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
// Create iFrame with vjs-tech class so it's 100% width/height
var iFrm = vjs.createEl('iframe', {
'id': objId + '_iframe',
'name': objId + '_iframe',
'className': 'vjs-tech',
'scrolling': 'no',
'marginWidth': 0,
'marginHeight': 0,
'frameBorder': 0
});
// Update ready function names in flash vars for iframe window
flashVars['readyFunction'] = 'ready';
flashVars['eventProxyFunction'] = 'events';
flashVars['errorEventProxyFunction'] = 'errors';
// Tried multiple methods to get this to work in all browsers
// Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.
// The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error
// var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
// (in onload)
// var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
// iDoc.body.appendChild(temp);
// Tried embedding the flash object through javascript in the iframe source.
// This works in webkit but still triggers the firefox security error
// iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
// Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe
// We should add an option to host the iframe locally though, because it could help a lot of issues.
// iFrm.src = "iframe.html";
// Wait until iFrame has loaded to write into it.
vjs.on(iFrm, 'load', vjs.bind(this, function(){
var iDoc,
iWin = iFrm.contentWindow;
// The one working method I found was to use the iframe's document.write() to create the swf object
// This got around the security issue in all browsers except firefox.
// I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
// However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
// Plus Firefox 3.6 didn't work no matter what I tried.
// if (vjs.USER_AGENT.match('Firefox')) {
// iWin.location.href = '';
// }
// Get the iFrame's document depending on what the browser supports
iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
// Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
// Even tried adding /. that was mentioned in a browser security writeup
// document.domain = document.domain+'/.';
// iDoc.domain = document.domain+'/.';
// Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
// iDoc.body.innerHTML = swfObjectHTML;
// Tried appending the object to the iframe doc's body. Security error in all browsers.
// iDoc.body.appendChild(swfObject);
// Using document.write actually got around the security error that browsers were throwing.
// Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
// Not sure why that's a security issue, but apparently it is.
iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
// Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
// So far no issues with swf ready event being called before it's set on the window.
iWin['player'] = this.player_;
// Create swf ready function for iFrame window
iWin['ready'] = vjs.bind(this.player_, function(currSwf){
var el = iDoc.getElementById(currSwf),
player = this,
tech = player.tech;
// Update reference to playback technology element
tech.el_ = el;
// Now that the element is ready, make a click on the swf play the video
vjs.on(el, 'click', tech.bind(tech.onClick));
// Make sure swf is actually ready. Sometimes the API isn't actually yet.
vjs.Flash.checkReady(tech);
// Create iFrame with vjs-tech class so it's 100% width/height
var iFrm = vjs.createEl('iframe', {
'id': objId + '_iframe',
'name': objId + '_iframe',
'className': 'vjs-tech',
'scrolling': 'no',
'marginWidth': 0,
'marginHeight': 0,
'frameBorder': 0
});
// Create event listener for all swf events
iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){
var player = this;
if (player && player.techName === 'flash') {
player.trigger(eventName);
}
});
// Update ready function names in flash vars for iframe window
flashVars['readyFunction'] = 'ready';
flashVars['eventProxyFunction'] = 'events';
flashVars['errorEventProxyFunction'] = 'errors';
// Create error listener for all swf errors
iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){
vjs.log('Flash Error', eventName);
});
// Tried multiple methods to get this to work in all browsers
}));
// Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.
// The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error
// var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
// (in onload)
// var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
// iDoc.body.appendChild(temp);
// Replace placeholder with iFrame (it will load now)
placeHolder.parentNode.replaceChild(iFrm, placeHolder);
// Tried embedding the flash object through javascript in the iframe source.
// This works in webkit but still triggers the firefox security error
// iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
// If not using iFrame mode, embed as normal object
} else {
vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
// Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe
// We should add an option to host the iframe locally though, because it could help a lot of issues.
// iFrm.src = "iframe.html";
// Wait until iFrame has loaded to write into it.
vjs.on(iFrm, 'load', vjs.bind(this, function(){
var iDoc,
iWin = iFrm.contentWindow;
// The one working method I found was to use the iframe's document.write() to create the swf object
// This got around the security issue in all browsers except firefox.
// I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
// However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
// Plus Firefox 3.6 didn't work no matter what I tried.
// if (vjs.USER_AGENT.match('Firefox')) {
// iWin.location.href = '';
// }
// Get the iFrame's document depending on what the browser supports
iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
// Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
// Even tried adding /. that was mentioned in a browser security writeup
// document.domain = document.domain+'/.';
// iDoc.domain = document.domain+'/.';
// Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
// iDoc.body.innerHTML = swfObjectHTML;
// Tried appending the object to the iframe doc's body. Security error in all browsers.
// iDoc.body.appendChild(swfObject);
// Using document.write actually got around the security error that browsers were throwing.
// Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
// Not sure why that's a security issue, but apparently it is.
iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
// Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
// So far no issues with swf ready event being called before it's set on the window.
iWin['player'] = this.player_;
// Create swf ready function for iFrame window
iWin['ready'] = vjs.bind(this.player_, function(currSwf){
var el = iDoc.getElementById(currSwf),
player = this,
tech = player.tech;
// Update reference to playback technology element
tech.el_ = el;
// Now that the element is ready, make a click on the swf play the video
vjs.on(el, 'click', tech.bind(tech.onClick));
// Make sure swf is actually ready. Sometimes the API isn't actually yet.
vjs.Flash.checkReady(tech);
});
// Create event listener for all swf events
iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){
var player = this;
if (player && player.techName === 'flash') {
player.trigger(eventName);
}
});
// Create error listener for all swf errors
iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){
vjs.log('Flash Error', eventName);
});
}));
// Replace placeholder with iFrame (it will load now)
placeHolder.parentNode.replaceChild(iFrm, placeHolder);
// If not using iFrame mode, embed as normal object
} else {
vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
}
}
};
goog.inherits(vjs.Flash, vjs.MediaTechController);
});
vjs.Flash.prototype.dispose = function(){
goog.base(this, 'dispose');
vjs.MediaTechController.prototype.dispose.call(this);
};
vjs.Flash.prototype.play = function(){

View File

@ -9,8 +9,10 @@
* @param {Function=} ready
* @constructor
*/
vjs.Html5 = function(player, options, ready){
goog.base(this, player, options, ready);
vjs.Html5 = vjs.MediaTechController.extend({
/** @constructor */
init: function(player, options, ready){
vjs.MediaTechController.call(this, player, options, ready);
// volume cannot be changed from 1 on iOS
this.features.volumeControl = vjs.Html5.canControlVolume();
@ -20,37 +22,37 @@ vjs.Html5 = function(player, options, ready){
var source = options['source'];
// If the element source is already set, we may have missed the loadstart event, and want to trigger it.
// We don't want to set the source again and interrupt playback.
if (source && this.el_.currentSrc == source.src) {
player.trigger('loadstart');
// If the element source is already set, we may have missed the loadstart event, and want to trigger it.
// We don't want to set the source again and interrupt playback.
if (source && this.el_.currentSrc == source.src) {
player.trigger('loadstart');
// Otherwise set the source if one was provided.
} else if (source) {
this.el_.src = source.src;
}
// Chrome and Safari both have issues with autoplay.
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
// This fixes both issues. Need to wait for API, so it updates displays correctly
player.ready(function(){
if (this.options_['autoplay'] && this.paused()) {
this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16.
this.play();
// Otherwise set the source if one was provided.
} else if (source) {
this.el_.src = source.src;
}
});
this.on('click', this.onClick);
// Chrome and Safari both have issues with autoplay.
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
// This fixes both issues. Need to wait for API, so it updates displays correctly
player.ready(function(){
if (this.options_['autoplay'] && this.paused()) {
this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16.
this.play();
}
});
this.setupTriggers();
this.on('click', this.onClick);
this.triggerReady();
};
goog.inherits(vjs.Html5, vjs.MediaTechController);
this.setupTriggers();
this.triggerReady();
}
});
vjs.Html5.prototype.dispose = function(){
goog.base(this, 'dispose');
vjs.MediaTechController.prototype.dispose.call(this);
};
vjs.Html5.prototype.createEl = function(){

View File

@ -8,15 +8,17 @@
* @param {Object=} options Options object
* @constructor
*/
vjs.MediaTechController = function(player, options, ready){
goog.base(this, player, options, ready);
vjs.MediaTechController = vjs.Component.extend({
/** @constructor */
init: function(player, options, ready){
vjs.Component.call(this, player, options, ready);
// Make playback element clickable
// this.addEvent('click', this.proxy(this.onClick));
// Make playback element clickable
// this.addEvent('click', this.proxy(this.onClick));
// player.triggerEvent('techready');
};
goog.inherits(vjs.MediaTechController, vjs.Component);
// player.triggerEvent('techready');
}
});
// destroy: function(){},
// createElement: function(){},

30
src/js/media.loader.js Normal file
View File

@ -0,0 +1,30 @@
/**
* @constructor
*/
vjs.MediaLoader = vjs.Component.extend({
/** @constructor */
init: function(player, options, ready){
vjs.Component.call(this, player, options, ready);
// If there are no sources when the player is initialized,
// load the first supported playback technology.
if (!player.options_['sources'] || player.options_['sources'].length === 0) {
for (var i=0,j=player.options_['techOrder']; i<j.length; i++) {
var techName = vjs.capitalize(j[i]),
tech = window['videojs'][techName];
// Check if the browser supports this technology
if (tech && tech.isSupported()) {
player.loadTech(techName);
break;
}
}
} else {
// // Loop through playback technologies (HTML5, Flash) and check for support.
// // Then load the best source.
// // A few assumptions here:
// // All playback technologies respect preload false.
player.src(player.options_['sources']);
}
}
});

68
src/js/menu.js Normal file
View File

@ -0,0 +1,68 @@
/* Menu
================================================================================ */
/**
* The base for text track and settings menu buttons.
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Menu = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.Menu.prototype.addItem = function(component){
this.addChild(component);
component.on('click', vjs.bind(this, function(){
this.unlockShowing();
}));
};
vjs.Menu.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'ul', {
className: 'vjs-menu'
});
};
/**
* Menu item
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.MenuItem = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
if (options['selected']) {
this.addClass('vjs-selected');
this.el_.setAttribute('aria-selected',true);
} else {
this.el_.setAttribute('aria-selected',false);
}
}
});
vjs.MenuItem.prototype.createEl = function(type, props){
return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({
className: 'vjs-menu-item',
innerHTML: this.options_['label']
}, props));
};
vjs.MenuItem.prototype.onClick = function(){
this.selected(true);
};
vjs.MenuItem.prototype.selected = function(selected){
if (selected) {
this.addClass('vjs-selected');
this.el_.setAttribute('aria-selected',true);
} else {
this.removeClass('vjs-selected');
this.el_.setAttribute('aria-selected',false);
}
};

View File

@ -5,62 +5,64 @@
* @param {Function=} ready Ready callback function
* @constructor
*/
vjs.Player = function(tag, options, ready){
this.tag = tag; // Store the original tag used to set options
vjs.Player = vjs.Component.extend({
/** @constructor */
init: function(tag, options, ready){
this.tag = tag; // Store the original tag used to set options
// Set Options
// The options argument overrides options set in the video tag
// which overrides globally set options.
// This latter part coincides with the load order
// (tag must exist before Player)
options = vjs.obj.merge(this.getTagSettings(tag), options);
// Set Options
// The options argument overrides options set in the video tag
// which overrides globally set options.
// This latter part coincides with the load order
// (tag must exist before Player)
options = vjs.obj.merge(this.getTagSettings(tag), options);
// Cache for video property values.
this.cache_ = {};
// Cache for video property values.
this.cache_ = {};
// Set poster
this.poster_ = options['poster'];
// Set controls
this.controls_ = options['controls'];
// Set poster
this.poster_ = options['poster'];
// Set controls
this.controls_ = options['controls'];
// Run base component initializing with new options.
// Builds the element through createEl()
// Inits and embeds any child components in opts
vjs.Component.call(this, this, options, ready);
// Run base component initializing with new options.
// Builds the element through createEl()
// Inits and embeds any child components in opts
vjs.Component.call(this, this, options, ready);
// Firstplay event implimentation. Not sold on the event yet.
// Could probably just check currentTime==0?
this.one('play', function(e){
var fpEvent = { type: 'firstplay', target: this.el_ };
// Using vjs.trigger so we can check if default was prevented
var keepGoing = vjs.trigger(this.el_, fpEvent);
// Firstplay event implimentation. Not sold on the event yet.
// Could probably just check currentTime==0?
this.one('play', function(e){
var fpEvent = { type: 'firstplay', target: this.el_ };
// Using vjs.trigger so we can check if default was prevented
var keepGoing = vjs.trigger(this.el_, fpEvent);
if (!keepGoing) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if (!keepGoing) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
});
this.on('ended', this.onEnded);
this.on('play', this.onPlay);
this.on('firstplay', this.onFirstPlay);
this.on('pause', this.onPause);
this.on('progress', this.onProgress);
this.on('durationchange', this.onDurationChange);
this.on('error', this.onError);
this.on('fullscreenchange', this.onFullscreenChange);
// Make player easily findable by ID
vjs.players[this.id_] = this;
if (options['plugins']) {
vjs.obj.each(options['plugins'], function(key, val){
this[key](val);
}, this);
}
});
this.on('ended', this.onEnded);
this.on('play', this.onPlay);
this.on('firstplay', this.onFirstPlay);
this.on('pause', this.onPause);
this.on('progress', this.onProgress);
this.on('durationchange', this.onDurationChange);
this.on('error', this.onError);
this.on('fullscreenchange', this.onFullscreenChange);
// Make player easily findable by ID
vjs.players[this.id_] = this;
if (options['plugins']) {
vjs.obj.each(options['plugins'], function(key, val){
this[key](val);
}, this);
}
};
goog.inherits(vjs.Player, vjs.Component);
});
/**
* Player instance options, surfaced using vjs.options
@ -88,7 +90,7 @@ vjs.Player.prototype.dispose = function(){
if (this.tech) { this.tech.dispose(); }
// Component dispose
goog.base(this, 'dispose');
vjs.Component.prototype.dispose.call(this);
};
vjs.Player.prototype.getTagSettings = function(tag){
@ -125,7 +127,7 @@ vjs.Player.prototype.getTagSettings = function(tag){
};
vjs.Player.prototype.createEl = function(){
var el = this.el_ = goog.base(this, 'createEl', 'div');
var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
var tag = this.tag;
// Original tag settings stored in options
@ -759,7 +761,7 @@ vjs.Player.prototype.src = function(source){
}
} else {
this.el_.appendChild(vjs.createEl('p', {
innerHTML: 'Sorry, no compatible source and playback technology were found for this video. Try using another browser like <a href="http://www.google.com/chrome">Google Chrome</a> or download the latest <a href="http://get.adobe.com/flashplayer/">Adobe Flash Player</a>.'
innerHTML: 'Sorry, no compatible source and playback technology were found for this video. Try using another browser like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
}));
}
@ -946,33 +948,4 @@ vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
})();
/**
* @constructor
*/
vjs.MediaLoader = function(player, options, ready){
vjs.Component.call(this, player, options, ready);
// If there are no sources when the player is initialized,
// load the first supported playback technology.
if (!player.options_['sources'] || player.options_['sources'].length === 0) {
for (var i=0,j=player.options_['techOrder']; i<j.length; i++) {
var techName = vjs.capitalize(j[i]),
tech = window['videojs'][techName];
// Check if the browser supports this technology
if (tech && tech.isSupported()) {
player.loadTech(techName);
break;
}
}
} else {
// // Loop through playback technologies (HTML5, Flash) and check for support.
// // Then load the best source.
// // A few assumptions here:
// // All playback technologies respect preload false.
player.src(player.options_['sources']);
}
};
goog.inherits(vjs.MediaLoader, vjs.Component);

44
src/js/poster.js Normal file
View File

@ -0,0 +1,44 @@
/* Poster Image
================================================================================ */
/**
* Poster image. Shows before the video plays.
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.PosterImage = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
if (!player.poster()) {
this.hide();
}
player.on('play', vjs.bind(this, this.hide));
}
});
vjs.PosterImage.prototype.createEl = function(){
var el = vjs.createEl('div', {
className: 'vjs-poster',
// Don't want poster to be tabbable.
tabIndex: -1
}),
poster = this.player_.poster();
if (poster) {
if ('backgroundSize' in el.style) {
el.style.backgroundImage = 'url("' + poster + '")';
} else {
el.appendChild(vjs.createEl('img', { src: poster }));
}
}
return el;
};
vjs.PosterImage.prototype.onClick = function(){
this.player_.play();
};

164
src/js/slider.js Normal file
View File

@ -0,0 +1,164 @@
/* Slider
================================================================================ */
/**
* Parent for seek bar and volume slider
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Slider = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
// Set property names to bar and handle to match with the child Slider class is looking for
this.bar = this.getChild(this.options_['barName']);
this.handle = this.getChild(this.options_['handleName']);
player.on(this.playerEvent, vjs.bind(this, this.update));
this.on('mousedown', this.onMouseDown);
this.on('touchstart', this.onMouseDown);
this.on('focus', this.onFocus);
this.on('blur', this.onBlur);
this.player_.on('controlsvisible', vjs.bind(this, this.update));
// This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
// this.player_.one('timeupdate', vjs.bind(this, this.update));
player.ready(vjs.bind(this, this.update));
this.boundEvents = {};
}
});
vjs.Slider.prototype.createEl = function(type, props) {
props = vjs.obj.merge({
role: 'slider',
'aria-valuenow': 0,
'aria-valuemin': 0,
'aria-valuemax': 100,
tabIndex: 0
}, props);
return vjs.Component.prototype.createEl.call(this, type, props);
};
vjs.Slider.prototype.onMouseDown = function(event){
event.preventDefault();
vjs.blockTextSelection();
this.boundEvents.move = vjs.bind(this, this.onMouseMove);
this.boundEvents.end = vjs.bind(this, this.onMouseUp);
vjs.on(document, 'mousemove', this.boundEvents.move);
vjs.on(document, 'mouseup', this.boundEvents.end);
vjs.on(document, 'touchmove', this.boundEvents.move);
vjs.on(document, 'touchend', this.boundEvents.end);
this.onMouseMove(event);
};
vjs.Slider.prototype.onMouseUp = function() {
vjs.unblockTextSelection();
vjs.off(document, 'mousemove', this.boundEvents.move, false);
vjs.off(document, 'mouseup', this.boundEvents.end, false);
vjs.off(document, 'touchmove', this.boundEvents.move, false);
vjs.off(document, 'touchend', this.boundEvents.end, false);
this.update();
};
vjs.Slider.prototype.update = function(){
// In VolumeBar init we have a setTimeout for update that pops and update to the end of the
// execution stack. The player is destroyed before then update will cause an error
if (!this.el_) return;
// If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
// On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
// var progress = (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
var barProgress,
progress = this.getPercent(),
handle = this.handle,
bar = this.bar;
// Protect against no duration and other division issues
if (isNaN(progress)) { progress = 0; }
barProgress = progress;
// If there is a handle, we need to account for the handle in our calculation for progress bar
// so that it doesn't fall short of or extend past the handle.
if (handle) {
var box = this.el_,
boxWidth = box.offsetWidth,
handleWidth = handle.el().offsetWidth,
// The width of the handle in percent of the containing box
// In IE, widths may not be ready yet causing NaN
handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
// Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
// There is a margin of half the handle's width on both sides.
boxAdjustedPercent = 1 - handlePercent,
// Adjust the progress that we'll use to set widths to the new adjusted box width
adjustedProgress = progress * boxAdjustedPercent;
// The bar does reach the left side, so we need to account for this in the bar's width
barProgress = adjustedProgress + (handlePercent / 2);
// Move the handle from the left based on the adjected progress
handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';
}
// Set the new bar width
bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
};
vjs.Slider.prototype.calculateDistance = function(event){
var box = this.el_,
boxX = vjs.findPosX(box),
boxW = box.offsetWidth,
handle = this.handle,
pageX = event.pageX;
if (handle) {
var handleW = handle.el().offsetWidth;
// Adjusted X and Width, so handle doesn't go outside the bar
boxX = boxX + (handleW / 2);
boxW = boxW - handleW;
}
// This is done because on Android, event.pageX is always 0 and the actual
// values live under the changedTouches array.
if (pageX === 0 && event.changedTouches) {
pageX = event.changedTouches[0].pageX;
}
// Percent that the click is through the adjusted area
return Math.max(0, Math.min(1, (pageX - boxX) / boxW));
};
vjs.Slider.prototype.onFocus = function(){
vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
vjs.Slider.prototype.onKeyPress = function(event){
if (event.which == 37) { // Left Arrow
event.preventDefault();
this.stepBack();
} else if (event.which == 39) { // Right Arrow
event.preventDefault();
this.stepForward();
}
};
vjs.Slider.prototype.onBlur = function(){
vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
};

View File

@ -118,28 +118,30 @@ vjs.Player.prototype.showTextTrack = function(id, disableSameKind){
* @param {Object=} options
* @constructor
*/
vjs.TextTrack = function(player, options){
goog.base(this, player, options);
vjs.TextTrack = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
// Apply track info to track object
// Options will often be a track element
// Apply track info to track object
// Options will often be a track element
// Build ID if one doesn't exist
this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
this.src_ = options['src'];
// 'default' is a reserved keyword in js so we use an abbreviated version
this.dflt_ = options['default'] || options['dflt'];
this.title_ = options['title'];
this.language_ = options['srclang'];
this.label_ = options['label'];
this.cues_ = [];
this.activeCues_ = [];
this.readyState_ = 0;
this.mode_ = 0;
// Build ID if one doesn't exist
this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
this.src_ = options['src'];
// 'default' is a reserved keyword in js so we use an abbreviated version
this.dflt_ = options['default'] || options['dflt'];
this.title_ = options['title'];
this.language_ = options['srclang'];
this.label_ = options['label'];
this.cues_ = [];
this.activeCues_ = [];
this.readyState_ = 0;
this.mode_ = 0;
this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));
};
goog.inherits(vjs.TextTrack, vjs.Component);
this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));
}
});
/**
* Track kind value. Captions, subtitles, etc.
@ -317,7 +319,7 @@ vjs.TextTrack.prototype.adjustFontSize = function(){
* @return {Element}
*/
vjs.TextTrack.prototype.createEl = function(){
return goog.base(this, 'createEl', 'div', {
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-' + this.kind_ + ' vjs-text-track'
});
};
@ -338,7 +340,7 @@ vjs.TextTrack.prototype.show = function(){
this.mode_ = 2;
// Show element.
goog.base(this, 'show');
vjs.Component.prototype.show.call(this);
};
/**
@ -354,7 +356,7 @@ vjs.TextTrack.prototype.hide = function(){
this.mode_ = 1;
// Hide element.
goog.base(this, 'hide');
vjs.Component.prototype.hide.call(this);
};
/**
@ -668,10 +670,7 @@ vjs.TextTrack.prototype.reset = function(){
/**
* @constructor
*/
vjs.CaptionsTrack = function(player, options, ready){
goog.base(this, player, options, ready);
};
goog.inherits(vjs.CaptionsTrack, vjs.TextTrack);
vjs.CaptionsTrack = vjs.TextTrack.extend();
vjs.CaptionsTrack.prototype.kind_ = 'captions';
// Exporting here because Track creation requires the track kind
// to be available on global object. e.g. new window['videojs'][Kind + 'Track']
@ -679,19 +678,13 @@ vjs.CaptionsTrack.prototype.kind_ = 'captions';
/**
* @constructor
*/
vjs.SubtitlesTrack = function(player, options, ready){
goog.base(this, player, options, ready);
};
goog.inherits(vjs.SubtitlesTrack, vjs.TextTrack);
vjs.SubtitlesTrack = vjs.TextTrack.extend();
vjs.SubtitlesTrack.prototype.kind_ = 'subtitles';
/**
* @constructor
*/
vjs.ChaptersTrack = function(player, options, ready){
goog.base(this, player, options, ready);
};
goog.inherits(vjs.ChaptersTrack, vjs.TextTrack);
vjs.ChaptersTrack = vjs.TextTrack.extend();
vjs.ChaptersTrack.prototype.kind_ = 'chapters';
@ -702,21 +695,23 @@ vjs.ChaptersTrack.prototype.kind_ = 'chapters';
/**
* @constructor
*/
vjs.TextTrackDisplay = function(player, options, ready){
goog.base(this, player, options, ready);
vjs.TextTrackDisplay = vjs.Component.extend({
/** @constructor */
init: function(player, options, ready){
vjs.Component.call(this, player, options, ready);
// 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.
// Should probably be moved to an external track loader when we support
// tracks that don't need a display.
if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
this.player_.addTextTracks(player.options_['tracks']);
// 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.
// Should probably be moved to an external track loader when we support
// tracks that don't need a display.
if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
this.player_.addTextTracks(player.options_['tracks']);
}
}
};
goog.inherits(vjs.TextTrackDisplay, vjs.Component);
});
vjs.TextTrackDisplay.prototype.createEl = function(){
return goog.base(this, 'createEl', 'div', {
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-text-track-display'
});
};
@ -727,20 +722,22 @@ vjs.TextTrackDisplay.prototype.createEl = function(){
/**
* @constructor
*/
vjs.TextTrackMenuItem = function(player, options){
var track = this.track = options['track'];
vjs.TextTrackMenuItem = vjs.MenuItem.extend({
/** @constructor */
init: function(player, options){
var track = this.track = options['track'];
// Modify options for parent MenuItem class's init.
options['label'] = track.label();
options['selected'] = track.dflt();
goog.base(this, player, options);
// Modify options for parent MenuItem class's init.
options['label'] = track.label();
options['selected'] = track.dflt();
vjs.MenuItem.call(this, player, options);
this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));
};
goog.inherits(vjs.TextTrackMenuItem, vjs.MenuItem);
this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));
}
});
vjs.TextTrackMenuItem.prototype.onClick = function(){
goog.base(this, 'onClick');
vjs.MenuItem.prototype.onClick.call(this);
this.player_.showTextTrack(this.track.id_, this.track.kind());
};
@ -755,23 +752,25 @@ vjs.TextTrackMenuItem.prototype.update = function(){
/**
* @constructor
*/
vjs.OffTextTrackMenuItem = function(player, options){
// Create pseudo track info
// Requires options['kind']
options['track'] = {
kind: function() { return options['kind']; },
player: player,
label: function(){ return 'Off'; },
dflt: function(){ return false; },
mode: function(){ return false; }
};
goog.base(this, player, options);
this.selected(true);
};
goog.inherits(vjs.OffTextTrackMenuItem, vjs.TextTrackMenuItem);
vjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({
/** @constructor */
init: function(player, options){
// Create pseudo track info
// Requires options['kind']
options['track'] = {
kind: function() { return options['kind']; },
player: player,
label: function(){ return 'Off'; },
dflt: function(){ return false; },
mode: function(){ return false; }
};
vjs.TextTrackMenuItem.call(this, player, options);
this.selected(true);
}
});
vjs.OffTextTrackMenuItem.prototype.onClick = function(){
goog.base(this, 'onClick');
vjs.TextTrackMenuItem.prototype.onClick(this);
this.player_.showTextTrack(this.track.id_, this.track.kind());
};
@ -799,19 +798,21 @@ vjs.OffTextTrackMenuItem.prototype.update = function(){
/**
* @constructor
*/
vjs.TextTrackButton = function(player, options){
goog.base(this, player, options);
vjs.TextTrackButton = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
this.menu = this.createMenu();
this.menu = this.createMenu();
if (this.items.length === 0) {
this.hide();
if (this.items.length === 0) {
this.hide();
}
this.on('keyup', this.onKeyPress);
this.el_.setAttribute('aria-haspopup',true);
this.el_.setAttribute('role','button');
}
this.on('keyup', this.onKeyPress);
this.el_.setAttribute('aria-haspopup',true);
this.el_.setAttribute('role','button');
};
goog.inherits(vjs.TextTrackButton, vjs.Button);
});
vjs.TextTrackButton.prototype.buttonPressed = false;
@ -858,7 +859,7 @@ vjs.TextTrackButton.prototype.createItems = function(){
};
vjs.TextTrackButton.prototype.buildCSSClass = function(){
return this.className + ' vjs-menu-button ' + goog.base(this, 'buildCSSClass');
return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);
};
// Focus - Add keyboard functionality to element
@ -931,11 +932,13 @@ vjs.TextTrackButton.prototype.unpressButton = function(){
/**
* @constructor
*/
vjs.CaptionsButton = function(player, options, ready){
goog.base(this, player, options, ready);
this.el_.setAttribute('aria-label','Captions Menu');
};
goog.inherits(vjs.CaptionsButton, vjs.TextTrackButton);
vjs.CaptionsButton = vjs.TextTrackButton.extend({
/** @constructor */
init: function(player, options, ready){
vjs.TextTrackButton.call(this, player, options, ready);
this.el_.setAttribute('aria-label','Captions Menu');
}
});
vjs.CaptionsButton.prototype.kind_ = 'captions';
vjs.CaptionsButton.prototype.buttonText = 'Captions';
vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
@ -943,11 +946,13 @@ vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
/**
* @constructor
*/
vjs.SubtitlesButton = function(player, options, ready){
goog.base(this, player, options, ready);
this.el_.setAttribute('aria-label','Subtitles Menu');
};
goog.inherits(vjs.SubtitlesButton, vjs.TextTrackButton);
vjs.SubtitlesButton = vjs.TextTrackButton.extend({
/** @constructor */
init: function(player, options, ready){
vjs.TextTrackButton.call(this, player, options, ready);
this.el_.setAttribute('aria-label','Subtitles Menu');
}
});
vjs.SubtitlesButton.prototype.kind_ = 'subtitles';
vjs.SubtitlesButton.prototype.buttonText = 'Subtitles';
vjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';
@ -957,11 +962,13 @@ vjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';
/**
* @constructor
*/
vjs.ChaptersButton = function(player, options, ready){
goog.base(this, player, options, ready);
this.el_.setAttribute('aria-label','Chapters Menu');
};
goog.inherits(vjs.ChaptersButton, vjs.TextTrackButton);
vjs.ChaptersButton = vjs.TextTrackButton.extend({
/** @constructor */
init: function(player, options, ready){
vjs.TextTrackButton.call(this, player, options, ready);
this.el_.setAttribute('aria-label','Chapters Menu');
}
});
vjs.ChaptersButton.prototype.kind_ = 'chapters';
vjs.ChaptersButton.prototype.buttonText = 'Chapters';
vjs.ChaptersButton.prototype.className = 'vjs-chapters-button';
@ -1044,22 +1051,24 @@ vjs.ChaptersButton.prototype.createMenu = function(){
/**
* @constructor
*/
vjs.ChaptersTrackMenuItem = function(player, options){
var track = this.track = options['track'],
cue = this.cue = options['cue'],
currentTime = player.currentTime();
vjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({
/** @constructor */
init: function(player, options){
var track = this.track = options['track'],
cue = this.cue = options['cue'],
currentTime = player.currentTime();
// Modify options for parent MenuItem class's init.
options['label'] = cue.text;
options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
goog.base(this, player, options);
// Modify options for parent MenuItem class's init.
options['label'] = cue.text;
options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
vjs.MenuItem.call(this, player, options);
track.on('cuechange', vjs.bind(this, this.update));
};
goog.inherits(vjs.ChaptersTrackMenuItem, vjs.MenuItem);
track.on('cuechange', vjs.bind(this, this.update));
}
});
vjs.ChaptersTrackMenuItem.prototype.onClick = function(){
goog.base(this, 'onClick');
vjs.MenuItem.prototype.onClick.call(this);
this.player_.currentTime(this.cue.startTime);
this.update(this.cue.startTime);
};
@ -1084,7 +1093,8 @@ vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
});
// vjs.Cue = vjs.Component.extend({
// /** @constructor */
// init: function(player, options){
// goog.base(this, player, options);
// vjs.Component.call(this, player, options);
// }
// });

View File

@ -19,6 +19,7 @@
// ADD NEW TEST FILES HERE
var tests = [
'test/unit/core-object.js',
'test/unit/lib.js',
'test/unit/events.js',
'test/unit/component.js',

58
test/unit/core-object.js Normal file
View File

@ -0,0 +1,58 @@
module('Core Object');
test('should verify CoreObject extension', function(){
var TestObject = vjs.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 vjs.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 vjs.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 = vjs.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 vjs.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');
});

View File

@ -4,19 +4,20 @@
/**
* @constructor
*/
vjs.MediaFaker = function(player, options, onReady){
goog.base(this, player, options, onReady);
vjs.MediaFaker = vjs.MediaTechController.extend({
init: function(player, options, onReady){
vjs.MediaTechController.call(this, player, options, onReady);
this.triggerReady();
};
goog.inherits(vjs.MediaFaker, vjs.MediaTechController);
this.triggerReady();
}
});
// Support everything
vjs.MediaFaker.isSupported = function(){ return true; };
vjs.MediaFaker.canPlaySource = function(srcObj){ return true; };
vjs.MediaFaker.prototype.createEl = function(){
var el = goog.base(this, 'createEl', 'div', {
var el = vjs.MediaTechController.prototype.createEl.call(this, 'div', {
className: 'vjs-tech'
});
if (this.player().poster()) {
@ -32,6 +33,7 @@ vjs.MediaFaker.prototype.createEl = function(){
vjs.MediaFaker.prototype.currentTime = function(){ return 0; };
vjs.MediaFaker.prototype.volume = function(){ return 0; };
goog.exportSymbol('videojs.MediaFaker', vjs.MediaFaker);
goog.exportProperty(vjs.MediaFaker, 'isSupported', vjs.MediaFaker.isSupported);
goog.exportProperty(vjs.MediaFaker, 'canPlaySource', vjs.MediaFaker.canPlaySource);
// Export vars for Closure Compiler
vjs['MediaFaker'] = vjs.MediaFaker;
vjs['MediaFaker']['isSupported'] = vjs.MediaFaker.isSupported;
vjs['MediaFaker']['canPlaySource'] = vjs.MediaFaker.canPlaySource;