diff --git a/package-lock.json b/package-lock.json index 30320521e1..12ca0cfc41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -626,6 +626,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-object-assign": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.2.0.tgz", + "integrity": "sha512-nmE55cZBPFgUktbF2OuoZgPRadfxosLOpSgzEPYotKSls9J4pEPcembi8r78RU37Rph6UApCpNmsQA4QMWK9Ng==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-transform-object-super": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", @@ -5168,8 +5177,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5190,14 +5198,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5212,20 +5218,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5342,8 +5345,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5355,7 +5357,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5370,7 +5371,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5378,14 +5378,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5404,7 +5402,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5485,8 +5482,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5498,7 +5494,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5584,8 +5579,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5621,7 +5615,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5641,7 +5634,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5685,14 +5677,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/package.json b/package.json index 9a2ee7bf64..bf065d3432 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "@babel/cli": "^7.4.4", "@babel/core": "^7.4.5", "@babel/node": "^7.4.5", + "@babel/plugin-transform-object-assign": "^7.2.0", "@babel/plugin-transform-runtime": "^7.4.4", "@babel/preset-env": "^7.4.5", "@babel/register": "^7.4.4", diff --git a/rollup.config.js b/rollup.config.js index 34e6072794..0309490cfc 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -45,6 +45,9 @@ const primedBabel = babel({ loose: true, modules: false }] + ], + plugins: [ + '@babel/plugin-transform-object-assign' ] }); diff --git a/src/js/error-display.js b/src/js/error-display.js index 5393815bf0..adccdfacf7 100644 --- a/src/js/error-display.js +++ b/src/js/error-display.js @@ -3,7 +3,6 @@ */ import Component from './component'; import ModalDialog from './modal-dialog'; -import mergeOptions from './utils/merge-options'; /** * A display that indicates an error has occurred. This means that the video @@ -57,7 +56,7 @@ class ErrorDisplay extends ModalDialog { * * @private */ -ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, { +ErrorDisplay.prototype.options_ = Object.assign({}, ModalDialog.prototype.options_, { pauseOnOpen: false, fillAlways: true, temporary: false, diff --git a/src/js/tech/html5.js b/src/js/tech/html5.js index 3c1075bdae..2b24afea36 100644 --- a/src/js/tech/html5.js +++ b/src/js/tech/html5.js @@ -13,6 +13,7 @@ import mergeOptions from '../utils/merge-options.js'; import {toTitleCase} from '../utils/string-cases.js'; import {NORMAL as TRACK_TYPES} from '../tracks/track-types'; import setupSourceset from './setup-sourceset'; +import defineLazyProperty from '../utils/define-lazy-property.js'; /** * HTML5 Media Controller - Wrapper for HTML5 Media API @@ -887,23 +888,27 @@ class Html5 extends Tech { /* HTML5 Support Testing ---------------------------------------------------- */ -if (Dom.isReal()) { - - /** - * Element for testing browser HTML5 media capabilities - * - * @type {Element} - * @constant - * @private - */ - Html5.TEST_VID = document.createElement('video'); +/** + * Element for testing browser HTML5 media capabilities + * + * @type {Element} + * @constant + * @private + */ +defineLazyProperty(Html5, 'TEST_VID', function() { + if (!Dom.isReal()) { + return; + } + const video = document.createElement('video'); const track = document.createElement('track'); track.kind = 'captions'; track.srclang = 'en'; track.label = 'English'; - Html5.TEST_VID.appendChild(track); -} + video.appendChild(track); + + return video; +}); /** * Check if HTML5 media is supported by this browser/device. @@ -1115,15 +1120,12 @@ Html5.Events = [ * @type {boolean} * @default {@link Html5.canControlVolume} */ -Html5.prototype.featuresVolumeControl = Html5.canControlVolume(); - /** * Boolean indicating whether the `Tech` supports muting volume. * * @type {bolean} * @default {@link Html5.canMuteVolume} */ -Html5.prototype.featuresMuteControl = Html5.canMuteVolume(); /** * Boolean indicating whether the `Tech` supports changing the speed at which the media @@ -1134,7 +1136,6 @@ Html5.prototype.featuresMuteControl = Html5.canMuteVolume(); * @type {boolean} * @default {@link Html5.canControlPlaybackRate} */ -Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); /** * Boolean indicating whether the `Tech` supports the `sourceset` event. @@ -1142,7 +1143,35 @@ Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); * @type {boolean} * @default */ -Html5.prototype.featuresSourceset = Html5.canOverrideAttributes(); +/** + * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. + * + * @type {boolean} + * @default {@link Html5.supportsNativeTextTracks} + */ +/** + * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. + * + * @type {boolean} + * @default {@link Html5.supportsNativeVideoTracks} + */ +/** + * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. + * + * @type {boolean} + * @default {@link Html5.supportsNativeAudioTracks} + */ +[ + ['featuresVolumeControl', 'canControlVolume'], + ['featuresMuteControl', 'canMuteVolume'], + ['featuresPlaybackRate', 'canControlPlaybackRate'], + ['featuresSourceset', 'canOverrideAttributes'], + ['featuresNativeTextTracks', 'supportsNativeTextTracks'], + ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], + ['featuresNativeAudioTracks', 'supportsNativeAudioTracks'] +].forEach(function([key, fn]) { + defineLazyProperty(Html5.prototype, key, () => Html5[fn](), false); +}); /** * Boolean indicating whether the `HTML5` tech currently supports the media element @@ -1182,40 +1211,18 @@ Html5.prototype.featuresProgressEvents = true; */ Html5.prototype.featuresTimeupdateEvents = true; -/** - * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. - * - * @type {boolean} - * @default {@link Html5.supportsNativeTextTracks} - */ -Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks(); - -/** - * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. - * - * @type {boolean} - * @default {@link Html5.supportsNativeVideoTracks} - */ -Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks(); - -/** - * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. - * - * @type {boolean} - * @default {@link Html5.supportsNativeAudioTracks} - */ -Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); - // HTML5 Feature detection and Device Fixes --------------------------------- // -const canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; -const mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; +let canPlayType; Html5.patchCanPlayType = function() { // Android 4.0 and above can play HLS to some extent but it reports being unable to do so // Firefox and Chrome report correctly if (browser.ANDROID_VERSION >= 4.0 && !browser.IS_FIREFOX && !browser.IS_CHROME) { + canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; Html5.TEST_VID.constructor.prototype.canPlayType = function(type) { + const mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; + if (type && mpegurlRE.test(type)) { return 'maybe'; } @@ -1227,7 +1234,9 @@ Html5.patchCanPlayType = function() { Html5.unpatchCanPlayType = function() { const r = Html5.TEST_VID.constructor.prototype.canPlayType; - Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; + if (canPlayType) { + Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; + } return r; }; diff --git a/src/js/tracks/track-types.js b/src/js/tracks/track-types.js index beee5e4e8c..f6f6a1b5ee 100644 --- a/src/js/tracks/track-types.js +++ b/src/js/tracks/track-types.js @@ -8,8 +8,6 @@ import AudioTrack from './audio-track'; import VideoTrack from './video-track'; import HTMLTrackElement from './html-track-element'; -import mergeOptions from '../utils/merge-options'; - /* * This file contains all track properties that are used in * player.js, tech.js, html5.js and possibly other techs in the future. @@ -55,7 +53,7 @@ const REMOTE = { } }; -const ALL = mergeOptions(NORMAL, REMOTE); +const ALL = Object.assign({}, NORMAL, REMOTE); REMOTE.names = Object.keys(REMOTE); NORMAL.names = Object.keys(NORMAL); diff --git a/src/js/utils/define-lazy-property.js b/src/js/utils/define-lazy-property.js new file mode 100644 index 0000000000..75315340de --- /dev/null +++ b/src/js/utils/define-lazy-property.js @@ -0,0 +1,33 @@ +/** + * Object.defineProperty but "lazy", which means that the value is only set after + * it retrieved the first time, rather than being set right away. + * + * @param {Object} obj the object to set the property on + * @param {string} key the key for the property to set + * @param {Function} getValue the function used to get the value when it is needed. + * @param {boolean} setter wether a setter shoould be allowed or not + */ +const defineLazyProperty = function(obj, key, getValue, setter = true) { + const set = (value) => + Object.defineProperty(obj, key, {value, enumerable: true, writable: true}); + + const options = { + configurable: true, + enumerable: true, + get() { + const value = getValue(); + + set(value); + + return value; + } + }; + + if (setter) { + options.set = set; + } + + return Object.defineProperty(obj, key, options); +}; + +export default defineLazyProperty; diff --git a/src/js/utils/dom.js b/src/js/utils/dom.js index 9a4b35ad09..8fbac22be9 100644 --- a/src/js/utils/dom.js +++ b/src/js/utils/dom.js @@ -20,7 +20,12 @@ import computedStyle from './computed-style'; * */ function isNonBlankString(str) { - return typeof str === 'string' && (/\S/).test(str); + // we use str.trim as it will trim any whitespace characters + // from the front or back of non-whitespace characters. aka + // Any string that contains non-whitespace characters will + // still contain them after `trim` but whitespace only strings + // will have a length of 0, failing this check. + return typeof str === 'string' && Boolean(str.trim()); } /** @@ -35,7 +40,8 @@ function isNonBlankString(str) { * Throws an error if there is whitespace in the string. */ function throwIfWhitespace(str) { - if ((/\s/).test(str)) { + // str.indexOf instead of regex because str.indexOf is faster performance wise. + if (str.indexOf(' ') >= 0) { throw new Error('class has illegal whitespace characters'); } } @@ -159,7 +165,7 @@ export function createEl(tagName = 'div', properties = {}, attributes = {}, cont // method for it. } else if (propName === 'textContent') { textContent(el, val); - } else { + } else if (el[propName] !== val) { el[propName] = val; } }); diff --git a/src/js/utils/events.js b/src/js/utils/events.js index 296c24073b..fe578f80c0 100644 --- a/src/js/utils/events.js +++ b/src/js/utils/events.js @@ -87,6 +87,9 @@ function _handleMultipleEvents(fn, elem, types, callback) { * Fixed event object. */ export function fixEvent(event) { + if (event.fixed_) { + return event; + } function returnTrue() { return true; @@ -201,6 +204,7 @@ export function fixEvent(event) { } } + event.fixed_ = true; // Returns fixed-up instance return event; } @@ -208,22 +212,27 @@ export function fixEvent(event) { /** * Whether passive event listeners are supported */ -let _supportsPassive = false; - -(function() { - try { - const opts = Object.defineProperty({}, 'passive', { - get() { - _supportsPassive = true; - } - }); +let _supportsPassive; + +const supportsPassive = function() { + if (typeof _supportsPassive !== 'boolean') { + _supportsPassive = false; + try { + const opts = Object.defineProperty({}, 'passive', { + get() { + _supportsPassive = true; + } + }); - window.addEventListener('test', null, opts); - window.removeEventListener('test', null, opts); - } catch (e) { - // disregard + window.addEventListener('test', null, opts); + window.removeEventListener('test', null, opts); + } catch (e) { + // disregard + } } -})(); + + return _supportsPassive; +}; /** * Touch events Chrome expects to be passive @@ -310,7 +319,7 @@ export function on(elem, type, fn) { if (elem.addEventListener) { let options = false; - if (_supportsPassive && + if (supportsPassive() && passiveEvents.indexOf(type) > -1) { options = {passive: true}; } diff --git a/src/js/video.js b/src/js/video.js index 41044ce438..9fa76b346c 100644 --- a/src/js/video.js +++ b/src/js/video.js @@ -31,6 +31,7 @@ import xhr from 'xhr'; // Include the built-in techs import Tech from './tech/tech.js'; import { use as middlewareUse, TERMINATOR } from './tech/middleware.js'; +import defineLazyProperty from './utils/define-lazy-property.js'; /** * Normalize an `id` value by trimming off a leading `#` @@ -566,5 +567,7 @@ videojs.dom = Dom; */ videojs.url = Url; +videojs.defineLazyProperty = defineLazyProperty; + export default videojs;