2012-12-30 21:45:50 -08:00
|
|
|
/**
|
2013-10-28 18:25:28 -07:00
|
|
|
* An instance of the `vjs.Player` class is created when any of the Video.js setup methods are used to initialize a video.
|
|
|
|
*
|
|
|
|
* ```js
|
|
|
|
* var myPlayer = videojs('example_video_1');
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* In the follwing example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
|
|
|
|
*
|
|
|
|
* ```html
|
|
|
|
* <video id="example_video_1" data-setup='{}' controls>
|
|
|
|
* <source src="my-source.mp4" type="video/mp4">
|
|
|
|
* </video>
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* After an instance has been created it can be accessed globally using `Video('example_video_1')`.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @extends vjs.Component
|
2012-12-30 21:45:50 -08:00
|
|
|
*/
|
2013-04-09 13:43:35 -07:00
|
|
|
vjs.Player = vjs.Component.extend({
|
2013-10-28 18:25:28 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* player's constructor function
|
|
|
|
*
|
|
|
|
* @constructs
|
|
|
|
* @method init
|
|
|
|
* @param {Element} tag The original video tag used for configuring options
|
|
|
|
* @param {Object=} options Player options
|
|
|
|
* @param {Function=} ready Ready callback function
|
|
|
|
*/
|
2013-04-09 13:43:35 -07:00
|
|
|
init: function(tag, options, ready){
|
|
|
|
this.tag = tag; // Store the original tag used to set options
|
|
|
|
|
2013-12-02 15:29:41 -08:00
|
|
|
// Make sure tag ID exists
|
|
|
|
tag.id = tag.id || 'vjs_video_' + vjs.guid++;
|
|
|
|
|
2013-04-09 13:43:35 -07:00
|
|
|
// Set Options
|
|
|
|
// The options argument overrides options set in the video tag
|
|
|
|
// which overrides globally set options.
|
|
|
|
// This latter part coincides with the load order
|
|
|
|
// (tag must exist before Player)
|
|
|
|
options = vjs.obj.merge(this.getTagSettings(tag), options);
|
|
|
|
|
|
|
|
// Cache for video property values.
|
|
|
|
this.cache_ = {};
|
|
|
|
|
|
|
|
// Set poster
|
|
|
|
this.poster_ = options['poster'];
|
|
|
|
// Set controls
|
|
|
|
this.controls_ = options['controls'];
|
2013-08-09 14:29:22 -07:00
|
|
|
// Original tag settings stored in options
|
|
|
|
// now remove immediately so native controls don't flash.
|
|
|
|
// May be turned back on by HTML5 tech if nativeControlsForTouch is true
|
|
|
|
tag.controls = false;
|
2013-04-09 13:43:35 -07:00
|
|
|
|
2014-02-06 16:54:35 -08:00
|
|
|
// we don't want the player to report touch activity on itself
|
|
|
|
// see enableTouchActivity in Component
|
|
|
|
options.reportTouchActivity = false;
|
2014-02-06 15:59:57 -05:00
|
|
|
|
2013-04-09 13:43:35 -07:00
|
|
|
// 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);
|
|
|
|
|
2013-08-09 14:29:22 -07:00
|
|
|
// Update controls className. Can't do this when the controls are initially
|
|
|
|
// set because the element doesn't exist yet.
|
|
|
|
if (this.controls()) {
|
|
|
|
this.addClass('vjs-controls-enabled');
|
|
|
|
} else {
|
|
|
|
this.addClass('vjs-controls-disabled');
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Make this smarter. Toggle user state between touching/mousing
|
|
|
|
// using events, since devices can have both touch and mouse events.
|
|
|
|
// if (vjs.TOUCH_ENABLED) {
|
|
|
|
// this.addClass('vjs-touch-enabled');
|
|
|
|
// }
|
|
|
|
|
2013-04-09 13:43:35 -07:00
|
|
|
// 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);
|
|
|
|
}
|
2013-08-09 14:29:22 -07:00
|
|
|
|
|
|
|
this.listenForUserActivity();
|
2013-02-04 10:31:53 -08:00
|
|
|
}
|
2013-04-09 13:43:35 -07:00
|
|
|
});
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-01-21 16:19:46 -08:00
|
|
|
/**
|
|
|
|
* Player instance options, surfaced using vjs.options
|
|
|
|
* vjs.options = vjs.Player.prototype.options_
|
|
|
|
* Make changes in vjs.options, not here.
|
|
|
|
* All options should use string keys so they avoid
|
|
|
|
* renaming by closure compiler
|
|
|
|
* @type {Object}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.options_ = vjs.options;
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Destroys the video player and does any necessary cleanup
|
|
|
|
*
|
|
|
|
* myPlayer.dispose();
|
|
|
|
*
|
|
|
|
* This is especially helpful if you are dynamically adding and removing videos
|
|
|
|
* to/from the DOM.
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.dispose = function(){
|
2013-07-18 14:39:14 -07:00
|
|
|
this.trigger('dispose');
|
|
|
|
// prevent dispose from being called twice
|
|
|
|
this.off('dispose');
|
2012-04-06 16:42:09 -07:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Kill reference to this player
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.players[this.id_] = null;
|
2013-01-16 20:24:38 -05:00
|
|
|
if (this.tag && this.tag['player']) { this.tag['player'] = null; }
|
|
|
|
if (this.el_ && this.el_['player']) { this.el_['player'] = null; }
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Ensure that tracking progress and time progress will stop and plater deleted
|
2012-12-31 15:25:56 -08:00
|
|
|
this.stopTrackingProgress();
|
2013-01-17 20:33:53 -05:00
|
|
|
this.stopTrackingCurrentTime();
|
2012-04-06 16:42:09 -07:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
if (this.tech) { this.tech.dispose(); }
|
2012-04-06 17:14:45 -07:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Component dispose
|
2013-04-09 13:43:35 -07:00
|
|
|
vjs.Component.prototype.dispose.call(this);
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.getTagSettings = function(tag){
|
2012-12-30 21:45:50 -08:00
|
|
|
var options = {
|
2013-01-10 13:06:12 -08:00
|
|
|
'sources': [],
|
|
|
|
'tracks': []
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-01-25 17:36:40 -08:00
|
|
|
vjs.obj.merge(options, vjs.getAttributeValues(tag));
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Get tag children settings
|
|
|
|
if (tag.hasChildNodes()) {
|
2013-06-28 13:06:50 -07:00
|
|
|
var children, child, childName, i, j;
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-06-28 13:06:50 -07:00
|
|
|
children = tag.childNodes;
|
|
|
|
|
|
|
|
for (i=0,j=children.length; i<j; i++) {
|
2012-12-30 21:45:50 -08:00
|
|
|
child = children[i];
|
|
|
|
// Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
|
|
|
|
childName = child.nodeName.toLowerCase();
|
2013-01-10 13:06:12 -08:00
|
|
|
if (childName === 'source') {
|
|
|
|
options['sources'].push(vjs.getAttributeValues(child));
|
|
|
|
} else if (childName === 'track') {
|
|
|
|
options['tracks'].push(vjs.getAttributeValues(child));
|
2011-11-30 13:06:32 -08:00
|
|
|
}
|
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2013-01-10 13:06:12 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
return options;
|
|
|
|
};
|
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.createEl = function(){
|
2013-04-09 13:43:35 -07:00
|
|
|
var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
|
2012-12-30 21:45:50 -08:00
|
|
|
var tag = this.tag;
|
|
|
|
|
|
|
|
// Remove width/height attrs from tag so CSS can make it 100% width/height
|
2013-01-10 13:06:12 -08:00
|
|
|
tag.removeAttribute('width');
|
|
|
|
tag.removeAttribute('height');
|
2013-10-15 13:38:48 -07:00
|
|
|
// Empty video tag tracks so the built-in player doesn't use them also.
|
2012-12-30 21:45:50 -08:00
|
|
|
// This may not be fast enough to stop HTML5 browsers from reading the tags
|
|
|
|
// so we'll need to turn off any default tracks if we're manually doing
|
|
|
|
// captions and subtitles. videoElement.textTracks
|
|
|
|
if (tag.hasChildNodes()) {
|
2013-06-28 13:06:50 -07:00
|
|
|
var nodes, nodesLength, i, node, nodeName, removeNodes;
|
|
|
|
|
|
|
|
nodes = tag.childNodes;
|
|
|
|
nodesLength = nodes.length;
|
|
|
|
removeNodes = [];
|
|
|
|
|
|
|
|
while (nodesLength--) {
|
|
|
|
node = nodes[nodesLength];
|
|
|
|
nodeName = node.nodeName.toLowerCase();
|
2013-10-15 13:38:48 -07:00
|
|
|
if (nodeName === 'track') {
|
2013-06-28 13:06:50 -07:00
|
|
|
removeNodes.push(node);
|
2011-11-29 11:40:05 -08:00
|
|
|
}
|
|
|
|
}
|
2013-06-28 13:06:50 -07:00
|
|
|
|
|
|
|
for (i=0; i<removeNodes.length; i++) {
|
|
|
|
tag.removeChild(removeNodes[i]);
|
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Give video tag ID and class to player div
|
|
|
|
// ID will now reference player box, not the video tag
|
|
|
|
el.id = tag.id;
|
|
|
|
el.className = tag.className;
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Update tag id/class for use as HTML5 playback tech
|
|
|
|
// Might think we should do this after embedding in container so .vjs-tech class
|
|
|
|
// doesn't flash 100% width/height, but class only applies with .video-js parent
|
2013-01-10 13:06:12 -08:00
|
|
|
tag.id += '_html5_api';
|
|
|
|
tag.className = 'vjs-tech';
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Make player findable on elements
|
2013-01-16 20:24:38 -05:00
|
|
|
tag['player'] = el['player'] = this;
|
2012-12-30 21:45:50 -08:00
|
|
|
// Default state of video is paused
|
2013-01-10 13:06:12 -08:00
|
|
|
this.addClass('vjs-paused');
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-03-05 18:35:55 +02:00
|
|
|
// Make box use width/height of tag, or rely on default implementation
|
2012-12-30 21:45:50 -08:00
|
|
|
// Enforce with CSS since width/height attrs don't work on divs
|
2013-01-17 21:03:25 -05:00
|
|
|
this.width(this.options_['width'], true); // (true) Skip resize listener on load
|
|
|
|
this.height(this.options_['height'], true);
|
2013-04-04 11:06:13 -07:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Wrap video tag in div (el/box) container
|
|
|
|
if (tag.parentNode) {
|
|
|
|
tag.parentNode.insertBefore(el, tag);
|
|
|
|
}
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2014-06-10 16:55:08 -07:00
|
|
|
// The event listeners need to be added before the children are added
|
|
|
|
// in the component init because the tech (loaded with mediaLoader) may
|
|
|
|
// fire events, like loadstart, that these events need to capture.
|
|
|
|
// Long term it might be better to expose a way to do this in component.init
|
|
|
|
// like component.initEventListeners() that runs between el creation and
|
|
|
|
// adding children
|
|
|
|
this.el_ = el;
|
|
|
|
this.on('loadstart', this.onLoadStart);
|
|
|
|
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('fullscreenchange', this.onFullscreenChange);
|
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
return el;
|
2013-01-10 13:06:12 -08:00
|
|
|
};
|
2012-12-30 21:45:50 -08:00
|
|
|
|
|
|
|
// /* Media Technology (tech)
|
|
|
|
// ================================================================================ */
|
|
|
|
// Load/Create an instance of playback technlogy including element and API methods
|
|
|
|
// And append playback element in player div.
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.loadTech = function(techName, source){
|
2012-12-30 21:45:50 -08:00
|
|
|
|
|
|
|
// Pause and remove current playback technology
|
|
|
|
if (this.tech) {
|
|
|
|
this.unloadTech();
|
2013-12-11 13:27:47 -08:00
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-12-11 13:27:47 -08:00
|
|
|
// get rid of the HTML5 video tag as soon as we are using another tech
|
|
|
|
if (techName !== 'Html5' && this.tag) {
|
2013-10-15 13:38:48 -07:00
|
|
|
vjs.Html5.disposeMediaElement(this.tag);
|
2012-12-30 21:45:50 -08:00
|
|
|
this.tag = null;
|
|
|
|
}
|
2012-01-05 23:25:09 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
this.techName = techName;
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Turn off API access because we're loading a new tech that might load asynchronously
|
|
|
|
this.isReady_ = false;
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
var techReady = function(){
|
2013-01-16 20:24:38 -05:00
|
|
|
this.player_.triggerReady();
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-31 15:25:56 -08:00
|
|
|
// Manually track progress in cases where the browser/flash player doesn't report it.
|
2013-08-25 18:50:59 -07:00
|
|
|
if (!this.features['progressEvents']) {
|
2013-01-16 20:24:38 -05:00
|
|
|
this.player_.manualProgressOn();
|
2012-12-31 15:25:56 -08:00
|
|
|
}
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-31 15:25:56 -08:00
|
|
|
// Manually track timeudpates in cases where the browser/flash player doesn't report it.
|
2013-08-25 18:50:59 -07:00
|
|
|
if (!this.features['timeupdateEvents']) {
|
2013-01-16 20:24:38 -05:00
|
|
|
this.player_.manualTimeUpdatesOn();
|
2012-12-31 15:25:56 -08:00
|
|
|
}
|
2013-01-10 13:06:12 -08:00
|
|
|
};
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Grab tech-specific options from player options and add source and parent element to use.
|
2013-02-15 23:31:31 +03:00
|
|
|
var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
if (source) {
|
|
|
|
if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
|
2013-01-10 13:06:12 -08:00
|
|
|
techOptions['startTime'] = this.cache_.currentTime;
|
2011-11-29 11:40:05 -08:00
|
|
|
}
|
2012-04-25 15:15:33 -07:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
this.cache_.src = source.src;
|
|
|
|
}
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Initialize tech instance
|
2013-01-04 16:58:23 -08:00
|
|
|
this.tech = new window['videojs'][techName](this, techOptions);
|
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
this.tech.ready(techReady);
|
|
|
|
};
|
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.Player.prototype.unloadTech = function(){
|
2013-04-04 11:06:13 -07:00
|
|
|
this.isReady_ = false;
|
2013-01-10 13:06:12 -08:00
|
|
|
this.tech.dispose();
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
// Turn off any manual progress or timeupdate tracking
|
|
|
|
if (this.manualProgress) { this.manualProgressOff(); }
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
this.tech = false;
|
|
|
|
};
|
2012-12-30 21:45:50 -08:00
|
|
|
|
|
|
|
// There's many issues around changing the size of a Flash (or other plugin) object.
|
|
|
|
// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
|
|
|
|
// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
|
|
|
|
// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
|
|
|
|
// reloadTech: function(betweenFn){
|
2013-01-10 13:06:12 -08:00
|
|
|
// vjs.log('unloadingTech')
|
2012-12-30 21:45:50 -08:00
|
|
|
// this.unloadTech();
|
2013-01-10 13:06:12 -08:00
|
|
|
// vjs.log('unloadedTech')
|
2012-12-30 21:45:50 -08:00
|
|
|
// if (betweenFn) { betweenFn.call(); }
|
2013-01-10 13:06:12 -08:00
|
|
|
// vjs.log('LoadingTech')
|
2012-12-30 21:45:50 -08:00
|
|
|
// this.loadTech(this.techName, { src: this.cache_.src })
|
2013-01-10 13:06:12 -08:00
|
|
|
// vjs.log('loadedTech')
|
2012-12-30 21:45:50 -08:00
|
|
|
// },
|
|
|
|
|
|
|
|
/* Fallbacks for unsupported event types
|
2011-11-29 11:40:05 -08:00
|
|
|
================================================================================ */
|
2012-12-30 21:45:50 -08:00
|
|
|
// Manually trigger progress events based on changes to the buffered amount
|
|
|
|
// Many flash players and older HTML5 browsers don't send progress or progress-like events
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.manualProgressOn = function(){
|
2012-12-31 15:25:56 -08:00
|
|
|
this.manualProgress = true;
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2012-12-31 15:25:56 -08:00
|
|
|
// Trigger progress watching when a source begins loading
|
|
|
|
this.trackProgress();
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2012-12-31 15:25:56 -08:00
|
|
|
// Watch for a native progress event call on the tech element
|
|
|
|
// In HTML5, some older versions don't support the progress event
|
|
|
|
// So we're assuming they don't, and turning off manual progress if they do.
|
|
|
|
// As opposed to doing user agent detection
|
2014-04-25 13:14:03 -07:00
|
|
|
if (this.tech) {
|
|
|
|
this.tech.one('progress', function(){
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2014-04-25 13:14:03 -07:00
|
|
|
// Update known progress support for this playback technology
|
|
|
|
this.features['progressEvents'] = true;
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2014-04-25 13:14:03 -07:00
|
|
|
// Turn off manual progress tracking
|
|
|
|
this.player_.manualProgressOff();
|
|
|
|
});
|
|
|
|
}
|
2012-12-31 15:25:56 -08:00
|
|
|
};
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.manualProgressOff = function(){
|
2012-12-31 15:25:56 -08:00
|
|
|
this.manualProgress = false;
|
|
|
|
this.stopTrackingProgress();
|
|
|
|
};
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.trackProgress = function(){
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
this.progressInterval = setInterval(vjs.bind(this, function(){
|
2012-12-31 15:25:56 -08:00
|
|
|
// Don't trigger unless buffered amount is greater than last time
|
|
|
|
// log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
|
|
|
|
/* TODO: update for multiple buffered regions */
|
|
|
|
if (this.cache_.bufferEnd < this.buffered().end(0)) {
|
2013-01-10 13:06:12 -08:00
|
|
|
this.trigger('progress');
|
2012-12-31 15:25:56 -08:00
|
|
|
} else if (this.bufferedPercent() == 1) {
|
|
|
|
this.stopTrackingProgress();
|
2013-01-10 13:06:12 -08:00
|
|
|
this.trigger('progress'); // Last update
|
2012-12-31 15:25:56 -08:00
|
|
|
}
|
|
|
|
}), 500);
|
|
|
|
};
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.stopTrackingProgress = function(){ clearInterval(this.progressInterval); };
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/*! Time Tracking -------------------------------------------------------------- */
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.manualTimeUpdatesOn = function(){
|
2012-12-31 15:25:56 -08:00
|
|
|
this.manualTimeUpdates = true;
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
this.on('play', this.trackCurrentTime);
|
|
|
|
this.on('pause', this.stopTrackingCurrentTime);
|
2012-12-31 15:25:56 -08:00
|
|
|
// timeupdate is also called by .currentTime whenever current time is set
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2012-12-31 15:25:56 -08:00
|
|
|
// Watch for native timeupdate event
|
2014-04-25 13:14:03 -07:00
|
|
|
if (this.tech) {
|
|
|
|
this.tech.one('timeupdate', function(){
|
|
|
|
// Update known progress support for this playback technology
|
|
|
|
this.features['timeupdateEvents'] = true;
|
|
|
|
// Turn off manual progress tracking
|
|
|
|
this.player_.manualTimeUpdatesOff();
|
|
|
|
});
|
|
|
|
}
|
2012-12-31 15:25:56 -08:00
|
|
|
};
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.manualTimeUpdatesOff = function(){
|
2012-12-31 15:25:56 -08:00
|
|
|
this.manualTimeUpdates = false;
|
|
|
|
this.stopTrackingCurrentTime();
|
2013-01-10 13:06:12 -08:00
|
|
|
this.off('play', this.trackCurrentTime);
|
|
|
|
this.off('pause', this.stopTrackingCurrentTime);
|
2012-12-31 15:25:56 -08:00
|
|
|
};
|
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.trackCurrentTime = function(){
|
2012-12-31 15:25:56 -08:00
|
|
|
if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
|
2013-01-04 16:58:23 -08:00
|
|
|
this.currentTimeInterval = setInterval(vjs.bind(this, function(){
|
2013-01-10 13:06:12 -08:00
|
|
|
this.trigger('timeupdate');
|
2012-12-31 15:25:56 -08:00
|
|
|
}), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
|
|
|
|
};
|
|
|
|
|
|
|
|
// Turn off play progress tracking (when paused or dragging)
|
2014-04-25 13:42:11 -07:00
|
|
|
vjs.Player.prototype.stopTrackingCurrentTime = function(){
|
|
|
|
clearInterval(this.currentTimeInterval);
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2014-04-25 13:42:11 -07:00
|
|
|
// #1002 - if the video ends right before the next timeupdate would happen,
|
|
|
|
// the progress bar won't make it all the way to the end
|
|
|
|
this.trigger('timeupdate');
|
|
|
|
};
|
2012-12-30 21:45:50 -08:00
|
|
|
// /* Player event handlers (how the player reacts to certain events)
|
|
|
|
// ================================================================================ */
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Fired when the user agent begins looking for media data
|
|
|
|
* @event loadstart
|
|
|
|
*/
|
2014-04-03 10:41:02 -07:00
|
|
|
vjs.Player.prototype.onLoadStart = function() {
|
2014-06-10 16:55:08 -07:00
|
|
|
// TODO: Update to use `emptied` event instead. See #1277.
|
|
|
|
|
|
|
|
// reset the error state
|
|
|
|
this.error(null);
|
|
|
|
|
|
|
|
// If it's already playing we want to trigger a firstplay event now.
|
|
|
|
// The firstplay event relies on both the play and loadstart events
|
|
|
|
// which can happen in any order for a new source
|
|
|
|
if (!this.paused()) {
|
|
|
|
this.trigger('firstplay');
|
|
|
|
} else {
|
|
|
|
// reset the hasStarted state
|
|
|
|
this.hasStarted(false);
|
|
|
|
this.one('play', function(){
|
|
|
|
this.hasStarted(true);
|
|
|
|
});
|
2014-05-12 17:08:48 -07:00
|
|
|
}
|
2014-04-03 10:41:02 -07:00
|
|
|
};
|
|
|
|
|
2014-06-10 16:55:08 -07:00
|
|
|
vjs.Player.prototype.hasStarted_ = false;
|
|
|
|
|
|
|
|
vjs.Player.prototype.hasStarted = function(hasStarted){
|
|
|
|
if (hasStarted !== undefined) {
|
|
|
|
// only update if this is a new value
|
|
|
|
if (this.hasStarted_ !== hasStarted) {
|
|
|
|
this.hasStarted_ = hasStarted;
|
|
|
|
if (hasStarted) {
|
|
|
|
this.addClass('vjs-has-started');
|
|
|
|
// trigger the firstplay event if this newly has played
|
|
|
|
this.trigger('firstplay');
|
|
|
|
} else {
|
|
|
|
this.removeClass('vjs-has-started');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
2014-04-03 10:41:02 -07:00
|
|
|
}
|
2014-06-10 16:55:08 -07:00
|
|
|
return this.hasStarted_;
|
|
|
|
};
|
2013-10-28 18:25:28 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when the player has initial duration and dimension information
|
|
|
|
* @event loadedmetadata
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.onLoadedMetaData;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when the player has downloaded data at the current playback position
|
|
|
|
* @event loadeddata
|
|
|
|
*/
|
2013-10-31 11:37:54 -06:00
|
|
|
vjs.Player.prototype.onLoadedData;
|
2013-10-28 18:25:28 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when the player has finished downloading the source data
|
|
|
|
* @event loadedalldata
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.onLoadedAllData;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired whenever the media begins or resumes playback
|
|
|
|
* @event play
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.onPlay = function(){
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.removeClass(this.el_, 'vjs-paused');
|
|
|
|
vjs.addClass(this.el_, 'vjs-playing');
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Fired the first time a video is played
|
|
|
|
*
|
|
|
|
* Not part of the HLS spec, and we're not sure if this is the best
|
|
|
|
* implementation yet, so use sparingly. If you don't have a reason to
|
|
|
|
* prevent playback, use `myPlayer.one('play');` instead.
|
|
|
|
*
|
|
|
|
* @event firstplay
|
|
|
|
*/
|
2013-02-10 22:45:00 -08:00
|
|
|
vjs.Player.prototype.onFirstPlay = function(){
|
|
|
|
//If the first starttime attribute is specified
|
|
|
|
//then we will start at the given offset in seconds
|
|
|
|
if(this.options_['starttime']){
|
|
|
|
this.currentTime(this.options_['starttime']);
|
|
|
|
}
|
2013-08-09 14:29:22 -07:00
|
|
|
|
|
|
|
this.addClass('vjs-has-started');
|
2013-02-10 22:45:00 -08:00
|
|
|
};
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Fired whenever the media has been paused
|
|
|
|
* @event pause
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.onPause = function(){
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.removeClass(this.el_, 'vjs-playing');
|
|
|
|
vjs.addClass(this.el_, 'vjs-paused');
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Fired when the current playback position has changed
|
|
|
|
*
|
|
|
|
* During playback this is fired every 15-250 milliseconds, depnding on the
|
|
|
|
* playback technology in use.
|
|
|
|
* @event timeupdate
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.onTimeUpdate;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired while the user agent is downloading media data
|
|
|
|
* @event progress
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.onProgress = function(){
|
2012-12-30 21:45:50 -08:00
|
|
|
// Add custom event for when source is finished downloading.
|
|
|
|
if (this.bufferedPercent() == 1) {
|
2013-01-10 13:06:12 -08:00
|
|
|
this.trigger('loadedalldata');
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Fired when the end of the media resource is reached (currentTime == duration)
|
|
|
|
* @event ended
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.onEnded = function(){
|
|
|
|
if (this.options_['loop']) {
|
|
|
|
this.currentTime(0);
|
|
|
|
this.play();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when the duration of the media resource is first known or changed
|
|
|
|
* @event durationchange
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.onDurationChange = function(){
|
2013-10-28 18:25:28 -07:00
|
|
|
// Allows for cacheing value instead of asking player each time.
|
2013-12-02 17:37:12 -08:00
|
|
|
// We need to get the techGet response and check for a value so we don't
|
|
|
|
// accidentally cause the stack to blow up.
|
|
|
|
var duration = this.techGet('duration');
|
|
|
|
if (duration) {
|
2014-04-01 17:19:28 -07:00
|
|
|
if (duration < 0) {
|
|
|
|
duration = Infinity;
|
|
|
|
}
|
2013-12-02 17:37:12 -08:00
|
|
|
this.duration(duration);
|
2014-04-01 17:19:28 -07:00
|
|
|
// Determine if the stream is live and propagate styles down to UI.
|
|
|
|
if (duration === Infinity) {
|
|
|
|
this.addClass('vjs-live');
|
|
|
|
} else {
|
|
|
|
this.removeClass('vjs-live');
|
|
|
|
}
|
2013-12-02 17:37:12 -08:00
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Fired when the volume changes
|
|
|
|
* @event volumechange
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.onVolumeChange;
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Fired when the player switches in or out of fullscreen mode
|
|
|
|
* @event fullscreenchange
|
|
|
|
*/
|
2013-05-28 15:29:42 -07:00
|
|
|
vjs.Player.prototype.onFullscreenChange = function() {
|
2014-05-15 14:46:28 -07:00
|
|
|
if (this.isFullscreen()) {
|
2013-02-08 01:11:33 +03:00
|
|
|
this.addClass('vjs-fullscreen');
|
|
|
|
} else {
|
|
|
|
this.removeClass('vjs-fullscreen');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// /* Player API
|
|
|
|
// ================================================================================ */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Object for cached values.
|
|
|
|
* @private
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.cache_;
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.getCache = function(){
|
2012-12-30 21:45:50 -08:00
|
|
|
return this.cache_;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Pass values to the playback tech
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.techCall = function(method, arg){
|
2012-12-30 21:45:50 -08:00
|
|
|
// If it's not ready yet, call method when it is
|
2013-05-31 10:31:18 -07:00
|
|
|
if (this.tech && !this.tech.isReady_) {
|
2012-12-30 21:45:50 -08:00
|
|
|
this.tech.ready(function(){
|
|
|
|
this[method](arg);
|
|
|
|
});
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Otherwise call method now
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
this.tech[method](arg);
|
|
|
|
} catch(e) {
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.log(e);
|
2013-05-02 17:07:05 -07:00
|
|
|
throw e;
|
2012-02-02 19:16:45 -08:00
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
|
|
|
};
|
2012-02-02 19:16:45 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Get calls can't wait for the tech, and sometimes don't need to.
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.techGet = function(method){
|
2013-08-26 16:31:07 -07:00
|
|
|
if (this.tech && this.tech.isReady_) {
|
2012-02-03 10:28:00 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Flash likes to die and reload when you hide or reposition it.
|
|
|
|
// In these cases the object methods go away and we get errors.
|
|
|
|
// When that happens we'll catch the errors and inform tech that it's not ready any more.
|
|
|
|
try {
|
|
|
|
return this.tech[method]();
|
|
|
|
} catch(e) {
|
|
|
|
// When building additional tech libs, an expected method may not be defined yet
|
|
|
|
if (this.tech[method] === undefined) {
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);
|
2012-12-30 21:45:50 -08:00
|
|
|
} else {
|
|
|
|
// When a method isn't available on the object it throws a TypeError
|
2013-01-10 13:06:12 -08:00
|
|
|
if (e.name == 'TypeError') {
|
|
|
|
vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);
|
2012-12-30 21:45:50 -08:00
|
|
|
this.tech.isReady_ = false;
|
|
|
|
} else {
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.log(e);
|
2012-02-03 10:28:00 -08:00
|
|
|
}
|
|
|
|
}
|
2013-05-02 17:07:05 -07:00
|
|
|
throw e;
|
2011-11-29 11:40:05 -08:00
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2012-02-03 10:28:00 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
return;
|
|
|
|
};
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-01-17 20:33:53 -05:00
|
|
|
/**
|
2013-10-28 18:25:28 -07:00
|
|
|
* start media playback
|
|
|
|
*
|
|
|
|
* myPlayer.play();
|
|
|
|
*
|
|
|
|
* @return {vjs.Player} self
|
2013-01-17 20:33:53 -05:00
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.play = function(){
|
2014-05-14 15:46:23 -07:00
|
|
|
this.techCall('play');
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
};
|
2011-12-05 11:28:18 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Pause the video playback
|
|
|
|
*
|
|
|
|
* myPlayer.pause();
|
|
|
|
*
|
|
|
|
* @return {vjs.Player} self
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.pause = function(){
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('pause');
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
};
|
2011-12-05 11:28:18 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Check if the player is paused
|
|
|
|
*
|
|
|
|
* var isPaused = myPlayer.paused();
|
|
|
|
* var isPlaying = !myPlayer.paused();
|
|
|
|
*
|
|
|
|
* @return {Boolean} false if the media is currently playing, or true otherwise
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.paused = function(){
|
2013-10-28 18:25:28 -07:00
|
|
|
// The initial state of paused should be true (in Safari it's actually false)
|
2013-01-10 13:06:12 -08:00
|
|
|
return (this.techGet('paused') === false) ? false : true;
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Get or set the current time (in seconds)
|
|
|
|
*
|
|
|
|
* // get
|
|
|
|
* var whereYouAt = myPlayer.currentTime();
|
|
|
|
*
|
|
|
|
* // set
|
|
|
|
* myPlayer.currentTime(120); // 2 minutes into the video
|
|
|
|
*
|
|
|
|
* @param {Number|String=} seconds The time to seek to
|
|
|
|
* @return {Number} The time in seconds, when not setting
|
|
|
|
* @return {vjs.Player} self, when the current time is set
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.currentTime = function(seconds){
|
2012-12-30 21:45:50 -08:00
|
|
|
if (seconds !== undefined) {
|
2011-12-05 11:28:18 -08:00
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('setCurrentTime', seconds);
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
// improve the accuracy of manual timeupdates
|
2013-01-10 13:06:12 -08:00
|
|
|
if (this.manualTimeUpdates) { this.trigger('timeupdate'); }
|
2012-04-25 15:15:33 -07:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
}
|
2012-04-25 15:15:33 -07:00
|
|
|
|
2014-02-11 17:20:07 -08:00
|
|
|
// cache last currentTime and return. default to 0 seconds
|
|
|
|
//
|
|
|
|
// Caching the currentTime is meant to prevent a massive amount of reads on the tech's
|
|
|
|
// currentTime when scrubbing, but may not provide much performace benefit afterall.
|
|
|
|
// Should be tested. Also something has to read the actual current time or the cache will
|
|
|
|
// never get updated.
|
2013-01-10 13:06:12 -08:00
|
|
|
return this.cache_.currentTime = (this.techGet('currentTime') || 0);
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2012-04-25 15:15:33 -07:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Get the length in time of the video in seconds
|
|
|
|
*
|
|
|
|
* var lengthOfVideo = myPlayer.duration();
|
|
|
|
*
|
|
|
|
* **NOTE**: The video must have started loading before the duration can be
|
|
|
|
* known, and in the case of Flash, may not be known until the video starts
|
|
|
|
* playing.
|
|
|
|
*
|
|
|
|
* @return {Number} The duration of the video in seconds
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.duration = function(seconds){
|
2012-12-30 21:45:50 -08:00
|
|
|
if (seconds !== undefined) {
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
// cache the last set value for optimiized scrubbing (esp. Flash)
|
2012-12-30 21:45:50 -08:00
|
|
|
this.cache_.duration = parseFloat(seconds);
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
}
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-10-15 13:38:48 -07:00
|
|
|
if (this.cache_.duration === undefined) {
|
|
|
|
this.onDurationChange();
|
|
|
|
}
|
|
|
|
|
2013-12-02 17:37:12 -08:00
|
|
|
return this.cache_.duration || 0;
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Calculates how much time is left. Not in spec, but useful.
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.remainingTime = function(){
|
2012-12-30 21:45:50 -08:00
|
|
|
return this.duration() - this.currentTime();
|
|
|
|
};
|
|
|
|
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-buffered
|
2013-01-10 13:06:12 -08:00
|
|
|
// Buffered returns a timerange object.
|
|
|
|
// Kind of like an array of portions of the video that have been downloaded.
|
2012-12-30 21:45:50 -08:00
|
|
|
// So far no browsers return more than one range (portion)
|
2013-10-28 18:25:28 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a TimeRange object with the times of the video that have been downloaded
|
|
|
|
*
|
|
|
|
* If you just want the percent of the video that's been downloaded,
|
|
|
|
* use bufferedPercent.
|
|
|
|
*
|
|
|
|
* // Number of different ranges of time have been buffered. Usually 1.
|
|
|
|
* numberOfRanges = bufferedTimeRange.length,
|
|
|
|
*
|
|
|
|
* // Time in seconds when the first range starts. Usually 0.
|
|
|
|
* firstRangeStart = bufferedTimeRange.start(0),
|
|
|
|
*
|
|
|
|
* // Time in seconds when the first range ends
|
|
|
|
* firstRangeEnd = bufferedTimeRange.end(0),
|
|
|
|
*
|
|
|
|
* // Length in seconds of the first time range
|
|
|
|
* firstRangeLength = firstRangeEnd - firstRangeStart;
|
|
|
|
*
|
|
|
|
* @return {Object} A mock TimeRange object (following HTML spec)
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.buffered = function(){
|
2013-01-10 13:06:12 -08:00
|
|
|
var buffered = this.techGet('buffered'),
|
2012-12-30 21:45:50 -08:00
|
|
|
start = 0,
|
2013-07-29 15:48:25 -07:00
|
|
|
buflast = buffered.length - 1,
|
2013-01-10 13:06:12 -08:00
|
|
|
// Default end to 0 and store in values
|
|
|
|
end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-07-29 15:48:25 -07:00
|
|
|
if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {
|
|
|
|
end = buffered.end(buflast);
|
2012-12-30 21:45:50 -08:00
|
|
|
// Storing values allows them be overridden by setBufferedFromProgress
|
|
|
|
this.cache_.bufferEnd = end;
|
|
|
|
}
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
return vjs.createTimeRange(start, end);
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Get the percent (as a decimal) of the video that's been downloaded
|
|
|
|
*
|
|
|
|
* var howMuchIsDownloaded = myPlayer.bufferedPercent();
|
|
|
|
*
|
|
|
|
* 0 means none, 1 means all.
|
|
|
|
* (This method isn't in the HTML5 spec, but it's very convenient)
|
|
|
|
*
|
|
|
|
* @return {Number} A decimal between 0 and 1 representing the percent
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.bufferedPercent = function(){
|
2012-12-30 21:45:50 -08:00
|
|
|
return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
|
|
|
|
};
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Get or set the current volume of the media
|
|
|
|
*
|
|
|
|
* // get
|
|
|
|
* var howLoudIsIt = myPlayer.volume();
|
|
|
|
*
|
|
|
|
* // set
|
|
|
|
* myPlayer.volume(0.5); // Set volume to half
|
|
|
|
*
|
|
|
|
* 0 is off (muted), 1.0 is all the way up, 0.5 is half way.
|
|
|
|
*
|
|
|
|
* @param {Number} percentAsDecimal The new volume as a decimal percent
|
|
|
|
* @return {Number} The current volume, when getting
|
|
|
|
* @return {vjs.Player} self, when setting
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.volume = function(percentAsDecimal){
|
2012-12-30 21:45:50 -08:00
|
|
|
var vol;
|
2012-04-09 14:53:03 -07:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
if (percentAsDecimal !== undefined) {
|
|
|
|
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
|
|
|
|
this.cache_.volume = vol;
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('setVolume', vol);
|
|
|
|
vjs.setLocalStorage('volume', vol);
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
}
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Default to 1 when returning current volume.
|
2013-01-10 13:06:12 -08:00
|
|
|
vol = parseFloat(this.techGet('volume'));
|
2012-12-30 21:45:50 -08:00
|
|
|
return (isNaN(vol)) ? 1 : vol;
|
|
|
|
};
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current muted state, or turn mute on or off
|
|
|
|
*
|
|
|
|
* // get
|
|
|
|
* var isVolumeMuted = myPlayer.muted();
|
|
|
|
*
|
|
|
|
* // set
|
|
|
|
* myPlayer.muted(true); // mute the volume
|
|
|
|
*
|
|
|
|
* @param {Boolean=} muted True to mute, false to unmute
|
|
|
|
* @return {Boolean} True if mute is on, false if not, when getting
|
|
|
|
* @return {vjs.Player} self, when setting mute
|
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.muted = function(muted){
|
2012-12-30 21:45:50 -08:00
|
|
|
if (muted !== undefined) {
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('setMuted', muted);
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
}
|
2013-01-10 13:06:12 -08:00
|
|
|
return this.techGet('muted') || false; // Default to false
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2012-01-05 23:25:09 -08:00
|
|
|
|
2013-12-09 16:16:09 -08:00
|
|
|
// Check if current tech can support native fullscreen
|
|
|
|
// (e.g. with built in controls lik iOS, so not our flash swf)
|
|
|
|
vjs.Player.prototype.supportsFullScreen = function(){
|
|
|
|
return this.techGet('supportsFullScreen') || false;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* is the player in fullscreen
|
|
|
|
* @type {Boolean}
|
|
|
|
* @private
|
|
|
|
*/
|
2014-05-15 14:46:28 -07:00
|
|
|
vjs.Player.prototype.isFullscreen_ = false;
|
2013-12-09 16:16:09 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the player is in fullscreen mode
|
|
|
|
*
|
|
|
|
* // get
|
2014-05-15 14:46:28 -07:00
|
|
|
* var fullscreenOrNot = myPlayer.isFullscreen();
|
2013-12-09 16:16:09 -08:00
|
|
|
*
|
|
|
|
* // set
|
2014-05-15 14:46:28 -07:00
|
|
|
* myPlayer.isFullscreen(true); // tell the player it's in fullscreen
|
2013-12-09 16:16:09 -08:00
|
|
|
*
|
2014-05-15 14:46:28 -07:00
|
|
|
* NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
|
|
|
|
* property and instead document.fullscreenElement is used. But isFullscreen is
|
2013-12-09 16:16:09 -08:00
|
|
|
* still a valuable property for internal player workings.
|
|
|
|
*
|
|
|
|
* @param {Boolean=} isFS Update the player's fullscreen state
|
|
|
|
* @return {Boolean} true if fullscreen, false if not
|
|
|
|
* @return {vjs.Player} self, when setting
|
|
|
|
*/
|
2014-05-15 14:46:28 -07:00
|
|
|
vjs.Player.prototype.isFullscreen = function(isFS){
|
2013-12-09 16:16:09 -08:00
|
|
|
if (isFS !== undefined) {
|
2014-05-15 14:46:28 -07:00
|
|
|
this.isFullscreen_ = !!isFS;
|
2013-12-09 16:16:09 -08:00
|
|
|
return this;
|
|
|
|
}
|
2014-05-15 14:46:28 -07:00
|
|
|
return this.isFullscreen_;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Old naming for isFullscreen()
|
|
|
|
* @deprecated for lowercase 's' version
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.isFullScreen = function(isFS){
|
|
|
|
vjs.log.warn('player.isFullScreen() has been deprecated, use player.isFullscreen() with a lowercase "s")');
|
|
|
|
return this.isFullscreen(isFS);
|
2013-12-09 16:16:09 -08:00
|
|
|
};
|
2012-01-29 20:03:44 -08:00
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Increase the size of the video to full screen
|
|
|
|
*
|
2014-05-15 14:46:28 -07:00
|
|
|
* myPlayer.requestFullscreen();
|
2013-10-28 18:25:28 -07:00
|
|
|
*
|
|
|
|
* In some browsers, full screen is not supported natively, so it enters
|
|
|
|
* "full window mode", where the video fills the browser window.
|
|
|
|
* In browsers and devices that support native full screen, sometimes the
|
|
|
|
* browser's default controls will be shown, and not the Video.js custom skin.
|
|
|
|
* This includes most mobile devices (iOS, Android) and older versions of
|
|
|
|
* Safari.
|
|
|
|
*
|
|
|
|
* @return {vjs.Player} self
|
|
|
|
*/
|
2014-05-15 14:46:28 -07:00
|
|
|
vjs.Player.prototype.requestFullscreen = function(){
|
|
|
|
var fsApi = vjs.browser.fullscreenAPI;
|
|
|
|
|
|
|
|
this.isFullscreen(true);
|
2012-02-13 17:51:52 -08:00
|
|
|
|
2014-05-15 14:46:28 -07:00
|
|
|
if (fsApi) {
|
2013-04-19 20:48:09 -04:00
|
|
|
// the browser supports going fullscreen at the element level so we can
|
|
|
|
// take the controls fullscreen as well as the video
|
2012-02-13 17:51:52 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Trigger fullscreenchange event after change
|
2013-05-28 15:29:42 -07:00
|
|
|
// We have to specifically add this each time, and remove
|
|
|
|
// when cancelling fullscreen. Otherwise if there's multiple
|
|
|
|
// players on a page, they would all be reacting to the same fullscreen
|
|
|
|
// events
|
2014-06-12 14:05:27 -07:00
|
|
|
vjs.on(document, fsApi['fullscreenchange'], vjs.bind(this, function(e){
|
2014-05-15 14:46:28 -07:00
|
|
|
this.isFullscreen(document[fsApi.fullscreenElement]);
|
2012-02-13 17:51:52 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// If cancelling fullscreen, remove event listener.
|
2014-05-15 14:46:28 -07:00
|
|
|
if (this.isFullscreen() === false) {
|
2014-06-12 14:05:27 -07:00
|
|
|
vjs.off(document, fsApi['fullscreenchange'], arguments.callee);
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2012-01-05 23:25:09 -08:00
|
|
|
|
2013-05-28 15:29:42 -07:00
|
|
|
this.trigger('fullscreenchange');
|
2012-12-30 21:45:50 -08:00
|
|
|
}));
|
2012-01-05 23:25:09 -08:00
|
|
|
|
2014-05-15 14:46:28 -07:00
|
|
|
this.el_[fsApi.requestFullscreen]();
|
2013-05-06 10:44:59 -07:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
} else if (this.tech.supportsFullScreen()) {
|
2013-04-19 20:48:09 -04:00
|
|
|
// we can't take the video.js controls fullscreen but we can go fullscreen
|
|
|
|
// with native controls
|
|
|
|
this.techCall('enterFullScreen');
|
2012-12-30 21:45:50 -08:00
|
|
|
} else {
|
2013-04-19 20:48:09 -04:00
|
|
|
// fullscreen isn't supported so we'll just stretch the video element to
|
|
|
|
// fill the viewport
|
2012-12-30 21:45:50 -08:00
|
|
|
this.enterFullWindow();
|
2013-05-06 10:44:59 -07:00
|
|
|
this.trigger('fullscreenchange');
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2012-01-05 23:25:09 -08:00
|
|
|
|
2013-04-19 20:48:09 -04:00
|
|
|
return this;
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2012-02-13 17:42:22 -08:00
|
|
|
|
2014-05-15 14:46:28 -07:00
|
|
|
/**
|
|
|
|
* Old naming for requestFullscreen
|
|
|
|
* @deprecated for lower case 's' version
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.requestFullScreen = function(){
|
|
|
|
vjs.log.warn('player.requestFullScreen() has been deprecated, use player.requestFullscreen() with a lowercase "s")');
|
|
|
|
return this.requestFullscreen();
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* Return the video to its normal size after having been in full screen mode
|
|
|
|
*
|
2014-05-15 14:46:28 -07:00
|
|
|
* myPlayer.exitFullscreen();
|
2013-10-28 18:25:28 -07:00
|
|
|
*
|
|
|
|
* @return {vjs.Player} self
|
|
|
|
*/
|
2014-05-15 14:46:28 -07:00
|
|
|
vjs.Player.prototype.exitFullscreen = function(){
|
|
|
|
var fsApi = vjs.browser.fullscreenAPI;
|
|
|
|
this.isFullscreen(false);
|
2012-01-05 23:25:09 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Check for browser element fullscreen support
|
2014-05-15 14:46:28 -07:00
|
|
|
if (fsApi) {
|
|
|
|
document[fsApi.exitFullscreen]();
|
2013-05-06 10:44:59 -07:00
|
|
|
} else if (this.tech.supportsFullScreen()) {
|
2013-04-19 20:48:09 -04:00
|
|
|
this.techCall('exitFullScreen');
|
2012-12-30 21:45:50 -08:00
|
|
|
} else {
|
|
|
|
this.exitFullWindow();
|
2013-01-10 13:06:12 -08:00
|
|
|
this.trigger('fullscreenchange');
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
};
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2014-05-15 14:46:28 -07:00
|
|
|
/**
|
|
|
|
* Old naming for exitFullscreen
|
|
|
|
* @deprecated for exitFullscreen
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.cancelFullScreen = function(){
|
|
|
|
vjs.log.warn('player.cancelFullScreen() has been deprecated, use player.exitFullscreen()');
|
|
|
|
return this.exitFullscreen();
|
|
|
|
};
|
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.enterFullWindow = function(){
|
2012-12-30 21:45:50 -08:00
|
|
|
this.isFullWindow = true;
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Storing original doc overflow value to return to when fullscreen is off
|
|
|
|
this.docOrigOverflow = document.documentElement.style.overflow;
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Add listener for esc key to exit fullscreen
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Hide any scroll bars
|
|
|
|
document.documentElement.style.overflow = 'hidden';
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Apply fullscreen styles
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.addClass(document.body, 'vjs-full-window');
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
this.trigger('enterFullWindow');
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.fullWindowOnEscKey = function(event){
|
2013-01-10 13:06:12 -08:00
|
|
|
if (event.keyCode === 27) {
|
2014-05-15 14:46:28 -07:00
|
|
|
if (this.isFullscreen() === true) {
|
|
|
|
this.exitFullscreen();
|
2012-12-30 21:45:50 -08:00
|
|
|
} else {
|
|
|
|
this.exitFullWindow();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.exitFullWindow = function(){
|
2012-12-30 21:45:50 -08:00
|
|
|
this.isFullWindow = false;
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.off(document, 'keydown', this.fullWindowOnEscKey);
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Unhide scroll bars.
|
|
|
|
document.documentElement.style.overflow = this.docOrigOverflow;
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Remove fullscreen styles
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.removeClass(document.body, 'vjs-full-window');
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Resize the box, controller, and poster to original sizes
|
|
|
|
// this.positionAll();
|
2013-01-10 13:06:12 -08:00
|
|
|
this.trigger('exitFullWindow');
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.selectSource = function(sources){
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Loop through each playback technology in the options order
|
2013-01-17 21:03:25 -05:00
|
|
|
for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
|
2013-01-04 16:58:23 -08:00
|
|
|
var techName = vjs.capitalize(j[i]),
|
|
|
|
tech = window['videojs'][techName];
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2014-05-19 12:45:00 -07:00
|
|
|
// Check if the current tech is defined before continuing
|
|
|
|
if (!tech) {
|
|
|
|
vjs.log.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Check if the browser supports this technology
|
2012-12-31 15:25:56 -08:00
|
|
|
if (tech.isSupported()) {
|
2012-12-30 21:45:50 -08:00
|
|
|
// Loop through each source object
|
|
|
|
for (var a=0,b=sources;a<b.length;a++) {
|
|
|
|
var source = b[a];
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Check if source can be played with this technology
|
2012-12-31 15:25:56 -08:00
|
|
|
if (tech['canPlaySource'](source)) {
|
2012-12-30 21:45:50 -08:00
|
|
|
return { source: source, tech: techName };
|
2012-02-02 14:56:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2013-10-28 18:25:28 -07:00
|
|
|
/**
|
|
|
|
* The source function updates the video source
|
|
|
|
*
|
|
|
|
* There are three types of variables you can pass as the argument.
|
|
|
|
*
|
|
|
|
* **URL String**: A URL to the the video file. Use this method if you are sure
|
|
|
|
* the current playback technology (HTML5/Flash) can support the source you
|
|
|
|
* provide. Currently only MP4 files can be used in both HTML5 and Flash.
|
|
|
|
*
|
|
|
|
* myPlayer.src("http://www.example.com/path/to/video.mp4");
|
|
|
|
*
|
|
|
|
* **Source Object (or element):** A javascript object containing information
|
|
|
|
* about the source file. Use this method if you want the player to determine if
|
|
|
|
* it can support the file using the type information.
|
|
|
|
*
|
|
|
|
* myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" });
|
|
|
|
*
|
|
|
|
* **Array of Source Objects:** To provide multiple versions of the source so
|
|
|
|
* that it can be played using HTML5 across browsers you can use an array of
|
|
|
|
* source objects. Video.js will detect which version is supported and load that
|
|
|
|
* file.
|
|
|
|
*
|
|
|
|
* myPlayer.src([
|
|
|
|
* { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" },
|
|
|
|
* { type: "video/webm", src: "http://www.example.com/path/to/video.webm" },
|
|
|
|
* { type: "video/ogg", src: "http://www.example.com/path/to/video.ogv" }
|
|
|
|
* ]);
|
|
|
|
*
|
|
|
|
* @param {String|Object|Array=} source The source URL, object, or array of sources
|
2014-02-27 17:34:01 -08:00
|
|
|
* @return {String} The current video source when getting
|
|
|
|
* @return {String} The player when setting
|
2013-10-28 18:25:28 -07:00
|
|
|
*/
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.src = function(source){
|
2014-01-25 22:37:40 -05:00
|
|
|
if (source === undefined) {
|
|
|
|
return this.techGet('src');
|
|
|
|
}
|
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Case: Array of source objects to choose from and pick the best to play
|
2014-06-12 15:24:46 -07:00
|
|
|
if (vjs.obj.isArray(source)) {
|
2012-12-30 21:45:50 -08:00
|
|
|
|
|
|
|
var sourceTech = this.selectSource(source),
|
|
|
|
techName;
|
|
|
|
|
|
|
|
if (sourceTech) {
|
|
|
|
source = sourceTech.source;
|
|
|
|
techName = sourceTech.tech;
|
|
|
|
|
|
|
|
// If this technology is already loaded, set source
|
|
|
|
if (techName == this.techName) {
|
|
|
|
this.src(source); // Passing the source object
|
|
|
|
// Otherwise load this technology with chosen source
|
2012-02-02 14:56:47 -08:00
|
|
|
} else {
|
2012-12-30 21:45:50 -08:00
|
|
|
this.loadTech(techName, source);
|
2011-11-29 11:40:05 -08:00
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
} else {
|
2014-05-12 17:08:48 -07:00
|
|
|
// this.el_.appendChild(vjs.createEl('p', {
|
|
|
|
// innerHTML: this.options()['notSupportedMessage']
|
|
|
|
// }));
|
|
|
|
this.error({ code: 4, message: this.options()['notSupportedMessage'] });
|
2014-03-13 00:45:06 +01:00
|
|
|
this.triggerReady(); // we could not find an appropriate tech, but let's still notify the delegate that this is it
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2011-11-29 11:40:05 -08:00
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
// Case: Source object { src: '', type: '' ... }
|
2012-12-30 21:45:50 -08:00
|
|
|
} else if (source instanceof Object) {
|
2012-02-02 14:56:47 -08:00
|
|
|
|
2013-01-04 16:58:23 -08:00
|
|
|
if (window['videojs'][this.techName]['canPlaySource'](source)) {
|
2012-12-30 21:45:50 -08:00
|
|
|
this.src(source.src);
|
2011-11-29 11:40:05 -08:00
|
|
|
} else {
|
2012-12-30 21:45:50 -08:00
|
|
|
// Send through tech loop to check for a compatible technology.
|
|
|
|
this.src([source]);
|
|
|
|
}
|
2012-01-05 23:25:09 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Case: URL String (http://myvideo...)
|
|
|
|
} else {
|
|
|
|
// Cache for getting last set source
|
|
|
|
this.cache_.src = source;
|
|
|
|
|
|
|
|
if (!this.isReady_) {
|
|
|
|
this.ready(function(){
|
|
|
|
this.src(source);
|
|
|
|
});
|
|
|
|
} else {
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('src', source);
|
2013-01-17 21:03:25 -05:00
|
|
|
if (this.options_['preload'] == 'auto') {
|
2012-12-30 21:45:50 -08:00
|
|
|
this.load();
|
|
|
|
}
|
2013-01-17 21:03:25 -05:00
|
|
|
if (this.options_['autoplay']) {
|
2012-12-30 21:45:50 -08:00
|
|
|
this.play();
|
2011-12-01 15:47:12 -08:00
|
|
|
}
|
2011-11-29 11:40:05 -08:00
|
|
|
}
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2014-02-27 17:34:01 -08:00
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Begin loading the src data
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-load
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.load = function(){
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('load');
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.currentSrc = function(){
|
2013-01-10 13:06:12 -08:00
|
|
|
return this.techGet('currentSrc') || this.cache_.src || '';
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Attributes/Options
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.preload = function(value){
|
2012-12-30 21:45:50 -08:00
|
|
|
if (value !== undefined) {
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('setPreload', value);
|
2013-01-17 21:03:25 -05:00
|
|
|
this.options_['preload'] = value;
|
2011-11-29 11:40:05 -08:00
|
|
|
return this;
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2013-01-10 13:06:12 -08:00
|
|
|
return this.techGet('preload');
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.autoplay = function(value){
|
2012-12-30 21:45:50 -08:00
|
|
|
if (value !== undefined) {
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('setAutoplay', value);
|
2013-01-17 21:03:25 -05:00
|
|
|
this.options_['autoplay'] = value;
|
2011-11-29 11:40:05 -08:00
|
|
|
return this;
|
2012-12-30 21:45:50 -08:00
|
|
|
}
|
2013-01-10 13:06:12 -08:00
|
|
|
return this.techGet('autoplay', value);
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
2013-01-04 16:58:23 -08:00
|
|
|
vjs.Player.prototype.loop = function(value){
|
2012-12-30 21:45:50 -08:00
|
|
|
if (value !== undefined) {
|
2013-01-10 13:06:12 -08:00
|
|
|
this.techCall('setLoop', value);
|
2013-01-17 21:03:25 -05:00
|
|
|
this.options_['loop'] = value;
|
2012-12-30 21:45:50 -08:00
|
|
|
return this;
|
|
|
|
}
|
2013-01-10 13:06:12 -08:00
|
|
|
return this.techGet('loop');
|
2012-12-30 21:45:50 -08:00
|
|
|
};
|
|
|
|
|
2013-01-18 18:06:15 -08:00
|
|
|
/**
|
2013-10-28 18:25:28 -07:00
|
|
|
* the url of the poster image source
|
2013-01-18 18:06:15 -08:00
|
|
|
* @type {String}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.poster_;
|
|
|
|
|
|
|
|
/**
|
2013-10-28 18:25:28 -07:00
|
|
|
* get or set the poster image source url
|
|
|
|
*
|
|
|
|
* ##### EXAMPLE:
|
|
|
|
*
|
|
|
|
* // getting
|
|
|
|
* var currentPoster = myPlayer.poster();
|
|
|
|
*
|
|
|
|
* // setting
|
|
|
|
* myPlayer.poster('http://example.com/myImage.jpg');
|
|
|
|
*
|
|
|
|
* @param {String=} [src] Poster image source URL
|
|
|
|
* @return {String} poster URL when getting
|
|
|
|
* @return {vjs.Player} self when setting
|
2013-01-18 18:06:15 -08:00
|
|
|
*/
|
|
|
|
vjs.Player.prototype.poster = function(src){
|
2013-08-24 19:34:26 -04:00
|
|
|
if (src === undefined) {
|
|
|
|
return this.poster_;
|
2013-01-18 18:06:15 -08:00
|
|
|
}
|
2013-08-24 19:34:26 -04:00
|
|
|
|
|
|
|
// update the internal poster variable
|
|
|
|
this.poster_ = src;
|
|
|
|
|
|
|
|
// update the tech's poster
|
|
|
|
this.techCall('setPoster', src);
|
|
|
|
|
|
|
|
// alert components that the poster has been set
|
|
|
|
this.trigger('posterchange');
|
2013-01-18 18:06:15 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not the controls are showing
|
|
|
|
* @type {Boolean}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.controls_;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get or set whether or not the controls are showing.
|
|
|
|
* @param {Boolean} controls Set controls to showing or not
|
|
|
|
* @return {Boolean} Controls are showing
|
|
|
|
*/
|
2013-08-09 14:29:22 -07:00
|
|
|
vjs.Player.prototype.controls = function(bool){
|
|
|
|
if (bool !== undefined) {
|
|
|
|
bool = !!bool; // force boolean
|
2013-05-02 17:07:05 -07:00
|
|
|
// Don't trigger a change event unless it actually changed
|
2013-08-09 14:29:22 -07:00
|
|
|
if (this.controls_ !== bool) {
|
|
|
|
this.controls_ = bool;
|
|
|
|
if (bool) {
|
|
|
|
this.removeClass('vjs-controls-disabled');
|
|
|
|
this.addClass('vjs-controls-enabled');
|
|
|
|
this.trigger('controlsenabled');
|
|
|
|
} else {
|
|
|
|
this.removeClass('vjs-controls-enabled');
|
|
|
|
this.addClass('vjs-controls-disabled');
|
|
|
|
this.trigger('controlsdisabled');
|
|
|
|
}
|
2013-05-02 17:07:05 -07:00
|
|
|
}
|
2013-08-09 14:29:22 -07:00
|
|
|
return this;
|
2013-01-18 18:06:15 -08:00
|
|
|
}
|
|
|
|
return this.controls_;
|
|
|
|
};
|
|
|
|
|
2013-08-09 14:29:22 -07:00
|
|
|
vjs.Player.prototype.usingNativeControls_;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggle native controls on/off. Native controls are the controls built into
|
|
|
|
* devices (e.g. default iPhone controls), Flash, or other techs
|
|
|
|
* (e.g. Vimeo Controls)
|
|
|
|
*
|
|
|
|
* **This should only be set by the current tech, because only the tech knows
|
|
|
|
* if it can support native controls**
|
|
|
|
*
|
|
|
|
* @param {Boolean} bool True signals that native controls are on
|
|
|
|
* @return {vjs.Player} Returns the player
|
2013-10-28 18:25:28 -07:00
|
|
|
* @private
|
2013-08-09 14:29:22 -07:00
|
|
|
*/
|
|
|
|
vjs.Player.prototype.usingNativeControls = function(bool){
|
|
|
|
if (bool !== undefined) {
|
|
|
|
bool = !!bool; // force boolean
|
|
|
|
// Don't trigger a change event unless it actually changed
|
|
|
|
if (this.usingNativeControls_ !== bool) {
|
|
|
|
this.usingNativeControls_ = bool;
|
|
|
|
if (bool) {
|
|
|
|
this.addClass('vjs-using-native-controls');
|
2013-10-28 18:25:28 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* player is using the native device controls
|
|
|
|
*
|
|
|
|
* @event usingnativecontrols
|
|
|
|
* @memberof vjs.Player
|
|
|
|
* @instance
|
|
|
|
* @private
|
|
|
|
*/
|
2013-08-09 14:29:22 -07:00
|
|
|
this.trigger('usingnativecontrols');
|
|
|
|
} else {
|
|
|
|
this.removeClass('vjs-using-native-controls');
|
2013-10-28 18:25:28 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* player is using the custom HTML controls
|
|
|
|
*
|
|
|
|
* @event usingcustomcontrols
|
|
|
|
* @memberof vjs.Player
|
|
|
|
* @instance
|
|
|
|
* @private
|
|
|
|
*/
|
2013-08-09 14:29:22 -07:00
|
|
|
this.trigger('usingcustomcontrols');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
return this.usingNativeControls_;
|
|
|
|
};
|
|
|
|
|
2014-05-12 17:08:48 -07:00
|
|
|
/**
|
|
|
|
* Store the current media error
|
|
|
|
* @type {Object}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.error_ = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set or get the current MediaError
|
|
|
|
* @param {*} err A MediaError or a String/Number to be turned into a MediaError
|
|
|
|
* @return {vjs.MediaError|null} when getting
|
|
|
|
* @return {vjs.Player} when setting
|
|
|
|
*/
|
|
|
|
vjs.Player.prototype.error = function(err){
|
|
|
|
if (err === undefined) {
|
|
|
|
return this.error_;
|
|
|
|
}
|
|
|
|
|
|
|
|
// restoring to default
|
|
|
|
if (err === null) {
|
|
|
|
this.error_ = err;
|
|
|
|
this.removeClass('vjs-error');
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// error instance
|
|
|
|
if (err instanceof vjs.MediaError) {
|
|
|
|
this.error_ = err;
|
|
|
|
} else {
|
|
|
|
this.error_ = new vjs.MediaError(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// fire an error event on the player
|
|
|
|
this.trigger('error');
|
|
|
|
|
|
|
|
// add the vjs-error classname to the player
|
|
|
|
this.addClass('vjs-error');
|
|
|
|
|
|
|
|
// log the name of the error type and any message
|
|
|
|
// ie8 just logs "[object object]" if you just log the error object
|
|
|
|
vjs.log.error('(CODE:'+this.error_.code+' '+vjs.MediaError.errorTypes[this.error_.code]+')', this.error_.message, this.error_);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
2013-01-10 13:06:12 -08:00
|
|
|
vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
|
2013-08-23 15:05:04 -07:00
|
|
|
vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };
|
2012-12-30 21:45:50 -08:00
|
|
|
|
2013-08-09 14:29:22 -07:00
|
|
|
// When the player is first initialized, trigger activity so components
|
|
|
|
// like the control bar show themselves if needed
|
|
|
|
vjs.Player.prototype.userActivity_ = true;
|
|
|
|
vjs.Player.prototype.reportUserActivity = function(event){
|
|
|
|
this.userActivity_ = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
vjs.Player.prototype.userActive_ = true;
|
|
|
|
vjs.Player.prototype.userActive = function(bool){
|
|
|
|
if (bool !== undefined) {
|
|
|
|
bool = !!bool;
|
|
|
|
if (bool !== this.userActive_) {
|
|
|
|
this.userActive_ = bool;
|
|
|
|
if (bool) {
|
|
|
|
// If the user was inactive and is now active we want to reset the
|
|
|
|
// inactivity timer
|
|
|
|
this.userActivity_ = true;
|
|
|
|
this.removeClass('vjs-user-inactive');
|
|
|
|
this.addClass('vjs-user-active');
|
|
|
|
this.trigger('useractive');
|
|
|
|
} else {
|
|
|
|
// We're switching the state to inactive manually, so erase any other
|
|
|
|
// activity
|
|
|
|
this.userActivity_ = false;
|
|
|
|
|
|
|
|
// Chrome/Safari/IE have bugs where when you change the cursor it can
|
|
|
|
// trigger a mousemove event. This causes an issue when you're hiding
|
|
|
|
// the cursor when the user is inactive, and a mousemove signals user
|
|
|
|
// activity. Making it impossible to go into inactive mode. Specifically
|
|
|
|
// this happens in fullscreen when we really need to hide the cursor.
|
|
|
|
//
|
|
|
|
// When this gets resolved in ALL browsers it can be removed
|
|
|
|
// https://code.google.com/p/chromium/issues/detail?id=103041
|
2014-03-13 00:46:00 +01:00
|
|
|
if(this.tech) {
|
2014-03-11 16:20:12 +01:00
|
|
|
this.tech.one('mousemove', function(e){
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-08-09 14:29:22 -07:00
|
|
|
this.removeClass('vjs-user-active');
|
|
|
|
this.addClass('vjs-user-inactive');
|
|
|
|
this.trigger('userinactive');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
return this.userActive_;
|
|
|
|
};
|
|
|
|
|
|
|
|
vjs.Player.prototype.listenForUserActivity = function(){
|
2014-03-09 13:09:52 -07:00
|
|
|
var onActivity, onMouseMove, onMouseDown, mouseInProgress, onMouseUp,
|
|
|
|
activityCheck, inactivityTimeout, lastMoveX, lastMoveY;
|
2013-08-09 14:29:22 -07:00
|
|
|
|
2014-03-09 13:09:52 -07:00
|
|
|
onActivity = vjs.bind(this, this.reportUserActivity);
|
|
|
|
|
|
|
|
onMouseMove = function(e) {
|
2014-04-25 14:04:57 -07:00
|
|
|
// #1068 - Prevent mousemove spamming
|
|
|
|
// Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
|
2014-03-09 13:09:52 -07:00
|
|
|
if(e.screenX != lastMoveX || e.screenY != lastMoveY) {
|
|
|
|
lastMoveX = e.screenX;
|
|
|
|
lastMoveY = e.screenY;
|
|
|
|
onActivity();
|
|
|
|
}
|
|
|
|
};
|
2013-08-09 14:29:22 -07:00
|
|
|
|
|
|
|
onMouseDown = function() {
|
2014-03-09 13:09:52 -07:00
|
|
|
onActivity();
|
2013-08-09 14:29:22 -07:00
|
|
|
// For as long as the they are touching the device or have their mouse down,
|
|
|
|
// we consider them active even if they're not moving their finger or mouse.
|
|
|
|
// So we want to continue to update that they are active
|
|
|
|
clearInterval(mouseInProgress);
|
|
|
|
// Setting userActivity=true now and setting the interval to the same time
|
|
|
|
// as the activityCheck interval (250) should ensure we never miss the
|
|
|
|
// next activityCheck
|
2014-03-09 13:09:52 -07:00
|
|
|
mouseInProgress = setInterval(onActivity, 250);
|
2013-08-09 14:29:22 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
onMouseUp = function(event) {
|
2014-03-09 13:09:52 -07:00
|
|
|
onActivity();
|
2013-08-09 14:29:22 -07:00
|
|
|
// Stop the interval that maintains activity if the mouse/touch is down
|
|
|
|
clearInterval(mouseInProgress);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Any mouse movement will be considered user activity
|
|
|
|
this.on('mousedown', onMouseDown);
|
2014-03-09 13:09:52 -07:00
|
|
|
this.on('mousemove', onMouseMove);
|
2013-08-09 14:29:22 -07:00
|
|
|
this.on('mouseup', onMouseUp);
|
|
|
|
|
|
|
|
// Listen for keyboard navigation
|
|
|
|
// Shouldn't need to use inProgress interval because of key repeat
|
2014-03-09 13:09:52 -07:00
|
|
|
this.on('keydown', onActivity);
|
|
|
|
this.on('keyup', onActivity);
|
2013-08-09 14:29:22 -07:00
|
|
|
|
|
|
|
// Run an interval every 250 milliseconds instead of stuffing everything into
|
|
|
|
// the mousemove/touchmove function itself, to prevent performance degradation.
|
|
|
|
// `this.reportUserActivity` simply sets this.userActivity_ to true, which
|
|
|
|
// then gets picked up by this loop
|
|
|
|
// http://ejohn.org/blog/learning-from-twitter/
|
|
|
|
activityCheck = setInterval(vjs.bind(this, function() {
|
|
|
|
// Check to see if mouse/touch activity has happened
|
|
|
|
if (this.userActivity_) {
|
|
|
|
// Reset the activity tracker
|
|
|
|
this.userActivity_ = false;
|
|
|
|
|
|
|
|
// If the user state was inactive, set the state to active
|
|
|
|
this.userActive(true);
|
|
|
|
|
|
|
|
// Clear any existing inactivity timeout to start the timer over
|
|
|
|
clearTimeout(inactivityTimeout);
|
|
|
|
|
|
|
|
// In X seconds, if no more activity has occurred the user will be
|
|
|
|
// considered inactive
|
|
|
|
inactivityTimeout = setTimeout(vjs.bind(this, function() {
|
|
|
|
// Protect against the case where the inactivityTimeout can trigger just
|
|
|
|
// before the next user activity is picked up by the activityCheck loop
|
|
|
|
// causing a flicker
|
|
|
|
if (!this.userActivity_) {
|
|
|
|
this.userActive(false);
|
|
|
|
}
|
|
|
|
}), 2000);
|
|
|
|
}
|
|
|
|
}), 250);
|
|
|
|
|
|
|
|
// Clean up the intervals when we kill the player
|
|
|
|
this.on('dispose', function(){
|
|
|
|
clearInterval(activityCheck);
|
|
|
|
clearTimeout(inactivityTimeout);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-05-13 14:02:02 -07:00
|
|
|
vjs.Player.prototype.playbackRate = function(rate) {
|
|
|
|
if (rate !== undefined) {
|
|
|
|
this.techCall('setPlaybackRate', rate);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.tech && this.tech.features && this.tech.features['playbackRate']) {
|
|
|
|
return this.techGet('playbackRate');
|
|
|
|
} else {
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2012-12-30 21:45:50 -08:00
|
|
|
// Methods to add support for
|
2013-01-10 13:06:12 -08:00
|
|
|
// networkState: function(){ return this.techCall('networkState'); },
|
|
|
|
// readyState: function(){ return this.techCall('readyState'); },
|
|
|
|
// initialTime: function(){ return this.techCall('initialTime'); },
|
|
|
|
// startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
|
|
|
|
// played: function(){ return this.techCall('played'); },
|
|
|
|
// seekable: function(){ return this.techCall('seekable'); },
|
|
|
|
// videoTracks: function(){ return this.techCall('videoTracks'); },
|
|
|
|
// audioTracks: function(){ return this.techCall('audioTracks'); },
|
|
|
|
// videoWidth: function(){ return this.techCall('videoWidth'); },
|
|
|
|
// videoHeight: function(){ return this.techCall('videoHeight'); },
|
|
|
|
// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
|
|
|
|
// mediaGroup: function(){ return this.techCall('mediaGroup'); },
|
|
|
|
// controller: function(){ return this.techCall('controller'); },
|
|
|
|
// defaultMuted: function(){ return this.techCall('defaultMuted'); }
|
2012-12-30 21:45:50 -08:00
|
|
|
|
|
|
|
// TODO
|
|
|
|
// currentSrcList: the array of sources including other formats and bitrates
|
|
|
|
// playList: array of source lists in order of playback
|