mirror of
https://github.com/videojs/video.js.git
synced 2025-04-11 11:42:08 +02:00
Some error events don't cause the error property on the video element to be set. In that case, handle the event like any other. Fixes #1465.
384 lines
12 KiB
JavaScript
384 lines
12 KiB
JavaScript
/**
|
|
* @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
|
|
*/
|
|
|
|
/**
|
|
* HTML5 Media Controller - Wrapper for HTML5 Media API
|
|
* @param {vjs.Player|Object} player
|
|
* @param {Object=} options
|
|
* @param {Function=} ready
|
|
* @constructor
|
|
*/
|
|
vjs.Html5 = vjs.MediaTechController.extend({
|
|
/** @constructor */
|
|
init: function(player, options, ready){
|
|
// volume cannot be changed from 1 on iOS
|
|
this['featuresVolumeControl'] = vjs.Html5.canControlVolume();
|
|
|
|
// just in case; or is it excessively...
|
|
this['featuresPlaybackRate'] = vjs.Html5.canControlPlaybackRate();
|
|
|
|
// In iOS, if you move a video element in the DOM, it breaks video playback.
|
|
this['movingMediaElementInDOM'] = !vjs.IS_IOS;
|
|
|
|
// HTML video is able to automatically resize when going to fullscreen
|
|
this['featuresFullscreenResize'] = true;
|
|
|
|
// HTML video supports progress events
|
|
this['featuresProgressEvents'] = true;
|
|
|
|
vjs.MediaTechController.call(this, player, options, ready);
|
|
this.setupTriggers();
|
|
|
|
var source = options['source'];
|
|
|
|
// set the source if one was provided
|
|
if (source && this.el_.currentSrc !== source.src) {
|
|
this.el_.src = source.src;
|
|
}
|
|
|
|
// Determine if native controls should be used
|
|
// Our goal should be to get the custom controls on mobile solid everywhere
|
|
// so we can remove this all together. Right now this will block custom
|
|
// controls on touch enabled laptops like the Chrome Pixel
|
|
if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] !== false) {
|
|
this.useNativeControls();
|
|
}
|
|
|
|
// 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.tag && this.options_['autoplay'] && this.paused()) {
|
|
delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.
|
|
this.play();
|
|
}
|
|
});
|
|
|
|
this.triggerReady();
|
|
}
|
|
});
|
|
|
|
vjs.Html5.prototype.dispose = function(){
|
|
vjs.Html5.disposeMediaElement(this.el_);
|
|
vjs.MediaTechController.prototype.dispose.call(this);
|
|
};
|
|
|
|
vjs.Html5.prototype.createEl = function(){
|
|
var player = this.player_,
|
|
// If possible, reuse original tag for HTML5 playback technology element
|
|
el = player.tag,
|
|
newEl,
|
|
clone;
|
|
|
|
// Check if this browser supports moving the element into the box.
|
|
// On the iPhone video will break if you move the element,
|
|
// So we have to create a brand new element.
|
|
if (!el || this['movingMediaElementInDOM'] === false) {
|
|
|
|
// If the original tag is still there, clone and remove it.
|
|
if (el) {
|
|
clone = el.cloneNode(false);
|
|
vjs.Html5.disposeMediaElement(el);
|
|
el = clone;
|
|
player.tag = null;
|
|
} else {
|
|
el = vjs.createEl('video');
|
|
vjs.setElementAttributes(el,
|
|
vjs.obj.merge(player.tagAttributes || {}, {
|
|
id:player.id() + '_html5_api',
|
|
'class':'vjs-tech'
|
|
})
|
|
);
|
|
}
|
|
// associate the player with the new tag
|
|
el['player'] = player;
|
|
|
|
vjs.insertFirst(el, player.el());
|
|
}
|
|
|
|
// Update specific tag settings, in case they were overridden
|
|
var settingsAttrs = ['autoplay','preload','loop','muted'];
|
|
for (var i = settingsAttrs.length - 1; i >= 0; i--) {
|
|
var attr = settingsAttrs[i];
|
|
var overwriteAttrs = {};
|
|
if (typeof player.options_[attr] !== 'undefined') {
|
|
overwriteAttrs[attr] = player.options_[attr];
|
|
}
|
|
vjs.setElementAttributes(el, overwriteAttrs);
|
|
}
|
|
|
|
return el;
|
|
// jenniisawesome = true;
|
|
};
|
|
|
|
// Make video events trigger player events
|
|
// May seem verbose here, but makes other APIs possible.
|
|
// Triggers removed using this.off when disposed
|
|
vjs.Html5.prototype.setupTriggers = function(){
|
|
for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
|
|
vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this, this.eventHandler));
|
|
}
|
|
};
|
|
|
|
vjs.Html5.prototype.eventHandler = function(evt){
|
|
// In the case of an error on the video element, set the error prop
|
|
// on the player and let the player handle triggering the event. On
|
|
// some platforms, error events fire that do not cause the error
|
|
// property on the video element to be set. See #1465 for an example.
|
|
if (evt.type == 'error' && this.error()) {
|
|
this.player().error(this.error().code);
|
|
|
|
// in some cases we pass the event directly to the player
|
|
} else {
|
|
// No need for media events to bubble up.
|
|
evt.bubbles = false;
|
|
|
|
this.player().trigger(evt);
|
|
}
|
|
};
|
|
|
|
vjs.Html5.prototype.useNativeControls = function(){
|
|
var tech, player, controlsOn, controlsOff, cleanUp;
|
|
|
|
tech = this;
|
|
player = this.player();
|
|
|
|
// If the player controls are enabled turn on the native controls
|
|
tech.setControls(player.controls());
|
|
|
|
// Update the native controls when player controls state is updated
|
|
controlsOn = function(){
|
|
tech.setControls(true);
|
|
};
|
|
controlsOff = function(){
|
|
tech.setControls(false);
|
|
};
|
|
player.on('controlsenabled', controlsOn);
|
|
player.on('controlsdisabled', controlsOff);
|
|
|
|
// Clean up when not using native controls anymore
|
|
cleanUp = function(){
|
|
player.off('controlsenabled', controlsOn);
|
|
player.off('controlsdisabled', controlsOff);
|
|
};
|
|
tech.on('dispose', cleanUp);
|
|
player.on('usingcustomcontrols', cleanUp);
|
|
|
|
// Update the state of the player to using native controls
|
|
player.usingNativeControls(true);
|
|
};
|
|
|
|
|
|
vjs.Html5.prototype.play = function(){ this.el_.play(); };
|
|
vjs.Html5.prototype.pause = function(){ this.el_.pause(); };
|
|
vjs.Html5.prototype.paused = function(){ return this.el_.paused; };
|
|
|
|
vjs.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };
|
|
vjs.Html5.prototype.setCurrentTime = function(seconds){
|
|
try {
|
|
this.el_.currentTime = seconds;
|
|
} catch(e) {
|
|
vjs.log(e, 'Video is not ready. (Video.js)');
|
|
// this.warning(VideoJS.warnings.videoNotReady);
|
|
}
|
|
};
|
|
|
|
vjs.Html5.prototype.duration = function(){ return this.el_.duration || 0; };
|
|
vjs.Html5.prototype.buffered = function(){ return this.el_.buffered; };
|
|
|
|
vjs.Html5.prototype.volume = function(){ return this.el_.volume; };
|
|
vjs.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };
|
|
vjs.Html5.prototype.muted = function(){ return this.el_.muted; };
|
|
vjs.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };
|
|
|
|
vjs.Html5.prototype.width = function(){ return this.el_.offsetWidth; };
|
|
vjs.Html5.prototype.height = function(){ return this.el_.offsetHeight; };
|
|
|
|
vjs.Html5.prototype.supportsFullScreen = function(){
|
|
if (typeof this.el_.webkitEnterFullScreen == 'function') {
|
|
|
|
// Seems to be broken in Chromium/Chrome && Safari in Leopard
|
|
if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
vjs.Html5.prototype.enterFullScreen = function(){
|
|
var video = this.el_;
|
|
if (video.paused && video.networkState <= video.HAVE_METADATA) {
|
|
// attempt to prime the video element for programmatic access
|
|
// this isn't necessary on the desktop but shouldn't hurt
|
|
this.el_.play();
|
|
|
|
// playing and pausing synchronously during the transition to fullscreen
|
|
// can get iOS ~6.1 devices into a play/pause loop
|
|
setTimeout(function(){
|
|
video.pause();
|
|
video.webkitEnterFullScreen();
|
|
}, 0);
|
|
} else {
|
|
video.webkitEnterFullScreen();
|
|
}
|
|
};
|
|
vjs.Html5.prototype.exitFullScreen = function(){
|
|
this.el_.webkitExitFullScreen();
|
|
};
|
|
vjs.Html5.prototype.src = function(src) {
|
|
if (src === undefined) {
|
|
return this.el_.src;
|
|
} else {
|
|
this.el_.src = src;
|
|
}
|
|
};
|
|
vjs.Html5.prototype.load = function(){ this.el_.load(); };
|
|
vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
|
|
|
|
vjs.Html5.prototype.poster = function(){ return this.el_.poster; };
|
|
vjs.Html5.prototype.setPoster = function(val){ this.el_.poster = val; };
|
|
|
|
vjs.Html5.prototype.preload = function(){ return this.el_.preload; };
|
|
vjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };
|
|
|
|
vjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };
|
|
vjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };
|
|
|
|
vjs.Html5.prototype.controls = function(){ return this.el_.controls; };
|
|
vjs.Html5.prototype.setControls = function(val){ this.el_.controls = !!val; };
|
|
|
|
vjs.Html5.prototype.loop = function(){ return this.el_.loop; };
|
|
vjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };
|
|
|
|
vjs.Html5.prototype.error = function(){ return this.el_.error; };
|
|
vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
|
|
vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
|
|
vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
|
|
|
|
vjs.Html5.prototype.playbackRate = function(){ return this.el_.playbackRate; };
|
|
vjs.Html5.prototype.setPlaybackRate = function(val){ this.el_.playbackRate = val; };
|
|
|
|
vjs.Html5.prototype.networkState = function(){ return this.el_.networkState; };
|
|
|
|
/* HTML5 Support Testing ---------------------------------------------------- */
|
|
|
|
vjs.Html5.isSupported = function(){
|
|
// ie9 with no Media Player is a LIAR! (#984)
|
|
try {
|
|
vjs.TEST_VID['volume'] = 0.5;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
|
|
return !!vjs.TEST_VID.canPlayType;
|
|
};
|
|
|
|
vjs.Html5.canPlaySource = function(srcObj){
|
|
// IE9 on Windows 7 without MediaPlayer throws an error here
|
|
// https://github.com/videojs/video.js/issues/519
|
|
try {
|
|
return !!vjs.TEST_VID.canPlayType(srcObj.type);
|
|
} catch(e) {
|
|
return '';
|
|
}
|
|
// TODO: Check Type
|
|
// If no Type, check ext
|
|
// Check Media Type
|
|
};
|
|
|
|
vjs.Html5.canControlVolume = function(){
|
|
var volume = vjs.TEST_VID.volume;
|
|
vjs.TEST_VID.volume = (volume / 2) + 0.1;
|
|
return volume !== vjs.TEST_VID.volume;
|
|
};
|
|
|
|
vjs.Html5.canControlPlaybackRate = function(){
|
|
var playbackRate = vjs.TEST_VID.playbackRate;
|
|
vjs.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
|
|
return playbackRate !== vjs.TEST_VID.playbackRate;
|
|
};
|
|
|
|
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
|
(function() {
|
|
var canPlayType,
|
|
mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i,
|
|
mp4RE = /^video\/mp4/i;
|
|
|
|
vjs.Html5.patchCanPlayType = function() {
|
|
// Android 4.0 and above can play HLS to some extent but it reports being unable to do so
|
|
if (vjs.ANDROID_VERSION >= 4.0) {
|
|
if (!canPlayType) {
|
|
canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType;
|
|
}
|
|
|
|
vjs.TEST_VID.constructor.prototype.canPlayType = function(type) {
|
|
if (type && mpegurlRE.test(type)) {
|
|
return 'maybe';
|
|
}
|
|
return canPlayType.call(this, type);
|
|
};
|
|
}
|
|
|
|
// Override Android 2.2 and less canPlayType method which is broken
|
|
if (vjs.IS_OLD_ANDROID) {
|
|
if (!canPlayType) {
|
|
canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType;
|
|
}
|
|
|
|
vjs.TEST_VID.constructor.prototype.canPlayType = function(type){
|
|
if (type && mp4RE.test(type)) {
|
|
return 'maybe';
|
|
}
|
|
return canPlayType.call(this, type);
|
|
};
|
|
}
|
|
};
|
|
|
|
vjs.Html5.unpatchCanPlayType = function() {
|
|
var r = vjs.TEST_VID.constructor.prototype.canPlayType;
|
|
vjs.TEST_VID.constructor.prototype.canPlayType = canPlayType;
|
|
canPlayType = null;
|
|
return r;
|
|
};
|
|
|
|
// by default, patch the video element
|
|
vjs.Html5.patchCanPlayType();
|
|
})();
|
|
|
|
// List of all HTML5 events (various uses).
|
|
vjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');
|
|
|
|
vjs.Html5.disposeMediaElement = function(el){
|
|
if (!el) { return; }
|
|
|
|
el['player'] = null;
|
|
|
|
if (el.parentNode) {
|
|
el.parentNode.removeChild(el);
|
|
}
|
|
|
|
// remove any child track or source nodes to prevent their loading
|
|
while(el.hasChildNodes()) {
|
|
el.removeChild(el.firstChild);
|
|
}
|
|
|
|
// remove any src reference. not setting `src=''` because that causes a warning
|
|
// in firefox
|
|
el.removeAttribute('src');
|
|
|
|
// force the media element to update its loading state by calling load()
|
|
// however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
|
|
if (typeof el.load === 'function') {
|
|
// wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
|
|
(function() {
|
|
try {
|
|
el.load();
|
|
} catch (e) {
|
|
// not supported
|
|
}
|
|
})();
|
|
}
|
|
};
|