1
0
mirror of https://github.com/videojs/video.js.git synced 2025-04-11 11:42:08 +02:00
video.js/src/js/media/html5.js
David LaPalomento b9181ee55b Make sure the error property is set before proxying through the tech.
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.
2014-09-05 13:36:50 -07:00

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
}
})();
}
};