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