mirror of
https://github.com/videojs/video.js.git
synced 2025-04-13 11:50:49 +02:00
696 lines
22 KiB
JavaScript
696 lines
22 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){
|
|
var nodes, nodesLength, i, node, nodeName, removeNodes;
|
|
|
|
if (options['nativeCaptions'] === false || options['nativeTextTracks'] === false) {
|
|
this['featuresNativeTextTracks'] = false;
|
|
}
|
|
|
|
vjs.MediaTechController.call(this, player, options, ready);
|
|
|
|
this.setupTriggers();
|
|
|
|
var source = options['source'];
|
|
|
|
// Set the source if one is provided
|
|
// 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
|
|
// 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
|
|
// anyway so the error gets fired.
|
|
if (source && (this.el_.currentSrc !== source.src || (player.tag && player.tag.initNetworkState_ === 3))) {
|
|
this.setSource(source);
|
|
}
|
|
|
|
if (this.el_.hasChildNodes()) {
|
|
|
|
nodes = this.el_.childNodes;
|
|
nodesLength = nodes.length;
|
|
removeNodes = [];
|
|
|
|
while (nodesLength--) {
|
|
node = nodes[nodesLength];
|
|
nodeName = node.nodeName.toLowerCase();
|
|
if (nodeName === 'track') {
|
|
if (!this['featuresNativeTextTracks']) {
|
|
// Empty video tag tracks so the built-in player doesn't use them also.
|
|
// 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
|
|
removeNodes.push(node);
|
|
} else {
|
|
this.remoteTextTracks().addTrack_(node['track']);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=0; i<removeNodes.length; i++) {
|
|
this.el_.removeChild(removeNodes[i]);
|
|
}
|
|
}
|
|
|
|
// 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'] === true) {
|
|
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.src() && 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_,
|
|
track,
|
|
trackEl,
|
|
i,
|
|
// If possible, reuse original tag for HTML5 playback technology element
|
|
el = player.tag,
|
|
attributes,
|
|
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');
|
|
|
|
// determine if native controls should be used
|
|
attributes = videojs.util.mergeOptions({}, player.tagAttributes);
|
|
if (!vjs.TOUCH_ENABLED || player.options()['nativeControlsForTouch'] !== true) {
|
|
delete attributes.controls;
|
|
}
|
|
|
|
vjs.setElementAttributes(el,
|
|
vjs.obj.merge(attributes, {
|
|
id:player.id() + '_html5_api',
|
|
'class':'vjs-tech'
|
|
})
|
|
);
|
|
}
|
|
// associate the player with the new tag
|
|
el['player'] = player;
|
|
|
|
if (player.options_.tracks) {
|
|
for (i = 0; i < player.options_.tracks.length; i++) {
|
|
track = player.options_.tracks[i];
|
|
trackEl = document.createElement('track');
|
|
trackEl.kind = track.kind;
|
|
trackEl.label = track.label;
|
|
trackEl.srclang = track.srclang;
|
|
trackEl.src = track.src;
|
|
if ('default' in track) {
|
|
trackEl.setAttribute('default', 'default');
|
|
}
|
|
el.appendChild(trackEl);
|
|
}
|
|
}
|
|
|
|
vjs.insertFirst(el, player.el());
|
|
}
|
|
|
|
// Update specific tag settings, in case they were overridden
|
|
var settingsAttrs = ['autoplay','preload','loop','muted'];
|
|
for (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--) {
|
|
this.on(vjs.Html5.Events[i], 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 ('webkitDisplayingFullscreen' in video) {
|
|
this.one('webkitbeginfullscreen', function() {
|
|
this.player_.isFullscreen(true);
|
|
|
|
this.one('webkitendfullscreen', function() {
|
|
this.player_.isFullscreen(false);
|
|
this.player_.trigger('fullscreenchange');
|
|
});
|
|
|
|
this.player_.trigger('fullscreenchange');
|
|
});
|
|
}
|
|
|
|
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
|
|
this.setTimeout(function(){
|
|
video.pause();
|
|
video.webkitEnterFullScreen();
|
|
}, 0);
|
|
} else {
|
|
video.webkitEnterFullScreen();
|
|
}
|
|
};
|
|
|
|
vjs.Html5.prototype.exitFullScreen = function(){
|
|
this.el_.webkitExitFullScreen();
|
|
};
|
|
|
|
// Checks to see if the element's reported URI (either from `el_.src`
|
|
// or `el_.currentSrc`) is a blob-uri and, if so, returns the uri that
|
|
// was passed into the source-handler when it was first invoked instead
|
|
// of the blob-uri
|
|
vjs.Html5.prototype.returnOriginalIfBlobURI_ = function (elementURI, originalURI) {
|
|
var blobURIRegExp = /^blob\:/i;
|
|
|
|
// If originalURI is undefined then we are probably in a non-source-handler-enabled
|
|
// tech that inherits from the Html5 tech so we should just return the elementURI
|
|
// regardless of it's blobby-ness
|
|
if (originalURI && elementURI && blobURIRegExp.test(elementURI)) {
|
|
return originalURI;
|
|
}
|
|
return elementURI;
|
|
};
|
|
|
|
vjs.Html5.prototype.src = function(src) {
|
|
var elementSrc = this.el_.src;
|
|
|
|
if (src === undefined) {
|
|
return this.returnOriginalIfBlobURI_(elementSrc, this.source_);
|
|
} else {
|
|
// Setting src through `src` instead of `setSrc` will be deprecated
|
|
this.setSrc(src);
|
|
}
|
|
};
|
|
|
|
vjs.Html5.prototype.setSrc = function(src) {
|
|
this.el_.src = src;
|
|
};
|
|
|
|
vjs.Html5.prototype.load = function(){ this.el_.load(); };
|
|
vjs.Html5.prototype.currentSrc = function(){
|
|
var elementSrc = this.el_.currentSrc;
|
|
|
|
if (!this.currentSource_) {
|
|
return elementSrc;
|
|
}
|
|
|
|
return this.returnOriginalIfBlobURI_(elementSrc, this.currentSource_.src);
|
|
};
|
|
|
|
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.seekable = function(){ return this.el_.seekable; };
|
|
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; };
|
|
vjs.Html5.prototype.readyState = function(){ return this.el_.readyState; };
|
|
|
|
vjs.Html5.prototype.textTracks = function() {
|
|
if (!this['featuresNativeTextTracks']) {
|
|
return vjs.MediaTechController.prototype.textTracks.call(this);
|
|
}
|
|
|
|
return this.el_.textTracks;
|
|
};
|
|
vjs.Html5.prototype.addTextTrack = function(kind, label, language) {
|
|
if (!this['featuresNativeTextTracks']) {
|
|
return vjs.MediaTechController.prototype.addTextTrack.call(this, kind, label, language);
|
|
}
|
|
|
|
return this.el_.addTextTrack(kind, label, language);
|
|
};
|
|
|
|
vjs.Html5.prototype.addRemoteTextTrack = function(options) {
|
|
if (!this['featuresNativeTextTracks']) {
|
|
return vjs.MediaTechController.prototype.addRemoteTextTrack.call(this, options);
|
|
}
|
|
|
|
var track = document.createElement('track');
|
|
options = options || {};
|
|
|
|
if (options['kind']) {
|
|
track['kind'] = options['kind'];
|
|
}
|
|
if (options['label']) {
|
|
track['label'] = options['label'];
|
|
}
|
|
if (options['language'] || options['srclang']) {
|
|
track['srclang'] = options['language'] || options['srclang'];
|
|
}
|
|
if (options['default']) {
|
|
track['default'] = options['default'];
|
|
}
|
|
if (options['id']) {
|
|
track['id'] = options['id'];
|
|
}
|
|
if (options['src']) {
|
|
track['src'] = options['src'];
|
|
}
|
|
|
|
this.el().appendChild(track);
|
|
this.remoteTextTracks().addTrack_(track.track);
|
|
|
|
return track;
|
|
};
|
|
|
|
vjs.Html5.prototype.removeRemoteTextTrack = function(track) {
|
|
if (!this['featuresNativeTextTracks']) {
|
|
return vjs.MediaTechController.prototype.removeRemoteTextTrack.call(this, track);
|
|
}
|
|
|
|
var tracks, i;
|
|
|
|
this.remoteTextTracks().removeTrack_(track);
|
|
|
|
tracks = this.el()['querySelectorAll']('track');
|
|
|
|
for (i = 0; i < tracks.length; i++) {
|
|
if (tracks[i] === track || tracks[i]['track'] === track) {
|
|
tracks[i]['parentNode']['removeChild'](tracks[i]);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
/* HTML5 Support Testing ---------------------------------------------------- */
|
|
|
|
/**
|
|
* Check if HTML5 video is supported by this browser/device
|
|
* @return {Boolean}
|
|
*/
|
|
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;
|
|
};
|
|
|
|
// Add Source Handler pattern functions to this tech
|
|
vjs.MediaTechController.withSourceHandlers(vjs.Html5);
|
|
|
|
/*
|
|
* Override the withSourceHandler mixin's methods with our own because
|
|
* the HTML5 Media Element returns blob urls when utilizing MSE and we
|
|
* want to still return proper source urls even when in that case
|
|
*/
|
|
(function(){
|
|
var
|
|
origSetSource = vjs.Html5.prototype.setSource,
|
|
origDisposeSourceHandler = vjs.Html5.prototype.disposeSourceHandler;
|
|
|
|
vjs.Html5.prototype.setSource = function (source) {
|
|
var retVal = origSetSource.call(this, source);
|
|
this.source_ = source.src;
|
|
return retVal;
|
|
};
|
|
|
|
vjs.Html5.prototype.disposeSourceHandler = function () {
|
|
this.source_ = undefined;
|
|
return origDisposeSourceHandler.call(this);
|
|
};
|
|
})();
|
|
|
|
/**
|
|
* The default native source handler.
|
|
* This simply passes the source to the video element. Nothing fancy.
|
|
* @param {Object} source The source object
|
|
* @param {vjs.Html5} tech The instance of the HTML5 tech
|
|
*/
|
|
vjs.Html5['nativeSourceHandler'] = {};
|
|
|
|
/**
|
|
* Check if the video element can handle the source natively
|
|
* @param {Object} source The source object
|
|
* @return {String} 'probably', 'maybe', or '' (empty string)
|
|
*/
|
|
vjs.Html5['nativeSourceHandler']['canHandleSource'] = function(source){
|
|
var match, ext;
|
|
|
|
function canPlayType(type){
|
|
// IE9 on Windows 7 without MediaPlayer throws an error here
|
|
// https://github.com/videojs/video.js/issues/519
|
|
try {
|
|
return vjs.TEST_VID.canPlayType(type);
|
|
} catch(e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// If a type was provided we should rely on that
|
|
if (source.type) {
|
|
return canPlayType(source.type);
|
|
} else if (source.src) {
|
|
// If no type, fall back to checking 'video/[EXTENSION]'
|
|
match = source.src.match(/\.([^.\/\?]+)(\?[^\/]+)?$/i);
|
|
ext = match && match[1];
|
|
|
|
return canPlayType('video/'+ext);
|
|
}
|
|
|
|
return '';
|
|
};
|
|
|
|
/**
|
|
* Pass the source to the video element
|
|
* Adaptive source handlers will have more complicated workflows before passing
|
|
* video data to the video element
|
|
* @param {Object} source The source object
|
|
* @param {vjs.Html5} tech The instance of the Html5 tech
|
|
*/
|
|
vjs.Html5['nativeSourceHandler']['handleSource'] = function(source, tech){
|
|
tech.setSrc(source.src);
|
|
};
|
|
|
|
/**
|
|
* Clean up the source handler when disposing the player or switching sources..
|
|
* (no cleanup is needed when supporting the format natively)
|
|
*/
|
|
vjs.Html5['nativeSourceHandler']['dispose'] = function(){};
|
|
|
|
// Register the native source handler
|
|
vjs.Html5['registerSourceHandler'](vjs.Html5['nativeSourceHandler']);
|
|
|
|
/**
|
|
* Check if the volume can be changed in this browser/device.
|
|
* Volume cannot be changed in a lot of mobile devices.
|
|
* Specifically, it can't be changed from 1 on iOS.
|
|
* @return {Boolean}
|
|
*/
|
|
vjs.Html5.canControlVolume = function(){
|
|
var volume = vjs.TEST_VID.volume;
|
|
vjs.TEST_VID.volume = (volume / 2) + 0.1;
|
|
return volume !== vjs.TEST_VID.volume;
|
|
};
|
|
|
|
/**
|
|
* Check if playbackRate is supported in this browser/device.
|
|
* @return {[type]} [description]
|
|
*/
|
|
vjs.Html5.canControlPlaybackRate = function(){
|
|
var playbackRate = vjs.TEST_VID.playbackRate;
|
|
vjs.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
|
|
return playbackRate !== vjs.TEST_VID.playbackRate;
|
|
};
|
|
|
|
/**
|
|
* Check to see if native text tracks are supported by this browser/device
|
|
* @return {Boolean}
|
|
*/
|
|
vjs.Html5.supportsNativeTextTracks = function() {
|
|
var supportsTextTracks;
|
|
|
|
// Figure out native text track support
|
|
// If mode is a number, we cannot change it because it'll disappear from view.
|
|
// Browsers with numeric modes include IE10 and older (<=2013) samsung android models.
|
|
// Firefox isn't playing nice either with modifying the mode
|
|
// TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862
|
|
supportsTextTracks = !!vjs.TEST_VID.textTracks;
|
|
if (supportsTextTracks && vjs.TEST_VID.textTracks.length > 0) {
|
|
supportsTextTracks = typeof vjs.TEST_VID.textTracks[0]['mode'] !== 'number';
|
|
}
|
|
if (supportsTextTracks && vjs.IS_FIREFOX) {
|
|
supportsTextTracks = false;
|
|
}
|
|
|
|
return supportsTextTracks;
|
|
};
|
|
|
|
/**
|
|
* Set the tech's volume control support status
|
|
* @type {Boolean}
|
|
*/
|
|
vjs.Html5.prototype['featuresVolumeControl'] = vjs.Html5.canControlVolume();
|
|
|
|
/**
|
|
* Set the tech's playbackRate support status
|
|
* @type {Boolean}
|
|
*/
|
|
vjs.Html5.prototype['featuresPlaybackRate'] = vjs.Html5.canControlPlaybackRate();
|
|
|
|
/**
|
|
* Set the tech's status on moving the video element.
|
|
* In iOS, if you move a video element in the DOM, it breaks video playback.
|
|
* @type {Boolean}
|
|
*/
|
|
vjs.Html5.prototype['movingMediaElementInDOM'] = !vjs.IS_IOS;
|
|
|
|
/**
|
|
* Set the the tech's fullscreen resize support status.
|
|
* HTML video is able to automatically resize when going to fullscreen.
|
|
* (No longer appears to be used. Can probably be removed.)
|
|
*/
|
|
vjs.Html5.prototype['featuresFullscreenResize'] = true;
|
|
|
|
/**
|
|
* Set the tech's progress event support status
|
|
* (this disables the manual progress events of the MediaTechController)
|
|
*/
|
|
vjs.Html5.prototype['featuresProgressEvents'] = true;
|
|
|
|
/**
|
|
* Sets the tech's status on native text track support
|
|
* @type {Boolean}
|
|
*/
|
|
vjs.Html5.prototype['featuresNativeTextTracks'] = vjs.Html5.supportsNativeTextTracks();
|
|
|
|
// 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
|
|
}
|
|
})();
|
|
}
|
|
};
|