diff options
Diffstat (limited to 'dist/plyr.polyfilled.js')
-rw-r--r-- | dist/plyr.polyfilled.js | 1046 |
1 files changed, 747 insertions, 299 deletions
diff --git a/dist/plyr.polyfilled.js b/dist/plyr.polyfilled.js index eda5833e..ea9a5afa 100644 --- a/dist/plyr.polyfilled.js +++ b/dist/plyr.polyfilled.js @@ -1,4 +1,4 @@ -(function (global, factory) { +typeof navigator === "object" && (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define('Plyr', factory) : (global.Plyr = factory()); @@ -20,7 +20,7 @@ if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef }); var _core = createCommonjsModule(function (module) { -var core = module.exports = { version: '2.5.3' }; +var core = module.exports = { version: '2.5.6' }; if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef }); var _core_1 = _core.version; @@ -333,11 +333,18 @@ var _arrayIncludes = function (IS_INCLUDES) { }; }; +var _shared = createCommonjsModule(function (module) { var SHARED = '__core-js_shared__'; var store = _global[SHARED] || (_global[SHARED] = {}); -var _shared = function (key) { - return store[key] || (store[key] = {}); -}; + +(module.exports = function (key, value) { + return store[key] || (store[key] = value !== undefined ? value : {}); +})('versions', []).push({ + version: _core.version, + mode: _library ? 'pure' : 'global', + copyright: '© 2018 Denis Pushkarev (zloirock.ru)' +}); +}); var shared = _shared('keys'); @@ -991,7 +998,7 @@ var _iterDefine = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORC var VALUES_BUG = false; var proto = Base.prototype; var $native = proto[ITERATOR$2] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT]; - var $default = (!BUGGY && $native) || getMethod(DEFAULT); + var $default = $native || getMethod(DEFAULT); var $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined; var $anyNative = NAME == 'Array' ? proto.entries || $native : $native; var methods, key, IteratorPrototype; @@ -1002,7 +1009,7 @@ var _iterDefine = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORC // Set @@toStringTag to native iterators _setToStringTag(IteratorPrototype, TAG, true); // fix for some old engines - if (!_library && !_has(IteratorPrototype, ITERATOR$2)) _hide(IteratorPrototype, ITERATOR$2, returnThis); + if (!_library && typeof IteratorPrototype[ITERATOR$2] != 'function') _hide(IteratorPrototype, ITERATOR$2, returnThis); } } // fix Array#{values, @@iterator}.name in V8 / FF @@ -2496,9 +2503,11 @@ function set(target, propertyKey, V /* , receiver */) { } if (_has(ownDesc, 'value')) { if (ownDesc.writable === false || !_isObject(receiver)) return false; - existingDescriptor = _objectGopd.f(receiver, propertyKey) || _propertyDesc(0); - existingDescriptor.value = V; - _objectDp.f(receiver, propertyKey, existingDescriptor); + if (existingDescriptor = _objectGopd.f(receiver, propertyKey)) { + if (existingDescriptor.get || existingDescriptor.set || existingDescriptor.writable === false) return false; + existingDescriptor.value = V; + _objectDp.f(receiver, propertyKey, existingDescriptor); + } else _objectDp.f(receiver, propertyKey, _propertyDesc(0, V)); return true; } return ownDesc.set === undefined ? false : (ownDesc.set.call(receiver, V), true); @@ -2643,7 +2652,8 @@ var _microtask = function () { }; // environments with maybe non-completely correct, but existent Promise } else if (Promise$1 && Promise$1.resolve) { - var promise = Promise$1.resolve(); + // Promise.resolve without an argument throws an error in LG WebOS 2 + var promise = Promise$1.resolve(undefined); notify = function () { promise.then(flush); }; @@ -2700,6 +2710,10 @@ var _perform = function (exec) { } }; +var navigator$1 = _global.navigator; + +var _userAgent = navigator$1 && navigator$1.userAgent || ''; + var _promiseResolve = function (C, x) { _anObject(C); if (_isObject(x) && x.constructor === C) return x; @@ -2714,9 +2728,12 @@ var microtask = _microtask(); + var PROMISE = 'Promise'; var TypeError$1 = _global.TypeError; var process$2 = _global.process; +var versions = process$2 && process$2.versions; +var v8 = versions && versions.v8 || ''; var $Promise = _global[PROMISE]; var isNode$1 = _classof(process$2) == 'process'; var empty = function () { /* empty */ }; @@ -2731,7 +2748,13 @@ var USE_NATIVE = !!function () { exec(empty, empty); }; // unhandled rejections tracking support, NodeJS Promise without it fails @@species test - return (isNode$1 || typeof PromiseRejectionEvent == 'function') && promise.then(empty) instanceof FakePromise; + return (isNode$1 || typeof PromiseRejectionEvent == 'function') + && promise.then(empty) instanceof FakePromise + // v8 6.6 (Node 10 and Chrome 66) have a bug with resolving custom thenables + // https://bugs.chromium.org/p/chromium/issues/detail?id=830565 + // we can't detect it synchronously, so just check versions + && v8.indexOf('6.6') !== 0 + && _userAgent.indexOf('Chrome/66') === -1; } catch (e) { /* empty */ } }(); @@ -2753,7 +2776,7 @@ var notify = function (promise, isReject) { var resolve = reaction.resolve; var reject = reaction.reject; var domain = reaction.domain; - var result, then; + var result, then, exited; try { if (handler) { if (!ok) { @@ -2763,8 +2786,11 @@ var notify = function (promise, isReject) { if (handler === true) result = value; else { if (domain) domain.enter(); - result = handler(value); - if (domain) domain.exit(); + result = handler(value); // may throw + if (domain) { + domain.exit(); + exited = true; + } } if (result === reaction.promise) { reject(TypeError$1('Promise-chain cycle')); @@ -2773,6 +2799,7 @@ var notify = function (promise, isReject) { } else resolve(result); } else reject(value); } catch (e) { + if (domain && !exited) domain.exit(); reject(e); } }; @@ -4155,10 +4182,6 @@ var _stringPad = function (that, maxLength, fillString, left) { return left ? stringFiller + S : S + stringFiller; }; -var navigator$1 = _global.navigator; - -var _userAgent = navigator$1 && navigator$1.userAgent || ''; - // https://github.com/tc39/proposal-string-pad-start-end @@ -5044,6 +5067,355 @@ try { window.CustomEvent = CustomEvent$1; // expose definition to window } +(function(global) {
+ /**
+ * Polyfill URLSearchParams
+ *
+ * Inspired from : https://github.com/WebReflection/url-search-params/blob/master/src/url-search-params.js
+ */
+
+ var checkIfIteratorIsSupported = function() {
+ try {
+ return !!Symbol.iterator;
+ } catch(error) {
+ return false;
+ }
+ };
+
+
+ var iteratorSupported = checkIfIteratorIsSupported();
+
+ var createIterator = function(items) {
+ var iterator = {
+ next: function() {
+ var value = items.shift();
+ return { done: value === void 0, value: value };
+ }
+ };
+
+ if(iteratorSupported) {
+ iterator[Symbol.iterator] = function() {
+ return iterator;
+ };
+ }
+
+ return iterator;
+ };
+
+ /**
+ * Search param name and values should be encoded according to https://url.spec.whatwg.org/#urlencoded-serializing
+ * encodeURIComponent() produces the same result except encoding spaces as `%20` instead of `+`.
+ */
+ var serializeParam = function(value) {
+ return encodeURIComponent(value).replace(/%20/g, '+');
+ };
+
+ var deserializeParam = function(value) {
+ return decodeURIComponent(value).replace(/\+/g, ' ');
+ };
+
+ var polyfillURLSearchParams= function() {
+
+ var URLSearchParams = function(searchString) {
+ Object.defineProperty(this, '_entries', { value: {} });
+
+ if(typeof searchString === 'string') {
+ if(searchString !== '') {
+ searchString = searchString.replace(/^\?/, '');
+ var attributes = searchString.split('&');
+ var attribute;
+ for(var i = 0; i < attributes.length; i++) {
+ attribute = attributes[i].split('=');
+ this.append(
+ deserializeParam(attribute[0]),
+ (attribute.length > 1) ? deserializeParam(attribute[1]) : ''
+ );
+ }
+ }
+ } else if(searchString instanceof URLSearchParams) {
+ var _this = this;
+ searchString.forEach(function(value, name) {
+ _this.append(value, name);
+ });
+ }
+ };
+
+ var proto = URLSearchParams.prototype;
+
+ proto.append = function(name, value) {
+ if(name in this._entries) {
+ this._entries[name].push(value.toString());
+ } else {
+ this._entries[name] = [value.toString()];
+ }
+ };
+
+ proto.delete = function(name) {
+ delete this._entries[name];
+ };
+
+ proto.get = function(name) {
+ return (name in this._entries) ? this._entries[name][0] : null;
+ };
+
+ proto.getAll = function(name) {
+ return (name in this._entries) ? this._entries[name].slice(0) : [];
+ };
+
+ proto.has = function(name) {
+ return (name in this._entries);
+ };
+
+ proto.set = function(name, value) {
+ this._entries[name] = [value.toString()];
+ };
+
+ proto.forEach = function(callback, thisArg) {
+ var entries;
+ for(var name in this._entries) {
+ if(this._entries.hasOwnProperty(name)) {
+ entries = this._entries[name];
+ for(var i = 0; i < entries.length; i++) {
+ callback.call(thisArg, entries[i], name, this);
+ }
+ }
+ }
+ };
+
+ proto.keys = function() {
+ var items = [];
+ this.forEach(function(value, name) { items.push(name); });
+ return createIterator(items);
+ };
+
+ proto.values = function() {
+ var items = [];
+ this.forEach(function(value) { items.push(value); });
+ return createIterator(items);
+ };
+
+ proto.entries = function() {
+ var items = [];
+ this.forEach(function(value, name) { items.push([name, value]); });
+ return createIterator(items);
+ };
+
+ if(iteratorSupported) {
+ proto[Symbol.iterator] = proto.entries;
+ }
+
+ proto.toString = function() {
+ var searchString = '';
+ this.forEach(function(value, name) {
+ if(searchString.length > 0) searchString+= '&';
+ searchString += serializeParam(name) + '=' + serializeParam(value);
+ });
+ return searchString;
+ };
+
+ global.URLSearchParams = URLSearchParams;
+ };
+
+ if(!('URLSearchParams' in global) || (new URLSearchParams('?a=1').toString() !== 'a=1')) {
+ polyfillURLSearchParams();
+ }
+
+ // HTMLAnchorElement
+
+})(
+ (typeof commonjsGlobal !== 'undefined') ? commonjsGlobal
+ : ((typeof window !== 'undefined') ? window
+ : ((typeof self !== 'undefined') ? self : commonjsGlobal))
+);
+
+(function(global) {
+ /**
+ * Polyfill URL
+ *
+ * Inspired from : https://github.com/arv/DOM-URL-Polyfill/blob/master/src/url.js
+ */
+
+ var checkIfURLIsSupported = function() {
+ try {
+ var u = new URL('b', 'http://a');
+ u.pathname = 'c%20d';
+ return (u.href === 'http://a/c%20d') && u.searchParams;
+ } catch(e) {
+ return false;
+ }
+ };
+
+
+ var polyfillURL = function() {
+ var _URL = global.URL;
+
+ var URL = function(url, base) {
+ if(typeof url !== 'string') url = String(url);
+
+ var doc = document.implementation.createHTMLDocument('');
+ window.doc = doc;
+ if(base) {
+ var baseElement = doc.createElement('base');
+ baseElement.href = base;
+ doc.head.appendChild(baseElement);
+ }
+
+ var anchorElement = doc.createElement('a');
+ anchorElement.href = url;
+ doc.body.appendChild(anchorElement);
+ anchorElement.href = anchorElement.href; // force href to refresh
+
+ if(anchorElement.protocol === ':' || !/:/.test(anchorElement.href)) {
+ throw new TypeError('Invalid URL');
+ }
+
+ Object.defineProperty(this, '_anchorElement', {
+ value: anchorElement
+ });
+ };
+
+ var proto = URL.prototype;
+
+ var linkURLWithAnchorAttribute = function(attributeName) {
+ Object.defineProperty(proto, attributeName, {
+ get: function() {
+ return this._anchorElement[attributeName];
+ },
+ set: function(value) {
+ this._anchorElement[attributeName] = value;
+ },
+ enumerable: true
+ });
+ };
+
+ ['hash', 'host', 'hostname', 'port', 'protocol', 'search']
+ .forEach(function(attributeName) {
+ linkURLWithAnchorAttribute(attributeName);
+ });
+
+ Object.defineProperties(proto, {
+
+ 'toString': {
+ get: function() {
+ var _this = this;
+ return function() {
+ return _this.href;
+ };
+ }
+ },
+
+ 'href' : {
+ get: function() {
+ return this._anchorElement.href.replace(/\?$/,'');
+ },
+ set: function(value) {
+ this._anchorElement.href = value;
+ },
+ enumerable: true
+ },
+
+ 'pathname' : {
+ get: function() {
+ return this._anchorElement.pathname.replace(/(^\/?)/,'/');
+ },
+ set: function(value) {
+ this._anchorElement.pathname = value;
+ },
+ enumerable: true
+ },
+
+ 'origin': {
+ get: function() {
+ // get expected port from protocol
+ var expectedPort = {'http:': 80, 'https:': 443, 'ftp:': 21}[this._anchorElement.protocol];
+ // add port to origin if, expected port is different than actual port
+ // and it is not empty f.e http://foo:8080
+ // 8080 != 80 && 8080 != ''
+ var addPortToOrigin = this._anchorElement.port != expectedPort &&
+ this._anchorElement.port !== '';
+
+ return this._anchorElement.protocol +
+ '//' +
+ this._anchorElement.hostname +
+ (addPortToOrigin ? (':' + this._anchorElement.port) : '');
+ },
+ enumerable: true
+ },
+
+ 'password': { // TODO
+ get: function() {
+ return '';
+ },
+ set: function(value) {
+ },
+ enumerable: true
+ },
+
+ 'username': { // TODO
+ get: function() {
+ return '';
+ },
+ set: function(value) {
+ },
+ enumerable: true
+ },
+
+ 'searchParams': {
+ get: function() {
+ var searchParams = new URLSearchParams(this.search);
+ var _this = this;
+ ['append', 'delete', 'set'].forEach(function(methodName) {
+ var method = searchParams[methodName];
+ searchParams[methodName] = function() {
+ method.apply(searchParams, arguments);
+ _this.search = searchParams.toString();
+ };
+ });
+ return searchParams;
+ },
+ enumerable: true
+ }
+ });
+
+ URL.createObjectURL = function(blob) {
+ return _URL.createObjectURL.apply(_URL, arguments);
+ };
+
+ URL.revokeObjectURL = function(url) {
+ return _URL.revokeObjectURL.apply(_URL, arguments);
+ };
+
+ global.URL = URL;
+
+ };
+
+ if(!checkIfURLIsSupported()) {
+ polyfillURL();
+ }
+
+ if((global.location !== void 0) && !('origin' in global.location)) {
+ var getOrigin = function() {
+ return global.location.protocol + '//' + global.location.hostname + (global.location.port ? (':' + global.location.port) : '');
+ };
+
+ try {
+ Object.defineProperty(global.location, 'origin', {
+ get: getOrigin,
+ enumerable: true
+ });
+ } catch(e) {
+ setInterval(function() {
+ global.location.origin = getOrigin();
+ }, 100);
+ }
+ }
+
+})(
+ (typeof commonjsGlobal !== 'undefined') ? commonjsGlobal
+ : ((typeof window !== 'undefined') ? window
+ : ((typeof self !== 'undefined') ? self : commonjsGlobal))
+); + var loadjs_umd = createCommonjsModule(function (module, exports) { (function(root, factory) { if (typeof undefined === 'function' && undefined.amd) { @@ -5636,6 +6008,24 @@ var utils = { }, + // Load image avoiding xhr/fetch CORS issues + // Server status can't be obtained this way unfortunately, so this uses "naturalWidth" to determine if the image has loaded. + // By default it checks if it is at least 1px, but you can add a second argument to change this. + loadImage: function loadImage(src) { + var minWidth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; + + return new Promise(function (resolve, reject) { + var image = new Image(); + var handler = function handler() { + delete image.onload; + delete image.onerror; + (image.naturalWidth >= minWidth ? resolve : reject)(image); + }; + Object.assign(image, { onload: handler, onerror: handler, src: src }); + }); + }, + + // Load an external script loadScript: function loadScript(url) { return new Promise(function (resolve, reject) { @@ -5764,7 +6154,7 @@ var utils = { // Add text node if (utils.is.string(text)) { - element.textContent = text; + element.innerText = text; } // Return built element @@ -5918,14 +6308,16 @@ var utils = { }, - // Toggle class on an element - toggleClass: function toggleClass(element, className, toggle) { + // Mirror Element.classList.toggle, with IE compatibility for "force" argument + toggleClass: function toggleClass(element, className, force) { if (utils.is.element(element)) { - var contains = element.classList.contains(className); - - element.classList[toggle ? 'add' : 'remove'](className); + var method = 'toggle'; + if (typeof force !== 'undefined') { + method = force ? 'add' : 'remove'; + } - return toggle && !contains || !toggle && contains; + element.classList[method](className); + return element.classList.contains(className); } return null; @@ -6308,6 +6700,12 @@ var utils = { }, + // Clone nested objects + cloneDeep: function cloneDeep(object) { + return JSON.parse(JSON.stringify(object)); + }, + + // Get the closest value in an array closest: function closest(array, value) { if (!utils.is.array(array) || !array.length) { @@ -7127,7 +7525,7 @@ var controls = { break; } - progress.textContent = '% ' + suffix.toLowerCase(); + progress.innerText = '% ' + suffix.toLowerCase(); } this.elements.display[type] = progress; @@ -7201,7 +7599,7 @@ var controls = { var forceHours = utils.getHours(this.duration) > 0; // eslint-disable-next-line no-param-reassign - target.textContent = utils.formatTime(time, forceHours, inverted); + target.innerText = utils.formatTime(time, forceHours, inverted); }, @@ -7270,6 +7668,7 @@ var controls = { // Video playing case 'timeupdate': case 'seeking': + case 'seeked': value = utils.getPercentage(this.currentTime, this.duration); // Set seek range value only if it's a 'natural' time event @@ -7327,7 +7726,7 @@ var controls = { // Calculate percentage var percent = 0; - var clientRect = this.elements.inputs.seek.getBoundingClientRect(); + var clientRect = this.elements.progress.getBoundingClientRect(); var visible = this.config.classNames.tooltip + '--visible'; var toggle = function toggle(_toggle) { @@ -7388,9 +7787,10 @@ var controls = { }, - // Show the duration on metadataloaded + // Show the duration on metadataloaded or durationchange events durationUpdate: function durationUpdate() { - if (!this.supported.ui) { + // Bail if no ui or durationchange event triggered after playing/seek when invertTime is false + if (!this.supported.ui || !this.config.invertTime && this.currentTime) { return; } @@ -7954,7 +8354,6 @@ var controls = { // Seek tooltip if (this.config.tooltips.seek) { var tooltip = utils.createElement('span', { - role: 'tooltip', class: this.config.classNames.tooltip }, '00:00'); @@ -8505,7 +8904,7 @@ var captions = { // Set the span content if (utils.is.string(caption)) { - content.textContent = caption.trim(); + content.innerText = caption.trim(); } else { content.appendChild(caption); } @@ -8635,7 +9034,7 @@ var defaults$1 = { // Sprite (for icons) loadSprite: true, iconPrefix: 'plyr', - iconUrl: 'https://cdn.plyr.io/3.3.7/plyr.svg', + iconUrl: 'https://cdn.plyr.io/3.3.8/plyr.svg', // Blank video (used to prevent errors on source change) blankVideo: 'https://cdn.plyr.io/static/blank.mp4', @@ -8743,8 +9142,7 @@ var defaults$1 = { }, youtube: { sdk: 'https://www.youtube.com/iframe_api', - api: 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet', - poster: 'https://img.youtube.com/vi/{0}/maxresdefault.jpg,https://img.youtube.com/vi/{0}/hqdefault.jpg' + api: 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet' }, googleIMA: { sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js' @@ -8840,13 +9238,13 @@ var defaults$1 = { embed: 'plyr__video-embed', embedContainer: 'plyr__video-embed__container', poster: 'plyr__poster', + posterEnabled: 'plyr__poster-enabled', ads: 'plyr__ads', control: 'plyr__control', playing: 'plyr--playing', paused: 'plyr--paused', stopped: 'plyr--stopped', loading: 'plyr--loading', - error: 'plyr--has-error', hover: 'plyr--hover', tooltip: 'plyr__tooltip', cues: 'plyr__cues', @@ -9241,8 +9639,10 @@ var ui = { // Set the title ui.setTitle.call(this); - // Set the poster image - ui.setPoster.call(this); + // Assure the poster image is set, if the property was added before the element was created + if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) { + ui.setPoster.call(this, this.poster); + } }, @@ -9284,17 +9684,43 @@ var ui = { }, - // Set the poster image - setPoster: function setPoster() { - if (!utils.is.element(this.elements.poster) || utils.is.empty(this.poster)) { - return; + // Toggle poster + togglePoster: function togglePoster(enable) { + utils.toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable); + }, + + + // Set the poster image (async) + setPoster: function setPoster(poster) { + var _this2 = this; + + // Set property regardless of validity + this.media.setAttribute('poster', poster); + + // Bail if element is missing + if (!utils.is.element(this.elements.poster)) { + return Promise.reject(); } - // Set the inline style - var posters = this.poster.split(','); - this.elements.poster.style.backgroundImage = posters.map(function (p) { - return 'url(\'' + p + '\')'; - }).join(','); + // Load the image, and set poster if successful + var loadPromise = utils.loadImage(poster).then(function () { + _this2.elements.poster.style.backgroundImage = 'url(\'' + poster + '\')'; + Object.assign(_this2.elements.poster.style, { + backgroundImage: 'url(\'' + poster + '\')', + // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube) + backgroundSize: '' + }); + ui.togglePoster.call(_this2, true); + return poster; + }); + + // Hide the element if the poster can't be loaded (otherwise it will just be a black element covering the video) + loadPromise.catch(function () { + return ui.togglePoster.call(_this2, false); + }); + + // Return the promise so the caller can use it as well + return loadPromise; }, @@ -9314,13 +9740,13 @@ var ui = { } // Toggle controls - this.toggleControls(!this.playing); + ui.toggleControls.call(this); }, // Check if media is loading checkLoading: function checkLoading(event) { - var _this2 = this; + var _this3 = this; this.loading = ['stalled', 'waiting'].includes(event.type); @@ -9329,38 +9755,24 @@ var ui = { // Timer to prevent flicker when seeking this.timers.loading = setTimeout(function () { - // Toggle container class hook - utils.toggleClass(_this2.elements.container, _this2.config.classNames.loading, _this2.loading); + // Update progress bar loading class state + utils.toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading); - // Show controls if loading, hide if done - _this2.toggleControls(_this2.loading); + // Update controls visibility + ui.toggleControls.call(_this3); }, this.loading ? 250 : 0); }, - // Check if media failed to load - checkFailed: function checkFailed() { - var _this3 = this; + // Toggle controls based on state and `force` argument + toggleControls: function toggleControls(force) { + var controls$$1 = this.elements.controls; - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/networkState - this.failed = this.media.networkState === 3; - if (this.failed) { - utils.toggleClass(this.elements.container, this.config.classNames.loading, false); - utils.toggleClass(this.elements.container, this.config.classNames.error, true); + if (controls$$1 && this.config.hideControls) { + // Show controls if force, loading, paused, or button interaction, otherwise hide + this.toggleControls(Boolean(force || this.loading || this.paused || controls$$1.pressed || controls$$1.hover)); } - - // Clear timer - clearTimeout(this.timers.failed); - - // Timer to prevent flicker when seeking - this.timers.loading = setTimeout(function () { - // Toggle container class hook - utils.toggleClass(_this3.elements.container, _this3.config.classNames.loading, _this3.loading); - - // Show controls if loading, hide if done - _this3.toggleControls(_this3.loading); - }, this.loading ? 250 : 0); } }; @@ -9598,13 +10010,35 @@ var Listeners = function () { }, 0); }); - // Toggle controls visibility based on mouse movement - if (this.player.config.hideControls) { - // Toggle controls on mouse events and entering fullscreen - utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', function (event) { - _this2.player.toggleControls(event); - }); - } + // Toggle controls on mouse events and entering fullscreen + utils.on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) { + var controls$$1 = _this2.player.elements.controls; + + // Remove button states for fullscreen + + if (event.type === 'enterfullscreen') { + controls$$1.pressed = false; + controls$$1.hover = false; + } + + // Show, then hide after a timeout unless another control event occurs + var show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type); + + var delay = 0; + + if (show) { + ui.toggleControls.call(_this2.player, true); + // Use longer timeout for touch devices + delay = _this2.player.touch ? 3000 : 2000; + } + + // Clear timer + clearTimeout(_this2.player.timers.controls); + // Timer to prevent flicker when seeking + _this2.player.timers.controls = setTimeout(function () { + return ui.toggleControls.call(_this2.player, false); + }, delay); + }); } // Listen for media events @@ -9615,7 +10049,7 @@ var Listeners = function () { var _this3 = this; // Time change on media - utils.on(this.player.media, 'timeupdate seeking', function (event) { + utils.on(this.player.media, 'timeupdate seeking seeked', function (event) { return controls.timeUpdate.call(_this3.player, event); }); @@ -9641,7 +10075,7 @@ var Listeners = function () { }); // Check for buffer progress - utils.on(this.player.media, 'progress playing', function (event) { + utils.on(this.player.media, 'progress playing seeking seeked', function (event) { return controls.updateProgress.call(_this3.player, event); }); @@ -9660,9 +10094,6 @@ var Listeners = function () { return ui.checkLoading.call(_this3.player, event); }); - // Check if media failed to load - // utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event)); - // If autoplay, then load advertisement if required // TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows utils.on(this.player.media, 'playing', function () { @@ -9884,9 +10315,47 @@ var Listeners = function () { } }); + // Set range input alternative "value", which matches the tooltip time (#954) + on(this.player.elements.inputs.seek, 'mousedown mousemove', function (event) { + var clientRect = _this4.player.elements.progress.getBoundingClientRect(); + var percent = 100 / clientRect.width * (event.pageX - clientRect.left); + event.currentTarget.setAttribute('seek-value', percent); + }); + + // Pause while seeking + on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) { + var seek = event.currentTarget; + + // Was playing before? + var play = seek.hasAttribute('play-on-seeked'); + + // Done seeking + var done = ['mouseup', 'touchend', 'keyup'].includes(event.type); + + // If we're done seeking and it was playing, resume playback + if (play && done) { + seek.removeAttribute('play-on-seeked'); + _this4.player.play(); + } else if (!done && _this4.player.playing) { + seek.setAttribute('play-on-seeked', ''); + _this4.player.pause(); + } + }); + // Seek on(this.player.elements.inputs.seek, inputEvent, function (event) { - _this4.player.currentTime = event.target.value / event.target.max * _this4.player.duration; + var seek = event.currentTarget; + + // If it exists, use seek-value instead of "value" for consistency with tooltip time (#954) + var seekTo = seek.getAttribute('seek-value'); + + if (utils.is.empty(seekTo)) { + seekTo = seek.value; + } + + seek.removeAttribute('seek-value'); + + _this4.player.currentTime = seekTo / seek.max * _this4.player.duration; }, 'seek'); // Current time invert @@ -9921,23 +10390,48 @@ var Listeners = function () { return controls.updateSeekTooltip.call(_this4.player, event); }); - // Toggle controls visibility based on mouse movement - if (this.player.config.hideControls) { - // Watch for cursor over controls so they don't hide when trying to interact - on(this.player.elements.controls, 'mouseenter mouseleave', function (event) { - _this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter'; - }); + // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) + on(this.player.elements.controls, 'mouseenter mouseleave', function (event) { + _this4.player.elements.controls.hover = !_this4.player.touch && event.type === 'mouseenter'; + }); - // Watch for cursor over controls so they don't hide when trying to interact - on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) { - _this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); - }); + // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) + on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) { + _this4.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); + }); - // Focus in/out on controls - on(this.player.elements.controls, 'focusin focusout', function (event) { - _this4.player.toggleControls(event); - }); - } + // Focus in/out on controls + on(this.player.elements.controls, 'focusin focusout', function (event) { + var _player = _this4.player, + config = _player.config, + elements = _player.elements, + timers = _player.timers; + + // Skip transition to prevent focus from scrolling the parent element + + utils.toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin'); + + // Toggle + ui.toggleControls.call(_this4.player, event.type === 'focusin'); + + // If focusin, hide again after delay + if (event.type === 'focusin') { + // Restore transition + setTimeout(function () { + utils.toggleClass(elements.controls, config.classNames.noTransition, false); + }, 0); + + // Delay a little more for keyboard users + var delay = _this4.touch ? 3000 : 4000; + + // Clear timer + clearTimeout(timers.controls); + // Hide + timers.controls = setTimeout(function () { + return ui.toggleControls.call(_this4.player, false); + }, delay); + } + }); // Mouse wheel for volume on(this.player.elements.inputs.volume, 'wheel', function (event) { @@ -9989,6 +10483,14 @@ var Listeners = function () { // ========================================================================== +// Set playback state and trigger change (only on actual change) +function assurePlaybackState(play) { + if (this.media.paused === play) { + this.media.paused = !play; + utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause'); + } +} + var vimeo = { setup: function setup() { var _this = this; @@ -10084,11 +10586,8 @@ var vimeo = { // Get original image url.pathname = url.pathname.split('_')[0] + '.jpg'; - // Set attribute - player.media.setAttribute('poster', url.href); - - // Update - ui.setPoster.call(player); + // Set and show poster + ui.setPoster.call(player, url.href); }); // Setup instance @@ -10108,15 +10607,13 @@ var vimeo = { // Create a faux HTML5 API using the Vimeo API player.media.play = function () { - player.embed.play().then(function () { - player.media.paused = false; - }); + assurePlaybackState.call(player, true); + return player.embed.play(); }; player.media.pause = function () { - player.embed.pause().then(function () { - player.media.paused = true; - }); + assurePlaybackState.call(player, false); + return player.embed.pause(); }; player.media.stop = function () { @@ -10132,26 +10629,35 @@ var vimeo = { return currentTime; }, set: function set(time) { - // Get current paused state - // Vimeo will automatically play on seek - var paused = player.media.paused; - - // Set seeking flag - - player.media.seeking = true; - - // Trigger seeking - utils.dispatchEvent.call(player, player.media, 'seeking'); - - // Seek after events - player.embed.setCurrentTime(time).catch(function () { + // Vimeo will automatically play on seek if the video hasn't been played before + + // Get current paused state and volume etc + var embed = player.embed, + media = player.media, + paused = player.paused, + volume = player.volume; + + // Set seeking state and trigger event + + media.seeking = true; + utils.dispatchEvent.call(player, media, 'seeking'); + + // If paused, mute until seek is complete + Promise.resolve(paused && embed.setVolume(0)) + // Seek + .then(function () { + return embed.setCurrentTime(time); + }) + // Restore paused + .then(function () { + return paused && embed.pause(); + }) + // Restore volume + .then(function () { + return paused && embed.setVolume(volume); + }).catch(function () { // Do nothing }); - - // Restore pause state - if (paused) { - player.pause(); - } } }); @@ -10299,17 +10805,12 @@ var vimeo = { }); player.embed.on('play', function () { - // Only fire play if paused before - if (player.media.paused) { - utils.dispatchEvent.call(player, player.media, 'play'); - } - player.media.paused = false; + assurePlaybackState.call(player, true); utils.dispatchEvent.call(player, player.media, 'playing'); }); player.embed.on('pause', function () { - player.media.paused = true; - utils.dispatchEvent.call(player, player.media, 'pause'); + assurePlaybackState.call(player, false); }); player.embed.on('timeupdate', function (data) { @@ -10340,7 +10841,6 @@ var vimeo = { player.embed.on('seeked', function () { player.media.seeking = false; utils.dispatchEvent.call(player, player.media, 'seeked'); - utils.dispatchEvent.call(player, player.media, 'play'); }); player.embed.on('ended', function () { @@ -10422,6 +10922,14 @@ function mapQualityUnits(levels) { })); } +// Set playback state and trigger change (only on actual change) +function assurePlaybackState$1(play) { + if (this.media.paused === play) { + this.media.paused = !play; + utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause'); + } +} + var youtube = { setup: function setup() { var _this = this; @@ -10525,7 +11033,26 @@ var youtube = { player.media = utils.replaceElement(container, player.media); // Set poster image - player.media.setAttribute('poster', utils.format(player.config.urls.youtube.poster, videoId)); + var posterSrc = function posterSrc(format) { + return 'https://img.youtube.com/vi/' + videoId + '/' + format + 'default.jpg'; + }; + + // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide) + utils.loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded + .catch(function () { + return utils.loadImage(posterSrc('sd'), 121); + }) // 480p padded 4:3 + .catch(function () { + return utils.loadImage(posterSrc('hq')); + }) // 360p padded 4:3. Always exists + .then(function (image) { + return ui.setPoster.call(player, image.src); + }).then(function (posterSrc) { + // If the image is padded, use background-size "cover" instead (like youtube does too with their posters) + if (!posterSrc.includes('maxres')) { + player.elements.poster.style.backgroundSize = 'cover'; + } + }); // Setup instance // https://developers.google.com/youtube/iframe_api_reference @@ -10612,10 +11139,12 @@ var youtube = { // Create a faux HTML5 API using the YouTube API player.media.play = function () { + assurePlaybackState$1.call(player, true); instance.playVideo(); }; player.media.pause = function () { + assurePlaybackState$1.call(player, false); instance.pauseVideo(); }; @@ -10633,23 +11162,17 @@ var youtube = { return Number(instance.getCurrentTime()); }, set: function set(time) { - // Vimeo will automatically play on seek - var paused = player.media.paused; - - // Set seeking flag + // If paused, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet). + if (player.paused) { + player.embed.mute(); + } + // Set seeking state and trigger event player.media.seeking = true; - - // Trigger seeking utils.dispatchEvent.call(player, player.media, 'seeking'); // Seek after events sent instance.seekTo(time); - - // Restore pause state - if (paused) { - player.pause(); - } } }); @@ -10772,6 +11295,14 @@ var youtube = { // Reset timer clearInterval(player.timers.playing); + var seeked = player.media.seeking && [1, 2].includes(event.data); + + if (seeked) { + // Unset seeking and fire seeked event + player.media.seeking = false; + utils.dispatchEvent.call(player, player.media, 'seeked'); + } + // Handle events // -1 Unstarted // 0 Ended @@ -10791,7 +11322,7 @@ var youtube = { break; case 0: - player.media.paused = true; + assurePlaybackState$1.call(player, false); // YouTube doesn't support loop for a single video, so mimick it. if (player.media.loop) { @@ -10805,42 +11336,39 @@ var youtube = { break; case 1: - // If we were seeking, fire seeked event - if (player.media.seeking) { - utils.dispatchEvent.call(player, player.media, 'seeked'); - } - player.media.seeking = false; - - // Only fire play if paused before + // Restore paused state (YouTube starts playing on seek if the video hasn't been played yet) if (player.media.paused) { - utils.dispatchEvent.call(player, player.media, 'play'); - } - player.media.paused = false; + player.media.pause(); + } else { + assurePlaybackState$1.call(player, true); - utils.dispatchEvent.call(player, player.media, 'playing'); + utils.dispatchEvent.call(player, player.media, 'playing'); - // Poll to get playback progress - player.timers.playing = setInterval(function () { - utils.dispatchEvent.call(player, player.media, 'timeupdate'); - }, 50); + // Poll to get playback progress + player.timers.playing = setInterval(function () { + utils.dispatchEvent.call(player, player.media, 'timeupdate'); + }, 50); - // Check duration again due to YouTube bug - // https://github.com/sampotts/plyr/issues/374 - // https://code.google.com/p/gdata-issues/issues/detail?id=8690 - if (player.media.duration !== instance.getDuration()) { - player.media.duration = instance.getDuration(); - utils.dispatchEvent.call(player, player.media, 'durationchange'); - } + // Check duration again due to YouTube bug + // https://github.com/sampotts/plyr/issues/374 + // https://code.google.com/p/gdata-issues/issues/detail?id=8690 + if (player.media.duration !== instance.getDuration()) { + player.media.duration = instance.getDuration(); + utils.dispatchEvent.call(player, player.media, 'durationchange'); + } - // Get quality - controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels())); + // Get quality + controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels())); + } break; case 2: - player.media.paused = true; - - utils.dispatchEvent.call(player, player.media, 'pause'); + // Restore audio (YouTube starts playing on seek if the video hasn't been played yet) + if (!player.muted) { + player.embed.unMute(); + } + assurePlaybackState$1.call(player, false); break; @@ -11774,7 +12302,7 @@ var Plyr = function () { } // Set config - this.config = utils.extend({}, defaults$1, options || {}, function () { + this.config = utils.extend({}, defaults$1, Plyr.defaults, options || {}, function () { try { return JSON.parse(_this.media.getAttribute('data-plyr-config')); } catch (e) { @@ -12214,114 +12742,35 @@ var Plyr = function () { /** * Toggle the player controls - * @param {boolean} toggle - Whether to show the controls + * @param {boolean} [toggle] - Whether to show the controls */ }, { key: 'toggleControls', value: function toggleControls(toggle) { - var _this2 = this; + // Don't toggle if missing UI support or if it's audio + if (this.supported.ui && !this.isAudio) { + // Get state before change + var isHidden = utils.hasClass(this.elements.container, this.config.classNames.hideControls); - // We need controls of course... - if (!utils.is.element(this.elements.controls)) { - return; - } - - // Don't hide if no UI support or it's audio - if (!this.supported.ui || this.isAudio) { - return; - } - - var delay = 0; - var show = toggle; - var isEnterFullscreen = false; - - // Get toggle state if not set - if (!utils.is.boolean(toggle)) { - if (utils.is.event(toggle)) { - // Is the enter fullscreen event - isEnterFullscreen = toggle.type === 'enterfullscreen'; - - // Events that show the controls - var showEvents = ['touchstart', 'touchmove', 'mouseenter', 'mousemove', 'focusin']; - - // Events that delay hiding - var delayEvents = ['touchmove', 'touchend', 'mousemove']; - - // Whether to show controls - show = showEvents.includes(toggle.type); + // Negate the argument if not undefined since adding the class to hides the controls + var force = typeof toggle === 'undefined' ? undefined : !toggle; - // Delay hiding on move events - if (delayEvents.includes(toggle.type)) { - delay = 2000; - } - - // Delay a little more for keyboard users - if (!this.touch && toggle.type === 'focusin') { - delay = 3000; - utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true); - } - } else { - show = utils.hasClass(this.elements.container, this.config.classNames.hideControls); - } - } - - // Clear timer on every call - clearTimeout(this.timers.controls); - - // If the mouse is not over the controls, set a timeout to hide them - if (show || this.paused || this.loading) { - // Check if controls toggled - var toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, false); - - // Trigger event - if (toggled) { - utils.dispatchEvent.call(this, this.media, 'controlsshown'); - } + // Apply and get updated state + var hiding = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, force); - // Always show controls when paused or if touch - if (this.paused || this.loading) { - return; + // Close menu + if (hiding && this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { + controls.toggleMenu.call(this, false); } - - // Delay for hiding on touch - if (this.touch) { - delay = 3000; + // Trigger event on change + if (hiding !== isHidden) { + var eventName = hiding ? 'controlshidden' : 'controlsshown'; + utils.dispatchEvent.call(this, this.media, eventName); } + return !hiding; } - - // If toggle is false or if we're playing (regardless of toggle), - // then set the timer to hide the controls - if (!show || this.playing) { - this.timers.controls = setTimeout(function () { - // We need controls of course... - if (!utils.is.element(_this2.elements.controls)) { - return; - } - - // If the mouse is over the controls (and not entering fullscreen), bail - if ((_this2.elements.controls.pressed || _this2.elements.controls.hover) && !isEnterFullscreen) { - return; - } - - // Restore transition behaviour - if (!utils.hasClass(_this2.elements.container, _this2.config.classNames.hideControls)) { - utils.toggleClass(_this2.elements.controls, _this2.config.classNames.noTransition, false); - } - - // Set hideControls class - var toggled = utils.toggleClass(_this2.elements.container, _this2.config.classNames.hideControls, _this2.config.hideControls); - - // Trigger event and close menu - if (toggled) { - utils.dispatchEvent.call(_this2, _this2.media, 'controlshidden'); - - if (_this2.config.controls.includes('settings') && !utils.is.empty(_this2.config.settings)) { - controls.toggleMenu.call(_this2, false); - } - } - }, delay); - } + return false; } /** @@ -12359,7 +12808,7 @@ var Plyr = function () { }, { key: 'destroy', value: function destroy(callback) { - var _this3 = this; + var _this2 = this; var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; @@ -12372,22 +12821,22 @@ var Plyr = function () { document.body.style.overflow = ''; // GC for embed - _this3.embed = null; + _this2.embed = null; // If it's a soft destroy, make minimal changes if (soft) { - if (Object.keys(_this3.elements).length) { + if (Object.keys(_this2.elements).length) { // Remove elements - utils.removeElement(_this3.elements.buttons.play); - utils.removeElement(_this3.elements.captions); - utils.removeElement(_this3.elements.controls); - utils.removeElement(_this3.elements.wrapper); + utils.removeElement(_this2.elements.buttons.play); + utils.removeElement(_this2.elements.captions); + utils.removeElement(_this2.elements.controls); + utils.removeElement(_this2.elements.wrapper); // Clear for GC - _this3.elements.buttons.play = null; - _this3.elements.captions = null; - _this3.elements.controls = null; - _this3.elements.wrapper = null; + _this2.elements.buttons.play = null; + _this2.elements.captions = null; + _this2.elements.controls = null; + _this2.elements.wrapper = null; } // Callback @@ -12396,26 +12845,26 @@ var Plyr = function () { } } else { // Unbind listeners - _this3.listeners.clear(); + _this2.listeners.clear(); // Replace the container with the original element provided - utils.replaceElement(_this3.elements.original, _this3.elements.container); + utils.replaceElement(_this2.elements.original, _this2.elements.container); // Event - utils.dispatchEvent.call(_this3, _this3.elements.original, 'destroyed', true); + utils.dispatchEvent.call(_this2, _this2.elements.original, 'destroyed', true); // Callback if (utils.is.function(callback)) { - callback.call(_this3.elements.original); + callback.call(_this2.elements.original); } // Reset state - _this3.ready = false; + _this2.ready = false; // Clear for garbage collection setTimeout(function () { - _this3.elements = null; - _this3.media = null; + _this2.elements = null; + _this2.media = null; }, 200); } }; @@ -12934,10 +13383,7 @@ var Plyr = function () { return; } - if (utils.is.string(input)) { - this.media.setAttribute('poster', input); - ui.setPoster.call(this); - } + ui.setPoster.call(this, input); } /** @@ -13118,6 +13564,8 @@ var Plyr = function () { return Plyr; }(); +Plyr.defaults = utils.cloneDeep(defaults$1); + // ========================================================================== return Plyr; |