/******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ 490: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; var window = __webpack_require__(908); var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) { if (decodeResponseBody === void 0) { decodeResponseBody = false; } return function (err, response, responseBody) { // if the XHR failed, return that error if (err) { callback(err); return; } // if the HTTP status code is 4xx or 5xx, the request also failed if (response.statusCode >= 400 && response.statusCode <= 599) { var cause = responseBody; if (decodeResponseBody) { if (window.TextDecoder) { var charset = getCharset(response.headers && response.headers['content-type']); try { cause = new TextDecoder(charset).decode(responseBody); } catch (e) {} } else { cause = String.fromCharCode.apply(null, new Uint8Array(responseBody)); } } callback({ cause: cause }); return; } // otherwise, request succeeded callback(null, responseBody); }; }; function getCharset(contentTypeHeader) { if (contentTypeHeader === void 0) { contentTypeHeader = ''; } return contentTypeHeader.toLowerCase().split(';').reduce(function (charset, contentType) { var _contentType$split = contentType.split('='), type = _contentType$split[0], value = _contentType$split[1]; if (type.trim() === 'charset') { return value.trim(); } return charset; }, 'utf-8'); } module.exports = httpResponseHandler; /***/ }), /***/ 603: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; var window = __webpack_require__(908); var _extends = __webpack_require__(434); var isFunction = __webpack_require__(376); var InterceptorsStorage = __webpack_require__(608); var RetryManager = __webpack_require__(973); createXHR.httpHandler = __webpack_require__(490); createXHR.requestInterceptorsStorage = new InterceptorsStorage(); createXHR.responseInterceptorsStorage = new InterceptorsStorage(); createXHR.retryManager = new RetryManager(); /** * @license * slighly modified parse-headers 2.0.2 * Copyright (c) 2014 David Björklund * Available under the MIT license * */ var parseHeaders = function parseHeaders(headers) { var result = {}; if (!headers) { return result; } headers.trim().split('\n').forEach(function (row) { var index = row.indexOf(':'); var key = row.slice(0, index).trim().toLowerCase(); var value = row.slice(index + 1).trim(); if (typeof result[key] === 'undefined') { result[key] = value; } else if (Array.isArray(result[key])) { result[key].push(value); } else { result[key] = [result[key], value]; } }); return result; }; module.exports = createXHR; // Allow use of default import syntax in TypeScript module.exports["default"] = createXHR; createXHR.XMLHttpRequest = window.XMLHttpRequest || noop; createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window.XDomainRequest; forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) { createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) { options = initParams(uri, options, callback); options.method = method.toUpperCase(); return _createXHR(options); }; }); function forEachArray(array, iterator) { for (var i = 0; i < array.length; i++) { iterator(array[i]); } } function isEmpty(obj) { for (var i in obj) { if (obj.hasOwnProperty(i)) return false; } return true; } function initParams(uri, options, callback) { var params = uri; if (isFunction(options)) { callback = options; if (typeof uri === "string") { params = { uri: uri }; } } else { params = _extends({}, options, { uri: uri }); } params.callback = callback; return params; } function createXHR(uri, options, callback) { options = initParams(uri, options, callback); return _createXHR(options); } function _createXHR(options) { if (typeof options.callback === "undefined") { throw new Error("callback argument missing"); } // call all registered request interceptors for a given request type: if (options.requestType && createXHR.requestInterceptorsStorage.getIsEnabled()) { var requestInterceptorPayload = { uri: options.uri || options.url, headers: options.headers || {}, body: options.body, metadata: options.metadata || {}, retry: options.retry, timeout: options.timeout }; var updatedPayload = createXHR.requestInterceptorsStorage.execute(options.requestType, requestInterceptorPayload); options.uri = updatedPayload.uri; options.headers = updatedPayload.headers; options.body = updatedPayload.body; options.metadata = updatedPayload.metadata; options.retry = updatedPayload.retry; options.timeout = updatedPayload.timeout; } var called = false; var callback = function cbOnce(err, response, body) { if (!called) { called = true; options.callback(err, response, body); } }; function readystatechange() { // do not call load 2 times when response interceptors are enabled // why do we even need this 2nd load? if (xhr.readyState === 4 && !createXHR.responseInterceptorsStorage.getIsEnabled()) { setTimeout(loadFunc, 0); } } function getBody() { // Chrome with requestType=blob throws errors arround when even testing access to responseText var body = undefined; if (xhr.response) { body = xhr.response; } else { body = xhr.responseText || getXml(xhr); } if (isJson) { try { body = JSON.parse(body); } catch (e) {} } return body; } function errorFunc(evt) { clearTimeout(timeoutTimer); clearTimeout(options.retryTimeout); if (!(evt instanceof Error)) { evt = new Error("" + (evt || "Unknown XMLHttpRequest Error")); } evt.statusCode = 0; // we would like to retry on error: if (!aborted && createXHR.retryManager.getIsEnabled() && options.retry && options.retry.shouldRetry()) { options.retryTimeout = setTimeout(function () { options.retry.moveToNextAttempt(); // we want to re-use the same options and the same xhr object: options.xhr = xhr; _createXHR(options); }, options.retry.getCurrentFuzzedDelay()); return; } // call all registered response interceptors for a given request type: if (options.requestType && createXHR.responseInterceptorsStorage.getIsEnabled()) { var responseInterceptorPayload = { headers: failureResponse.headers || {}, body: failureResponse.body, responseUrl: xhr.responseURL, responseType: xhr.responseType }; var _updatedPayload = createXHR.responseInterceptorsStorage.execute(options.requestType, responseInterceptorPayload); failureResponse.body = _updatedPayload.body; failureResponse.headers = _updatedPayload.headers; } return callback(evt, failureResponse); } // will load the data & process the response in a special response object function loadFunc() { if (aborted) return; var status; clearTimeout(timeoutTimer); clearTimeout(options.retryTimeout); if (options.useXDR && xhr.status === undefined) { //IE8 CORS GET successful response doesn't have a status field, but body is fine status = 200; } else { status = xhr.status === 1223 ? 204 : xhr.status; } var response = failureResponse; var err = null; if (status !== 0) { response = { body: getBody(), statusCode: status, method: method, headers: {}, url: uri, rawRequest: xhr }; if (xhr.getAllResponseHeaders) { //remember xhr can in fact be XDR for CORS in IE response.headers = parseHeaders(xhr.getAllResponseHeaders()); } } else { err = new Error("Internal XMLHttpRequest Error"); } // call all registered response interceptors for a given request type: if (options.requestType && createXHR.responseInterceptorsStorage.getIsEnabled()) { var responseInterceptorPayload = { headers: response.headers || {}, body: response.body, responseUrl: xhr.responseURL, responseType: xhr.responseType }; var _updatedPayload2 = createXHR.responseInterceptorsStorage.execute(options.requestType, responseInterceptorPayload); response.body = _updatedPayload2.body; response.headers = _updatedPayload2.headers; } return callback(err, response, response.body); } var xhr = options.xhr || null; if (!xhr) { if (options.cors || options.useXDR) { xhr = new createXHR.XDomainRequest(); } else { xhr = new createXHR.XMLHttpRequest(); } } var key; var aborted; var uri = xhr.url = options.uri || options.url; var method = xhr.method = options.method || "GET"; var body = options.body || options.data; var headers = xhr.headers = options.headers || {}; var sync = !!options.sync; var isJson = false; var timeoutTimer; var failureResponse = { body: undefined, headers: {}, statusCode: 0, method: method, url: uri, rawRequest: xhr }; if ("json" in options && options.json !== false) { isJson = true; headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user if (method !== "GET" && method !== "HEAD") { headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user body = JSON.stringify(options.json === true ? body : options.json); } } xhr.onreadystatechange = readystatechange; xhr.onload = loadFunc; xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function. xhr.onprogress = function () {// IE must die }; xhr.onabort = function () { aborted = true; clearTimeout(options.retryTimeout); }; xhr.ontimeout = errorFunc; xhr.open(method, uri, !sync, options.username, options.password); //has to be after open if (!sync) { xhr.withCredentials = !!options.withCredentials; } // Cannot set timeout with sync request // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent if (!sync && options.timeout > 0) { timeoutTimer = setTimeout(function () { if (aborted) return; aborted = true; //IE9 may still call readystatechange xhr.abort("timeout"); var e = new Error("XMLHttpRequest timeout"); e.code = "ETIMEDOUT"; errorFunc(e); }, options.timeout); } if (xhr.setRequestHeader) { for (key in headers) { if (headers.hasOwnProperty(key)) { xhr.setRequestHeader(key, headers[key]); } } } else if (options.headers && !isEmpty(options.headers)) { throw new Error("Headers cannot be set on an XDomainRequest object"); } if ("responseType" in options) { xhr.responseType = options.responseType; } if ("beforeSend" in options && typeof options.beforeSend === "function") { options.beforeSend(xhr); } // Microsoft Edge browser sends "undefined" when send is called with undefined value. // XMLHttpRequest spec says to pass null as body to indicate no body // See https://github.com/naugtur/xhr/issues/100. xhr.send(body || null); return xhr; } function getXml(xhr) { // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. try { if (xhr.responseType === "document") { return xhr.responseXML; } var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; if (xhr.responseType === "" && !firefoxBugTakenEffect) { return xhr.responseXML; } } catch (e) {} return null; } function noop() {} /***/ }), /***/ 608: /***/ ((module) => { "use strict"; function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } var InterceptorsStorage = /*#__PURE__*/function () { function InterceptorsStorage() { this.typeToInterceptorsMap_ = new Map(); this.enabled_ = false; } var _proto = InterceptorsStorage.prototype; _proto.getIsEnabled = function getIsEnabled() { return this.enabled_; }; _proto.enable = function enable() { this.enabled_ = true; }; _proto.disable = function disable() { this.enabled_ = false; }; _proto.reset = function reset() { this.typeToInterceptorsMap_ = new Map(); this.enabled_ = false; }; _proto.addInterceptor = function addInterceptor(type, interceptor) { if (!this.typeToInterceptorsMap_.has(type)) { this.typeToInterceptorsMap_.set(type, new Set()); } var interceptorsSet = this.typeToInterceptorsMap_.get(type); if (interceptorsSet.has(interceptor)) { // already have this interceptor return false; } interceptorsSet.add(interceptor); return true; }; _proto.removeInterceptor = function removeInterceptor(type, interceptor) { var interceptorsSet = this.typeToInterceptorsMap_.get(type); if (interceptorsSet && interceptorsSet.has(interceptor)) { interceptorsSet.delete(interceptor); return true; } return false; }; _proto.clearInterceptorsByType = function clearInterceptorsByType(type) { var interceptorsSet = this.typeToInterceptorsMap_.get(type); if (!interceptorsSet) { return false; } this.typeToInterceptorsMap_.delete(type); this.typeToInterceptorsMap_.set(type, new Set()); return true; }; _proto.clear = function clear() { if (!this.typeToInterceptorsMap_.size) { return false; } this.typeToInterceptorsMap_ = new Map(); return true; }; _proto.getForType = function getForType(type) { return this.typeToInterceptorsMap_.get(type) || new Set(); }; _proto.execute = function execute(type, payload) { var interceptors = this.getForType(type); for (var _iterator = _createForOfIteratorHelperLoose(interceptors), _step; !(_step = _iterator()).done;) { var interceptor = _step.value; try { payload = interceptor(payload); } catch (e) {//ignore } } return payload; }; return InterceptorsStorage; }(); module.exports = InterceptorsStorage; /***/ }), /***/ 973: /***/ ((module) => { "use strict"; var RetryManager = /*#__PURE__*/function () { function RetryManager() { this.maxAttempts_ = 1; this.delayFactor_ = 0.1; this.fuzzFactor_ = 0.1; this.initialDelay_ = 1000; this.enabled_ = false; } var _proto = RetryManager.prototype; _proto.getIsEnabled = function getIsEnabled() { return this.enabled_; }; _proto.enable = function enable() { this.enabled_ = true; }; _proto.disable = function disable() { this.enabled_ = false; }; _proto.reset = function reset() { this.maxAttempts_ = 1; this.delayFactor_ = 0.1; this.fuzzFactor_ = 0.1; this.initialDelay_ = 1000; this.enabled_ = false; }; _proto.getMaxAttempts = function getMaxAttempts() { return this.maxAttempts_; }; _proto.setMaxAttempts = function setMaxAttempts(maxAttempts) { this.maxAttempts_ = maxAttempts; }; _proto.getDelayFactor = function getDelayFactor() { return this.delayFactor_; }; _proto.setDelayFactor = function setDelayFactor(delayFactor) { this.delayFactor_ = delayFactor; }; _proto.getFuzzFactor = function getFuzzFactor() { return this.fuzzFactor_; }; _proto.setFuzzFactor = function setFuzzFactor(fuzzFactor) { this.fuzzFactor_ = fuzzFactor; }; _proto.getInitialDelay = function getInitialDelay() { return this.initialDelay_; }; _proto.setInitialDelay = function setInitialDelay(initialDelay) { this.initialDelay_ = initialDelay; }; _proto.createRetry = function createRetry(_temp) { var _ref = _temp === void 0 ? {} : _temp, maxAttempts = _ref.maxAttempts, delayFactor = _ref.delayFactor, fuzzFactor = _ref.fuzzFactor, initialDelay = _ref.initialDelay; return new Retry({ maxAttempts: maxAttempts || this.maxAttempts_, delayFactor: delayFactor || this.delayFactor_, fuzzFactor: fuzzFactor || this.fuzzFactor_, initialDelay: initialDelay || this.initialDelay_ }); }; return RetryManager; }(); var Retry = /*#__PURE__*/function () { function Retry(options) { this.maxAttempts_ = options.maxAttempts; this.delayFactor_ = options.delayFactor; this.fuzzFactor_ = options.fuzzFactor; this.currentDelay_ = options.initialDelay; this.currentAttempt_ = 1; } var _proto2 = Retry.prototype; _proto2.moveToNextAttempt = function moveToNextAttempt() { this.currentAttempt_++; var delayDelta = this.currentDelay_ * this.delayFactor_; this.currentDelay_ = this.currentDelay_ + delayDelta; }; _proto2.shouldRetry = function shouldRetry() { return this.currentAttempt_ < this.maxAttempts_; }; _proto2.getCurrentDelay = function getCurrentDelay() { return this.currentDelay_; }; _proto2.getCurrentMinPossibleDelay = function getCurrentMinPossibleDelay() { return (1 - this.fuzzFactor_) * this.currentDelay_; }; _proto2.getCurrentMaxPossibleDelay = function getCurrentMaxPossibleDelay() { return (1 + this.fuzzFactor_) * this.currentDelay_; } /** * For example fuzzFactor is 0.1 * This means ±10% deviation * So if we have delay as 1000 * This function can generate any value from 900 to 1100 */ ; _proto2.getCurrentFuzzedDelay = function getCurrentFuzzedDelay() { var lowValue = this.getCurrentMinPossibleDelay(); var highValue = this.getCurrentMaxPossibleDelay(); return lowValue + Math.random() * (highValue - lowValue); }; return Retry; }(); module.exports = RetryManager; /***/ }), /***/ 167: /***/ ((__unused_webpack_module, exports) => { "use strict"; /** * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes. * * Works with anything that has a `length` property and index access properties, including NodeList. * * @template {unknown} T * @param {Array | ({length:number, [number]: T})} list * @param {function (item: T, index: number, list:Array | ({length:number, [number]: T})):boolean} predicate * @param {Partial>?} ac `Array.prototype` by default, * allows injecting a custom implementation in tests * @returns {T | undefined} * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find */ function find(list, predicate, ac) { if (ac === undefined) { ac = Array.prototype; } if (list && typeof ac.find === 'function') { return ac.find.call(list, predicate); } for (var i = 0; i < list.length; i++) { if (Object.prototype.hasOwnProperty.call(list, i)) { var item = list[i]; if (predicate.call(undefined, item, i, list)) { return item; } } } } /** * "Shallow freezes" an object to render it immutable. * Uses `Object.freeze` if available, * otherwise the immutability is only in the type. * * Is used to create "enum like" objects. * * @template T * @param {T} object the object to freeze * @param {Pick = Object} oc `Object` by default, * allows to inject custom object constructor for tests * @returns {Readonly} * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze */ function freeze(object, oc) { if (oc === undefined) { oc = Object } return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object } /** * Since we can not rely on `Object.assign` we provide a simplified version * that is sufficient for our needs. * * @param {Object} target * @param {Object | null | undefined} source * * @returns {Object} target * @throws TypeError if target is not an object * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign * @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign */ function assign(target, source) { if (target === null || typeof target !== 'object') { throw new TypeError('target is not an object') } for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key] } } return target } /** * All mime types that are allowed as input to `DOMParser.parseFromString` * * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec * @see DOMParser.prototype.parseFromString */ var MIME_TYPE = freeze({ /** * `text/html`, the only mime type that triggers treating an XML document as HTML. * * @see DOMParser.SupportedType.isHTML * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration * @see https://en.wikipedia.org/wiki/HTML Wikipedia * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec */ HTML: 'text/html', /** * Helper method to check a mime type if it indicates an HTML document * * @param {string} [value] * @returns {boolean} * * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration * @see https://en.wikipedia.org/wiki/HTML Wikipedia * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring */ isHTML: function (value) { return value === MIME_TYPE.HTML }, /** * `application/xml`, the standard mime type for XML documents. * * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303 * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia */ XML_APPLICATION: 'application/xml', /** * `text/html`, an alias for `application/xml`. * * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303 * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia */ XML_TEXT: 'text/xml', /** * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace, * but is parsed as an XML document. * * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec * @see https://en.wikipedia.org/wiki/XHTML Wikipedia */ XML_XHTML_APPLICATION: 'application/xhtml+xml', /** * `image/svg+xml`, * * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1 * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia */ XML_SVG_IMAGE: 'image/svg+xml', }) /** * Namespaces that are used in this code base. * * @see http://www.w3.org/TR/REC-xml-names */ var NAMESPACE = freeze({ /** * The XHTML namespace. * * @see http://www.w3.org/1999/xhtml */ HTML: 'http://www.w3.org/1999/xhtml', /** * Checks if `uri` equals `NAMESPACE.HTML`. * * @param {string} [uri] * * @see NAMESPACE.HTML */ isHTML: function (uri) { return uri === NAMESPACE.HTML }, /** * The SVG namespace. * * @see http://www.w3.org/2000/svg */ SVG: 'http://www.w3.org/2000/svg', /** * The `xml:` namespace. * * @see http://www.w3.org/XML/1998/namespace */ XML: 'http://www.w3.org/XML/1998/namespace', /** * The `xmlns:` namespace * * @see https://www.w3.org/2000/xmlns/ */ XMLNS: 'http://www.w3.org/2000/xmlns/', }) exports.assign = assign; exports.find = find; exports.freeze = freeze; exports.MIME_TYPE = MIME_TYPE; exports.NAMESPACE = NAMESPACE; /***/ }), /***/ 129: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { var __webpack_unused_export__; var conventions = __webpack_require__(167); var dom = __webpack_require__(146) var entities = __webpack_require__(45); var sax = __webpack_require__(925); var DOMImplementation = dom.DOMImplementation; var NAMESPACE = conventions.NAMESPACE; var ParseError = sax.ParseError; var XMLReader = sax.XMLReader; /** * Normalizes line ending according to https://www.w3.org/TR/xml11/#sec-line-ends: * * > XML parsed entities are often stored in computer files which, * > for editing convenience, are organized into lines. * > These lines are typically separated by some combination * > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA). * > * > To simplify the tasks of applications, the XML processor must behave * > as if it normalized all line breaks in external parsed entities (including the document entity) * > on input, before parsing, by translating all of the following to a single #xA character: * > * > 1. the two-character sequence #xD #xA * > 2. the two-character sequence #xD #x85 * > 3. the single character #x85 * > 4. the single character #x2028 * > 5. any #xD character that is not immediately followed by #xA or #x85. * * @param {string} input * @returns {string} */ function normalizeLineEndings(input) { return input .replace(/\r[\n\u0085]/g, '\n') .replace(/[\r\u0085\u2028]/g, '\n') } /** * @typedef Locator * @property {number} [columnNumber] * @property {number} [lineNumber] */ /** * @typedef DOMParserOptions * @property {DOMHandler} [domBuilder] * @property {Function} [errorHandler] * @property {(string) => string} [normalizeLineEndings] used to replace line endings before parsing * defaults to `normalizeLineEndings` * @property {Locator} [locator] * @property {Record} [xmlns] * * @see normalizeLineEndings */ /** * The DOMParser interface provides the ability to parse XML or HTML source code * from a string into a DOM `Document`. * * _xmldom is different from the spec in that it allows an `options` parameter, * to override the default behavior._ * * @param {DOMParserOptions} [options] * @constructor * * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization */ function DOMParser(options){ this.options = options ||{locator:{}}; } DOMParser.prototype.parseFromString = function(source,mimeType){ var options = this.options; var sax = new XMLReader(); var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler var errorHandler = options.errorHandler; var locator = options.locator; var defaultNSMap = options.xmlns||{}; var isHTML = /\/x?html?$/.test(mimeType);//mimeType.toLowerCase().indexOf('html') > -1; var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES; if(locator){ domBuilder.setDocumentLocator(locator) } sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator); sax.domBuilder = options.domBuilder || domBuilder; if(isHTML){ defaultNSMap[''] = NAMESPACE.HTML; } defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML; var normalize = options.normalizeLineEndings || normalizeLineEndings; if (source && typeof source === 'string') { sax.parse( normalize(source), defaultNSMap, entityMap ) } else { sax.errorHandler.error('invalid doc source') } return domBuilder.doc; } function buildErrorHandler(errorImpl,domBuilder,locator){ if(!errorImpl){ if(domBuilder instanceof DOMHandler){ return domBuilder; } errorImpl = domBuilder ; } var errorHandler = {} var isCallback = errorImpl instanceof Function; locator = locator||{} function build(key){ var fn = errorImpl[key]; if(!fn && isCallback){ fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl; } errorHandler[key] = fn && function(msg){ fn('[xmldom '+key+']\t'+msg+_locator(locator)); }||function(){}; } build('warning'); build('error'); build('fatalError'); return errorHandler; } //console.log('#\n\n\n\n\n\n\n####') /** * +ContentHandler+ErrorHandler * +LexicalHandler+EntityResolver2 * -DeclHandler-DTDHandler * * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html */ function DOMHandler() { this.cdata = false; } function position(locator,node){ node.lineNumber = locator.lineNumber; node.columnNumber = locator.columnNumber; } /** * @see org.xml.sax.ContentHandler#startDocument * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html */ DOMHandler.prototype = { startDocument : function() { this.doc = new DOMImplementation().createDocument(null, null, null); if (this.locator) { this.doc.documentURI = this.locator.systemId; } }, startElement:function(namespaceURI, localName, qName, attrs) { var doc = this.doc; var el = doc.createElementNS(namespaceURI, qName||localName); var len = attrs.length; appendElement(this, el); this.currentElement = el; this.locator && position(this.locator,el) for (var i = 0 ; i < len; i++) { var namespaceURI = attrs.getURI(i); var value = attrs.getValue(i); var qName = attrs.getQName(i); var attr = doc.createAttributeNS(namespaceURI, qName); this.locator &&position(attrs.getLocator(i),attr); attr.value = attr.nodeValue = value; el.setAttributeNode(attr) } }, endElement:function(namespaceURI, localName, qName) { var current = this.currentElement var tagName = current.tagName; this.currentElement = current.parentNode; }, startPrefixMapping:function(prefix, uri) { }, endPrefixMapping:function(prefix) { }, processingInstruction:function(target, data) { var ins = this.doc.createProcessingInstruction(target, data); this.locator && position(this.locator,ins) appendElement(this, ins); }, ignorableWhitespace:function(ch, start, length) { }, characters:function(chars, start, length) { chars = _toString.apply(this,arguments) //console.log(chars) if(chars){ if (this.cdata) { var charNode = this.doc.createCDATASection(chars); } else { var charNode = this.doc.createTextNode(chars); } if(this.currentElement){ this.currentElement.appendChild(charNode); }else if(/^\s*$/.test(chars)){ this.doc.appendChild(charNode); //process xml } this.locator && position(this.locator,charNode) } }, skippedEntity:function(name) { }, endDocument:function() { this.doc.normalize(); }, setDocumentLocator:function (locator) { if(this.locator = locator){// && !('lineNumber' in locator)){ locator.lineNumber = 0; } }, //LexicalHandler comment:function(chars, start, length) { chars = _toString.apply(this,arguments) var comm = this.doc.createComment(chars); this.locator && position(this.locator,comm) appendElement(this, comm); }, startCDATA:function() { //used in characters() methods this.cdata = true; }, endCDATA:function() { this.cdata = false; }, startDTD:function(name, publicId, systemId) { var impl = this.doc.implementation; if (impl && impl.createDocumentType) { var dt = impl.createDocumentType(name, publicId, systemId); this.locator && position(this.locator,dt) appendElement(this, dt); this.doc.doctype = dt; } }, /** * @see org.xml.sax.ErrorHandler * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html */ warning:function(error) { console.warn('[xmldom warning]\t'+error,_locator(this.locator)); }, error:function(error) { console.error('[xmldom error]\t'+error,_locator(this.locator)); }, fatalError:function(error) { throw new ParseError(error, this.locator); } } function _locator(l){ if(l){ return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']' } } function _toString(chars,start,length){ if(typeof chars == 'string'){ return chars.substr(start,length) }else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") if(chars.length >= start+length || start){ return new java.lang.String(chars,start,length)+''; } return chars; } } /* * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html * used method of org.xml.sax.ext.LexicalHandler: * #comment(chars, start, length) * #startCDATA() * #endCDATA() * #startDTD(name, publicId, systemId) * * * IGNORED method of org.xml.sax.ext.LexicalHandler: * #endDTD() * #startEntity(name) * #endEntity(name) * * * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html * IGNORED method of org.xml.sax.ext.DeclHandler * #attributeDecl(eName, aName, type, mode, value) * #elementDecl(name, model) * #externalEntityDecl(name, publicId, systemId) * #internalEntityDecl(name, value) * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html * IGNORED method of org.xml.sax.EntityResolver2 * #resolveEntity(String name,String publicId,String baseURI,String systemId) * #resolveEntity(publicId, systemId) * #getExternalSubset(name, baseURI) * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html * IGNORED method of org.xml.sax.DTDHandler * #notationDecl(name, publicId, systemId) {}; * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; */ "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){ DOMHandler.prototype[key] = function(){return null} }) /* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */ function appendElement (hander,node) { if (!hander.currentElement) { hander.doc.appendChild(node); } else { hander.currentElement.appendChild(node); } }//appendChild and setAttributeNS are preformance key __webpack_unused_export__ = DOMHandler; __webpack_unused_export__ = normalizeLineEndings; exports.DOMParser = DOMParser; /***/ }), /***/ 146: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { var conventions = __webpack_require__(167); var find = conventions.find; var NAMESPACE = conventions.NAMESPACE; /** * A prerequisite for `[].filter`, to drop elements that are empty * @param {string} input * @returns {boolean} */ function notEmptyString (input) { return input !== '' } /** * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace * @see https://infra.spec.whatwg.org/#ascii-whitespace * * @param {string} input * @returns {string[]} (can be empty) */ function splitOnASCIIWhitespace(input) { // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : [] } /** * Adds element as a key to current if it is not already present. * * @param {Record} current * @param {string} element * @returns {Record} */ function orderedSetReducer (current, element) { if (!current.hasOwnProperty(element)) { current[element] = true; } return current; } /** * @see https://infra.spec.whatwg.org/#ordered-set * @param {string} input * @returns {string[]} */ function toOrderedSet(input) { if (!input) return []; var list = splitOnASCIIWhitespace(input); return Object.keys(list.reduce(orderedSetReducer, {})) } /** * Uses `list.indexOf` to implement something like `Array.prototype.includes`, * which we can not rely on being available. * * @param {any[]} list * @returns {function(any): boolean} */ function arrayIncludes (list) { return function(element) { return list && list.indexOf(element) !== -1; } } function copy(src,dest){ for(var p in src){ if (Object.prototype.hasOwnProperty.call(src, p)) { dest[p] = src[p]; } } } /** ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));? ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));? */ function _extends(Class,Super){ var pt = Class.prototype; if(!(pt instanceof Super)){ function t(){}; t.prototype = Super.prototype; t = new t(); copy(pt,t); Class.prototype = pt = t; } if(pt.constructor != Class){ if(typeof Class != 'function'){ console.error("unknown Class:"+Class) } pt.constructor = Class } } // Node Types var NodeType = {} var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; var TEXT_NODE = NodeType.TEXT_NODE = 3; var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; var ENTITY_NODE = NodeType.ENTITY_NODE = 6; var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; var COMMENT_NODE = NodeType.COMMENT_NODE = 8; var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; var NOTATION_NODE = NodeType.NOTATION_NODE = 12; // ExceptionCode var ExceptionCode = {} var ExceptionMessage = {}; var INDEX_SIZE_ERR = ExceptionCode.INDEX_SIZE_ERR = ((ExceptionMessage[1]="Index size error"),1); var DOMSTRING_SIZE_ERR = ExceptionCode.DOMSTRING_SIZE_ERR = ((ExceptionMessage[2]="DOMString size error"),2); var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = ((ExceptionMessage[3]="Hierarchy request error"),3); var WRONG_DOCUMENT_ERR = ExceptionCode.WRONG_DOCUMENT_ERR = ((ExceptionMessage[4]="Wrong document"),4); var INVALID_CHARACTER_ERR = ExceptionCode.INVALID_CHARACTER_ERR = ((ExceptionMessage[5]="Invalid character"),5); var NO_DATA_ALLOWED_ERR = ExceptionCode.NO_DATA_ALLOWED_ERR = ((ExceptionMessage[6]="No data allowed"),6); var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = ((ExceptionMessage[7]="No modification allowed"),7); var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = ((ExceptionMessage[8]="Not found"),8); var NOT_SUPPORTED_ERR = ExceptionCode.NOT_SUPPORTED_ERR = ((ExceptionMessage[9]="Not supported"),9); var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = ((ExceptionMessage[10]="Attribute in use"),10); //level2 var INVALID_STATE_ERR = ExceptionCode.INVALID_STATE_ERR = ((ExceptionMessage[11]="Invalid state"),11); var SYNTAX_ERR = ExceptionCode.SYNTAX_ERR = ((ExceptionMessage[12]="Syntax error"),12); var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = ((ExceptionMessage[13]="Invalid modification"),13); var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14]="Invalid namespace"),14); var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15]="Invalid access"),15); /** * DOM Level 2 * Object DOMException * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html */ function DOMException(code, message) { if(message instanceof Error){ var error = message; }else{ error = this; Error.call(this, ExceptionMessage[code]); this.message = ExceptionMessage[code]; if(Error.captureStackTrace) Error.captureStackTrace(this, DOMException); } error.code = code; if(message) this.message = this.message + ": " + message; return error; }; DOMException.prototype = Error.prototype; copy(ExceptionCode,DOMException) /** * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 * The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live. * The items in the NodeList are accessible via an integral index, starting from 0. */ function NodeList() { }; NodeList.prototype = { /** * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive. * @standard level1 */ length:0, /** * Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null. * @standard level1 * @param index unsigned long * Index into the collection. * @return Node * The node at the indexth position in the NodeList, or null if that is not a valid index. */ item: function(index) { return index >= 0 && index < this.length ? this[index] : null; }, toString:function(isHTML,nodeFilter){ for(var buf = [], i = 0;i=0){ var lastIndex = list.length-1 while(i0 || key == 'xmlns'){ // return null; // } //console.log() var i = this.length; while(i--){ var attr = this[i]; //console.log(attr.nodeName,key) if(attr.nodeName == key){ return attr; } } }, setNamedItem: function(attr) { var el = attr.ownerElement; if(el && el!=this._ownerElement){ throw new DOMException(INUSE_ATTRIBUTE_ERR); } var oldAttr = this.getNamedItem(attr.nodeName); _addNamedNode(this._ownerElement,this,attr,oldAttr); return oldAttr; }, /* returns Node */ setNamedItemNS: function(attr) {// raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR var el = attr.ownerElement, oldAttr; if(el && el!=this._ownerElement){ throw new DOMException(INUSE_ATTRIBUTE_ERR); } oldAttr = this.getNamedItemNS(attr.namespaceURI,attr.localName); _addNamedNode(this._ownerElement,this,attr,oldAttr); return oldAttr; }, /* returns Node */ removeNamedItem: function(key) { var attr = this.getNamedItem(key); _removeNamedNode(this._ownerElement,this,attr); return attr; },// raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR //for level2 removeNamedItemNS:function(namespaceURI,localName){ var attr = this.getNamedItemNS(namespaceURI,localName); _removeNamedNode(this._ownerElement,this,attr); return attr; }, getNamedItemNS: function(namespaceURI, localName) { var i = this.length; while(i--){ var node = this[i]; if(node.localName == localName && node.namespaceURI == namespaceURI){ return node; } } return null; } }; /** * The DOMImplementation interface represents an object providing methods * which are not dependent on any particular document. * Such an object is returned by the `Document.implementation` property. * * __The individual methods describe the differences compared to the specs.__ * * @constructor * * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial) * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard */ function DOMImplementation() { } DOMImplementation.prototype = { /** * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported. * The different implementations fairly diverged in what kind of features were reported. * The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use. * * @deprecated It is deprecated and modern browsers return true in all cases. * * @param {string} feature * @param {string} [version] * @returns {boolean} always true * * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard */ hasFeature: function(feature, version) { return true; }, /** * Creates an XML Document object of the specified type with its document element. * * __It behaves slightly different from the description in the living standard__: * - There is no interface/class `XMLDocument`, it returns a `Document` instance. * - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared. * - this implementation is not validating names or qualified names * (when parsing XML strings, the SAX parser takes care of that) * * @param {string|null} namespaceURI * @param {string} qualifiedName * @param {DocumentType=null} doctype * @returns {Document} * * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial) * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument DOM Level 2 Core * * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names */ createDocument: function(namespaceURI, qualifiedName, doctype){ var doc = new Document(); doc.implementation = this; doc.childNodes = new NodeList(); doc.doctype = doctype || null; if (doctype){ doc.appendChild(doctype); } if (qualifiedName){ var root = doc.createElementNS(namespaceURI, qualifiedName); doc.appendChild(root); } return doc; }, /** * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`. * * __This behavior is slightly different from the in the specs__: * - this implementation is not validating names or qualified names * (when parsing XML strings, the SAX parser takes care of that) * * @param {string} qualifiedName * @param {string} [publicId] * @param {string} [systemId] * @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation * or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()` * * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard * * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names */ createDocumentType: function(qualifiedName, publicId, systemId){ var node = new DocumentType(); node.name = qualifiedName; node.nodeName = qualifiedName; node.publicId = publicId || ''; node.systemId = systemId || ''; return node; } }; /** * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 */ function Node() { }; Node.prototype = { firstChild : null, lastChild : null, previousSibling : null, nextSibling : null, attributes : null, parentNode : null, childNodes : null, ownerDocument : null, nodeValue : null, namespaceURI : null, prefix : null, localName : null, // Modified in DOM Level 2: insertBefore:function(newChild, refChild){//raises return _insertBefore(this,newChild,refChild); }, replaceChild:function(newChild, oldChild){//raises _insertBefore(this, newChild,oldChild, assertPreReplacementValidityInDocument); if(oldChild){ this.removeChild(oldChild); } }, removeChild:function(oldChild){ return _removeChild(this,oldChild); }, appendChild:function(newChild){ return this.insertBefore(newChild,null); }, hasChildNodes:function(){ return this.firstChild != null; }, cloneNode:function(deep){ return cloneNode(this.ownerDocument||this,this,deep); }, // Modified in DOM Level 2: normalize:function(){ var child = this.firstChild; while(child){ var next = child.nextSibling; if(next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE){ this.removeChild(next); child.appendData(next.data); }else{ child.normalize(); child = next; } } }, // Introduced in DOM Level 2: isSupported:function(feature, version){ return this.ownerDocument.implementation.hasFeature(feature,version); }, // Introduced in DOM Level 2: hasAttributes:function(){ return this.attributes.length>0; }, /** * Look up the prefix associated to the given namespace URI, starting from this node. * **The default namespace declarations are ignored by this method.** * See Namespace Prefix Lookup for details on the algorithm used by this method. * * _Note: The implementation seems to be incomplete when compared to the algorithm described in the specs._ * * @param {string | null} namespaceURI * @returns {string | null} * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix * @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo * @see https://dom.spec.whatwg.org/#dom-node-lookupprefix * @see https://github.com/xmldom/xmldom/issues/322 */ lookupPrefix:function(namespaceURI){ var el = this; while(el){ var map = el._nsMap; //console.dir(map) if(map){ for(var n in map){ if (Object.prototype.hasOwnProperty.call(map, n) && map[n] === namespaceURI) { return n; } } } el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode; } return null; }, // Introduced in DOM Level 3: lookupNamespaceURI:function(prefix){ var el = this; while(el){ var map = el._nsMap; //console.dir(map) if(map){ if(Object.prototype.hasOwnProperty.call(map, prefix)){ return map[prefix] ; } } el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode; } return null; }, // Introduced in DOM Level 3: isDefaultNamespace:function(namespaceURI){ var prefix = this.lookupPrefix(namespaceURI); return prefix == null; } }; function _xmlEncoder(c){ return c == '<' && '<' || c == '>' && '>' || c == '&' && '&' || c == '"' && '"' || '&#'+c.charCodeAt()+';' } copy(NodeType,Node); copy(NodeType,Node.prototype); /** * @param callback return true for continue,false for break * @return boolean true: break visit; */ function _visitNode(node,callback){ if(callback(node)){ return true; } if(node = node.firstChild){ do{ if(_visitNode(node,callback)){return true} }while(node=node.nextSibling) } } function Document(){ this.ownerDocument = this; } function _onAddAttribute(doc,el,newAttr){ doc && doc._inc++; var ns = newAttr.namespaceURI ; if(ns === NAMESPACE.XMLNS){ //update namespace el._nsMap[newAttr.prefix?newAttr.localName:''] = newAttr.value } } function _onRemoveAttribute(doc,el,newAttr,remove){ doc && doc._inc++; var ns = newAttr.namespaceURI ; if(ns === NAMESPACE.XMLNS){ //update namespace delete el._nsMap[newAttr.prefix?newAttr.localName:''] } } /** * Updates `el.childNodes`, updating the indexed items and it's `length`. * Passing `newChild` means it will be appended. * Otherwise it's assumed that an item has been removed, * and `el.firstNode` and it's `.nextSibling` are used * to walk the current list of child nodes. * * @param {Document} doc * @param {Node} el * @param {Node} [newChild] * @private */ function _onUpdateChild (doc, el, newChild) { if(doc && doc._inc){ doc._inc++; //update childNodes var cs = el.childNodes; if (newChild) { cs[cs.length++] = newChild; } else { var child = el.firstChild; var i = 0; while (child) { cs[i++] = child; child = child.nextSibling; } cs.length = i; delete cs[cs.length]; } } } /** * Removes the connections between `parentNode` and `child` * and any existing `child.previousSibling` or `child.nextSibling`. * * @see https://github.com/xmldom/xmldom/issues/135 * @see https://github.com/xmldom/xmldom/issues/145 * * @param {Node} parentNode * @param {Node} child * @returns {Node} the child that was removed. * @private */ function _removeChild (parentNode, child) { var previous = child.previousSibling; var next = child.nextSibling; if (previous) { previous.nextSibling = next; } else { parentNode.firstChild = next; } if (next) { next.previousSibling = previous; } else { parentNode.lastChild = previous; } child.parentNode = null; child.previousSibling = null; child.nextSibling = null; _onUpdateChild(parentNode.ownerDocument, parentNode); return child; } /** * Returns `true` if `node` can be a parent for insertion. * @param {Node} node * @returns {boolean} */ function hasValidParentNodeType(node) { return ( node && (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE) ); } /** * Returns `true` if `node` can be inserted according to it's `nodeType`. * @param {Node} node * @returns {boolean} */ function hasInsertableNodeType(node) { return ( node && (isElementNode(node) || isTextNode(node) || isDocTypeNode(node) || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.COMMENT_NODE || node.nodeType === Node.PROCESSING_INSTRUCTION_NODE) ); } /** * Returns true if `node` is a DOCTYPE node * @param {Node} node * @returns {boolean} */ function isDocTypeNode(node) { return node && node.nodeType === Node.DOCUMENT_TYPE_NODE; } /** * Returns true if the node is an element * @param {Node} node * @returns {boolean} */ function isElementNode(node) { return node && node.nodeType === Node.ELEMENT_NODE; } /** * Returns true if `node` is a text node * @param {Node} node * @returns {boolean} */ function isTextNode(node) { return node && node.nodeType === Node.TEXT_NODE; } /** * Check if en element node can be inserted before `child`, or at the end if child is falsy, * according to the presence and position of a doctype node on the same level. * * @param {Document} doc The document node * @param {Node} child the node that would become the nextSibling if the element would be inserted * @returns {boolean} `true` if an element can be inserted before child * @private * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity */ function isElementInsertionPossible(doc, child) { var parentChildNodes = doc.childNodes || []; if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) { return false; } var docTypeNode = find(parentChildNodes, isDocTypeNode); return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); } /** * Check if en element node can be inserted before `child`, or at the end if child is falsy, * according to the presence and position of a doctype node on the same level. * * @param {Node} doc The document node * @param {Node} child the node that would become the nextSibling if the element would be inserted * @returns {boolean} `true` if an element can be inserted before child * @private * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity */ function isElementReplacementPossible(doc, child) { var parentChildNodes = doc.childNodes || []; function hasElementChildThatIsNotChild(node) { return isElementNode(node) && node !== child; } if (find(parentChildNodes, hasElementChildThatIsNotChild)) { return false; } var docTypeNode = find(parentChildNodes, isDocTypeNode); return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); } /** * @private * Steps 1-5 of the checks before inserting and before replacing a child are the same. * * @param {Node} parent the parent node to insert `node` into * @param {Node} node the node to insert * @param {Node=} child the node that should become the `nextSibling` of `node` * @returns {Node} * @throws DOMException for several node combinations that would create a DOM that is not well-formed. * @throws DOMException if `child` is provided but is not a child of `parent`. * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity * @see https://dom.spec.whatwg.org/#concept-node-replace */ function assertPreInsertionValidity1to5(parent, node, child) { // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException. if (!hasValidParentNodeType(parent)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType); } // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException. // not implemented! // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException. if (child && child.parentNode !== parent) { throw new DOMException(NOT_FOUND_ERR, 'child not in parent'); } if ( // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. !hasInsertableNodeType(node) || // 5. If either `node` is a Text node and `parent` is a document, // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0 // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE) // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException. (isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE) ) { throw new DOMException( HIERARCHY_REQUEST_ERR, 'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType ); } } /** * @private * Step 6 of the checks before inserting and before replacing a child are different. * * @param {Document} parent the parent node to insert `node` into * @param {Node} node the node to insert * @param {Node | undefined} child the node that should become the `nextSibling` of `node` * @returns {Node} * @throws DOMException for several node combinations that would create a DOM that is not well-formed. * @throws DOMException if `child` is provided but is not a child of `parent`. * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity * @see https://dom.spec.whatwg.org/#concept-node-replace */ function assertPreInsertionValidityInDocument(parent, node, child) { var parentChildNodes = parent.childNodes || []; var nodeChildNodes = node.childNodes || []; // DocumentFragment if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { var nodeChildElements = nodeChildNodes.filter(isElementNode); // If node has more than one element child or has a Text node child. if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); } // Otherwise, if `node` has one element child and either `parent` has an element child, // `child` is a doctype, or `child` is non-null and a doctype is following `child`. if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype'); } } // Element if (isElementNode(node)) { // `parent` has an element child, `child` is a doctype, // or `child` is non-null and a doctype is following `child`. if (!isElementInsertionPossible(parent, child)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); } } // DocumentType if (isDocTypeNode(node)) { // `parent` has a doctype child, if (find(parentChildNodes, isDocTypeNode)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); } var parentElementChild = find(parentChildNodes, isElementNode); // `child` is non-null and an element is preceding `child`, if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); } // or `child` is null and `parent` has an element child. if (!child && parentElementChild) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present'); } } } /** * @private * Step 6 of the checks before inserting and before replacing a child are different. * * @param {Document} parent the parent node to insert `node` into * @param {Node} node the node to insert * @param {Node | undefined} child the node that should become the `nextSibling` of `node` * @returns {Node} * @throws DOMException for several node combinations that would create a DOM that is not well-formed. * @throws DOMException if `child` is provided but is not a child of `parent`. * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity * @see https://dom.spec.whatwg.org/#concept-node-replace */ function assertPreReplacementValidityInDocument(parent, node, child) { var parentChildNodes = parent.childNodes || []; var nodeChildNodes = node.childNodes || []; // DocumentFragment if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { var nodeChildElements = nodeChildNodes.filter(isElementNode); // If `node` has more than one element child or has a Text node child. if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); } // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`. if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype'); } } // Element if (isElementNode(node)) { // `parent` has an element child that is not `child` or a doctype is following `child`. if (!isElementReplacementPossible(parent, child)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); } } // DocumentType if (isDocTypeNode(node)) { function hasDoctypeChildThatIsNotChild(node) { return isDocTypeNode(node) && node !== child; } // `parent` has a doctype child that is not `child`, if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); } var parentElementChild = find(parentChildNodes, isElementNode); // or an element is preceding `child`. if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); } } } /** * @private * @param {Node} parent the parent node to insert `node` into * @param {Node} node the node to insert * @param {Node=} child the node that should become the `nextSibling` of `node` * @returns {Node} * @throws DOMException for several node combinations that would create a DOM that is not well-formed. * @throws DOMException if `child` is provided but is not a child of `parent`. * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity */ function _insertBefore(parent, node, child, _inDocumentAssertion) { // To ensure pre-insertion validity of a node into a parent before a child, run these steps: assertPreInsertionValidity1to5(parent, node, child); // If parent is a document, and any of the statements below, switched on the interface node implements, // are true, then throw a "HierarchyRequestError" DOMException. if (parent.nodeType === Node.DOCUMENT_NODE) { (_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child); } var cp = node.parentNode; if(cp){ cp.removeChild(node);//remove and update } if(node.nodeType === DOCUMENT_FRAGMENT_NODE){ var newFirst = node.firstChild; if (newFirst == null) { return node; } var newLast = node.lastChild; }else{ newFirst = newLast = node; } var pre = child ? child.previousSibling : parent.lastChild; newFirst.previousSibling = pre; newLast.nextSibling = child; if(pre){ pre.nextSibling = newFirst; }else{ parent.firstChild = newFirst; } if(child == null){ parent.lastChild = newLast; }else{ child.previousSibling = newLast; } do{ newFirst.parentNode = parent; // Update ownerDocument for each node being inserted var targetDoc = parent.ownerDocument || parent; _updateOwnerDocument(newFirst, targetDoc); }while(newFirst !== newLast && (newFirst= newFirst.nextSibling)) _onUpdateChild(parent.ownerDocument||parent, parent); //console.log(parent.lastChild.nextSibling == null) if (node.nodeType == DOCUMENT_FRAGMENT_NODE) { node.firstChild = node.lastChild = null; } return node; } /** * Recursively updates the ownerDocument property for a node and all its descendants * @param {Node} node * @param {Document} newOwnerDocument * @private */ function _updateOwnerDocument(node, newOwnerDocument) { if (node.ownerDocument === newOwnerDocument) { return; } node.ownerDocument = newOwnerDocument; // Update attributes if this is an element if (node.nodeType === ELEMENT_NODE && node.attributes) { for (var i = 0; i < node.attributes.length; i++) { var attr = node.attributes.item(i); if (attr) { attr.ownerDocument = newOwnerDocument; } } } // Recursively update child nodes var child = node.firstChild; while (child) { _updateOwnerDocument(child, newOwnerDocument); child = child.nextSibling; } } /** * Appends `newChild` to `parentNode`. * If `newChild` is already connected to a `parentNode` it is first removed from it. * * @see https://github.com/xmldom/xmldom/issues/135 * @see https://github.com/xmldom/xmldom/issues/145 * @param {Node} parentNode * @param {Node} newChild * @returns {Node} * @private */ function _appendSingleChild (parentNode, newChild) { if (newChild.parentNode) { newChild.parentNode.removeChild(newChild); } newChild.parentNode = parentNode; newChild.previousSibling = parentNode.lastChild; newChild.nextSibling = null; if (newChild.previousSibling) { newChild.previousSibling.nextSibling = newChild; } else { parentNode.firstChild = newChild; } parentNode.lastChild = newChild; _onUpdateChild(parentNode.ownerDocument, parentNode, newChild); // Update ownerDocument for the new child and all its descendants var targetDoc = parentNode.ownerDocument || parentNode; _updateOwnerDocument(newChild, targetDoc); return newChild; } Document.prototype = { //implementation : null, nodeName : '#document', nodeType : DOCUMENT_NODE, /** * The DocumentType node of the document. * * @readonly * @type DocumentType */ doctype : null, documentElement : null, _inc : 1, insertBefore : function(newChild, refChild){//raises if(newChild.nodeType == DOCUMENT_FRAGMENT_NODE){ var child = newChild.firstChild; while(child){ var next = child.nextSibling; this.insertBefore(child,refChild); child = next; } return newChild; } _insertBefore(this, newChild, refChild); _updateOwnerDocument(newChild, this); if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) { this.documentElement = newChild; } return newChild; }, removeChild : function(oldChild){ if(this.documentElement == oldChild){ this.documentElement = null; } return _removeChild(this,oldChild); }, replaceChild: function (newChild, oldChild) { //raises _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument); _updateOwnerDocument(newChild, this); if (oldChild) { this.removeChild(oldChild); } if (isElementNode(newChild)) { this.documentElement = newChild; } }, // Introduced in DOM Level 2: importNode : function(importedNode,deep){ return importNode(this,importedNode,deep); }, // Introduced in DOM Level 2: getElementById : function(id){ var rtv = null; _visitNode(this.documentElement,function(node){ if(node.nodeType == ELEMENT_NODE){ if(node.getAttribute('id') == id){ rtv = node; return true; } } }) return rtv; }, /** * The `getElementsByClassName` method of `Document` interface returns an array-like object * of all child elements which have **all** of the given class name(s). * * Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters. * * * Warning: This is a live LiveNodeList. * Changes in the DOM will reflect in the array as the changes occur. * If an element selected by this array no longer qualifies for the selector, * it will automatically be removed. Be aware of this for iteration purposes. * * @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace * * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname */ getElementsByClassName: function(classNames) { var classNamesSet = toOrderedSet(classNames) return new LiveNodeList(this, function(base) { var ls = []; if (classNamesSet.length > 0) { _visitNode(base.documentElement, function(node) { if(node !== base && node.nodeType === ELEMENT_NODE) { var nodeClassNames = node.getAttribute('class') // can be null if the attribute does not exist if (nodeClassNames) { // before splitting and iterating just compare them for the most common case var matches = classNames === nodeClassNames; if (!matches) { var nodeClassNamesSet = toOrderedSet(nodeClassNames) matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet)) } if(matches) { ls.push(node); } } } }); } return ls; }); }, //document factory method: createElement : function(tagName){ var node = new Element(); node.ownerDocument = this; node.nodeName = tagName; node.tagName = tagName; node.localName = tagName; node.childNodes = new NodeList(); var attrs = node.attributes = new NamedNodeMap(); attrs._ownerElement = node; return node; }, createDocumentFragment : function(){ var node = new DocumentFragment(); node.ownerDocument = this; node.childNodes = new NodeList(); return node; }, createTextNode : function(data){ var node = new Text(); node.ownerDocument = this; node.appendData(data) return node; }, createComment : function(data){ var node = new Comment(); node.ownerDocument = this; node.appendData(data) return node; }, createCDATASection : function(data){ var node = new CDATASection(); node.ownerDocument = this; node.appendData(data) return node; }, createProcessingInstruction : function(target,data){ var node = new ProcessingInstruction(); node.ownerDocument = this; node.tagName = node.nodeName = node.target = target; node.nodeValue = node.data = data; return node; }, createAttribute : function(name){ var node = new Attr(); node.ownerDocument = this; node.name = name; node.nodeName = name; node.localName = name; node.specified = true; return node; }, createEntityReference : function(name){ var node = new EntityReference(); node.ownerDocument = this; node.nodeName = name; return node; }, // Introduced in DOM Level 2: createElementNS : function(namespaceURI,qualifiedName){ var node = new Element(); var pl = qualifiedName.split(':'); var attrs = node.attributes = new NamedNodeMap(); node.childNodes = new NodeList(); node.ownerDocument = this; node.nodeName = qualifiedName; node.tagName = qualifiedName; node.namespaceURI = namespaceURI; if(pl.length == 2){ node.prefix = pl[0]; node.localName = pl[1]; }else{ //el.prefix = null; node.localName = qualifiedName; } attrs._ownerElement = node; return node; }, // Introduced in DOM Level 2: createAttributeNS : function(namespaceURI,qualifiedName){ var node = new Attr(); var pl = qualifiedName.split(':'); node.ownerDocument = this; node.nodeName = qualifiedName; node.name = qualifiedName; node.namespaceURI = namespaceURI; node.specified = true; if(pl.length == 2){ node.prefix = pl[0]; node.localName = pl[1]; }else{ //el.prefix = null; node.localName = qualifiedName; } return node; } }; _extends(Document,Node); function Element() { this._nsMap = {}; }; Element.prototype = { nodeType : ELEMENT_NODE, hasAttribute : function(name){ return this.getAttributeNode(name)!=null; }, getAttribute : function(name){ var attr = this.getAttributeNode(name); return attr && attr.value || ''; }, getAttributeNode : function(name){ return this.attributes.getNamedItem(name); }, setAttribute : function(name, value){ var attr = this.ownerDocument.createAttribute(name); attr.value = attr.nodeValue = "" + value; this.setAttributeNode(attr) }, removeAttribute : function(name){ var attr = this.getAttributeNode(name) attr && this.removeAttributeNode(attr); }, //four real opeartion method appendChild:function(newChild){ if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){ return this.insertBefore(newChild,null); }else{ return _appendSingleChild(this,newChild); } }, setAttributeNode : function(newAttr){ return this.attributes.setNamedItem(newAttr); }, setAttributeNodeNS : function(newAttr){ return this.attributes.setNamedItemNS(newAttr); }, removeAttributeNode : function(oldAttr){ //console.log(this == oldAttr.ownerElement) return this.attributes.removeNamedItem(oldAttr.nodeName); }, //get real attribute name,and remove it by removeAttributeNode removeAttributeNS : function(namespaceURI, localName){ var old = this.getAttributeNodeNS(namespaceURI, localName); old && this.removeAttributeNode(old); }, hasAttributeNS : function(namespaceURI, localName){ return this.getAttributeNodeNS(namespaceURI, localName)!=null; }, getAttributeNS : function(namespaceURI, localName){ var attr = this.getAttributeNodeNS(namespaceURI, localName); return attr && attr.value || ''; }, setAttributeNS : function(namespaceURI, qualifiedName, value){ var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); attr.value = attr.nodeValue = "" + value; this.setAttributeNode(attr) }, getAttributeNodeNS : function(namespaceURI, localName){ return this.attributes.getNamedItemNS(namespaceURI, localName); }, getElementsByTagName : function(tagName){ return new LiveNodeList(this,function(base){ var ls = []; _visitNode(base,function(node){ if(node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)){ ls.push(node); } }); return ls; }); }, getElementsByTagNameNS : function(namespaceURI, localName){ return new LiveNodeList(this,function(base){ var ls = []; _visitNode(base,function(node){ if(node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)){ ls.push(node); } }); return ls; }); } }; Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; _extends(Element,Node); function Attr() { }; Attr.prototype.nodeType = ATTRIBUTE_NODE; _extends(Attr,Node); function CharacterData() { }; CharacterData.prototype = { data : '', substringData : function(offset, count) { return this.data.substring(offset, offset+count); }, appendData: function(text) { text = this.data+text; this.nodeValue = this.data = text; this.length = text.length; }, insertData: function(offset,text) { this.replaceData(offset,0,text); }, appendChild:function(newChild){ throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]) }, deleteData: function(offset, count) { this.replaceData(offset,count,""); }, replaceData: function(offset, count, text) { var start = this.data.substring(0,offset); var end = this.data.substring(offset+count); text = start + text + end; this.nodeValue = this.data = text; this.length = text.length; } } _extends(CharacterData,Node); function Text() { }; Text.prototype = { nodeName : "#text", nodeType : TEXT_NODE, splitText : function(offset) { var text = this.data; var newText = text.substring(offset); text = text.substring(0, offset); this.data = this.nodeValue = text; this.length = text.length; var newNode = this.ownerDocument.createTextNode(newText); if(this.parentNode){ this.parentNode.insertBefore(newNode, this.nextSibling); } return newNode; } } _extends(Text,CharacterData); function Comment() { }; Comment.prototype = { nodeName : "#comment", nodeType : COMMENT_NODE } _extends(Comment,CharacterData); function CDATASection() { }; CDATASection.prototype = { nodeName : "#cdata-section", nodeType : CDATA_SECTION_NODE } _extends(CDATASection,CharacterData); function DocumentType() { }; DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; _extends(DocumentType,Node); function Notation() { }; Notation.prototype.nodeType = NOTATION_NODE; _extends(Notation,Node); function Entity() { }; Entity.prototype.nodeType = ENTITY_NODE; _extends(Entity,Node); function EntityReference() { }; EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; _extends(EntityReference,Node); function DocumentFragment() { }; DocumentFragment.prototype.nodeName = "#document-fragment"; DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; _extends(DocumentFragment,Node); function ProcessingInstruction() { } ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; _extends(ProcessingInstruction,Node); function XMLSerializer(){} XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter){ return nodeSerializeToString.call(node,isHtml,nodeFilter); } Node.prototype.toString = nodeSerializeToString; function nodeSerializeToString(isHtml,nodeFilter){ var buf = []; var refNode = this.nodeType == 9 && this.documentElement || this; var prefix = refNode.prefix; var uri = refNode.namespaceURI; if(uri && prefix == null){ //console.log(prefix) var prefix = refNode.lookupPrefix(uri); if(prefix == null){ //isHTML = true; var visibleNamespaces=[ {namespace:uri,prefix:null} //{namespace:uri,prefix:''} ] } } serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces); //console.log('###',this.nodeType,uri,prefix,buf.join('')) return buf.join(''); } function needNamespaceDefine(node, isHTML, visibleNamespaces) { var prefix = node.prefix || ''; var uri = node.namespaceURI; // According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) , // and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl : // > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty. // in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using) // and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared : // > [...] Furthermore, the attribute value [...] must not be an empty string. // so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document. if (!uri) { return false; } if (prefix === "xml" && uri === NAMESPACE.XML || uri === NAMESPACE.XMLNS) { return false; } var i = visibleNamespaces.length while (i--) { var ns = visibleNamespaces[i]; // get namespace prefix if (ns.prefix === prefix) { return ns.namespace !== uri; } } return true; } /** * Well-formed constraint: No < in Attribute Values * > The replacement text of any entity referred to directly or indirectly * > in an attribute value must not contain a <. * @see https://www.w3.org/TR/xml11/#CleanAttrVals * @see https://www.w3.org/TR/xml11/#NT-AttValue * * Literal whitespace other than space that appear in attribute values * are serialized as their entity references, so they will be preserved. * (In contrast to whitespace literals in the input which are normalized to spaces) * @see https://www.w3.org/TR/xml11/#AVNormalize * @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes */ function addSerializedAttribute(buf, qualifiedName, value) { buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"') } function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){ if (!visibleNamespaces) { visibleNamespaces = []; } if(nodeFilter){ node = nodeFilter(node); if(node){ if(typeof node == 'string'){ buf.push(node); return; } }else{ return; } //buf.sort.apply(attrs, attributeSorter); } switch(node.nodeType){ case ELEMENT_NODE: var attrs = node.attributes; var len = attrs.length; var child = node.firstChild; var nodeName = node.tagName; isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML var prefixedNodeName = nodeName if (!isHTML && !node.prefix && node.namespaceURI) { var defaultNS // lookup current default ns from `xmlns` attribute for (var ai = 0; ai < attrs.length; ai++) { if (attrs.item(ai).name === 'xmlns') { defaultNS = attrs.item(ai).value break } } if (!defaultNS) { // lookup current default ns in visibleNamespaces for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) { var namespace = visibleNamespaces[nsi] if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) { defaultNS = namespace.namespace break } } } if (defaultNS !== node.namespaceURI) { for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) { var namespace = visibleNamespaces[nsi] if (namespace.namespace === node.namespaceURI) { if (namespace.prefix) { prefixedNodeName = namespace.prefix + ':' + nodeName } break } } } } buf.push('<', prefixedNodeName); for(var i=0;i'); //if is cdata child node if(isHTML && /^script$/i.test(nodeName)){ while(child){ if(child.data){ buf.push(child.data); }else{ serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); } child = child.nextSibling; } }else { while(child){ serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); child = child.nextSibling; } } buf.push(''); }else{ buf.push('/>'); } // remove added visible namespaces //visibleNamespaces.length = startVisibleNamespaces; return; case DOCUMENT_NODE: case DOCUMENT_FRAGMENT_NODE: var child = node.firstChild; while(child){ serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); child = child.nextSibling; } return; case ATTRIBUTE_NODE: return addSerializedAttribute(buf, node.name, node.value); case TEXT_NODE: /** * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form, * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section. * If they are needed elsewhere, they must be escaped using either numeric character references or the strings * `&` and `<` respectively. * The right angle bracket (>) may be represented using the string " > ", and must, for compatibility, * be escaped using either `>` or a character reference when it appears in the string `]]>` in content, * when that string is not marking the end of a CDATA section. * * In the content of elements, character data is any string of characters * which does not contain the start-delimiter of any markup * and does not include the CDATA-section-close delimiter, `]]>`. * * @see https://www.w3.org/TR/xml/#NT-CharData * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node */ return buf.push(node.data .replace(/[<&>]/g,_xmlEncoder) ); case CDATA_SECTION_NODE: return buf.push( ''); case COMMENT_NODE: return buf.push( ""); case DOCUMENT_TYPE_NODE: var pubid = node.publicId; var sysid = node.systemId; buf.push(''); }else if(sysid && sysid!='.'){ buf.push(' SYSTEM ', sysid, '>'); }else{ var sub = node.internalSubset; if(sub){ buf.push(" [",sub,"]"); } buf.push(">"); } return; case PROCESSING_INSTRUCTION_NODE: return buf.push( ""); case ENTITY_REFERENCE_NODE: return buf.push( '&',node.nodeName,';'); //case ENTITY_NODE: //case NOTATION_NODE: default: buf.push('??',node.nodeName); } } function importNode(doc,node,deep){ var node2; switch (node.nodeType) { case ELEMENT_NODE: node2 = node.cloneNode(false); node2.ownerDocument = doc; //var attrs = node2.attributes; //var len = attrs.length; //for(var i=0;i { "use strict"; var freeze = (__webpack_require__(167).freeze); /** * The entities that are predefined in every XML document. * * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1 * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0 * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia */ exports.XML_ENTITIES = freeze({ amp: '&', apos: "'", gt: '>', lt: '<', quot: '"', }); /** * A map of all entities that are detected in an HTML document. * They contain all entries from `XML_ENTITIES`. * * @see XML_ENTITIES * @see DOMParser.parseFromString * @see DOMImplementation.prototype.createHTMLDocument * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec * @see https://html.spec.whatwg.org/entities.json JSON * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML) * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML) */ exports.HTML_ENTITIES = freeze({ Aacute: '\u00C1', aacute: '\u00E1', Abreve: '\u0102', abreve: '\u0103', ac: '\u223E', acd: '\u223F', acE: '\u223E\u0333', Acirc: '\u00C2', acirc: '\u00E2', acute: '\u00B4', Acy: '\u0410', acy: '\u0430', AElig: '\u00C6', aelig: '\u00E6', af: '\u2061', Afr: '\uD835\uDD04', afr: '\uD835\uDD1E', Agrave: '\u00C0', agrave: '\u00E0', alefsym: '\u2135', aleph: '\u2135', Alpha: '\u0391', alpha: '\u03B1', Amacr: '\u0100', amacr: '\u0101', amalg: '\u2A3F', AMP: '\u0026', amp: '\u0026', And: '\u2A53', and: '\u2227', andand: '\u2A55', andd: '\u2A5C', andslope: '\u2A58', andv: '\u2A5A', ang: '\u2220', ange: '\u29A4', angle: '\u2220', angmsd: '\u2221', angmsdaa: '\u29A8', angmsdab: '\u29A9', angmsdac: '\u29AA', angmsdad: '\u29AB', angmsdae: '\u29AC', angmsdaf: '\u29AD', angmsdag: '\u29AE', angmsdah: '\u29AF', angrt: '\u221F', angrtvb: '\u22BE', angrtvbd: '\u299D', angsph: '\u2222', angst: '\u00C5', angzarr: '\u237C', Aogon: '\u0104', aogon: '\u0105', Aopf: '\uD835\uDD38', aopf: '\uD835\uDD52', ap: '\u2248', apacir: '\u2A6F', apE: '\u2A70', ape: '\u224A', apid: '\u224B', apos: '\u0027', ApplyFunction: '\u2061', approx: '\u2248', approxeq: '\u224A', Aring: '\u00C5', aring: '\u00E5', Ascr: '\uD835\uDC9C', ascr: '\uD835\uDCB6', Assign: '\u2254', ast: '\u002A', asymp: '\u2248', asympeq: '\u224D', Atilde: '\u00C3', atilde: '\u00E3', Auml: '\u00C4', auml: '\u00E4', awconint: '\u2233', awint: '\u2A11', backcong: '\u224C', backepsilon: '\u03F6', backprime: '\u2035', backsim: '\u223D', backsimeq: '\u22CD', Backslash: '\u2216', Barv: '\u2AE7', barvee: '\u22BD', Barwed: '\u2306', barwed: '\u2305', barwedge: '\u2305', bbrk: '\u23B5', bbrktbrk: '\u23B6', bcong: '\u224C', Bcy: '\u0411', bcy: '\u0431', bdquo: '\u201E', becaus: '\u2235', Because: '\u2235', because: '\u2235', bemptyv: '\u29B0', bepsi: '\u03F6', bernou: '\u212C', Bernoullis: '\u212C', Beta: '\u0392', beta: '\u03B2', beth: '\u2136', between: '\u226C', Bfr: '\uD835\uDD05', bfr: '\uD835\uDD1F', bigcap: '\u22C2', bigcirc: '\u25EF', bigcup: '\u22C3', bigodot: '\u2A00', bigoplus: '\u2A01', bigotimes: '\u2A02', bigsqcup: '\u2A06', bigstar: '\u2605', bigtriangledown: '\u25BD', bigtriangleup: '\u25B3', biguplus: '\u2A04', bigvee: '\u22C1', bigwedge: '\u22C0', bkarow: '\u290D', blacklozenge: '\u29EB', blacksquare: '\u25AA', blacktriangle: '\u25B4', blacktriangledown: '\u25BE', blacktriangleleft: '\u25C2', blacktriangleright: '\u25B8', blank: '\u2423', blk12: '\u2592', blk14: '\u2591', blk34: '\u2593', block: '\u2588', bne: '\u003D\u20E5', bnequiv: '\u2261\u20E5', bNot: '\u2AED', bnot: '\u2310', Bopf: '\uD835\uDD39', bopf: '\uD835\uDD53', bot: '\u22A5', bottom: '\u22A5', bowtie: '\u22C8', boxbox: '\u29C9', boxDL: '\u2557', boxDl: '\u2556', boxdL: '\u2555', boxdl: '\u2510', boxDR: '\u2554', boxDr: '\u2553', boxdR: '\u2552', boxdr: '\u250C', boxH: '\u2550', boxh: '\u2500', boxHD: '\u2566', boxHd: '\u2564', boxhD: '\u2565', boxhd: '\u252C', boxHU: '\u2569', boxHu: '\u2567', boxhU: '\u2568', boxhu: '\u2534', boxminus: '\u229F', boxplus: '\u229E', boxtimes: '\u22A0', boxUL: '\u255D', boxUl: '\u255C', boxuL: '\u255B', boxul: '\u2518', boxUR: '\u255A', boxUr: '\u2559', boxuR: '\u2558', boxur: '\u2514', boxV: '\u2551', boxv: '\u2502', boxVH: '\u256C', boxVh: '\u256B', boxvH: '\u256A', boxvh: '\u253C', boxVL: '\u2563', boxVl: '\u2562', boxvL: '\u2561', boxvl: '\u2524', boxVR: '\u2560', boxVr: '\u255F', boxvR: '\u255E', boxvr: '\u251C', bprime: '\u2035', Breve: '\u02D8', breve: '\u02D8', brvbar: '\u00A6', Bscr: '\u212C', bscr: '\uD835\uDCB7', bsemi: '\u204F', bsim: '\u223D', bsime: '\u22CD', bsol: '\u005C', bsolb: '\u29C5', bsolhsub: '\u27C8', bull: '\u2022', bullet: '\u2022', bump: '\u224E', bumpE: '\u2AAE', bumpe: '\u224F', Bumpeq: '\u224E', bumpeq: '\u224F', Cacute: '\u0106', cacute: '\u0107', Cap: '\u22D2', cap: '\u2229', capand: '\u2A44', capbrcup: '\u2A49', capcap: '\u2A4B', capcup: '\u2A47', capdot: '\u2A40', CapitalDifferentialD: '\u2145', caps: '\u2229\uFE00', caret: '\u2041', caron: '\u02C7', Cayleys: '\u212D', ccaps: '\u2A4D', Ccaron: '\u010C', ccaron: '\u010D', Ccedil: '\u00C7', ccedil: '\u00E7', Ccirc: '\u0108', ccirc: '\u0109', Cconint: '\u2230', ccups: '\u2A4C', ccupssm: '\u2A50', Cdot: '\u010A', cdot: '\u010B', cedil: '\u00B8', Cedilla: '\u00B8', cemptyv: '\u29B2', cent: '\u00A2', CenterDot: '\u00B7', centerdot: '\u00B7', Cfr: '\u212D', cfr: '\uD835\uDD20', CHcy: '\u0427', chcy: '\u0447', check: '\u2713', checkmark: '\u2713', Chi: '\u03A7', chi: '\u03C7', cir: '\u25CB', circ: '\u02C6', circeq: '\u2257', circlearrowleft: '\u21BA', circlearrowright: '\u21BB', circledast: '\u229B', circledcirc: '\u229A', circleddash: '\u229D', CircleDot: '\u2299', circledR: '\u00AE', circledS: '\u24C8', CircleMinus: '\u2296', CirclePlus: '\u2295', CircleTimes: '\u2297', cirE: '\u29C3', cire: '\u2257', cirfnint: '\u2A10', cirmid: '\u2AEF', cirscir: '\u29C2', ClockwiseContourIntegral: '\u2232', CloseCurlyDoubleQuote: '\u201D', CloseCurlyQuote: '\u2019', clubs: '\u2663', clubsuit: '\u2663', Colon: '\u2237', colon: '\u003A', Colone: '\u2A74', colone: '\u2254', coloneq: '\u2254', comma: '\u002C', commat: '\u0040', comp: '\u2201', compfn: '\u2218', complement: '\u2201', complexes: '\u2102', cong: '\u2245', congdot: '\u2A6D', Congruent: '\u2261', Conint: '\u222F', conint: '\u222E', ContourIntegral: '\u222E', Copf: '\u2102', copf: '\uD835\uDD54', coprod: '\u2210', Coproduct: '\u2210', COPY: '\u00A9', copy: '\u00A9', copysr: '\u2117', CounterClockwiseContourIntegral: '\u2233', crarr: '\u21B5', Cross: '\u2A2F', cross: '\u2717', Cscr: '\uD835\uDC9E', cscr: '\uD835\uDCB8', csub: '\u2ACF', csube: '\u2AD1', csup: '\u2AD0', csupe: '\u2AD2', ctdot: '\u22EF', cudarrl: '\u2938', cudarrr: '\u2935', cuepr: '\u22DE', cuesc: '\u22DF', cularr: '\u21B6', cularrp: '\u293D', Cup: '\u22D3', cup: '\u222A', cupbrcap: '\u2A48', CupCap: '\u224D', cupcap: '\u2A46', cupcup: '\u2A4A', cupdot: '\u228D', cupor: '\u2A45', cups: '\u222A\uFE00', curarr: '\u21B7', curarrm: '\u293C', curlyeqprec: '\u22DE', curlyeqsucc: '\u22DF', curlyvee: '\u22CE', curlywedge: '\u22CF', curren: '\u00A4', curvearrowleft: '\u21B6', curvearrowright: '\u21B7', cuvee: '\u22CE', cuwed: '\u22CF', cwconint: '\u2232', cwint: '\u2231', cylcty: '\u232D', Dagger: '\u2021', dagger: '\u2020', daleth: '\u2138', Darr: '\u21A1', dArr: '\u21D3', darr: '\u2193', dash: '\u2010', Dashv: '\u2AE4', dashv: '\u22A3', dbkarow: '\u290F', dblac: '\u02DD', Dcaron: '\u010E', dcaron: '\u010F', Dcy: '\u0414', dcy: '\u0434', DD: '\u2145', dd: '\u2146', ddagger: '\u2021', ddarr: '\u21CA', DDotrahd: '\u2911', ddotseq: '\u2A77', deg: '\u00B0', Del: '\u2207', Delta: '\u0394', delta: '\u03B4', demptyv: '\u29B1', dfisht: '\u297F', Dfr: '\uD835\uDD07', dfr: '\uD835\uDD21', dHar: '\u2965', dharl: '\u21C3', dharr: '\u21C2', DiacriticalAcute: '\u00B4', DiacriticalDot: '\u02D9', DiacriticalDoubleAcute: '\u02DD', DiacriticalGrave: '\u0060', DiacriticalTilde: '\u02DC', diam: '\u22C4', Diamond: '\u22C4', diamond: '\u22C4', diamondsuit: '\u2666', diams: '\u2666', die: '\u00A8', DifferentialD: '\u2146', digamma: '\u03DD', disin: '\u22F2', div: '\u00F7', divide: '\u00F7', divideontimes: '\u22C7', divonx: '\u22C7', DJcy: '\u0402', djcy: '\u0452', dlcorn: '\u231E', dlcrop: '\u230D', dollar: '\u0024', Dopf: '\uD835\uDD3B', dopf: '\uD835\uDD55', Dot: '\u00A8', dot: '\u02D9', DotDot: '\u20DC', doteq: '\u2250', doteqdot: '\u2251', DotEqual: '\u2250', dotminus: '\u2238', dotplus: '\u2214', dotsquare: '\u22A1', doublebarwedge: '\u2306', DoubleContourIntegral: '\u222F', DoubleDot: '\u00A8', DoubleDownArrow: '\u21D3', DoubleLeftArrow: '\u21D0', DoubleLeftRightArrow: '\u21D4', DoubleLeftTee: '\u2AE4', DoubleLongLeftArrow: '\u27F8', DoubleLongLeftRightArrow: '\u27FA', DoubleLongRightArrow: '\u27F9', DoubleRightArrow: '\u21D2', DoubleRightTee: '\u22A8', DoubleUpArrow: '\u21D1', DoubleUpDownArrow: '\u21D5', DoubleVerticalBar: '\u2225', DownArrow: '\u2193', Downarrow: '\u21D3', downarrow: '\u2193', DownArrowBar: '\u2913', DownArrowUpArrow: '\u21F5', DownBreve: '\u0311', downdownarrows: '\u21CA', downharpoonleft: '\u21C3', downharpoonright: '\u21C2', DownLeftRightVector: '\u2950', DownLeftTeeVector: '\u295E', DownLeftVector: '\u21BD', DownLeftVectorBar: '\u2956', DownRightTeeVector: '\u295F', DownRightVector: '\u21C1', DownRightVectorBar: '\u2957', DownTee: '\u22A4', DownTeeArrow: '\u21A7', drbkarow: '\u2910', drcorn: '\u231F', drcrop: '\u230C', Dscr: '\uD835\uDC9F', dscr: '\uD835\uDCB9', DScy: '\u0405', dscy: '\u0455', dsol: '\u29F6', Dstrok: '\u0110', dstrok: '\u0111', dtdot: '\u22F1', dtri: '\u25BF', dtrif: '\u25BE', duarr: '\u21F5', duhar: '\u296F', dwangle: '\u29A6', DZcy: '\u040F', dzcy: '\u045F', dzigrarr: '\u27FF', Eacute: '\u00C9', eacute: '\u00E9', easter: '\u2A6E', Ecaron: '\u011A', ecaron: '\u011B', ecir: '\u2256', Ecirc: '\u00CA', ecirc: '\u00EA', ecolon: '\u2255', Ecy: '\u042D', ecy: '\u044D', eDDot: '\u2A77', Edot: '\u0116', eDot: '\u2251', edot: '\u0117', ee: '\u2147', efDot: '\u2252', Efr: '\uD835\uDD08', efr: '\uD835\uDD22', eg: '\u2A9A', Egrave: '\u00C8', egrave: '\u00E8', egs: '\u2A96', egsdot: '\u2A98', el: '\u2A99', Element: '\u2208', elinters: '\u23E7', ell: '\u2113', els: '\u2A95', elsdot: '\u2A97', Emacr: '\u0112', emacr: '\u0113', empty: '\u2205', emptyset: '\u2205', EmptySmallSquare: '\u25FB', emptyv: '\u2205', EmptyVerySmallSquare: '\u25AB', emsp: '\u2003', emsp13: '\u2004', emsp14: '\u2005', ENG: '\u014A', eng: '\u014B', ensp: '\u2002', Eogon: '\u0118', eogon: '\u0119', Eopf: '\uD835\uDD3C', eopf: '\uD835\uDD56', epar: '\u22D5', eparsl: '\u29E3', eplus: '\u2A71', epsi: '\u03B5', Epsilon: '\u0395', epsilon: '\u03B5', epsiv: '\u03F5', eqcirc: '\u2256', eqcolon: '\u2255', eqsim: '\u2242', eqslantgtr: '\u2A96', eqslantless: '\u2A95', Equal: '\u2A75', equals: '\u003D', EqualTilde: '\u2242', equest: '\u225F', Equilibrium: '\u21CC', equiv: '\u2261', equivDD: '\u2A78', eqvparsl: '\u29E5', erarr: '\u2971', erDot: '\u2253', Escr: '\u2130', escr: '\u212F', esdot: '\u2250', Esim: '\u2A73', esim: '\u2242', Eta: '\u0397', eta: '\u03B7', ETH: '\u00D0', eth: '\u00F0', Euml: '\u00CB', euml: '\u00EB', euro: '\u20AC', excl: '\u0021', exist: '\u2203', Exists: '\u2203', expectation: '\u2130', ExponentialE: '\u2147', exponentiale: '\u2147', fallingdotseq: '\u2252', Fcy: '\u0424', fcy: '\u0444', female: '\u2640', ffilig: '\uFB03', fflig: '\uFB00', ffllig: '\uFB04', Ffr: '\uD835\uDD09', ffr: '\uD835\uDD23', filig: '\uFB01', FilledSmallSquare: '\u25FC', FilledVerySmallSquare: '\u25AA', fjlig: '\u0066\u006A', flat: '\u266D', fllig: '\uFB02', fltns: '\u25B1', fnof: '\u0192', Fopf: '\uD835\uDD3D', fopf: '\uD835\uDD57', ForAll: '\u2200', forall: '\u2200', fork: '\u22D4', forkv: '\u2AD9', Fouriertrf: '\u2131', fpartint: '\u2A0D', frac12: '\u00BD', frac13: '\u2153', frac14: '\u00BC', frac15: '\u2155', frac16: '\u2159', frac18: '\u215B', frac23: '\u2154', frac25: '\u2156', frac34: '\u00BE', frac35: '\u2157', frac38: '\u215C', frac45: '\u2158', frac56: '\u215A', frac58: '\u215D', frac78: '\u215E', frasl: '\u2044', frown: '\u2322', Fscr: '\u2131', fscr: '\uD835\uDCBB', gacute: '\u01F5', Gamma: '\u0393', gamma: '\u03B3', Gammad: '\u03DC', gammad: '\u03DD', gap: '\u2A86', Gbreve: '\u011E', gbreve: '\u011F', Gcedil: '\u0122', Gcirc: '\u011C', gcirc: '\u011D', Gcy: '\u0413', gcy: '\u0433', Gdot: '\u0120', gdot: '\u0121', gE: '\u2267', ge: '\u2265', gEl: '\u2A8C', gel: '\u22DB', geq: '\u2265', geqq: '\u2267', geqslant: '\u2A7E', ges: '\u2A7E', gescc: '\u2AA9', gesdot: '\u2A80', gesdoto: '\u2A82', gesdotol: '\u2A84', gesl: '\u22DB\uFE00', gesles: '\u2A94', Gfr: '\uD835\uDD0A', gfr: '\uD835\uDD24', Gg: '\u22D9', gg: '\u226B', ggg: '\u22D9', gimel: '\u2137', GJcy: '\u0403', gjcy: '\u0453', gl: '\u2277', gla: '\u2AA5', glE: '\u2A92', glj: '\u2AA4', gnap: '\u2A8A', gnapprox: '\u2A8A', gnE: '\u2269', gne: '\u2A88', gneq: '\u2A88', gneqq: '\u2269', gnsim: '\u22E7', Gopf: '\uD835\uDD3E', gopf: '\uD835\uDD58', grave: '\u0060', GreaterEqual: '\u2265', GreaterEqualLess: '\u22DB', GreaterFullEqual: '\u2267', GreaterGreater: '\u2AA2', GreaterLess: '\u2277', GreaterSlantEqual: '\u2A7E', GreaterTilde: '\u2273', Gscr: '\uD835\uDCA2', gscr: '\u210A', gsim: '\u2273', gsime: '\u2A8E', gsiml: '\u2A90', Gt: '\u226B', GT: '\u003E', gt: '\u003E', gtcc: '\u2AA7', gtcir: '\u2A7A', gtdot: '\u22D7', gtlPar: '\u2995', gtquest: '\u2A7C', gtrapprox: '\u2A86', gtrarr: '\u2978', gtrdot: '\u22D7', gtreqless: '\u22DB', gtreqqless: '\u2A8C', gtrless: '\u2277', gtrsim: '\u2273', gvertneqq: '\u2269\uFE00', gvnE: '\u2269\uFE00', Hacek: '\u02C7', hairsp: '\u200A', half: '\u00BD', hamilt: '\u210B', HARDcy: '\u042A', hardcy: '\u044A', hArr: '\u21D4', harr: '\u2194', harrcir: '\u2948', harrw: '\u21AD', Hat: '\u005E', hbar: '\u210F', Hcirc: '\u0124', hcirc: '\u0125', hearts: '\u2665', heartsuit: '\u2665', hellip: '\u2026', hercon: '\u22B9', Hfr: '\u210C', hfr: '\uD835\uDD25', HilbertSpace: '\u210B', hksearow: '\u2925', hkswarow: '\u2926', hoarr: '\u21FF', homtht: '\u223B', hookleftarrow: '\u21A9', hookrightarrow: '\u21AA', Hopf: '\u210D', hopf: '\uD835\uDD59', horbar: '\u2015', HorizontalLine: '\u2500', Hscr: '\u210B', hscr: '\uD835\uDCBD', hslash: '\u210F', Hstrok: '\u0126', hstrok: '\u0127', HumpDownHump: '\u224E', HumpEqual: '\u224F', hybull: '\u2043', hyphen: '\u2010', Iacute: '\u00CD', iacute: '\u00ED', ic: '\u2063', Icirc: '\u00CE', icirc: '\u00EE', Icy: '\u0418', icy: '\u0438', Idot: '\u0130', IEcy: '\u0415', iecy: '\u0435', iexcl: '\u00A1', iff: '\u21D4', Ifr: '\u2111', ifr: '\uD835\uDD26', Igrave: '\u00CC', igrave: '\u00EC', ii: '\u2148', iiiint: '\u2A0C', iiint: '\u222D', iinfin: '\u29DC', iiota: '\u2129', IJlig: '\u0132', ijlig: '\u0133', Im: '\u2111', Imacr: '\u012A', imacr: '\u012B', image: '\u2111', ImaginaryI: '\u2148', imagline: '\u2110', imagpart: '\u2111', imath: '\u0131', imof: '\u22B7', imped: '\u01B5', Implies: '\u21D2', in: '\u2208', incare: '\u2105', infin: '\u221E', infintie: '\u29DD', inodot: '\u0131', Int: '\u222C', int: '\u222B', intcal: '\u22BA', integers: '\u2124', Integral: '\u222B', intercal: '\u22BA', Intersection: '\u22C2', intlarhk: '\u2A17', intprod: '\u2A3C', InvisibleComma: '\u2063', InvisibleTimes: '\u2062', IOcy: '\u0401', iocy: '\u0451', Iogon: '\u012E', iogon: '\u012F', Iopf: '\uD835\uDD40', iopf: '\uD835\uDD5A', Iota: '\u0399', iota: '\u03B9', iprod: '\u2A3C', iquest: '\u00BF', Iscr: '\u2110', iscr: '\uD835\uDCBE', isin: '\u2208', isindot: '\u22F5', isinE: '\u22F9', isins: '\u22F4', isinsv: '\u22F3', isinv: '\u2208', it: '\u2062', Itilde: '\u0128', itilde: '\u0129', Iukcy: '\u0406', iukcy: '\u0456', Iuml: '\u00CF', iuml: '\u00EF', Jcirc: '\u0134', jcirc: '\u0135', Jcy: '\u0419', jcy: '\u0439', Jfr: '\uD835\uDD0D', jfr: '\uD835\uDD27', jmath: '\u0237', Jopf: '\uD835\uDD41', jopf: '\uD835\uDD5B', Jscr: '\uD835\uDCA5', jscr: '\uD835\uDCBF', Jsercy: '\u0408', jsercy: '\u0458', Jukcy: '\u0404', jukcy: '\u0454', Kappa: '\u039A', kappa: '\u03BA', kappav: '\u03F0', Kcedil: '\u0136', kcedil: '\u0137', Kcy: '\u041A', kcy: '\u043A', Kfr: '\uD835\uDD0E', kfr: '\uD835\uDD28', kgreen: '\u0138', KHcy: '\u0425', khcy: '\u0445', KJcy: '\u040C', kjcy: '\u045C', Kopf: '\uD835\uDD42', kopf: '\uD835\uDD5C', Kscr: '\uD835\uDCA6', kscr: '\uD835\uDCC0', lAarr: '\u21DA', Lacute: '\u0139', lacute: '\u013A', laemptyv: '\u29B4', lagran: '\u2112', Lambda: '\u039B', lambda: '\u03BB', Lang: '\u27EA', lang: '\u27E8', langd: '\u2991', langle: '\u27E8', lap: '\u2A85', Laplacetrf: '\u2112', laquo: '\u00AB', Larr: '\u219E', lArr: '\u21D0', larr: '\u2190', larrb: '\u21E4', larrbfs: '\u291F', larrfs: '\u291D', larrhk: '\u21A9', larrlp: '\u21AB', larrpl: '\u2939', larrsim: '\u2973', larrtl: '\u21A2', lat: '\u2AAB', lAtail: '\u291B', latail: '\u2919', late: '\u2AAD', lates: '\u2AAD\uFE00', lBarr: '\u290E', lbarr: '\u290C', lbbrk: '\u2772', lbrace: '\u007B', lbrack: '\u005B', lbrke: '\u298B', lbrksld: '\u298F', lbrkslu: '\u298D', Lcaron: '\u013D', lcaron: '\u013E', Lcedil: '\u013B', lcedil: '\u013C', lceil: '\u2308', lcub: '\u007B', Lcy: '\u041B', lcy: '\u043B', ldca: '\u2936', ldquo: '\u201C', ldquor: '\u201E', ldrdhar: '\u2967', ldrushar: '\u294B', ldsh: '\u21B2', lE: '\u2266', le: '\u2264', LeftAngleBracket: '\u27E8', LeftArrow: '\u2190', Leftarrow: '\u21D0', leftarrow: '\u2190', LeftArrowBar: '\u21E4', LeftArrowRightArrow: '\u21C6', leftarrowtail: '\u21A2', LeftCeiling: '\u2308', LeftDoubleBracket: '\u27E6', LeftDownTeeVector: '\u2961', LeftDownVector: '\u21C3', LeftDownVectorBar: '\u2959', LeftFloor: '\u230A', leftharpoondown: '\u21BD', leftharpoonup: '\u21BC', leftleftarrows: '\u21C7', LeftRightArrow: '\u2194', Leftrightarrow: '\u21D4', leftrightarrow: '\u2194', leftrightarrows: '\u21C6', leftrightharpoons: '\u21CB', leftrightsquigarrow: '\u21AD', LeftRightVector: '\u294E', LeftTee: '\u22A3', LeftTeeArrow: '\u21A4', LeftTeeVector: '\u295A', leftthreetimes: '\u22CB', LeftTriangle: '\u22B2', LeftTriangleBar: '\u29CF', LeftTriangleEqual: '\u22B4', LeftUpDownVector: '\u2951', LeftUpTeeVector: '\u2960', LeftUpVector: '\u21BF', LeftUpVectorBar: '\u2958', LeftVector: '\u21BC', LeftVectorBar: '\u2952', lEg: '\u2A8B', leg: '\u22DA', leq: '\u2264', leqq: '\u2266', leqslant: '\u2A7D', les: '\u2A7D', lescc: '\u2AA8', lesdot: '\u2A7F', lesdoto: '\u2A81', lesdotor: '\u2A83', lesg: '\u22DA\uFE00', lesges: '\u2A93', lessapprox: '\u2A85', lessdot: '\u22D6', lesseqgtr: '\u22DA', lesseqqgtr: '\u2A8B', LessEqualGreater: '\u22DA', LessFullEqual: '\u2266', LessGreater: '\u2276', lessgtr: '\u2276', LessLess: '\u2AA1', lesssim: '\u2272', LessSlantEqual: '\u2A7D', LessTilde: '\u2272', lfisht: '\u297C', lfloor: '\u230A', Lfr: '\uD835\uDD0F', lfr: '\uD835\uDD29', lg: '\u2276', lgE: '\u2A91', lHar: '\u2962', lhard: '\u21BD', lharu: '\u21BC', lharul: '\u296A', lhblk: '\u2584', LJcy: '\u0409', ljcy: '\u0459', Ll: '\u22D8', ll: '\u226A', llarr: '\u21C7', llcorner: '\u231E', Lleftarrow: '\u21DA', llhard: '\u296B', lltri: '\u25FA', Lmidot: '\u013F', lmidot: '\u0140', lmoust: '\u23B0', lmoustache: '\u23B0', lnap: '\u2A89', lnapprox: '\u2A89', lnE: '\u2268', lne: '\u2A87', lneq: '\u2A87', lneqq: '\u2268', lnsim: '\u22E6', loang: '\u27EC', loarr: '\u21FD', lobrk: '\u27E6', LongLeftArrow: '\u27F5', Longleftarrow: '\u27F8', longleftarrow: '\u27F5', LongLeftRightArrow: '\u27F7', Longleftrightarrow: '\u27FA', longleftrightarrow: '\u27F7', longmapsto: '\u27FC', LongRightArrow: '\u27F6', Longrightarrow: '\u27F9', longrightarrow: '\u27F6', looparrowleft: '\u21AB', looparrowright: '\u21AC', lopar: '\u2985', Lopf: '\uD835\uDD43', lopf: '\uD835\uDD5D', loplus: '\u2A2D', lotimes: '\u2A34', lowast: '\u2217', lowbar: '\u005F', LowerLeftArrow: '\u2199', LowerRightArrow: '\u2198', loz: '\u25CA', lozenge: '\u25CA', lozf: '\u29EB', lpar: '\u0028', lparlt: '\u2993', lrarr: '\u21C6', lrcorner: '\u231F', lrhar: '\u21CB', lrhard: '\u296D', lrm: '\u200E', lrtri: '\u22BF', lsaquo: '\u2039', Lscr: '\u2112', lscr: '\uD835\uDCC1', Lsh: '\u21B0', lsh: '\u21B0', lsim: '\u2272', lsime: '\u2A8D', lsimg: '\u2A8F', lsqb: '\u005B', lsquo: '\u2018', lsquor: '\u201A', Lstrok: '\u0141', lstrok: '\u0142', Lt: '\u226A', LT: '\u003C', lt: '\u003C', ltcc: '\u2AA6', ltcir: '\u2A79', ltdot: '\u22D6', lthree: '\u22CB', ltimes: '\u22C9', ltlarr: '\u2976', ltquest: '\u2A7B', ltri: '\u25C3', ltrie: '\u22B4', ltrif: '\u25C2', ltrPar: '\u2996', lurdshar: '\u294A', luruhar: '\u2966', lvertneqq: '\u2268\uFE00', lvnE: '\u2268\uFE00', macr: '\u00AF', male: '\u2642', malt: '\u2720', maltese: '\u2720', Map: '\u2905', map: '\u21A6', mapsto: '\u21A6', mapstodown: '\u21A7', mapstoleft: '\u21A4', mapstoup: '\u21A5', marker: '\u25AE', mcomma: '\u2A29', Mcy: '\u041C', mcy: '\u043C', mdash: '\u2014', mDDot: '\u223A', measuredangle: '\u2221', MediumSpace: '\u205F', Mellintrf: '\u2133', Mfr: '\uD835\uDD10', mfr: '\uD835\uDD2A', mho: '\u2127', micro: '\u00B5', mid: '\u2223', midast: '\u002A', midcir: '\u2AF0', middot: '\u00B7', minus: '\u2212', minusb: '\u229F', minusd: '\u2238', minusdu: '\u2A2A', MinusPlus: '\u2213', mlcp: '\u2ADB', mldr: '\u2026', mnplus: '\u2213', models: '\u22A7', Mopf: '\uD835\uDD44', mopf: '\uD835\uDD5E', mp: '\u2213', Mscr: '\u2133', mscr: '\uD835\uDCC2', mstpos: '\u223E', Mu: '\u039C', mu: '\u03BC', multimap: '\u22B8', mumap: '\u22B8', nabla: '\u2207', Nacute: '\u0143', nacute: '\u0144', nang: '\u2220\u20D2', nap: '\u2249', napE: '\u2A70\u0338', napid: '\u224B\u0338', napos: '\u0149', napprox: '\u2249', natur: '\u266E', natural: '\u266E', naturals: '\u2115', nbsp: '\u00A0', nbump: '\u224E\u0338', nbumpe: '\u224F\u0338', ncap: '\u2A43', Ncaron: '\u0147', ncaron: '\u0148', Ncedil: '\u0145', ncedil: '\u0146', ncong: '\u2247', ncongdot: '\u2A6D\u0338', ncup: '\u2A42', Ncy: '\u041D', ncy: '\u043D', ndash: '\u2013', ne: '\u2260', nearhk: '\u2924', neArr: '\u21D7', nearr: '\u2197', nearrow: '\u2197', nedot: '\u2250\u0338', NegativeMediumSpace: '\u200B', NegativeThickSpace: '\u200B', NegativeThinSpace: '\u200B', NegativeVeryThinSpace: '\u200B', nequiv: '\u2262', nesear: '\u2928', nesim: '\u2242\u0338', NestedGreaterGreater: '\u226B', NestedLessLess: '\u226A', NewLine: '\u000A', nexist: '\u2204', nexists: '\u2204', Nfr: '\uD835\uDD11', nfr: '\uD835\uDD2B', ngE: '\u2267\u0338', nge: '\u2271', ngeq: '\u2271', ngeqq: '\u2267\u0338', ngeqslant: '\u2A7E\u0338', nges: '\u2A7E\u0338', nGg: '\u22D9\u0338', ngsim: '\u2275', nGt: '\u226B\u20D2', ngt: '\u226F', ngtr: '\u226F', nGtv: '\u226B\u0338', nhArr: '\u21CE', nharr: '\u21AE', nhpar: '\u2AF2', ni: '\u220B', nis: '\u22FC', nisd: '\u22FA', niv: '\u220B', NJcy: '\u040A', njcy: '\u045A', nlArr: '\u21CD', nlarr: '\u219A', nldr: '\u2025', nlE: '\u2266\u0338', nle: '\u2270', nLeftarrow: '\u21CD', nleftarrow: '\u219A', nLeftrightarrow: '\u21CE', nleftrightarrow: '\u21AE', nleq: '\u2270', nleqq: '\u2266\u0338', nleqslant: '\u2A7D\u0338', nles: '\u2A7D\u0338', nless: '\u226E', nLl: '\u22D8\u0338', nlsim: '\u2274', nLt: '\u226A\u20D2', nlt: '\u226E', nltri: '\u22EA', nltrie: '\u22EC', nLtv: '\u226A\u0338', nmid: '\u2224', NoBreak: '\u2060', NonBreakingSpace: '\u00A0', Nopf: '\u2115', nopf: '\uD835\uDD5F', Not: '\u2AEC', not: '\u00AC', NotCongruent: '\u2262', NotCupCap: '\u226D', NotDoubleVerticalBar: '\u2226', NotElement: '\u2209', NotEqual: '\u2260', NotEqualTilde: '\u2242\u0338', NotExists: '\u2204', NotGreater: '\u226F', NotGreaterEqual: '\u2271', NotGreaterFullEqual: '\u2267\u0338', NotGreaterGreater: '\u226B\u0338', NotGreaterLess: '\u2279', NotGreaterSlantEqual: '\u2A7E\u0338', NotGreaterTilde: '\u2275', NotHumpDownHump: '\u224E\u0338', NotHumpEqual: '\u224F\u0338', notin: '\u2209', notindot: '\u22F5\u0338', notinE: '\u22F9\u0338', notinva: '\u2209', notinvb: '\u22F7', notinvc: '\u22F6', NotLeftTriangle: '\u22EA', NotLeftTriangleBar: '\u29CF\u0338', NotLeftTriangleEqual: '\u22EC', NotLess: '\u226E', NotLessEqual: '\u2270', NotLessGreater: '\u2278', NotLessLess: '\u226A\u0338', NotLessSlantEqual: '\u2A7D\u0338', NotLessTilde: '\u2274', NotNestedGreaterGreater: '\u2AA2\u0338', NotNestedLessLess: '\u2AA1\u0338', notni: '\u220C', notniva: '\u220C', notnivb: '\u22FE', notnivc: '\u22FD', NotPrecedes: '\u2280', NotPrecedesEqual: '\u2AAF\u0338', NotPrecedesSlantEqual: '\u22E0', NotReverseElement: '\u220C', NotRightTriangle: '\u22EB', NotRightTriangleBar: '\u29D0\u0338', NotRightTriangleEqual: '\u22ED', NotSquareSubset: '\u228F\u0338', NotSquareSubsetEqual: '\u22E2', NotSquareSuperset: '\u2290\u0338', NotSquareSupersetEqual: '\u22E3', NotSubset: '\u2282\u20D2', NotSubsetEqual: '\u2288', NotSucceeds: '\u2281', NotSucceedsEqual: '\u2AB0\u0338', NotSucceedsSlantEqual: '\u22E1', NotSucceedsTilde: '\u227F\u0338', NotSuperset: '\u2283\u20D2', NotSupersetEqual: '\u2289', NotTilde: '\u2241', NotTildeEqual: '\u2244', NotTildeFullEqual: '\u2247', NotTildeTilde: '\u2249', NotVerticalBar: '\u2224', npar: '\u2226', nparallel: '\u2226', nparsl: '\u2AFD\u20E5', npart: '\u2202\u0338', npolint: '\u2A14', npr: '\u2280', nprcue: '\u22E0', npre: '\u2AAF\u0338', nprec: '\u2280', npreceq: '\u2AAF\u0338', nrArr: '\u21CF', nrarr: '\u219B', nrarrc: '\u2933\u0338', nrarrw: '\u219D\u0338', nRightarrow: '\u21CF', nrightarrow: '\u219B', nrtri: '\u22EB', nrtrie: '\u22ED', nsc: '\u2281', nsccue: '\u22E1', nsce: '\u2AB0\u0338', Nscr: '\uD835\uDCA9', nscr: '\uD835\uDCC3', nshortmid: '\u2224', nshortparallel: '\u2226', nsim: '\u2241', nsime: '\u2244', nsimeq: '\u2244', nsmid: '\u2224', nspar: '\u2226', nsqsube: '\u22E2', nsqsupe: '\u22E3', nsub: '\u2284', nsubE: '\u2AC5\u0338', nsube: '\u2288', nsubset: '\u2282\u20D2', nsubseteq: '\u2288', nsubseteqq: '\u2AC5\u0338', nsucc: '\u2281', nsucceq: '\u2AB0\u0338', nsup: '\u2285', nsupE: '\u2AC6\u0338', nsupe: '\u2289', nsupset: '\u2283\u20D2', nsupseteq: '\u2289', nsupseteqq: '\u2AC6\u0338', ntgl: '\u2279', Ntilde: '\u00D1', ntilde: '\u00F1', ntlg: '\u2278', ntriangleleft: '\u22EA', ntrianglelefteq: '\u22EC', ntriangleright: '\u22EB', ntrianglerighteq: '\u22ED', Nu: '\u039D', nu: '\u03BD', num: '\u0023', numero: '\u2116', numsp: '\u2007', nvap: '\u224D\u20D2', nVDash: '\u22AF', nVdash: '\u22AE', nvDash: '\u22AD', nvdash: '\u22AC', nvge: '\u2265\u20D2', nvgt: '\u003E\u20D2', nvHarr: '\u2904', nvinfin: '\u29DE', nvlArr: '\u2902', nvle: '\u2264\u20D2', nvlt: '\u003C\u20D2', nvltrie: '\u22B4\u20D2', nvrArr: '\u2903', nvrtrie: '\u22B5\u20D2', nvsim: '\u223C\u20D2', nwarhk: '\u2923', nwArr: '\u21D6', nwarr: '\u2196', nwarrow: '\u2196', nwnear: '\u2927', Oacute: '\u00D3', oacute: '\u00F3', oast: '\u229B', ocir: '\u229A', Ocirc: '\u00D4', ocirc: '\u00F4', Ocy: '\u041E', ocy: '\u043E', odash: '\u229D', Odblac: '\u0150', odblac: '\u0151', odiv: '\u2A38', odot: '\u2299', odsold: '\u29BC', OElig: '\u0152', oelig: '\u0153', ofcir: '\u29BF', Ofr: '\uD835\uDD12', ofr: '\uD835\uDD2C', ogon: '\u02DB', Ograve: '\u00D2', ograve: '\u00F2', ogt: '\u29C1', ohbar: '\u29B5', ohm: '\u03A9', oint: '\u222E', olarr: '\u21BA', olcir: '\u29BE', olcross: '\u29BB', oline: '\u203E', olt: '\u29C0', Omacr: '\u014C', omacr: '\u014D', Omega: '\u03A9', omega: '\u03C9', Omicron: '\u039F', omicron: '\u03BF', omid: '\u29B6', ominus: '\u2296', Oopf: '\uD835\uDD46', oopf: '\uD835\uDD60', opar: '\u29B7', OpenCurlyDoubleQuote: '\u201C', OpenCurlyQuote: '\u2018', operp: '\u29B9', oplus: '\u2295', Or: '\u2A54', or: '\u2228', orarr: '\u21BB', ord: '\u2A5D', order: '\u2134', orderof: '\u2134', ordf: '\u00AA', ordm: '\u00BA', origof: '\u22B6', oror: '\u2A56', orslope: '\u2A57', orv: '\u2A5B', oS: '\u24C8', Oscr: '\uD835\uDCAA', oscr: '\u2134', Oslash: '\u00D8', oslash: '\u00F8', osol: '\u2298', Otilde: '\u00D5', otilde: '\u00F5', Otimes: '\u2A37', otimes: '\u2297', otimesas: '\u2A36', Ouml: '\u00D6', ouml: '\u00F6', ovbar: '\u233D', OverBar: '\u203E', OverBrace: '\u23DE', OverBracket: '\u23B4', OverParenthesis: '\u23DC', par: '\u2225', para: '\u00B6', parallel: '\u2225', parsim: '\u2AF3', parsl: '\u2AFD', part: '\u2202', PartialD: '\u2202', Pcy: '\u041F', pcy: '\u043F', percnt: '\u0025', period: '\u002E', permil: '\u2030', perp: '\u22A5', pertenk: '\u2031', Pfr: '\uD835\uDD13', pfr: '\uD835\uDD2D', Phi: '\u03A6', phi: '\u03C6', phiv: '\u03D5', phmmat: '\u2133', phone: '\u260E', Pi: '\u03A0', pi: '\u03C0', pitchfork: '\u22D4', piv: '\u03D6', planck: '\u210F', planckh: '\u210E', plankv: '\u210F', plus: '\u002B', plusacir: '\u2A23', plusb: '\u229E', pluscir: '\u2A22', plusdo: '\u2214', plusdu: '\u2A25', pluse: '\u2A72', PlusMinus: '\u00B1', plusmn: '\u00B1', plussim: '\u2A26', plustwo: '\u2A27', pm: '\u00B1', Poincareplane: '\u210C', pointint: '\u2A15', Popf: '\u2119', popf: '\uD835\uDD61', pound: '\u00A3', Pr: '\u2ABB', pr: '\u227A', prap: '\u2AB7', prcue: '\u227C', prE: '\u2AB3', pre: '\u2AAF', prec: '\u227A', precapprox: '\u2AB7', preccurlyeq: '\u227C', Precedes: '\u227A', PrecedesEqual: '\u2AAF', PrecedesSlantEqual: '\u227C', PrecedesTilde: '\u227E', preceq: '\u2AAF', precnapprox: '\u2AB9', precneqq: '\u2AB5', precnsim: '\u22E8', precsim: '\u227E', Prime: '\u2033', prime: '\u2032', primes: '\u2119', prnap: '\u2AB9', prnE: '\u2AB5', prnsim: '\u22E8', prod: '\u220F', Product: '\u220F', profalar: '\u232E', profline: '\u2312', profsurf: '\u2313', prop: '\u221D', Proportion: '\u2237', Proportional: '\u221D', propto: '\u221D', prsim: '\u227E', prurel: '\u22B0', Pscr: '\uD835\uDCAB', pscr: '\uD835\uDCC5', Psi: '\u03A8', psi: '\u03C8', puncsp: '\u2008', Qfr: '\uD835\uDD14', qfr: '\uD835\uDD2E', qint: '\u2A0C', Qopf: '\u211A', qopf: '\uD835\uDD62', qprime: '\u2057', Qscr: '\uD835\uDCAC', qscr: '\uD835\uDCC6', quaternions: '\u210D', quatint: '\u2A16', quest: '\u003F', questeq: '\u225F', QUOT: '\u0022', quot: '\u0022', rAarr: '\u21DB', race: '\u223D\u0331', Racute: '\u0154', racute: '\u0155', radic: '\u221A', raemptyv: '\u29B3', Rang: '\u27EB', rang: '\u27E9', rangd: '\u2992', range: '\u29A5', rangle: '\u27E9', raquo: '\u00BB', Rarr: '\u21A0', rArr: '\u21D2', rarr: '\u2192', rarrap: '\u2975', rarrb: '\u21E5', rarrbfs: '\u2920', rarrc: '\u2933', rarrfs: '\u291E', rarrhk: '\u21AA', rarrlp: '\u21AC', rarrpl: '\u2945', rarrsim: '\u2974', Rarrtl: '\u2916', rarrtl: '\u21A3', rarrw: '\u219D', rAtail: '\u291C', ratail: '\u291A', ratio: '\u2236', rationals: '\u211A', RBarr: '\u2910', rBarr: '\u290F', rbarr: '\u290D', rbbrk: '\u2773', rbrace: '\u007D', rbrack: '\u005D', rbrke: '\u298C', rbrksld: '\u298E', rbrkslu: '\u2990', Rcaron: '\u0158', rcaron: '\u0159', Rcedil: '\u0156', rcedil: '\u0157', rceil: '\u2309', rcub: '\u007D', Rcy: '\u0420', rcy: '\u0440', rdca: '\u2937', rdldhar: '\u2969', rdquo: '\u201D', rdquor: '\u201D', rdsh: '\u21B3', Re: '\u211C', real: '\u211C', realine: '\u211B', realpart: '\u211C', reals: '\u211D', rect: '\u25AD', REG: '\u00AE', reg: '\u00AE', ReverseElement: '\u220B', ReverseEquilibrium: '\u21CB', ReverseUpEquilibrium: '\u296F', rfisht: '\u297D', rfloor: '\u230B', Rfr: '\u211C', rfr: '\uD835\uDD2F', rHar: '\u2964', rhard: '\u21C1', rharu: '\u21C0', rharul: '\u296C', Rho: '\u03A1', rho: '\u03C1', rhov: '\u03F1', RightAngleBracket: '\u27E9', RightArrow: '\u2192', Rightarrow: '\u21D2', rightarrow: '\u2192', RightArrowBar: '\u21E5', RightArrowLeftArrow: '\u21C4', rightarrowtail: '\u21A3', RightCeiling: '\u2309', RightDoubleBracket: '\u27E7', RightDownTeeVector: '\u295D', RightDownVector: '\u21C2', RightDownVectorBar: '\u2955', RightFloor: '\u230B', rightharpoondown: '\u21C1', rightharpoonup: '\u21C0', rightleftarrows: '\u21C4', rightleftharpoons: '\u21CC', rightrightarrows: '\u21C9', rightsquigarrow: '\u219D', RightTee: '\u22A2', RightTeeArrow: '\u21A6', RightTeeVector: '\u295B', rightthreetimes: '\u22CC', RightTriangle: '\u22B3', RightTriangleBar: '\u29D0', RightTriangleEqual: '\u22B5', RightUpDownVector: '\u294F', RightUpTeeVector: '\u295C', RightUpVector: '\u21BE', RightUpVectorBar: '\u2954', RightVector: '\u21C0', RightVectorBar: '\u2953', ring: '\u02DA', risingdotseq: '\u2253', rlarr: '\u21C4', rlhar: '\u21CC', rlm: '\u200F', rmoust: '\u23B1', rmoustache: '\u23B1', rnmid: '\u2AEE', roang: '\u27ED', roarr: '\u21FE', robrk: '\u27E7', ropar: '\u2986', Ropf: '\u211D', ropf: '\uD835\uDD63', roplus: '\u2A2E', rotimes: '\u2A35', RoundImplies: '\u2970', rpar: '\u0029', rpargt: '\u2994', rppolint: '\u2A12', rrarr: '\u21C9', Rrightarrow: '\u21DB', rsaquo: '\u203A', Rscr: '\u211B', rscr: '\uD835\uDCC7', Rsh: '\u21B1', rsh: '\u21B1', rsqb: '\u005D', rsquo: '\u2019', rsquor: '\u2019', rthree: '\u22CC', rtimes: '\u22CA', rtri: '\u25B9', rtrie: '\u22B5', rtrif: '\u25B8', rtriltri: '\u29CE', RuleDelayed: '\u29F4', ruluhar: '\u2968', rx: '\u211E', Sacute: '\u015A', sacute: '\u015B', sbquo: '\u201A', Sc: '\u2ABC', sc: '\u227B', scap: '\u2AB8', Scaron: '\u0160', scaron: '\u0161', sccue: '\u227D', scE: '\u2AB4', sce: '\u2AB0', Scedil: '\u015E', scedil: '\u015F', Scirc: '\u015C', scirc: '\u015D', scnap: '\u2ABA', scnE: '\u2AB6', scnsim: '\u22E9', scpolint: '\u2A13', scsim: '\u227F', Scy: '\u0421', scy: '\u0441', sdot: '\u22C5', sdotb: '\u22A1', sdote: '\u2A66', searhk: '\u2925', seArr: '\u21D8', searr: '\u2198', searrow: '\u2198', sect: '\u00A7', semi: '\u003B', seswar: '\u2929', setminus: '\u2216', setmn: '\u2216', sext: '\u2736', Sfr: '\uD835\uDD16', sfr: '\uD835\uDD30', sfrown: '\u2322', sharp: '\u266F', SHCHcy: '\u0429', shchcy: '\u0449', SHcy: '\u0428', shcy: '\u0448', ShortDownArrow: '\u2193', ShortLeftArrow: '\u2190', shortmid: '\u2223', shortparallel: '\u2225', ShortRightArrow: '\u2192', ShortUpArrow: '\u2191', shy: '\u00AD', Sigma: '\u03A3', sigma: '\u03C3', sigmaf: '\u03C2', sigmav: '\u03C2', sim: '\u223C', simdot: '\u2A6A', sime: '\u2243', simeq: '\u2243', simg: '\u2A9E', simgE: '\u2AA0', siml: '\u2A9D', simlE: '\u2A9F', simne: '\u2246', simplus: '\u2A24', simrarr: '\u2972', slarr: '\u2190', SmallCircle: '\u2218', smallsetminus: '\u2216', smashp: '\u2A33', smeparsl: '\u29E4', smid: '\u2223', smile: '\u2323', smt: '\u2AAA', smte: '\u2AAC', smtes: '\u2AAC\uFE00', SOFTcy: '\u042C', softcy: '\u044C', sol: '\u002F', solb: '\u29C4', solbar: '\u233F', Sopf: '\uD835\uDD4A', sopf: '\uD835\uDD64', spades: '\u2660', spadesuit: '\u2660', spar: '\u2225', sqcap: '\u2293', sqcaps: '\u2293\uFE00', sqcup: '\u2294', sqcups: '\u2294\uFE00', Sqrt: '\u221A', sqsub: '\u228F', sqsube: '\u2291', sqsubset: '\u228F', sqsubseteq: '\u2291', sqsup: '\u2290', sqsupe: '\u2292', sqsupset: '\u2290', sqsupseteq: '\u2292', squ: '\u25A1', Square: '\u25A1', square: '\u25A1', SquareIntersection: '\u2293', SquareSubset: '\u228F', SquareSubsetEqual: '\u2291', SquareSuperset: '\u2290', SquareSupersetEqual: '\u2292', SquareUnion: '\u2294', squarf: '\u25AA', squf: '\u25AA', srarr: '\u2192', Sscr: '\uD835\uDCAE', sscr: '\uD835\uDCC8', ssetmn: '\u2216', ssmile: '\u2323', sstarf: '\u22C6', Star: '\u22C6', star: '\u2606', starf: '\u2605', straightepsilon: '\u03F5', straightphi: '\u03D5', strns: '\u00AF', Sub: '\u22D0', sub: '\u2282', subdot: '\u2ABD', subE: '\u2AC5', sube: '\u2286', subedot: '\u2AC3', submult: '\u2AC1', subnE: '\u2ACB', subne: '\u228A', subplus: '\u2ABF', subrarr: '\u2979', Subset: '\u22D0', subset: '\u2282', subseteq: '\u2286', subseteqq: '\u2AC5', SubsetEqual: '\u2286', subsetneq: '\u228A', subsetneqq: '\u2ACB', subsim: '\u2AC7', subsub: '\u2AD5', subsup: '\u2AD3', succ: '\u227B', succapprox: '\u2AB8', succcurlyeq: '\u227D', Succeeds: '\u227B', SucceedsEqual: '\u2AB0', SucceedsSlantEqual: '\u227D', SucceedsTilde: '\u227F', succeq: '\u2AB0', succnapprox: '\u2ABA', succneqq: '\u2AB6', succnsim: '\u22E9', succsim: '\u227F', SuchThat: '\u220B', Sum: '\u2211', sum: '\u2211', sung: '\u266A', Sup: '\u22D1', sup: '\u2283', sup1: '\u00B9', sup2: '\u00B2', sup3: '\u00B3', supdot: '\u2ABE', supdsub: '\u2AD8', supE: '\u2AC6', supe: '\u2287', supedot: '\u2AC4', Superset: '\u2283', SupersetEqual: '\u2287', suphsol: '\u27C9', suphsub: '\u2AD7', suplarr: '\u297B', supmult: '\u2AC2', supnE: '\u2ACC', supne: '\u228B', supplus: '\u2AC0', Supset: '\u22D1', supset: '\u2283', supseteq: '\u2287', supseteqq: '\u2AC6', supsetneq: '\u228B', supsetneqq: '\u2ACC', supsim: '\u2AC8', supsub: '\u2AD4', supsup: '\u2AD6', swarhk: '\u2926', swArr: '\u21D9', swarr: '\u2199', swarrow: '\u2199', swnwar: '\u292A', szlig: '\u00DF', Tab: '\u0009', target: '\u2316', Tau: '\u03A4', tau: '\u03C4', tbrk: '\u23B4', Tcaron: '\u0164', tcaron: '\u0165', Tcedil: '\u0162', tcedil: '\u0163', Tcy: '\u0422', tcy: '\u0442', tdot: '\u20DB', telrec: '\u2315', Tfr: '\uD835\uDD17', tfr: '\uD835\uDD31', there4: '\u2234', Therefore: '\u2234', therefore: '\u2234', Theta: '\u0398', theta: '\u03B8', thetasym: '\u03D1', thetav: '\u03D1', thickapprox: '\u2248', thicksim: '\u223C', ThickSpace: '\u205F\u200A', thinsp: '\u2009', ThinSpace: '\u2009', thkap: '\u2248', thksim: '\u223C', THORN: '\u00DE', thorn: '\u00FE', Tilde: '\u223C', tilde: '\u02DC', TildeEqual: '\u2243', TildeFullEqual: '\u2245', TildeTilde: '\u2248', times: '\u00D7', timesb: '\u22A0', timesbar: '\u2A31', timesd: '\u2A30', tint: '\u222D', toea: '\u2928', top: '\u22A4', topbot: '\u2336', topcir: '\u2AF1', Topf: '\uD835\uDD4B', topf: '\uD835\uDD65', topfork: '\u2ADA', tosa: '\u2929', tprime: '\u2034', TRADE: '\u2122', trade: '\u2122', triangle: '\u25B5', triangledown: '\u25BF', triangleleft: '\u25C3', trianglelefteq: '\u22B4', triangleq: '\u225C', triangleright: '\u25B9', trianglerighteq: '\u22B5', tridot: '\u25EC', trie: '\u225C', triminus: '\u2A3A', TripleDot: '\u20DB', triplus: '\u2A39', trisb: '\u29CD', tritime: '\u2A3B', trpezium: '\u23E2', Tscr: '\uD835\uDCAF', tscr: '\uD835\uDCC9', TScy: '\u0426', tscy: '\u0446', TSHcy: '\u040B', tshcy: '\u045B', Tstrok: '\u0166', tstrok: '\u0167', twixt: '\u226C', twoheadleftarrow: '\u219E', twoheadrightarrow: '\u21A0', Uacute: '\u00DA', uacute: '\u00FA', Uarr: '\u219F', uArr: '\u21D1', uarr: '\u2191', Uarrocir: '\u2949', Ubrcy: '\u040E', ubrcy: '\u045E', Ubreve: '\u016C', ubreve: '\u016D', Ucirc: '\u00DB', ucirc: '\u00FB', Ucy: '\u0423', ucy: '\u0443', udarr: '\u21C5', Udblac: '\u0170', udblac: '\u0171', udhar: '\u296E', ufisht: '\u297E', Ufr: '\uD835\uDD18', ufr: '\uD835\uDD32', Ugrave: '\u00D9', ugrave: '\u00F9', uHar: '\u2963', uharl: '\u21BF', uharr: '\u21BE', uhblk: '\u2580', ulcorn: '\u231C', ulcorner: '\u231C', ulcrop: '\u230F', ultri: '\u25F8', Umacr: '\u016A', umacr: '\u016B', uml: '\u00A8', UnderBar: '\u005F', UnderBrace: '\u23DF', UnderBracket: '\u23B5', UnderParenthesis: '\u23DD', Union: '\u22C3', UnionPlus: '\u228E', Uogon: '\u0172', uogon: '\u0173', Uopf: '\uD835\uDD4C', uopf: '\uD835\uDD66', UpArrow: '\u2191', Uparrow: '\u21D1', uparrow: '\u2191', UpArrowBar: '\u2912', UpArrowDownArrow: '\u21C5', UpDownArrow: '\u2195', Updownarrow: '\u21D5', updownarrow: '\u2195', UpEquilibrium: '\u296E', upharpoonleft: '\u21BF', upharpoonright: '\u21BE', uplus: '\u228E', UpperLeftArrow: '\u2196', UpperRightArrow: '\u2197', Upsi: '\u03D2', upsi: '\u03C5', upsih: '\u03D2', Upsilon: '\u03A5', upsilon: '\u03C5', UpTee: '\u22A5', UpTeeArrow: '\u21A5', upuparrows: '\u21C8', urcorn: '\u231D', urcorner: '\u231D', urcrop: '\u230E', Uring: '\u016E', uring: '\u016F', urtri: '\u25F9', Uscr: '\uD835\uDCB0', uscr: '\uD835\uDCCA', utdot: '\u22F0', Utilde: '\u0168', utilde: '\u0169', utri: '\u25B5', utrif: '\u25B4', uuarr: '\u21C8', Uuml: '\u00DC', uuml: '\u00FC', uwangle: '\u29A7', vangrt: '\u299C', varepsilon: '\u03F5', varkappa: '\u03F0', varnothing: '\u2205', varphi: '\u03D5', varpi: '\u03D6', varpropto: '\u221D', vArr: '\u21D5', varr: '\u2195', varrho: '\u03F1', varsigma: '\u03C2', varsubsetneq: '\u228A\uFE00', varsubsetneqq: '\u2ACB\uFE00', varsupsetneq: '\u228B\uFE00', varsupsetneqq: '\u2ACC\uFE00', vartheta: '\u03D1', vartriangleleft: '\u22B2', vartriangleright: '\u22B3', Vbar: '\u2AEB', vBar: '\u2AE8', vBarv: '\u2AE9', Vcy: '\u0412', vcy: '\u0432', VDash: '\u22AB', Vdash: '\u22A9', vDash: '\u22A8', vdash: '\u22A2', Vdashl: '\u2AE6', Vee: '\u22C1', vee: '\u2228', veebar: '\u22BB', veeeq: '\u225A', vellip: '\u22EE', Verbar: '\u2016', verbar: '\u007C', Vert: '\u2016', vert: '\u007C', VerticalBar: '\u2223', VerticalLine: '\u007C', VerticalSeparator: '\u2758', VerticalTilde: '\u2240', VeryThinSpace: '\u200A', Vfr: '\uD835\uDD19', vfr: '\uD835\uDD33', vltri: '\u22B2', vnsub: '\u2282\u20D2', vnsup: '\u2283\u20D2', Vopf: '\uD835\uDD4D', vopf: '\uD835\uDD67', vprop: '\u221D', vrtri: '\u22B3', Vscr: '\uD835\uDCB1', vscr: '\uD835\uDCCB', vsubnE: '\u2ACB\uFE00', vsubne: '\u228A\uFE00', vsupnE: '\u2ACC\uFE00', vsupne: '\u228B\uFE00', Vvdash: '\u22AA', vzigzag: '\u299A', Wcirc: '\u0174', wcirc: '\u0175', wedbar: '\u2A5F', Wedge: '\u22C0', wedge: '\u2227', wedgeq: '\u2259', weierp: '\u2118', Wfr: '\uD835\uDD1A', wfr: '\uD835\uDD34', Wopf: '\uD835\uDD4E', wopf: '\uD835\uDD68', wp: '\u2118', wr: '\u2240', wreath: '\u2240', Wscr: '\uD835\uDCB2', wscr: '\uD835\uDCCC', xcap: '\u22C2', xcirc: '\u25EF', xcup: '\u22C3', xdtri: '\u25BD', Xfr: '\uD835\uDD1B', xfr: '\uD835\uDD35', xhArr: '\u27FA', xharr: '\u27F7', Xi: '\u039E', xi: '\u03BE', xlArr: '\u27F8', xlarr: '\u27F5', xmap: '\u27FC', xnis: '\u22FB', xodot: '\u2A00', Xopf: '\uD835\uDD4F', xopf: '\uD835\uDD69', xoplus: '\u2A01', xotime: '\u2A02', xrArr: '\u27F9', xrarr: '\u27F6', Xscr: '\uD835\uDCB3', xscr: '\uD835\uDCCD', xsqcup: '\u2A06', xuplus: '\u2A04', xutri: '\u25B3', xvee: '\u22C1', xwedge: '\u22C0', Yacute: '\u00DD', yacute: '\u00FD', YAcy: '\u042F', yacy: '\u044F', Ycirc: '\u0176', ycirc: '\u0177', Ycy: '\u042B', ycy: '\u044B', yen: '\u00A5', Yfr: '\uD835\uDD1C', yfr: '\uD835\uDD36', YIcy: '\u0407', yicy: '\u0457', Yopf: '\uD835\uDD50', yopf: '\uD835\uDD6A', Yscr: '\uD835\uDCB4', yscr: '\uD835\uDCCE', YUcy: '\u042E', yucy: '\u044E', Yuml: '\u0178', yuml: '\u00FF', Zacute: '\u0179', zacute: '\u017A', Zcaron: '\u017D', zcaron: '\u017E', Zcy: '\u0417', zcy: '\u0437', Zdot: '\u017B', zdot: '\u017C', zeetrf: '\u2128', ZeroWidthSpace: '\u200B', Zeta: '\u0396', zeta: '\u03B6', Zfr: '\u2128', zfr: '\uD835\uDD37', ZHcy: '\u0416', zhcy: '\u0436', zigrarr: '\u21DD', Zopf: '\u2124', zopf: '\uD835\uDD6B', Zscr: '\uD835\uDCB5', zscr: '\uD835\uDCCF', zwj: '\u200D', zwnj: '\u200C', }); /** * @deprecated use `HTML_ENTITIES` instead * @see HTML_ENTITIES */ exports.entityMap = exports.HTML_ENTITIES; /***/ }), /***/ 969: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { var __webpack_unused_export__; var dom = __webpack_require__(146) __webpack_unused_export__ = dom.DOMImplementation __webpack_unused_export__ = dom.XMLSerializer exports.DOMParser = __webpack_require__(129).DOMParser /***/ }), /***/ 925: /***/ ((__unused_webpack_module, exports, __webpack_require__) => { var NAMESPACE = (__webpack_require__(167).NAMESPACE); //[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] //[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] //[5] Name ::= NameStartChar (NameChar)* var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]///\u10000-\uEFFFF var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"); var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$'); //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/ //var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',') //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE var S_TAG = 0;//tag name offerring var S_ATTR = 1;//attr name offerring var S_ATTR_SPACE=2;//attr name end and space offer var S_EQ = 3;//=space? var S_ATTR_NOQUOT_VALUE = 4;//attr value(no quot value only) var S_ATTR_END = 5;//attr value end and no space(quot end) var S_TAG_SPACE = 6;//(attr value end || tag end ) && (space offer) var S_TAG_CLOSE = 7;//closed el /** * Creates an error that will not be caught by XMLReader aka the SAX parser. * * @param {string} message * @param {any?} locator Optional, can provide details about the location in the source * @constructor */ function ParseError(message, locator) { this.message = message this.locator = locator if(Error.captureStackTrace) Error.captureStackTrace(this, ParseError); } ParseError.prototype = new Error(); ParseError.prototype.name = ParseError.name function XMLReader(){ } XMLReader.prototype = { parse:function(source,defaultNSMap,entityMap){ var domBuilder = this.domBuilder; domBuilder.startDocument(); _copy(defaultNSMap ,defaultNSMap = {}) parse(source,defaultNSMap,entityMap, domBuilder,this.errorHandler); domBuilder.endDocument(); } } function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){ function fixedFromCharCode(code) { // String.prototype.fromCharCode does not supports // > 2 bytes unicode chars directly if (code > 0xffff) { code -= 0x10000; var surrogate1 = 0xd800 + (code >> 10) , surrogate2 = 0xdc00 + (code & 0x3ff); return String.fromCharCode(surrogate1, surrogate2); } else { return String.fromCharCode(code); } } function entityReplacer(a){ var k = a.slice(1,-1); if (Object.hasOwnProperty.call(entityMap, k)) { return entityMap[k]; }else if(k.charAt(0) === '#'){ return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x'))) }else{ errorHandler.error('entity not found:'+a); return a; } } function appendText(end){//has some bugs if(end>start){ var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer); locator&&position(start); domBuilder.characters(xt,0,end-start); start = end } } function position(p,m){ while(p>=lineEnd && (m = linePattern.exec(source))){ lineStart = m.index; lineEnd = lineStart + m[0].length; locator.lineNumber++; //console.log('line++:',locator,startPos,endPos) } locator.columnNumber = p-lineStart+1; } var lineStart = 0; var lineEnd = 0; var linePattern = /.*(?:\r\n?|\n)|.*$/g var locator = domBuilder.locator; var parseStack = [{currentNSMap:defaultNSMapCopy}] var closeMap = {}; var start = 0; while(true){ try{ var tagStart = source.indexOf('<',start); if(tagStart<0){ if(!source.substr(start).match(/^\s*$/)){ var doc = domBuilder.doc; var text = doc.createTextNode(source.substr(start)); doc.appendChild(text); domBuilder.currentElement = text; } return; } if(tagStart>start){ appendText(tagStart); } switch(source.charAt(tagStart+1)){ case '/': var end = source.indexOf('>',tagStart+3); var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, ''); var config = parseStack.pop(); if(end<0){ tagName = source.substring(tagStart+2).replace(/[\s<].*/,''); errorHandler.error("end tag name: "+tagName+' is not complete:'+config.tagName); end = tagStart+1+tagName.length; }else if(tagName.match(/\s locator&&position(tagStart); end = parseInstruction(source,tagStart,domBuilder); break; case '!':// start){ start = end; }else{ //TODO: 这里有可能sax回退,有位置错误风险 appendText(Math.max(tagStart,start)+1); } } } function copyLocator(f,t){ t.lineNumber = f.lineNumber; t.columnNumber = f.columnNumber; return t; } /** * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack); * @return end of the elementStartPart(end of elementEndPart for selfClosed el) */ function parseElementStartPart(source,start,el,currentNSMap,entityReplacer,errorHandler){ /** * @param {string} qname * @param {string} value * @param {number} startIndex */ function addAttribute(qname, value, startIndex) { if (el.attributeNames.hasOwnProperty(qname)) { errorHandler.fatalError('Attribute ' + qname + ' redefined') } el.addValue( qname, // @see https://www.w3.org/TR/xml/#AVNormalize // since the xmldom sax parser does not "interpret" DTD the following is not implemented: // - recursive replacement of (DTD) entity references // - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA value.replace(/[\t\n\r]/g, ' ').replace(/&#?\w+;/g, entityReplacer), startIndex ) } var attrName; var value; var p = ++start; var s = S_TAG;//status while(true){ var c = source.charAt(p); switch(c){ case '=': if(s === S_ATTR){//attrName attrName = source.slice(start,p); s = S_EQ; }else if(s === S_ATTR_SPACE){ s = S_EQ; }else{ //fatalError: equal must after attrName or space after attrName throw new Error('attribute equal must after attrName'); // No known test case } break; case '\'': case '"': if(s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE ){//equal if(s === S_ATTR){ errorHandler.warning('attribute value must after "="') attrName = source.slice(start,p) } start = p+1; p = source.indexOf(c,start) if(p>0){ value = source.slice(start, p); addAttribute(attrName, value, start-1); s = S_ATTR_END; }else{ //fatalError: no end quot match throw new Error('attribute value no end \''+c+'\' match'); } }else if(s == S_ATTR_NOQUOT_VALUE){ value = source.slice(start, p); addAttribute(attrName, value, start); errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!'); start = p+1; s = S_ATTR_END }else{ //fatalError: no equal before throw new Error('attribute value must after "="'); // No known test case } break; case '/': switch(s){ case S_TAG: el.setTagName(source.slice(start,p)); case S_ATTR_END: case S_TAG_SPACE: case S_TAG_CLOSE: s =S_TAG_CLOSE; el.closed = true; case S_ATTR_NOQUOT_VALUE: case S_ATTR: break; case S_ATTR_SPACE: el.closed = true; break; //case S_EQ: default: throw new Error("attribute invalid close char('/')") // No known test case } break; case ''://end document errorHandler.error('unexpected end of input'); if(s == S_TAG){ el.setTagName(source.slice(start,p)); } return p; case '>': switch(s){ case S_TAG: el.setTagName(source.slice(start,p)); case S_ATTR_END: case S_TAG_SPACE: case S_TAG_CLOSE: break;//normal case S_ATTR_NOQUOT_VALUE://Compatible state case S_ATTR: value = source.slice(start,p); if(value.slice(-1) === '/'){ el.closed = true; value = value.slice(0,-1) } case S_ATTR_SPACE: if(s === S_ATTR_SPACE){ value = attrName; } if(s == S_ATTR_NOQUOT_VALUE){ errorHandler.warning('attribute "'+value+'" missed quot(")!'); addAttribute(attrName, value, start) }else{ if(!NAMESPACE.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)){ errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!') } addAttribute(value, value, start) } break; case S_EQ: throw new Error('attribute value missed!!'); } // console.log(tagName,tagNamePattern,tagNamePattern.test(tagName)) return p; /*xml space '\x20' | #x9 | #xD | #xA; */ case '\u0080': c = ' '; default: if(c<= ' '){//space switch(s){ case S_TAG: el.setTagName(source.slice(start,p));//tagName s = S_TAG_SPACE; break; case S_ATTR: attrName = source.slice(start,p) s = S_ATTR_SPACE; break; case S_ATTR_NOQUOT_VALUE: var value = source.slice(start, p); errorHandler.warning('attribute "'+value+'" missed quot(")!!'); addAttribute(attrName, value, start) case S_ATTR_END: s = S_TAG_SPACE; break; //case S_TAG_SPACE: //case S_EQ: //case S_ATTR_SPACE: // void();break; //case S_TAG_CLOSE: //ignore warning } }else{//not space //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE switch(s){ //case S_TAG:void();break; //case S_ATTR:void();break; //case S_ATTR_NOQUOT_VALUE:void();break; case S_ATTR_SPACE: var tagName = el.tagName; if (!NAMESPACE.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) { errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead2!!') } addAttribute(attrName, attrName, start); start = p; s = S_ATTR; break; case S_ATTR_END: errorHandler.warning('attribute space is required"'+attrName+'"!!') case S_TAG_SPACE: s = S_ATTR; start = p; break; case S_EQ: s = S_ATTR_NOQUOT_VALUE; start = p; break; case S_TAG_CLOSE: throw new Error("elements closed character '/' and '>' must be connected to"); } } }//end outer switch //console.log('p++',p) p++; } } /** * @return true if has new namespace define */ function appendElement(el,domBuilder,currentNSMap){ var tagName = el.tagName; var localNSMap = null; //var currentNSMap = parseStack[parseStack.length-1].currentNSMap; var i = el.length; while(i--){ var a = el[i]; var qName = a.qName; var value = a.value; var nsp = qName.indexOf(':'); if(nsp>0){ var prefix = a.prefix = qName.slice(0,nsp); var localName = qName.slice(nsp+1); var nsPrefix = prefix === 'xmlns' && localName }else{ localName = qName; prefix = null nsPrefix = qName === 'xmlns' && '' } //can not set prefix,because prefix !== '' a.localName = localName ; //prefix == null for no ns prefix attribute if(nsPrefix !== false){//hack!! if(localNSMap == null){ localNSMap = {} //console.log(currentNSMap,0) _copy(currentNSMap,currentNSMap={}) //console.log(currentNSMap,1) } currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; a.uri = NAMESPACE.XMLNS domBuilder.startPrefixMapping(nsPrefix, value) } } var i = el.length; while(i--){ a = el[i]; var prefix = a.prefix; if(prefix){//no prefix attribute has no namespace if(prefix === 'xml'){ a.uri = NAMESPACE.XML; }if(prefix !== 'xmlns'){ a.uri = currentNSMap[prefix || ''] //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)} } } } var nsp = tagName.indexOf(':'); if(nsp>0){ prefix = el.prefix = tagName.slice(0,nsp); localName = el.localName = tagName.slice(nsp+1); }else{ prefix = null;//important!! localName = el.localName = tagName; } //no prefix element has default namespace var ns = el.uri = currentNSMap[prefix || '']; domBuilder.startElement(ns,localName,tagName,el); //endPrefixMapping and startPrefixMapping have not any help for dom builder //localNSMap = null if(el.closed){ domBuilder.endElement(ns,localName,tagName); if(localNSMap){ for (prefix in localNSMap) { if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) { domBuilder.endPrefixMapping(prefix); } } } }else{ el.currentNSMap = currentNSMap; el.localNSMap = localNSMap; //parseStack.push(el); return true; } } function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){ if(/^(?:script|textarea)$/i.test(tagName)){ var elEndStart = source.indexOf('',elStartEnd); var text = source.substring(elStartEnd+1,elEndStart); if(/[&<]/.test(text)){ if(/^script$/i.test(tagName)){ //if(!/\]\]>/.test(text)){ //lexHandler.startCDATA(); domBuilder.characters(text,0,text.length); //lexHandler.endCDATA(); return elEndStart; //} }//}else{//text area text = text.replace(/&#?\w+;/g,entityReplacer); domBuilder.characters(text,0,text.length); return elEndStart; //} } } return elStartEnd+1; } function fixSelfClosed(source,elStartEnd,tagName,closeMap){ //if(tagName in closeMap){ var pos = closeMap[tagName]; if(pos == null){ //console.log(tagName) pos = source.lastIndexOf('') if(pos',start+4); //append comment source.substring(4,end)//") { // (3) next characters must match "-->" throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput); } input = input.substr(3); skipWhitespace(); cue.endTime = consumeTimeStamp(); // (5) collect cue end time // 4.1 WebVTT cue settings list. skipWhitespace(); consumeCueSettings(input, cue); } // When evaluating this file as part of a Webpack bundle for server // side rendering, `document` is an empty object. var TEXTAREA_ELEMENT = document.createElement && document.createElement("textarea"); var TAG_NAME = { c: "span", i: "i", b: "b", u: "u", ruby: "ruby", rt: "rt", v: "span", lang: "span" }; // 5.1 default text color // 5.2 default text background color is equivalent to text color with bg_ prefix var DEFAULT_COLOR_CLASS = { white: 'rgba(255,255,255,1)', lime: 'rgba(0,255,0,1)', cyan: 'rgba(0,255,255,1)', red: 'rgba(255,0,0,1)', yellow: 'rgba(255,255,0,1)', magenta: 'rgba(255,0,255,1)', blue: 'rgba(0,0,255,1)', black: 'rgba(0,0,0,1)' }; var TAG_ANNOTATION = { v: "title", lang: "lang" }; var NEEDS_PARENT = { rt: "ruby" }; // Parse content into a document fragment. function parseContent(window, input) { function nextToken() { // Check for end-of-string. if (!input) { return null; } // Consume 'n' characters from the input. function consume(result) { input = input.substr(result.length); return result; } var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return // the tag. return consume(m[1] ? m[1] : m[2]); } function unescape(s) { TEXTAREA_ELEMENT.innerHTML = s; s = TEXTAREA_ELEMENT.textContent; TEXTAREA_ELEMENT.textContent = ""; return s; } function shouldAdd(current, element) { return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName; } // Create an element for this tag. function createElement(type, annotation) { var tagName = TAG_NAME[type]; if (!tagName) { return null; } var element = window.document.createElement(tagName); var name = TAG_ANNOTATION[type]; if (name && annotation) { element[name] = annotation.trim(); } return element; } var rootDiv = window.document.createElement("div"), current = rootDiv, t, tagStack = []; while ((t = nextToken()) !== null) { if (t[0] === '<') { if (t[1] === "/") { // If the closing tag matches, move back up to the parent node. if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { tagStack.pop(); current = current.parentNode; } // Otherwise just ignore the end tag. continue; } var ts = parseTimeStamp(t.substr(1, t.length - 2)); var node; if (ts) { // Timestamps are lead nodes as well. node = window.document.createProcessingInstruction("timestamp", ts); current.appendChild(node); continue; } var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag. if (!m) { continue; } // Try to construct an element, and ignore the tag if we couldn't. node = createElement(m[1], m[3]); if (!node) { continue; } // Determine if the tag should be added based on the context of where it // is placed in the cuetext. if (!shouldAdd(current, node)) { continue; } // Set the class list (as a list of classes, separated by space). if (m[2]) { var classes = m[2].split('.'); classes.forEach(function(cl) { var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color var colorName = bgColor ? cl.slice(3) : cl; if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) { var propName = bgColor ? 'background-color' : 'color'; var propValue = DEFAULT_COLOR_CLASS[colorName]; node.style[propName] = propValue; } }); node.className = classes.join(' '); } // Append the node to the current node, and enter the scope of the new // node. tagStack.push(m[1]); current.appendChild(node); current = node; continue; } // Text nodes are leaf nodes. current.appendChild(window.document.createTextNode(unescape(t))); } return rootDiv; } // This is a list of all the Unicode characters that have a strong // right-to-left category. What this means is that these characters are // written right-to-left for sure. It was generated by pulling all the strong // right-to-left characters out of the Unicode data table. That table can // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]]; function isStrongRTLChar(charCode) { for (var i = 0; i < strongRTLRanges.length; i++) { var currentRange = strongRTLRanges[i]; if (charCode >= currentRange[0] && charCode <= currentRange[1]) { return true; } } return false; } function determineBidi(cueDiv) { var nodeStack = [], text = "", charCode; if (!cueDiv || !cueDiv.childNodes) { return "ltr"; } function pushNodes(nodeStack, node) { for (var i = node.childNodes.length - 1; i >= 0; i--) { nodeStack.push(node.childNodes[i]); } } function nextTextNode(nodeStack) { if (!nodeStack || !nodeStack.length) { return null; } var node = nodeStack.pop(), text = node.textContent || node.innerText; if (text) { // TODO: This should match all unicode type B characters (paragraph // separator characters). See issue #115. var m = text.match(/^.*(\n|\r)/); if (m) { nodeStack.length = 0; return m[0]; } return text; } if (node.tagName === "ruby") { return nextTextNode(nodeStack); } if (node.childNodes) { pushNodes(nodeStack, node); return nextTextNode(nodeStack); } } pushNodes(nodeStack, cueDiv); while ((text = nextTextNode(nodeStack))) { for (var i = 0; i < text.length; i++) { charCode = text.charCodeAt(i); if (isStrongRTLChar(charCode)) { return "rtl"; } } } return "ltr"; } function computeLinePos(cue) { if (typeof cue.line === "number" && (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) { return cue.line; } if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) { return -1; } var track = cue.track, trackList = track.textTrackList, count = 0; for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { if (trackList[i].mode === "showing") { count++; } } return ++count * -1; } function StyleBox() { } // Apply styles to a div. If there is no div passed then it defaults to the // div on 'this'. StyleBox.prototype.applyStyles = function(styles, div) { div = div || this.div; for (var prop in styles) { if (styles.hasOwnProperty(prop)) { div.style[prop] = styles[prop]; } } }; StyleBox.prototype.formatStyle = function(val, unit) { return val === 0 ? 0 : val + unit; }; // Constructs the computed display state of the cue (a div). Places the div // into the overlay which should be a block level element (usually a div). function CueStyleBox(window, cue, styleOptions) { StyleBox.call(this); this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will // have inline positioning and will function as the cue background box. this.cueDiv = parseContent(window, cue.text); var styles = { color: "rgba(255, 255, 255, 1)", backgroundColor: "rgba(0, 0, 0, 0.8)", position: "relative", left: 0, right: 0, top: 0, bottom: 0, display: "inline", writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", unicodeBidi: "plaintext" }; this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS // mirrors of them except middle instead of center on Safari. this.div = window.document.createElement("div"); styles = { direction: determineBidi(this.cueDiv), writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", unicodeBidi: "plaintext", textAlign: cue.align === "middle" ? "center" : cue.align, font: styleOptions.font, whiteSpace: "pre-line", position: "absolute" }; this.applyStyles(styles); this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text // position of the cue box. The reference edge will be resolved later when // the box orientation styles are applied. var textPos = 0; switch (cue.positionAlign) { case "start": case "line-left": textPos = cue.position; break; case "center": textPos = cue.position - (cue.size / 2); break; case "end": case "line-right": textPos = cue.position - cue.size; break; } // Horizontal box orientation; textPos is the distance from the left edge of the // area to the left edge of the box and cue.size is the distance extending to // the right from there. if (cue.vertical === "") { this.applyStyles({ left: this.formatStyle(textPos, "%"), width: this.formatStyle(cue.size, "%") }); // Vertical box orientation; textPos is the distance from the top edge of the // area to the top edge of the box and cue.size is the height extending // downwards from there. } else { this.applyStyles({ top: this.formatStyle(textPos, "%"), height: this.formatStyle(cue.size, "%") }); } this.move = function(box) { this.applyStyles({ top: this.formatStyle(box.top, "px"), bottom: this.formatStyle(box.bottom, "px"), left: this.formatStyle(box.left, "px"), right: this.formatStyle(box.right, "px"), height: this.formatStyle(box.height, "px"), width: this.formatStyle(box.width, "px") }); }; } CueStyleBox.prototype = _objCreate(StyleBox.prototype); CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily // compute things with such as if it overlaps or intersects with another Element. // Can initialize it with either a StyleBox or another BoxPosition. function BoxPosition(obj) { // Either a BoxPosition was passed in and we need to copy it, or a StyleBox // was passed in and we need to copy the results of 'getBoundingClientRect' // as the object returned is readonly. All co-ordinate values are in reference // to the viewport origin (top left). var lh, height, width, top; if (obj.div) { height = obj.div.offsetHeight; width = obj.div.offsetWidth; top = obj.div.offsetTop; var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of // the inner div's lines. This could be due to bold text, etc, on some platforms. // In this case we should get the average line height and use that. This will // result in the desired behaviour. lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length) : 0; } this.left = obj.left; this.right = obj.right; this.top = obj.top || top; this.height = obj.height || height; this.bottom = obj.bottom || (top + (obj.height || height)); this.width = obj.width || width; this.lineHeight = lh !== undefined ? lh : obj.lineHeight; } // Move the box along a particular axis. Optionally pass in an amount to move // the box. If no amount is passed then the default is the line height of the // box. BoxPosition.prototype.move = function(axis, toMove) { toMove = toMove !== undefined ? toMove : this.lineHeight; switch (axis) { case "+x": this.left += toMove; this.right += toMove; break; case "-x": this.left -= toMove; this.right -= toMove; break; case "+y": this.top += toMove; this.bottom += toMove; break; case "-y": this.top -= toMove; this.bottom -= toMove; break; } }; // Check if this box overlaps another box, b2. BoxPosition.prototype.overlaps = function(b2) { return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top; }; // Check if this box overlaps any other boxes in boxes. BoxPosition.prototype.overlapsAny = function(boxes) { for (var i = 0; i < boxes.length; i++) { if (this.overlaps(boxes[i])) { return true; } } return false; }; // Check if this box is within another box. BoxPosition.prototype.within = function(container) { return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right; }; // Check if this box is entirely within the container or it is overlapping // on the edge opposite of the axis direction passed. For example, if "+x" is // passed and the box is overlapping on the left edge of the container, then // return true. BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) { switch (axis) { case "+x": return this.left < container.left; case "-x": return this.right > container.right; case "+y": return this.top < container.top; case "-y": return this.bottom > container.bottom; } }; // Find the percentage of the area that this box is overlapping with another // box. BoxPosition.prototype.intersectPercentage = function(b2) { var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), intersectArea = x * y; return intersectArea / (this.height * this.width); }; // Convert the positions from this box to CSS compatible positions using // the reference container's positions. This has to be done because this // box's positions are in reference to the viewport origin, whereas, CSS // values are in referecne to their respective edges. BoxPosition.prototype.toCSSCompatValues = function(reference) { return { top: this.top - reference.top, bottom: reference.bottom - this.bottom, left: this.left - reference.left, right: reference.right - this.right, height: this.height, width: this.width }; }; // Get an object that represents the box's position without anything extra. // Can pass a StyleBox, HTMLElement, or another BoxPositon. BoxPosition.getSimpleBoxPosition = function(obj) { var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; var ret = { left: obj.left, right: obj.right, top: obj.top || top, height: obj.height || height, bottom: obj.bottom || (top + (obj.height || height)), width: obj.width || width }; return ret; }; // Move a StyleBox to its specified, or next best, position. The containerBox // is the box that contains the StyleBox, such as a div. boxPositions are // a list of other boxes that the styleBox can't overlap with. function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { // Find the best position for a cue box, b, on the video. The axis parameter // is a list of axis, the order of which, it will move the box along. For example: // Passing ["+x", "-x"] will move the box first along the x axis in the positive // direction. If it doesn't find a good position for it there it will then move // it along the x axis in the negative direction. function findBestPosition(b, axis) { var bestPosition, specifiedPosition = new BoxPosition(b), percentage = 1; // Highest possible so the first thing we get is better. for (var i = 0; i < axis.length; i++) { while (b.overlapsOppositeAxis(containerBox, axis[i]) || (b.within(containerBox) && b.overlapsAny(boxPositions))) { b.move(axis[i]); } // We found a spot where we aren't overlapping anything. This is our // best position. if (b.within(containerBox)) { return b; } var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try // then remember this position as the best position. if (percentage > p) { bestPosition = new BoxPosition(b); percentage = p; } // Reset the box position to the specified position. b = new BoxPosition(specifiedPosition); } return bestPosition || specifiedPosition; } var boxPosition = new BoxPosition(styleBox), cue = styleBox.cue, linePos = computeLinePos(cue), axis = []; // If we have a line number to align the cue to. if (cue.snapToLines) { var size; switch (cue.vertical) { case "": axis = [ "+y", "-y" ]; size = "height"; break; case "rl": axis = [ "+x", "-x" ]; size = "width"; break; case "lr": axis = [ "-x", "+x" ]; size = "width"; break; } var step = boxPosition.lineHeight, position = step * Math.round(linePos), maxPosition = containerBox[size] + step, initialAxis = axis[0]; // If the specified intial position is greater then the max position then // clamp the box to the amount of steps it would take for the box to // reach the max position. if (Math.abs(position) > maxPosition) { position = position < 0 ? -1 : 1; position *= Math.ceil(maxPosition / step) * step; } // If computed line position returns negative then line numbers are // relative to the bottom of the video instead of the top. Therefore, we // need to increase our initial position by the length or width of the // video, depending on the writing direction, and reverse our axis directions. if (linePos < 0) { position += cue.vertical === "" ? containerBox.height : containerBox.width; axis = axis.reverse(); } // Move the box to the specified position. This may not be its best // position. boxPosition.move(initialAxis, position); } else { // If we have a percentage line value for the cue. var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100; switch (cue.lineAlign) { case "center": linePos -= (calculatedPercentage / 2); break; case "end": linePos -= calculatedPercentage; break; } // Apply initial line position to the cue box. switch (cue.vertical) { case "": styleBox.applyStyles({ top: styleBox.formatStyle(linePos, "%") }); break; case "rl": styleBox.applyStyles({ left: styleBox.formatStyle(linePos, "%") }); break; case "lr": styleBox.applyStyles({ right: styleBox.formatStyle(linePos, "%") }); break; } axis = [ "+y", "-x", "+x", "-y" ]; // Get the box position again after we've applied the specified positioning // to it. boxPosition = new BoxPosition(styleBox); } var bestPosition = findBestPosition(boxPosition, axis); styleBox.move(bestPosition.toCSSCompatValues(containerBox)); } function WebVTT() { // Nothing } // Helper to allow strings to be decoded instead of the default binary utf8 data. WebVTT.StringDecoder = function() { return { decode: function(data) { if (!data) { return ""; } if (typeof data !== "string") { throw new Error("Error - expected string data."); } return decodeURIComponent(encodeURIComponent(data)); } }; }; WebVTT.convertCueToDOMTree = function(window, cuetext) { if (!window || !cuetext) { return null; } return parseContent(window, cuetext); }; var FONT_SIZE_PERCENT = 0.05; var FONT_STYLE = "sans-serif"; var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it. // @param overlay A block level element (usually a div) that the computed cues // and regions will be placed into. WebVTT.processCues = function(window, cues, overlay) { if (!window || !cues || !overlay) { return null; } // Remove all previous children. while (overlay.firstChild) { overlay.removeChild(overlay.firstChild); } var paddedOverlay = window.document.createElement("div"); paddedOverlay.style.position = "absolute"; paddedOverlay.style.left = "0"; paddedOverlay.style.right = "0"; paddedOverlay.style.top = "0"; paddedOverlay.style.bottom = "0"; paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could // be the case if a cue's state has been changed since the last computation or // if it has not been computed yet. function shouldCompute(cues) { for (var i = 0; i < cues.length; i++) { if (cues[i].hasBeenReset || !cues[i].displayState) { return true; } } return false; } // We don't need to recompute the cues' display states. Just reuse them. if (!shouldCompute(cues)) { for (var i = 0; i < cues.length; i++) { paddedOverlay.appendChild(cues[i].displayState); } return; } var boxPositions = [], containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; var styleOptions = { font: fontSize + "px " + FONT_STYLE }; (function() { var styleBox, cue; for (var i = 0; i < cues.length; i++) { cue = cues[i]; // Compute the intial position and styles of the cue div. styleBox = new CueStyleBox(window, cue, styleOptions); paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later // if we don't have too. cue.displayState = styleBox.div; boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); } })(); }; WebVTT.Parser = function(window, vttjs, decoder) { if (!decoder) { decoder = vttjs; vttjs = {}; } if (!vttjs) { vttjs = {}; } this.window = window; this.vttjs = vttjs; this.state = "INITIAL"; this.buffer = ""; this.decoder = decoder || new TextDecoder("utf8"); this.regionList = []; }; WebVTT.Parser.prototype = { // If the error is a ParsingError then report it to the consumer if // possible. If it's not a ParsingError then throw it like normal. reportOrThrowError: function(e) { if (e instanceof ParsingError) { this.onparsingerror && this.onparsingerror(e); } else { throw e; } }, parse: function (data) { var self = this; // If there is no data then we won't decode it, but will just try to parse // whatever is in buffer already. This may occur in circumstances, for // example when flush() is called. if (data) { // Try to decode the data that we received. self.buffer += self.decoder.decode(data, {stream: true}); } function collectNextLine() { var buffer = self.buffer; var pos = 0; while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { ++pos; } var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below. if (buffer[pos] === '\r') { ++pos; } if (buffer[pos] === '\n') { ++pos; } self.buffer = buffer.substr(pos); return line; } // 3.4 WebVTT region and WebVTT region settings syntax function parseRegion(input) { var settings = new Settings(); parseOptions(input, function (k, v) { switch (k) { case "id": settings.set(k, v); break; case "width": settings.percent(k, v); break; case "lines": settings.integer(k, v); break; case "regionanchor": case "viewportanchor": var xy = v.split(','); if (xy.length !== 2) { break; } // We have to make sure both x and y parse, so use a temporary // settings object here. var anchor = new Settings(); anchor.percent("x", xy[0]); anchor.percent("y", xy[1]); if (!anchor.has("x") || !anchor.has("y")) { break; } settings.set(k + "X", anchor.get("x")); settings.set(k + "Y", anchor.get("y")); break; case "scroll": settings.alt(k, v, ["up"]); break; } }, /=/, /\s/); // Create the region, using default values for any values that were not // specified. if (settings.has("id")) { var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); region.width = settings.get("width", 100); region.lines = settings.get("lines", 3); region.regionAnchorX = settings.get("regionanchorX", 0); region.regionAnchorY = settings.get("regionanchorY", 100); region.viewportAnchorX = settings.get("viewportanchorX", 0); region.viewportAnchorY = settings.get("viewportanchorY", 100); region.scroll = settings.get("scroll", ""); // Register the region. self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that // reference it. self.regionList.push({ id: settings.get("id"), region: region }); } } // draft-pantos-http-live-streaming-20 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 // 3.5 WebVTT function parseTimestampMap(input) { var settings = new Settings(); parseOptions(input, function(k, v) { switch(k) { case "MPEGT": settings.integer(k + 'S', v); break; case "LOCA": settings.set(k + 'L', parseTimeStamp(v)); break; } }, /[^\d]:/, /,/); self.ontimestampmap && self.ontimestampmap({ "MPEGTS": settings.get("MPEGTS"), "LOCAL": settings.get("LOCAL") }); } // 3.2 WebVTT metadata header syntax function parseHeader(input) { if (input.match(/X-TIMESTAMP-MAP/)) { // This line contains HLS X-TIMESTAMP-MAP metadata parseOptions(input, function(k, v) { switch(k) { case "X-TIMESTAMP-MAP": parseTimestampMap(v); break; } }, /=/); } else { parseOptions(input, function (k, v) { switch (k) { case "Region": // 3.3 WebVTT region metadata header syntax parseRegion(v); break; } }, /:/); } } // 5.1 WebVTT file parsing. try { var line; if (self.state === "INITIAL") { // We can't start parsing until we have the first line. if (!/\r\n|\n/.test(self.buffer)) { return this; } line = collectNextLine(); var m = line.match(/^WEBVTT([ \t].*)?$/); if (!m || !m[0]) { throw new ParsingError(ParsingError.Errors.BadSignature); } self.state = "HEADER"; } var alreadyCollectedLine = false; while (self.buffer) { // We can't parse a line until we have the full line. if (!/\r\n|\n/.test(self.buffer)) { return this; } if (!alreadyCollectedLine) { line = collectNextLine(); } else { alreadyCollectedLine = false; } switch (self.state) { case "HEADER": // 13-18 - Allow a header (metadata) under the WEBVTT line. if (/:/.test(line)) { parseHeader(line); } else if (!line) { // An empty line terminates the header and starts the body (cues). self.state = "ID"; } continue; case "NOTE": // Ignore NOTE blocks. if (!line) { self.state = "ID"; } continue; case "ID": // Check for the start of NOTE blocks. if (/^NOTE($|[ \t])/.test(line)) { self.state = "NOTE"; break; } // 19-29 - Allow any number of line terminators, then initialize new cue values. if (!line) { continue; } self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center try { self.cue.align = "center"; } catch (e) { self.cue.align = "middle"; } self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data. if (line.indexOf("-->") === -1) { self.cue.id = line; continue; } // Process line as start of a cue. /*falls through*/ case "CUE": // 40 - Collect cue timings and settings. try { parseCue(line, self.cue, self.regionList); } catch (e) { self.reportOrThrowError(e); // In case of an error ignore rest of the cue. self.cue = null; self.state = "BADCUE"; continue; } self.state = "CUETEXT"; continue; case "CUETEXT": var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue. // 35 - If we have the special substring '-->' then report the cue, // but do not collect the line as we need to process the current // one as a new cue. if (!line || hasSubstring && (alreadyCollectedLine = true)) { // We are done parsing self cue. self.oncue && self.oncue(self.cue); self.cue = null; self.state = "ID"; continue; } if (self.cue.text) { self.cue.text += "\n"; } self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n'); continue; case "BADCUE": // BADCUE // 54-62 - Collect and discard the remaining cue. if (!line) { self.state = "ID"; } continue; } } } catch (e) { self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have. if (self.state === "CUETEXT" && self.cue && self.oncue) { self.oncue(self.cue); } self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise // another exception occurred so enter BADCUE state. self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; } return this; }, flush: function () { var self = this; try { // Finish decoding the stream. self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region. if (self.cue || self.state === "HEADER") { self.buffer += "\n\n"; self.parse(); } // If we've flushed, parsed, and we're still on the INITIAL state then // that means we don't have enough of the stream to parse the first // line. if (self.state === "INITIAL") { throw new ParsingError(ParsingError.Errors.BadSignature); } } catch(e) { self.reportOrThrowError(e); } self.onflush && self.onflush(); return this; } }; module.exports = WebVTT; /***/ }), /***/ 230: /***/ ((module) => { /** * Copyright 2013 vtt.js Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var autoKeyword = "auto"; var directionSetting = { "": 1, "lr": 1, "rl": 1 }; var alignSetting = { "start": 1, "center": 1, "end": 1, "left": 1, "right": 1, "auto": 1, "line-left": 1, "line-right": 1 }; function findDirectionSetting(value) { if (typeof value !== "string") { return false; } var dir = directionSetting[value.toLowerCase()]; return dir ? value.toLowerCase() : false; } function findAlignSetting(value) { if (typeof value !== "string") { return false; } var align = alignSetting[value.toLowerCase()]; return align ? value.toLowerCase() : false; } function VTTCue(startTime, endTime, text) { /** * Shim implementation specific properties. These properties are not in * the spec. */ // Lets us know when the VTTCue's data has changed in such a way that we need // to recompute its display state. This lets us compute its display state // lazily. this.hasBeenReset = false; /** * VTTCue and TextTrackCue properties * http://dev.w3.org/html5/webvtt/#vttcue-interface */ var _id = ""; var _pauseOnExit = false; var _startTime = startTime; var _endTime = endTime; var _text = text; var _region = null; var _vertical = ""; var _snapToLines = true; var _line = "auto"; var _lineAlign = "start"; var _position = "auto"; var _positionAlign = "auto"; var _size = 100; var _align = "center"; Object.defineProperties(this, { "id": { enumerable: true, get: function() { return _id; }, set: function(value) { _id = "" + value; } }, "pauseOnExit": { enumerable: true, get: function() { return _pauseOnExit; }, set: function(value) { _pauseOnExit = !!value; } }, "startTime": { enumerable: true, get: function() { return _startTime; }, set: function(value) { if (typeof value !== "number") { throw new TypeError("Start time must be set to a number."); } _startTime = value; this.hasBeenReset = true; } }, "endTime": { enumerable: true, get: function() { return _endTime; }, set: function(value) { if (typeof value !== "number") { throw new TypeError("End time must be set to a number."); } _endTime = value; this.hasBeenReset = true; } }, "text": { enumerable: true, get: function() { return _text; }, set: function(value) { _text = "" + value; this.hasBeenReset = true; } }, "region": { enumerable: true, get: function() { return _region; }, set: function(value) { _region = value; this.hasBeenReset = true; } }, "vertical": { enumerable: true, get: function() { return _vertical; }, set: function(value) { var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. if (setting === false) { throw new SyntaxError("Vertical: an invalid or illegal direction string was specified."); } _vertical = setting; this.hasBeenReset = true; } }, "snapToLines": { enumerable: true, get: function() { return _snapToLines; }, set: function(value) { _snapToLines = !!value; this.hasBeenReset = true; } }, "line": { enumerable: true, get: function() { return _line; }, set: function(value) { if (typeof value !== "number" && value !== autoKeyword) { throw new SyntaxError("Line: an invalid number or illegal string was specified."); } _line = value; this.hasBeenReset = true; } }, "lineAlign": { enumerable: true, get: function() { return _lineAlign; }, set: function(value) { var setting = findAlignSetting(value); if (!setting) { console.warn("lineAlign: an invalid or illegal string was specified."); } else { _lineAlign = setting; this.hasBeenReset = true; } } }, "position": { enumerable: true, get: function() { return _position; }, set: function(value) { if (value < 0 || value > 100) { throw new Error("Position must be between 0 and 100."); } _position = value; this.hasBeenReset = true; } }, "positionAlign": { enumerable: true, get: function() { return _positionAlign; }, set: function(value) { var setting = findAlignSetting(value); if (!setting) { console.warn("positionAlign: an invalid or illegal string was specified."); } else { _positionAlign = setting; this.hasBeenReset = true; } } }, "size": { enumerable: true, get: function() { return _size; }, set: function(value) { if (value < 0 || value > 100) { throw new Error("Size must be between 0 and 100."); } _size = value; this.hasBeenReset = true; } }, "align": { enumerable: true, get: function() { return _align; }, set: function(value) { var setting = findAlignSetting(value); if (!setting) { throw new SyntaxError("align: an invalid or illegal alignment string was specified."); } _align = setting; this.hasBeenReset = true; } } }); /** * Other spec defined properties */ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state this.displayState = undefined; } /** * VTTCue methods */ VTTCue.prototype.getCueAsHTML = function() { // Assume WebVTT.convertCueToDOMTree is on the global. return WebVTT.convertCueToDOMTree(window, this.text); }; module.exports = VTTCue; /***/ }), /***/ 710: /***/ ((module) => { /** * Copyright 2013 vtt.js Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var scrollSetting = { "": true, "up": true }; function findScrollSetting(value) { if (typeof value !== "string") { return false; } var scroll = scrollSetting[value.toLowerCase()]; return scroll ? value.toLowerCase() : false; } function isValidPercentValue(value) { return typeof value === "number" && (value >= 0 && value <= 100); } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface function VTTRegion() { var _width = 100; var _lines = 3; var _regionAnchorX = 0; var _regionAnchorY = 100; var _viewportAnchorX = 0; var _viewportAnchorY = 100; var _scroll = ""; Object.defineProperties(this, { "width": { enumerable: true, get: function() { return _width; }, set: function(value) { if (!isValidPercentValue(value)) { throw new Error("Width must be between 0 and 100."); } _width = value; } }, "lines": { enumerable: true, get: function() { return _lines; }, set: function(value) { if (typeof value !== "number") { throw new TypeError("Lines must be set to a number."); } _lines = value; } }, "regionAnchorY": { enumerable: true, get: function() { return _regionAnchorY; }, set: function(value) { if (!isValidPercentValue(value)) { throw new Error("RegionAnchorX must be between 0 and 100."); } _regionAnchorY = value; } }, "regionAnchorX": { enumerable: true, get: function() { return _regionAnchorX; }, set: function(value) { if(!isValidPercentValue(value)) { throw new Error("RegionAnchorY must be between 0 and 100."); } _regionAnchorX = value; } }, "viewportAnchorY": { enumerable: true, get: function() { return _viewportAnchorY; }, set: function(value) { if (!isValidPercentValue(value)) { throw new Error("ViewportAnchorY must be between 0 and 100."); } _viewportAnchorY = value; } }, "viewportAnchorX": { enumerable: true, get: function() { return _viewportAnchorX; }, set: function(value) { if (!isValidPercentValue(value)) { throw new Error("ViewportAnchorX must be between 0 and 100."); } _viewportAnchorX = value; } }, "scroll": { enumerable: true, get: function() { return _scroll; }, set: function(value) { var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value. if (setting === false) { console.warn("Scroll: an invalid or illegal string was specified."); } else { _scroll = setting; } } } }); } module.exports = VTTRegion; /***/ }), /***/ 893: /***/ (() => { /* (ignored) */ /***/ }), /***/ 434: /***/ ((module) => { function _extends() { return module.exports = _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, module.exports.__esModule = true, module.exports["default"] = module.exports, _extends.apply(null, arguments); } module.exports = _extends, module.exports.__esModule = true, module.exports["default"] = module.exports; /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ var getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/global */ /******/ (() => { /******/ __webpack_require__.g = (function() { /******/ if (typeof globalThis === 'object') return globalThis; /******/ try { /******/ return this || new Function('return this')(); /******/ } catch (e) { /******/ if (typeof window === 'object') return window; /******/ } /******/ })(); /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // This entry need to be wrapped in an IIFE because it need to be in strict mode. (() => { "use strict"; ;// CONCATENATED MODULE: ./node_modules/swiper/shared/ssr-window.esm.mjs /** * SSR Window 5.0.1 * Better handling for window object in SSR environment * https://github.com/nolimits4web/ssr-window * * Copyright 2025, Vladimir Kharlampidi * * Licensed under MIT * * Released on: June 27, 2025 */ /* eslint-disable no-param-reassign */ function ssr_window_esm_isObject(obj) { return obj !== null && typeof obj === 'object' && 'constructor' in obj && obj.constructor === Object; } function extend(target = {}, src = {}) { const noExtend = ['__proto__', 'constructor', 'prototype']; Object.keys(src).filter(key => noExtend.indexOf(key) < 0).forEach(key => { if (typeof target[key] === 'undefined') target[key] = src[key];else if (ssr_window_esm_isObject(src[key]) && ssr_window_esm_isObject(target[key]) && Object.keys(src[key]).length > 0) { extend(target[key], src[key]); } }); } const ssrDocument = { body: {}, addEventListener() {}, removeEventListener() {}, activeElement: { blur() {}, nodeName: '' }, querySelector() { return null; }, querySelectorAll() { return []; }, getElementById() { return null; }, createEvent() { return { initEvent() {} }; }, createElement() { return { children: [], childNodes: [], style: {}, setAttribute() {}, getElementsByTagName() { return []; } }; }, createElementNS() { return {}; }, importNode() { return null; }, location: { hash: '', host: '', hostname: '', href: '', origin: '', pathname: '', protocol: '', search: '' } }; function ssr_window_esm_getDocument() { const doc = typeof document !== 'undefined' ? document : {}; extend(doc, ssrDocument); return doc; } const ssrWindow = { document: ssrDocument, navigator: { userAgent: '' }, location: { hash: '', host: '', hostname: '', href: '', origin: '', pathname: '', protocol: '', search: '' }, history: { replaceState() {}, pushState() {}, go() {}, back() {} }, CustomEvent: function CustomEvent() { return this; }, addEventListener() {}, removeEventListener() {}, getComputedStyle() { return { getPropertyValue() { return ''; } }; }, Image() {}, Date() {}, screen: {}, setTimeout() {}, clearTimeout() {}, matchMedia() { return {}; }, requestAnimationFrame(callback) { if (typeof setTimeout === 'undefined') { callback(); return null; } return setTimeout(callback, 0); }, cancelAnimationFrame(id) { if (typeof setTimeout === 'undefined') { return; } clearTimeout(id); } }; function ssr_window_esm_getWindow() { const win = typeof window !== 'undefined' ? window : {}; extend(win, ssrWindow); return win; } ;// CONCATENATED MODULE: ./node_modules/swiper/shared/utils.mjs function utils_classesToTokens(classes = '') { return classes.trim().split(' ').filter(c => !!c.trim()); } function deleteProps(obj) { const object = obj; Object.keys(object).forEach(key => { try { object[key] = null; } catch (e) { // no getter for object } try { delete object[key]; } catch (e) { // something got wrong } }); } function utils_nextTick(callback, delay = 0) { return setTimeout(callback, delay); } function utils_now() { return Date.now(); } function utils_getComputedStyle(el) { const window = ssr_window_esm_getWindow(); let style; if (window.getComputedStyle) { style = window.getComputedStyle(el, null); } if (!style && el.currentStyle) { style = el.currentStyle; } if (!style) { style = el.style; } return style; } function utils_getTranslate(el, axis = 'x') { const window = ssr_window_esm_getWindow(); let matrix; let curTransform; let transformMatrix; const curStyle = utils_getComputedStyle(el); if (window.WebKitCSSMatrix) { curTransform = curStyle.transform || curStyle.webkitTransform; if (curTransform.split(',').length > 6) { curTransform = curTransform.split(', ').map(a => a.replace(',', '.')).join(', '); } // Some old versions of Webkit choke when 'none' is passed; pass // empty string instead in this case transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform); } else { transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,'); matrix = transformMatrix.toString().split(','); } if (axis === 'x') { // Latest Chrome and webkits Fix if (window.WebKitCSSMatrix) curTransform = transformMatrix.m41; // Crazy IE10 Matrix else if (matrix.length === 16) curTransform = parseFloat(matrix[12]); // Normal Browsers else curTransform = parseFloat(matrix[4]); } if (axis === 'y') { // Latest Chrome and webkits Fix if (window.WebKitCSSMatrix) curTransform = transformMatrix.m42; // Crazy IE10 Matrix else if (matrix.length === 16) curTransform = parseFloat(matrix[13]); // Normal Browsers else curTransform = parseFloat(matrix[5]); } return curTransform || 0; } function utils_isObject(o) { return typeof o === 'object' && o !== null && o.constructor && Object.prototype.toString.call(o).slice(8, -1) === 'Object'; } function isNode(node) { // eslint-disable-next-line if (typeof window !== 'undefined' && typeof window.HTMLElement !== 'undefined') { return node instanceof HTMLElement; } return node && (node.nodeType === 1 || node.nodeType === 11); } function utils_extend(...args) { const to = Object(args[0]); for (let i = 1; i < args.length; i += 1) { const nextSource = args[i]; if (nextSource !== undefined && nextSource !== null && !isNode(nextSource)) { const keysArray = Object.keys(Object(nextSource)).filter(key => key !== '__proto__' && key !== 'constructor' && key !== 'prototype'); for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex += 1) { const nextKey = keysArray[nextIndex]; const desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); if (desc !== undefined && desc.enumerable) { if (utils_isObject(to[nextKey]) && utils_isObject(nextSource[nextKey])) { if (nextSource[nextKey].__swiper__) { to[nextKey] = nextSource[nextKey]; } else { utils_extend(to[nextKey], nextSource[nextKey]); } } else if (!utils_isObject(to[nextKey]) && utils_isObject(nextSource[nextKey])) { to[nextKey] = {}; if (nextSource[nextKey].__swiper__) { to[nextKey] = nextSource[nextKey]; } else { utils_extend(to[nextKey], nextSource[nextKey]); } } else { to[nextKey] = nextSource[nextKey]; } } } } } return to; } function utils_setCSSProperty(el, varName, varValue) { el.style.setProperty(varName, varValue); } function animateCSSModeScroll({ swiper, targetPosition, side }) { const window = ssr_window_esm_getWindow(); const startPosition = -swiper.translate; let startTime = null; let time; const duration = swiper.params.speed; swiper.wrapperEl.style.scrollSnapType = 'none'; window.cancelAnimationFrame(swiper.cssModeFrameID); const dir = targetPosition > startPosition ? 'next' : 'prev'; const isOutOfBound = (current, target) => { return dir === 'next' && current >= target || dir === 'prev' && current <= target; }; const animate = () => { time = new Date().getTime(); if (startTime === null) { startTime = time; } const progress = Math.max(Math.min((time - startTime) / duration, 1), 0); const easeProgress = 0.5 - Math.cos(progress * Math.PI) / 2; let currentPosition = startPosition + easeProgress * (targetPosition - startPosition); if (isOutOfBound(currentPosition, targetPosition)) { currentPosition = targetPosition; } swiper.wrapperEl.scrollTo({ [side]: currentPosition }); if (isOutOfBound(currentPosition, targetPosition)) { swiper.wrapperEl.style.overflow = 'hidden'; swiper.wrapperEl.style.scrollSnapType = ''; setTimeout(() => { swiper.wrapperEl.style.overflow = ''; swiper.wrapperEl.scrollTo({ [side]: currentPosition }); }); window.cancelAnimationFrame(swiper.cssModeFrameID); return; } swiper.cssModeFrameID = window.requestAnimationFrame(animate); }; animate(); } function utils_getSlideTransformEl(slideEl) { return slideEl.querySelector('.swiper-slide-transform') || slideEl.shadowRoot && slideEl.shadowRoot.querySelector('.swiper-slide-transform') || slideEl; } function utils_elementChildren(element, selector = '') { const window = ssr_window_esm_getWindow(); const children = [...element.children]; if (window.HTMLSlotElement && element instanceof HTMLSlotElement) { children.push(...element.assignedElements()); } if (!selector) { return children; } return children.filter(el => el.matches(selector)); } function elementIsChildOfSlot(el, slot) { // Breadth-first search through all parent's children and assigned elements const elementsQueue = [slot]; while (elementsQueue.length > 0) { const elementToCheck = elementsQueue.shift(); if (el === elementToCheck) { return true; } elementsQueue.push(...elementToCheck.children, ...(elementToCheck.shadowRoot ? elementToCheck.shadowRoot.children : []), ...(elementToCheck.assignedElements ? elementToCheck.assignedElements() : [])); } } function elementIsChildOf(el, parent) { const window = ssr_window_esm_getWindow(); let isChild = parent.contains(el); if (!isChild && window.HTMLSlotElement && parent instanceof HTMLSlotElement) { const children = [...parent.assignedElements()]; isChild = children.includes(el); if (!isChild) { isChild = elementIsChildOfSlot(el, parent); } } return isChild; } function showWarning(text) { try { console.warn(text); return; } catch (err) { // err } } function utils_createElement(tag, classes = []) { const el = document.createElement(tag); el.classList.add(...(Array.isArray(classes) ? classes : utils_classesToTokens(classes))); return el; } function utils_elementOffset(el) { const window = getWindow(); const document = getDocument(); const box = el.getBoundingClientRect(); const body = document.body; const clientTop = el.clientTop || body.clientTop || 0; const clientLeft = el.clientLeft || body.clientLeft || 0; const scrollTop = el === window ? window.scrollY : el.scrollTop; const scrollLeft = el === window ? window.scrollX : el.scrollLeft; return { top: box.top + scrollTop - clientTop, left: box.left + scrollLeft - clientLeft }; } function elementPrevAll(el, selector) { const prevEls = []; while (el.previousElementSibling) { const prev = el.previousElementSibling; // eslint-disable-line if (selector) { if (prev.matches(selector)) prevEls.push(prev); } else prevEls.push(prev); el = prev; } return prevEls; } function elementNextAll(el, selector) { const nextEls = []; while (el.nextElementSibling) { const next = el.nextElementSibling; // eslint-disable-line if (selector) { if (next.matches(selector)) nextEls.push(next); } else nextEls.push(next); el = next; } return nextEls; } function elementStyle(el, prop) { const window = ssr_window_esm_getWindow(); return window.getComputedStyle(el, null).getPropertyValue(prop); } function utils_elementIndex(el) { let child = el; let i; if (child) { i = 0; // eslint-disable-next-line while ((child = child.previousSibling) !== null) { if (child.nodeType === 1) i += 1; } return i; } return undefined; } function utils_elementParents(el, selector) { const parents = []; // eslint-disable-line let parent = el.parentElement; // eslint-disable-line while (parent) { if (selector) { if (parent.matches(selector)) parents.push(parent); } else { parents.push(parent); } parent = parent.parentElement; } return parents; } function utils_elementTransitionEnd(el, callback) { function fireCallBack(e) { if (e.target !== el) return; callback.call(el, e); el.removeEventListener('transitionend', fireCallBack); } if (callback) { el.addEventListener('transitionend', fireCallBack); } } function utils_elementOuterSize(el, size, includeMargins) { const window = ssr_window_esm_getWindow(); if (includeMargins) { return el[size === 'width' ? 'offsetWidth' : 'offsetHeight'] + parseFloat(window.getComputedStyle(el, null).getPropertyValue(size === 'width' ? 'margin-right' : 'margin-top')) + parseFloat(window.getComputedStyle(el, null).getPropertyValue(size === 'width' ? 'margin-left' : 'margin-bottom')); } return el.offsetWidth; } function utils_makeElementsArray(el) { return (Array.isArray(el) ? el : [el]).filter(e => !!e); } function utils_getRotateFix(swiper) { return v => { if (Math.abs(v) > 0 && swiper.browser && swiper.browser.need3dFix && Math.abs(v) % 90 === 0) { return v + 0.001; } return v; }; } function utils_setInnerHTML(el, html = '') { if (typeof trustedTypes !== 'undefined') { el.innerHTML = trustedTypes.createPolicy('html', { createHTML: s => s }).createHTML(html); } else { el.innerHTML = html; } } ;// CONCATENATED MODULE: ./node_modules/swiper/shared/swiper-core.mjs let support; function calcSupport() { const window = ssr_window_esm_getWindow(); const document = ssr_window_esm_getDocument(); return { smoothScroll: document.documentElement && document.documentElement.style && 'scrollBehavior' in document.documentElement.style, touch: !!('ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch) }; } function getSupport() { if (!support) { support = calcSupport(); } return support; } let deviceCached; function calcDevice({ userAgent } = {}) { const support = getSupport(); const window = ssr_window_esm_getWindow(); const platform = window.navigator.platform; const ua = userAgent || window.navigator.userAgent; const device = { ios: false, android: false }; const screenWidth = window.screen.width; const screenHeight = window.screen.height; const android = ua.match(/(Android);?[\s\/]+([\d.]+)?/); // eslint-disable-line let ipad = ua.match(/(iPad)(?!\1).*OS\s([\d_]+)/); const ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/); const iphone = !ipad && ua.match(/(iPhone\sOS|iOS)\s([\d_]+)/); const windows = platform === 'Win32'; let macos = platform === 'MacIntel'; // iPadOs 13 fix const iPadScreens = ['1024x1366', '1366x1024', '834x1194', '1194x834', '834x1112', '1112x834', '768x1024', '1024x768', '820x1180', '1180x820', '810x1080', '1080x810']; if (!ipad && macos && support.touch && iPadScreens.indexOf(`${screenWidth}x${screenHeight}`) >= 0) { ipad = ua.match(/(Version)\/([\d.]+)/); if (!ipad) ipad = [0, 1, '13_0_0']; macos = false; } // Android if (android && !windows) { device.os = 'android'; device.android = true; } if (ipad || iphone || ipod) { device.os = 'ios'; device.ios = true; } // Export object return device; } function getDevice(overrides = {}) { if (!deviceCached) { deviceCached = calcDevice(overrides); } return deviceCached; } let browser; function calcBrowser() { const window = ssr_window_esm_getWindow(); const device = getDevice(); let needPerspectiveFix = false; function isSafari() { const ua = window.navigator.userAgent.toLowerCase(); return ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0; } if (isSafari()) { const ua = String(window.navigator.userAgent); if (ua.includes('Version/')) { const [major, minor] = ua.split('Version/')[1].split(' ')[0].split('.').map(num => Number(num)); needPerspectiveFix = major < 16 || major === 16 && minor < 2; } } const isWebView = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(window.navigator.userAgent); const isSafariBrowser = isSafari(); const need3dFix = isSafariBrowser || isWebView && device.ios; return { isSafari: needPerspectiveFix || isSafariBrowser, needPerspectiveFix, need3dFix, isWebView }; } function getBrowser() { if (!browser) { browser = calcBrowser(); } return browser; } function Resize({ swiper, on, emit }) { const window = ssr_window_esm_getWindow(); let observer = null; let animationFrame = null; const resizeHandler = () => { if (!swiper || swiper.destroyed || !swiper.initialized) return; emit('beforeResize'); emit('resize'); }; const createObserver = () => { if (!swiper || swiper.destroyed || !swiper.initialized) return; observer = new ResizeObserver(entries => { animationFrame = window.requestAnimationFrame(() => { const { width, height } = swiper; let newWidth = width; let newHeight = height; entries.forEach(({ contentBoxSize, contentRect, target }) => { if (target && target !== swiper.el) return; newWidth = contentRect ? contentRect.width : (contentBoxSize[0] || contentBoxSize).inlineSize; newHeight = contentRect ? contentRect.height : (contentBoxSize[0] || contentBoxSize).blockSize; }); if (newWidth !== width || newHeight !== height) { resizeHandler(); } }); }); observer.observe(swiper.el); }; const removeObserver = () => { if (animationFrame) { window.cancelAnimationFrame(animationFrame); } if (observer && observer.unobserve && swiper.el) { observer.unobserve(swiper.el); observer = null; } }; const orientationChangeHandler = () => { if (!swiper || swiper.destroyed || !swiper.initialized) return; emit('orientationchange'); }; on('init', () => { if (swiper.params.resizeObserver && typeof window.ResizeObserver !== 'undefined') { createObserver(); return; } window.addEventListener('resize', resizeHandler); window.addEventListener('orientationchange', orientationChangeHandler); }); on('destroy', () => { removeObserver(); window.removeEventListener('resize', resizeHandler); window.removeEventListener('orientationchange', orientationChangeHandler); }); } function Observer({ swiper, extendParams, on, emit }) { const observers = []; const window = ssr_window_esm_getWindow(); const attach = (target, options = {}) => { const ObserverFunc = window.MutationObserver || window.WebkitMutationObserver; const observer = new ObserverFunc(mutations => { // The observerUpdate event should only be triggered // once despite the number of mutations. Additional // triggers are redundant and are very costly if (swiper.__preventObserver__) return; if (mutations.length === 1) { emit('observerUpdate', mutations[0]); return; } const observerUpdate = function observerUpdate() { emit('observerUpdate', mutations[0]); }; if (window.requestAnimationFrame) { window.requestAnimationFrame(observerUpdate); } else { window.setTimeout(observerUpdate, 0); } }); observer.observe(target, { attributes: typeof options.attributes === 'undefined' ? true : options.attributes, childList: swiper.isElement || (typeof options.childList === 'undefined' ? true : options).childList, characterData: typeof options.characterData === 'undefined' ? true : options.characterData }); observers.push(observer); }; const init = () => { if (!swiper.params.observer) return; if (swiper.params.observeParents) { const containerParents = utils_elementParents(swiper.hostEl); for (let i = 0; i < containerParents.length; i += 1) { attach(containerParents[i]); } } // Observe container attach(swiper.hostEl, { childList: swiper.params.observeSlideChildren }); // Observe wrapper attach(swiper.wrapperEl, { attributes: false }); }; const destroy = () => { observers.forEach(observer => { observer.disconnect(); }); observers.splice(0, observers.length); }; extendParams({ observer: false, observeParents: false, observeSlideChildren: false }); on('init', init); on('destroy', destroy); } /* eslint-disable no-underscore-dangle */ var eventsEmitter = { on(events, handler, priority) { const self = this; if (!self.eventsListeners || self.destroyed) return self; if (typeof handler !== 'function') return self; const method = priority ? 'unshift' : 'push'; events.split(' ').forEach(event => { if (!self.eventsListeners[event]) self.eventsListeners[event] = []; self.eventsListeners[event][method](handler); }); return self; }, once(events, handler, priority) { const self = this; if (!self.eventsListeners || self.destroyed) return self; if (typeof handler !== 'function') return self; function onceHandler(...args) { self.off(events, onceHandler); if (onceHandler.__emitterProxy) { delete onceHandler.__emitterProxy; } handler.apply(self, args); } onceHandler.__emitterProxy = handler; return self.on(events, onceHandler, priority); }, onAny(handler, priority) { const self = this; if (!self.eventsListeners || self.destroyed) return self; if (typeof handler !== 'function') return self; const method = priority ? 'unshift' : 'push'; if (self.eventsAnyListeners.indexOf(handler) < 0) { self.eventsAnyListeners[method](handler); } return self; }, offAny(handler) { const self = this; if (!self.eventsListeners || self.destroyed) return self; if (!self.eventsAnyListeners) return self; const index = self.eventsAnyListeners.indexOf(handler); if (index >= 0) { self.eventsAnyListeners.splice(index, 1); } return self; }, off(events, handler) { const self = this; if (!self.eventsListeners || self.destroyed) return self; if (!self.eventsListeners) return self; events.split(' ').forEach(event => { if (typeof handler === 'undefined') { self.eventsListeners[event] = []; } else if (self.eventsListeners[event]) { self.eventsListeners[event].forEach((eventHandler, index) => { if (eventHandler === handler || eventHandler.__emitterProxy && eventHandler.__emitterProxy === handler) { self.eventsListeners[event].splice(index, 1); } }); } }); return self; }, emit(...args) { const self = this; if (!self.eventsListeners || self.destroyed) return self; if (!self.eventsListeners) return self; let events; let data; let context; if (typeof args[0] === 'string' || Array.isArray(args[0])) { events = args[0]; data = args.slice(1, args.length); context = self; } else { events = args[0].events; data = args[0].data; context = args[0].context || self; } data.unshift(context); const eventsArray = Array.isArray(events) ? events : events.split(' '); eventsArray.forEach(event => { if (self.eventsAnyListeners && self.eventsAnyListeners.length) { self.eventsAnyListeners.forEach(eventHandler => { eventHandler.apply(context, [event, ...data]); }); } if (self.eventsListeners && self.eventsListeners[event]) { self.eventsListeners[event].forEach(eventHandler => { eventHandler.apply(context, data); }); } }); return self; } }; function updateSize() { const swiper = this; let width; let height; const el = swiper.el; if (typeof swiper.params.width !== 'undefined' && swiper.params.width !== null) { width = swiper.params.width; } else { width = el.clientWidth; } if (typeof swiper.params.height !== 'undefined' && swiper.params.height !== null) { height = swiper.params.height; } else { height = el.clientHeight; } if (width === 0 && swiper.isHorizontal() || height === 0 && swiper.isVertical()) { return; } // Subtract paddings width = width - parseInt(elementStyle(el, 'padding-left') || 0, 10) - parseInt(elementStyle(el, 'padding-right') || 0, 10); height = height - parseInt(elementStyle(el, 'padding-top') || 0, 10) - parseInt(elementStyle(el, 'padding-bottom') || 0, 10); if (Number.isNaN(width)) width = 0; if (Number.isNaN(height)) height = 0; Object.assign(swiper, { width, height, size: swiper.isHorizontal() ? width : height }); } function updateSlides() { const swiper = this; function getDirectionPropertyValue(node, label) { return parseFloat(node.getPropertyValue(swiper.getDirectionLabel(label)) || 0); } const params = swiper.params; const { wrapperEl, slidesEl, rtlTranslate: rtl, wrongRTL } = swiper; const isVirtual = swiper.virtual && params.virtual.enabled; const previousSlidesLength = isVirtual ? swiper.virtual.slides.length : swiper.slides.length; const slides = utils_elementChildren(slidesEl, `.${swiper.params.slideClass}, swiper-slide`); const slidesLength = isVirtual ? swiper.virtual.slides.length : slides.length; let snapGrid = []; const slidesGrid = []; const slidesSizesGrid = []; let offsetBefore = params.slidesOffsetBefore; if (typeof offsetBefore === 'function') { offsetBefore = params.slidesOffsetBefore.call(swiper); } let offsetAfter = params.slidesOffsetAfter; if (typeof offsetAfter === 'function') { offsetAfter = params.slidesOffsetAfter.call(swiper); } const previousSnapGridLength = swiper.snapGrid.length; const previousSlidesGridLength = swiper.slidesGrid.length; const swiperSize = swiper.size - offsetBefore - offsetAfter; let spaceBetween = params.spaceBetween; let slidePosition = -offsetBefore; let prevSlideSize = 0; let index = 0; if (typeof swiperSize === 'undefined') { return; } if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) { spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * swiperSize; } else if (typeof spaceBetween === 'string') { spaceBetween = parseFloat(spaceBetween); } swiper.virtualSize = -spaceBetween - offsetBefore - offsetAfter; // reset margins slides.forEach(slideEl => { if (rtl) { slideEl.style.marginLeft = ''; } else { slideEl.style.marginRight = ''; } slideEl.style.marginBottom = ''; slideEl.style.marginTop = ''; }); // reset cssMode offsets if (params.centeredSlides && params.cssMode) { utils_setCSSProperty(wrapperEl, '--swiper-centered-offset-before', ''); utils_setCSSProperty(wrapperEl, '--swiper-centered-offset-after', ''); } // set cssMode offsets if (params.cssMode) { utils_setCSSProperty(wrapperEl, '--swiper-slides-offset-before', `${offsetBefore}px`); utils_setCSSProperty(wrapperEl, '--swiper-slides-offset-after', `${offsetAfter}px`); } const gridEnabled = params.grid && params.grid.rows > 1 && swiper.grid; if (gridEnabled) { swiper.grid.initSlides(slides); } else if (swiper.grid) { swiper.grid.unsetSlides(); } // Calc slides let slideSize; const shouldResetSlideSize = params.slidesPerView === 'auto' && params.breakpoints && Object.keys(params.breakpoints).filter(key => { return typeof params.breakpoints[key].slidesPerView !== 'undefined'; }).length > 0; for (let i = 0; i < slidesLength; i += 1) { slideSize = 0; const slide = slides[i]; if (slide) { if (gridEnabled) { swiper.grid.updateSlide(i, slide, slides); } if (elementStyle(slide, 'display') === 'none') continue; // eslint-disable-line } if (isVirtual && params.slidesPerView === 'auto') { if (params.virtual.slidesPerViewAutoSlideSize) { slideSize = params.virtual.slidesPerViewAutoSlideSize; } if (slideSize && slide) { if (params.roundLengths) slideSize = Math.floor(slideSize); slide.style[swiper.getDirectionLabel('width')] = `${slideSize}px`; } } else if (params.slidesPerView === 'auto') { if (shouldResetSlideSize) { slide.style[swiper.getDirectionLabel('width')] = ``; } const slideStyles = getComputedStyle(slide); const currentTransform = slide.style.transform; const currentWebKitTransform = slide.style.webkitTransform; if (currentTransform) { slide.style.transform = 'none'; } if (currentWebKitTransform) { slide.style.webkitTransform = 'none'; } if (params.roundLengths) { slideSize = swiper.isHorizontal() ? utils_elementOuterSize(slide, 'width', true) : utils_elementOuterSize(slide, 'height', true); } else { // eslint-disable-next-line const width = getDirectionPropertyValue(slideStyles, 'width'); const paddingLeft = getDirectionPropertyValue(slideStyles, 'padding-left'); const paddingRight = getDirectionPropertyValue(slideStyles, 'padding-right'); const marginLeft = getDirectionPropertyValue(slideStyles, 'margin-left'); const marginRight = getDirectionPropertyValue(slideStyles, 'margin-right'); const boxSizing = slideStyles.getPropertyValue('box-sizing'); if (boxSizing && boxSizing === 'border-box') { slideSize = width + marginLeft + marginRight; } else { const { clientWidth, offsetWidth } = slide; slideSize = width + paddingLeft + paddingRight + marginLeft + marginRight + (offsetWidth - clientWidth); } } if (currentTransform) { slide.style.transform = currentTransform; } if (currentWebKitTransform) { slide.style.webkitTransform = currentWebKitTransform; } if (params.roundLengths) slideSize = Math.floor(slideSize); } else { slideSize = (swiperSize - (params.slidesPerView - 1) * spaceBetween) / params.slidesPerView; if (params.roundLengths) slideSize = Math.floor(slideSize); if (slide) { slide.style[swiper.getDirectionLabel('width')] = `${slideSize}px`; } } if (slide) { slide.swiperSlideSize = slideSize; } slidesSizesGrid.push(slideSize); if (params.centeredSlides) { slidePosition = slidePosition + slideSize / 2 + prevSlideSize / 2 + spaceBetween; if (prevSlideSize === 0 && i !== 0) slidePosition = slidePosition - swiperSize / 2 - spaceBetween; if (i === 0) slidePosition = slidePosition - swiperSize / 2 - spaceBetween; if (Math.abs(slidePosition) < 1 / 1000) slidePosition = 0; if (params.roundLengths) slidePosition = Math.floor(slidePosition); if (index % params.slidesPerGroup === 0) snapGrid.push(slidePosition); slidesGrid.push(slidePosition); } else { if (params.roundLengths) slidePosition = Math.floor(slidePosition); if ((index - Math.min(swiper.params.slidesPerGroupSkip, index)) % swiper.params.slidesPerGroup === 0) snapGrid.push(slidePosition); slidesGrid.push(slidePosition); slidePosition = slidePosition + slideSize + spaceBetween; } swiper.virtualSize += slideSize + spaceBetween; prevSlideSize = slideSize; index += 1; } swiper.virtualSize = Math.max(swiper.virtualSize, swiperSize) + offsetAfter; if (rtl && wrongRTL && (params.effect === 'slide' || params.effect === 'coverflow')) { wrapperEl.style.width = `${swiper.virtualSize + spaceBetween}px`; } if (params.setWrapperSize) { wrapperEl.style[swiper.getDirectionLabel('width')] = `${swiper.virtualSize + spaceBetween}px`; } if (gridEnabled) { swiper.grid.updateWrapperSize(slideSize, snapGrid); } // Remove last grid elements depending on width if (!params.centeredSlides) { // Check if snapToSlideEdge should be applied const isFractionalSlidesPerView = params.slidesPerView !== 'auto' && params.slidesPerView % 1 !== 0; const shouldSnapToSlideEdge = params.snapToSlideEdge && !params.loop && (params.slidesPerView === 'auto' || isFractionalSlidesPerView); // Calculate the last allowed snap index when snapToSlideEdge is enabled // This ensures minimum slides are visible at the end let lastAllowedSnapIndex = snapGrid.length; if (shouldSnapToSlideEdge) { let minVisibleSlides; if (params.slidesPerView === 'auto') { // For 'auto' mode, calculate how many slides fit based on actual sizes minVisibleSlides = 1; let accumulatedSize = 0; for (let i = slidesSizesGrid.length - 1; i >= 0; i -= 1) { accumulatedSize += slidesSizesGrid[i] + (i < slidesSizesGrid.length - 1 ? spaceBetween : 0); if (accumulatedSize <= swiperSize) { minVisibleSlides = slidesSizesGrid.length - i; } else { break; } } } else { minVisibleSlides = Math.floor(params.slidesPerView); } lastAllowedSnapIndex = Math.max(slidesLength - minVisibleSlides, 0); } const newSlidesGrid = []; for (let i = 0; i < snapGrid.length; i += 1) { let slidesGridItem = snapGrid[i]; if (params.roundLengths) slidesGridItem = Math.floor(slidesGridItem); if (shouldSnapToSlideEdge) { // When snapToSlideEdge is enabled, only keep snaps up to lastAllowedSnapIndex if (i <= lastAllowedSnapIndex) { newSlidesGrid.push(slidesGridItem); } } else if (snapGrid[i] <= swiper.virtualSize - swiperSize) { // When snapToSlideEdge is disabled, keep snaps that fit within scrollable area newSlidesGrid.push(slidesGridItem); } } snapGrid = newSlidesGrid; if (Math.floor(swiper.virtualSize - swiperSize) - Math.floor(snapGrid[snapGrid.length - 1]) > 1) { // Only add edge-aligned snap if snapToSlideEdge is not enabled if (!shouldSnapToSlideEdge) { snapGrid.push(swiper.virtualSize - swiperSize); } } } if (isVirtual && params.loop) { const size = slidesSizesGrid[0] + spaceBetween; if (params.slidesPerGroup > 1) { const groups = Math.ceil((swiper.virtual.slidesBefore + swiper.virtual.slidesAfter) / params.slidesPerGroup); const groupSize = size * params.slidesPerGroup; for (let i = 0; i < groups; i += 1) { snapGrid.push(snapGrid[snapGrid.length - 1] + groupSize); } } for (let i = 0; i < swiper.virtual.slidesBefore + swiper.virtual.slidesAfter; i += 1) { if (params.slidesPerGroup === 1) { snapGrid.push(snapGrid[snapGrid.length - 1] + size); } slidesGrid.push(slidesGrid[slidesGrid.length - 1] + size); swiper.virtualSize += size; } } if (snapGrid.length === 0) snapGrid = [0]; if (spaceBetween !== 0) { const key = swiper.isHorizontal() && rtl ? 'marginLeft' : swiper.getDirectionLabel('marginRight'); slides.filter((_, slideIndex) => { if (!params.cssMode || params.loop) return true; if (slideIndex === slides.length - 1) { return false; } return true; }).forEach(slideEl => { slideEl.style[key] = `${spaceBetween}px`; }); } if (params.centeredSlides && params.centeredSlidesBounds) { let allSlidesSize = 0; slidesSizesGrid.forEach(slideSizeValue => { allSlidesSize += slideSizeValue + (spaceBetween || 0); }); allSlidesSize -= spaceBetween; const maxSnap = allSlidesSize > swiperSize ? allSlidesSize - swiperSize : 0; snapGrid = snapGrid.map(snap => { if (snap <= 0) return -offsetBefore; if (snap > maxSnap) return maxSnap + offsetAfter; return snap; }); } if (params.centerInsufficientSlides) { let allSlidesSize = 0; slidesSizesGrid.forEach(slideSizeValue => { allSlidesSize += slideSizeValue + (spaceBetween || 0); }); allSlidesSize -= spaceBetween; if (allSlidesSize < swiperSize) { const allSlidesOffset = (swiperSize - allSlidesSize) / 2; snapGrid.forEach((snap, snapIndex) => { snapGrid[snapIndex] = snap - allSlidesOffset; }); slidesGrid.forEach((snap, snapIndex) => { slidesGrid[snapIndex] = snap + allSlidesOffset; }); } } Object.assign(swiper, { slides, snapGrid, slidesGrid, slidesSizesGrid }); if (params.centeredSlides && params.cssMode && !params.centeredSlidesBounds) { utils_setCSSProperty(wrapperEl, '--swiper-centered-offset-before', `${-snapGrid[0]}px`); utils_setCSSProperty(wrapperEl, '--swiper-centered-offset-after', `${swiper.size / 2 - slidesSizesGrid[slidesSizesGrid.length - 1] / 2}px`); const addToSnapGrid = -swiper.snapGrid[0]; const addToSlidesGrid = -swiper.slidesGrid[0]; swiper.snapGrid = swiper.snapGrid.map(v => v + addToSnapGrid); swiper.slidesGrid = swiper.slidesGrid.map(v => v + addToSlidesGrid); } if (slidesLength !== previousSlidesLength) { swiper.emit('slidesLengthChange'); } if (snapGrid.length !== previousSnapGridLength) { if (swiper.params.watchOverflow) swiper.checkOverflow(); swiper.emit('snapGridLengthChange'); } if (slidesGrid.length !== previousSlidesGridLength) { swiper.emit('slidesGridLengthChange'); } if (params.watchSlidesProgress) { swiper.updateSlidesOffset(); } swiper.emit('slidesUpdated'); if (!isVirtual && !params.cssMode && (params.effect === 'slide' || params.effect === 'fade')) { const backFaceHiddenClass = `${params.containerModifierClass}backface-hidden`; const hasClassBackfaceClassAdded = swiper.el.classList.contains(backFaceHiddenClass); if (slidesLength <= params.maxBackfaceHiddenSlides) { if (!hasClassBackfaceClassAdded) swiper.el.classList.add(backFaceHiddenClass); } else if (hasClassBackfaceClassAdded) { swiper.el.classList.remove(backFaceHiddenClass); } } } function updateAutoHeight(speed) { const swiper = this; const activeSlides = []; const isVirtual = swiper.virtual && swiper.params.virtual.enabled; let newHeight = 0; let i; if (typeof speed === 'number') { swiper.setTransition(speed); } else if (speed === true) { swiper.setTransition(swiper.params.speed); } const getSlideByIndex = index => { if (isVirtual) { return swiper.slides[swiper.getSlideIndexByData(index)]; } return swiper.slides[index]; }; // Find slides currently in view if (swiper.params.slidesPerView !== 'auto' && swiper.params.slidesPerView > 1) { if (swiper.params.centeredSlides) { (swiper.visibleSlides || []).forEach(slide => { activeSlides.push(slide); }); } else { for (i = 0; i < Math.ceil(swiper.params.slidesPerView); i += 1) { const index = swiper.activeIndex + i; if (index > swiper.slides.length && !isVirtual) break; activeSlides.push(getSlideByIndex(index)); } } } else { activeSlides.push(getSlideByIndex(swiper.activeIndex)); } // Find new height from highest slide in view for (i = 0; i < activeSlides.length; i += 1) { if (typeof activeSlides[i] !== 'undefined') { const height = activeSlides[i].offsetHeight; newHeight = height > newHeight ? height : newHeight; } } // Update Height if (newHeight || newHeight === 0) swiper.wrapperEl.style.height = `${newHeight}px`; } function updateSlidesOffset() { const swiper = this; const slides = swiper.slides; // eslint-disable-next-line const minusOffset = swiper.isElement ? swiper.isHorizontal() ? swiper.wrapperEl.offsetLeft : swiper.wrapperEl.offsetTop : 0; for (let i = 0; i < slides.length; i += 1) { slides[i].swiperSlideOffset = (swiper.isHorizontal() ? slides[i].offsetLeft : slides[i].offsetTop) - minusOffset - swiper.cssOverflowAdjustment(); } } const toggleSlideClasses$1 = (slideEl, condition, className) => { if (condition && !slideEl.classList.contains(className)) { slideEl.classList.add(className); } else if (!condition && slideEl.classList.contains(className)) { slideEl.classList.remove(className); } }; function updateSlidesProgress(translate = this && this.translate || 0) { const swiper = this; const params = swiper.params; const { slides, rtlTranslate: rtl, snapGrid } = swiper; if (slides.length === 0) return; if (typeof slides[0].swiperSlideOffset === 'undefined') swiper.updateSlidesOffset(); let offsetCenter = -translate; if (rtl) offsetCenter = translate; swiper.visibleSlidesIndexes = []; swiper.visibleSlides = []; let spaceBetween = params.spaceBetween; if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) { spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * swiper.size; } else if (typeof spaceBetween === 'string') { spaceBetween = parseFloat(spaceBetween); } for (let i = 0; i < slides.length; i += 1) { const slide = slides[i]; let slideOffset = slide.swiperSlideOffset; if (params.cssMode && params.centeredSlides) { slideOffset -= slides[0].swiperSlideOffset; } const slideProgress = (offsetCenter + (params.centeredSlides ? swiper.minTranslate() : 0) - slideOffset) / (slide.swiperSlideSize + spaceBetween); const originalSlideProgress = (offsetCenter - snapGrid[0] + (params.centeredSlides ? swiper.minTranslate() : 0) - slideOffset) / (slide.swiperSlideSize + spaceBetween); const slideBefore = -(offsetCenter - slideOffset); const slideAfter = slideBefore + swiper.slidesSizesGrid[i]; const isFullyVisible = slideBefore >= 0 && slideBefore <= swiper.size - swiper.slidesSizesGrid[i]; const isVisible = slideBefore >= 0 && slideBefore < swiper.size - 1 || slideAfter > 1 && slideAfter <= swiper.size || slideBefore <= 0 && slideAfter >= swiper.size; if (isVisible) { swiper.visibleSlides.push(slide); swiper.visibleSlidesIndexes.push(i); } toggleSlideClasses$1(slide, isVisible, params.slideVisibleClass); toggleSlideClasses$1(slide, isFullyVisible, params.slideFullyVisibleClass); slide.progress = rtl ? -slideProgress : slideProgress; slide.originalProgress = rtl ? -originalSlideProgress : originalSlideProgress; } } function updateProgress(translate) { const swiper = this; if (typeof translate === 'undefined') { const multiplier = swiper.rtlTranslate ? -1 : 1; // eslint-disable-next-line translate = swiper && swiper.translate && swiper.translate * multiplier || 0; } const params = swiper.params; const translatesDiff = swiper.maxTranslate() - swiper.minTranslate(); let { progress, isBeginning, isEnd, progressLoop } = swiper; const wasBeginning = isBeginning; const wasEnd = isEnd; if (translatesDiff === 0) { progress = 0; isBeginning = true; isEnd = true; } else { progress = (translate - swiper.minTranslate()) / translatesDiff; const isBeginningRounded = Math.abs(translate - swiper.minTranslate()) < 1; const isEndRounded = Math.abs(translate - swiper.maxTranslate()) < 1; isBeginning = isBeginningRounded || progress <= 0; isEnd = isEndRounded || progress >= 1; if (isBeginningRounded) progress = 0; if (isEndRounded) progress = 1; } if (params.loop) { const firstSlideIndex = swiper.getSlideIndexByData(0); const lastSlideIndex = swiper.getSlideIndexByData(swiper.slides.length - 1); const firstSlideTranslate = swiper.slidesGrid[firstSlideIndex]; const lastSlideTranslate = swiper.slidesGrid[lastSlideIndex]; const translateMax = swiper.slidesGrid[swiper.slidesGrid.length - 1]; const translateAbs = Math.abs(translate); if (translateAbs >= firstSlideTranslate) { progressLoop = (translateAbs - firstSlideTranslate) / translateMax; } else { progressLoop = (translateAbs + translateMax - lastSlideTranslate) / translateMax; } if (progressLoop > 1) progressLoop -= 1; } Object.assign(swiper, { progress, progressLoop, isBeginning, isEnd }); if (params.watchSlidesProgress || params.centeredSlides && params.autoHeight) swiper.updateSlidesProgress(translate); if (isBeginning && !wasBeginning) { swiper.emit('reachBeginning toEdge'); } if (isEnd && !wasEnd) { swiper.emit('reachEnd toEdge'); } if (wasBeginning && !isBeginning || wasEnd && !isEnd) { swiper.emit('fromEdge'); } swiper.emit('progress', progress); } const toggleSlideClasses = (slideEl, condition, className) => { if (condition && !slideEl.classList.contains(className)) { slideEl.classList.add(className); } else if (!condition && slideEl.classList.contains(className)) { slideEl.classList.remove(className); } }; function updateSlidesClasses() { const swiper = this; const { slides, params, slidesEl, activeIndex } = swiper; const isVirtual = swiper.virtual && params.virtual.enabled; const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1; const getFilteredSlide = selector => { return utils_elementChildren(slidesEl, `.${params.slideClass}${selector}, swiper-slide${selector}`)[0]; }; let activeSlide; let prevSlide; let nextSlide; if (isVirtual) { if (params.loop) { let slideIndex = activeIndex - swiper.virtual.slidesBefore; if (slideIndex < 0) slideIndex = swiper.virtual.slides.length + slideIndex; if (slideIndex >= swiper.virtual.slides.length) slideIndex -= swiper.virtual.slides.length; activeSlide = getFilteredSlide(`[data-swiper-slide-index="${slideIndex}"]`); } else { activeSlide = getFilteredSlide(`[data-swiper-slide-index="${activeIndex}"]`); } } else { if (gridEnabled) { activeSlide = slides.find(slideEl => slideEl.column === activeIndex); nextSlide = slides.find(slideEl => slideEl.column === activeIndex + 1); prevSlide = slides.find(slideEl => slideEl.column === activeIndex - 1); } else { activeSlide = slides[activeIndex]; } } if (activeSlide) { if (!gridEnabled) { // Next Slide nextSlide = elementNextAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0]; if (params.loop && !nextSlide) { nextSlide = slides[0]; } // Prev Slide prevSlide = elementPrevAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0]; if (params.loop && !prevSlide === 0) { prevSlide = slides[slides.length - 1]; } } } slides.forEach(slideEl => { toggleSlideClasses(slideEl, slideEl === activeSlide, params.slideActiveClass); toggleSlideClasses(slideEl, slideEl === nextSlide, params.slideNextClass); toggleSlideClasses(slideEl, slideEl === prevSlide, params.slidePrevClass); }); swiper.emitSlidesClasses(); } const processLazyPreloader = (swiper, imageEl) => { if (!swiper || swiper.destroyed || !swiper.params) return; const slideSelector = () => swiper.isElement ? `swiper-slide` : `.${swiper.params.slideClass}`; const slideEl = imageEl.closest(slideSelector()); if (slideEl) { let lazyEl = slideEl.querySelector(`.${swiper.params.lazyPreloaderClass}`); if (!lazyEl && swiper.isElement) { if (slideEl.shadowRoot) { lazyEl = slideEl.shadowRoot.querySelector(`.${swiper.params.lazyPreloaderClass}`); } else { // init later requestAnimationFrame(() => { if (slideEl.shadowRoot) { lazyEl = slideEl.shadowRoot.querySelector(`.${swiper.params.lazyPreloaderClass}`); if (lazyEl && !lazyEl.lazyPreloaderManaged) lazyEl.remove(); } }); } } // Skip removal if managed by React/Vue component if (lazyEl && !lazyEl.lazyPreloaderManaged) lazyEl.remove(); } }; const unlazy = (swiper, index) => { if (!swiper.slides[index]) return; const imageEl = swiper.slides[index].querySelector('[loading="lazy"]'); if (imageEl) imageEl.removeAttribute('loading'); }; const preload = swiper => { if (!swiper || swiper.destroyed || !swiper.params) return; let amount = swiper.params.lazyPreloadPrevNext; const len = swiper.slides.length; if (!len || !amount || amount < 0) return; amount = Math.min(amount, len); const slidesPerView = swiper.params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : Math.ceil(swiper.params.slidesPerView); const activeIndex = swiper.activeIndex; if (swiper.params.grid && swiper.params.grid.rows > 1) { const activeColumn = activeIndex; const preloadColumns = [activeColumn - amount]; preloadColumns.push(...Array.from({ length: amount }).map((_, i) => { return activeColumn + slidesPerView + i; })); swiper.slides.forEach((slideEl, i) => { if (preloadColumns.includes(slideEl.column)) unlazy(swiper, i); }); return; } const slideIndexLastInView = activeIndex + slidesPerView - 1; if (swiper.params.rewind || swiper.params.loop) { for (let i = activeIndex - amount; i <= slideIndexLastInView + amount; i += 1) { const realIndex = (i % len + len) % len; if (realIndex < activeIndex || realIndex > slideIndexLastInView) unlazy(swiper, realIndex); } } else { for (let i = Math.max(activeIndex - amount, 0); i <= Math.min(slideIndexLastInView + amount, len - 1); i += 1) { if (i !== activeIndex && (i > slideIndexLastInView || i < activeIndex)) { unlazy(swiper, i); } } } }; function getActiveIndexByTranslate(swiper) { const { slidesGrid, params } = swiper; const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate; let activeIndex; for (let i = 0; i < slidesGrid.length; i += 1) { if (typeof slidesGrid[i + 1] !== 'undefined') { if (translate >= slidesGrid[i] && translate < slidesGrid[i + 1] - (slidesGrid[i + 1] - slidesGrid[i]) / 2) { activeIndex = i; } else if (translate >= slidesGrid[i] && translate < slidesGrid[i + 1]) { activeIndex = i + 1; } } else if (translate >= slidesGrid[i]) { activeIndex = i; } } // Normalize slideIndex if (params.normalizeSlideIndex) { if (activeIndex < 0 || typeof activeIndex === 'undefined') activeIndex = 0; } return activeIndex; } function updateActiveIndex(newActiveIndex) { const swiper = this; const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate; const { snapGrid, params, activeIndex: previousIndex, realIndex: previousRealIndex, snapIndex: previousSnapIndex } = swiper; let activeIndex = newActiveIndex; let snapIndex; const getVirtualRealIndex = aIndex => { let realIndex = aIndex - swiper.virtual.slidesBefore; if (realIndex < 0) { realIndex = swiper.virtual.slides.length + realIndex; } if (realIndex >= swiper.virtual.slides.length) { realIndex -= swiper.virtual.slides.length; } return realIndex; }; if (typeof activeIndex === 'undefined') { activeIndex = getActiveIndexByTranslate(swiper); } if (snapGrid.indexOf(translate) >= 0) { snapIndex = snapGrid.indexOf(translate); } else { const skip = Math.min(params.slidesPerGroupSkip, activeIndex); snapIndex = skip + Math.floor((activeIndex - skip) / params.slidesPerGroup); } if (snapIndex >= snapGrid.length) snapIndex = snapGrid.length - 1; if (activeIndex === previousIndex && !swiper.params.loop) { if (snapIndex !== previousSnapIndex) { swiper.snapIndex = snapIndex; swiper.emit('snapIndexChange'); } return; } if (activeIndex === previousIndex && swiper.params.loop && swiper.virtual && swiper.params.virtual.enabled) { swiper.realIndex = getVirtualRealIndex(activeIndex); return; } const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1; // Get real index let realIndex; if (swiper.virtual && params.virtual.enabled) { if (params.loop) { realIndex = getVirtualRealIndex(activeIndex); } else { realIndex = activeIndex; } } else if (gridEnabled) { const firstSlideInColumn = swiper.slides.find(slideEl => slideEl.column === activeIndex); let activeSlideIndex = parseInt(firstSlideInColumn.getAttribute('data-swiper-slide-index'), 10); if (Number.isNaN(activeSlideIndex)) { activeSlideIndex = Math.max(swiper.slides.indexOf(firstSlideInColumn), 0); } realIndex = Math.floor(activeSlideIndex / params.grid.rows); } else if (swiper.slides[activeIndex]) { const slideIndex = swiper.slides[activeIndex].getAttribute('data-swiper-slide-index'); if (slideIndex) { realIndex = parseInt(slideIndex, 10); } else { realIndex = activeIndex; } } else { realIndex = activeIndex; } Object.assign(swiper, { previousSnapIndex, snapIndex, previousRealIndex, realIndex, previousIndex, activeIndex }); if (swiper.initialized) { preload(swiper); } swiper.emit('activeIndexChange'); swiper.emit('snapIndexChange'); if (swiper.initialized || swiper.params.runCallbacksOnInit) { if (previousRealIndex !== realIndex) { swiper.emit('realIndexChange'); } swiper.emit('slideChange'); } } function updateClickedSlide(el, path) { const swiper = this; const params = swiper.params; let slide = el.closest(`.${params.slideClass}, swiper-slide`); if (!slide && swiper.isElement && path && path.length > 1 && path.includes(el)) { [...path.slice(path.indexOf(el) + 1, path.length)].forEach(pathEl => { if (!slide && pathEl.matches && pathEl.matches(`.${params.slideClass}, swiper-slide`)) { slide = pathEl; } }); } let slideFound = false; let slideIndex; if (slide) { for (let i = 0; i < swiper.slides.length; i += 1) { if (swiper.slides[i] === slide) { slideFound = true; slideIndex = i; break; } } } if (slide && slideFound) { swiper.clickedSlide = slide; if (swiper.virtual && swiper.params.virtual.enabled) { swiper.clickedIndex = parseInt(slide.getAttribute('data-swiper-slide-index'), 10); } else { swiper.clickedIndex = slideIndex; } } else { swiper.clickedSlide = undefined; swiper.clickedIndex = undefined; return; } if (params.slideToClickedSlide && swiper.clickedIndex !== undefined && swiper.clickedIndex !== swiper.activeIndex) { swiper.slideToClickedSlide(); } } var update = { updateSize, updateSlides, updateAutoHeight, updateSlidesOffset, updateSlidesProgress, updateProgress, updateSlidesClasses, updateActiveIndex, updateClickedSlide }; function getSwiperTranslate(axis = this.isHorizontal() ? 'x' : 'y') { const swiper = this; const { params, rtlTranslate: rtl, translate, wrapperEl } = swiper; if (params.virtualTranslate) { return rtl ? -translate : translate; } if (params.cssMode) { return translate; } let currentTranslate = utils_getTranslate(wrapperEl, axis); currentTranslate += swiper.cssOverflowAdjustment(); if (rtl) currentTranslate = -currentTranslate; return currentTranslate || 0; } function setTranslate(translate, byController) { const swiper = this; const { rtlTranslate: rtl, params, wrapperEl, progress } = swiper; let x = 0; let y = 0; const z = 0; if (swiper.isHorizontal()) { x = rtl ? -translate : translate; } else { y = translate; } if (params.roundLengths) { x = Math.floor(x); y = Math.floor(y); } swiper.previousTranslate = swiper.translate; swiper.translate = swiper.isHorizontal() ? x : y; if (params.cssMode) { wrapperEl[swiper.isHorizontal() ? 'scrollLeft' : 'scrollTop'] = swiper.isHorizontal() ? -x : -y; } else if (!params.virtualTranslate) { if (swiper.isHorizontal()) { x -= swiper.cssOverflowAdjustment(); } else { y -= swiper.cssOverflowAdjustment(); } wrapperEl.style.transform = `translate3d(${x}px, ${y}px, ${z}px)`; } // Check if we need to update progress let newProgress; const translatesDiff = swiper.maxTranslate() - swiper.minTranslate(); if (translatesDiff === 0) { newProgress = 0; } else { newProgress = (translate - swiper.minTranslate()) / translatesDiff; } if (newProgress !== progress) { swiper.updateProgress(translate); } swiper.emit('setTranslate', swiper.translate, byController); } function minTranslate() { return -this.snapGrid[0]; } function maxTranslate() { return -this.snapGrid[this.snapGrid.length - 1]; } function translateTo(translate = 0, speed = this.params.speed, runCallbacks = true, translateBounds = true, internal) { const swiper = this; const { params, wrapperEl } = swiper; if (swiper.animating && params.preventInteractionOnTransition) { return false; } const minTranslate = swiper.minTranslate(); const maxTranslate = swiper.maxTranslate(); let newTranslate; if (translateBounds && translate > minTranslate) newTranslate = minTranslate;else if (translateBounds && translate < maxTranslate) newTranslate = maxTranslate;else newTranslate = translate; // Update progress swiper.updateProgress(newTranslate); if (params.cssMode) { const isH = swiper.isHorizontal(); if (speed === 0) { wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = -newTranslate; } else { if (!swiper.support.smoothScroll) { animateCSSModeScroll({ swiper, targetPosition: -newTranslate, side: isH ? 'left' : 'top' }); return true; } wrapperEl.scrollTo({ [isH ? 'left' : 'top']: -newTranslate, behavior: 'smooth' }); } return true; } if (speed === 0) { swiper.setTransition(0); swiper.setTranslate(newTranslate); if (runCallbacks) { swiper.emit('beforeTransitionStart', speed, internal); swiper.emit('transitionEnd'); } } else { swiper.setTransition(speed); swiper.setTranslate(newTranslate); if (runCallbacks) { swiper.emit('beforeTransitionStart', speed, internal); swiper.emit('transitionStart'); } if (!swiper.animating) { swiper.animating = true; if (!swiper.onTranslateToWrapperTransitionEnd) { swiper.onTranslateToWrapperTransitionEnd = function transitionEnd(e) { if (!swiper || swiper.destroyed) return; if (e.target !== this) return; swiper.wrapperEl.removeEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd); swiper.onTranslateToWrapperTransitionEnd = null; delete swiper.onTranslateToWrapperTransitionEnd; swiper.animating = false; if (runCallbacks) { swiper.emit('transitionEnd'); } }; } swiper.wrapperEl.addEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd); } } return true; } var translate = { getTranslate: getSwiperTranslate, setTranslate, minTranslate, maxTranslate, translateTo }; function setTransition(duration, byController) { const swiper = this; if (!swiper.params.cssMode) { swiper.wrapperEl.style.transitionDuration = `${duration}ms`; swiper.wrapperEl.style.transitionDelay = duration === 0 ? `0ms` : ''; } swiper.emit('setTransition', duration, byController); } function transitionEmit({ swiper, runCallbacks, direction, step }) { const { activeIndex, previousIndex } = swiper; let dir = direction; if (!dir) { if (activeIndex > previousIndex) dir = 'next';else if (activeIndex < previousIndex) dir = 'prev';else dir = 'reset'; } swiper.emit(`transition${step}`); if (runCallbacks && dir === 'reset') { swiper.emit(`slideResetTransition${step}`); } else if (runCallbacks && activeIndex !== previousIndex) { swiper.emit(`slideChangeTransition${step}`); if (dir === 'next') { swiper.emit(`slideNextTransition${step}`); } else { swiper.emit(`slidePrevTransition${step}`); } } } function transitionStart(runCallbacks = true, direction) { const swiper = this; const { params } = swiper; if (params.cssMode) return; if (params.autoHeight) { swiper.updateAutoHeight(); } transitionEmit({ swiper, runCallbacks, direction, step: 'Start' }); } function transitionEnd(runCallbacks = true, direction) { const swiper = this; const { params } = swiper; swiper.animating = false; if (params.cssMode) return; swiper.setTransition(0); transitionEmit({ swiper, runCallbacks, direction, step: 'End' }); } var transition = { setTransition, transitionStart, transitionEnd }; function slideTo(index = 0, speed, runCallbacks = true, internal, initial) { if (typeof index === 'string') { index = parseInt(index, 10); } const swiper = this; let slideIndex = index; if (slideIndex < 0) slideIndex = 0; const { params, snapGrid, slidesGrid, previousIndex, activeIndex, rtlTranslate: rtl, wrapperEl, enabled } = swiper; if (!enabled && !internal && !initial || swiper.destroyed || swiper.animating && params.preventInteractionOnTransition) { return false; } if (typeof speed === 'undefined') { speed = swiper.params.speed; } const skip = Math.min(swiper.params.slidesPerGroupSkip, slideIndex); let snapIndex = skip + Math.floor((slideIndex - skip) / swiper.params.slidesPerGroup); if (snapIndex >= snapGrid.length) snapIndex = snapGrid.length - 1; const translate = -snapGrid[snapIndex]; // Normalize slideIndex if (params.normalizeSlideIndex) { for (let i = 0; i < slidesGrid.length; i += 1) { const normalizedTranslate = -Math.floor(translate * 100); const normalizedGrid = Math.floor(slidesGrid[i] * 100); const normalizedGridNext = Math.floor(slidesGrid[i + 1] * 100); if (typeof slidesGrid[i + 1] !== 'undefined') { if (normalizedTranslate >= normalizedGrid && normalizedTranslate < normalizedGridNext - (normalizedGridNext - normalizedGrid) / 2) { slideIndex = i; } else if (normalizedTranslate >= normalizedGrid && normalizedTranslate < normalizedGridNext) { slideIndex = i + 1; } } else if (normalizedTranslate >= normalizedGrid) { slideIndex = i; } } } // Directions locks if (swiper.initialized && slideIndex !== activeIndex) { if (!swiper.allowSlideNext && (rtl ? translate > swiper.translate && translate > swiper.minTranslate() : translate < swiper.translate && translate < swiper.minTranslate())) { return false; } if (!swiper.allowSlidePrev && translate > swiper.translate && translate > swiper.maxTranslate()) { if ((activeIndex || 0) !== slideIndex) { return false; } } } if (slideIndex !== (previousIndex || 0) && runCallbacks) { swiper.emit('beforeSlideChangeStart'); } // Update progress swiper.updateProgress(translate); let direction; if (slideIndex > activeIndex) direction = 'next';else if (slideIndex < activeIndex) direction = 'prev';else direction = 'reset'; // initial virtual const isVirtual = swiper.virtual && swiper.params.virtual.enabled; const isInitialVirtual = isVirtual && initial; // Update Index if (!isInitialVirtual && (rtl && -translate === swiper.translate || !rtl && translate === swiper.translate)) { swiper.updateActiveIndex(slideIndex); // Update Height if (params.autoHeight) { swiper.updateAutoHeight(); } swiper.updateSlidesClasses(); if (params.effect !== 'slide') { swiper.setTranslate(translate); } if (direction !== 'reset') { swiper.transitionStart(runCallbacks, direction); swiper.transitionEnd(runCallbacks, direction); } return false; } if (params.cssMode) { const isH = swiper.isHorizontal(); const t = rtl ? translate : -translate; if (speed === 0) { if (isVirtual) { swiper.wrapperEl.style.scrollSnapType = 'none'; swiper._immediateVirtual = true; } if (isVirtual && !swiper._cssModeVirtualInitialSet && swiper.params.initialSlide > 0) { swiper._cssModeVirtualInitialSet = true; requestAnimationFrame(() => { wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = t; }); } else { wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = t; } if (isVirtual) { requestAnimationFrame(() => { swiper.wrapperEl.style.scrollSnapType = ''; swiper._immediateVirtual = false; }); } } else { if (!swiper.support.smoothScroll) { animateCSSModeScroll({ swiper, targetPosition: t, side: isH ? 'left' : 'top' }); return true; } wrapperEl.scrollTo({ [isH ? 'left' : 'top']: t, behavior: 'smooth' }); } return true; } const browser = getBrowser(); const isSafari = browser.isSafari; if (isVirtual && !initial && isSafari && swiper.isElement) { swiper.virtual.update(false, false, slideIndex); } swiper.setTransition(speed); swiper.setTranslate(translate); swiper.updateActiveIndex(slideIndex); swiper.updateSlidesClasses(); swiper.emit('beforeTransitionStart', speed, internal); swiper.transitionStart(runCallbacks, direction); if (speed === 0) { swiper.transitionEnd(runCallbacks, direction); } else if (!swiper.animating) { swiper.animating = true; if (!swiper.onSlideToWrapperTransitionEnd) { swiper.onSlideToWrapperTransitionEnd = function transitionEnd(e) { if (!swiper || swiper.destroyed) return; if (e.target !== this) return; swiper.wrapperEl.removeEventListener('transitionend', swiper.onSlideToWrapperTransitionEnd); swiper.onSlideToWrapperTransitionEnd = null; delete swiper.onSlideToWrapperTransitionEnd; swiper.transitionEnd(runCallbacks, direction); }; } swiper.wrapperEl.addEventListener('transitionend', swiper.onSlideToWrapperTransitionEnd); } return true; } function slideToLoop(index = 0, speed, runCallbacks = true, internal) { if (typeof index === 'string') { const indexAsNumber = parseInt(index, 10); index = indexAsNumber; } const swiper = this; if (swiper.destroyed) return; if (typeof speed === 'undefined') { speed = swiper.params.speed; } const gridEnabled = swiper.grid && swiper.params.grid && swiper.params.grid.rows > 1; let newIndex = index; if (swiper.params.loop) { if (swiper.virtual && swiper.params.virtual.enabled) { // eslint-disable-next-line newIndex = newIndex + swiper.virtual.slidesBefore; } else { let targetSlideIndex; if (gridEnabled) { const slideIndex = newIndex * swiper.params.grid.rows; targetSlideIndex = swiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === slideIndex).column; } else { targetSlideIndex = swiper.getSlideIndexByData(newIndex); } const cols = gridEnabled ? Math.ceil(swiper.slides.length / swiper.params.grid.rows) : swiper.slides.length; const { centeredSlides, slidesOffsetBefore, slidesOffsetAfter } = swiper.params; const bothDirections = centeredSlides || !!slidesOffsetBefore || !!slidesOffsetAfter; let slidesPerView = swiper.params.slidesPerView; if (slidesPerView === 'auto') { slidesPerView = swiper.slidesPerViewDynamic(); } else { slidesPerView = Math.ceil(parseFloat(swiper.params.slidesPerView, 10)); if (bothDirections && slidesPerView % 2 === 0) { slidesPerView = slidesPerView + 1; } } let needLoopFix = cols - targetSlideIndex < slidesPerView; if (bothDirections) { needLoopFix = needLoopFix || targetSlideIndex < Math.ceil(slidesPerView / 2); } if (internal && bothDirections && swiper.params.slidesPerView !== 'auto' && !gridEnabled) { needLoopFix = false; } if (needLoopFix) { const direction = bothDirections ? targetSlideIndex < swiper.activeIndex ? 'prev' : 'next' : targetSlideIndex - swiper.activeIndex - 1 < swiper.params.slidesPerView ? 'next' : 'prev'; swiper.loopFix({ direction, slideTo: true, activeSlideIndex: direction === 'next' ? targetSlideIndex + 1 : targetSlideIndex - cols + 1, slideRealIndex: direction === 'next' ? swiper.realIndex : undefined }); } if (gridEnabled) { const slideIndex = newIndex * swiper.params.grid.rows; newIndex = swiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === slideIndex).column; } else { newIndex = swiper.getSlideIndexByData(newIndex); } } } requestAnimationFrame(() => { swiper.slideTo(newIndex, speed, runCallbacks, internal); }); return swiper; } /* eslint no-unused-vars: "off" */ function slideNext(speed, runCallbacks = true, internal) { const swiper = this; const { enabled, params, animating } = swiper; if (!enabled || swiper.destroyed) return swiper; if (typeof speed === 'undefined') { speed = swiper.params.speed; } let perGroup = params.slidesPerGroup; if (params.slidesPerView === 'auto' && params.slidesPerGroup === 1 && params.slidesPerGroupAuto) { perGroup = Math.max(swiper.slidesPerViewDynamic('current', true), 1); } const increment = swiper.activeIndex < params.slidesPerGroupSkip ? 1 : perGroup; const isVirtual = swiper.virtual && params.virtual.enabled; if (params.loop) { if (animating && !isVirtual && params.loopPreventsSliding) return false; swiper.loopFix({ direction: 'next' }); // eslint-disable-next-line swiper._clientLeft = swiper.wrapperEl.clientLeft; if (swiper.activeIndex === swiper.slides.length - 1 && params.cssMode) { requestAnimationFrame(() => { swiper.slideTo(swiper.activeIndex + increment, speed, runCallbacks, internal); }); return true; } } if (params.rewind && swiper.isEnd) { return swiper.slideTo(0, speed, runCallbacks, internal); } return swiper.slideTo(swiper.activeIndex + increment, speed, runCallbacks, internal); } /* eslint no-unused-vars: "off" */ function slidePrev(speed, runCallbacks = true, internal) { const swiper = this; const { params, snapGrid, slidesGrid, rtlTranslate, enabled, animating } = swiper; if (!enabled || swiper.destroyed) return swiper; if (typeof speed === 'undefined') { speed = swiper.params.speed; } const isVirtual = swiper.virtual && params.virtual.enabled; if (params.loop) { if (animating && !isVirtual && params.loopPreventsSliding) return false; swiper.loopFix({ direction: 'prev' }); // eslint-disable-next-line swiper._clientLeft = swiper.wrapperEl.clientLeft; } const translate = rtlTranslate ? swiper.translate : -swiper.translate; function normalize(val) { if (val < 0) return -Math.floor(Math.abs(val)); return Math.floor(val); } const normalizedTranslate = normalize(translate); const normalizedSnapGrid = snapGrid.map(val => normalize(val)); const isFreeMode = params.freeMode && params.freeMode.enabled; let prevSnap = snapGrid[normalizedSnapGrid.indexOf(normalizedTranslate) - 1]; if (typeof prevSnap === 'undefined' && (params.cssMode || isFreeMode)) { let prevSnapIndex; snapGrid.forEach((snap, snapIndex) => { if (normalizedTranslate >= snap) { // prevSnap = snap; prevSnapIndex = snapIndex; } }); if (typeof prevSnapIndex !== 'undefined') { prevSnap = isFreeMode ? snapGrid[prevSnapIndex] : snapGrid[prevSnapIndex > 0 ? prevSnapIndex - 1 : prevSnapIndex]; } } let prevIndex = 0; if (typeof prevSnap !== 'undefined') { prevIndex = slidesGrid.indexOf(prevSnap); if (prevIndex < 0) prevIndex = swiper.activeIndex - 1; if (params.slidesPerView === 'auto' && params.slidesPerGroup === 1 && params.slidesPerGroupAuto) { prevIndex = prevIndex - swiper.slidesPerViewDynamic('previous', true) + 1; prevIndex = Math.max(prevIndex, 0); } } if (params.rewind && swiper.isBeginning) { const lastIndex = swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual ? swiper.virtual.slides.length - 1 : swiper.slides.length - 1; return swiper.slideTo(lastIndex, speed, runCallbacks, internal); } else if (params.loop && swiper.activeIndex === 0 && params.cssMode) { requestAnimationFrame(() => { swiper.slideTo(prevIndex, speed, runCallbacks, internal); }); return true; } return swiper.slideTo(prevIndex, speed, runCallbacks, internal); } /* eslint no-unused-vars: "off" */ function slideReset(speed, runCallbacks = true, internal) { const swiper = this; if (swiper.destroyed) return; if (typeof speed === 'undefined') { speed = swiper.params.speed; } return swiper.slideTo(swiper.activeIndex, speed, runCallbacks, internal); } /* eslint no-unused-vars: "off" */ function slideToClosest(speed, runCallbacks = true, internal, threshold = 0.5) { const swiper = this; if (swiper.destroyed) return; if (typeof speed === 'undefined') { speed = swiper.params.speed; } let index = swiper.activeIndex; const skip = Math.min(swiper.params.slidesPerGroupSkip, index); const snapIndex = skip + Math.floor((index - skip) / swiper.params.slidesPerGroup); const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate; if (translate >= swiper.snapGrid[snapIndex]) { // The current translate is on or after the current snap index, so the choice // is between the current index and the one after it. const currentSnap = swiper.snapGrid[snapIndex]; const nextSnap = swiper.snapGrid[snapIndex + 1]; if (translate - currentSnap > (nextSnap - currentSnap) * threshold) { index += swiper.params.slidesPerGroup; } } else { // The current translate is before the current snap index, so the choice // is between the current index and the one before it. const prevSnap = swiper.snapGrid[snapIndex - 1]; const currentSnap = swiper.snapGrid[snapIndex]; if (translate - prevSnap <= (currentSnap - prevSnap) * threshold) { index -= swiper.params.slidesPerGroup; } } index = Math.max(index, 0); index = Math.min(index, swiper.slidesGrid.length - 1); return swiper.slideTo(index, speed, runCallbacks, internal); } function slideToClickedSlide() { const swiper = this; if (swiper.destroyed) return; const { params, slidesEl } = swiper; const slidesPerView = params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : params.slidesPerView; let slideToIndex = swiper.getSlideIndexWhenGrid(swiper.clickedIndex); let realIndex; const slideSelector = swiper.isElement ? `swiper-slide` : `.${params.slideClass}`; const isGrid = swiper.grid && swiper.params.grid && swiper.params.grid.rows > 1; if (params.loop) { if (swiper.animating) return; realIndex = parseInt(swiper.clickedSlide.getAttribute('data-swiper-slide-index'), 10); if (params.centeredSlides) { swiper.slideToLoop(realIndex); } else if (slideToIndex > (isGrid ? (swiper.slides.length - slidesPerView) / 2 - (swiper.params.grid.rows - 1) : swiper.slides.length - slidesPerView)) { swiper.loopFix(); slideToIndex = swiper.getSlideIndex(utils_elementChildren(slidesEl, `${slideSelector}[data-swiper-slide-index="${realIndex}"]`)[0]); utils_nextTick(() => { swiper.slideTo(slideToIndex); }); } else { swiper.slideTo(slideToIndex); } } else { swiper.slideTo(slideToIndex); } } var slide = { slideTo, slideToLoop, slideNext, slidePrev, slideReset, slideToClosest, slideToClickedSlide }; function loopCreate(slideRealIndex, initial) { const swiper = this; const { params, slidesEl } = swiper; if (!params.loop || swiper.virtual && swiper.params.virtual.enabled) return; const initSlides = () => { const slides = utils_elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`); slides.forEach((el, index) => { el.setAttribute('data-swiper-slide-index', index); }); }; const clearBlankSlides = () => { const slides = utils_elementChildren(slidesEl, `.${params.slideBlankClass}`); slides.forEach(el => { el.remove(); }); if (slides.length > 0) { swiper.recalcSlides(); swiper.updateSlides(); } }; const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1; if (params.loopAddBlankSlides && (params.slidesPerGroup > 1 || gridEnabled)) { clearBlankSlides(); } const slidesPerGroup = params.slidesPerGroup * (gridEnabled ? params.grid.rows : 1); const shouldFillGroup = swiper.slides.length % slidesPerGroup !== 0; const shouldFillGrid = gridEnabled && swiper.slides.length % params.grid.rows !== 0; const addBlankSlides = amountOfSlides => { for (let i = 0; i < amountOfSlides; i += 1) { const slideEl = swiper.isElement ? utils_createElement('swiper-slide', [params.slideBlankClass]) : utils_createElement('div', [params.slideClass, params.slideBlankClass]); swiper.slidesEl.append(slideEl); } }; if (shouldFillGroup) { if (params.loopAddBlankSlides) { const slidesToAdd = slidesPerGroup - swiper.slides.length % slidesPerGroup; addBlankSlides(slidesToAdd); swiper.recalcSlides(); swiper.updateSlides(); } else { showWarning('Swiper Loop Warning: The number of slides is not even to slidesPerGroup, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)'); } initSlides(); } else if (shouldFillGrid) { if (params.loopAddBlankSlides) { const slidesToAdd = params.grid.rows - swiper.slides.length % params.grid.rows; addBlankSlides(slidesToAdd); swiper.recalcSlides(); swiper.updateSlides(); } else { showWarning('Swiper Loop Warning: The number of slides is not even to grid.rows, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)'); } initSlides(); } else { initSlides(); } const bothDirections = params.centeredSlides || !!params.slidesOffsetBefore || !!params.slidesOffsetAfter; swiper.loopFix({ slideRealIndex, direction: bothDirections ? undefined : 'next', initial }); } function loopFix({ slideRealIndex, slideTo = true, direction, setTranslate, activeSlideIndex, initial, byController, byMousewheel } = {}) { const swiper = this; if (!swiper.params.loop) return; swiper.emit('beforeLoopFix'); const { slides, allowSlidePrev, allowSlideNext, slidesEl, params } = swiper; const { centeredSlides, slidesOffsetBefore, slidesOffsetAfter, initialSlide } = params; const bothDirections = centeredSlides || !!slidesOffsetBefore || !!slidesOffsetAfter; swiper.allowSlidePrev = true; swiper.allowSlideNext = true; if (swiper.virtual && params.virtual.enabled) { if (slideTo) { if (!bothDirections && swiper.snapIndex === 0) { swiper.slideTo(swiper.virtual.slides.length, 0, false, true); } else if (bothDirections && swiper.snapIndex < params.slidesPerView) { swiper.slideTo(swiper.virtual.slides.length + swiper.snapIndex, 0, false, true); } else if (swiper.snapIndex === swiper.snapGrid.length - 1) { swiper.slideTo(swiper.virtual.slidesBefore, 0, false, true); } } swiper.allowSlidePrev = allowSlidePrev; swiper.allowSlideNext = allowSlideNext; swiper.emit('loopFix'); return; } let slidesPerView = params.slidesPerView; if (slidesPerView === 'auto') { slidesPerView = swiper.slidesPerViewDynamic(); } else { slidesPerView = Math.ceil(parseFloat(params.slidesPerView, 10)); if (bothDirections && slidesPerView % 2 === 0) { slidesPerView = slidesPerView + 1; } } const slidesPerGroup = params.slidesPerGroupAuto ? slidesPerView : params.slidesPerGroup; let loopedSlides = bothDirections ? Math.max(slidesPerGroup, Math.ceil(slidesPerView / 2)) : slidesPerGroup; if (loopedSlides % slidesPerGroup !== 0) { loopedSlides += slidesPerGroup - loopedSlides % slidesPerGroup; } loopedSlides += params.loopAdditionalSlides; swiper.loopedSlides = loopedSlides; const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1; if (slides.length < slidesPerView + loopedSlides || swiper.params.effect === 'cards' && slides.length < slidesPerView + loopedSlides * 2) { showWarning('Swiper Loop Warning: The number of slides is not enough for loop mode, it will be disabled or not function properly. You need to add more slides (or make duplicates) or lower the values of slidesPerView and slidesPerGroup parameters'); } else if (gridEnabled && params.grid.fill === 'row') { showWarning('Swiper Loop Warning: Loop mode is not compatible with grid.fill = `row`'); } const prependSlidesIndexes = []; const appendSlidesIndexes = []; const cols = gridEnabled ? Math.ceil(slides.length / params.grid.rows) : slides.length; const isInitialOverflow = initial && cols - initialSlide < slidesPerView && !bothDirections; let activeIndex = isInitialOverflow ? initialSlide : swiper.activeIndex; if (typeof activeSlideIndex === 'undefined') { activeSlideIndex = swiper.getSlideIndex(slides.find(el => el.classList.contains(params.slideActiveClass))); } else { activeIndex = activeSlideIndex; } const isNext = direction === 'next' || !direction; const isPrev = direction === 'prev' || !direction; let slidesPrepended = 0; let slidesAppended = 0; const activeColIndex = gridEnabled ? slides[activeSlideIndex].column : activeSlideIndex; const activeColIndexWithShift = activeColIndex + (bothDirections && typeof setTranslate === 'undefined' ? -slidesPerView / 2 + 0.5 : 0); // prepend last slides before start if (activeColIndexWithShift < loopedSlides) { slidesPrepended = Math.max(loopedSlides - activeColIndexWithShift, slidesPerGroup); for (let i = 0; i < loopedSlides - activeColIndexWithShift; i += 1) { const index = i - Math.floor(i / cols) * cols; if (gridEnabled) { const colIndexToPrepend = cols - index - 1; for (let i = slides.length - 1; i >= 0; i -= 1) { if (slides[i].column === colIndexToPrepend) prependSlidesIndexes.push(i); } // slides.forEach((slide, slideIndex) => { // if (slide.column === colIndexToPrepend) prependSlidesIndexes.push(slideIndex); // }); } else { prependSlidesIndexes.push(cols - index - 1); } } } else if (activeColIndexWithShift + slidesPerView > cols - loopedSlides) { slidesAppended = Math.max(activeColIndexWithShift - (cols - loopedSlides * 2), slidesPerGroup); if (isInitialOverflow) { slidesAppended = Math.max(slidesAppended, slidesPerView - cols + initialSlide + 1); } for (let i = 0; i < slidesAppended; i += 1) { const index = i - Math.floor(i / cols) * cols; if (gridEnabled) { slides.forEach((slide, slideIndex) => { if (slide.column === index) appendSlidesIndexes.push(slideIndex); }); } else { appendSlidesIndexes.push(index); } } } swiper.__preventObserver__ = true; requestAnimationFrame(() => { swiper.__preventObserver__ = false; }); if (swiper.params.effect === 'cards' && slides.length < slidesPerView + loopedSlides * 2) { if (appendSlidesIndexes.includes(activeSlideIndex)) { appendSlidesIndexes.splice(appendSlidesIndexes.indexOf(activeSlideIndex), 1); } if (prependSlidesIndexes.includes(activeSlideIndex)) { prependSlidesIndexes.splice(prependSlidesIndexes.indexOf(activeSlideIndex), 1); } } if (isPrev) { prependSlidesIndexes.forEach(index => { slides[index].swiperLoopMoveDOM = true; slidesEl.prepend(slides[index]); slides[index].swiperLoopMoveDOM = false; }); } if (isNext) { appendSlidesIndexes.forEach(index => { slides[index].swiperLoopMoveDOM = true; slidesEl.append(slides[index]); slides[index].swiperLoopMoveDOM = false; }); } swiper.recalcSlides(); if (params.slidesPerView === 'auto') { swiper.updateSlides(); } else if (gridEnabled && (prependSlidesIndexes.length > 0 && isPrev || appendSlidesIndexes.length > 0 && isNext)) { swiper.slides.forEach((slide, slideIndex) => { swiper.grid.updateSlide(slideIndex, slide, swiper.slides); }); } if (params.watchSlidesProgress) { swiper.updateSlidesOffset(); } if (slideTo) { if (prependSlidesIndexes.length > 0 && isPrev) { if (typeof slideRealIndex === 'undefined') { const currentSlideTranslate = swiper.slidesGrid[activeIndex]; const newSlideTranslate = swiper.slidesGrid[activeIndex + slidesPrepended]; const diff = newSlideTranslate - currentSlideTranslate; if (byMousewheel) { swiper.setTranslate(swiper.translate - diff); } else { swiper.slideTo(activeIndex + Math.ceil(slidesPrepended), 0, false, true); if (setTranslate) { swiper.touchEventsData.startTranslate = swiper.touchEventsData.startTranslate - diff; swiper.touchEventsData.currentTranslate = swiper.touchEventsData.currentTranslate - diff; } } } else { if (setTranslate) { const shift = gridEnabled ? prependSlidesIndexes.length / params.grid.rows : prependSlidesIndexes.length; swiper.slideTo(swiper.activeIndex + shift, 0, false, true); swiper.touchEventsData.currentTranslate = swiper.translate; } } } else if (appendSlidesIndexes.length > 0 && isNext) { if (typeof slideRealIndex === 'undefined') { const currentSlideTranslate = swiper.slidesGrid[activeIndex]; const newSlideTranslate = swiper.slidesGrid[activeIndex - slidesAppended]; const diff = newSlideTranslate - currentSlideTranslate; if (byMousewheel) { swiper.setTranslate(swiper.translate - diff); } else { swiper.slideTo(activeIndex - slidesAppended, 0, false, true); if (setTranslate) { swiper.touchEventsData.startTranslate = swiper.touchEventsData.startTranslate - diff; swiper.touchEventsData.currentTranslate = swiper.touchEventsData.currentTranslate - diff; } } } else { const shift = gridEnabled ? appendSlidesIndexes.length / params.grid.rows : appendSlidesIndexes.length; swiper.slideTo(swiper.activeIndex - shift, 0, false, true); } } } swiper.allowSlidePrev = allowSlidePrev; swiper.allowSlideNext = allowSlideNext; if (swiper.controller && swiper.controller.control && !byController) { const loopParams = { slideRealIndex, direction, setTranslate, activeSlideIndex, byController: true }; if (Array.isArray(swiper.controller.control)) { swiper.controller.control.forEach(c => { if (!c.destroyed && c.params.loop) c.loopFix({ ...loopParams, slideTo: c.params.slidesPerView === params.slidesPerView ? slideTo : false }); }); } else if (swiper.controller.control instanceof swiper.constructor && swiper.controller.control.params.loop) { swiper.controller.control.loopFix({ ...loopParams, slideTo: swiper.controller.control.params.slidesPerView === params.slidesPerView ? slideTo : false }); } } swiper.emit('loopFix'); } function loopDestroy() { const swiper = this; const { params, slidesEl } = swiper; if (!params.loop || !slidesEl || swiper.virtual && swiper.params.virtual.enabled) return; swiper.recalcSlides(); const newSlidesOrder = []; swiper.slides.forEach(slideEl => { const index = typeof slideEl.swiperSlideIndex === 'undefined' ? slideEl.getAttribute('data-swiper-slide-index') * 1 : slideEl.swiperSlideIndex; newSlidesOrder[index] = slideEl; }); swiper.slides.forEach(slideEl => { slideEl.removeAttribute('data-swiper-slide-index'); }); newSlidesOrder.forEach(slideEl => { slidesEl.append(slideEl); }); swiper.recalcSlides(); swiper.slideTo(swiper.realIndex, 0); } var loop = { loopCreate, loopFix, loopDestroy }; function setGrabCursor(moving) { const swiper = this; if (!swiper.params.simulateTouch || swiper.params.watchOverflow && swiper.isLocked || swiper.params.cssMode) return; const el = swiper.params.touchEventsTarget === 'container' ? swiper.el : swiper.wrapperEl; if (swiper.isElement) { swiper.__preventObserver__ = true; } el.style.cursor = 'move'; el.style.cursor = moving ? 'grabbing' : 'grab'; if (swiper.isElement) { requestAnimationFrame(() => { swiper.__preventObserver__ = false; }); } } function unsetGrabCursor() { const swiper = this; if (swiper.params.watchOverflow && swiper.isLocked || swiper.params.cssMode) { return; } if (swiper.isElement) { swiper.__preventObserver__ = true; } swiper[swiper.params.touchEventsTarget === 'container' ? 'el' : 'wrapperEl'].style.cursor = ''; if (swiper.isElement) { requestAnimationFrame(() => { swiper.__preventObserver__ = false; }); } } var grabCursor = { setGrabCursor, unsetGrabCursor }; // Modified from https://stackoverflow.com/questions/54520554/custom-element-getrootnode-closest-function-crossing-multiple-parent-shadowd function closestElement(selector, base = this) { function __closestFrom(el) { if (!el || el === ssr_window_esm_getDocument() || el === ssr_window_esm_getWindow()) return null; if (el.assignedSlot) el = el.assignedSlot; const found = el.closest(selector); if (!found && !el.getRootNode) { return null; } return found || __closestFrom(el.getRootNode().host); } return __closestFrom(base); } function preventEdgeSwipe(swiper, event, startX) { const window = ssr_window_esm_getWindow(); const { params } = swiper; const edgeSwipeDetection = params.edgeSwipeDetection; const edgeSwipeThreshold = params.edgeSwipeThreshold; if (edgeSwipeDetection && (startX <= edgeSwipeThreshold || startX >= window.innerWidth - edgeSwipeThreshold)) { if (edgeSwipeDetection === 'prevent') { event.preventDefault(); return true; } return false; } return true; } function onTouchStart(event) { const swiper = this; const document = ssr_window_esm_getDocument(); let e = event; if (e.originalEvent) e = e.originalEvent; const data = swiper.touchEventsData; if (e.type === 'pointerdown') { if (data.pointerId !== null && data.pointerId !== e.pointerId) { return; } data.pointerId = e.pointerId; } else if (e.type === 'touchstart' && e.targetTouches.length === 1) { data.touchId = e.targetTouches[0].identifier; } if (e.type === 'touchstart') { // don't proceed touch event preventEdgeSwipe(swiper, e, e.targetTouches[0].pageX); return; } const { params, touches, enabled } = swiper; if (!enabled) return; if (!params.simulateTouch && e.pointerType === 'mouse') return; if (swiper.animating && params.preventInteractionOnTransition) { return; } if (!swiper.animating && params.cssMode && params.loop) { swiper.loopFix(); } let targetEl = e.target; if (params.touchEventsTarget === 'wrapper') { if (!elementIsChildOf(targetEl, swiper.wrapperEl)) return; } if ('which' in e && e.which === 3) return; if ('button' in e && e.button > 0) return; if (data.isTouched && data.isMoved) return; // change target el for shadow root component const swipingClassHasValue = !!params.noSwipingClass && params.noSwipingClass !== ''; // eslint-disable-next-line const eventPath = e.composedPath ? e.composedPath() : e.path; if (swipingClassHasValue && e.target && e.target.shadowRoot && eventPath) { targetEl = eventPath[0]; } const noSwipingSelector = params.noSwipingSelector ? params.noSwipingSelector : `.${params.noSwipingClass}`; const isTargetShadow = !!(e.target && e.target.shadowRoot); // use closestElement for shadow root element to get the actual closest for nested shadow root element if (params.noSwiping && (isTargetShadow ? closestElement(noSwipingSelector, targetEl) : targetEl.closest(noSwipingSelector))) { swiper.allowClick = true; return; } if (params.swipeHandler) { if (!targetEl.closest(params.swipeHandler)) return; } touches.currentX = e.pageX; touches.currentY = e.pageY; const startX = touches.currentX; const startY = touches.currentY; // Do NOT start if iOS edge swipe is detected. Otherwise iOS app cannot swipe-to-go-back anymore if (!preventEdgeSwipe(swiper, e, startX)) { return; } Object.assign(data, { isTouched: true, isMoved: false, allowTouchCallbacks: true, isScrolling: undefined, startMoving: undefined }); touches.startX = startX; touches.startY = startY; data.touchStartTime = utils_now(); swiper.allowClick = true; swiper.updateSize(); swiper.swipeDirection = undefined; if (params.threshold > 0) data.allowThresholdMove = false; let preventDefault = true; if (targetEl.matches(data.focusableElements)) { preventDefault = false; if (targetEl.nodeName === 'SELECT') { data.isTouched = false; } } if (document.activeElement && document.activeElement.matches(data.focusableElements) && document.activeElement !== targetEl && (e.pointerType === 'mouse' || e.pointerType !== 'mouse' && !targetEl.matches(data.focusableElements))) { document.activeElement.blur(); } const shouldPreventDefault = preventDefault && swiper.allowTouchMove && params.touchStartPreventDefault; if ((params.touchStartForcePreventDefault || shouldPreventDefault) && !targetEl.isContentEditable) { e.preventDefault(); } if (params.freeMode && params.freeMode.enabled && swiper.freeMode && swiper.animating && !params.cssMode) { swiper.freeMode.onTouchStart(); } swiper.emit('touchStart', e); } function onTouchMove(event) { const document = ssr_window_esm_getDocument(); const swiper = this; const data = swiper.touchEventsData; const { params, touches, rtlTranslate: rtl, enabled } = swiper; if (!enabled) return; if (!params.simulateTouch && event.pointerType === 'mouse') return; let e = event; if (e.originalEvent) e = e.originalEvent; if (e.type === 'pointermove') { if (data.touchId !== null) return; // return from pointer if we use touch const id = e.pointerId; if (id !== data.pointerId) return; } let targetTouch; if (e.type === 'touchmove') { targetTouch = [...e.changedTouches].find(t => t.identifier === data.touchId); if (!targetTouch || targetTouch.identifier !== data.touchId) return; } else { targetTouch = e; } if (!data.isTouched) { if (data.startMoving && data.isScrolling) { swiper.emit('touchMoveOpposite', e); } return; } const pageX = targetTouch.pageX; const pageY = targetTouch.pageY; if (e.preventedByNestedSwiper) { touches.startX = pageX; touches.startY = pageY; return; } if (!swiper.allowTouchMove) { if (!e.target.matches(data.focusableElements)) { swiper.allowClick = false; } if (data.isTouched) { Object.assign(touches, { startX: pageX, startY: pageY, currentX: pageX, currentY: pageY }); data.touchStartTime = utils_now(); } return; } if (params.touchReleaseOnEdges && !params.loop) { if (swiper.isVertical()) { // Vertical if (pageY < touches.startY && swiper.translate <= swiper.maxTranslate() || pageY > touches.startY && swiper.translate >= swiper.minTranslate()) { data.isTouched = false; data.isMoved = false; return; } } else if (rtl && (pageX > touches.startX && -swiper.translate <= swiper.maxTranslate() || pageX < touches.startX && -swiper.translate >= swiper.minTranslate())) { return; } else if (!rtl && (pageX < touches.startX && swiper.translate <= swiper.maxTranslate() || pageX > touches.startX && swiper.translate >= swiper.minTranslate())) { return; } } if (document.activeElement && document.activeElement.matches(data.focusableElements) && document.activeElement !== e.target && e.pointerType !== 'mouse') { document.activeElement.blur(); } if (document.activeElement) { if (e.target === document.activeElement && e.target.matches(data.focusableElements)) { data.isMoved = true; swiper.allowClick = false; return; } } if (data.allowTouchCallbacks) { swiper.emit('touchMove', e); } touches.previousX = touches.currentX; touches.previousY = touches.currentY; touches.currentX = pageX; touches.currentY = pageY; const diffX = touches.currentX - touches.startX; const diffY = touches.currentY - touches.startY; if (swiper.params.threshold && Math.sqrt(diffX ** 2 + diffY ** 2) < swiper.params.threshold) return; if (typeof data.isScrolling === 'undefined') { let touchAngle; if (swiper.isHorizontal() && touches.currentY === touches.startY || swiper.isVertical() && touches.currentX === touches.startX) { data.isScrolling = false; } else { // eslint-disable-next-line if (diffX * diffX + diffY * diffY >= 25) { touchAngle = Math.atan2(Math.abs(diffY), Math.abs(diffX)) * 180 / Math.PI; data.isScrolling = swiper.isHorizontal() ? touchAngle > params.touchAngle : 90 - touchAngle > params.touchAngle; } } } if (data.isScrolling) { swiper.emit('touchMoveOpposite', e); } if (typeof data.startMoving === 'undefined') { if (touches.currentX !== touches.startX || touches.currentY !== touches.startY) { data.startMoving = true; } } if (data.isScrolling || e.type === 'touchmove' && data.preventTouchMoveFromPointerMove) { data.isTouched = false; return; } if (!data.startMoving) { return; } swiper.allowClick = false; if (!params.cssMode && e.cancelable) { e.preventDefault(); } if (params.touchMoveStopPropagation && !params.nested) { e.stopPropagation(); } let diff = swiper.isHorizontal() ? diffX : diffY; let touchesDiff = swiper.isHorizontal() ? touches.currentX - touches.previousX : touches.currentY - touches.previousY; if (params.oneWayMovement) { diff = Math.abs(diff) * (rtl ? 1 : -1); touchesDiff = Math.abs(touchesDiff) * (rtl ? 1 : -1); } touches.diff = diff; diff *= params.touchRatio; if (rtl) { diff = -diff; touchesDiff = -touchesDiff; } const prevTouchesDirection = swiper.touchesDirection; swiper.swipeDirection = diff > 0 ? 'prev' : 'next'; swiper.touchesDirection = touchesDiff > 0 ? 'prev' : 'next'; const isLoop = swiper.params.loop && !params.cssMode; const allowLoopFix = swiper.touchesDirection === 'next' && swiper.allowSlideNext || swiper.touchesDirection === 'prev' && swiper.allowSlidePrev; if (!data.isMoved) { if (isLoop && allowLoopFix) { swiper.loopFix({ direction: swiper.swipeDirection }); } data.startTranslate = swiper.getTranslate(); swiper.setTransition(0); if (swiper.animating) { const evt = new window.CustomEvent('transitionend', { bubbles: true, cancelable: true, detail: { bySwiperTouchMove: true } }); swiper.wrapperEl.dispatchEvent(evt); } data.allowMomentumBounce = false; // Grab Cursor if (params.grabCursor && (swiper.allowSlideNext === true || swiper.allowSlidePrev === true)) { swiper.setGrabCursor(true); } swiper.emit('sliderFirstMove', e); } let loopFixed; new Date().getTime(); if (params._loopSwapReset !== false && data.isMoved && data.allowThresholdMove && prevTouchesDirection !== swiper.touchesDirection && isLoop && allowLoopFix && Math.abs(diff) >= 1) { Object.assign(touches, { startX: pageX, startY: pageY, currentX: pageX, currentY: pageY, startTranslate: data.currentTranslate }); data.loopSwapReset = true; data.startTranslate = data.currentTranslate; return; } swiper.emit('sliderMove', e); data.isMoved = true; data.currentTranslate = diff + data.startTranslate; let disableParentSwiper = true; let resistanceRatio = params.resistanceRatio; if (params.touchReleaseOnEdges) { resistanceRatio = 0; } if (diff > 0) { if (isLoop && allowLoopFix && !loopFixed && data.allowThresholdMove && data.currentTranslate > (params.centeredSlides ? swiper.minTranslate() - swiper.slidesSizesGrid[swiper.activeIndex + 1] - (params.slidesPerView !== 'auto' && swiper.slides.length - params.slidesPerView >= 2 ? swiper.slidesSizesGrid[swiper.activeIndex + 1] + swiper.params.spaceBetween : 0) - swiper.params.spaceBetween : swiper.minTranslate())) { swiper.loopFix({ direction: 'prev', setTranslate: true, activeSlideIndex: 0 }); } if (data.currentTranslate > swiper.minTranslate()) { disableParentSwiper = false; if (params.resistance) { data.currentTranslate = swiper.minTranslate() - 1 + (-swiper.minTranslate() + data.startTranslate + diff) ** resistanceRatio; } } } else if (diff < 0) { if (isLoop && allowLoopFix && !loopFixed && data.allowThresholdMove && data.currentTranslate < (params.centeredSlides ? swiper.maxTranslate() + swiper.slidesSizesGrid[swiper.slidesSizesGrid.length - 1] + swiper.params.spaceBetween + (params.slidesPerView !== 'auto' && swiper.slides.length - params.slidesPerView >= 2 ? swiper.slidesSizesGrid[swiper.slidesSizesGrid.length - 1] + swiper.params.spaceBetween : 0) : swiper.maxTranslate())) { swiper.loopFix({ direction: 'next', setTranslate: true, activeSlideIndex: swiper.slides.length - (params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : Math.ceil(parseFloat(params.slidesPerView, 10))) }); } if (data.currentTranslate < swiper.maxTranslate()) { disableParentSwiper = false; if (params.resistance) { data.currentTranslate = swiper.maxTranslate() + 1 - (swiper.maxTranslate() - data.startTranslate - diff) ** resistanceRatio; } } } if (disableParentSwiper) { e.preventedByNestedSwiper = true; } // Directions locks if (!swiper.allowSlideNext && swiper.swipeDirection === 'next' && data.currentTranslate < data.startTranslate) { data.currentTranslate = data.startTranslate; } if (!swiper.allowSlidePrev && swiper.swipeDirection === 'prev' && data.currentTranslate > data.startTranslate) { data.currentTranslate = data.startTranslate; } if (!swiper.allowSlidePrev && !swiper.allowSlideNext) { data.currentTranslate = data.startTranslate; } // Threshold if (params.threshold > 0) { if (Math.abs(diff) > params.threshold || data.allowThresholdMove) { if (!data.allowThresholdMove) { data.allowThresholdMove = true; touches.startX = touches.currentX; touches.startY = touches.currentY; data.currentTranslate = data.startTranslate; touches.diff = swiper.isHorizontal() ? touches.currentX - touches.startX : touches.currentY - touches.startY; return; } } else { data.currentTranslate = data.startTranslate; return; } } if (!params.followFinger || params.cssMode) return; // Update active index in free mode if (params.freeMode && params.freeMode.enabled && swiper.freeMode || params.watchSlidesProgress) { swiper.updateActiveIndex(); swiper.updateSlidesClasses(); } if (params.freeMode && params.freeMode.enabled && swiper.freeMode) { swiper.freeMode.onTouchMove(); } // Update progress swiper.updateProgress(data.currentTranslate); // Update translate swiper.setTranslate(data.currentTranslate); } function onTouchEnd(event) { const swiper = this; const data = swiper.touchEventsData; let e = event; if (e.originalEvent) e = e.originalEvent; let targetTouch; const isTouchEvent = e.type === 'touchend' || e.type === 'touchcancel'; if (!isTouchEvent) { if (data.touchId !== null) return; // return from pointer if we use touch if (e.pointerId !== data.pointerId) return; targetTouch = e; } else { targetTouch = [...e.changedTouches].find(t => t.identifier === data.touchId); if (!targetTouch || targetTouch.identifier !== data.touchId) return; } if (['pointercancel', 'pointerout', 'pointerleave', 'contextmenu'].includes(e.type)) { const proceed = ['pointercancel', 'contextmenu'].includes(e.type) && (swiper.browser.isSafari || swiper.browser.isWebView); if (!proceed) { return; } } data.pointerId = null; data.touchId = null; const { params, touches, rtlTranslate: rtl, slidesGrid, enabled } = swiper; if (!enabled) return; if (!params.simulateTouch && e.pointerType === 'mouse') return; if (data.allowTouchCallbacks) { swiper.emit('touchEnd', e); } data.allowTouchCallbacks = false; if (!data.isTouched) { if (data.isMoved && params.grabCursor) { swiper.setGrabCursor(false); } data.isMoved = false; data.startMoving = false; return; } // Return Grab Cursor if (params.grabCursor && data.isMoved && data.isTouched && (swiper.allowSlideNext === true || swiper.allowSlidePrev === true)) { swiper.setGrabCursor(false); } // Time diff const touchEndTime = utils_now(); const timeDiff = touchEndTime - data.touchStartTime; // Tap, doubleTap, Click if (swiper.allowClick) { const pathTree = e.path || e.composedPath && e.composedPath(); swiper.updateClickedSlide(pathTree && pathTree[0] || e.target, pathTree); swiper.emit('tap click', e); if (timeDiff < 300 && touchEndTime - data.lastClickTime < 300) { swiper.emit('doubleTap doubleClick', e); } } data.lastClickTime = utils_now(); utils_nextTick(() => { if (!swiper.destroyed) swiper.allowClick = true; }); if (!data.isTouched || !data.isMoved || !swiper.swipeDirection || touches.diff === 0 && !data.loopSwapReset || data.currentTranslate === data.startTranslate && !data.loopSwapReset) { data.isTouched = false; data.isMoved = false; data.startMoving = false; return; } data.isTouched = false; data.isMoved = false; data.startMoving = false; let currentPos; if (params.followFinger) { currentPos = rtl ? swiper.translate : -swiper.translate; } else { currentPos = -data.currentTranslate; } if (params.cssMode) { return; } if (params.freeMode && params.freeMode.enabled) { swiper.freeMode.onTouchEnd({ currentPos }); return; } // Find current slide const swipeToLast = currentPos >= -swiper.maxTranslate() && !swiper.params.loop; let stopIndex = 0; let groupSize = swiper.slidesSizesGrid[0]; for (let i = 0; i < slidesGrid.length; i += i < params.slidesPerGroupSkip ? 1 : params.slidesPerGroup) { const increment = i < params.slidesPerGroupSkip - 1 ? 1 : params.slidesPerGroup; if (typeof slidesGrid[i + increment] !== 'undefined') { if (swipeToLast || currentPos >= slidesGrid[i] && currentPos < slidesGrid[i + increment]) { stopIndex = i; groupSize = slidesGrid[i + increment] - slidesGrid[i]; } } else if (swipeToLast || currentPos >= slidesGrid[i]) { stopIndex = i; groupSize = slidesGrid[slidesGrid.length - 1] - slidesGrid[slidesGrid.length - 2]; } } let rewindFirstIndex = null; let rewindLastIndex = null; if (params.rewind) { if (swiper.isBeginning) { rewindLastIndex = params.virtual && params.virtual.enabled && swiper.virtual ? swiper.virtual.slides.length - 1 : swiper.slides.length - 1; } else if (swiper.isEnd) { rewindFirstIndex = 0; } } // Find current slide size const ratio = (currentPos - slidesGrid[stopIndex]) / groupSize; const increment = stopIndex < params.slidesPerGroupSkip - 1 ? 1 : params.slidesPerGroup; if (timeDiff > params.longSwipesMs) { // Long touches if (!params.longSwipes) { swiper.slideTo(swiper.activeIndex); return; } if (swiper.swipeDirection === 'next') { if (ratio >= params.longSwipesRatio) swiper.slideTo(params.rewind && swiper.isEnd ? rewindFirstIndex : stopIndex + increment);else swiper.slideTo(stopIndex); } if (swiper.swipeDirection === 'prev') { if (ratio > 1 - params.longSwipesRatio) { swiper.slideTo(stopIndex + increment); } else if (rewindLastIndex !== null && ratio < 0 && Math.abs(ratio) > params.longSwipesRatio) { swiper.slideTo(rewindLastIndex); } else { swiper.slideTo(stopIndex); } } } else { // Short swipes if (!params.shortSwipes) { swiper.slideTo(swiper.activeIndex); return; } const isNavButtonTarget = swiper.navigation && (e.target === swiper.navigation.nextEl || e.target === swiper.navigation.prevEl); if (!isNavButtonTarget) { if (swiper.swipeDirection === 'next') { swiper.slideTo(rewindFirstIndex !== null ? rewindFirstIndex : stopIndex + increment); } if (swiper.swipeDirection === 'prev') { swiper.slideTo(rewindLastIndex !== null ? rewindLastIndex : stopIndex); } } else if (e.target === swiper.navigation.nextEl) { swiper.slideTo(stopIndex + increment); } else { swiper.slideTo(stopIndex); } } } function onResize() { const swiper = this; const { params, el } = swiper; if (el && el.offsetWidth === 0) return; // Breakpoints if (params.breakpoints) { swiper.setBreakpoint(); } // Save locks const { allowSlideNext, allowSlidePrev, snapGrid } = swiper; const isVirtual = swiper.virtual && swiper.params.virtual.enabled; // Disable locks on resize swiper.allowSlideNext = true; swiper.allowSlidePrev = true; swiper.updateSize(); swiper.updateSlides(); swiper.updateSlidesClasses(); const isVirtualLoop = isVirtual && params.loop; if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) && swiper.isEnd && !swiper.isBeginning && !swiper.params.centeredSlides && !isVirtualLoop) { swiper.slideTo(swiper.slides.length - 1, 0, false, true); } else { if (swiper.params.loop && !isVirtual) { swiper.slideToLoop(swiper.realIndex, 0, false, true); } else { swiper.slideTo(swiper.activeIndex, 0, false, true); } } if (swiper.autoplay && swiper.autoplay.running && swiper.autoplay.paused) { clearTimeout(swiper.autoplay.resizeTimeout); swiper.autoplay.resizeTimeout = setTimeout(() => { if (swiper.autoplay && swiper.autoplay.running && swiper.autoplay.paused) { swiper.autoplay.resume(); } }, 500); } // Return locks after resize swiper.allowSlidePrev = allowSlidePrev; swiper.allowSlideNext = allowSlideNext; if (swiper.params.watchOverflow && snapGrid !== swiper.snapGrid) { swiper.checkOverflow(); } } function onClick(e) { const swiper = this; if (!swiper.enabled) return; if (!swiper.allowClick) { if (swiper.params.preventClicks) e.preventDefault(); if (swiper.params.preventClicksPropagation && swiper.animating) { e.stopPropagation(); e.stopImmediatePropagation(); } } } function onScroll() { const swiper = this; const { wrapperEl, rtlTranslate, enabled } = swiper; if (!enabled) return; swiper.previousTranslate = swiper.translate; if (swiper.isHorizontal()) { swiper.translate = -wrapperEl.scrollLeft; } else { swiper.translate = -wrapperEl.scrollTop; } // eslint-disable-next-line if (swiper.translate === 0) swiper.translate = 0; swiper.updateActiveIndex(); swiper.updateSlidesClasses(); let newProgress; const translatesDiff = swiper.maxTranslate() - swiper.minTranslate(); if (translatesDiff === 0) { newProgress = 0; } else { newProgress = (swiper.translate - swiper.minTranslate()) / translatesDiff; } if (newProgress !== swiper.progress) { swiper.updateProgress(rtlTranslate ? -swiper.translate : swiper.translate); } swiper.emit('setTranslate', swiper.translate, false); } function onLoad(e) { const swiper = this; processLazyPreloader(swiper, e.target); if (swiper.params.cssMode || swiper.params.slidesPerView !== 'auto' && !swiper.params.autoHeight) { return; } swiper.update(); } function onDocumentTouchStart() { const swiper = this; if (swiper.documentTouchHandlerProceeded) return; swiper.documentTouchHandlerProceeded = true; if (swiper.params.touchReleaseOnEdges) { swiper.el.style.touchAction = 'auto'; } } const events = (swiper, method) => { const document = ssr_window_esm_getDocument(); const { params, el, wrapperEl, device } = swiper; const capture = !!params.nested; const domMethod = method === 'on' ? 'addEventListener' : 'removeEventListener'; const swiperMethod = method; if (!el || typeof el === 'string') return; // Touch Events document[domMethod]('touchstart', swiper.onDocumentTouchStart, { passive: false, capture }); el[domMethod]('touchstart', swiper.onTouchStart, { passive: false }); el[domMethod]('pointerdown', swiper.onTouchStart, { passive: false }); document[domMethod]('touchmove', swiper.onTouchMove, { passive: false, capture }); document[domMethod]('pointermove', swiper.onTouchMove, { passive: false, capture }); document[domMethod]('touchend', swiper.onTouchEnd, { passive: true }); document[domMethod]('pointerup', swiper.onTouchEnd, { passive: true }); document[domMethod]('pointercancel', swiper.onTouchEnd, { passive: true }); document[domMethod]('touchcancel', swiper.onTouchEnd, { passive: true }); document[domMethod]('pointerout', swiper.onTouchEnd, { passive: true }); document[domMethod]('pointerleave', swiper.onTouchEnd, { passive: true }); document[domMethod]('contextmenu', swiper.onTouchEnd, { passive: true }); // Prevent Links Clicks if (params.preventClicks || params.preventClicksPropagation) { el[domMethod]('click', swiper.onClick, true); } if (params.cssMode) { wrapperEl[domMethod]('scroll', swiper.onScroll); } // Resize handler if (params.updateOnWindowResize) { swiper[swiperMethod](device.ios || device.android ? 'resize orientationchange observerUpdate' : 'resize observerUpdate', onResize, true); } else { swiper[swiperMethod]('observerUpdate', onResize, true); } // Images loader el[domMethod]('load', swiper.onLoad, { capture: true }); }; function attachEvents() { const swiper = this; const { params } = swiper; swiper.onTouchStart = onTouchStart.bind(swiper); swiper.onTouchMove = onTouchMove.bind(swiper); swiper.onTouchEnd = onTouchEnd.bind(swiper); swiper.onDocumentTouchStart = onDocumentTouchStart.bind(swiper); if (params.cssMode) { swiper.onScroll = onScroll.bind(swiper); } swiper.onClick = onClick.bind(swiper); swiper.onLoad = onLoad.bind(swiper); events(swiper, 'on'); } function detachEvents() { const swiper = this; events(swiper, 'off'); } var events$1 = { attachEvents, detachEvents }; const isGridEnabled = (swiper, params) => { return swiper.grid && params.grid && params.grid.rows > 1; }; function setBreakpoint() { const swiper = this; const { realIndex, initialized, params, el } = swiper; const breakpoints = params.breakpoints; if (!breakpoints || breakpoints && Object.keys(breakpoints).length === 0) return; const document = ssr_window_esm_getDocument(); // Get breakpoint for window/container width and update parameters const breakpointsBase = params.breakpointsBase === 'window' || !params.breakpointsBase ? params.breakpointsBase : 'container'; const breakpointContainer = ['window', 'container'].includes(params.breakpointsBase) || !params.breakpointsBase ? swiper.el : document.querySelector(params.breakpointsBase); const breakpoint = swiper.getBreakpoint(breakpoints, breakpointsBase, breakpointContainer); if (!breakpoint || swiper.currentBreakpoint === breakpoint) return; const breakpointOnlyParams = breakpoint in breakpoints ? breakpoints[breakpoint] : undefined; const breakpointParams = breakpointOnlyParams || swiper.originalParams; const wasMultiRow = isGridEnabled(swiper, params); const isMultiRow = isGridEnabled(swiper, breakpointParams); const wasGrabCursor = swiper.params.grabCursor; const isGrabCursor = breakpointParams.grabCursor; const wasEnabled = params.enabled; if (wasMultiRow && !isMultiRow) { el.classList.remove(`${params.containerModifierClass}grid`, `${params.containerModifierClass}grid-column`); swiper.emitContainerClasses(); } else if (!wasMultiRow && isMultiRow) { el.classList.add(`${params.containerModifierClass}grid`); if (breakpointParams.grid.fill && breakpointParams.grid.fill === 'column' || !breakpointParams.grid.fill && params.grid.fill === 'column') { el.classList.add(`${params.containerModifierClass}grid-column`); } swiper.emitContainerClasses(); } if (wasGrabCursor && !isGrabCursor) { swiper.unsetGrabCursor(); } else if (!wasGrabCursor && isGrabCursor) { swiper.setGrabCursor(); } // Toggle navigation, pagination, scrollbar ['navigation', 'pagination', 'scrollbar'].forEach(prop => { if (typeof breakpointParams[prop] === 'undefined') return; const wasModuleEnabled = params[prop] && params[prop].enabled; const isModuleEnabled = breakpointParams[prop] && breakpointParams[prop].enabled; if (wasModuleEnabled && !isModuleEnabled) { swiper[prop].disable(); } if (!wasModuleEnabled && isModuleEnabled) { swiper[prop].enable(); } }); const directionChanged = breakpointParams.direction && breakpointParams.direction !== params.direction; const needsReLoop = params.loop && (breakpointParams.slidesPerView !== params.slidesPerView || directionChanged); const wasLoop = params.loop; if (directionChanged && initialized) { swiper.changeDirection(); } utils_extend(swiper.params, breakpointParams); const isEnabled = swiper.params.enabled; const hasLoop = swiper.params.loop; Object.assign(swiper, { allowTouchMove: swiper.params.allowTouchMove, allowSlideNext: swiper.params.allowSlideNext, allowSlidePrev: swiper.params.allowSlidePrev }); if (wasEnabled && !isEnabled) { swiper.disable(); } else if (!wasEnabled && isEnabled) { swiper.enable(); } swiper.currentBreakpoint = breakpoint; swiper.emit('_beforeBreakpoint', breakpointParams); if (initialized) { if (needsReLoop) { swiper.loopDestroy(); swiper.loopCreate(realIndex); swiper.updateSlides(); } else if (!wasLoop && hasLoop) { swiper.loopCreate(realIndex); swiper.updateSlides(); } else if (wasLoop && !hasLoop) { swiper.loopDestroy(); } } swiper.emit('breakpoint', breakpointParams); } function getBreakpoint(breakpoints, base = 'window', containerEl) { if (!breakpoints || base === 'container' && !containerEl) return undefined; let breakpoint = false; const window = ssr_window_esm_getWindow(); const currentHeight = base === 'window' ? window.innerHeight : containerEl.clientHeight; const points = Object.keys(breakpoints).map(point => { if (typeof point === 'string' && point.indexOf('@') === 0) { const minRatio = parseFloat(point.substr(1)); const value = currentHeight * minRatio; return { value, point }; } return { value: point, point }; }); points.sort((a, b) => parseInt(a.value, 10) - parseInt(b.value, 10)); for (let i = 0; i < points.length; i += 1) { const { point, value } = points[i]; if (base === 'window') { if (window.matchMedia(`(min-width: ${value}px)`).matches) { breakpoint = point; } } else if (value <= containerEl.clientWidth) { breakpoint = point; } } return breakpoint || 'max'; } var breakpoints = { setBreakpoint, getBreakpoint }; function prepareClasses(entries, prefix) { const resultClasses = []; entries.forEach(item => { if (typeof item === 'object') { Object.keys(item).forEach(classNames => { if (item[classNames]) { resultClasses.push(prefix + classNames); } }); } else if (typeof item === 'string') { resultClasses.push(prefix + item); } }); return resultClasses; } function addClasses() { const swiper = this; const { classNames, params, rtl, el, device } = swiper; // prettier-ignore const suffixes = prepareClasses(['initialized', params.direction, { 'free-mode': swiper.params.freeMode && params.freeMode.enabled }, { 'autoheight': params.autoHeight }, { 'rtl': rtl }, { 'grid': params.grid && params.grid.rows > 1 }, { 'grid-column': params.grid && params.grid.rows > 1 && params.grid.fill === 'column' }, { 'android': device.android }, { 'ios': device.ios }, { 'css-mode': params.cssMode }, { 'centered': params.cssMode && params.centeredSlides }, { 'watch-progress': params.watchSlidesProgress }], params.containerModifierClass); classNames.push(...suffixes); el.classList.add(...classNames); swiper.emitContainerClasses(); } function removeClasses() { const swiper = this; const { el, classNames } = swiper; if (!el || typeof el === 'string') return; el.classList.remove(...classNames); swiper.emitContainerClasses(); } var classes = { addClasses, removeClasses }; function checkOverflow() { const swiper = this; const { isLocked: wasLocked, params } = swiper; const { slidesOffsetBefore } = params; if (slidesOffsetBefore) { const lastSlideIndex = swiper.slides.length - 1; const lastSlideRightEdge = swiper.slidesGrid[lastSlideIndex] + swiper.slidesSizesGrid[lastSlideIndex] + slidesOffsetBefore * 2; swiper.isLocked = swiper.size > lastSlideRightEdge; } else { swiper.isLocked = swiper.snapGrid.length === 1; } if (params.allowSlideNext === true) { swiper.allowSlideNext = !swiper.isLocked; } if (params.allowSlidePrev === true) { swiper.allowSlidePrev = !swiper.isLocked; } if (wasLocked && wasLocked !== swiper.isLocked) { swiper.isEnd = false; } if (wasLocked !== swiper.isLocked) { swiper.emit(swiper.isLocked ? 'lock' : 'unlock'); } } var checkOverflow$1 = { checkOverflow }; var defaults = { init: true, direction: 'horizontal', oneWayMovement: false, swiperElementNodeName: 'SWIPER-CONTAINER', touchEventsTarget: 'wrapper', initialSlide: 0, speed: 300, cssMode: false, updateOnWindowResize: true, resizeObserver: true, nested: false, createElements: false, eventsPrefix: 'swiper', enabled: true, focusableElements: 'input, select, option, textarea, button, video, label', // Overrides width: null, height: null, // preventInteractionOnTransition: false, // ssr userAgent: null, url: null, // To support iOS's swipe-to-go-back gesture (when being used in-app). edgeSwipeDetection: false, edgeSwipeThreshold: 20, // Autoheight autoHeight: false, // Set wrapper width setWrapperSize: false, // Virtual Translate virtualTranslate: false, // Effects effect: 'slide', // 'slide' or 'fade' or 'cube' or 'coverflow' or 'flip' // Breakpoints breakpoints: undefined, breakpointsBase: 'window', // Slides grid spaceBetween: 0, slidesPerView: 1, slidesPerGroup: 1, slidesPerGroupSkip: 0, slidesPerGroupAuto: false, centeredSlides: false, centeredSlidesBounds: false, slidesOffsetBefore: 0, // in px slidesOffsetAfter: 0, // in px normalizeSlideIndex: true, centerInsufficientSlides: false, snapToSlideEdge: false, // Disable swiper and hide navigation when container not overflow watchOverflow: true, // Round length roundLengths: false, // Touches touchRatio: 1, touchAngle: 45, simulateTouch: true, shortSwipes: true, longSwipes: true, longSwipesRatio: 0.5, longSwipesMs: 300, followFinger: true, allowTouchMove: true, threshold: 5, touchMoveStopPropagation: false, touchStartPreventDefault: true, touchStartForcePreventDefault: false, touchReleaseOnEdges: false, // Unique Navigation Elements uniqueNavElements: true, // Resistance resistance: true, resistanceRatio: 0.85, // Progress watchSlidesProgress: false, // Cursor grabCursor: false, // Clicks preventClicks: true, preventClicksPropagation: true, slideToClickedSlide: false, // loop loop: false, loopAddBlankSlides: true, loopAdditionalSlides: 0, loopPreventsSliding: true, // rewind rewind: false, // Swiping/no swiping allowSlidePrev: true, allowSlideNext: true, swipeHandler: null, // '.swipe-handler', noSwiping: true, noSwipingClass: 'swiper-no-swiping', noSwipingSelector: null, // Passive Listeners passiveListeners: true, maxBackfaceHiddenSlides: 10, // NS containerModifierClass: 'swiper-', // NEW slideClass: 'swiper-slide', slideBlankClass: 'swiper-slide-blank', slideActiveClass: 'swiper-slide-active', slideVisibleClass: 'swiper-slide-visible', slideFullyVisibleClass: 'swiper-slide-fully-visible', slideNextClass: 'swiper-slide-next', slidePrevClass: 'swiper-slide-prev', wrapperClass: 'swiper-wrapper', lazyPreloaderClass: 'swiper-lazy-preloader', lazyPreloadPrevNext: 0, // Callbacks runCallbacksOnInit: true, // Internals _emitClasses: false }; function moduleExtendParams(params, allModulesParams) { return function extendParams(obj = {}) { const moduleParamName = Object.keys(obj)[0]; const moduleParams = obj[moduleParamName]; if (typeof moduleParams !== 'object' || moduleParams === null) { utils_extend(allModulesParams, obj); return; } if (params[moduleParamName] === true) { params[moduleParamName] = { enabled: true }; } if (moduleParamName === 'navigation' && params[moduleParamName] && params[moduleParamName].enabled && !params[moduleParamName].prevEl && !params[moduleParamName].nextEl) { params[moduleParamName].auto = true; } if (['pagination', 'scrollbar'].indexOf(moduleParamName) >= 0 && params[moduleParamName] && params[moduleParamName].enabled && !params[moduleParamName].el) { params[moduleParamName].auto = true; } if (!(moduleParamName in params && 'enabled' in moduleParams)) { utils_extend(allModulesParams, obj); return; } if (typeof params[moduleParamName] === 'object' && !('enabled' in params[moduleParamName])) { params[moduleParamName].enabled = true; } if (!params[moduleParamName]) params[moduleParamName] = { enabled: false }; utils_extend(allModulesParams, obj); }; } /* eslint no-param-reassign: "off" */ const prototypes = { eventsEmitter, update, translate, transition, slide, loop, grabCursor, events: events$1, breakpoints, checkOverflow: checkOverflow$1, classes }; const extendedDefaults = {}; class Swiper { constructor(...args) { let el; let params; if (args.length === 1 && args[0].constructor && Object.prototype.toString.call(args[0]).slice(8, -1) === 'Object') { params = args[0]; } else { [el, params] = args; } if (!params) params = {}; params = utils_extend({}, params); if (el && !params.el) params.el = el; const document = ssr_window_esm_getDocument(); if (params.el && typeof params.el === 'string' && document.querySelectorAll(params.el).length > 1) { const swipers = []; document.querySelectorAll(params.el).forEach(containerEl => { const newParams = utils_extend({}, params, { el: containerEl }); swipers.push(new Swiper(newParams)); }); // eslint-disable-next-line no-constructor-return return swipers; } // Swiper Instance const swiper = this; swiper.__swiper__ = true; swiper.support = getSupport(); swiper.device = getDevice({ userAgent: params.userAgent }); swiper.browser = getBrowser(); swiper.eventsListeners = {}; swiper.eventsAnyListeners = []; swiper.modules = [...swiper.__modules__]; if (params.modules && Array.isArray(params.modules)) { params.modules.forEach(mod => { if (typeof mod === 'function' && swiper.modules.indexOf(mod) < 0) { swiper.modules.push(mod); } }); } const allModulesParams = {}; swiper.modules.forEach(mod => { mod({ params, swiper, extendParams: moduleExtendParams(params, allModulesParams), on: swiper.on.bind(swiper), once: swiper.once.bind(swiper), off: swiper.off.bind(swiper), emit: swiper.emit.bind(swiper) }); }); // Extend defaults with modules params const swiperParams = utils_extend({}, defaults, allModulesParams); // Extend defaults with passed params swiper.params = utils_extend({}, swiperParams, extendedDefaults, params); swiper.originalParams = utils_extend({}, swiper.params); swiper.passedParams = utils_extend({}, params); // add event listeners if (swiper.params && swiper.params.on) { Object.keys(swiper.params.on).forEach(eventName => { swiper.on(eventName, swiper.params.on[eventName]); }); } if (swiper.params && swiper.params.onAny) { swiper.onAny(swiper.params.onAny); } // Extend Swiper Object.assign(swiper, { enabled: swiper.params.enabled, el, // Classes classNames: [], // Slides slides: [], slidesGrid: [], snapGrid: [], slidesSizesGrid: [], // isDirection isHorizontal() { return swiper.params.direction === 'horizontal'; }, isVertical() { return swiper.params.direction === 'vertical'; }, // Indexes activeIndex: 0, realIndex: 0, // isBeginning: true, isEnd: false, // Props translate: 0, previousTranslate: 0, progress: 0, velocity: 0, animating: false, cssOverflowAdjustment() { // Returns 0 unless `translate` is > 2**23 // Should be subtracted from css values to prevent overflow return Math.trunc(this.translate / 2 ** 23) * 2 ** 23; }, // Locks allowSlideNext: swiper.params.allowSlideNext, allowSlidePrev: swiper.params.allowSlidePrev, // Touch Events touchEventsData: { isTouched: undefined, isMoved: undefined, allowTouchCallbacks: undefined, touchStartTime: undefined, isScrolling: undefined, currentTranslate: undefined, startTranslate: undefined, allowThresholdMove: undefined, // Form elements to match focusableElements: swiper.params.focusableElements, // Last click time lastClickTime: 0, clickTimeout: undefined, // Velocities velocities: [], allowMomentumBounce: undefined, startMoving: undefined, pointerId: null, touchId: null }, // Clicks allowClick: true, // Touches allowTouchMove: swiper.params.allowTouchMove, touches: { startX: 0, startY: 0, currentX: 0, currentY: 0, diff: 0 }, // Images imagesToLoad: [], imagesLoaded: 0 }); swiper.emit('_swiper'); // Init if (swiper.params.init) { swiper.init(); } // Return app instance // eslint-disable-next-line no-constructor-return return swiper; } getDirectionLabel(property) { if (this.isHorizontal()) { return property; } // prettier-ignore return { 'width': 'height', 'margin-top': 'margin-left', 'margin-bottom ': 'margin-right', 'margin-left': 'margin-top', 'margin-right': 'margin-bottom', 'padding-left': 'padding-top', 'padding-right': 'padding-bottom', 'marginRight': 'marginBottom' }[property]; } getSlideIndex(slideEl) { const { slidesEl, params } = this; const slides = utils_elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`); const firstSlideIndex = utils_elementIndex(slides[0]); return utils_elementIndex(slideEl) - firstSlideIndex; } getSlideIndexByData(index) { return this.getSlideIndex(this.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === index)); } getSlideIndexWhenGrid(index) { if (this.grid && this.params.grid && this.params.grid.rows > 1) { if (this.params.grid.fill === 'column') { index = Math.floor(index / this.params.grid.rows); } else if (this.params.grid.fill === 'row') { index = index % Math.ceil(this.slides.length / this.params.grid.rows); } } return index; } recalcSlides() { const swiper = this; const { slidesEl, params } = swiper; swiper.slides = utils_elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`); } enable() { const swiper = this; if (swiper.enabled) return; swiper.enabled = true; if (swiper.params.grabCursor) { swiper.setGrabCursor(); } swiper.emit('enable'); } disable() { const swiper = this; if (!swiper.enabled) return; swiper.enabled = false; if (swiper.params.grabCursor) { swiper.unsetGrabCursor(); } swiper.emit('disable'); } setProgress(progress, speed) { const swiper = this; progress = Math.min(Math.max(progress, 0), 1); const min = swiper.minTranslate(); const max = swiper.maxTranslate(); const current = (max - min) * progress + min; swiper.translateTo(current, typeof speed === 'undefined' ? 0 : speed); swiper.updateActiveIndex(); swiper.updateSlidesClasses(); } emitContainerClasses() { const swiper = this; if (!swiper.params._emitClasses || !swiper.el) return; const cls = swiper.el.className.split(' ').filter(className => { return className.indexOf('swiper') === 0 || className.indexOf(swiper.params.containerModifierClass) === 0; }); swiper.emit('_containerClasses', cls.join(' ')); } getSlideClasses(slideEl) { const swiper = this; if (swiper.destroyed) return ''; return slideEl.className.split(' ').filter(className => { return className.indexOf('swiper-slide') === 0 || className.indexOf(swiper.params.slideClass) === 0; }).join(' '); } emitSlidesClasses() { const swiper = this; if (!swiper.params._emitClasses || !swiper.el) return; const updates = []; swiper.slides.forEach(slideEl => { const classNames = swiper.getSlideClasses(slideEl); updates.push({ slideEl, classNames }); swiper.emit('_slideClass', slideEl, classNames); }); swiper.emit('_slideClasses', updates); } slidesPerViewDynamic(view = 'current', exact = false) { const swiper = this; const { params, slides, slidesGrid, slidesSizesGrid, size: swiperSize, activeIndex } = swiper; let spv = 1; if (typeof params.slidesPerView === 'number') return params.slidesPerView; if (params.centeredSlides) { let slideSize = slides[activeIndex] ? Math.ceil(slides[activeIndex].swiperSlideSize) : 0; let breakLoop; for (let i = activeIndex + 1; i < slides.length; i += 1) { if (slides[i] && !breakLoop) { slideSize += Math.ceil(slides[i].swiperSlideSize); spv += 1; if (slideSize > swiperSize) breakLoop = true; } } for (let i = activeIndex - 1; i >= 0; i -= 1) { if (slides[i] && !breakLoop) { slideSize += slides[i].swiperSlideSize; spv += 1; if (slideSize > swiperSize) breakLoop = true; } } } else { // eslint-disable-next-line if (view === 'current') { for (let i = activeIndex + 1; i < slides.length; i += 1) { const slideInView = exact ? slidesGrid[i] + slidesSizesGrid[i] - slidesGrid[activeIndex] < swiperSize : slidesGrid[i] - slidesGrid[activeIndex] < swiperSize; if (slideInView) { spv += 1; } } } else { // previous for (let i = activeIndex - 1; i >= 0; i -= 1) { const slideInView = slidesGrid[activeIndex] - slidesGrid[i] < swiperSize; if (slideInView) { spv += 1; } } } } return spv; } update() { const swiper = this; if (!swiper || swiper.destroyed) return; const { snapGrid, params } = swiper; // Breakpoints if (params.breakpoints) { swiper.setBreakpoint(); } [...swiper.el.querySelectorAll('[loading="lazy"]')].forEach(imageEl => { if (imageEl.complete) { processLazyPreloader(swiper, imageEl); } }); swiper.updateSize(); swiper.updateSlides(); swiper.updateProgress(); swiper.updateSlidesClasses(); function setTranslate() { const translateValue = swiper.rtlTranslate ? swiper.translate * -1 : swiper.translate; const newTranslate = Math.min(Math.max(translateValue, swiper.maxTranslate()), swiper.minTranslate()); swiper.setTranslate(newTranslate); swiper.updateActiveIndex(); swiper.updateSlidesClasses(); } let translated; if (params.freeMode && params.freeMode.enabled && !params.cssMode) { setTranslate(); if (params.autoHeight) { swiper.updateAutoHeight(); } } else { if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) && swiper.isEnd && !params.centeredSlides) { const slides = swiper.virtual && params.virtual.enabled ? swiper.virtual.slides : swiper.slides; translated = swiper.slideTo(slides.length - 1, 0, false, true); } else { translated = swiper.slideTo(swiper.activeIndex, 0, false, true); } if (!translated) { setTranslate(); } } if (params.watchOverflow && snapGrid !== swiper.snapGrid) { swiper.checkOverflow(); } swiper.emit('update'); } changeDirection(newDirection, needUpdate = true) { const swiper = this; const currentDirection = swiper.params.direction; if (!newDirection) { // eslint-disable-next-line newDirection = currentDirection === 'horizontal' ? 'vertical' : 'horizontal'; } if (newDirection === currentDirection || newDirection !== 'horizontal' && newDirection !== 'vertical') { return swiper; } swiper.el.classList.remove(`${swiper.params.containerModifierClass}${currentDirection}`); swiper.el.classList.add(`${swiper.params.containerModifierClass}${newDirection}`); swiper.emitContainerClasses(); swiper.params.direction = newDirection; swiper.slides.forEach(slideEl => { if (newDirection === 'vertical') { slideEl.style.width = ''; } else { slideEl.style.height = ''; } }); swiper.emit('changeDirection'); if (needUpdate) swiper.update(); return swiper; } changeLanguageDirection(direction) { const swiper = this; if (swiper.rtl && direction === 'rtl' || !swiper.rtl && direction === 'ltr') return; swiper.rtl = direction === 'rtl'; swiper.rtlTranslate = swiper.params.direction === 'horizontal' && swiper.rtl; if (swiper.rtl) { swiper.el.classList.add(`${swiper.params.containerModifierClass}rtl`); swiper.el.dir = 'rtl'; } else { swiper.el.classList.remove(`${swiper.params.containerModifierClass}rtl`); swiper.el.dir = 'ltr'; } swiper.update(); } mount(element) { const swiper = this; if (swiper.mounted) return true; // Find el let el = element || swiper.params.el; if (typeof el === 'string') { el = document.querySelector(el); } if (!el) { return false; } el.swiper = swiper; if (el.parentNode && el.parentNode.host && el.parentNode.host.nodeName === swiper.params.swiperElementNodeName.toUpperCase()) { swiper.isElement = true; } const getWrapperSelector = () => { return `.${(swiper.params.wrapperClass || '').trim().split(' ').join('.')}`; }; const getWrapper = () => { if (el && el.shadowRoot && el.shadowRoot.querySelector) { const res = el.shadowRoot.querySelector(getWrapperSelector()); // Children needs to return slot items return res; } return utils_elementChildren(el, getWrapperSelector())[0]; }; // Find Wrapper let wrapperEl = getWrapper(); if (!wrapperEl && swiper.params.createElements) { wrapperEl = utils_createElement('div', swiper.params.wrapperClass); el.append(wrapperEl); utils_elementChildren(el, `.${swiper.params.slideClass}`).forEach(slideEl => { wrapperEl.append(slideEl); }); } Object.assign(swiper, { el, wrapperEl, slidesEl: swiper.isElement && !el.parentNode.host.slideSlots ? el.parentNode.host : wrapperEl, hostEl: swiper.isElement ? el.parentNode.host : el, mounted: true, // RTL rtl: el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl', rtlTranslate: swiper.params.direction === 'horizontal' && (el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl'), wrongRTL: elementStyle(wrapperEl, 'display') === '-webkit-box' }); return true; } init(el) { const swiper = this; if (swiper.initialized) return swiper; const mounted = swiper.mount(el); if (mounted === false) return swiper; swiper.emit('beforeInit'); // Set breakpoint if (swiper.params.breakpoints) { swiper.setBreakpoint(); } // Add Classes swiper.addClasses(); // Update size swiper.updateSize(); // Update slides swiper.updateSlides(); if (swiper.params.watchOverflow) { swiper.checkOverflow(); } // Set Grab Cursor if (swiper.params.grabCursor && swiper.enabled) { swiper.setGrabCursor(); } // Slide To Initial Slide if (swiper.params.loop && swiper.virtual && swiper.params.virtual.enabled) { swiper.slideTo(swiper.params.initialSlide + swiper.virtual.slidesBefore, 0, swiper.params.runCallbacksOnInit, false, true); } else { swiper.slideTo(swiper.params.initialSlide, 0, swiper.params.runCallbacksOnInit, false, true); } // Create loop if (swiper.params.loop) { swiper.loopCreate(undefined, true); } // Attach events swiper.attachEvents(); const lazyElements = [...swiper.el.querySelectorAll('[loading="lazy"]')]; if (swiper.isElement) { lazyElements.push(...swiper.hostEl.querySelectorAll('[loading="lazy"]')); } lazyElements.forEach(imageEl => { if (imageEl.complete) { processLazyPreloader(swiper, imageEl); } else { imageEl.addEventListener('load', e => { processLazyPreloader(swiper, e.target); }); } }); preload(swiper); // Init Flag swiper.initialized = true; preload(swiper); // Emit swiper.emit('init'); swiper.emit('afterInit'); return swiper; } destroy(deleteInstance = true, cleanStyles = true) { const swiper = this; const { params, el, wrapperEl, slides } = swiper; if (typeof swiper.params === 'undefined' || swiper.destroyed) { return null; } swiper.emit('beforeDestroy'); // Init Flag swiper.initialized = false; // Detach events swiper.detachEvents(); // Destroy loop if (params.loop) { swiper.loopDestroy(); } // Cleanup styles if (cleanStyles) { swiper.removeClasses(); if (el && typeof el !== 'string') { el.removeAttribute('style'); } if (wrapperEl) { wrapperEl.removeAttribute('style'); } if (slides && slides.length) { slides.forEach(slideEl => { slideEl.classList.remove(params.slideVisibleClass, params.slideFullyVisibleClass, params.slideActiveClass, params.slideNextClass, params.slidePrevClass); slideEl.removeAttribute('style'); slideEl.removeAttribute('data-swiper-slide-index'); }); } } swiper.emit('destroy'); // Detach emitter events Object.keys(swiper.eventsListeners).forEach(eventName => { swiper.off(eventName); }); if (deleteInstance !== false) { if (swiper.el && typeof swiper.el !== 'string') { swiper.el.swiper = null; } deleteProps(swiper); } swiper.destroyed = true; return null; } static extendDefaults(newDefaults) { utils_extend(extendedDefaults, newDefaults); } static get extendedDefaults() { return extendedDefaults; } static get defaults() { return defaults; } static installModule(mod) { if (!Swiper.prototype.__modules__) Swiper.prototype.__modules__ = []; const modules = Swiper.prototype.__modules__; if (typeof mod === 'function' && modules.indexOf(mod) < 0) { modules.push(mod); } } static use(module) { if (Array.isArray(module)) { module.forEach(m => Swiper.installModule(m)); return Swiper; } Swiper.installModule(module); return Swiper; } } Object.keys(prototypes).forEach(prototypeGroup => { Object.keys(prototypes[prototypeGroup]).forEach(protoMethod => { Swiper.prototype[protoMethod] = prototypes[prototypeGroup][protoMethod]; }); }); Swiper.use([Resize, Observer]); ;// CONCATENATED MODULE: ./node_modules/swiper/swiper.mjs /** * Swiper 12.1.2 * Most modern mobile touch slider and framework with hardware accelerated transitions * https://swiperjs.com * * Copyright 2014-2026 Vladimir Kharlampidi * * Released under the MIT License * * Released on: February 18, 2026 */ ;// CONCATENATED MODULE: ./node_modules/swiper/modules/virtual.mjs function Virtual({ swiper, extendParams, on, emit }) { extendParams({ virtual: { enabled: false, slides: [], cache: true, slidesPerViewAutoSlideSize: 320, renderSlide: null, renderExternal: null, renderExternalUpdate: true, addSlidesBefore: 0, addSlidesAfter: 0 } }); let cssModeTimeout; const document = getDocument(); swiper.virtual = { cache: {}, from: undefined, to: undefined, slides: [], offset: 0, slidesGrid: [] }; const tempDOM = document.createElement('div'); function renderSlide(slide, index) { const params = swiper.params.virtual; if (params.cache && swiper.virtual.cache[index]) { return swiper.virtual.cache[index]; } // eslint-disable-next-line let slideEl; if (params.renderSlide) { slideEl = params.renderSlide.call(swiper, slide, index); if (typeof slideEl === 'string') { setInnerHTML(tempDOM, slideEl); slideEl = tempDOM.children[0]; } } else if (swiper.isElement) { slideEl = createElement('swiper-slide'); } else { slideEl = createElement('div', swiper.params.slideClass); } slideEl.setAttribute('data-swiper-slide-index', index); if (!params.renderSlide) { setInnerHTML(slideEl, slide); } if (params.cache) { swiper.virtual.cache[index] = slideEl; } return slideEl; } function update(force, beforeInit, forceActiveIndex) { const { slidesPerGroup, centeredSlides, slidesPerView, loop: isLoop, initialSlide } = swiper.params; if (beforeInit && !isLoop && initialSlide > 0) { return; } const { addSlidesBefore, addSlidesAfter, slidesPerViewAutoSlideSize } = swiper.params.virtual; const { from: previousFrom, to: previousTo, slides, slidesGrid: previousSlidesGrid, offset: previousOffset } = swiper.virtual; if (!swiper.params.cssMode) { swiper.updateActiveIndex(); } const activeIndex = typeof forceActiveIndex === 'undefined' ? swiper.activeIndex || 0 : forceActiveIndex; let offsetProp; if (swiper.rtlTranslate) offsetProp = 'right';else offsetProp = swiper.isHorizontal() ? 'left' : 'top'; let slidesPerViewNumeric; if (slidesPerView === 'auto') { if (slidesPerViewAutoSlideSize) { let swiperSize = swiper.size; if (!swiperSize) { swiperSize = swiper.isHorizontal() ? swiper.el.getBoundingClientRect().width : swiper.el.getBoundingClientRect().height; } slidesPerViewNumeric = Math.max(1, Math.ceil(swiperSize / slidesPerViewAutoSlideSize)); } else { slidesPerViewNumeric = 1; } } else { slidesPerViewNumeric = slidesPerView; } let slidesAfter; let slidesBefore; if (centeredSlides) { slidesAfter = Math.floor(slidesPerViewNumeric / 2) + slidesPerGroup + addSlidesAfter; slidesBefore = Math.floor(slidesPerViewNumeric / 2) + slidesPerGroup + addSlidesBefore; } else { slidesAfter = slidesPerViewNumeric + (slidesPerGroup - 1) + addSlidesAfter; slidesBefore = (isLoop ? slidesPerViewNumeric : slidesPerGroup) + addSlidesBefore; } let from = activeIndex - slidesBefore; let to = activeIndex + slidesAfter; if (!isLoop) { from = Math.max(from, 0); to = Math.min(to, slides.length - 1); } let offset = (swiper.slidesGrid[from] || 0) - (swiper.slidesGrid[0] || 0); if (isLoop && activeIndex >= slidesBefore) { from -= slidesBefore; if (!centeredSlides) offset += swiper.slidesGrid[0]; } else if (isLoop && activeIndex < slidesBefore) { from = -slidesBefore; if (centeredSlides) offset += swiper.slidesGrid[0]; } Object.assign(swiper.virtual, { from, to, offset, slidesGrid: swiper.slidesGrid, slidesBefore, slidesAfter }); function onRendered() { swiper.updateSlides(); swiper.updateProgress(); swiper.updateSlidesClasses(); emit('virtualUpdate'); } if (previousFrom === from && previousTo === to && !force) { if (swiper.slidesGrid !== previousSlidesGrid && offset !== previousOffset) { swiper.slides.forEach(slideEl => { slideEl.style[offsetProp] = `${offset - Math.abs(swiper.cssOverflowAdjustment())}px`; }); } swiper.updateProgress(); emit('virtualUpdate'); return; } if (swiper.params.virtual.renderExternal) { swiper.params.virtual.renderExternal.call(swiper, { offset, from, to, slides: function getSlides() { const slidesToRender = []; for (let i = from; i <= to; i += 1) { slidesToRender.push(slides[i]); } return slidesToRender; }() }); if (swiper.params.virtual.renderExternalUpdate) { onRendered(); } else { emit('virtualUpdate'); } return; } const prependIndexes = []; const appendIndexes = []; const getSlideIndex = index => { let slideIndex = index; if (index < 0) { slideIndex = slides.length + index; } else if (slideIndex >= slides.length) { // eslint-disable-next-line slideIndex = slideIndex - slides.length; } return slideIndex; }; if (force) { swiper.slides.filter(el => el.matches(`.${swiper.params.slideClass}, swiper-slide`)).forEach(slideEl => { slideEl.remove(); }); } else { for (let i = previousFrom; i <= previousTo; i += 1) { if (i < from || i > to) { const slideIndex = getSlideIndex(i); swiper.slides.filter(el => el.matches(`.${swiper.params.slideClass}[data-swiper-slide-index="${slideIndex}"], swiper-slide[data-swiper-slide-index="${slideIndex}"]`)).forEach(slideEl => { slideEl.remove(); }); } } } const loopFrom = isLoop ? -slides.length : 0; const loopTo = isLoop ? slides.length * 2 : slides.length; for (let i = loopFrom; i < loopTo; i += 1) { if (i >= from && i <= to) { const slideIndex = getSlideIndex(i); if (typeof previousTo === 'undefined' || force) { appendIndexes.push(slideIndex); } else { if (i > previousTo) appendIndexes.push(slideIndex); if (i < previousFrom) prependIndexes.push(slideIndex); } } } appendIndexes.forEach(index => { swiper.slidesEl.append(renderSlide(slides[index], index)); }); if (isLoop) { for (let i = prependIndexes.length - 1; i >= 0; i -= 1) { const index = prependIndexes[i]; swiper.slidesEl.prepend(renderSlide(slides[index], index)); } } else { prependIndexes.sort((a, b) => b - a); prependIndexes.forEach(index => { swiper.slidesEl.prepend(renderSlide(slides[index], index)); }); } elementChildren(swiper.slidesEl, '.swiper-slide, swiper-slide').forEach(slideEl => { slideEl.style[offsetProp] = `${offset - Math.abs(swiper.cssOverflowAdjustment())}px`; }); onRendered(); } function appendSlide(slides) { if (typeof slides === 'object' && 'length' in slides) { for (let i = 0; i < slides.length; i += 1) { if (slides[i]) swiper.virtual.slides.push(slides[i]); } } else { swiper.virtual.slides.push(slides); } update(true); } function prependSlide(slides) { const activeIndex = swiper.activeIndex; let newActiveIndex = activeIndex + 1; let numberOfNewSlides = 1; if (Array.isArray(slides)) { for (let i = 0; i < slides.length; i += 1) { if (slides[i]) swiper.virtual.slides.unshift(slides[i]); } newActiveIndex = activeIndex + slides.length; numberOfNewSlides = slides.length; } else { swiper.virtual.slides.unshift(slides); } if (swiper.params.virtual.cache) { const cache = swiper.virtual.cache; const newCache = {}; Object.keys(cache).forEach(cachedIndex => { const cachedEl = cache[cachedIndex]; const cachedElIndex = cachedEl.getAttribute('data-swiper-slide-index'); if (cachedElIndex) { cachedEl.setAttribute('data-swiper-slide-index', parseInt(cachedElIndex, 10) + numberOfNewSlides); } newCache[parseInt(cachedIndex, 10) + numberOfNewSlides] = cachedEl; }); swiper.virtual.cache = newCache; } update(true); swiper.slideTo(newActiveIndex, 0); } function removeSlide(slidesIndexes) { if (typeof slidesIndexes === 'undefined' || slidesIndexes === null) return; let activeIndex = swiper.activeIndex; if (Array.isArray(slidesIndexes)) { for (let i = slidesIndexes.length - 1; i >= 0; i -= 1) { if (swiper.params.virtual.cache) { delete swiper.virtual.cache[slidesIndexes[i]]; // shift cache indexes Object.keys(swiper.virtual.cache).forEach(key => { if (key > slidesIndexes) { swiper.virtual.cache[key - 1] = swiper.virtual.cache[key]; swiper.virtual.cache[key - 1].setAttribute('data-swiper-slide-index', key - 1); delete swiper.virtual.cache[key]; } }); } swiper.virtual.slides.splice(slidesIndexes[i], 1); if (slidesIndexes[i] < activeIndex) activeIndex -= 1; activeIndex = Math.max(activeIndex, 0); } } else { if (swiper.params.virtual.cache) { delete swiper.virtual.cache[slidesIndexes]; // shift cache indexes Object.keys(swiper.virtual.cache).forEach(key => { if (key > slidesIndexes) { swiper.virtual.cache[key - 1] = swiper.virtual.cache[key]; swiper.virtual.cache[key - 1].setAttribute('data-swiper-slide-index', key - 1); delete swiper.virtual.cache[key]; } }); } swiper.virtual.slides.splice(slidesIndexes, 1); if (slidesIndexes < activeIndex) activeIndex -= 1; activeIndex = Math.max(activeIndex, 0); } update(true); swiper.slideTo(activeIndex, 0); } function removeAllSlides() { swiper.virtual.slides = []; if (swiper.params.virtual.cache) { swiper.virtual.cache = {}; } update(true); swiper.slideTo(0, 0); } on('beforeInit', () => { if (!swiper.params.virtual.enabled) return; let domSlidesAssigned; if (typeof swiper.passedParams.virtual.slides === 'undefined') { const slides = [...swiper.slidesEl.children].filter(el => el.matches(`.${swiper.params.slideClass}, swiper-slide`)); if (slides && slides.length) { swiper.virtual.slides = [...slides]; domSlidesAssigned = true; slides.forEach((slideEl, slideIndex) => { slideEl.setAttribute('data-swiper-slide-index', slideIndex); swiper.virtual.cache[slideIndex] = slideEl; slideEl.remove(); }); } } if (!domSlidesAssigned) { swiper.virtual.slides = swiper.params.virtual.slides; } swiper.classNames.push(`${swiper.params.containerModifierClass}virtual`); swiper.params.watchSlidesProgress = true; swiper.originalParams.watchSlidesProgress = true; update(false, true); }); on('setTranslate', () => { if (!swiper.params.virtual.enabled) return; if (swiper.params.cssMode && !swiper._immediateVirtual) { clearTimeout(cssModeTimeout); cssModeTimeout = setTimeout(() => { update(); }, 100); } else { update(); } }); on('init update resize', () => { if (!swiper.params.virtual.enabled) return; if (swiper.params.cssMode) { setCSSProperty(swiper.wrapperEl, '--swiper-virtual-size', `${swiper.virtualSize}px`); } }); Object.assign(swiper.virtual, { appendSlide, prependSlide, removeSlide, removeAllSlides, update }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/keyboard.mjs /* eslint-disable consistent-return */ function Keyboard({ swiper, extendParams, on, emit }) { const document = getDocument(); const window = getWindow(); swiper.keyboard = { enabled: false }; extendParams({ keyboard: { enabled: false, onlyInViewport: true, pageUpDown: true, speed: undefined } }); function handle(event) { if (!swiper.enabled) return; const { rtlTranslate: rtl } = swiper; let e = event; if (e.originalEvent) e = e.originalEvent; // jquery fix const kc = e.keyCode || e.charCode; const pageUpDown = swiper.params.keyboard.pageUpDown; const isPageUp = pageUpDown && kc === 33; const isPageDown = pageUpDown && kc === 34; const isArrowLeft = kc === 37; const isArrowRight = kc === 39; const isArrowUp = kc === 38; const isArrowDown = kc === 40; // Directions locks if (!swiper.allowSlideNext && (swiper.isHorizontal() && isArrowRight || swiper.isVertical() && isArrowDown || isPageDown)) { return false; } if (!swiper.allowSlidePrev && (swiper.isHorizontal() && isArrowLeft || swiper.isVertical() && isArrowUp || isPageUp)) { return false; } if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) { return undefined; } if (document.activeElement && (document.activeElement.isContentEditable || document.activeElement.nodeName && (document.activeElement.nodeName.toLowerCase() === 'input' || document.activeElement.nodeName.toLowerCase() === 'textarea'))) { return undefined; } if (swiper.params.keyboard.onlyInViewport && (isPageUp || isPageDown || isArrowLeft || isArrowRight || isArrowUp || isArrowDown)) { let inView = false; // Check that swiper should be inside of visible area of window if (elementParents(swiper.el, `.${swiper.params.slideClass}, swiper-slide`).length > 0 && elementParents(swiper.el, `.${swiper.params.slideActiveClass}`).length === 0) { return undefined; } const el = swiper.el; const swiperWidth = el.clientWidth; const swiperHeight = el.clientHeight; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const swiperOffset = elementOffset(el); if (rtl) swiperOffset.left -= el.scrollLeft; const swiperCoord = [[swiperOffset.left, swiperOffset.top], [swiperOffset.left + swiperWidth, swiperOffset.top], [swiperOffset.left, swiperOffset.top + swiperHeight], [swiperOffset.left + swiperWidth, swiperOffset.top + swiperHeight]]; for (let i = 0; i < swiperCoord.length; i += 1) { const point = swiperCoord[i]; if (point[0] >= 0 && point[0] <= windowWidth && point[1] >= 0 && point[1] <= windowHeight) { if (point[0] === 0 && point[1] === 0) continue; // eslint-disable-line inView = true; } } if (!inView) return undefined; } const speed = swiper.params.keyboard.speed; if (swiper.isHorizontal()) { if (isPageUp || isPageDown || isArrowLeft || isArrowRight) { if (e.preventDefault) e.preventDefault();else e.returnValue = false; } if ((isPageDown || isArrowRight) && !rtl || (isPageUp || isArrowLeft) && rtl) swiper.slideNext(speed); if ((isPageUp || isArrowLeft) && !rtl || (isPageDown || isArrowRight) && rtl) swiper.slidePrev(speed); } else { if (isPageUp || isPageDown || isArrowUp || isArrowDown) { if (e.preventDefault) e.preventDefault();else e.returnValue = false; } if (isPageDown || isArrowDown) swiper.slideNext(speed); if (isPageUp || isArrowUp) swiper.slidePrev(speed); } emit('keyPress', kc); return undefined; } function enable() { if (swiper.keyboard.enabled) return; document.addEventListener('keydown', handle); swiper.keyboard.enabled = true; } function disable() { if (!swiper.keyboard.enabled) return; document.removeEventListener('keydown', handle); swiper.keyboard.enabled = false; } on('init', () => { if (swiper.params.keyboard.enabled) { enable(); } }); on('destroy', () => { if (swiper.keyboard.enabled) { disable(); } }); Object.assign(swiper.keyboard, { enable, disable }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/mousewheel.mjs /* eslint-disable consistent-return */ function Mousewheel({ swiper, extendParams, on, emit }) { const window = getWindow(); extendParams({ mousewheel: { enabled: false, releaseOnEdges: false, invert: false, forceToAxis: false, sensitivity: 1, eventsTarget: 'container', thresholdDelta: null, thresholdTime: null, noMousewheelClass: 'swiper-no-mousewheel' } }); swiper.mousewheel = { enabled: false }; let timeout; let lastScrollTime = now(); let lastEventBeforeSnap; const recentWheelEvents = []; function normalize(e) { // Reasonable defaults const PIXEL_STEP = 10; const LINE_HEIGHT = 40; const PAGE_HEIGHT = 800; let sX = 0; let sY = 0; // spinX, spinY let pX = 0; let pY = 0; // pixelX, pixelY // Legacy if ('detail' in e) { sY = e.detail; } if ('wheelDelta' in e) { sY = -e.wheelDelta / 120; } if ('wheelDeltaY' in e) { sY = -e.wheelDeltaY / 120; } if ('wheelDeltaX' in e) { sX = -e.wheelDeltaX / 120; } // side scrolling on FF with DOMMouseScroll if ('axis' in e && e.axis === e.HORIZONTAL_AXIS) { sX = sY; sY = 0; } pX = sX * PIXEL_STEP; pY = sY * PIXEL_STEP; if ('deltaY' in e) { pY = e.deltaY; } if ('deltaX' in e) { pX = e.deltaX; } if (e.shiftKey && !pX) { // if user scrolls with shift he wants horizontal scroll pX = pY; pY = 0; } if ((pX || pY) && e.deltaMode) { if (e.deltaMode === 1) { // delta in LINE units pX *= LINE_HEIGHT; pY *= LINE_HEIGHT; } else { // delta in PAGE units pX *= PAGE_HEIGHT; pY *= PAGE_HEIGHT; } } // Fall-back if spin cannot be determined if (pX && !sX) { sX = pX < 1 ? -1 : 1; } if (pY && !sY) { sY = pY < 1 ? -1 : 1; } return { spinX: sX, spinY: sY, pixelX: pX, pixelY: pY }; } function handleMouseEnter() { if (!swiper.enabled) return; swiper.mouseEntered = true; } function handleMouseLeave() { if (!swiper.enabled) return; swiper.mouseEntered = false; } function animateSlider(newEvent) { if (swiper.params.mousewheel.thresholdDelta && newEvent.delta < swiper.params.mousewheel.thresholdDelta) { // Prevent if delta of wheel scroll delta is below configured threshold return false; } if (swiper.params.mousewheel.thresholdTime && now() - lastScrollTime < swiper.params.mousewheel.thresholdTime) { // Prevent if time between scrolls is below configured threshold return false; } // If the movement is NOT big enough and // if the last time the user scrolled was too close to the current one (avoid continuously triggering the slider): // Don't go any further (avoid insignificant scroll movement). if (newEvent.delta >= 6 && now() - lastScrollTime < 60) { // Return false as a default return true; } // If user is scrolling towards the end: // If the slider hasn't hit the latest slide or // if the slider is a loop and // if the slider isn't moving right now: // Go to next slide and // emit a scroll event. // Else (the user is scrolling towards the beginning) and // if the slider hasn't hit the first slide or // if the slider is a loop and // if the slider isn't moving right now: // Go to prev slide and // emit a scroll event. if (newEvent.direction < 0) { if ((!swiper.isEnd || swiper.params.loop) && !swiper.animating) { swiper.slideNext(); emit('scroll', newEvent.raw); } } else if ((!swiper.isBeginning || swiper.params.loop) && !swiper.animating) { swiper.slidePrev(); emit('scroll', newEvent.raw); } // If you got here is because an animation has been triggered so store the current time lastScrollTime = new window.Date().getTime(); // Return false as a default return false; } function releaseScroll(newEvent) { const params = swiper.params.mousewheel; if (newEvent.direction < 0) { if (swiper.isEnd && !swiper.params.loop && params.releaseOnEdges) { // Return true to animate scroll on edges return true; } } else if (swiper.isBeginning && !swiper.params.loop && params.releaseOnEdges) { // Return true to animate scroll on edges return true; } return false; } function handle(event) { let e = event; let disableParentSwiper = true; if (!swiper.enabled) return; // Ignore event if the target or its parents have the swiper-no-mousewheel class if (event.target.closest(`.${swiper.params.mousewheel.noMousewheelClass}`)) return; const params = swiper.params.mousewheel; if (swiper.params.cssMode) { e.preventDefault(); } let targetEl = swiper.el; if (swiper.params.mousewheel.eventsTarget !== 'container') { targetEl = document.querySelector(swiper.params.mousewheel.eventsTarget); } const targetElContainsTarget = targetEl && targetEl.contains(e.target); if (!swiper.mouseEntered && !targetElContainsTarget && !params.releaseOnEdges) return true; if (e.originalEvent) e = e.originalEvent; // jquery fix let delta = 0; const rtlFactor = swiper.rtlTranslate ? -1 : 1; const data = normalize(e); if (params.forceToAxis) { if (swiper.isHorizontal()) { if (Math.abs(data.pixelX) > Math.abs(data.pixelY)) delta = -data.pixelX * rtlFactor;else return true; } else if (Math.abs(data.pixelY) > Math.abs(data.pixelX)) delta = -data.pixelY;else return true; } else { delta = Math.abs(data.pixelX) > Math.abs(data.pixelY) ? -data.pixelX * rtlFactor : -data.pixelY; } if (delta === 0) return true; if (params.invert) delta = -delta; // Get the scroll positions let positions = swiper.getTranslate() + delta * params.sensitivity; if (positions >= swiper.minTranslate()) positions = swiper.minTranslate(); if (positions <= swiper.maxTranslate()) positions = swiper.maxTranslate(); // When loop is true: // the disableParentSwiper will be true. // When loop is false: // if the scroll positions is not on edge, // then the disableParentSwiper will be true. // if the scroll on edge positions, // then the disableParentSwiper will be false. disableParentSwiper = swiper.params.loop ? true : !(positions === swiper.minTranslate() || positions === swiper.maxTranslate()); if (disableParentSwiper && swiper.params.nested) e.stopPropagation(); if (!swiper.params.freeMode || !swiper.params.freeMode.enabled) { // Register the new event in a variable which stores the relevant data const newEvent = { time: now(), delta: Math.abs(delta), direction: Math.sign(delta), raw: event }; // Keep the most recent events if (recentWheelEvents.length >= 2) { recentWheelEvents.shift(); // only store the last N events } const prevEvent = recentWheelEvents.length ? recentWheelEvents[recentWheelEvents.length - 1] : undefined; recentWheelEvents.push(newEvent); // If there is at least one previous recorded event: // If direction has changed or // if the scroll is quicker than the previous one: // Animate the slider. // Else (this is the first time the wheel is moved): // Animate the slider. if (prevEvent) { if (newEvent.direction !== prevEvent.direction || newEvent.delta > prevEvent.delta || newEvent.time > prevEvent.time + 150) { animateSlider(newEvent); } } else { animateSlider(newEvent); } // If it's time to release the scroll: // Return now so you don't hit the preventDefault. if (releaseScroll(newEvent)) { return true; } } else { // Freemode or scrollContainer: // If we recently snapped after a momentum scroll, then ignore wheel events // to give time for the deceleration to finish. Stop ignoring after 500 msecs // or if it's a new scroll (larger delta or inverse sign as last event before // an end-of-momentum snap). const newEvent = { time: now(), delta: Math.abs(delta), direction: Math.sign(delta) }; const ignoreWheelEvents = lastEventBeforeSnap && newEvent.time < lastEventBeforeSnap.time + 500 && newEvent.delta <= lastEventBeforeSnap.delta && newEvent.direction === lastEventBeforeSnap.direction; if (!ignoreWheelEvents) { lastEventBeforeSnap = undefined; let position = swiper.getTranslate() + delta * params.sensitivity; const wasBeginning = swiper.isBeginning; const wasEnd = swiper.isEnd; if (position >= swiper.minTranslate()) position = swiper.minTranslate(); if (position <= swiper.maxTranslate()) position = swiper.maxTranslate(); swiper.setTransition(0); swiper.setTranslate(position); swiper.updateProgress(); swiper.updateActiveIndex(); swiper.updateSlidesClasses(); if (!wasBeginning && swiper.isBeginning || !wasEnd && swiper.isEnd) { swiper.updateSlidesClasses(); } if (swiper.params.loop) { swiper.loopFix({ direction: newEvent.direction < 0 ? 'next' : 'prev', byMousewheel: true }); } if (swiper.params.freeMode.sticky) { // When wheel scrolling starts with sticky (aka snap) enabled, then detect // the end of a momentum scroll by storing recent (N=15?) wheel events. // 1. do all N events have decreasing or same (absolute value) delta? // 2. did all N events arrive in the last M (M=500?) msecs? // 3. does the earliest event have an (absolute value) delta that's // at least P (P=1?) larger than the most recent event's delta? // 4. does the latest event have a delta that's smaller than Q (Q=6?) pixels? // If 1-4 are "yes" then we're near the end of a momentum scroll deceleration. // Snap immediately and ignore remaining wheel events in this scroll. // See comment above for "remaining wheel events in this scroll" determination. // If 1-4 aren't satisfied, then wait to snap until 500ms after the last event. clearTimeout(timeout); timeout = undefined; if (recentWheelEvents.length >= 15) { recentWheelEvents.shift(); // only store the last N events } const prevEvent = recentWheelEvents.length ? recentWheelEvents[recentWheelEvents.length - 1] : undefined; const firstEvent = recentWheelEvents[0]; recentWheelEvents.push(newEvent); if (prevEvent && (newEvent.delta > prevEvent.delta || newEvent.direction !== prevEvent.direction)) { // Increasing or reverse-sign delta means the user started scrolling again. Clear the wheel event log. recentWheelEvents.splice(0); } else if (recentWheelEvents.length >= 15 && newEvent.time - firstEvent.time < 500 && firstEvent.delta - newEvent.delta >= 1 && newEvent.delta <= 6) { // We're at the end of the deceleration of a momentum scroll, so there's no need // to wait for more events. Snap ASAP on the next tick. // Also, because there's some remaining momentum we'll bias the snap in the // direction of the ongoing scroll because it's better UX for the scroll to snap // in the same direction as the scroll instead of reversing to snap. Therefore, // if it's already scrolled more than 20% in the current direction, keep going. const snapToThreshold = delta > 0 ? 0.8 : 0.2; lastEventBeforeSnap = newEvent; recentWheelEvents.splice(0); timeout = nextTick(() => { if (swiper.destroyed || !swiper.params) return; swiper.slideToClosest(swiper.params.speed, true, undefined, snapToThreshold); }, 0); // no delay; move on next tick } if (!timeout) { // if we get here, then we haven't detected the end of a momentum scroll, so // we'll consider a scroll "complete" when there haven't been any wheel events // for 500ms. timeout = nextTick(() => { if (swiper.destroyed || !swiper.params) return; const snapToThreshold = 0.5; lastEventBeforeSnap = newEvent; recentWheelEvents.splice(0); swiper.slideToClosest(swiper.params.speed, true, undefined, snapToThreshold); }, 500); } } // Emit event if (!ignoreWheelEvents) emit('scroll', e); // Stop autoplay if (swiper.params.autoplay && swiper.params.autoplay.disableOnInteraction) swiper.autoplay.stop(); // Return page scroll on edge positions if (params.releaseOnEdges && (position === swiper.minTranslate() || position === swiper.maxTranslate())) { return true; } } } if (e.preventDefault) e.preventDefault();else e.returnValue = false; return false; } function events(method) { let targetEl = swiper.el; if (swiper.params.mousewheel.eventsTarget !== 'container') { targetEl = document.querySelector(swiper.params.mousewheel.eventsTarget); } targetEl[method]('mouseenter', handleMouseEnter); targetEl[method]('mouseleave', handleMouseLeave); targetEl[method]('wheel', handle); } function enable() { if (swiper.params.cssMode) { swiper.wrapperEl.removeEventListener('wheel', handle); return true; } if (swiper.mousewheel.enabled) return false; events('addEventListener'); swiper.mousewheel.enabled = true; return true; } function disable() { if (swiper.params.cssMode) { swiper.wrapperEl.addEventListener(event, handle); return true; } if (!swiper.mousewheel.enabled) return false; events('removeEventListener'); swiper.mousewheel.enabled = false; return true; } on('init', () => { if (!swiper.params.mousewheel.enabled && swiper.params.cssMode) { disable(); } if (swiper.params.mousewheel.enabled) enable(); }); on('destroy', () => { if (swiper.params.cssMode) { enable(); } if (swiper.mousewheel.enabled) disable(); }); Object.assign(swiper.mousewheel, { enable, disable }); } ;// CONCATENATED MODULE: ./node_modules/swiper/shared/create-element-if-not-defined.mjs function create_element_if_not_defined_createElementIfNotDefined(swiper, originalParams, params, checkProps) { if (swiper.params.createElements) { Object.keys(checkProps).forEach(key => { if (!params[key] && params.auto === true) { let element = utils_elementChildren(swiper.el, `.${checkProps[key]}`)[0]; if (!element) { element = utils_createElement('div', checkProps[key]); element.className = checkProps[key]; swiper.el.append(element); } params[key] = element; originalParams[key] = element; } }); } return params; } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/navigation.mjs const arrowSvg = ``; function Navigation({ swiper, extendParams, on, emit }) { extendParams({ navigation: { nextEl: null, prevEl: null, addIcons: true, hideOnClick: false, disabledClass: 'swiper-button-disabled', hiddenClass: 'swiper-button-hidden', lockClass: 'swiper-button-lock', navigationDisabledClass: 'swiper-navigation-disabled' } }); swiper.navigation = { nextEl: null, prevEl: null, arrowSvg }; function getEl(el) { let res; if (el && typeof el === 'string' && swiper.isElement) { res = swiper.el.querySelector(el) || swiper.hostEl.querySelector(el); if (res) return res; } if (el) { if (typeof el === 'string') res = [...document.querySelectorAll(el)]; if (swiper.params.uniqueNavElements && typeof el === 'string' && res && res.length > 1 && swiper.el.querySelectorAll(el).length === 1) { res = swiper.el.querySelector(el); } else if (res && res.length === 1) { res = res[0]; } } if (el && !res) return el; // if (Array.isArray(res) && res.length === 1) res = res[0]; return res; } function toggleEl(el, disabled) { const params = swiper.params.navigation; el = utils_makeElementsArray(el); el.forEach(subEl => { if (subEl) { subEl.classList[disabled ? 'add' : 'remove'](...params.disabledClass.split(' ')); if (subEl.tagName === 'BUTTON') subEl.disabled = disabled; if (swiper.params.watchOverflow && swiper.enabled) { subEl.classList[swiper.isLocked ? 'add' : 'remove'](params.lockClass); } } }); } function update() { // Update Navigation Buttons const { nextEl, prevEl } = swiper.navigation; if (swiper.params.loop) { toggleEl(prevEl, false); toggleEl(nextEl, false); return; } toggleEl(prevEl, swiper.isBeginning && !swiper.params.rewind); toggleEl(nextEl, swiper.isEnd && !swiper.params.rewind); } function onPrevClick(e) { e.preventDefault(); if (swiper.isBeginning && !swiper.params.loop && !swiper.params.rewind) return; swiper.slidePrev(); emit('navigationPrev'); } function onNextClick(e) { e.preventDefault(); if (swiper.isEnd && !swiper.params.loop && !swiper.params.rewind) return; swiper.slideNext(); emit('navigationNext'); } function init() { const params = swiper.params.navigation; swiper.params.navigation = create_element_if_not_defined_createElementIfNotDefined(swiper, swiper.originalParams.navigation, swiper.params.navigation, { nextEl: 'swiper-button-next', prevEl: 'swiper-button-prev' }); if (!(params.nextEl || params.prevEl)) return; let nextEl = getEl(params.nextEl); let prevEl = getEl(params.prevEl); Object.assign(swiper.navigation, { nextEl, prevEl }); nextEl = utils_makeElementsArray(nextEl); prevEl = utils_makeElementsArray(prevEl); const initButton = (el, dir) => { if (el) { if (params.addIcons && el.matches('.swiper-button-next,.swiper-button-prev') && !el.querySelector('svg')) { const tempEl = document.createElement('div'); utils_setInnerHTML(tempEl, arrowSvg); el.appendChild(tempEl.querySelector('svg')); tempEl.remove(); } el.addEventListener('click', dir === 'next' ? onNextClick : onPrevClick); } if (!swiper.enabled && el) { el.classList.add(...params.lockClass.split(' ')); } }; nextEl.forEach(el => initButton(el, 'next')); prevEl.forEach(el => initButton(el, 'prev')); } function destroy() { let { nextEl, prevEl } = swiper.navigation; nextEl = utils_makeElementsArray(nextEl); prevEl = utils_makeElementsArray(prevEl); const destroyButton = (el, dir) => { el.removeEventListener('click', dir === 'next' ? onNextClick : onPrevClick); el.classList.remove(...swiper.params.navigation.disabledClass.split(' ')); }; nextEl.forEach(el => destroyButton(el, 'next')); prevEl.forEach(el => destroyButton(el, 'prev')); } on('init', () => { if (swiper.params.navigation.enabled === false) { // eslint-disable-next-line disable(); } else { init(); update(); } }); on('toEdge fromEdge lock unlock', () => { update(); }); on('destroy', () => { destroy(); }); on('enable disable', () => { let { nextEl, prevEl } = swiper.navigation; nextEl = utils_makeElementsArray(nextEl); prevEl = utils_makeElementsArray(prevEl); if (swiper.enabled) { update(); return; } [...nextEl, ...prevEl].filter(el => !!el).forEach(el => el.classList.add(swiper.params.navigation.lockClass)); }); on('click', (_s, e) => { let { nextEl, prevEl } = swiper.navigation; nextEl = utils_makeElementsArray(nextEl); prevEl = utils_makeElementsArray(prevEl); const targetEl = e.target; let targetIsButton = prevEl.includes(targetEl) || nextEl.includes(targetEl); if (swiper.isElement && !targetIsButton) { const path = e.path || e.composedPath && e.composedPath(); if (path) { targetIsButton = path.find(pathEl => nextEl.includes(pathEl) || prevEl.includes(pathEl)); } } if (swiper.params.navigation.hideOnClick && !targetIsButton) { if (swiper.pagination && swiper.params.pagination && swiper.params.pagination.clickable && (swiper.pagination.el === targetEl || swiper.pagination.el.contains(targetEl))) return; let isHidden; if (nextEl.length) { isHidden = nextEl[0].classList.contains(swiper.params.navigation.hiddenClass); } else if (prevEl.length) { isHidden = prevEl[0].classList.contains(swiper.params.navigation.hiddenClass); } if (isHidden === true) { emit('navigationShow'); } else { emit('navigationHide'); } [...nextEl, ...prevEl].filter(el => !!el).forEach(el => el.classList.toggle(swiper.params.navigation.hiddenClass)); } }); const enable = () => { swiper.el.classList.remove(...swiper.params.navigation.navigationDisabledClass.split(' ')); init(); update(); }; const disable = () => { swiper.el.classList.add(...swiper.params.navigation.navigationDisabledClass.split(' ')); destroy(); }; Object.assign(swiper.navigation, { enable, disable, update, init, destroy }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/pagination.mjs function Pagination({ swiper, extendParams, on, emit }) { const pfx = 'swiper-pagination'; extendParams({ pagination: { el: null, bulletElement: 'span', clickable: false, hideOnClick: false, renderBullet: null, renderProgressbar: null, renderFraction: null, renderCustom: null, progressbarOpposite: false, type: 'bullets', // 'bullets' or 'progressbar' or 'fraction' or 'custom' dynamicBullets: false, dynamicMainBullets: 1, formatFractionCurrent: number => number, formatFractionTotal: number => number, bulletClass: `${pfx}-bullet`, bulletActiveClass: `${pfx}-bullet-active`, modifierClass: `${pfx}-`, currentClass: `${pfx}-current`, totalClass: `${pfx}-total`, hiddenClass: `${pfx}-hidden`, progressbarFillClass: `${pfx}-progressbar-fill`, progressbarOppositeClass: `${pfx}-progressbar-opposite`, clickableClass: `${pfx}-clickable`, lockClass: `${pfx}-lock`, horizontalClass: `${pfx}-horizontal`, verticalClass: `${pfx}-vertical`, paginationDisabledClass: `${pfx}-disabled` } }); swiper.pagination = { el: null, bullets: [] }; let bulletSize; let dynamicBulletIndex = 0; function isPaginationDisabled() { return !swiper.params.pagination.el || !swiper.pagination.el || Array.isArray(swiper.pagination.el) && swiper.pagination.el.length === 0; } function setSideBullets(bulletEl, position) { const { bulletActiveClass } = swiper.params.pagination; if (!bulletEl) return; bulletEl = bulletEl[`${position === 'prev' ? 'previous' : 'next'}ElementSibling`]; if (bulletEl) { bulletEl.classList.add(`${bulletActiveClass}-${position}`); bulletEl = bulletEl[`${position === 'prev' ? 'previous' : 'next'}ElementSibling`]; if (bulletEl) { bulletEl.classList.add(`${bulletActiveClass}-${position}-${position}`); } } } function getMoveDirection(prevIndex, nextIndex, length) { prevIndex = prevIndex % length; nextIndex = nextIndex % length; if (nextIndex === prevIndex + 1) { return 'next'; } else if (nextIndex === prevIndex - 1) { return 'previous'; } return; } function onBulletClick(e) { const bulletEl = e.target.closest(classesToSelector(swiper.params.pagination.bulletClass)); if (!bulletEl) { return; } e.preventDefault(); const index = elementIndex(bulletEl) * swiper.params.slidesPerGroup; if (swiper.params.loop) { if (swiper.realIndex === index) return; const moveDirection = getMoveDirection(swiper.realIndex, index, swiper.slides.length); if (moveDirection === 'next') { swiper.slideNext(); } else if (moveDirection === 'previous') { swiper.slidePrev(); } else { swiper.slideToLoop(index); } } else { swiper.slideTo(index); } } function update() { // Render || Update Pagination bullets/items const rtl = swiper.rtl; const params = swiper.params.pagination; if (isPaginationDisabled()) return; let el = swiper.pagination.el; el = makeElementsArray(el); // Current/Total let current; let previousIndex; const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length : swiper.slides.length; const total = swiper.params.loop ? Math.ceil(slidesLength / swiper.params.slidesPerGroup) : swiper.snapGrid.length; if (swiper.params.loop) { previousIndex = swiper.previousRealIndex || 0; current = swiper.params.slidesPerGroup > 1 ? Math.floor(swiper.realIndex / swiper.params.slidesPerGroup) : swiper.realIndex; } else if (typeof swiper.snapIndex !== 'undefined') { current = swiper.snapIndex; previousIndex = swiper.previousSnapIndex; } else { previousIndex = swiper.previousIndex || 0; current = swiper.activeIndex || 0; } // Types if (params.type === 'bullets' && swiper.pagination.bullets && swiper.pagination.bullets.length > 0) { const bullets = swiper.pagination.bullets; let firstIndex; let lastIndex; let midIndex; if (params.dynamicBullets) { bulletSize = elementOuterSize(bullets[0], swiper.isHorizontal() ? 'width' : 'height', true); el.forEach(subEl => { subEl.style[swiper.isHorizontal() ? 'width' : 'height'] = `${bulletSize * (params.dynamicMainBullets + 4)}px`; }); if (params.dynamicMainBullets > 1 && previousIndex !== undefined) { dynamicBulletIndex += current - (previousIndex || 0); if (dynamicBulletIndex > params.dynamicMainBullets - 1) { dynamicBulletIndex = params.dynamicMainBullets - 1; } else if (dynamicBulletIndex < 0) { dynamicBulletIndex = 0; } } firstIndex = Math.max(current - dynamicBulletIndex, 0); lastIndex = firstIndex + (Math.min(bullets.length, params.dynamicMainBullets) - 1); midIndex = (lastIndex + firstIndex) / 2; } bullets.forEach(bulletEl => { const classesToRemove = [...['', '-next', '-next-next', '-prev', '-prev-prev', '-main'].map(suffix => `${params.bulletActiveClass}${suffix}`)].map(s => typeof s === 'string' && s.includes(' ') ? s.split(' ') : s).flat(); bulletEl.classList.remove(...classesToRemove); }); if (el.length > 1) { bullets.forEach(bullet => { const bulletIndex = elementIndex(bullet); if (bulletIndex === current) { bullet.classList.add(...params.bulletActiveClass.split(' ')); } else if (swiper.isElement) { bullet.setAttribute('part', 'bullet'); } if (params.dynamicBullets) { if (bulletIndex >= firstIndex && bulletIndex <= lastIndex) { bullet.classList.add(...`${params.bulletActiveClass}-main`.split(' ')); } if (bulletIndex === firstIndex) { setSideBullets(bullet, 'prev'); } if (bulletIndex === lastIndex) { setSideBullets(bullet, 'next'); } } }); } else { const bullet = bullets[current]; if (bullet) { bullet.classList.add(...params.bulletActiveClass.split(' ')); } if (swiper.isElement) { bullets.forEach((bulletEl, bulletIndex) => { bulletEl.setAttribute('part', bulletIndex === current ? 'bullet-active' : 'bullet'); }); } if (params.dynamicBullets) { const firstDisplayedBullet = bullets[firstIndex]; const lastDisplayedBullet = bullets[lastIndex]; for (let i = firstIndex; i <= lastIndex; i += 1) { if (bullets[i]) { bullets[i].classList.add(...`${params.bulletActiveClass}-main`.split(' ')); } } setSideBullets(firstDisplayedBullet, 'prev'); setSideBullets(lastDisplayedBullet, 'next'); } } if (params.dynamicBullets) { const dynamicBulletsLength = Math.min(bullets.length, params.dynamicMainBullets + 4); const bulletsOffset = (bulletSize * dynamicBulletsLength - bulletSize) / 2 - midIndex * bulletSize; const offsetProp = rtl ? 'right' : 'left'; bullets.forEach(bullet => { bullet.style[swiper.isHorizontal() ? offsetProp : 'top'] = `${bulletsOffset}px`; }); } } el.forEach((subEl, subElIndex) => { if (params.type === 'fraction') { subEl.querySelectorAll(classesToSelector(params.currentClass)).forEach(fractionEl => { fractionEl.textContent = params.formatFractionCurrent(current + 1); }); subEl.querySelectorAll(classesToSelector(params.totalClass)).forEach(totalEl => { totalEl.textContent = params.formatFractionTotal(total); }); } if (params.type === 'progressbar') { let progressbarDirection; if (params.progressbarOpposite) { progressbarDirection = swiper.isHorizontal() ? 'vertical' : 'horizontal'; } else { progressbarDirection = swiper.isHorizontal() ? 'horizontal' : 'vertical'; } const scale = (current + 1) / total; let scaleX = 1; let scaleY = 1; if (progressbarDirection === 'horizontal') { scaleX = scale; } else { scaleY = scale; } subEl.querySelectorAll(classesToSelector(params.progressbarFillClass)).forEach(progressEl => { progressEl.style.transform = `translate3d(0,0,0) scaleX(${scaleX}) scaleY(${scaleY})`; progressEl.style.transitionDuration = `${swiper.params.speed}ms`; }); } if (params.type === 'custom' && params.renderCustom) { setInnerHTML(subEl, params.renderCustom(swiper, current + 1, total)); if (subElIndex === 0) emit('paginationRender', subEl); } else { if (subElIndex === 0) emit('paginationRender', subEl); emit('paginationUpdate', subEl); } if (swiper.params.watchOverflow && swiper.enabled) { subEl.classList[swiper.isLocked ? 'add' : 'remove'](params.lockClass); } }); } function render() { // Render Container const params = swiper.params.pagination; if (isPaginationDisabled()) return; const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length : swiper.grid && swiper.params.grid.rows > 1 ? swiper.slides.length / Math.ceil(swiper.params.grid.rows) : swiper.slides.length; let el = swiper.pagination.el; el = makeElementsArray(el); let paginationHTML = ''; if (params.type === 'bullets') { let numberOfBullets = swiper.params.loop ? Math.ceil(slidesLength / swiper.params.slidesPerGroup) : swiper.snapGrid.length; if (swiper.params.freeMode && swiper.params.freeMode.enabled && numberOfBullets > slidesLength) { numberOfBullets = slidesLength; } for (let i = 0; i < numberOfBullets; i += 1) { if (params.renderBullet) { paginationHTML += params.renderBullet.call(swiper, i, params.bulletClass); } else { // prettier-ignore paginationHTML += `<${params.bulletElement} ${swiper.isElement ? 'part="bullet"' : ''} class="${params.bulletClass}">`; } } } if (params.type === 'fraction') { if (params.renderFraction) { paginationHTML = params.renderFraction.call(swiper, params.currentClass, params.totalClass); } else { paginationHTML = `` + ' / ' + ``; } } if (params.type === 'progressbar') { if (params.renderProgressbar) { paginationHTML = params.renderProgressbar.call(swiper, params.progressbarFillClass); } else { paginationHTML = ``; } } swiper.pagination.bullets = []; el.forEach(subEl => { if (params.type !== 'custom') { setInnerHTML(subEl, paginationHTML || ''); } if (params.type === 'bullets') { swiper.pagination.bullets.push(...subEl.querySelectorAll(classesToSelector(params.bulletClass))); } }); if (params.type !== 'custom') { emit('paginationRender', el[0]); } } function init() { swiper.params.pagination = createElementIfNotDefined(swiper, swiper.originalParams.pagination, swiper.params.pagination, { el: 'swiper-pagination' }); const params = swiper.params.pagination; if (!params.el) return; let el; if (typeof params.el === 'string' && swiper.isElement) { el = swiper.el.querySelector(params.el); } if (!el && typeof params.el === 'string') { el = [...document.querySelectorAll(params.el)]; } if (!el) { el = params.el; } if (!el || el.length === 0) return; if (swiper.params.uniqueNavElements && typeof params.el === 'string' && Array.isArray(el) && el.length > 1) { el = [...swiper.el.querySelectorAll(params.el)]; // check if it belongs to another nested Swiper if (el.length > 1) { el = el.find(subEl => { if (elementParents(subEl, '.swiper')[0] !== swiper.el) return false; return true; }); } } if (Array.isArray(el) && el.length === 1) el = el[0]; Object.assign(swiper.pagination, { el }); el = makeElementsArray(el); el.forEach(subEl => { if (params.type === 'bullets' && params.clickable) { subEl.classList.add(...(params.clickableClass || '').split(' ')); } subEl.classList.add(params.modifierClass + params.type); subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass); if (params.type === 'bullets' && params.dynamicBullets) { subEl.classList.add(`${params.modifierClass}${params.type}-dynamic`); dynamicBulletIndex = 0; if (params.dynamicMainBullets < 1) { params.dynamicMainBullets = 1; } } if (params.type === 'progressbar' && params.progressbarOpposite) { subEl.classList.add(params.progressbarOppositeClass); } if (params.clickable) { subEl.addEventListener('click', onBulletClick); } if (!swiper.enabled) { subEl.classList.add(params.lockClass); } }); } function destroy() { const params = swiper.params.pagination; if (isPaginationDisabled()) return; let el = swiper.pagination.el; if (el) { el = makeElementsArray(el); el.forEach(subEl => { subEl.classList.remove(params.hiddenClass); subEl.classList.remove(params.modifierClass + params.type); subEl.classList.remove(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass); if (params.clickable) { subEl.classList.remove(...(params.clickableClass || '').split(' ')); subEl.removeEventListener('click', onBulletClick); } }); } if (swiper.pagination.bullets) swiper.pagination.bullets.forEach(subEl => subEl.classList.remove(...params.bulletActiveClass.split(' '))); } on('changeDirection', () => { if (!swiper.pagination || !swiper.pagination.el) return; const params = swiper.params.pagination; let { el } = swiper.pagination; el = makeElementsArray(el); el.forEach(subEl => { subEl.classList.remove(params.horizontalClass, params.verticalClass); subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass); }); }); on('init', () => { if (swiper.params.pagination.enabled === false) { // eslint-disable-next-line disable(); } else { init(); render(); update(); } }); on('activeIndexChange', () => { if (typeof swiper.snapIndex === 'undefined') { update(); } }); on('snapIndexChange', () => { update(); }); on('snapGridLengthChange', () => { render(); update(); }); on('destroy', () => { destroy(); }); on('enable disable', () => { let { el } = swiper.pagination; if (el) { el = makeElementsArray(el); el.forEach(subEl => subEl.classList[swiper.enabled ? 'remove' : 'add'](swiper.params.pagination.lockClass)); } }); on('lock unlock', () => { update(); }); on('click', (_s, e) => { const targetEl = e.target; const el = makeElementsArray(swiper.pagination.el); if (swiper.params.pagination.el && swiper.params.pagination.hideOnClick && el && el.length > 0 && !targetEl.classList.contains(swiper.params.pagination.bulletClass)) { if (swiper.navigation && (swiper.navigation.nextEl && targetEl === swiper.navigation.nextEl || swiper.navigation.prevEl && targetEl === swiper.navigation.prevEl)) return; const isHidden = el[0].classList.contains(swiper.params.pagination.hiddenClass); if (isHidden === true) { emit('paginationShow'); } else { emit('paginationHide'); } el.forEach(subEl => subEl.classList.toggle(swiper.params.pagination.hiddenClass)); } }); const enable = () => { swiper.el.classList.remove(swiper.params.pagination.paginationDisabledClass); let { el } = swiper.pagination; if (el) { el = makeElementsArray(el); el.forEach(subEl => subEl.classList.remove(swiper.params.pagination.paginationDisabledClass)); } init(); render(); update(); }; const disable = () => { swiper.el.classList.add(swiper.params.pagination.paginationDisabledClass); let { el } = swiper.pagination; if (el) { el = makeElementsArray(el); el.forEach(subEl => subEl.classList.add(swiper.params.pagination.paginationDisabledClass)); } destroy(); }; Object.assign(swiper.pagination, { enable, disable, render, update, init, destroy }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/scrollbar.mjs function Scrollbar({ swiper, extendParams, on, emit }) { const document = getDocument(); let isTouched = false; let timeout = null; let dragTimeout = null; let dragStartPos; let dragSize; let trackSize; let divider; extendParams({ scrollbar: { el: null, dragSize: 'auto', hide: false, draggable: false, snapOnRelease: true, lockClass: 'swiper-scrollbar-lock', dragClass: 'swiper-scrollbar-drag', scrollbarDisabledClass: 'swiper-scrollbar-disabled', horizontalClass: `swiper-scrollbar-horizontal`, verticalClass: `swiper-scrollbar-vertical` } }); swiper.scrollbar = { el: null, dragEl: null }; function setTranslate() { if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return; const { scrollbar, rtlTranslate: rtl } = swiper; const { dragEl, el } = scrollbar; const params = swiper.params.scrollbar; const progress = swiper.params.loop ? swiper.progressLoop : swiper.progress; let newSize = dragSize; let newPos = (trackSize - dragSize) * progress; if (rtl) { newPos = -newPos; if (newPos > 0) { newSize = dragSize - newPos; newPos = 0; } else if (-newPos + dragSize > trackSize) { newSize = trackSize + newPos; } } else if (newPos < 0) { newSize = dragSize + newPos; newPos = 0; } else if (newPos + dragSize > trackSize) { newSize = trackSize - newPos; } if (swiper.isHorizontal()) { dragEl.style.transform = `translate3d(${newPos}px, 0, 0)`; dragEl.style.width = `${newSize}px`; } else { dragEl.style.transform = `translate3d(0px, ${newPos}px, 0)`; dragEl.style.height = `${newSize}px`; } if (params.hide) { clearTimeout(timeout); el.style.opacity = 1; timeout = setTimeout(() => { el.style.opacity = 0; el.style.transitionDuration = '400ms'; }, 1000); } } function setTransition(duration) { if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return; swiper.scrollbar.dragEl.style.transitionDuration = `${duration}ms`; } function updateSize() { if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return; const { scrollbar } = swiper; const { dragEl, el } = scrollbar; dragEl.style.width = ''; dragEl.style.height = ''; trackSize = swiper.isHorizontal() ? el.offsetWidth : el.offsetHeight; divider = swiper.size / (swiper.virtualSize + swiper.params.slidesOffsetBefore - (swiper.params.centeredSlides ? swiper.snapGrid[0] : 0)); if (swiper.params.scrollbar.dragSize === 'auto') { dragSize = trackSize * divider; } else { dragSize = parseInt(swiper.params.scrollbar.dragSize, 10); } if (swiper.isHorizontal()) { dragEl.style.width = `${dragSize}px`; } else { dragEl.style.height = `${dragSize}px`; } if (divider >= 1) { el.style.display = 'none'; } else { el.style.display = ''; } if (swiper.params.scrollbar.hide) { el.style.opacity = 0; } if (swiper.params.watchOverflow && swiper.enabled) { scrollbar.el.classList[swiper.isLocked ? 'add' : 'remove'](swiper.params.scrollbar.lockClass); } } function getPointerPosition(e) { return swiper.isHorizontal() ? e.clientX : e.clientY; } function setDragPosition(e) { const { scrollbar, rtlTranslate: rtl } = swiper; const { el } = scrollbar; let positionRatio; positionRatio = (getPointerPosition(e) - elementOffset(el)[swiper.isHorizontal() ? 'left' : 'top'] - (dragStartPos !== null ? dragStartPos : dragSize / 2)) / (trackSize - dragSize); positionRatio = Math.max(Math.min(positionRatio, 1), 0); if (rtl) { positionRatio = 1 - positionRatio; } const position = swiper.minTranslate() + (swiper.maxTranslate() - swiper.minTranslate()) * positionRatio; swiper.updateProgress(position); swiper.setTranslate(position); swiper.updateActiveIndex(); swiper.updateSlidesClasses(); } function onDragStart(e) { const params = swiper.params.scrollbar; const { scrollbar, wrapperEl } = swiper; const { el, dragEl } = scrollbar; isTouched = true; dragStartPos = e.target === dragEl ? getPointerPosition(e) - e.target.getBoundingClientRect()[swiper.isHorizontal() ? 'left' : 'top'] : null; e.preventDefault(); e.stopPropagation(); wrapperEl.style.transitionDuration = '100ms'; dragEl.style.transitionDuration = '100ms'; setDragPosition(e); clearTimeout(dragTimeout); el.style.transitionDuration = '0ms'; if (params.hide) { el.style.opacity = 1; } if (swiper.params.cssMode) { swiper.wrapperEl.style['scroll-snap-type'] = 'none'; } emit('scrollbarDragStart', e); } function onDragMove(e) { const { scrollbar, wrapperEl } = swiper; const { el, dragEl } = scrollbar; if (!isTouched) return; if (e.preventDefault && e.cancelable) e.preventDefault();else e.returnValue = false; setDragPosition(e); wrapperEl.style.transitionDuration = '0ms'; el.style.transitionDuration = '0ms'; dragEl.style.transitionDuration = '0ms'; emit('scrollbarDragMove', e); } function onDragEnd(e) { const params = swiper.params.scrollbar; const { scrollbar, wrapperEl } = swiper; const { el } = scrollbar; if (!isTouched) return; isTouched = false; if (swiper.params.cssMode) { swiper.wrapperEl.style['scroll-snap-type'] = ''; wrapperEl.style.transitionDuration = ''; } if (params.hide) { clearTimeout(dragTimeout); dragTimeout = nextTick(() => { el.style.opacity = 0; el.style.transitionDuration = '400ms'; }, 1000); } emit('scrollbarDragEnd', e); if (params.snapOnRelease) { swiper.slideToClosest(); } } function events(method) { const { scrollbar, params } = swiper; const el = scrollbar.el; if (!el) return; const target = el; const activeListener = params.passiveListeners ? { passive: false, capture: false } : false; const passiveListener = params.passiveListeners ? { passive: true, capture: false } : false; if (!target) return; const eventMethod = method === 'on' ? 'addEventListener' : 'removeEventListener'; target[eventMethod]('pointerdown', onDragStart, activeListener); document[eventMethod]('pointermove', onDragMove, activeListener); document[eventMethod]('pointerup', onDragEnd, passiveListener); } function enableDraggable() { if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return; events('on'); } function disableDraggable() { if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return; events('off'); } function init() { const { scrollbar, el: swiperEl } = swiper; swiper.params.scrollbar = createElementIfNotDefined(swiper, swiper.originalParams.scrollbar, swiper.params.scrollbar, { el: 'swiper-scrollbar' }); const params = swiper.params.scrollbar; if (!params.el) return; let el; if (typeof params.el === 'string' && swiper.isElement) { el = swiper.el.querySelector(params.el); } if (!el && typeof params.el === 'string') { el = document.querySelectorAll(params.el); if (!el.length) return; } else if (!el) { el = params.el; } if (swiper.params.uniqueNavElements && typeof params.el === 'string' && el.length > 1 && swiperEl.querySelectorAll(params.el).length === 1) { el = swiperEl.querySelector(params.el); } if (el.length > 0) el = el[0]; el.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass); let dragEl; if (el) { dragEl = el.querySelector(classesToSelector(swiper.params.scrollbar.dragClass)); if (!dragEl) { dragEl = createElement('div', swiper.params.scrollbar.dragClass); el.append(dragEl); } } Object.assign(scrollbar, { el, dragEl }); if (params.draggable) { enableDraggable(); } if (el) { el.classList[swiper.enabled ? 'remove' : 'add'](...classesToTokens(swiper.params.scrollbar.lockClass)); } } function destroy() { const params = swiper.params.scrollbar; const el = swiper.scrollbar.el; if (el) { el.classList.remove(...classesToTokens(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass)); } disableDraggable(); } on('changeDirection', () => { if (!swiper.scrollbar || !swiper.scrollbar.el) return; const params = swiper.params.scrollbar; let { el } = swiper.scrollbar; el = makeElementsArray(el); el.forEach(subEl => { subEl.classList.remove(params.horizontalClass, params.verticalClass); subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass); }); }); on('init', () => { if (swiper.params.scrollbar.enabled === false) { // eslint-disable-next-line disable(); } else { init(); updateSize(); setTranslate(); } }); on('update resize observerUpdate lock unlock changeDirection', () => { updateSize(); }); on('setTranslate', () => { setTranslate(); }); on('setTransition', (_s, duration) => { setTransition(duration); }); on('enable disable', () => { const { el } = swiper.scrollbar; if (el) { el.classList[swiper.enabled ? 'remove' : 'add'](...classesToTokens(swiper.params.scrollbar.lockClass)); } }); on('destroy', () => { destroy(); }); const enable = () => { swiper.el.classList.remove(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass)); if (swiper.scrollbar.el) { swiper.scrollbar.el.classList.remove(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass)); } init(); updateSize(); setTranslate(); }; const disable = () => { swiper.el.classList.add(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass)); if (swiper.scrollbar.el) { swiper.scrollbar.el.classList.add(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass)); } destroy(); }; Object.assign(swiper.scrollbar, { enable, disable, updateSize, setTranslate, init, destroy }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/parallax.mjs function Parallax({ swiper, extendParams, on }) { extendParams({ parallax: { enabled: false } }); const elementsSelector = '[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y], [data-swiper-parallax-opacity], [data-swiper-parallax-scale]'; const setTransform = (el, progress) => { const { rtl } = swiper; const rtlFactor = rtl ? -1 : 1; const p = el.getAttribute('data-swiper-parallax') || '0'; let x = el.getAttribute('data-swiper-parallax-x'); let y = el.getAttribute('data-swiper-parallax-y'); const scale = el.getAttribute('data-swiper-parallax-scale'); const opacity = el.getAttribute('data-swiper-parallax-opacity'); const rotate = el.getAttribute('data-swiper-parallax-rotate'); if (x || y) { x = x || '0'; y = y || '0'; } else if (swiper.isHorizontal()) { x = p; y = '0'; } else { y = p; x = '0'; } if (x.indexOf('%') >= 0) { x = `${parseInt(x, 10) * progress * rtlFactor}%`; } else { x = `${x * progress * rtlFactor}px`; } if (y.indexOf('%') >= 0) { y = `${parseInt(y, 10) * progress}%`; } else { y = `${y * progress}px`; } if (typeof opacity !== 'undefined' && opacity !== null) { const currentOpacity = opacity - (opacity - 1) * (1 - Math.abs(progress)); el.style.opacity = currentOpacity; } let transform = `translate3d(${x}, ${y}, 0px)`; if (typeof scale !== 'undefined' && scale !== null) { const currentScale = scale - (scale - 1) * (1 - Math.abs(progress)); transform += ` scale(${currentScale})`; } if (rotate && typeof rotate !== 'undefined' && rotate !== null) { const currentRotate = rotate * progress * -1; transform += ` rotate(${currentRotate}deg)`; } el.style.transform = transform; }; const setTranslate = () => { const { el, slides, progress, snapGrid, isElement } = swiper; const elements = elementChildren(el, elementsSelector); if (swiper.isElement) { elements.push(...elementChildren(swiper.hostEl, elementsSelector)); } elements.forEach(subEl => { setTransform(subEl, progress); }); slides.forEach((slideEl, slideIndex) => { let slideProgress = slideEl.progress; if (swiper.params.slidesPerGroup > 1 && swiper.params.slidesPerView !== 'auto') { slideProgress += Math.ceil(slideIndex / 2) - progress * (snapGrid.length - 1); } slideProgress = Math.min(Math.max(slideProgress, -1), 1); slideEl.querySelectorAll(`${elementsSelector}, [data-swiper-parallax-rotate]`).forEach(subEl => { setTransform(subEl, slideProgress); }); }); }; const setTransition = (duration = swiper.params.speed) => { const { el, hostEl } = swiper; const elements = [...el.querySelectorAll(elementsSelector)]; if (swiper.isElement) { elements.push(...hostEl.querySelectorAll(elementsSelector)); } elements.forEach(parallaxEl => { let parallaxDuration = parseInt(parallaxEl.getAttribute('data-swiper-parallax-duration'), 10) || duration; if (duration === 0) parallaxDuration = 0; parallaxEl.style.transitionDuration = `${parallaxDuration}ms`; }); }; on('beforeInit', () => { if (!swiper.params.parallax.enabled) return; swiper.params.watchSlidesProgress = true; swiper.originalParams.watchSlidesProgress = true; }); on('init', () => { if (!swiper.params.parallax.enabled) return; setTranslate(); }); on('setTranslate', () => { if (!swiper.params.parallax.enabled) return; setTranslate(); }); on('setTransition', (_swiper, duration) => { if (!swiper.params.parallax.enabled) return; setTransition(duration); }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/zoom.mjs function Zoom({ swiper, extendParams, on, emit }) { const window = getWindow(); extendParams({ zoom: { enabled: false, limitToOriginalSize: false, maxRatio: 3, minRatio: 1, panOnMouseMove: false, toggle: true, containerClass: 'swiper-zoom-container', zoomedSlideClass: 'swiper-slide-zoomed' } }); swiper.zoom = { enabled: false }; let currentScale = 1; let isScaling = false; let isPanningWithMouse = false; let mousePanStart = { x: 0, y: 0 }; const mousePanSensitivity = -3; // Negative to invert pan direction let fakeGestureTouched; let fakeGestureMoved; const evCache = []; const gesture = { originX: 0, originY: 0, slideEl: undefined, slideWidth: undefined, slideHeight: undefined, imageEl: undefined, imageWrapEl: undefined, maxRatio: 3 }; const image = { isTouched: undefined, isMoved: undefined, currentX: undefined, currentY: undefined, minX: undefined, minY: undefined, maxX: undefined, maxY: undefined, width: undefined, height: undefined, startX: undefined, startY: undefined, touchesStart: {}, touchesCurrent: {} }; const velocity = { x: undefined, y: undefined, prevPositionX: undefined, prevPositionY: undefined, prevTime: undefined }; let scale = 1; Object.defineProperty(swiper.zoom, 'scale', { get() { return scale; }, set(value) { if (scale !== value) { const imageEl = gesture.imageEl; const slideEl = gesture.slideEl; emit('zoomChange', value, imageEl, slideEl); } scale = value; } }); function getDistanceBetweenTouches() { if (evCache.length < 2) return 1; const x1 = evCache[0].pageX; const y1 = evCache[0].pageY; const x2 = evCache[1].pageX; const y2 = evCache[1].pageY; const distance = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); return distance; } function getMaxRatio() { const params = swiper.params.zoom; const maxRatio = gesture.imageWrapEl.getAttribute('data-swiper-zoom') || params.maxRatio; if (params.limitToOriginalSize && gesture.imageEl && gesture.imageEl.naturalWidth) { const imageMaxRatio = gesture.imageEl.naturalWidth / gesture.imageEl.offsetWidth; return Math.min(imageMaxRatio, maxRatio); } return maxRatio; } function getScaleOrigin() { if (evCache.length < 2) return { x: null, y: null }; const box = gesture.imageEl.getBoundingClientRect(); return [(evCache[0].pageX + (evCache[1].pageX - evCache[0].pageX) / 2 - box.x - window.scrollX) / currentScale, (evCache[0].pageY + (evCache[1].pageY - evCache[0].pageY) / 2 - box.y - window.scrollY) / currentScale]; } function getSlideSelector() { return swiper.isElement ? `swiper-slide` : `.${swiper.params.slideClass}`; } function eventWithinSlide(e) { const slideSelector = getSlideSelector(); if (e.target.matches(slideSelector)) return true; if (swiper.slides.filter(slideEl => slideEl.contains(e.target)).length > 0) return true; return false; } function eventWithinZoomContainer(e) { const selector = `.${swiper.params.zoom.containerClass}`; if (e.target.matches(selector)) return true; if ([...swiper.hostEl.querySelectorAll(selector)].filter(containerEl => containerEl.contains(e.target)).length > 0) return true; return false; } // Events function onGestureStart(e) { if (e.pointerType === 'mouse') { evCache.splice(0, evCache.length); } if (!eventWithinSlide(e)) return; const params = swiper.params.zoom; fakeGestureTouched = false; fakeGestureMoved = false; evCache.push(e); if (evCache.length < 2) { return; } fakeGestureTouched = true; gesture.scaleStart = getDistanceBetweenTouches(); if (!gesture.slideEl) { gesture.slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`); if (!gesture.slideEl) gesture.slideEl = swiper.slides[swiper.activeIndex]; let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`); if (imageEl) { imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0]; } gesture.imageEl = imageEl; if (imageEl) { gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0]; } else { gesture.imageWrapEl = undefined; } if (!gesture.imageWrapEl) { gesture.imageEl = undefined; return; } gesture.maxRatio = getMaxRatio(); } if (gesture.imageEl) { const [originX, originY] = getScaleOrigin(); gesture.originX = originX; gesture.originY = originY; gesture.imageEl.style.transitionDuration = '0ms'; } isScaling = true; } function onGestureChange(e) { if (!eventWithinSlide(e)) return; const params = swiper.params.zoom; const zoom = swiper.zoom; const pointerIndex = evCache.findIndex(cachedEv => cachedEv.pointerId === e.pointerId); if (pointerIndex >= 0) evCache[pointerIndex] = e; if (evCache.length < 2) { return; } fakeGestureMoved = true; gesture.scaleMove = getDistanceBetweenTouches(); if (!gesture.imageEl) { return; } zoom.scale = gesture.scaleMove / gesture.scaleStart * currentScale; if (zoom.scale > gesture.maxRatio) { zoom.scale = gesture.maxRatio - 1 + (zoom.scale - gesture.maxRatio + 1) ** 0.5; } if (zoom.scale < params.minRatio) { zoom.scale = params.minRatio + 1 - (params.minRatio - zoom.scale + 1) ** 0.5; } gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`; } function onGestureEnd(e) { if (!eventWithinSlide(e)) return; if (e.pointerType === 'mouse' && e.type === 'pointerout') return; const params = swiper.params.zoom; const zoom = swiper.zoom; const pointerIndex = evCache.findIndex(cachedEv => cachedEv.pointerId === e.pointerId); if (pointerIndex >= 0) evCache.splice(pointerIndex, 1); if (!fakeGestureTouched || !fakeGestureMoved) { return; } fakeGestureTouched = false; fakeGestureMoved = false; if (!gesture.imageEl) return; zoom.scale = Math.max(Math.min(zoom.scale, gesture.maxRatio), params.minRatio); gesture.imageEl.style.transitionDuration = `${swiper.params.speed}ms`; gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`; currentScale = zoom.scale; isScaling = false; if (zoom.scale > 1 && gesture.slideEl) { gesture.slideEl.classList.add(`${params.zoomedSlideClass}`); } else if (zoom.scale <= 1 && gesture.slideEl) { gesture.slideEl.classList.remove(`${params.zoomedSlideClass}`); } if (zoom.scale === 1) { gesture.originX = 0; gesture.originY = 0; gesture.slideEl = undefined; } } let allowTouchMoveTimeout; function allowTouchMove() { swiper.touchEventsData.preventTouchMoveFromPointerMove = false; } function preventTouchMove() { clearTimeout(allowTouchMoveTimeout); swiper.touchEventsData.preventTouchMoveFromPointerMove = true; allowTouchMoveTimeout = setTimeout(() => { if (swiper.destroyed) return; allowTouchMove(); }); } function onTouchStart(e) { const device = swiper.device; if (!gesture.imageEl) return; if (image.isTouched) return; if (device.android && e.cancelable) e.preventDefault(); image.isTouched = true; const event = evCache.length > 0 ? evCache[0] : e; image.touchesStart.x = event.pageX; image.touchesStart.y = event.pageY; } function onTouchMove(e) { const isMouseEvent = e.pointerType === 'mouse'; const isMousePan = isMouseEvent && swiper.params.zoom.panOnMouseMove; if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) { return; } const zoom = swiper.zoom; if (!gesture.imageEl) { return; } if (!image.isTouched || !gesture.slideEl) { if (isMousePan) onMouseMove(e); return; } if (isMousePan) { onMouseMove(e); return; } if (!image.isMoved) { image.width = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth; image.height = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight; image.startX = getTranslate(gesture.imageWrapEl, 'x') || 0; image.startY = getTranslate(gesture.imageWrapEl, 'y') || 0; gesture.slideWidth = gesture.slideEl.offsetWidth; gesture.slideHeight = gesture.slideEl.offsetHeight; gesture.imageWrapEl.style.transitionDuration = '0ms'; } // Define if we need image drag const scaledWidth = image.width * zoom.scale; const scaledHeight = image.height * zoom.scale; image.minX = Math.min(gesture.slideWidth / 2 - scaledWidth / 2, 0); image.maxX = -image.minX; image.minY = Math.min(gesture.slideHeight / 2 - scaledHeight / 2, 0); image.maxY = -image.minY; image.touchesCurrent.x = evCache.length > 0 ? evCache[0].pageX : e.pageX; image.touchesCurrent.y = evCache.length > 0 ? evCache[0].pageY : e.pageY; const touchesDiff = Math.max(Math.abs(image.touchesCurrent.x - image.touchesStart.x), Math.abs(image.touchesCurrent.y - image.touchesStart.y)); if (touchesDiff > 5) { swiper.allowClick = false; } if (!image.isMoved && !isScaling) { if (swiper.isHorizontal() && (Math.floor(image.minX) === Math.floor(image.startX) && image.touchesCurrent.x < image.touchesStart.x || Math.floor(image.maxX) === Math.floor(image.startX) && image.touchesCurrent.x > image.touchesStart.x)) { image.isTouched = false; allowTouchMove(); return; } if (!swiper.isHorizontal() && (Math.floor(image.minY) === Math.floor(image.startY) && image.touchesCurrent.y < image.touchesStart.y || Math.floor(image.maxY) === Math.floor(image.startY) && image.touchesCurrent.y > image.touchesStart.y)) { image.isTouched = false; allowTouchMove(); return; } } if (e.cancelable) { e.preventDefault(); } e.stopPropagation(); preventTouchMove(); image.isMoved = true; const scaleRatio = (zoom.scale - currentScale) / (gesture.maxRatio - swiper.params.zoom.minRatio); const { originX, originY } = gesture; image.currentX = image.touchesCurrent.x - image.touchesStart.x + image.startX + scaleRatio * (image.width - originX * 2); image.currentY = image.touchesCurrent.y - image.touchesStart.y + image.startY + scaleRatio * (image.height - originY * 2); if (image.currentX < image.minX) { image.currentX = image.minX + 1 - (image.minX - image.currentX + 1) ** 0.8; } if (image.currentX > image.maxX) { image.currentX = image.maxX - 1 + (image.currentX - image.maxX + 1) ** 0.8; } if (image.currentY < image.minY) { image.currentY = image.minY + 1 - (image.minY - image.currentY + 1) ** 0.8; } if (image.currentY > image.maxY) { image.currentY = image.maxY - 1 + (image.currentY - image.maxY + 1) ** 0.8; } // Velocity if (!velocity.prevPositionX) velocity.prevPositionX = image.touchesCurrent.x; if (!velocity.prevPositionY) velocity.prevPositionY = image.touchesCurrent.y; if (!velocity.prevTime) velocity.prevTime = Date.now(); velocity.x = (image.touchesCurrent.x - velocity.prevPositionX) / (Date.now() - velocity.prevTime) / 2; velocity.y = (image.touchesCurrent.y - velocity.prevPositionY) / (Date.now() - velocity.prevTime) / 2; if (Math.abs(image.touchesCurrent.x - velocity.prevPositionX) < 2) velocity.x = 0; if (Math.abs(image.touchesCurrent.y - velocity.prevPositionY) < 2) velocity.y = 0; velocity.prevPositionX = image.touchesCurrent.x; velocity.prevPositionY = image.touchesCurrent.y; velocity.prevTime = Date.now(); gesture.imageWrapEl.style.transform = `translate3d(${image.currentX}px, ${image.currentY}px,0)`; } function onTouchEnd() { const zoom = swiper.zoom; evCache.length = 0; if (!gesture.imageEl) return; if (!image.isTouched || !image.isMoved) { image.isTouched = false; image.isMoved = false; return; } image.isTouched = false; image.isMoved = false; let momentumDurationX = 300; let momentumDurationY = 300; const momentumDistanceX = velocity.x * momentumDurationX; const newPositionX = image.currentX + momentumDistanceX; const momentumDistanceY = velocity.y * momentumDurationY; const newPositionY = image.currentY + momentumDistanceY; // Fix duration if (velocity.x !== 0) momentumDurationX = Math.abs((newPositionX - image.currentX) / velocity.x); if (velocity.y !== 0) momentumDurationY = Math.abs((newPositionY - image.currentY) / velocity.y); const momentumDuration = Math.max(momentumDurationX, momentumDurationY); image.currentX = newPositionX; image.currentY = newPositionY; // Define if we need image drag const scaledWidth = image.width * zoom.scale; const scaledHeight = image.height * zoom.scale; image.minX = Math.min(gesture.slideWidth / 2 - scaledWidth / 2, 0); image.maxX = -image.minX; image.minY = Math.min(gesture.slideHeight / 2 - scaledHeight / 2, 0); image.maxY = -image.minY; image.currentX = Math.max(Math.min(image.currentX, image.maxX), image.minX); image.currentY = Math.max(Math.min(image.currentY, image.maxY), image.minY); gesture.imageWrapEl.style.transitionDuration = `${momentumDuration}ms`; gesture.imageWrapEl.style.transform = `translate3d(${image.currentX}px, ${image.currentY}px,0)`; } function onTransitionEnd() { const zoom = swiper.zoom; if (gesture.slideEl && swiper.activeIndex !== swiper.slides.indexOf(gesture.slideEl)) { if (gesture.imageEl) { gesture.imageEl.style.transform = 'translate3d(0,0,0) scale(1)'; } if (gesture.imageWrapEl) { gesture.imageWrapEl.style.transform = 'translate3d(0,0,0)'; } gesture.slideEl.classList.remove(`${swiper.params.zoom.zoomedSlideClass}`); zoom.scale = 1; currentScale = 1; gesture.slideEl = undefined; gesture.imageEl = undefined; gesture.imageWrapEl = undefined; gesture.originX = 0; gesture.originY = 0; } } function onMouseMove(e) { // Only pan if zoomed in and mouse panning is enabled if (currentScale <= 1 || !gesture.imageWrapEl) return; if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) return; const currentTransform = window.getComputedStyle(gesture.imageWrapEl).transform; const matrix = new window.DOMMatrix(currentTransform); if (!isPanningWithMouse) { isPanningWithMouse = true; mousePanStart.x = e.clientX; mousePanStart.y = e.clientY; image.startX = matrix.e; image.startY = matrix.f; image.width = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth; image.height = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight; gesture.slideWidth = gesture.slideEl.offsetWidth; gesture.slideHeight = gesture.slideEl.offsetHeight; return; } const deltaX = (e.clientX - mousePanStart.x) * mousePanSensitivity; const deltaY = (e.clientY - mousePanStart.y) * mousePanSensitivity; const scaledWidth = image.width * currentScale; const scaledHeight = image.height * currentScale; const slideWidth = gesture.slideWidth; const slideHeight = gesture.slideHeight; const minX = Math.min(slideWidth / 2 - scaledWidth / 2, 0); const maxX = -minX; const minY = Math.min(slideHeight / 2 - scaledHeight / 2, 0); const maxY = -minY; const newX = Math.max(Math.min(image.startX + deltaX, maxX), minX); const newY = Math.max(Math.min(image.startY + deltaY, maxY), minY); gesture.imageWrapEl.style.transitionDuration = '0ms'; gesture.imageWrapEl.style.transform = `translate3d(${newX}px, ${newY}px, 0)`; mousePanStart.x = e.clientX; mousePanStart.y = e.clientY; image.startX = newX; image.startY = newY; image.currentX = newX; image.currentY = newY; } function zoomIn(e) { const zoom = swiper.zoom; const params = swiper.params.zoom; if (!gesture.slideEl) { if (e && e.target) { gesture.slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`); } if (!gesture.slideEl) { if (swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual) { gesture.slideEl = elementChildren(swiper.slidesEl, `.${swiper.params.slideActiveClass}`)[0]; } else { gesture.slideEl = swiper.slides[swiper.activeIndex]; } } let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`); if (imageEl) { imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0]; } gesture.imageEl = imageEl; if (imageEl) { gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0]; } else { gesture.imageWrapEl = undefined; } } if (!gesture.imageEl || !gesture.imageWrapEl) return; gesture.maxRatio = getMaxRatio(); if (swiper.params.cssMode) { swiper.wrapperEl.style.overflow = 'hidden'; swiper.wrapperEl.style.touchAction = 'none'; } gesture.slideEl.classList.add(`${params.zoomedSlideClass}`); let touchX; let touchY; let offsetX; let offsetY; let diffX; let diffY; let translateX; let translateY; let imageWidth; let imageHeight; let scaledWidth; let scaledHeight; let translateMinX; let translateMinY; let translateMaxX; let translateMaxY; let slideWidth; let slideHeight; if (typeof image.touchesStart.x === 'undefined' && e) { touchX = e.pageX; touchY = e.pageY; } else { touchX = image.touchesStart.x; touchY = image.touchesStart.y; } const prevScale = currentScale; const forceZoomRatio = typeof e === 'number' ? e : null; if (currentScale === 1 && forceZoomRatio) { touchX = undefined; touchY = undefined; image.touchesStart.x = undefined; image.touchesStart.y = undefined; } const maxRatio = getMaxRatio(); zoom.scale = forceZoomRatio || maxRatio; currentScale = forceZoomRatio || maxRatio; if (e && !(currentScale === 1 && forceZoomRatio)) { slideWidth = gesture.slideEl.offsetWidth; slideHeight = gesture.slideEl.offsetHeight; offsetX = elementOffset(gesture.slideEl).left + window.scrollX; offsetY = elementOffset(gesture.slideEl).top + window.scrollY; diffX = offsetX + slideWidth / 2 - touchX; diffY = offsetY + slideHeight / 2 - touchY; imageWidth = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth; imageHeight = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight; scaledWidth = imageWidth * zoom.scale; scaledHeight = imageHeight * zoom.scale; translateMinX = Math.min(slideWidth / 2 - scaledWidth / 2, 0); translateMinY = Math.min(slideHeight / 2 - scaledHeight / 2, 0); translateMaxX = -translateMinX; translateMaxY = -translateMinY; if (prevScale > 0 && forceZoomRatio && typeof image.currentX === 'number' && typeof image.currentY === 'number') { translateX = image.currentX * zoom.scale / prevScale; translateY = image.currentY * zoom.scale / prevScale; } else { translateX = diffX * zoom.scale; translateY = diffY * zoom.scale; } if (translateX < translateMinX) { translateX = translateMinX; } if (translateX > translateMaxX) { translateX = translateMaxX; } if (translateY < translateMinY) { translateY = translateMinY; } if (translateY > translateMaxY) { translateY = translateMaxY; } } else { translateX = 0; translateY = 0; } if (forceZoomRatio && zoom.scale === 1) { gesture.originX = 0; gesture.originY = 0; } image.currentX = translateX; image.currentY = translateY; gesture.imageWrapEl.style.transitionDuration = '300ms'; gesture.imageWrapEl.style.transform = `translate3d(${translateX}px, ${translateY}px,0)`; gesture.imageEl.style.transitionDuration = '300ms'; gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`; } function zoomOut() { const zoom = swiper.zoom; const params = swiper.params.zoom; if (!gesture.slideEl) { if (swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual) { gesture.slideEl = elementChildren(swiper.slidesEl, `.${swiper.params.slideActiveClass}`)[0]; } else { gesture.slideEl = swiper.slides[swiper.activeIndex]; } let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`); if (imageEl) { imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0]; } gesture.imageEl = imageEl; if (imageEl) { gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0]; } else { gesture.imageWrapEl = undefined; } } if (!gesture.imageEl || !gesture.imageWrapEl) return; gesture.maxRatio = getMaxRatio(); if (swiper.params.cssMode) { swiper.wrapperEl.style.overflow = ''; swiper.wrapperEl.style.touchAction = ''; } zoom.scale = 1; currentScale = 1; image.currentX = undefined; image.currentY = undefined; image.touchesStart.x = undefined; image.touchesStart.y = undefined; gesture.imageWrapEl.style.transitionDuration = '300ms'; gesture.imageWrapEl.style.transform = 'translate3d(0,0,0)'; gesture.imageEl.style.transitionDuration = '300ms'; gesture.imageEl.style.transform = 'translate3d(0,0,0) scale(1)'; gesture.slideEl.classList.remove(`${params.zoomedSlideClass}`); gesture.slideEl = undefined; gesture.originX = 0; gesture.originY = 0; if (swiper.params.zoom.panOnMouseMove) { mousePanStart = { x: 0, y: 0 }; if (isPanningWithMouse) { isPanningWithMouse = false; image.startX = 0; image.startY = 0; } } } // Toggle Zoom function zoomToggle(e) { const zoom = swiper.zoom; if (zoom.scale && zoom.scale !== 1) { // Zoom Out zoomOut(); } else { // Zoom In zoomIn(e); } } function getListeners() { const passiveListener = swiper.params.passiveListeners ? { passive: true, capture: false } : false; const activeListenerWithCapture = swiper.params.passiveListeners ? { passive: false, capture: true } : true; return { passiveListener, activeListenerWithCapture }; } // Attach/Detach Events function enable() { const zoom = swiper.zoom; if (zoom.enabled) return; zoom.enabled = true; const { passiveListener, activeListenerWithCapture } = getListeners(); // Scale image swiper.wrapperEl.addEventListener('pointerdown', onGestureStart, passiveListener); swiper.wrapperEl.addEventListener('pointermove', onGestureChange, activeListenerWithCapture); ['pointerup', 'pointercancel', 'pointerout'].forEach(eventName => { swiper.wrapperEl.addEventListener(eventName, onGestureEnd, passiveListener); }); // Move image swiper.wrapperEl.addEventListener('pointermove', onTouchMove, activeListenerWithCapture); } function disable() { const zoom = swiper.zoom; if (!zoom.enabled) return; zoom.enabled = false; const { passiveListener, activeListenerWithCapture } = getListeners(); // Scale image swiper.wrapperEl.removeEventListener('pointerdown', onGestureStart, passiveListener); swiper.wrapperEl.removeEventListener('pointermove', onGestureChange, activeListenerWithCapture); ['pointerup', 'pointercancel', 'pointerout'].forEach(eventName => { swiper.wrapperEl.removeEventListener(eventName, onGestureEnd, passiveListener); }); // Move image swiper.wrapperEl.removeEventListener('pointermove', onTouchMove, activeListenerWithCapture); } on('init', () => { if (swiper.params.zoom.enabled) { enable(); } }); on('destroy', () => { disable(); }); on('touchStart', (_s, e) => { if (!swiper.zoom.enabled) return; onTouchStart(e); }); on('touchEnd', (_s, e) => { if (!swiper.zoom.enabled) return; onTouchEnd(); }); on('doubleTap', (_s, e) => { if (!swiper.animating && swiper.params.zoom.enabled && swiper.zoom.enabled && swiper.params.zoom.toggle) { zoomToggle(e); } }); on('transitionEnd', () => { if (swiper.zoom.enabled && swiper.params.zoom.enabled) { onTransitionEnd(); } }); on('slideChange', () => { if (swiper.zoom.enabled && swiper.params.zoom.enabled && swiper.params.cssMode) { onTransitionEnd(); } }); Object.assign(swiper.zoom, { enable, disable, in: zoomIn, out: zoomOut, toggle: zoomToggle }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/controller.mjs /* eslint no-bitwise: ["error", { "allow": [">>"] }] */ function Controller({ swiper, extendParams, on }) { extendParams({ controller: { control: undefined, inverse: false, by: 'slide' // or 'container' } }); swiper.controller = { control: undefined }; function LinearSpline(x, y) { const binarySearch = function search() { let maxIndex; let minIndex; let guess; return (array, val) => { minIndex = -1; maxIndex = array.length; while (maxIndex - minIndex > 1) { guess = maxIndex + minIndex >> 1; if (array[guess] <= val) { minIndex = guess; } else { maxIndex = guess; } } return maxIndex; }; }(); this.x = x; this.y = y; this.lastIndex = x.length - 1; // Given an x value (x2), return the expected y2 value: // (x1,y1) is the known point before given value, // (x3,y3) is the known point after given value. let i1; let i3; this.interpolate = function interpolate(x2) { if (!x2) return 0; // Get the indexes of x1 and x3 (the array indexes before and after given x2): i3 = binarySearch(this.x, x2); i1 = i3 - 1; // We have our indexes i1 & i3, so we can calculate already: // y2 := ((x2−x1) × (y3−y1)) ÷ (x3−x1) + y1 return (x2 - this.x[i1]) * (this.y[i3] - this.y[i1]) / (this.x[i3] - this.x[i1]) + this.y[i1]; }; return this; } function getInterpolateFunction(c) { swiper.controller.spline = swiper.params.loop ? new LinearSpline(swiper.slidesGrid, c.slidesGrid) : new LinearSpline(swiper.snapGrid, c.snapGrid); } function setTranslate(_t, byController) { const controlled = swiper.controller.control; let multiplier; let controlledTranslate; const Swiper = swiper.constructor; function setControlledTranslate(c) { if (c.destroyed) return; // this will create an Interpolate function based on the snapGrids // x is the Grid of the scrolled scroller and y will be the controlled scroller // it makes sense to create this only once and recall it for the interpolation // the function does a lot of value caching for performance const translate = swiper.rtlTranslate ? -swiper.translate : swiper.translate; if (swiper.params.controller.by === 'slide') { getInterpolateFunction(c); // i am not sure why the values have to be multiplicated this way, tried to invert the snapGrid // but it did not work out controlledTranslate = -swiper.controller.spline.interpolate(-translate); } if (!controlledTranslate || swiper.params.controller.by === 'container') { multiplier = (c.maxTranslate() - c.minTranslate()) / (swiper.maxTranslate() - swiper.minTranslate()); if (Number.isNaN(multiplier) || !Number.isFinite(multiplier)) { multiplier = 1; } controlledTranslate = (translate - swiper.minTranslate()) * multiplier + c.minTranslate(); } if (swiper.params.controller.inverse) { controlledTranslate = c.maxTranslate() - controlledTranslate; } c.updateProgress(controlledTranslate); c.setTranslate(controlledTranslate, swiper); c.updateActiveIndex(); c.updateSlidesClasses(); } if (Array.isArray(controlled)) { for (let i = 0; i < controlled.length; i += 1) { if (controlled[i] !== byController && controlled[i] instanceof Swiper) { setControlledTranslate(controlled[i]); } } } else if (controlled instanceof Swiper && byController !== controlled) { setControlledTranslate(controlled); } } function setTransition(duration, byController) { const Swiper = swiper.constructor; const controlled = swiper.controller.control; let i; function setControlledTransition(c) { if (c.destroyed) return; c.setTransition(duration, swiper); if (duration !== 0) { c.transitionStart(); if (c.params.autoHeight) { nextTick(() => { c.updateAutoHeight(); }); } elementTransitionEnd(c.wrapperEl, () => { if (!controlled) return; c.transitionEnd(); }); } } if (Array.isArray(controlled)) { for (i = 0; i < controlled.length; i += 1) { if (controlled[i] !== byController && controlled[i] instanceof Swiper) { setControlledTransition(controlled[i]); } } } else if (controlled instanceof Swiper && byController !== controlled) { setControlledTransition(controlled); } } function removeSpline() { if (!swiper.controller.control) return; if (swiper.controller.spline) { swiper.controller.spline = undefined; delete swiper.controller.spline; } } on('beforeInit', () => { if (typeof window !== 'undefined' && ( // eslint-disable-line typeof swiper.params.controller.control === 'string' || swiper.params.controller.control instanceof HTMLElement)) { const controlElements = typeof swiper.params.controller.control === 'string' ? [...document.querySelectorAll(swiper.params.controller.control)] : [swiper.params.controller.control]; controlElements.forEach(controlElement => { if (!swiper.controller.control) swiper.controller.control = []; if (controlElement && controlElement.swiper) { swiper.controller.control.push(controlElement.swiper); } else if (controlElement) { const eventName = `${swiper.params.eventsPrefix}init`; const onControllerSwiper = e => { swiper.controller.control.push(e.detail[0]); swiper.update(); controlElement.removeEventListener(eventName, onControllerSwiper); }; controlElement.addEventListener(eventName, onControllerSwiper); } }); return; } swiper.controller.control = swiper.params.controller.control; }); on('update', () => { removeSpline(); }); on('resize', () => { removeSpline(); }); on('observerUpdate', () => { removeSpline(); }); on('setTranslate', (_s, translate, byController) => { if (!swiper.controller.control || swiper.controller.control.destroyed) return; swiper.controller.setTranslate(translate, byController); }); on('setTransition', (_s, duration, byController) => { if (!swiper.controller.control || swiper.controller.control.destroyed) return; swiper.controller.setTransition(duration, byController); }); Object.assign(swiper.controller, { setTranslate, setTransition }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/a11y.mjs function A11y({ swiper, extendParams, on }) { extendParams({ a11y: { enabled: true, notificationClass: 'swiper-notification', prevSlideMessage: 'Previous slide', nextSlideMessage: 'Next slide', firstSlideMessage: 'This is the first slide', lastSlideMessage: 'This is the last slide', paginationBulletMessage: 'Go to slide {{index}}', slideLabelMessage: '{{index}} / {{slidesLength}}', containerMessage: null, containerRoleDescriptionMessage: null, containerRole: null, itemRoleDescriptionMessage: null, slideRole: 'group', id: null, scrollOnFocus: true, wrapperLiveRegion: true } }); swiper.a11y = { clicked: false }; let liveRegion = null; let preventFocusHandler; let focusTargetSlideEl; let visibilityChangedTimestamp = new Date().getTime(); function notify(message) { const notification = liveRegion; if (notification.length === 0) return; setInnerHTML(notification, message); } function getRandomNumber(size = 16) { const randomChar = () => Math.round(16 * Math.random()).toString(16); return 'x'.repeat(size).replace(/x/g, randomChar); } function makeElFocusable(el) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('tabIndex', '0'); }); } function makeElNotFocusable(el) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('tabIndex', '-1'); }); } function addElRole(el, role) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('role', role); }); } function addElRoleDescription(el, description) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('aria-roledescription', description); }); } function addElControls(el, controls) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('aria-controls', controls); }); } function addElLabel(el, label) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('aria-label', label); }); } function addElId(el, id) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('id', id); }); } function addElLive(el, live) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('aria-live', live); }); } function disableEl(el) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('aria-disabled', true); }); } function enableEl(el) { el = makeElementsArray(el); el.forEach(subEl => { subEl.setAttribute('aria-disabled', false); }); } function onEnterOrSpaceKey(e) { if (e.keyCode !== 13 && e.keyCode !== 32) return; const params = swiper.params.a11y; const targetEl = e.target; if (swiper.pagination && swiper.pagination.el && (targetEl === swiper.pagination.el || swiper.pagination.el.contains(e.target))) { if (!e.target.matches(classesToSelector(swiper.params.pagination.bulletClass))) return; } if (swiper.navigation && swiper.navigation.prevEl && swiper.navigation.nextEl) { const prevEls = makeElementsArray(swiper.navigation.prevEl); const nextEls = makeElementsArray(swiper.navigation.nextEl); if (nextEls.includes(targetEl)) { if (!(swiper.isEnd && !swiper.params.loop)) { swiper.slideNext(); } if (swiper.isEnd) { notify(params.lastSlideMessage); } else { notify(params.nextSlideMessage); } } if (prevEls.includes(targetEl)) { if (!(swiper.isBeginning && !swiper.params.loop)) { swiper.slidePrev(); } if (swiper.isBeginning) { notify(params.firstSlideMessage); } else { notify(params.prevSlideMessage); } } } if (swiper.pagination && targetEl.matches(classesToSelector(swiper.params.pagination.bulletClass))) { targetEl.click(); } } function updateNavigation() { if (swiper.params.loop || swiper.params.rewind || !swiper.navigation) return; const { nextEl, prevEl } = swiper.navigation; if (prevEl) { if (swiper.isBeginning) { disableEl(prevEl); makeElNotFocusable(prevEl); } else { enableEl(prevEl); makeElFocusable(prevEl); } } if (nextEl) { if (swiper.isEnd) { disableEl(nextEl); makeElNotFocusable(nextEl); } else { enableEl(nextEl); makeElFocusable(nextEl); } } } function hasPagination() { return swiper.pagination && swiper.pagination.bullets && swiper.pagination.bullets.length; } function hasClickablePagination() { return hasPagination() && swiper.params.pagination.clickable; } function updatePagination() { const params = swiper.params.a11y; if (!hasPagination()) return; swiper.pagination.bullets.forEach(bulletEl => { if (swiper.params.pagination.clickable) { makeElFocusable(bulletEl); if (!swiper.params.pagination.renderBullet) { addElRole(bulletEl, 'button'); addElLabel(bulletEl, params.paginationBulletMessage.replace(/\{\{index\}\}/, elementIndex(bulletEl) + 1)); } } if (bulletEl.matches(classesToSelector(swiper.params.pagination.bulletActiveClass))) { bulletEl.setAttribute('aria-current', 'true'); } else { bulletEl.removeAttribute('aria-current'); } }); } const initNavEl = (el, wrapperId, message) => { makeElFocusable(el); if (el.tagName !== 'BUTTON') { addElRole(el, 'button'); el.addEventListener('keydown', onEnterOrSpaceKey); } addElLabel(el, message); addElControls(el, wrapperId); }; const handlePointerDown = e => { if (focusTargetSlideEl && focusTargetSlideEl !== e.target && !focusTargetSlideEl.contains(e.target)) { preventFocusHandler = true; } swiper.a11y.clicked = true; }; const handlePointerUp = () => { preventFocusHandler = false; requestAnimationFrame(() => { requestAnimationFrame(() => { if (!swiper.destroyed) { swiper.a11y.clicked = false; } }); }); }; const onVisibilityChange = e => { visibilityChangedTimestamp = new Date().getTime(); }; const handleFocus = e => { if (swiper.a11y.clicked || !swiper.params.a11y.scrollOnFocus) return; if (new Date().getTime() - visibilityChangedTimestamp < 100) return; const slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`); if (!slideEl || !swiper.slides.includes(slideEl)) return; focusTargetSlideEl = slideEl; const isVirtual = swiper.virtual && swiper.params.virtual.enabled; const isActive = (isVirtual ? parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10) : swiper.slides.indexOf(slideEl)) === swiper.activeIndex; const isVisible = swiper.params.watchSlidesProgress && swiper.visibleSlides && swiper.visibleSlides.includes(slideEl); if (isActive || isVisible) return; if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents) return; if (swiper.isHorizontal()) { swiper.el.scrollLeft = 0; } else { swiper.el.scrollTop = 0; } requestAnimationFrame(() => { if (preventFocusHandler) return; if (swiper.params.loop) { swiper.slideToLoop(swiper.getSlideIndexWhenGrid(parseInt(slideEl.getAttribute('data-swiper-slide-index'))), 0); } else if (isVirtual) { swiper.slideTo(swiper.getSlideIndexWhenGrid(parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10)), 0); } else { swiper.slideTo(swiper.getSlideIndexWhenGrid(swiper.slides.indexOf(slideEl)), 0); } preventFocusHandler = false; }); }; const initSlides = () => { const params = swiper.params.a11y; if (params.itemRoleDescriptionMessage) { addElRoleDescription(swiper.slides, params.itemRoleDescriptionMessage); } if (params.slideRole) { addElRole(swiper.slides, params.slideRole); } const slidesLength = swiper.slides.length; if (params.slideLabelMessage) { swiper.slides.forEach((slideEl, index) => { const slideIndex = swiper.params.loop ? parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10) : index; const ariaLabelMessage = params.slideLabelMessage.replace(/\{\{index\}\}/, slideIndex + 1).replace(/\{\{slidesLength\}\}/, slidesLength); addElLabel(slideEl, ariaLabelMessage); }); } }; const init = () => { const params = swiper.params.a11y; swiper.el.append(liveRegion); // Container const containerEl = swiper.el; if (params.containerRoleDescriptionMessage) { addElRoleDescription(containerEl, params.containerRoleDescriptionMessage); } if (params.containerMessage) { addElLabel(containerEl, params.containerMessage); } if (params.containerRole) { addElRole(containerEl, params.containerRole); } // Wrapper const wrapperEl = swiper.wrapperEl; const wrapperId = params.id || wrapperEl.getAttribute('id') || `swiper-wrapper-${getRandomNumber(16)}`; addElId(wrapperEl, wrapperId); if (params.wrapperLiveRegion) { const live = swiper.params.autoplay && swiper.params.autoplay.enabled ? 'off' : 'polite'; addElLive(wrapperEl, live); } // Slide initSlides(); // Navigation let { nextEl, prevEl } = swiper.navigation ? swiper.navigation : {}; nextEl = makeElementsArray(nextEl); prevEl = makeElementsArray(prevEl); if (nextEl) { nextEl.forEach(el => initNavEl(el, wrapperId, params.nextSlideMessage)); } if (prevEl) { prevEl.forEach(el => initNavEl(el, wrapperId, params.prevSlideMessage)); } // Pagination if (hasClickablePagination()) { const paginationEl = makeElementsArray(swiper.pagination.el); paginationEl.forEach(el => { el.addEventListener('keydown', onEnterOrSpaceKey); }); } // Tab focus const document = getDocument(); document.addEventListener('visibilitychange', onVisibilityChange); swiper.el.addEventListener('focus', handleFocus, true); swiper.el.addEventListener('pointerdown', handlePointerDown, true); swiper.el.addEventListener('pointerup', handlePointerUp, true); }; function destroy() { if (liveRegion) liveRegion.remove(); let { nextEl, prevEl } = swiper.navigation ? swiper.navigation : {}; nextEl = makeElementsArray(nextEl); prevEl = makeElementsArray(prevEl); if (nextEl) { nextEl.forEach(el => el.removeEventListener('keydown', onEnterOrSpaceKey)); } if (prevEl) { prevEl.forEach(el => el.removeEventListener('keydown', onEnterOrSpaceKey)); } // Pagination if (hasClickablePagination()) { const paginationEl = makeElementsArray(swiper.pagination.el); paginationEl.forEach(el => { el.removeEventListener('keydown', onEnterOrSpaceKey); }); } const document = getDocument(); document.removeEventListener('visibilitychange', onVisibilityChange); // Tab focus if (swiper.el && typeof swiper.el !== 'string') { swiper.el.removeEventListener('focus', handleFocus, true); swiper.el.removeEventListener('pointerdown', handlePointerDown, true); swiper.el.removeEventListener('pointerup', handlePointerUp, true); } } on('beforeInit', () => { liveRegion = createElement('span', swiper.params.a11y.notificationClass); liveRegion.setAttribute('aria-live', 'assertive'); liveRegion.setAttribute('aria-atomic', 'true'); }); on('afterInit', () => { if (!swiper.params.a11y.enabled) return; init(); }); on('slidesLengthChange snapGridLengthChange slidesGridLengthChange', () => { if (!swiper.params.a11y.enabled) return; initSlides(); }); on('fromEdge toEdge afterInit lock unlock', () => { if (!swiper.params.a11y.enabled) return; updateNavigation(); }); on('paginationUpdate', () => { if (!swiper.params.a11y.enabled) return; updatePagination(); }); on('destroy', () => { if (!swiper.params.a11y.enabled) return; destroy(); }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/history.mjs function History({ swiper, extendParams, on }) { extendParams({ history: { enabled: false, root: '', replaceState: false, key: 'slides', keepQuery: false } }); let initialized = false; let paths = {}; const slugify = text => { return text.toString().replace(/\s+/g, '-').replace(/[^\w-]+/g, '').replace(/--+/g, '-').replace(/^-+/, '').replace(/-+$/, ''); }; const getPathValues = urlOverride => { const window = getWindow(); let location; if (urlOverride) { location = new URL(urlOverride); } else { location = window.location; } const pathArray = location.pathname.slice(1).split('/').filter(part => part !== ''); const total = pathArray.length; const key = pathArray[total - 2]; const value = pathArray[total - 1]; return { key, value }; }; const setHistory = (key, index) => { const window = getWindow(); if (!initialized || !swiper.params.history.enabled) return; let location; if (swiper.params.url) { location = new URL(swiper.params.url); } else { location = window.location; } const slide = swiper.virtual && swiper.params.virtual.enabled ? swiper.slidesEl.querySelector(`[data-swiper-slide-index="${index}"]`) : swiper.slides[index]; let value = slugify(slide.getAttribute('data-history')); if (swiper.params.history.root.length > 0) { let root = swiper.params.history.root; if (root[root.length - 1] === '/') root = root.slice(0, root.length - 1); value = `${root}/${key ? `${key}/` : ''}${value}`; } else if (!location.pathname.includes(key)) { value = `${key ? `${key}/` : ''}${value}`; } if (swiper.params.history.keepQuery) { value += location.search; } const currentState = window.history.state; if (currentState && currentState.value === value) { return; } if (swiper.params.history.replaceState) { window.history.replaceState({ value }, null, value); } else { window.history.pushState({ value }, null, value); } }; const scrollToSlide = (speed, value, runCallbacks) => { if (value) { for (let i = 0, length = swiper.slides.length; i < length; i += 1) { const slide = swiper.slides[i]; const slideHistory = slugify(slide.getAttribute('data-history')); if (slideHistory === value) { const index = swiper.getSlideIndex(slide); swiper.slideTo(index, speed, runCallbacks); } } } else { swiper.slideTo(0, speed, runCallbacks); } }; const setHistoryPopState = () => { paths = getPathValues(swiper.params.url); scrollToSlide(swiper.params.speed, paths.value, false); }; const init = () => { const window = getWindow(); if (!swiper.params.history) return; if (!window.history || !window.history.pushState) { swiper.params.history.enabled = false; swiper.params.hashNavigation.enabled = true; return; } initialized = true; paths = getPathValues(swiper.params.url); if (!paths.key && !paths.value) { if (!swiper.params.history.replaceState) { window.addEventListener('popstate', setHistoryPopState); } return; } scrollToSlide(0, paths.value, swiper.params.runCallbacksOnInit); if (!swiper.params.history.replaceState) { window.addEventListener('popstate', setHistoryPopState); } }; const destroy = () => { const window = getWindow(); if (!swiper.params.history.replaceState) { window.removeEventListener('popstate', setHistoryPopState); } }; on('init', () => { if (swiper.params.history.enabled) { init(); } }); on('destroy', () => { if (swiper.params.history.enabled) { destroy(); } }); on('transitionEnd _freeModeNoMomentumRelease', () => { if (initialized) { setHistory(swiper.params.history.key, swiper.activeIndex); } }); on('slideChange', () => { if (initialized && swiper.params.cssMode) { setHistory(swiper.params.history.key, swiper.activeIndex); } }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/hash-navigation.mjs function HashNavigation({ swiper, extendParams, emit, on }) { let initialized = false; const document = getDocument(); const window = getWindow(); extendParams({ hashNavigation: { enabled: false, replaceState: false, watchState: false, getSlideIndex(_s, hash) { if (swiper.virtual && swiper.params.virtual.enabled) { const slideWithHash = swiper.slides.find(slideEl => slideEl.getAttribute('data-hash') === hash); if (!slideWithHash) return 0; const index = parseInt(slideWithHash.getAttribute('data-swiper-slide-index'), 10); return index; } return swiper.getSlideIndex(elementChildren(swiper.slidesEl, `.${swiper.params.slideClass}[data-hash="${hash}"], swiper-slide[data-hash="${hash}"]`)[0]); } } }); const onHashChange = () => { emit('hashChange'); const newHash = document.location.hash.replace('#', ''); const activeSlideEl = swiper.virtual && swiper.params.virtual.enabled ? swiper.slidesEl.querySelector(`[data-swiper-slide-index="${swiper.activeIndex}"]`) : swiper.slides[swiper.activeIndex]; const activeSlideHash = activeSlideEl ? activeSlideEl.getAttribute('data-hash') : ''; if (newHash !== activeSlideHash) { const newIndex = swiper.params.hashNavigation.getSlideIndex(swiper, newHash); if (typeof newIndex === 'undefined' || Number.isNaN(newIndex)) return; swiper.slideTo(newIndex); } }; const setHash = () => { if (!initialized || !swiper.params.hashNavigation.enabled) return; const activeSlideEl = swiper.virtual && swiper.params.virtual.enabled ? swiper.slidesEl.querySelector(`[data-swiper-slide-index="${swiper.activeIndex}"]`) : swiper.slides[swiper.activeIndex]; const activeSlideHash = activeSlideEl ? activeSlideEl.getAttribute('data-hash') || activeSlideEl.getAttribute('data-history') : ''; if (swiper.params.hashNavigation.replaceState && window.history && window.history.replaceState) { window.history.replaceState(null, null, `#${activeSlideHash}` || ''); emit('hashSet'); } else { document.location.hash = activeSlideHash || ''; emit('hashSet'); } }; const init = () => { if (!swiper.params.hashNavigation.enabled || swiper.params.history && swiper.params.history.enabled) return; initialized = true; const hash = document.location.hash.replace('#', ''); if (hash) { const speed = 0; const index = swiper.params.hashNavigation.getSlideIndex(swiper, hash); swiper.slideTo(index || 0, speed, swiper.params.runCallbacksOnInit, true); } if (swiper.params.hashNavigation.watchState) { window.addEventListener('hashchange', onHashChange); } }; const destroy = () => { if (swiper.params.hashNavigation.watchState) { window.removeEventListener('hashchange', onHashChange); } }; on('init', () => { if (swiper.params.hashNavigation.enabled) { init(); } }); on('destroy', () => { if (swiper.params.hashNavigation.enabled) { destroy(); } }); on('transitionEnd _freeModeNoMomentumRelease', () => { if (initialized) { setHash(); } }); on('slideChange', () => { if (initialized && swiper.params.cssMode) { setHash(); } }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/autoplay.mjs /* eslint no-underscore-dangle: "off" */ /* eslint no-use-before-define: "off" */ function Autoplay({ swiper, extendParams, on, emit, params }) { swiper.autoplay = { running: false, paused: false, timeLeft: 0 }; extendParams({ autoplay: { enabled: false, delay: 3000, waitForTransition: true, disableOnInteraction: false, stopOnLastSlide: false, reverseDirection: false, pauseOnMouseEnter: false } }); let timeout; let raf; let autoplayDelayTotal = params && params.autoplay ? params.autoplay.delay : 3000; let autoplayDelayCurrent = params && params.autoplay ? params.autoplay.delay : 3000; let autoplayTimeLeft; let autoplayStartTime = new Date().getTime(); let wasPaused; let isTouched; let pausedByTouch; let touchStartTimeout; let pausedByInteraction; let pausedByPointerEnter; function onTransitionEnd(e) { if (!swiper || swiper.destroyed || !swiper.wrapperEl) return; if (e.target !== swiper.wrapperEl) return; swiper.wrapperEl.removeEventListener('transitionend', onTransitionEnd); if (pausedByPointerEnter || e.detail && e.detail.bySwiperTouchMove) { return; } resume(); } const calcTimeLeft = () => { if (swiper.destroyed || !swiper.autoplay.running) return; if (swiper.autoplay.paused) { wasPaused = true; } else if (wasPaused) { autoplayDelayCurrent = autoplayTimeLeft; wasPaused = false; } const timeLeft = swiper.autoplay.paused ? autoplayTimeLeft : autoplayStartTime + autoplayDelayCurrent - new Date().getTime(); swiper.autoplay.timeLeft = timeLeft; emit('autoplayTimeLeft', timeLeft, timeLeft / autoplayDelayTotal); raf = requestAnimationFrame(() => { calcTimeLeft(); }); }; const getSlideDelay = () => { let activeSlideEl; if (swiper.virtual && swiper.params.virtual.enabled) { activeSlideEl = swiper.slides.find(slideEl => slideEl.classList.contains('swiper-slide-active')); } else { activeSlideEl = swiper.slides[swiper.activeIndex]; } if (!activeSlideEl) return undefined; const currentSlideDelay = parseInt(activeSlideEl.getAttribute('data-swiper-autoplay'), 10); return currentSlideDelay; }; const getTotalDelay = () => { let totalDelay = swiper.params.autoplay.delay; const currentSlideDelay = getSlideDelay(); if (!Number.isNaN(currentSlideDelay) && currentSlideDelay > 0) { totalDelay = currentSlideDelay; } return totalDelay; }; const run = delayForce => { if (swiper.destroyed || !swiper.autoplay.running) return; cancelAnimationFrame(raf); calcTimeLeft(); let delay = delayForce; if (typeof delay === 'undefined') { delay = getTotalDelay(); autoplayDelayTotal = delay; autoplayDelayCurrent = delay; } autoplayTimeLeft = delay; const speed = swiper.params.speed; const proceed = () => { if (!swiper || swiper.destroyed) return; if (swiper.params.autoplay.reverseDirection) { if (!swiper.isBeginning || swiper.params.loop || swiper.params.rewind) { swiper.slidePrev(speed, true, true); emit('autoplay'); } else if (!swiper.params.autoplay.stopOnLastSlide) { swiper.slideTo(swiper.slides.length - 1, speed, true, true); emit('autoplay'); } } else { if (!swiper.isEnd || swiper.params.loop || swiper.params.rewind) { swiper.slideNext(speed, true, true); emit('autoplay'); } else if (!swiper.params.autoplay.stopOnLastSlide) { swiper.slideTo(0, speed, true, true); emit('autoplay'); } } if (swiper.params.cssMode) { autoplayStartTime = new Date().getTime(); requestAnimationFrame(() => { run(); }); } }; if (delay > 0) { clearTimeout(timeout); timeout = setTimeout(() => { proceed(); }, delay); } else { requestAnimationFrame(() => { proceed(); }); } // eslint-disable-next-line return delay; }; const start = () => { autoplayStartTime = new Date().getTime(); swiper.autoplay.running = true; run(); emit('autoplayStart'); }; const stop = () => { swiper.autoplay.running = false; clearTimeout(timeout); cancelAnimationFrame(raf); emit('autoplayStop'); }; const pause = (internal, reset) => { if (swiper.destroyed || !swiper.autoplay.running) return; clearTimeout(timeout); if (!internal) { pausedByInteraction = true; } const proceed = () => { emit('autoplayPause'); if (swiper.params.autoplay.waitForTransition) { swiper.wrapperEl.addEventListener('transitionend', onTransitionEnd); } else { resume(); } }; swiper.autoplay.paused = true; if (reset) { proceed(); return; } const delay = autoplayTimeLeft || swiper.params.autoplay.delay; autoplayTimeLeft = delay - (new Date().getTime() - autoplayStartTime); if (swiper.isEnd && autoplayTimeLeft < 0 && !swiper.params.loop) return; if (autoplayTimeLeft < 0) autoplayTimeLeft = 0; proceed(); }; const resume = () => { if (swiper.isEnd && autoplayTimeLeft < 0 && !swiper.params.loop || swiper.destroyed || !swiper.autoplay.running) return; autoplayStartTime = new Date().getTime(); if (pausedByInteraction) { pausedByInteraction = false; run(autoplayTimeLeft); } else { run(); } swiper.autoplay.paused = false; emit('autoplayResume'); }; const onVisibilityChange = () => { if (swiper.destroyed || !swiper.autoplay.running) return; const document = getDocument(); if (document.visibilityState === 'hidden') { pausedByInteraction = true; pause(true); } if (document.visibilityState === 'visible') { resume(); } }; const onPointerEnter = e => { if (e.pointerType !== 'mouse') return; pausedByInteraction = true; pausedByPointerEnter = true; if (swiper.animating || swiper.autoplay.paused) return; pause(true); }; const onPointerLeave = e => { if (e.pointerType !== 'mouse') return; pausedByPointerEnter = false; if (swiper.autoplay.paused) { resume(); } }; const attachMouseEvents = () => { if (swiper.params.autoplay.pauseOnMouseEnter) { swiper.el.addEventListener('pointerenter', onPointerEnter); swiper.el.addEventListener('pointerleave', onPointerLeave); } }; const detachMouseEvents = () => { if (swiper.el && typeof swiper.el !== 'string') { swiper.el.removeEventListener('pointerenter', onPointerEnter); swiper.el.removeEventListener('pointerleave', onPointerLeave); } }; const attachDocumentEvents = () => { const document = getDocument(); document.addEventListener('visibilitychange', onVisibilityChange); }; const detachDocumentEvents = () => { const document = getDocument(); document.removeEventListener('visibilitychange', onVisibilityChange); }; on('init', () => { if (swiper.params.autoplay.enabled) { attachMouseEvents(); attachDocumentEvents(); start(); } }); on('destroy', () => { detachMouseEvents(); detachDocumentEvents(); if (swiper.autoplay.running) { stop(); } }); on('_freeModeStaticRelease', () => { if (pausedByTouch || pausedByInteraction) { resume(); } }); on('_freeModeNoMomentumRelease', () => { if (!swiper.params.autoplay.disableOnInteraction) { pause(true, true); } else { stop(); } }); on('beforeTransitionStart', (_s, speed, internal) => { if (swiper.destroyed || !swiper.autoplay.running) return; if (internal || !swiper.params.autoplay.disableOnInteraction) { pause(true, true); } else { stop(); } }); on('sliderFirstMove', () => { if (swiper.destroyed || !swiper.autoplay.running) return; if (swiper.params.autoplay.disableOnInteraction) { stop(); return; } isTouched = true; pausedByTouch = false; pausedByInteraction = false; touchStartTimeout = setTimeout(() => { pausedByInteraction = true; pausedByTouch = true; pause(true); }, 200); }); on('touchEnd', () => { if (swiper.destroyed || !swiper.autoplay.running || !isTouched) return; clearTimeout(touchStartTimeout); clearTimeout(timeout); if (swiper.params.autoplay.disableOnInteraction) { pausedByTouch = false; isTouched = false; return; } if (pausedByTouch && swiper.params.cssMode) resume(); pausedByTouch = false; isTouched = false; }); on('slideChange', () => { if (swiper.destroyed || !swiper.autoplay.running) return; if (swiper.autoplay.paused) { autoplayTimeLeft = getTotalDelay(); autoplayDelayTotal = getTotalDelay(); } }); Object.assign(swiper.autoplay, { start, stop, pause, resume }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/thumbs.mjs function Thumb({ swiper, extendParams, on }) { extendParams({ thumbs: { swiper: null, multipleActiveThumbs: true, autoScrollOffset: 0, slideThumbActiveClass: 'swiper-slide-thumb-active', thumbsContainerClass: 'swiper-thumbs' } }); let initialized = false; let swiperCreated = false; swiper.thumbs = { swiper: null }; function isVirtualEnabled() { const thumbsSwiper = swiper.thumbs.swiper; if (!thumbsSwiper || thumbsSwiper.destroyed) return false; return thumbsSwiper.params.virtual && thumbsSwiper.params.virtual.enabled; } function onThumbClick() { const thumbsSwiper = swiper.thumbs.swiper; if (!thumbsSwiper || thumbsSwiper.destroyed) return; const clickedIndex = thumbsSwiper.clickedIndex; const clickedSlide = thumbsSwiper.clickedSlide; if (clickedSlide && clickedSlide.classList.contains(swiper.params.thumbs.slideThumbActiveClass)) return; if (typeof clickedIndex === 'undefined' || clickedIndex === null) return; let slideToIndex; if (thumbsSwiper.params.loop) { slideToIndex = parseInt(thumbsSwiper.clickedSlide.getAttribute('data-swiper-slide-index'), 10); } else { slideToIndex = clickedIndex; } if (swiper.params.loop) { swiper.slideToLoop(slideToIndex); } else { swiper.slideTo(slideToIndex); } } function init() { const { thumbs: thumbsParams } = swiper.params; if (initialized) return false; initialized = true; const SwiperClass = swiper.constructor; if (thumbsParams.swiper instanceof SwiperClass) { if (thumbsParams.swiper.destroyed) { initialized = false; return false; } swiper.thumbs.swiper = thumbsParams.swiper; Object.assign(swiper.thumbs.swiper.originalParams, { watchSlidesProgress: true, slideToClickedSlide: false }); Object.assign(swiper.thumbs.swiper.params, { watchSlidesProgress: true, slideToClickedSlide: false }); swiper.thumbs.swiper.update(); } else if (isObject(thumbsParams.swiper)) { const thumbsSwiperParams = Object.assign({}, thumbsParams.swiper); Object.assign(thumbsSwiperParams, { watchSlidesProgress: true, slideToClickedSlide: false }); swiper.thumbs.swiper = new SwiperClass(thumbsSwiperParams); swiperCreated = true; } swiper.thumbs.swiper.el.classList.add(swiper.params.thumbs.thumbsContainerClass); swiper.thumbs.swiper.on('tap', onThumbClick); if (isVirtualEnabled()) { swiper.thumbs.swiper.on('virtualUpdate', () => { update(false, { autoScroll: false }); }); } return true; } function update(initial, p) { const thumbsSwiper = swiper.thumbs.swiper; if (!thumbsSwiper || thumbsSwiper.destroyed) return; // Activate thumbs let thumbsToActivate = 1; const thumbActiveClass = swiper.params.thumbs.slideThumbActiveClass; if (swiper.params.slidesPerView > 1 && !swiper.params.centeredSlides) { thumbsToActivate = swiper.params.slidesPerView; } if (!swiper.params.thumbs.multipleActiveThumbs) { thumbsToActivate = 1; } thumbsToActivate = Math.floor(thumbsToActivate); thumbsSwiper.slides.forEach(slideEl => slideEl.classList.remove(thumbActiveClass)); if (thumbsSwiper.params.loop || isVirtualEnabled()) { for (let i = 0; i < thumbsToActivate; i += 1) { elementChildren(thumbsSwiper.slidesEl, `[data-swiper-slide-index="${swiper.realIndex + i}"]`).forEach(slideEl => { slideEl.classList.add(thumbActiveClass); }); } } else { for (let i = 0; i < thumbsToActivate; i += 1) { if (thumbsSwiper.slides[swiper.realIndex + i]) { thumbsSwiper.slides[swiper.realIndex + i].classList.add(thumbActiveClass); } } } if (p?.autoScroll ?? true) { autoScroll(initial ? 0 : undefined); } } function autoScroll(slideSpeed) { const thumbsSwiper = swiper.thumbs.swiper; if (!thumbsSwiper || thumbsSwiper.destroyed) return; const slidesPerView = thumbsSwiper.params.slidesPerView === 'auto' ? thumbsSwiper.slidesPerViewDynamic() : thumbsSwiper.params.slidesPerView; const autoScrollOffset = swiper.params.thumbs.autoScrollOffset; const useOffset = autoScrollOffset && !thumbsSwiper.params.loop; if (swiper.realIndex !== thumbsSwiper.realIndex || useOffset) { const currentThumbsIndex = thumbsSwiper.activeIndex; let newThumbsIndex; let direction; if (thumbsSwiper.params.loop) { const newThumbsSlide = thumbsSwiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') === `${swiper.realIndex}`); newThumbsIndex = thumbsSwiper.slides.indexOf(newThumbsSlide); direction = swiper.activeIndex > swiper.previousIndex ? 'next' : 'prev'; } else { newThumbsIndex = swiper.realIndex; direction = newThumbsIndex > swiper.previousIndex ? 'next' : 'prev'; } if (useOffset) { newThumbsIndex += direction === 'next' ? autoScrollOffset : -1 * autoScrollOffset; } if (thumbsSwiper.visibleSlidesIndexes && thumbsSwiper.visibleSlidesIndexes.indexOf(newThumbsIndex) < 0) { if (thumbsSwiper.params.centeredSlides) { if (newThumbsIndex > currentThumbsIndex) { newThumbsIndex = newThumbsIndex - Math.floor(slidesPerView / 2) + 1; } else { newThumbsIndex = newThumbsIndex + Math.floor(slidesPerView / 2) - 1; } } else if (newThumbsIndex > currentThumbsIndex && thumbsSwiper.params.slidesPerGroup === 1) ; thumbsSwiper.slideTo(newThumbsIndex, slideSpeed); } } } on('beforeInit', () => { const { thumbs } = swiper.params; if (!thumbs || !thumbs.swiper) return; if (typeof thumbs.swiper === 'string' || thumbs.swiper instanceof HTMLElement) { const document = getDocument(); const getThumbsElementAndInit = () => { const thumbsElement = typeof thumbs.swiper === 'string' ? document.querySelector(thumbs.swiper) : thumbs.swiper; if (thumbsElement && thumbsElement.swiper) { thumbs.swiper = thumbsElement.swiper; init(); update(true); } else if (thumbsElement) { const eventName = `${swiper.params.eventsPrefix}init`; const onThumbsSwiper = e => { thumbs.swiper = e.detail[0]; thumbsElement.removeEventListener(eventName, onThumbsSwiper); init(); update(true); thumbs.swiper.update(); swiper.update(); }; thumbsElement.addEventListener(eventName, onThumbsSwiper); } return thumbsElement; }; const watchForThumbsToAppear = () => { if (swiper.destroyed) return; const thumbsElement = getThumbsElementAndInit(); if (!thumbsElement) { requestAnimationFrame(watchForThumbsToAppear); } }; requestAnimationFrame(watchForThumbsToAppear); } else { init(); update(true); } }); on('slideChange update resize observerUpdate', () => { update(); }); on('setTransition', (_s, duration) => { const thumbsSwiper = swiper.thumbs.swiper; if (!thumbsSwiper || thumbsSwiper.destroyed) return; thumbsSwiper.setTransition(duration); }); on('beforeDestroy', () => { const thumbsSwiper = swiper.thumbs.swiper; if (!thumbsSwiper || thumbsSwiper.destroyed) return; if (swiperCreated) { thumbsSwiper.destroy(); } }); Object.assign(swiper.thumbs, { init, update }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/free-mode.mjs function freeMode({ swiper, extendParams, emit, once }) { extendParams({ freeMode: { enabled: false, momentum: true, momentumRatio: 1, momentumBounce: true, momentumBounceRatio: 1, momentumVelocityRatio: 1, sticky: false, minimumVelocity: 0.02 } }); function onTouchStart() { if (swiper.params.cssMode) return; const translate = swiper.getTranslate(); swiper.setTranslate(translate); swiper.setTransition(0); swiper.touchEventsData.velocities.length = 0; swiper.freeMode.onTouchEnd({ currentPos: swiper.rtl ? swiper.translate : -swiper.translate }); } function onTouchMove() { if (swiper.params.cssMode) return; const { touchEventsData: data, touches } = swiper; // Velocity if (data.velocities.length === 0) { data.velocities.push({ position: touches[swiper.isHorizontal() ? 'startX' : 'startY'], time: data.touchStartTime }); } data.velocities.push({ position: touches[swiper.isHorizontal() ? 'currentX' : 'currentY'], time: now() }); } function onTouchEnd({ currentPos }) { if (swiper.params.cssMode) return; const { params, wrapperEl, rtlTranslate: rtl, snapGrid, touchEventsData: data } = swiper; // Time diff const touchEndTime = now(); const timeDiff = touchEndTime - data.touchStartTime; if (currentPos < -swiper.minTranslate()) { swiper.slideTo(swiper.activeIndex); return; } if (currentPos > -swiper.maxTranslate()) { if (swiper.slides.length < snapGrid.length) { swiper.slideTo(snapGrid.length - 1); } else { swiper.slideTo(swiper.slides.length - 1); } return; } if (params.freeMode.momentum) { if (data.velocities.length > 1) { const lastMoveEvent = data.velocities.pop(); const velocityEvent = data.velocities.pop(); const distance = lastMoveEvent.position - velocityEvent.position; const time = lastMoveEvent.time - velocityEvent.time; swiper.velocity = distance / time; swiper.velocity /= 2; if (Math.abs(swiper.velocity) < params.freeMode.minimumVelocity) { swiper.velocity = 0; } // this implies that the user stopped moving a finger then released. // There would be no events with distance zero, so the last event is stale. if (time > 150 || now() - lastMoveEvent.time > 300) { swiper.velocity = 0; } } else { swiper.velocity = 0; } swiper.velocity *= params.freeMode.momentumVelocityRatio; data.velocities.length = 0; let momentumDuration = 1000 * params.freeMode.momentumRatio; const momentumDistance = swiper.velocity * momentumDuration; let newPosition = swiper.translate + momentumDistance; if (rtl) newPosition = -newPosition; let doBounce = false; let afterBouncePosition; const bounceAmount = Math.abs(swiper.velocity) * 20 * params.freeMode.momentumBounceRatio; let needsLoopFix; if (newPosition < swiper.maxTranslate()) { if (params.freeMode.momentumBounce) { if (newPosition + swiper.maxTranslate() < -bounceAmount) { newPosition = swiper.maxTranslate() - bounceAmount; } afterBouncePosition = swiper.maxTranslate(); doBounce = true; data.allowMomentumBounce = true; } else { newPosition = swiper.maxTranslate(); } if (params.loop && params.centeredSlides) needsLoopFix = true; } else if (newPosition > swiper.minTranslate()) { if (params.freeMode.momentumBounce) { if (newPosition - swiper.minTranslate() > bounceAmount) { newPosition = swiper.minTranslate() + bounceAmount; } afterBouncePosition = swiper.minTranslate(); doBounce = true; data.allowMomentumBounce = true; } else { newPosition = swiper.minTranslate(); } if (params.loop && params.centeredSlides) needsLoopFix = true; } else if (params.freeMode.sticky) { let nextSlide; for (let j = 0; j < snapGrid.length; j += 1) { if (snapGrid[j] > -newPosition) { nextSlide = j; break; } } if (Math.abs(snapGrid[nextSlide] - newPosition) < Math.abs(snapGrid[nextSlide - 1] - newPosition) || swiper.swipeDirection === 'next') { newPosition = snapGrid[nextSlide]; } else { newPosition = snapGrid[nextSlide - 1]; } newPosition = -newPosition; } if (needsLoopFix) { once('transitionEnd', () => { swiper.loopFix(); }); } // Fix duration if (swiper.velocity !== 0) { if (rtl) { momentumDuration = Math.abs((-newPosition - swiper.translate) / swiper.velocity); } else { momentumDuration = Math.abs((newPosition - swiper.translate) / swiper.velocity); } if (params.freeMode.sticky) { // If freeMode.sticky is active and the user ends a swipe with a slow-velocity // event, then durations can be 20+ seconds to slide one (or zero!) slides. // It's easy to see this when simulating touch with mouse events. To fix this, // limit single-slide swipes to the default slide duration. This also has the // nice side effect of matching slide speed if the user stopped moving before // lifting finger or mouse vs. moving slowly before lifting the finger/mouse. // For faster swipes, also apply limits (albeit higher ones). const moveDistance = Math.abs((rtl ? -newPosition : newPosition) - swiper.translate); const currentSlideSize = swiper.slidesSizesGrid[swiper.activeIndex]; if (moveDistance < currentSlideSize) { momentumDuration = params.speed; } else if (moveDistance < 2 * currentSlideSize) { momentumDuration = params.speed * 1.5; } else { momentumDuration = params.speed * 2.5; } } } else if (params.freeMode.sticky) { swiper.slideToClosest(); return; } if (params.freeMode.momentumBounce && doBounce) { swiper.updateProgress(afterBouncePosition); swiper.setTransition(momentumDuration); swiper.setTranslate(newPosition); swiper.transitionStart(true, swiper.swipeDirection); swiper.animating = true; elementTransitionEnd(wrapperEl, () => { if (!swiper || swiper.destroyed || !data.allowMomentumBounce) return; emit('momentumBounce'); swiper.setTransition(params.speed); setTimeout(() => { swiper.setTranslate(afterBouncePosition); elementTransitionEnd(wrapperEl, () => { if (!swiper || swiper.destroyed) return; swiper.transitionEnd(); }); }, 0); }); } else if (swiper.velocity) { emit('_freeModeNoMomentumRelease'); swiper.updateProgress(newPosition); swiper.setTransition(momentumDuration); swiper.setTranslate(newPosition); swiper.transitionStart(true, swiper.swipeDirection); if (!swiper.animating) { swiper.animating = true; elementTransitionEnd(wrapperEl, () => { if (!swiper || swiper.destroyed) return; swiper.transitionEnd(); }); } } else { swiper.updateProgress(newPosition); } swiper.updateActiveIndex(); swiper.updateSlidesClasses(); } else if (params.freeMode.sticky) { swiper.slideToClosest(); return; } else if (params.freeMode) { emit('_freeModeNoMomentumRelease'); } if (!params.freeMode.momentum || timeDiff >= params.longSwipesMs) { emit('_freeModeStaticRelease'); swiper.updateProgress(); swiper.updateActiveIndex(); swiper.updateSlidesClasses(); } } Object.assign(swiper, { freeMode: { onTouchStart, onTouchMove, onTouchEnd } }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/manipulation.mjs function appendSlide(slides) { const swiper = this; const { params, slidesEl } = swiper; if (params.loop) { swiper.loopDestroy(); } const appendElement = slideEl => { if (typeof slideEl === 'string') { const tempDOM = document.createElement('div'); setInnerHTML(tempDOM, slideEl); slidesEl.append(tempDOM.children[0]); setInnerHTML(tempDOM, ''); } else { slidesEl.append(slideEl); } }; if (typeof slides === 'object' && 'length' in slides) { for (let i = 0; i < slides.length; i += 1) { if (slides[i]) appendElement(slides[i]); } } else { appendElement(slides); } swiper.recalcSlides(); if (params.loop) { swiper.loopCreate(); } if (!params.observer || swiper.isElement) { swiper.update(); } } function prependSlide(slides) { const swiper = this; const { params, activeIndex, slidesEl } = swiper; if (params.loop) { swiper.loopDestroy(); } let newActiveIndex = activeIndex + 1; const prependElement = slideEl => { if (typeof slideEl === 'string') { const tempDOM = document.createElement('div'); setInnerHTML(tempDOM, slideEl); slidesEl.prepend(tempDOM.children[0]); setInnerHTML(tempDOM, ''); } else { slidesEl.prepend(slideEl); } }; if (typeof slides === 'object' && 'length' in slides) { for (let i = 0; i < slides.length; i += 1) { if (slides[i]) prependElement(slides[i]); } newActiveIndex = activeIndex + slides.length; } else { prependElement(slides); } swiper.recalcSlides(); if (params.loop) { swiper.loopCreate(); } if (!params.observer || swiper.isElement) { swiper.update(); } swiper.slideTo(newActiveIndex, 0, false); } function addSlide(index, slides) { const swiper = this; const { params, activeIndex, slidesEl } = swiper; let activeIndexBuffer = activeIndex; if (params.loop) { activeIndexBuffer -= swiper.loopedSlides; swiper.loopDestroy(); swiper.recalcSlides(); } const baseLength = swiper.slides.length; if (index <= 0) { swiper.prependSlide(slides); return; } if (index >= baseLength) { swiper.appendSlide(slides); return; } let newActiveIndex = activeIndexBuffer > index ? activeIndexBuffer + 1 : activeIndexBuffer; const slidesBuffer = []; for (let i = baseLength - 1; i >= index; i -= 1) { const currentSlide = swiper.slides[i]; currentSlide.remove(); slidesBuffer.unshift(currentSlide); } if (typeof slides === 'object' && 'length' in slides) { for (let i = 0; i < slides.length; i += 1) { if (slides[i]) slidesEl.append(slides[i]); } newActiveIndex = activeIndexBuffer > index ? activeIndexBuffer + slides.length : activeIndexBuffer; } else { slidesEl.append(slides); } for (let i = 0; i < slidesBuffer.length; i += 1) { slidesEl.append(slidesBuffer[i]); } swiper.recalcSlides(); if (params.loop) { swiper.loopCreate(); } if (!params.observer || swiper.isElement) { swiper.update(); } if (params.loop) { swiper.slideTo(newActiveIndex + swiper.loopedSlides, 0, false); } else { swiper.slideTo(newActiveIndex, 0, false); } } function removeSlide(slidesIndexes) { const swiper = this; const { params, activeIndex } = swiper; let activeIndexBuffer = activeIndex; if (params.loop) { activeIndexBuffer -= swiper.loopedSlides; swiper.loopDestroy(); } let newActiveIndex = activeIndexBuffer; let indexToRemove; if (typeof slidesIndexes === 'object' && 'length' in slidesIndexes) { for (let i = 0; i < slidesIndexes.length; i += 1) { indexToRemove = slidesIndexes[i]; if (swiper.slides[indexToRemove]) swiper.slides[indexToRemove].remove(); if (indexToRemove < newActiveIndex) newActiveIndex -= 1; } newActiveIndex = Math.max(newActiveIndex, 0); } else { indexToRemove = slidesIndexes; if (swiper.slides[indexToRemove]) swiper.slides[indexToRemove].remove(); if (indexToRemove < newActiveIndex) newActiveIndex -= 1; newActiveIndex = Math.max(newActiveIndex, 0); } swiper.recalcSlides(); if (params.loop) { swiper.loopCreate(); } if (!params.observer || swiper.isElement) { swiper.update(); } if (params.loop) { swiper.slideTo(newActiveIndex + swiper.loopedSlides, 0, false); } else { swiper.slideTo(newActiveIndex, 0, false); } } function removeAllSlides() { const swiper = this; const slidesIndexes = []; for (let i = 0; i < swiper.slides.length; i += 1) { slidesIndexes.push(i); } swiper.removeSlide(slidesIndexes); } function Manipulation({ swiper }) { Object.assign(swiper, { appendSlide: appendSlide.bind(swiper), prependSlide: prependSlide.bind(swiper), addSlide: addSlide.bind(swiper), removeSlide: removeSlide.bind(swiper), removeAllSlides: removeAllSlides.bind(swiper) }); } ;// CONCATENATED MODULE: ./node_modules/swiper/shared/effect-target.mjs function effect_target_effectTarget(effectParams, slideEl) { const transformEl = getSlideTransformEl(slideEl); if (transformEl !== slideEl) { transformEl.style.backfaceVisibility = 'hidden'; transformEl.style['-webkit-backface-visibility'] = 'hidden'; } return transformEl; } ;// CONCATENATED MODULE: ./node_modules/swiper/shared/effect-virtual-transition-end.mjs function effect_virtual_transition_end_effectVirtualTransitionEnd({ swiper, duration, transformElements, allSlides }) { const { activeIndex } = swiper; const getSlide = el => { if (!el.parentElement) { // assume shadow root const slide = swiper.slides.find(slideEl => slideEl.shadowRoot && slideEl.shadowRoot === el.parentNode); return slide; } return el.parentElement; }; if (swiper.params.virtualTranslate && duration !== 0) { let eventTriggered = false; let transitionEndTarget; if (allSlides) { transitionEndTarget = transformElements; } else { transitionEndTarget = transformElements.filter(transformEl => { const el = transformEl.classList.contains('swiper-slide-transform') ? getSlide(transformEl) : transformEl; return swiper.getSlideIndex(el) === activeIndex; }); } transitionEndTarget.forEach(el => { elementTransitionEnd(el, () => { if (eventTriggered) return; if (!swiper || swiper.destroyed) return; eventTriggered = true; swiper.animating = false; const evt = new window.CustomEvent('transitionend', { bubbles: true, cancelable: true }); swiper.wrapperEl.dispatchEvent(evt); }); }); } } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/effect-fade.mjs function EffectFade({ swiper, extendParams, on }) { extendParams({ fadeEffect: { crossFade: false } }); const setTranslate = () => { const { slides } = swiper; const params = swiper.params.fadeEffect; for (let i = 0; i < slides.length; i += 1) { const slideEl = swiper.slides[i]; const offset = slideEl.swiperSlideOffset; let tx = -offset; if (!swiper.params.virtualTranslate) tx -= swiper.translate; let ty = 0; if (!swiper.isHorizontal()) { ty = tx; tx = 0; } const slideOpacity = swiper.params.fadeEffect.crossFade ? Math.max(1 - Math.abs(slideEl.progress), 0) : 1 + Math.min(Math.max(slideEl.progress, -1), 0); const targetEl = effectTarget(params, slideEl); targetEl.style.opacity = slideOpacity; targetEl.style.transform = `translate3d(${tx}px, ${ty}px, 0px)`; } }; const setTransition = duration => { const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl)); transformElements.forEach(el => { el.style.transitionDuration = `${duration}ms`; }); effectVirtualTransitionEnd({ swiper, duration, transformElements, allSlides: true }); }; effectInit({ effect: 'fade', swiper, on, setTranslate, setTransition, overwriteParams: () => ({ slidesPerView: 1, slidesPerGroup: 1, watchSlidesProgress: true, spaceBetween: 0, virtualTranslate: !swiper.params.cssMode }) }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/effect-cube.mjs function EffectCube({ swiper, extendParams, on }) { extendParams({ cubeEffect: { slideShadows: true, shadow: true, shadowOffset: 20, shadowScale: 0.94 } }); const createSlideShadows = (slideEl, progress, isHorizontal) => { let shadowBefore = isHorizontal ? slideEl.querySelector('.swiper-slide-shadow-left') : slideEl.querySelector('.swiper-slide-shadow-top'); let shadowAfter = isHorizontal ? slideEl.querySelector('.swiper-slide-shadow-right') : slideEl.querySelector('.swiper-slide-shadow-bottom'); if (!shadowBefore) { shadowBefore = createElement('div', `swiper-slide-shadow-cube swiper-slide-shadow-${isHorizontal ? 'left' : 'top'}`.split(' ')); slideEl.append(shadowBefore); } if (!shadowAfter) { shadowAfter = createElement('div', `swiper-slide-shadow-cube swiper-slide-shadow-${isHorizontal ? 'right' : 'bottom'}`.split(' ')); slideEl.append(shadowAfter); } if (shadowBefore) shadowBefore.style.opacity = Math.max(-progress, 0); if (shadowAfter) shadowAfter.style.opacity = Math.max(progress, 0); }; const recreateShadows = () => { // create new ones const isHorizontal = swiper.isHorizontal(); swiper.slides.forEach(slideEl => { const progress = Math.max(Math.min(slideEl.progress, 1), -1); createSlideShadows(slideEl, progress, isHorizontal); }); }; const setTranslate = () => { const { el, wrapperEl, slides, width: swiperWidth, height: swiperHeight, rtlTranslate: rtl, size: swiperSize, browser } = swiper; const r = getRotateFix(swiper); const params = swiper.params.cubeEffect; const isHorizontal = swiper.isHorizontal(); const isVirtual = swiper.virtual && swiper.params.virtual.enabled; let wrapperRotate = 0; let cubeShadowEl; if (params.shadow) { if (isHorizontal) { cubeShadowEl = swiper.wrapperEl.querySelector('.swiper-cube-shadow'); if (!cubeShadowEl) { cubeShadowEl = createElement('div', 'swiper-cube-shadow'); swiper.wrapperEl.append(cubeShadowEl); } cubeShadowEl.style.height = `${swiperWidth}px`; } else { cubeShadowEl = el.querySelector('.swiper-cube-shadow'); if (!cubeShadowEl) { cubeShadowEl = createElement('div', 'swiper-cube-shadow'); el.append(cubeShadowEl); } } } for (let i = 0; i < slides.length; i += 1) { const slideEl = slides[i]; let slideIndex = i; if (isVirtual) { slideIndex = parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10); } let slideAngle = slideIndex * 90; let round = Math.floor(slideAngle / 360); if (rtl) { slideAngle = -slideAngle; round = Math.floor(-slideAngle / 360); } const progress = Math.max(Math.min(slideEl.progress, 1), -1); let tx = 0; let ty = 0; let tz = 0; if (slideIndex % 4 === 0) { tx = -round * 4 * swiperSize; tz = 0; } else if ((slideIndex - 1) % 4 === 0) { tx = 0; tz = -round * 4 * swiperSize; } else if ((slideIndex - 2) % 4 === 0) { tx = swiperSize + round * 4 * swiperSize; tz = swiperSize; } else if ((slideIndex - 3) % 4 === 0) { tx = -swiperSize; tz = 3 * swiperSize + swiperSize * 4 * round; } if (rtl) { tx = -tx; } if (!isHorizontal) { ty = tx; tx = 0; } const transform = `rotateX(${r(isHorizontal ? 0 : -slideAngle)}deg) rotateY(${r(isHorizontal ? slideAngle : 0)}deg) translate3d(${tx}px, ${ty}px, ${tz}px)`; if (progress <= 1 && progress > -1) { wrapperRotate = slideIndex * 90 + progress * 90; if (rtl) wrapperRotate = -slideIndex * 90 - progress * 90; } slideEl.style.transform = transform; if (params.slideShadows) { createSlideShadows(slideEl, progress, isHorizontal); } } wrapperEl.style.transformOrigin = `50% 50% -${swiperSize / 2}px`; wrapperEl.style['-webkit-transform-origin'] = `50% 50% -${swiperSize / 2}px`; if (params.shadow) { if (isHorizontal) { cubeShadowEl.style.transform = `translate3d(0px, ${swiperWidth / 2 + params.shadowOffset}px, ${-swiperWidth / 2}px) rotateX(89.99deg) rotateZ(0deg) scale(${params.shadowScale})`; } else { const shadowAngle = Math.abs(wrapperRotate) - Math.floor(Math.abs(wrapperRotate) / 90) * 90; const multiplier = 1.5 - (Math.sin(shadowAngle * 2 * Math.PI / 360) / 2 + Math.cos(shadowAngle * 2 * Math.PI / 360) / 2); const scale1 = params.shadowScale; const scale2 = params.shadowScale / multiplier; const offset = params.shadowOffset; cubeShadowEl.style.transform = `scale3d(${scale1}, 1, ${scale2}) translate3d(0px, ${swiperHeight / 2 + offset}px, ${-swiperHeight / 2 / scale2}px) rotateX(-89.99deg)`; } } const zFactor = (browser.isSafari || browser.isWebView) && browser.needPerspectiveFix ? -swiperSize / 2 : 0; wrapperEl.style.transform = `translate3d(0px,0,${zFactor}px) rotateX(${r(swiper.isHorizontal() ? 0 : wrapperRotate)}deg) rotateY(${r(swiper.isHorizontal() ? -wrapperRotate : 0)}deg)`; wrapperEl.style.setProperty('--swiper-cube-translate-z', `${zFactor}px`); }; const setTransition = duration => { const { el, slides } = swiper; slides.forEach(slideEl => { slideEl.style.transitionDuration = `${duration}ms`; slideEl.querySelectorAll('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').forEach(subEl => { subEl.style.transitionDuration = `${duration}ms`; }); }); if (swiper.params.cubeEffect.shadow && !swiper.isHorizontal()) { const shadowEl = el.querySelector('.swiper-cube-shadow'); if (shadowEl) shadowEl.style.transitionDuration = `${duration}ms`; } }; effectInit({ effect: 'cube', swiper, on, setTranslate, setTransition, recreateShadows, getEffectParams: () => swiper.params.cubeEffect, perspective: () => true, overwriteParams: () => ({ slidesPerView: 1, slidesPerGroup: 1, watchSlidesProgress: true, resistanceRatio: 0, spaceBetween: 0, centeredSlides: false, virtualTranslate: true }) }); } ;// CONCATENATED MODULE: ./node_modules/swiper/shared/create-shadow.mjs function create_shadow_createShadow(suffix, slideEl, side) { const shadowClass = `swiper-slide-shadow${side ? `-${side}` : ''}${suffix ? ` swiper-slide-shadow-${suffix}` : ''}`; const shadowContainer = getSlideTransformEl(slideEl); let shadowEl = shadowContainer.querySelector(`.${shadowClass.split(' ').join('.')}`); if (!shadowEl) { shadowEl = createElement('div', shadowClass.split(' ')); shadowContainer.append(shadowEl); } return shadowEl; } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/effect-flip.mjs function EffectFlip({ swiper, extendParams, on }) { extendParams({ flipEffect: { slideShadows: true, limitRotation: true } }); const createSlideShadows = (slideEl, progress) => { let shadowBefore = swiper.isHorizontal() ? slideEl.querySelector('.swiper-slide-shadow-left') : slideEl.querySelector('.swiper-slide-shadow-top'); let shadowAfter = swiper.isHorizontal() ? slideEl.querySelector('.swiper-slide-shadow-right') : slideEl.querySelector('.swiper-slide-shadow-bottom'); if (!shadowBefore) { shadowBefore = createShadow('flip', slideEl, swiper.isHorizontal() ? 'left' : 'top'); } if (!shadowAfter) { shadowAfter = createShadow('flip', slideEl, swiper.isHorizontal() ? 'right' : 'bottom'); } if (shadowBefore) shadowBefore.style.opacity = Math.max(-progress, 0); if (shadowAfter) shadowAfter.style.opacity = Math.max(progress, 0); }; const recreateShadows = () => { // Set shadows swiper.params.flipEffect; swiper.slides.forEach(slideEl => { let progress = slideEl.progress; if (swiper.params.flipEffect.limitRotation) { progress = Math.max(Math.min(slideEl.progress, 1), -1); } createSlideShadows(slideEl, progress); }); }; const setTranslate = () => { const { slides, rtlTranslate: rtl } = swiper; const params = swiper.params.flipEffect; const rotateFix = getRotateFix(swiper); for (let i = 0; i < slides.length; i += 1) { const slideEl = slides[i]; let progress = slideEl.progress; if (swiper.params.flipEffect.limitRotation) { progress = Math.max(Math.min(slideEl.progress, 1), -1); } const offset = slideEl.swiperSlideOffset; const rotate = -180 * progress; let rotateY = rotate; let rotateX = 0; let tx = swiper.params.cssMode ? -offset - swiper.translate : -offset; let ty = 0; if (!swiper.isHorizontal()) { ty = tx; tx = 0; rotateX = -rotateY; rotateY = 0; } else if (rtl) { rotateY = -rotateY; } slideEl.style.zIndex = -Math.abs(Math.round(progress)) + slides.length; if (params.slideShadows) { createSlideShadows(slideEl, progress); } const transform = `translate3d(${tx}px, ${ty}px, 0px) rotateX(${rotateFix(rotateX)}deg) rotateY(${rotateFix(rotateY)}deg)`; const targetEl = effectTarget(params, slideEl); targetEl.style.transform = transform; } }; const setTransition = duration => { const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl)); transformElements.forEach(el => { el.style.transitionDuration = `${duration}ms`; el.querySelectorAll('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').forEach(shadowEl => { shadowEl.style.transitionDuration = `${duration}ms`; }); }); effectVirtualTransitionEnd({ swiper, duration, transformElements }); }; effectInit({ effect: 'flip', swiper, on, setTranslate, setTransition, recreateShadows, getEffectParams: () => swiper.params.flipEffect, perspective: () => true, overwriteParams: () => ({ slidesPerView: 1, slidesPerGroup: 1, watchSlidesProgress: true, spaceBetween: 0, virtualTranslate: !swiper.params.cssMode }) }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/effect-coverflow.mjs function EffectCoverflow({ swiper, extendParams, on }) { extendParams({ coverflowEffect: { rotate: 50, stretch: 0, depth: 100, scale: 1, modifier: 1, slideShadows: true } }); const setTranslate = () => { const { width: swiperWidth, height: swiperHeight, slides, slidesSizesGrid } = swiper; const params = swiper.params.coverflowEffect; const isHorizontal = swiper.isHorizontal(); const transform = swiper.translate; const center = isHorizontal ? -transform + swiperWidth / 2 : -transform + swiperHeight / 2; const rotate = isHorizontal ? params.rotate : -params.rotate; const translate = params.depth; const r = getRotateFix(swiper); // Each slide offset from center for (let i = 0, length = slides.length; i < length; i += 1) { const slideEl = slides[i]; const slideSize = slidesSizesGrid[i]; const slideOffset = slideEl.swiperSlideOffset; const centerOffset = (center - slideOffset - slideSize / 2) / slideSize; const offsetMultiplier = typeof params.modifier === 'function' ? params.modifier(centerOffset) : centerOffset * params.modifier; let rotateY = isHorizontal ? rotate * offsetMultiplier : 0; let rotateX = isHorizontal ? 0 : rotate * offsetMultiplier; // var rotateZ = 0 let translateZ = -translate * Math.abs(offsetMultiplier); let stretch = params.stretch; // Allow percentage to make a relative stretch for responsive sliders if (typeof stretch === 'string' && stretch.indexOf('%') !== -1) { stretch = parseFloat(params.stretch) / 100 * slideSize; } let translateY = isHorizontal ? 0 : stretch * offsetMultiplier; let translateX = isHorizontal ? stretch * offsetMultiplier : 0; let scale = 1 - (1 - params.scale) * Math.abs(offsetMultiplier); // Fix for ultra small values if (Math.abs(translateX) < 0.001) translateX = 0; if (Math.abs(translateY) < 0.001) translateY = 0; if (Math.abs(translateZ) < 0.001) translateZ = 0; if (Math.abs(rotateY) < 0.001) rotateY = 0; if (Math.abs(rotateX) < 0.001) rotateX = 0; if (Math.abs(scale) < 0.001) scale = 0; const slideTransform = `translate3d(${translateX}px,${translateY}px,${translateZ}px) rotateX(${r(rotateX)}deg) rotateY(${r(rotateY)}deg) scale(${scale})`; const targetEl = effectTarget(params, slideEl); targetEl.style.transform = slideTransform; slideEl.style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1; if (params.slideShadows) { // Set shadows let shadowBeforeEl = isHorizontal ? slideEl.querySelector('.swiper-slide-shadow-left') : slideEl.querySelector('.swiper-slide-shadow-top'); let shadowAfterEl = isHorizontal ? slideEl.querySelector('.swiper-slide-shadow-right') : slideEl.querySelector('.swiper-slide-shadow-bottom'); if (!shadowBeforeEl) { shadowBeforeEl = createShadow('coverflow', slideEl, isHorizontal ? 'left' : 'top'); } if (!shadowAfterEl) { shadowAfterEl = createShadow('coverflow', slideEl, isHorizontal ? 'right' : 'bottom'); } if (shadowBeforeEl) shadowBeforeEl.style.opacity = offsetMultiplier > 0 ? offsetMultiplier : 0; if (shadowAfterEl) shadowAfterEl.style.opacity = -offsetMultiplier > 0 ? -offsetMultiplier : 0; } } }; const setTransition = duration => { const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl)); transformElements.forEach(el => { el.style.transitionDuration = `${duration}ms`; el.querySelectorAll('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').forEach(shadowEl => { shadowEl.style.transitionDuration = `${duration}ms`; }); }); }; effectInit({ effect: 'coverflow', swiper, on, setTranslate, setTransition, perspective: () => true, overwriteParams: () => ({ watchSlidesProgress: true }) }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/effect-creative.mjs function EffectCreative({ swiper, extendParams, on }) { extendParams({ creativeEffect: { limitProgress: 1, shadowPerProgress: false, progressMultiplier: 1, perspective: true, prev: { translate: [0, 0, 0], rotate: [0, 0, 0], opacity: 1, scale: 1 }, next: { translate: [0, 0, 0], rotate: [0, 0, 0], opacity: 1, scale: 1 } } }); const getTranslateValue = value => { if (typeof value === 'string') return value; return `${value}px`; }; const setTranslate = () => { const { slides, wrapperEl, slidesSizesGrid } = swiper; const params = swiper.params.creativeEffect; const { progressMultiplier: multiplier } = params; const isCenteredSlides = swiper.params.centeredSlides; const rotateFix = getRotateFix(swiper); if (isCenteredSlides) { const margin = slidesSizesGrid[0] / 2 - swiper.params.slidesOffsetBefore || 0; wrapperEl.style.transform = `translateX(calc(50% - ${margin}px))`; } for (let i = 0; i < slides.length; i += 1) { const slideEl = slides[i]; const slideProgress = slideEl.progress; const progress = Math.min(Math.max(slideEl.progress, -params.limitProgress), params.limitProgress); let originalProgress = progress; if (!isCenteredSlides) { originalProgress = Math.min(Math.max(slideEl.originalProgress, -params.limitProgress), params.limitProgress); } const offset = slideEl.swiperSlideOffset; const t = [swiper.params.cssMode ? -offset - swiper.translate : -offset, 0, 0]; const r = [0, 0, 0]; let custom = false; if (!swiper.isHorizontal()) { t[1] = t[0]; t[0] = 0; } let data = { translate: [0, 0, 0], rotate: [0, 0, 0], scale: 1, opacity: 1 }; if (progress < 0) { data = params.next; custom = true; } else if (progress > 0) { data = params.prev; custom = true; } // set translate t.forEach((value, index) => { t[index] = `calc(${value}px + (${getTranslateValue(data.translate[index])} * ${Math.abs(progress * multiplier)}))`; }); // set rotates r.forEach((value, index) => { let val = data.rotate[index] * Math.abs(progress * multiplier); r[index] = val; }); slideEl.style.zIndex = -Math.abs(Math.round(slideProgress)) + slides.length; const translateString = t.join(', '); const rotateString = `rotateX(${rotateFix(r[0])}deg) rotateY(${rotateFix(r[1])}deg) rotateZ(${rotateFix(r[2])}deg)`; const scaleString = originalProgress < 0 ? `scale(${1 + (1 - data.scale) * originalProgress * multiplier})` : `scale(${1 - (1 - data.scale) * originalProgress * multiplier})`; const opacityString = originalProgress < 0 ? 1 + (1 - data.opacity) * originalProgress * multiplier : 1 - (1 - data.opacity) * originalProgress * multiplier; const transform = `translate3d(${translateString}) ${rotateString} ${scaleString}`; // Set shadows if (custom && data.shadow || !custom) { let shadowEl = slideEl.querySelector('.swiper-slide-shadow'); if (!shadowEl && data.shadow) { shadowEl = createShadow('creative', slideEl); } if (shadowEl) { const shadowOpacity = params.shadowPerProgress ? progress * (1 / params.limitProgress) : progress; shadowEl.style.opacity = Math.min(Math.max(Math.abs(shadowOpacity), 0), 1); } } const targetEl = effectTarget(params, slideEl); targetEl.style.transform = transform; targetEl.style.opacity = opacityString; if (data.origin) { targetEl.style.transformOrigin = data.origin; } } }; const setTransition = duration => { const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl)); transformElements.forEach(el => { el.style.transitionDuration = `${duration}ms`; el.querySelectorAll('.swiper-slide-shadow').forEach(shadowEl => { shadowEl.style.transitionDuration = `${duration}ms`; }); }); effectVirtualTransitionEnd({ swiper, duration, transformElements, allSlides: true }); }; effectInit({ effect: 'creative', swiper, on, setTranslate, setTransition, perspective: () => swiper.params.creativeEffect.perspective, overwriteParams: () => ({ watchSlidesProgress: true, virtualTranslate: !swiper.params.cssMode }) }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/effect-cards.mjs function EffectCards({ swiper, extendParams, on }) { extendParams({ cardsEffect: { slideShadows: true, rotate: true, perSlideRotate: 2, perSlideOffset: 8 } }); const setTranslate = () => { const { slides, activeIndex, rtlTranslate: rtl } = swiper; const params = swiper.params.cardsEffect; const { startTranslate, isTouched } = swiper.touchEventsData; const currentTranslate = rtl ? -swiper.translate : swiper.translate; for (let i = 0; i < slides.length; i += 1) { const slideEl = slides[i]; const slideProgress = slideEl.progress; const progress = Math.min(Math.max(slideProgress, -4), 4); let offset = slideEl.swiperSlideOffset; if (swiper.params.centeredSlides && !swiper.params.cssMode) { swiper.wrapperEl.style.transform = `translateX(${swiper.minTranslate()}px)`; } if (swiper.params.centeredSlides && swiper.params.cssMode) { offset -= slides[0].swiperSlideOffset; } let tX = swiper.params.cssMode ? -offset - swiper.translate : -offset; let tY = 0; const tZ = -100 * Math.abs(progress); let scale = 1; let rotate = -params.perSlideRotate * progress; let tXAdd = params.perSlideOffset - Math.abs(progress) * 0.75; const slideIndex = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.from + i : i; const isSwipeToNext = (slideIndex === activeIndex || slideIndex === activeIndex - 1) && progress > 0 && progress < 1 && (isTouched || swiper.params.cssMode) && currentTranslate < startTranslate; const isSwipeToPrev = (slideIndex === activeIndex || slideIndex === activeIndex + 1) && progress < 0 && progress > -1 && (isTouched || swiper.params.cssMode) && currentTranslate > startTranslate; if (isSwipeToNext || isSwipeToPrev) { const subProgress = (1 - Math.abs((Math.abs(progress) - 0.5) / 0.5)) ** 0.5; rotate += -28 * progress * subProgress; scale += -0.5 * subProgress; tXAdd += 96 * subProgress; tY = `${(params.rotate || swiper.isHorizontal() ? -25 : 0) * subProgress * Math.abs(progress)}%`; } if (progress < 0) { // next tX = `calc(${tX}px ${rtl ? '-' : '+'} (${tXAdd * Math.abs(progress)}%))`; } else if (progress > 0) { // prev tX = `calc(${tX}px ${rtl ? '-' : '+'} (-${tXAdd * Math.abs(progress)}%))`; } else { tX = `${tX}px`; } if (!swiper.isHorizontal()) { const prevY = tY; tY = tX; tX = prevY; } const scaleString = progress < 0 ? `${1 + (1 - scale) * progress}` : `${1 - (1 - scale) * progress}`; /* eslint-disable */ const transform = ` translate3d(${tX}, ${tY}, ${tZ}px) rotateZ(${params.rotate ? rtl ? -rotate : rotate : 0}deg) scale(${scaleString}) `; /* eslint-enable */ if (params.slideShadows) { // Set shadows let shadowEl = slideEl.querySelector('.swiper-slide-shadow'); if (!shadowEl) { shadowEl = createShadow('cards', slideEl); } if (shadowEl) shadowEl.style.opacity = Math.min(Math.max((Math.abs(progress) - 0.5) / 0.5, 0), 1); } slideEl.style.zIndex = -Math.abs(Math.round(slideProgress)) + slides.length; const targetEl = effectTarget(params, slideEl); targetEl.style.transform = transform; } }; const setTransition = duration => { const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl)); transformElements.forEach(el => { el.style.transitionDuration = `${duration}ms`; el.querySelectorAll('.swiper-slide-shadow').forEach(shadowEl => { shadowEl.style.transitionDuration = `${duration}ms`; }); }); effectVirtualTransitionEnd({ swiper, duration, transformElements }); }; effectInit({ effect: 'cards', swiper, on, setTranslate, setTransition, perspective: () => true, overwriteParams: () => ({ _loopSwapReset: false, watchSlidesProgress: true, loopAdditionalSlides: swiper.params.cardsEffect.rotate ? 3 : 2, centeredSlides: true, virtualTranslate: !swiper.params.cssMode }) }); } ;// CONCATENATED MODULE: ./node_modules/swiper/modules/index.mjs ;// CONCATENATED MODULE: ./node_modules/countup.js/dist/countUp.min.js var t=function(){return t=Object.assign||function(t){for(var i,n=1,s=arguments.length;na.endVal;a.frameVal=n?a.endVal:a.frameVal,a.frameVal=Number(a.frameVal.toFixed(a.options.decimalPlaces)),a.printValue(a.frameVal),i1?a.options.decimal+r[1]:"",a.options.useGrouping){e="";for(var l=3,h=0,u=0,p=n.length;uwindow.scrollY&&t.paused?(t.paused=!1,setTimeout((function(){return t.start()}),t.options.scrollSpyDelay),t.options.scrollSpyOnce&&(t.once=!0)):(window.scrollY>a||s>i)&&!t.paused&&t.reset()}},i.prototype.determineDirectionAndSmartEasing=function(){var t=this.finalEndVal?this.finalEndVal:this.endVal;this.countDown=this.startVal>t;var i=t-this.startVal;if(Math.abs(i)>this.options.smartEasingThreshold&&this.options.useEasing){this.finalEndVal=t;var n=this.countDown?1:-1;this.endVal=t+n*this.options.smartEasingAmount,this.duration=this.duration/2}else this.endVal=t,this.finalEndVal=null;null!==this.finalEndVal?this.useEasing=!1:this.useEasing=this.options.useEasing},i.prototype.start=function(t){this.error||(this.options.onStartCallback&&this.options.onStartCallback(),t&&(this.options.onCompleteCallback=t),this.duration>0?(this.determineDirectionAndSmartEasing(),this.paused=!1,this.rAF=requestAnimationFrame(this.count)):this.printValue(this.endVal))},i.prototype.pauseResume=function(){this.paused?(this.startTime=null,this.duration=this.remaining,this.startVal=this.frameVal,this.determineDirectionAndSmartEasing(),this.rAF=requestAnimationFrame(this.count)):cancelAnimationFrame(this.rAF),this.paused=!this.paused},i.prototype.reset=function(){cancelAnimationFrame(this.rAF),this.paused=!0,this.resetDuration(),this.startVal=this.validateValue(this.options.startVal),this.frameVal=this.startVal,this.printValue(this.startVal)},i.prototype.update=function(t){cancelAnimationFrame(this.rAF),this.startTime=null,this.endVal=this.validateValue(t),this.endVal!==this.frameVal&&(this.startVal=this.frameVal,null==this.finalEndVal&&this.resetDuration(),this.finalEndVal=null,this.determineDirectionAndSmartEasing(),this.rAF=requestAnimationFrame(this.count))},i.prototype.printValue=function(t){var i;if(this.el){var n=this.formattingFn(t);if(null===(i=this.options.plugin)||void 0===i?void 0:i.render)this.options.plugin.render(this.el,n);else if("INPUT"===this.el.tagName)this.el.value=n;else"text"===this.el.tagName||"tspan"===this.el.tagName?this.el.textContent=n:this.el.innerHTML=n}},i.prototype.ensureNumber=function(t){return"number"==typeof t&&!isNaN(t)},i.prototype.validateValue=function(t){var i=Number(t);return this.ensureNumber(i)?i:(this.error="[CountUp] invalid start or end value: ".concat(t),null)},i.prototype.resetDuration=function(){this.startTime=null,this.duration=1e3*Number(this.options.duration),this.remaining=this.duration},i.prototype.parse=function(t){var i=function(t){return t.replace(/([.,'  ])/g,"\\$1")},n=i(this.options.separator),s=i(this.options.decimal),a=t.replace(new RegExp(n,"g"),"").replace(new RegExp(s,"g"),".");return parseFloat(a)},i}(); // EXTERNAL MODULE: ./node_modules/aos/dist/aos.js var aos = __webpack_require__(711); var aos_default = /*#__PURE__*/__webpack_require__.n(aos); // EXTERNAL MODULE: ./node_modules/global/window.js var global_window = __webpack_require__(908); var window_default = /*#__PURE__*/__webpack_require__.n(global_window); // EXTERNAL MODULE: ./node_modules/global/document.js var global_document = __webpack_require__(144); var document_default = /*#__PURE__*/__webpack_require__.n(global_document); // EXTERNAL MODULE: ./node_modules/@videojs/xhr/lib/index.js var lib = __webpack_require__(603); var lib_default = /*#__PURE__*/__webpack_require__.n(lib); // EXTERNAL MODULE: ./node_modules/videojs-vtt.js/lib/browser-index.js var browser_index = __webpack_require__(407); var browser_index_default = /*#__PURE__*/__webpack_require__.n(browser_index); ;// CONCATENATED MODULE: ./node_modules/@babel/runtime/helpers/esm/extends.js function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/resolve-url.js var DEFAULT_LOCATION = 'https://example.com'; var resolveUrl = function resolveUrl(baseUrl, relativeUrl) { // return early if we don't need to resolve if (/^[a-z]+:/i.test(relativeUrl)) { return relativeUrl; } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location if (/^data:/.test(baseUrl)) { baseUrl = (window_default()).location && (window_default()).location.href || ''; } var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node) // and if baseUrl isn't an absolute url var removeLocation = !(window_default()).location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location baseUrl = new (window_default()).URL(baseUrl, (window_default()).location || DEFAULT_LOCATION); var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol // and if we're location-less, remove the location // otherwise, return the url unmodified if (removeLocation) { return newUrl.href.slice(DEFAULT_LOCATION.length); } else if (protocolLess) { return newUrl.href.slice(newUrl.protocol.length); } return newUrl.href; }; /* harmony default export */ const resolve_url = (resolveUrl); ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/stream.js /** * @file stream.js */ /** * A lightweight readable stream implemention that handles event dispatching. * * @class Stream */ var Stream = /*#__PURE__*/function () { function Stream() { this.listeners = {}; } /** * Add a listener for a specified event type. * * @param {string} type the event name * @param {Function} listener the callback to be invoked when an event of * the specified type occurs */ var _proto = Stream.prototype; _proto.on = function on(type, listener) { if (!this.listeners[type]) { this.listeners[type] = []; } this.listeners[type].push(listener); } /** * Remove a listener for a specified event type. * * @param {string} type the event name * @param {Function} listener a function previously registered for this * type of event through `on` * @return {boolean} if we could turn it off or not */ ; _proto.off = function off(type, listener) { if (!this.listeners[type]) { return false; } var index = this.listeners[type].indexOf(listener); // TODO: which is better? // In Video.js we slice listener functions // on trigger so that it does not mess up the order // while we loop through. // // Here we slice on off so that the loop in trigger // can continue using it's old reference to loop without // messing up the order. this.listeners[type] = this.listeners[type].slice(0); this.listeners[type].splice(index, 1); return index > -1; } /** * Trigger an event of the specified type on this stream. Any additional * arguments to this function are passed as parameters to event listeners. * * @param {string} type the event name */ ; _proto.trigger = function trigger(type) { var callbacks = this.listeners[type]; if (!callbacks) { return; } // Slicing the arguments on every invocation of this method // can add a significant amount of overhead. Avoid the // intermediate object creation for the common case of a // single callback argument if (arguments.length === 2) { var length = callbacks.length; for (var i = 0; i < length; ++i) { callbacks[i].call(this, arguments[1]); } } else { var args = Array.prototype.slice.call(arguments, 1); var _length = callbacks.length; for (var _i = 0; _i < _length; ++_i) { callbacks[_i].apply(this, args); } } } /** * Destroys the stream and cleans up. */ ; _proto.dispose = function dispose() { this.listeners = {}; } /** * Forwards all `data` events on this stream to the destination stream. The * destination stream should provide a method `push` to receive the data * events as they arrive. * * @param {Stream} destination the stream that will receive all `data` events * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options */ ; _proto.pipe = function pipe(destination) { this.on('data', function (data) { destination.push(data); }); }; return Stream; }(); ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js var atob = function atob(s) { return (window_default()).atob ? window_default().atob(s) : Buffer.from(s, 'base64').toString('binary'); }; function decodeB64ToUint8Array(b64Text) { var decodedString = atob(b64Text); var array = new Uint8Array(decodedString.length); for (var i = 0; i < decodedString.length; i++) { array[i] = decodedString.charCodeAt(i); } return array; } ;// CONCATENATED MODULE: ./node_modules/m3u8-parser/dist/m3u8-parser.es.js /*! @name m3u8-parser @version 7.2.0 @license Apache-2.0 */ /** * @file m3u8/line-stream.js */ /** * A stream that buffers string input and generates a `data` event for each * line. * * @class LineStream * @extends Stream */ class LineStream extends Stream { constructor() { super(); this.buffer = ''; } /** * Add new data to be parsed. * * @param {string} data the text to process */ push(data) { let nextNewline; this.buffer += data; nextNewline = this.buffer.indexOf('\n'); for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { this.trigger('data', this.buffer.substring(0, nextNewline)); this.buffer = this.buffer.substring(nextNewline + 1); } } } const TAB = String.fromCharCode(0x09); const parseByterange = function (byterangeString) { // optionally match and capture 0+ digits before `@` // optionally match and capture 0+ digits after `@` const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || ''); const result = {}; if (match[1]) { result.length = parseInt(match[1], 10); } if (match[2]) { result.offset = parseInt(match[2], 10); } return result; }; /** * "forgiving" attribute list psuedo-grammar: * attributes -> keyvalue (',' keyvalue)* * keyvalue -> key '=' value * key -> [^=]* * value -> '"' [^"]* '"' | [^,]* */ const attributeSeparator = function () { const key = '[^=]*'; const value = '"[^"]*"|[^,]*'; const keyvalue = '(?:' + key + ')=(?:' + value + ')'; return new RegExp('(?:^|,)(' + keyvalue + ')'); }; /** * Parse attributes from a line given the separator * * @param {string} attributes the attribute line to parse */ const parseAttributes = function (attributes) { const result = {}; if (!attributes) { return result; } // split the string using attributes as the separator const attrs = attributes.split(attributeSeparator()); let i = attrs.length; let attr; while (i--) { // filter out unmatched portions of the string if (attrs[i] === '') { continue; } // split the key and value attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); result[attr[0]] = attr[1]; } return result; }; /** * Converts a string into a resolution object * * @param {string} resolution a string such as 3840x2160 * * @return {Object} An object representing the resolution * */ const parseResolution = resolution => { const split = resolution.split('x'); const result = {}; if (split[0]) { result.width = parseInt(split[0], 10); } if (split[1]) { result.height = parseInt(split[1], 10); } return result; }; /** * A line-level M3U8 parser event stream. It expects to receive input one * line at a time and performs a context-free parse of its contents. A stream * interpretation of a manifest can be useful if the manifest is expected to * be too large to fit comfortably into memory or the entirety of the input * is not immediately available. Otherwise, it's probably much easier to work * with a regular `Parser` object. * * Produces `data` events with an object that captures the parser's * interpretation of the input. That object has a property `tag` that is one * of `uri`, `comment`, or `tag`. URIs only have a single additional * property, `line`, which captures the entirety of the input without * interpretation. Comments similarly have a single additional property * `text` which is the input without the leading `#`. * * Tags always have a property `tagType` which is the lower-cased version of * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized * tags are given the tag type `unknown` and a single additional property * `data` with the remainder of the input. * * @class ParseStream * @extends Stream */ class ParseStream extends Stream { constructor() { super(); this.customParsers = []; this.tagMappers = []; } /** * Parses an additional line of input. * * @param {string} line a single line of an M3U8 file to parse */ push(line) { let match; let event; // strip whitespace line = line.trim(); if (line.length === 0) { // ignore empty lines return; } // URIs if (line[0] !== '#') { this.trigger('data', { type: 'uri', uri: line }); return; } // map tags const newLines = this.tagMappers.reduce((acc, mapper) => { const mappedLine = mapper(line); // skip if unchanged if (mappedLine === line) { return acc; } return acc.concat([mappedLine]); }, [line]); newLines.forEach(newLine => { for (let i = 0; i < this.customParsers.length; i++) { if (this.customParsers[i].call(this, newLine)) { return; } } // Comments if (newLine.indexOf('#EXT') !== 0) { this.trigger('data', { type: 'comment', text: newLine.slice(1) }); return; } // strip off any carriage returns here so the regex matching // doesn't have to account for them. newLine = newLine.replace('\r', ''); // Tags match = /^#EXTM3U/.exec(newLine); if (match) { this.trigger('data', { type: 'tag', tagType: 'm3u' }); return; } match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'inf' }; if (match[1]) { event.duration = parseFloat(match[1]); } if (match[2]) { event.title = match[2]; } this.trigger('data', event); return; } match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'targetduration' }; if (match[1]) { event.duration = parseInt(match[1], 10); } this.trigger('data', event); return; } match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'version' }; if (match[1]) { event.version = parseInt(match[1], 10); } this.trigger('data', event); return; } match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'media-sequence' }; if (match[1]) { event.number = parseInt(match[1], 10); } this.trigger('data', event); return; } match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'discontinuity-sequence' }; if (match[1]) { event.number = parseInt(match[1], 10); } this.trigger('data', event); return; } match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'playlist-type' }; if (match[1]) { event.playlistType = match[1]; } this.trigger('data', event); return; } match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine); if (match) { event = _extends(parseByterange(match[1]), { type: 'tag', tagType: 'byterange' }); this.trigger('data', event); return; } match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'allow-cache' }; if (match[1]) { event.allowed = !/NO/.test(match[1]); } this.trigger('data', event); return; } match = /^#EXT-X-MAP:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'map' }; if (match[1]) { const attributes = parseAttributes(match[1]); if (attributes.URI) { event.uri = attributes.URI; } if (attributes.BYTERANGE) { event.byterange = parseByterange(attributes.BYTERANGE); } } this.trigger('data', event); return; } match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'stream-inf' }; if (match[1]) { event.attributes = parseAttributes(match[1]); if (event.attributes.RESOLUTION) { event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION); } if (event.attributes.BANDWIDTH) { event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); } if (event.attributes['FRAME-RATE']) { event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']); } if (event.attributes['PROGRAM-ID']) { event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); } } this.trigger('data', event); return; } match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'media' }; if (match[1]) { event.attributes = parseAttributes(match[1]); } this.trigger('data', event); return; } match = /^#EXT-X-ENDLIST/.exec(newLine); if (match) { this.trigger('data', { type: 'tag', tagType: 'endlist' }); return; } match = /^#EXT-X-DISCONTINUITY/.exec(newLine); if (match) { this.trigger('data', { type: 'tag', tagType: 'discontinuity' }); return; } match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'program-date-time' }; if (match[1]) { event.dateTimeString = match[1]; event.dateTimeObject = new Date(match[1]); } this.trigger('data', event); return; } match = /^#EXT-X-KEY:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'key' }; if (match[1]) { event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array if (event.attributes.IV) { if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { event.attributes.IV = event.attributes.IV.substring(2); } event.attributes.IV = event.attributes.IV.match(/.{8}/g); event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); event.attributes.IV = new Uint32Array(event.attributes.IV); } } this.trigger('data', event); return; } match = /^#EXT-X-START:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'start' }; if (match[1]) { event.attributes = parseAttributes(match[1]); event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']); event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE); } this.trigger('data', event); return; } match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'cue-out-cont' }; if (match[1]) { event.data = match[1]; } else { event.data = ''; } this.trigger('data', event); return; } match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'cue-out' }; if (match[1]) { event.data = match[1]; } else { event.data = ''; } this.trigger('data', event); return; } match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'cue-in' }; if (match[1]) { event.data = match[1]; } else { event.data = ''; } this.trigger('data', event); return; } match = /^#EXT-X-SKIP:(.*)$/.exec(newLine); if (match && match[1]) { event = { type: 'tag', tagType: 'skip' }; event.attributes = parseAttributes(match[1]); if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) { event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10); } if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) { event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB); } this.trigger('data', event); return; } match = /^#EXT-X-PART:(.*)$/.exec(newLine); if (match && match[1]) { event = { type: 'tag', tagType: 'part' }; event.attributes = parseAttributes(match[1]); ['DURATION'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = parseFloat(event.attributes[key]); } }); ['INDEPENDENT', 'GAP'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = /YES/.test(event.attributes[key]); } }); if (event.attributes.hasOwnProperty('BYTERANGE')) { event.attributes.byterange = parseByterange(event.attributes.BYTERANGE); } this.trigger('data', event); return; } match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine); if (match && match[1]) { event = { type: 'tag', tagType: 'server-control' }; event.attributes = parseAttributes(match[1]); ['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = parseFloat(event.attributes[key]); } }); ['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = /YES/.test(event.attributes[key]); } }); this.trigger('data', event); return; } match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine); if (match && match[1]) { event = { type: 'tag', tagType: 'part-inf' }; event.attributes = parseAttributes(match[1]); ['PART-TARGET'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = parseFloat(event.attributes[key]); } }); this.trigger('data', event); return; } match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine); if (match && match[1]) { event = { type: 'tag', tagType: 'preload-hint' }; event.attributes = parseAttributes(match[1]); ['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = parseInt(event.attributes[key], 10); const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset'; event.attributes.byterange = event.attributes.byterange || {}; event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object. delete event.attributes[key]; } }); this.trigger('data', event); return; } match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine); if (match && match[1]) { event = { type: 'tag', tagType: 'rendition-report' }; event.attributes = parseAttributes(match[1]); ['LAST-MSN', 'LAST-PART'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = parseInt(event.attributes[key], 10); } }); this.trigger('data', event); return; } match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine); if (match && match[1]) { event = { type: 'tag', tagType: 'daterange' }; event.attributes = parseAttributes(match[1]); ['ID', 'CLASS'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = String(event.attributes[key]); } }); ['START-DATE', 'END-DATE'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = new Date(event.attributes[key]); } }); ['DURATION', 'PLANNED-DURATION'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = parseFloat(event.attributes[key]); } }); ['END-ON-NEXT'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = /YES/i.test(event.attributes[key]); } }); ['SCTE35-CMD', ' SCTE35-OUT', 'SCTE35-IN'].forEach(function (key) { if (event.attributes.hasOwnProperty(key)) { event.attributes[key] = event.attributes[key].toString(16); } }); const clientAttributePattern = /^X-([A-Z]+-)+[A-Z]+$/; for (const key in event.attributes) { if (!clientAttributePattern.test(key)) { continue; } const isHexaDecimal = /[0-9A-Fa-f]{6}/g.test(event.attributes[key]); const isDecimalFloating = /^\d+(\.\d+)?$/.test(event.attributes[key]); event.attributes[key] = isHexaDecimal ? event.attributes[key].toString(16) : isDecimalFloating ? parseFloat(event.attributes[key]) : String(event.attributes[key]); } this.trigger('data', event); return; } match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine); if (match) { this.trigger('data', { type: 'tag', tagType: 'independent-segments' }); return; } match = /^#EXT-X-I-FRAMES-ONLY/.exec(newLine); if (match) { this.trigger('data', { type: 'tag', tagType: 'i-frames-only' }); return; } match = /^#EXT-X-CONTENT-STEERING:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'content-steering' }; event.attributes = parseAttributes(match[1]); this.trigger('data', event); return; } match = /^#EXT-X-I-FRAME-STREAM-INF:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'i-frame-playlist' }; event.attributes = parseAttributes(match[1]); if (event.attributes.URI) { event.uri = event.attributes.URI; } if (event.attributes.BANDWIDTH) { event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); } if (event.attributes.RESOLUTION) { event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION); } if (event.attributes['AVERAGE-BANDWIDTH']) { event.attributes['AVERAGE-BANDWIDTH'] = parseInt(event.attributes['AVERAGE-BANDWIDTH'], 10); } if (event.attributes['FRAME-RATE']) { event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']); } this.trigger('data', event); return; } match = /^#EXT-X-DEFINE:(.*)$/.exec(newLine); if (match) { event = { type: 'tag', tagType: 'define' }; event.attributes = parseAttributes(match[1]); this.trigger('data', event); return; } // unknown tag type this.trigger('data', { type: 'tag', data: newLine.slice(4) }); }); } /** * Add a parser for custom headers * * @param {Object} options a map of options for the added parser * @param {RegExp} options.expression a regular expression to match the custom header * @param {string} options.customType the custom type to register to the output * @param {Function} [options.dataParser] function to parse the line into an object * @param {boolean} [options.segment] should tag data be attached to the segment object */ addParser({ expression, customType, dataParser, segment }) { if (typeof dataParser !== 'function') { dataParser = line => line; } this.customParsers.push(line => { const match = expression.exec(line); if (match) { this.trigger('data', { type: 'custom', data: dataParser(line), customType, segment }); return true; } }); } /** * Add a custom header mapper * * @param {Object} options * @param {RegExp} options.expression a regular expression to match the custom header * @param {Function} options.map function to translate tag into a different tag */ addTagMapper({ expression, map }) { const mapFn = line => { if (expression.test(line)) { return map(line); } return line; }; this.tagMappers.push(mapFn); } } const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase()); const camelCaseKeys = function (attributes) { const result = {}; Object.keys(attributes).forEach(function (key) { result[camelCase(key)] = attributes[key]; }); return result; }; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration // we need this helper because defaults are based upon targetDuration and // partTargetDuration being set, but they may not be if SERVER-CONTROL appears before // target durations are set. const setHoldBack = function (manifest) { const { serverControl, targetDuration, partTargetDuration } = manifest; if (!serverControl) { return; } const tag = '#EXT-X-SERVER-CONTROL'; const hb = 'holdBack'; const phb = 'partHoldBack'; const minTargetDuration = targetDuration && targetDuration * 3; const minPartDuration = partTargetDuration && partTargetDuration * 2; if (targetDuration && !serverControl.hasOwnProperty(hb)) { serverControl[hb] = minTargetDuration; this.trigger('info', { message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).` }); } if (minTargetDuration && serverControl[hb] < minTargetDuration) { this.trigger('warn', { message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})` }); serverControl[hb] = minTargetDuration; } // default no part hold back to part target duration * 3 if (partTargetDuration && !serverControl.hasOwnProperty(phb)) { serverControl[phb] = partTargetDuration * 3; this.trigger('info', { message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).` }); } // if part hold back is too small default it to part target duration * 2 if (partTargetDuration && serverControl[phb] < minPartDuration) { this.trigger('warn', { message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).` }); serverControl[phb] = minPartDuration; } }; /** * A parser for M3U8 files. The current interpretation of the input is * exposed as a property `manifest` on parser objects. It's just two lines to * create and parse a manifest once you have the contents available as a string: * * ```js * var parser = new m3u8.Parser(); * parser.push(xhr.responseText); * ``` * * New input can later be applied to update the manifest object by calling * `push` again. * * The parser attempts to create a usable manifest object even if the * underlying input is somewhat nonsensical. It emits `info` and `warning` * events during the parse if it encounters input that seems invalid or * requires some property of the manifest object to be defaulted. * * @class Parser * @param {Object} [opts] Options for the constructor, needed for substitutions * @param {string} [opts.uri] URL to check for query params * @param {Object} [opts.mainDefinitions] Definitions on main playlist that can be imported * @extends Stream */ class Parser extends Stream { constructor(opts = {}) { super(); this.lineStream = new LineStream(); this.parseStream = new ParseStream(); this.lineStream.pipe(this.parseStream); this.mainDefinitions = opts.mainDefinitions || {}; this.params = new URL(opts.uri, 'https://a.com').searchParams; this.lastProgramDateTime = null; /* eslint-disable consistent-this */ const self = this; /* eslint-enable consistent-this */ const uris = []; let currentUri = {}; // if specified, the active EXT-X-MAP definition let currentMap; // if specified, the active decryption key let key; let hasParts = false; const noop = function () {}; const defaultMediaGroups = { 'AUDIO': {}, 'VIDEO': {}, 'CLOSED-CAPTIONS': {}, 'SUBTITLES': {} }; // This is the Widevine UUID from DASH IF IOP. The same exact string is // used in MPDs with Widevine encrypted streams. const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data this.manifest = { allowCache: true, discontinuityStarts: [], dateRanges: [], iFramePlaylists: [], segments: [] }; // keep track of the last seen segment's byte range end, as segments are not required // to provide the offset, in which case it defaults to the next byte after the // previous segment let lastByterangeEnd = 0; // keep track of the last seen part's byte range end. let lastPartByterangeEnd = 0; const dateRangeTags = {}; this.on('end', () => { // only add preloadSegment if we don't yet have a uri for it. // and we actually have parts/preloadHints if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) { return; } if (!currentUri.map && currentMap) { currentUri.map = currentMap; } if (!currentUri.key && key) { currentUri.key = key; } if (!currentUri.timeline && typeof currentTimeline === 'number') { currentUri.timeline = currentTimeline; } this.manifest.preloadSegment = currentUri; }); // update the manifest with the m3u8 entry from the parse stream this.parseStream.on('data', function (entry) { let mediaGroup; let rendition; // Replace variables in uris and attributes as defined in #EXT-X-DEFINE tags if (self.manifest.definitions) { for (const def in self.manifest.definitions) { if (entry.uri) { entry.uri = entry.uri.replace(`{$${def}}`, self.manifest.definitions[def]); } if (entry.attributes) { for (const attr in entry.attributes) { if (typeof entry.attributes[attr] === 'string') { entry.attributes[attr] = entry.attributes[attr].replace(`{$${def}}`, self.manifest.definitions[def]); } } } } } ({ tag() { // switch based on the tag type (({ version() { if (entry.version) { this.manifest.version = entry.version; } }, 'allow-cache'() { this.manifest.allowCache = entry.allowed; if (!('allowed' in entry)) { this.trigger('info', { message: 'defaulting allowCache to YES' }); this.manifest.allowCache = true; } }, byterange() { const byterange = {}; if ('length' in entry) { currentUri.byterange = byterange; byterange.length = entry.length; if (!('offset' in entry)) { /* * From the latest spec (as of this writing): * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2 * * Same text since EXT-X-BYTERANGE's introduction in draft 7: * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1) * * "If o [offset] is not present, the sub-range begins at the next byte * following the sub-range of the previous media segment." */ entry.offset = lastByterangeEnd; } } if ('offset' in entry) { currentUri.byterange = byterange; byterange.offset = entry.offset; } lastByterangeEnd = byterange.offset + byterange.length; }, endlist() { this.manifest.endList = true; }, inf() { if (!('mediaSequence' in this.manifest)) { this.manifest.mediaSequence = 0; this.trigger('info', { message: 'defaulting media sequence to zero' }); } if (!('discontinuitySequence' in this.manifest)) { this.manifest.discontinuitySequence = 0; this.trigger('info', { message: 'defaulting discontinuity sequence to zero' }); } if (entry.title) { currentUri.title = entry.title; } if (entry.duration > 0) { currentUri.duration = entry.duration; } if (entry.duration === 0) { currentUri.duration = 0.01; this.trigger('info', { message: 'updating zero segment duration to a small value' }); } this.manifest.segments = uris; }, key() { if (!entry.attributes) { this.trigger('warn', { message: 'ignoring key declaration without attribute list' }); return; } // clear the active encryption key if (entry.attributes.METHOD === 'NONE') { key = null; return; } if (!entry.attributes.URI) { this.trigger('warn', { message: 'ignoring key declaration without URI' }); return; } if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') { this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this. this.manifest.contentProtection['com.apple.fps.1_0'] = { attributes: entry.attributes }; return; } if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') { this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this. this.manifest.contentProtection['com.microsoft.playready'] = { uri: entry.attributes.URI }; return; } // check if the content is encrypted for Widevine // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf if (entry.attributes.KEYFORMAT === widevineUuid) { const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC']; if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) { this.trigger('warn', { message: 'invalid key method provided for Widevine' }); return; } if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') { this.trigger('warn', { message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead' }); } if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') { this.trigger('warn', { message: 'invalid key URI provided for Widevine' }); return; } if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) { this.trigger('warn', { message: 'invalid key ID provided for Widevine' }); return; } // if Widevine key attributes are valid, store them as `contentProtection` // on the manifest to emulate Widevine tag structure in a DASH mpd this.manifest.contentProtection = this.manifest.contentProtection || {}; this.manifest.contentProtection['com.widevine.alpha'] = { attributes: { schemeIdUri: entry.attributes.KEYFORMAT, // remove '0x' from the key id string keyId: entry.attributes.KEYID.substring(2) }, // decode the base64-encoded PSSH box pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1]) }; return; } if (!entry.attributes.METHOD) { this.trigger('warn', { message: 'defaulting key method to AES-128' }); } // setup an encryption key for upcoming segments key = { method: entry.attributes.METHOD || 'AES-128', uri: entry.attributes.URI }; if (typeof entry.attributes.IV !== 'undefined') { key.iv = entry.attributes.IV; } }, 'media-sequence'() { if (!isFinite(entry.number)) { this.trigger('warn', { message: 'ignoring invalid media sequence: ' + entry.number }); return; } this.manifest.mediaSequence = entry.number; }, 'discontinuity-sequence'() { if (!isFinite(entry.number)) { this.trigger('warn', { message: 'ignoring invalid discontinuity sequence: ' + entry.number }); return; } this.manifest.discontinuitySequence = entry.number; currentTimeline = entry.number; }, 'playlist-type'() { if (!/VOD|EVENT/.test(entry.playlistType)) { this.trigger('warn', { message: 'ignoring unknown playlist type: ' + entry.playlist }); return; } this.manifest.playlistType = entry.playlistType; }, map() { currentMap = {}; if (entry.uri) { currentMap.uri = entry.uri; } if (entry.byterange) { currentMap.byterange = entry.byterange; } if (key) { currentMap.key = key; } }, 'stream-inf'() { this.manifest.playlists = uris; this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; if (!entry.attributes) { this.trigger('warn', { message: 'ignoring empty stream-inf attributes' }); return; } if (!currentUri.attributes) { currentUri.attributes = {}; } _extends(currentUri.attributes, entry.attributes); }, media() { this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { this.trigger('warn', { message: 'ignoring incomplete or missing media group' }); return; } // find the media group, creating defaults as necessary const mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata rendition = { default: /yes/i.test(entry.attributes.DEFAULT) }; if (rendition.default) { rendition.autoselect = true; } else { rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); } if (entry.attributes.LANGUAGE) { rendition.language = entry.attributes.LANGUAGE; } if (entry.attributes.URI) { rendition.uri = entry.attributes.URI; } if (entry.attributes['INSTREAM-ID']) { rendition.instreamId = entry.attributes['INSTREAM-ID']; } if (entry.attributes.CHARACTERISTICS) { rendition.characteristics = entry.attributes.CHARACTERISTICS; } if (entry.attributes.FORCED) { rendition.forced = /yes/i.test(entry.attributes.FORCED); } // insert the new rendition mediaGroup[entry.attributes.NAME] = rendition; }, discontinuity() { currentTimeline += 1; currentUri.discontinuity = true; this.manifest.discontinuityStarts.push(uris.length); }, 'program-date-time'() { if (typeof this.manifest.dateTimeString === 'undefined') { // PROGRAM-DATE-TIME is a media-segment tag, but for backwards // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag // to the manifest object // TODO: Consider removing this in future major version this.manifest.dateTimeString = entry.dateTimeString; this.manifest.dateTimeObject = entry.dateTimeObject; } currentUri.dateTimeString = entry.dateTimeString; currentUri.dateTimeObject = entry.dateTimeObject; const { lastProgramDateTime } = this; this.lastProgramDateTime = new Date(entry.dateTimeString).getTime(); // We should extrapolate Program Date Time backward only during first program date time occurrence. // Once we have at least one program date time point, we can always extrapolate it forward using lastProgramDateTime reference. if (lastProgramDateTime === null) { // Extrapolate Program Date Time backward // Since it is first program date time occurrence we're assuming that // all this.manifest.segments have no program date time info this.manifest.segments.reduceRight((programDateTime, segment) => { segment.programDateTime = programDateTime - segment.duration * 1000; return segment.programDateTime; }, this.lastProgramDateTime); } }, targetduration() { if (!isFinite(entry.duration) || entry.duration < 0) { this.trigger('warn', { message: 'ignoring invalid target duration: ' + entry.duration }); return; } this.manifest.targetDuration = entry.duration; setHoldBack.call(this, this.manifest); }, start() { if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) { this.trigger('warn', { message: 'ignoring start declaration without appropriate attribute list' }); return; } this.manifest.start = { timeOffset: entry.attributes['TIME-OFFSET'], precise: entry.attributes.PRECISE }; }, 'cue-out'() { currentUri.cueOut = entry.data; }, 'cue-out-cont'() { currentUri.cueOutCont = entry.data; }, 'cue-in'() { currentUri.cueIn = entry.data; }, 'skip'() { this.manifest.skip = camelCaseKeys(entry.attributes); this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']); }, 'part'() { hasParts = true; // parts are always specifed before a segment const segmentIndex = this.manifest.segments.length; const part = camelCaseKeys(entry.attributes); currentUri.parts = currentUri.parts || []; currentUri.parts.push(part); if (part.byterange) { if (!part.byterange.hasOwnProperty('offset')) { part.byterange.offset = lastPartByterangeEnd; } lastPartByterangeEnd = part.byterange.offset + part.byterange.length; } const partIndex = currentUri.parts.length - 1; this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']); if (this.manifest.renditionReports) { this.manifest.renditionReports.forEach((r, i) => { if (!r.hasOwnProperty('lastPart')) { this.trigger('warn', { message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART` }); } }); } }, 'server-control'() { const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes); if (!attrs.hasOwnProperty('canBlockReload')) { attrs.canBlockReload = false; this.trigger('info', { message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false' }); } setHoldBack.call(this, this.manifest); if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) { this.trigger('warn', { message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set' }); } }, 'preload-hint'() { // parts are always specifed before a segment const segmentIndex = this.manifest.segments.length; const hint = camelCaseKeys(entry.attributes); const isPart = hint.type && hint.type === 'PART'; currentUri.preloadHints = currentUri.preloadHints || []; currentUri.preloadHints.push(hint); if (hint.byterange) { if (!hint.byterange.hasOwnProperty('offset')) { // use last part byterange end or zero if not a part. hint.byterange.offset = isPart ? lastPartByterangeEnd : 0; if (isPart) { lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length; } } } const index = currentUri.preloadHints.length - 1; this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']); if (!hint.type) { return; } // search through all preload hints except for the current one for // a duplicate type. for (let i = 0; i < currentUri.preloadHints.length - 1; i++) { const otherHint = currentUri.preloadHints[i]; if (!otherHint.type) { continue; } if (otherHint.type === hint.type) { this.trigger('warn', { message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}` }); } } }, 'rendition-report'() { const report = camelCaseKeys(entry.attributes); this.manifest.renditionReports = this.manifest.renditionReports || []; this.manifest.renditionReports.push(report); const index = this.manifest.renditionReports.length - 1; const required = ['LAST-MSN', 'URI']; if (hasParts) { required.push('LAST-PART'); } this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required); }, 'part-inf'() { this.manifest.partInf = camelCaseKeys(entry.attributes); this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']); if (this.manifest.partInf.partTarget) { this.manifest.partTargetDuration = this.manifest.partInf.partTarget; } setHoldBack.call(this, this.manifest); }, 'daterange'() { this.manifest.dateRanges.push(camelCaseKeys(entry.attributes)); const index = this.manifest.dateRanges.length - 1; this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']); const dateRange = this.manifest.dateRanges[index]; if (dateRange.endDate && dateRange.startDate && new Date(dateRange.endDate) < new Date(dateRange.startDate)) { this.trigger('warn', { message: 'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE' }); } if (dateRange.duration && dateRange.duration < 0) { this.trigger('warn', { message: 'EXT-X-DATERANGE DURATION must not be negative' }); } if (dateRange.plannedDuration && dateRange.plannedDuration < 0) { this.trigger('warn', { message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative' }); } const endOnNextYes = !!dateRange.endOnNext; if (endOnNextYes && !dateRange.class) { this.trigger('warn', { message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute' }); } if (endOnNextYes && (dateRange.duration || dateRange.endDate)) { this.trigger('warn', { message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes' }); } if (dateRange.duration && dateRange.endDate) { const startDate = dateRange.startDate; const newDateInSeconds = startDate.getTime() + dateRange.duration * 1000; this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds); } if (!dateRangeTags[dateRange.id]) { dateRangeTags[dateRange.id] = dateRange; } else { for (const attribute in dateRangeTags[dateRange.id]) { if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) { this.trigger('warn', { message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values' }); break; } } // if tags with the same ID do not have conflicting attributes, merge them const dateRangeWithSameId = this.manifest.dateRanges.findIndex(dateRangeToFind => dateRangeToFind.id === dateRange.id); this.manifest.dateRanges[dateRangeWithSameId] = _extends(this.manifest.dateRanges[dateRangeWithSameId], dateRange); dateRangeTags[dateRange.id] = _extends(dateRangeTags[dateRange.id], dateRange); // after merging, delete the duplicate dateRange that was added last this.manifest.dateRanges.pop(); } }, 'independent-segments'() { this.manifest.independentSegments = true; }, 'i-frames-only'() { this.manifest.iFramesOnly = true; this.requiredCompatibilityversion(this.manifest.version, 4); }, 'content-steering'() { this.manifest.contentSteering = camelCaseKeys(entry.attributes); this.warnOnMissingAttributes_('#EXT-X-CONTENT-STEERING', entry.attributes, ['SERVER-URI']); }, /** @this {Parser} */ define() { this.manifest.definitions = this.manifest.definitions || {}; const addDef = (n, v) => { if (n in this.manifest.definitions) { // An EXT-X-DEFINE tag MUST NOT specify the same Variable Name as any other // EXT-X-DEFINE tag in the same Playlist. Parsers that encounter duplicate // Variable Name declarations MUST fail to parse the Playlist. this.trigger('error', { message: `EXT-X-DEFINE: Duplicate name ${n}` }); return; } this.manifest.definitions[n] = v; }; if ('QUERYPARAM' in entry.attributes) { if ('NAME' in entry.attributes || 'IMPORT' in entry.attributes) { // An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a // QUERYPARAM attribute, but only one of the three. Otherwise, the // client MUST fail to parse the Playlist. this.trigger('error', { message: 'EXT-X-DEFINE: Invalid attributes' }); return; } const val = this.params.get(entry.attributes.QUERYPARAM); if (!val) { // If the QUERYPARAM attribute value does not match any query parameter in // the URI or the matching parameter has no associated value, the parser // MUST fail to parse the Playlist. If more than one parameter matches, // any of the associated values MAY be used. this.trigger('error', { message: `EXT-X-DEFINE: No query param ${entry.attributes.QUERYPARAM}` }); return; } addDef(entry.attributes.QUERYPARAM, decodeURIComponent(val)); return; } if ('NAME' in entry.attributes) { if ('IMPORT' in entry.attributes) { // An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a // QUERYPARAM attribute, but only one of the three. Otherwise, the // client MUST fail to parse the Playlist. this.trigger('error', { message: 'EXT-X-DEFINE: Invalid attributes' }); return; } if (!('VALUE' in entry.attributes) || typeof entry.attributes.VALUE !== 'string') { // This attribute is REQUIRED if the EXT-X-DEFINE tag has a NAME attribute. // The quoted-string MAY be empty. this.trigger('error', { message: `EXT-X-DEFINE: No value for ${entry.attributes.NAME}` }); return; } addDef(entry.attributes.NAME, entry.attributes.VALUE); return; } if ('IMPORT' in entry.attributes) { if (!this.mainDefinitions[entry.attributes.IMPORT]) { // Covers two conditions, as mainDefinitions will always be empty on main // // EXT-X-DEFINE tags containing the IMPORT attribute MUST NOT occur in // Multivariant Playlists; they are only allowed in Media Playlists. // // If the IMPORT attribute value does not match any Variable Name in the // Multivariant Playlist, or if the Media Playlist loaded from a // Multivariant Playlist, the parser MUST fail the Playlist. this.trigger('error', { message: `EXT-X-DEFINE: No value ${entry.attributes.IMPORT} to import, or IMPORT used on main playlist` }); return; } addDef(entry.attributes.IMPORT, this.mainDefinitions[entry.attributes.IMPORT]); return; } // An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a QUERYPARAM // attribute, but only one of the three. Otherwise, the client MUST fail to // parse the Playlist. this.trigger('error', { message: 'EXT-X-DEFINE: No attribute' }); }, 'i-frame-playlist'() { this.manifest.iFramePlaylists.push({ attributes: entry.attributes, uri: entry.uri, timeline: currentTimeline }); this.warnOnMissingAttributes_('#EXT-X-I-FRAME-STREAM-INF', entry.attributes, ['BANDWIDTH', 'URI']); } })[entry.tagType] || noop).call(self); }, uri() { currentUri.uri = entry.uri; uris.push(currentUri); // if no explicit duration was declared, use the target duration if (this.manifest.targetDuration && !('duration' in currentUri)) { this.trigger('warn', { message: 'defaulting segment duration to the target duration' }); currentUri.duration = this.manifest.targetDuration; } // annotate with encryption information, if necessary if (key) { currentUri.key = key; } currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary if (currentMap) { currentUri.map = currentMap; } // reset the last byterange end as it needs to be 0 between parts lastPartByterangeEnd = 0; // Once we have at least one program date time we can always extrapolate it forward if (this.lastProgramDateTime !== null) { currentUri.programDateTime = this.lastProgramDateTime; this.lastProgramDateTime += currentUri.duration * 1000; } // prepare for the next URI currentUri = {}; }, comment() {// comments are not important for playback }, custom() { // if this is segment-level data attach the output to the segment if (entry.segment) { currentUri.custom = currentUri.custom || {}; currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object } else { this.manifest.custom = this.manifest.custom || {}; this.manifest.custom[entry.customType] = entry.data; } } })[entry.type].call(self); }); } requiredCompatibilityversion(currentVersion, targetVersion) { if (currentVersion < targetVersion || !currentVersion) { this.trigger('warn', { message: `manifest must be at least version ${targetVersion}` }); } } warnOnMissingAttributes_(identifier, attributes, required) { const missing = []; required.forEach(function (key) { if (!attributes.hasOwnProperty(key)) { missing.push(key); } }); if (missing.length) { this.trigger('warn', { message: `${identifier} lacks required attribute(s): ${missing.join(', ')}` }); } } /** * Parse the input string and update the manifest object. * * @param {string} chunk a potentially incomplete portion of the manifest */ push(chunk) { this.lineStream.push(chunk); } /** * Flush any remaining input. This can be handy if the last line of an M3U8 * manifest did not contain a trailing newline but the file has been * completely received. */ end() { // flush any buffered input this.lineStream.push('\n'); if (this.manifest.dateRanges.length && this.lastProgramDateTime === null) { this.trigger('warn', { message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag' }); } this.lastProgramDateTime = null; this.trigger('end'); } /** * Add an additional parser for non-standard tags * * @param {Object} options a map of options for the added parser * @param {RegExp} options.expression a regular expression to match the custom header * @param {string} options.customType the custom type to register to the output * @param {Function} [options.dataParser] function to parse the line into an object * @param {boolean} [options.segment] should tag data be attached to the segment object */ addParser(options) { this.parseStream.addParser(options); } /** * Add a custom header mapper * * @param {Object} options * @param {RegExp} options.expression a regular expression to match the custom header * @param {Function} options.map function to translate tag into a different tag */ addTagMapper(options) { this.parseStream.addTagMapper(options); } } ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/codecs.js var regexs = { // to determine mime types mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/, webm: /^(vp0?[89]|av0?1|opus|vorbis)/, ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/, // to determine if a codec is audio or video video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/, audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/, text: /^(stpp.ttml.im1t)/, // mux.js support regex muxerVideo: /^(avc0?1)/, muxerAudio: /^(mp4a)/, // match nothing as muxer does not support text right now. // there cannot never be a character before the start of a string // so this matches nothing. muxerText: /a^/ }; var mediaTypes = ['video', 'audio', 'text']; var upperMediaTypes = ['Video', 'Audio', 'Text']; /** * Replace the old apple-style `avc1.
.
` codec string with the standard * `avc1.` * * @param {string} codec * Codec string to translate * @return {string} * The translated codec string */ var translateLegacyCodec = function translateLegacyCodec(codec) { if (!codec) { return codec; } return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { var profileHex = ('00' + Number(profile).toString(16)).slice(-2); var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); return 'avc1.' + profileHex + '00' + avcLevelHex; }); }; /** * Replace the old apple-style `avc1.
.
` codec strings with the standard * `avc1.` * * @param {string[]} codecs * An array of codec strings to translate * @return {string[]} * The translated array of codec strings */ var translateLegacyCodecs = function translateLegacyCodecs(codecs) { return codecs.map(translateLegacyCodec); }; /** * Replace codecs in the codec string with the old apple-style `avc1.
.
` to the * standard `avc1.`. * * @param {string} codecString * The codec string * @return {string} * The codec string with old apple-style codecs replaced * * @private */ var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) { return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) { return translateLegacyCodecs([match])[0]; }); }; /** * @typedef {Object} ParsedCodecInfo * @property {number} codecCount * Number of codecs parsed * @property {string} [videoCodec] * Parsed video codec (if found) * @property {string} [videoObjectTypeIndicator] * Video object type indicator (if found) * @property {string|null} audioProfile * Audio profile */ /** * Parses a codec string to retrieve the number of codecs specified, the video codec and * object type indicator, and the audio profile. * * @param {string} [codecString] * The codec string to parse * @return {ParsedCodecInfo} * Parsed codec info */ var parseCodecs = function parseCodecs(codecString) { if (codecString === void 0) { codecString = ''; } var codecs = codecString.split(','); var result = []; codecs.forEach(function (codec) { codec = codec.trim(); var codecType; mediaTypes.forEach(function (name) { var match = regexs[name].exec(codec.toLowerCase()); if (!match || match.length <= 1) { return; } codecType = name; // maintain codec case var type = codec.substring(0, match[1].length); var details = codec.replace(type, ''); result.push({ type: type, details: details, mediaType: name }); }); if (!codecType) { result.push({ type: codec, details: '', mediaType: 'unknown' }); } }); return result; }; /** * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is * a default alternate audio playlist for the provided audio group. * * @param {Object} master * The master playlist * @param {string} audioGroupId * ID of the audio group for which to find the default codec info * @return {ParsedCodecInfo} * Parsed codec info */ var codecsFromDefault = function codecsFromDefault(master, audioGroupId) { if (!master.mediaGroups.AUDIO || !audioGroupId) { return null; } var audioGroup = master.mediaGroups.AUDIO[audioGroupId]; if (!audioGroup) { return null; } for (var name in audioGroup) { var audioType = audioGroup[name]; if (audioType.default && audioType.playlists) { // codec should be the same for all playlists within the audio type return parseCodecs(audioType.playlists[0].attributes.CODECS); } } return null; }; var isVideoCodec = function isVideoCodec(codec) { if (codec === void 0) { codec = ''; } return regexs.video.test(codec.trim().toLowerCase()); }; var isAudioCodec = function isAudioCodec(codec) { if (codec === void 0) { codec = ''; } return regexs.audio.test(codec.trim().toLowerCase()); }; var isTextCodec = function isTextCodec(codec) { if (codec === void 0) { codec = ''; } return regexs.text.test(codec.trim().toLowerCase()); }; var getMimeForCodec = function getMimeForCodec(codecString) { if (!codecString || typeof codecString !== 'string') { return; } var codecs = codecString.toLowerCase().split(',').map(function (c) { return translateLegacyCodec(c.trim()); }); // default to video type var type = 'video'; // only change to audio type if the only codec we have is // audio if (codecs.length === 1 && isAudioCodec(codecs[0])) { type = 'audio'; } else if (codecs.length === 1 && isTextCodec(codecs[0])) { // text uses application/ for now type = 'application'; } // default the container to mp4 var container = 'mp4'; // every codec must be able to go into the container // for that container to be the correct one if (codecs.every(function (c) { return regexs.mp4.test(c); })) { container = 'mp4'; } else if (codecs.every(function (c) { return regexs.webm.test(c); })) { container = 'webm'; } else if (codecs.every(function (c) { return regexs.ogg.test(c); })) { container = 'ogg'; } return type + "/" + container + ";codecs=\"" + codecString + "\""; }; /** * Tests whether the codec is supported by MediaSource. Optionally also tests ManagedMediaSource. * * @param {string} codecString * Codec to test * @param {boolean} [withMMS] * Whether to check if ManagedMediaSource supports it * @return {boolean} * Codec is supported */ var browserSupportsCodec = function browserSupportsCodec(codecString, withMMS) { if (codecString === void 0) { codecString = ''; } if (withMMS === void 0) { withMMS = false; } return (window_default()).MediaSource && (window_default()).MediaSource.isTypeSupported && window_default().MediaSource.isTypeSupported(getMimeForCodec(codecString)) || withMMS && (window_default()).ManagedMediaSource && (window_default()).ManagedMediaSource.isTypeSupported && window_default().ManagedMediaSource.isTypeSupported(getMimeForCodec(codecString)) || false; }; var muxerSupportsCodec = function muxerSupportsCodec(codecString) { if (codecString === void 0) { codecString = ''; } return codecString.toLowerCase().split(',').every(function (codec) { codec = codec.trim(); // any match is supported. for (var i = 0; i < upperMediaTypes.length; i++) { var type = upperMediaTypes[i]; if (regexs["muxer" + type].test(codec)) { return true; } } return false; }); }; var DEFAULT_AUDIO_CODEC = 'mp4a.40.2'; var DEFAULT_VIDEO_CODEC = 'avc1.4d400d'; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/media-types.js var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; var DASH_REGEX = /^application\/dash\+xml/i; /** * Returns a string that describes the type of source based on a video source object's * media type. * * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type} * * @param {string} type * Video source object media type * @return {('hls'|'dash'|'vhs-json'|null)} * VHS source type string */ var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) { if (MPEGURL_REGEX.test(type)) { return 'hls'; } if (DASH_REGEX.test(type)) { return 'dash'; } // Denotes the special case of a manifest object passed to http-streaming instead of a // source URL. // // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types. // // In this case, vnd stands for vendor, video.js for the organization, VHS for this // project, and the +json suffix identifies the structure of the media type. if (type === 'application/vnd.videojs.vhs+json') { return 'vhs-json'; } return null; }; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/byte-helpers.js // const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2)); var repeat = function repeat(str, len) { var acc = ''; while (len--) { acc += str; } return acc; }; // count the number of bits it would take to represent a number // we used to do this with log2 but BigInt does not support builtin math // Math.ceil(log2(x)); var countBits = function countBits(x) { return x.toString(2).length; }; // count the number of whole bytes it would take to represent a number var countBytes = function countBytes(x) { return Math.ceil(countBits(x) / 8); }; var byte_helpers_padStart = function padStart(b, len, str) { if (str === void 0) { str = ' '; } return (repeat(str, len) + b.toString()).slice(-len); }; var isArrayBufferView = function isArrayBufferView(obj) { if (ArrayBuffer.isView === 'function') { return ArrayBuffer.isView(obj); } return obj && obj.buffer instanceof ArrayBuffer; }; var isTypedArray = function isTypedArray(obj) { return isArrayBufferView(obj); }; var byte_helpers_toUint8 = function toUint8(bytes) { if (bytes instanceof Uint8Array) { return bytes; } if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) { // any non-number or NaN leads to empty uint8array // eslint-disable-next-line if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) { bytes = 0; } else { bytes = [bytes]; } } return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0); }; var byte_helpers_toHexString = function toHexString(bytes) { bytes = byte_helpers_toUint8(bytes); var str = ''; for (var i = 0; i < bytes.length; i++) { str += byte_helpers_padStart(bytes[i].toString(16), 2, '0'); } return str; }; var byte_helpers_toBinaryString = function toBinaryString(bytes) { bytes = byte_helpers_toUint8(bytes); var str = ''; for (var i = 0; i < bytes.length; i++) { str += byte_helpers_padStart(bytes[i].toString(2), 8, '0'); } return str; }; var BigInt = (window_default()).BigInt || Number; var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')]; var ENDIANNESS = function () { var a = new Uint16Array([0xFFCC]); var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength); if (b[0] === 0xFF) { return 'big'; } if (b[0] === 0xCC) { return 'little'; } return 'unknown'; }(); var IS_BIG_ENDIAN = ENDIANNESS === 'big'; var IS_LITTLE_ENDIAN = ENDIANNESS === 'little'; var byte_helpers_bytesToNumber = function bytesToNumber(bytes, _temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$signed = _ref.signed, signed = _ref$signed === void 0 ? false : _ref$signed, _ref$le = _ref.le, le = _ref$le === void 0 ? false : _ref$le; bytes = byte_helpers_toUint8(bytes); var fn = le ? 'reduce' : 'reduceRight'; var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn]; var number = obj.call(bytes, function (total, byte, i) { var exponent = le ? i : Math.abs(i + 1 - bytes.length); return total + BigInt(byte) * BYTE_TABLE[exponent]; }, BigInt(0)); if (signed) { var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1); number = BigInt(number); if (number > max) { number -= max; number -= max; number -= BigInt(2); } } return Number(number); }; var numberToBytes = function numberToBytes(number, _temp2) { var _ref2 = _temp2 === void 0 ? {} : _temp2, _ref2$le = _ref2.le, le = _ref2$le === void 0 ? false : _ref2$le; // eslint-disable-next-line if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) { number = 0; } number = BigInt(number); var byteCount = countBytes(number); var bytes = new Uint8Array(new ArrayBuffer(byteCount)); for (var i = 0; i < byteCount; i++) { var byteIndex = le ? i : Math.abs(i + 1 - bytes.length); bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF)); if (number < 0) { bytes[byteIndex] = Math.abs(~bytes[byteIndex]); bytes[byteIndex] -= i === 0 ? 1 : 2; } } return bytes; }; var byte_helpers_bytesToString = function bytesToString(bytes) { if (!bytes) { return ''; } // TODO: should toUint8 handle cases where we only have 8 bytes // but report more since this is a Uint16+ Array? bytes = Array.prototype.slice.call(bytes); var string = String.fromCharCode.apply(null, byte_helpers_toUint8(bytes)); try { return decodeURIComponent(escape(string)); } catch (e) {// if decodeURIComponent/escape fails, we are dealing with partial // or full non string data. Just return the potentially garbled string. } return string; }; var stringToBytes = function stringToBytes(string, stringIsBytes) { if (typeof string !== 'string' && string && typeof string.toString === 'function') { string = string.toString(); } if (typeof string !== 'string') { return new Uint8Array(); } // If the string already is bytes, we don't have to do this // otherwise we do this so that we split multi length characters // into individual bytes if (!stringIsBytes) { string = unescape(encodeURIComponent(string)); } var view = new Uint8Array(string.length); for (var i = 0; i < string.length; i++) { view[i] = string.charCodeAt(i); } return view; }; var concatTypedArrays = function concatTypedArrays() { for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) { buffers[_key] = arguments[_key]; } buffers = buffers.filter(function (b) { return b && (b.byteLength || b.length) && typeof b !== 'string'; }); if (buffers.length <= 1) { // for 0 length we will return empty uint8 // for 1 length we return the first uint8 return byte_helpers_toUint8(buffers[0]); } var totalLen = buffers.reduce(function (total, buf, i) { return total + (buf.byteLength || buf.length); }, 0); var tempBuffer = new Uint8Array(totalLen); var offset = 0; buffers.forEach(function (buf) { buf = byte_helpers_toUint8(buf); tempBuffer.set(buf, offset); offset += buf.byteLength; }); return tempBuffer; }; /** * Check if the bytes "b" are contained within bytes "a". * * @param {Uint8Array|Array} a * Bytes to check in * * @param {Uint8Array|Array} b * Bytes to check for * * @param {Object} options * options * * @param {Array|Uint8Array} [offset=0] * offset to use when looking at bytes in a * * @param {Array|Uint8Array} [mask=[]] * mask to use on bytes before comparison. * * @return {boolean} * If all bytes in b are inside of a, taking into account * bit masks. */ var byte_helpers_bytesMatch = function bytesMatch(a, b, _temp3) { var _ref3 = _temp3 === void 0 ? {} : _temp3, _ref3$offset = _ref3.offset, offset = _ref3$offset === void 0 ? 0 : _ref3$offset, _ref3$mask = _ref3.mask, mask = _ref3$mask === void 0 ? [] : _ref3$mask; a = byte_helpers_toUint8(a); b = byte_helpers_toUint8(b); // ie 11 does not support uint8 every var fn = b.every ? b.every : Array.prototype.every; return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8 fn.call(b, function (bByte, i) { var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i]; return bByte === aByte; }); }; var sliceBytes = function sliceBytes(src, start, end) { if (Uint8Array.prototype.slice) { return Uint8Array.prototype.slice.call(src, start, end); } return new Uint8Array(Array.prototype.slice.call(src, start, end)); }; var reverseBytes = function reverseBytes(src) { if (src.reverse) { return src.reverse(); } return Array.prototype.reverse.call(src); }; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/media-groups.js /** * Loops through all supported media groups in master and calls the provided * callback for each group * * @param {Object} master * The parsed master manifest object * @param {string[]} groups * The media groups to call the callback for * @param {Function} callback * Callback to call for each media group */ var forEachMediaGroup = function forEachMediaGroup(master, groups, callback) { groups.forEach(function (mediaType) { for (var groupKey in master.mediaGroups[mediaType]) { for (var labelKey in master.mediaGroups[mediaType][groupKey]) { var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey]; callback(mediaProperties, mediaType, groupKey, labelKey); } } }); }; // EXTERNAL MODULE: ./node_modules/@xmldom/xmldom/lib/index.js var xmldom_lib = __webpack_require__(969); ;// CONCATENATED MODULE: ./node_modules/mpd-parser/dist/mpd-parser.es.js /*! @name mpd-parser @version 1.3.1 @license Apache-2.0 */ var version = "1.3.1"; const mpd_parser_es_isObject = obj => { return !!obj && typeof obj === 'object'; }; const merge = (...objects) => { return objects.reduce((result, source) => { if (typeof source !== 'object') { return result; } Object.keys(source).forEach(key => { if (Array.isArray(result[key]) && Array.isArray(source[key])) { result[key] = result[key].concat(source[key]); } else if (mpd_parser_es_isObject(result[key]) && mpd_parser_es_isObject(source[key])) { result[key] = merge(result[key], source[key]); } else { result[key] = source[key]; } }); return result; }, {}); }; const values = o => Object.keys(o).map(k => o[k]); const range = (start, end) => { const result = []; for (let i = start; i < end; i++) { result.push(i); } return result; }; const flatten = lists => lists.reduce((x, y) => x.concat(y), []); const from = list => { if (!list.length) { return []; } const result = []; for (let i = 0; i < list.length; i++) { result.push(list[i]); } return result; }; const findIndexes = (l, key) => l.reduce((a, e, i) => { if (e[key]) { a.push(i); } return a; }, []); /** * Returns a union of the included lists provided each element can be identified by a key. * * @param {Array} list - list of lists to get the union of * @param {Function} keyFunction - the function to use as a key for each element * * @return {Array} the union of the arrays */ const union = (lists, keyFunction) => { return values(lists.reduce((acc, list) => { list.forEach(el => { acc[keyFunction(el)] = el; }); return acc; }, {})); }; var errors = { INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD', INVALID_NUMBER_OF_CONTENT_STEERING: 'INVALID_NUMBER_OF_CONTENT_STEERING', DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST', DASH_INVALID_XML: 'DASH_INVALID_XML', NO_BASE_URL: 'NO_BASE_URL', MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED', UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME' }; /** * @typedef {Object} SingleUri * @property {string} uri - relative location of segment * @property {string} resolvedUri - resolved location of segment * @property {Object} byterange - Object containing information on how to make byte range * requests following byte-range-spec per RFC2616. * @property {String} byterange.length - length of range request * @property {String} byterange.offset - byte offset of range request * * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 */ /** * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object * that conforms to how m3u8-parser is structured * * @see https://github.com/videojs/m3u8-parser * * @param {string} baseUrl - baseUrl provided by nodes * @param {string} source - source url for segment * @param {string} range - optional range used for range calls, * follows RFC 2616, Clause 14.35.1 * @return {SingleUri} full segment information transformed into a format similar * to m3u8-parser */ const urlTypeToSegment = ({ baseUrl = '', source = '', range = '', indexRange = '' }) => { const segment = { uri: source, resolvedUri: resolve_url(baseUrl || '', source) }; if (range || indexRange) { const rangeStr = range ? range : indexRange; const ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible let startRange = (window_default()).BigInt ? window_default().BigInt(ranges[0]) : parseInt(ranges[0], 10); let endRange = (window_default()).BigInt ? window_default().BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER if (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') { startRange = Number(startRange); } if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') { endRange = Number(endRange); } let length; if (typeof endRange === 'bigint' || typeof startRange === 'bigint') { length = window_default().BigInt(endRange) - window_default().BigInt(startRange) + window_default().BigInt(1); } else { length = endRange - startRange + 1; } if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) { length = Number(length); } // byterange should be inclusive according to // RFC 2616, Clause 14.35.1 segment.byterange = { length, offset: startRange }; } return segment; }; const byteRangeToString = byterange => { // `endRange` is one less than `offset + length` because the HTTP range // header uses inclusive ranges let endRange; if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') { endRange = window_default().BigInt(byterange.offset) + window_default().BigInt(byterange.length) - window_default().BigInt(1); } else { endRange = byterange.offset + byterange.length - 1; } return `${byterange.offset}-${endRange}`; }; /** * parse the end number attribue that can be a string * number, or undefined. * * @param {string|number|undefined} endNumber * The end number attribute. * * @return {number|null} * The result of parsing the end number. */ const parseEndNumber = endNumber => { if (endNumber && typeof endNumber !== 'number') { endNumber = parseInt(endNumber, 10); } if (isNaN(endNumber)) { return null; } return endNumber; }; /** * Functions for calculating the range of available segments in static and dynamic * manifests. */ const segmentRange = { /** * Returns the entire range of available segments for a static MPD * * @param {Object} attributes * Inheritied MPD attributes * @return {{ start: number, end: number }} * The start and end numbers for available segments */ static(attributes) { const { duration, timescale = 1, sourceDuration, periodDuration } = attributes; const endNumber = parseEndNumber(attributes.endNumber); const segmentDuration = duration / timescale; if (typeof endNumber === 'number') { return { start: 0, end: endNumber }; } if (typeof periodDuration === 'number') { return { start: 0, end: periodDuration / segmentDuration }; } return { start: 0, end: sourceDuration / segmentDuration }; }, /** * Returns the current live window range of available segments for a dynamic MPD * * @param {Object} attributes * Inheritied MPD attributes * @return {{ start: number, end: number }} * The start and end numbers for available segments */ dynamic(attributes) { const { NOW, clientOffset, availabilityStartTime, timescale = 1, duration, periodStart = 0, minimumUpdatePeriod = 0, timeShiftBufferDepth = Infinity } = attributes; const endNumber = parseEndNumber(attributes.endNumber); // clientOffset is passed in at the top level of mpd-parser and is an offset calculated // after retrieving UTC server time. const now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock. // Convert the period start time to EPOCH. const periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update. const periodEndWC = now + minimumUpdatePeriod; const periodDuration = periodEndWC - periodStartWC; const segmentCount = Math.ceil(periodDuration * timescale / duration); const availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration); const availableEnd = Math.floor((now - periodStartWC) * timescale / duration); return { start: Math.max(0, availableStart), end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd) }; } }; /** * Maps a range of numbers to objects with information needed to build the corresponding * segment list * * @name toSegmentsCallback * @function * @param {number} number * Number of the segment * @param {number} index * Index of the number in the range list * @return {{ number: Number, duration: Number, timeline: Number, time: Number }} * Object with segment timing and duration info */ /** * Returns a callback for Array.prototype.map for mapping a range of numbers to * information needed to build the segment list. * * @param {Object} attributes * Inherited MPD attributes * @return {toSegmentsCallback} * Callback map function */ const toSegments = attributes => number => { const { duration, timescale = 1, periodStart, startNumber = 1 } = attributes; return { number: startNumber + number, duration: duration / timescale, timeline: periodStart, time: number * duration }; }; /** * Returns a list of objects containing segment timing and duration info used for * building the list of segments. This uses the @duration attribute specified * in the MPD manifest to derive the range of segments. * * @param {Object} attributes * Inherited MPD attributes * @return {{number: number, duration: number, time: number, timeline: number}[]} * List of Objects with segment timing and duration info */ const parseByDuration = attributes => { const { type, duration, timescale = 1, periodDuration, sourceDuration } = attributes; const { start, end } = segmentRange[type](attributes); const segments = range(start, end).map(toSegments(attributes)); if (type === 'static') { const index = segments.length - 1; // section is either a period or the full source const sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration segments[index].duration = sectionDuration - duration / timescale * index; } return segments; }; /** * Translates SegmentBase into a set of segments. * (DASH SPEC Section 5.3.9.3.2) contains a set of nodes. Each * node should be translated into segment. * * @param {Object} attributes * Object containing all inherited attributes from parent elements with attribute * names as keys * @return {Object.} list of segments */ const segmentsFromBase = attributes => { const { baseUrl, initialization = {}, sourceDuration, indexRange = '', periodStart, presentationTime, number = 0, duration } = attributes; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) if (!baseUrl) { throw new Error(errors.NO_BASE_URL); } const initSegment = urlTypeToSegment({ baseUrl, source: initialization.sourceURL, range: initialization.range }); const segment = urlTypeToSegment({ baseUrl, source: baseUrl, indexRange }); segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source // (since SegmentBase is only for one total segment) if (duration) { const segmentTimeInfo = parseByDuration(attributes); if (segmentTimeInfo.length) { segment.duration = segmentTimeInfo[0].duration; segment.timeline = segmentTimeInfo[0].timeline; } } else if (sourceDuration) { segment.duration = sourceDuration; segment.timeline = periodStart; } // If presentation time is provided, these segments are being generated by SIDX // references, and should use the time provided. For the general case of SegmentBase, // there should only be one segment in the period, so its presentation time is the same // as its period start. segment.presentationTime = presentationTime || periodStart; segment.number = number; return [segment]; }; /** * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist * according to the sidx information given. * * playlist.sidx has metadadata about the sidx where-as the sidx param * is the parsed sidx box itself. * * @param {Object} playlist the playlist to update the sidx information for * @param {Object} sidx the parsed sidx box * @return {Object} the playlist object with the updated sidx information */ const addSidxSegmentsToPlaylist$1 = (playlist, sidx, baseUrl) => { // Retain init segment information const initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing const sourceDuration = playlist.sidx.duration; // Retain source timeline const timeline = playlist.timeline || 0; const sidxByteRange = playlist.sidx.byterange; const sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx const timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes const mediaReferences = sidx.references.filter(r => r.referenceType !== 1); const segments = []; const type = playlist.endList ? 'static' : 'dynamic'; const periodStart = playlist.sidx.timeline; let presentationTime = periodStart; let number = playlist.mediaSequence || 0; // firstOffset is the offset from the end of the sidx box let startIndex; // eslint-disable-next-line if (typeof sidx.firstOffset === 'bigint') { startIndex = window_default().BigInt(sidxEnd) + sidx.firstOffset; } else { startIndex = sidxEnd + sidx.firstOffset; } for (let i = 0; i < mediaReferences.length; i++) { const reference = sidx.references[i]; // size of the referenced (sub)segment const size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale // this will be converted to seconds when generating segments const duration = reference.subsegmentDuration; // should be an inclusive range let endIndex; // eslint-disable-next-line if (typeof startIndex === 'bigint') { endIndex = startIndex + window_default().BigInt(size) - window_default().BigInt(1); } else { endIndex = startIndex + size - 1; } const indexRange = `${startIndex}-${endIndex}`; const attributes = { baseUrl, timescale, timeline, periodStart, presentationTime, number, duration, sourceDuration, indexRange, type }; const segment = segmentsFromBase(attributes)[0]; if (initSegment) { segment.map = initSegment; } segments.push(segment); if (typeof startIndex === 'bigint') { startIndex += window_default().BigInt(size); } else { startIndex += size; } presentationTime += duration / timescale; number++; } playlist.segments = segments; return playlist; }; const SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen) const TIME_FUDGE = 1 / 60; /** * Given a list of timelineStarts, combines, dedupes, and sorts them. * * @param {TimelineStart[]} timelineStarts - list of timeline starts * * @return {TimelineStart[]} the combined and deduped timeline starts */ const getUniqueTimelineStarts = timelineStarts => { return union(timelineStarts, ({ timeline }) => timeline).sort((a, b) => a.timeline > b.timeline ? 1 : -1); }; /** * Finds the playlist with the matching NAME attribute. * * @param {Array} playlists - playlists to search through * @param {string} name - the NAME attribute to search for * * @return {Object|null} the matching playlist object, or null */ const findPlaylistWithName = (playlists, name) => { for (let i = 0; i < playlists.length; i++) { if (playlists[i].attributes.NAME === name) { return playlists[i]; } } return null; }; /** * Gets a flattened array of media group playlists. * * @param {Object} manifest - the main manifest object * * @return {Array} the media group playlists */ const getMediaGroupPlaylists = manifest => { let mediaGroupPlaylists = []; forEachMediaGroup(manifest, SUPPORTED_MEDIA_TYPES, (properties, type, group, label) => { mediaGroupPlaylists = mediaGroupPlaylists.concat(properties.playlists || []); }); return mediaGroupPlaylists; }; /** * Updates the playlist's media sequence numbers. * * @param {Object} config - options object * @param {Object} config.playlist - the playlist to update * @param {number} config.mediaSequence - the mediaSequence number to start with */ const updateMediaSequenceForPlaylist = ({ playlist, mediaSequence }) => { playlist.mediaSequence = mediaSequence; playlist.segments.forEach((segment, index) => { segment.number = playlist.mediaSequence + index; }); }; /** * Updates the media and discontinuity sequence numbers of newPlaylists given oldPlaylists * and a complete list of timeline starts. * * If no matching playlist is found, only the discontinuity sequence number of the playlist * will be updated. * * Since early available timelines are not supported, at least one segment must be present. * * @param {Object} config - options object * @param {Object[]} oldPlaylists - the old playlists to use as a reference * @param {Object[]} newPlaylists - the new playlists to update * @param {Object} timelineStarts - all timelineStarts seen in the stream to this point */ const updateSequenceNumbers = ({ oldPlaylists, newPlaylists, timelineStarts }) => { newPlaylists.forEach(playlist => { playlist.discontinuitySequence = timelineStarts.findIndex(function ({ timeline }) { return timeline === playlist.timeline; }); // Playlists NAMEs come from DASH Representation IDs, which are mandatory // (see ISO_23009-1-2012 5.3.5.2). // // If the same Representation existed in a prior Period, it will retain the same NAME. const oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME); if (!oldPlaylist) { // Since this is a new playlist, the media sequence values can start from 0 without // consequence. return; } // TODO better support for live SIDX // // As of this writing, mpd-parser does not support multiperiod SIDX (in live or VOD). // This is evident by a playlist only having a single SIDX reference. In a multiperiod // playlist there would need to be multiple SIDX references. In addition, live SIDX is // not supported when the SIDX properties change on refreshes. // // In the future, if support needs to be added, the merging logic here can be called // after SIDX references are resolved. For now, exit early to prevent exceptions being // thrown due to undefined references. if (playlist.sidx) { return; } // Since we don't yet support early available timelines, we don't need to support // playlists with no segments. const firstNewSegment = playlist.segments[0]; const oldMatchingSegmentIndex = oldPlaylist.segments.findIndex(function (oldSegment) { return Math.abs(oldSegment.presentationTime - firstNewSegment.presentationTime) < TIME_FUDGE; }); // No matching segment from the old playlist means the entire playlist was refreshed. // In this case the media sequence should account for this update, and the new segments // should be marked as discontinuous from the prior content, since the last prior // timeline was removed. if (oldMatchingSegmentIndex === -1) { updateMediaSequenceForPlaylist({ playlist, mediaSequence: oldPlaylist.mediaSequence + oldPlaylist.segments.length }); playlist.segments[0].discontinuity = true; playlist.discontinuityStarts.unshift(0); // No matching segment does not necessarily mean there's missing content. // // If the new playlist's timeline is the same as the last seen segment's timeline, // then a discontinuity can be added to identify that there's potentially missing // content. If there's no missing content, the discontinuity should still be rather // harmless. It's possible that if segment durations are accurate enough, that the // existence of a gap can be determined using the presentation times and durations, // but if the segment timing info is off, it may introduce more problems than simply // adding the discontinuity. // // If the new playlist's timeline is different from the last seen segment's timeline, // then a discontinuity can be added to identify that this is the first seen segment // of a new timeline. However, the logic at the start of this function that // determined the disconinuity sequence by timeline index is now off by one (the // discontinuity of the newest timeline hasn't yet fallen off the manifest...since // we added it), so the disconinuity sequence must be decremented. // // A period may also have a duration of zero, so the case of no segments is handled // here even though we don't yet support early available periods. if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) { playlist.discontinuitySequence--; } return; } // If the first segment matched with a prior segment on a discontinuity (it's matching // on the first segment of a period), then the discontinuitySequence shouldn't be the // timeline's matching one, but instead should be the one prior, and the first segment // of the new manifest should be marked with a discontinuity. // // The reason for this special case is that discontinuity sequence shows how many // discontinuities have fallen off of the playlist, and discontinuities are marked on // the first segment of a new "timeline." Because of this, while DASH will retain that // Period while the "timeline" exists, HLS keeps track of it via the discontinuity // sequence, and that first segment is an indicator, but can be removed before that // timeline is gone. const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex]; if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) { firstNewSegment.discontinuity = true; playlist.discontinuityStarts.unshift(0); playlist.discontinuitySequence--; } updateMediaSequenceForPlaylist({ playlist, mediaSequence: oldPlaylist.segments[oldMatchingSegmentIndex].number }); }); }; /** * Given an old parsed manifest object and a new parsed manifest object, updates the * sequence and timing values within the new manifest to ensure that it lines up with the * old. * * @param {Array} oldManifest - the old main manifest object * @param {Array} newManifest - the new main manifest object * * @return {Object} the updated new manifest object */ const positionManifestOnTimeline = ({ oldManifest, newManifest }) => { // Starting from v4.1.2 of the IOP, section 4.4.3.3 states: // // "MPD@availabilityStartTime and Period@start shall not be changed over MPD updates." // // This was added from https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/160 // // Because of this change, and the difficulty of supporting periods with changing start // times, periods with changing start times are not supported. This makes the logic much // simpler, since periods with the same start time can be considerred the same period // across refreshes. // // To give an example as to the difficulty of handling periods where the start time may // change, if a single period manifest is refreshed with another manifest with a single // period, and both the start and end times are increased, then the only way to determine // if it's a new period or an old one that has changed is to look through the segments of // each playlist and determine the presentation time bounds to find a match. In addition, // if the period start changed to exceed the old period end, then there would be no // match, and it would not be possible to determine whether the refreshed period is a new // one or the old one. const oldPlaylists = oldManifest.playlists.concat(getMediaGroupPlaylists(oldManifest)); const newPlaylists = newManifest.playlists.concat(getMediaGroupPlaylists(newManifest)); // Save all seen timelineStarts to the new manifest. Although this potentially means that // there's a "memory leak" in that it will never stop growing, in reality, only a couple // of properties are saved for each seen Period. Even long running live streams won't // generate too many Periods, unless the stream is watched for decades. In the future, // this can be optimized by mapping to discontinuity sequence numbers for each timeline, // but it may not become an issue, and the additional info can be useful for debugging. newManifest.timelineStarts = getUniqueTimelineStarts([oldManifest.timelineStarts, newManifest.timelineStarts]); updateSequenceNumbers({ oldPlaylists, newPlaylists, timelineStarts: newManifest.timelineStarts }); return newManifest; }; const generateSidxKey = sidx => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange); const mergeDiscontiguousPlaylists = playlists => { // Break out playlists into groups based on their baseUrl const playlistsByBaseUrl = playlists.reduce(function (acc, cur) { if (!acc[cur.attributes.baseUrl]) { acc[cur.attributes.baseUrl] = []; } acc[cur.attributes.baseUrl].push(cur); return acc; }, {}); let allPlaylists = []; Object.values(playlistsByBaseUrl).forEach(playlistGroup => { const mergedPlaylists = values(playlistGroup.reduce((acc, playlist) => { // assuming playlist IDs are the same across periods // TODO: handle multiperiod where representation sets are not the same // across periods const name = playlist.attributes.id + (playlist.attributes.lang || ''); if (!acc[name]) { // First Period acc[name] = playlist; acc[name].attributes.timelineStarts = []; } else { // Subsequent Periods if (playlist.segments) { // first segment of subsequent periods signal a discontinuity if (playlist.segments[0]) { playlist.segments[0].discontinuity = true; } acc[name].segments.push(...playlist.segments); } // bubble up contentProtection, this assumes all DRM content // has the same contentProtection if (playlist.attributes.contentProtection) { acc[name].attributes.contentProtection = playlist.attributes.contentProtection; } } acc[name].attributes.timelineStarts.push({ // Although they represent the same number, it's important to have both to make it // compatible with HLS potentially having a similar attribute. start: playlist.attributes.periodStart, timeline: playlist.attributes.periodStart }); return acc; }, {})); allPlaylists = allPlaylists.concat(mergedPlaylists); }); return allPlaylists.map(playlist => { playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity'); return playlist; }); }; const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => { const sidxKey = generateSidxKey(playlist.sidx); const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx; if (sidxMatch) { addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri); } return playlist; }; const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => { if (!Object.keys(sidxMapping).length) { return playlists; } for (const i in playlists) { playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping); } return playlists; }; const formatAudioPlaylist = ({ attributes, segments, sidx, mediaSequence, discontinuitySequence, discontinuityStarts }, isAudioOnly) => { const playlist = { attributes: { NAME: attributes.id, BANDWIDTH: attributes.bandwidth, CODECS: attributes.codecs, ['PROGRAM-ID']: 1 }, uri: '', endList: attributes.type === 'static', timeline: attributes.periodStart, resolvedUri: attributes.baseUrl || '', targetDuration: attributes.duration, discontinuitySequence, discontinuityStarts, timelineStarts: attributes.timelineStarts, mediaSequence, segments }; if (attributes.contentProtection) { playlist.contentProtection = attributes.contentProtection; } if (attributes.serviceLocation) { playlist.attributes.serviceLocation = attributes.serviceLocation; } if (sidx) { playlist.sidx = sidx; } if (isAudioOnly) { playlist.attributes.AUDIO = 'audio'; playlist.attributes.SUBTITLES = 'subs'; } return playlist; }; const formatVttPlaylist = ({ attributes, segments, mediaSequence, discontinuityStarts, discontinuitySequence }) => { if (typeof segments === 'undefined') { // vtt tracks may use single file in BaseURL segments = [{ uri: attributes.baseUrl, timeline: attributes.periodStart, resolvedUri: attributes.baseUrl || '', duration: attributes.sourceDuration, number: 0 }]; // targetDuration should be the same duration as the only segment attributes.duration = attributes.sourceDuration; } const m3u8Attributes = { NAME: attributes.id, BANDWIDTH: attributes.bandwidth, ['PROGRAM-ID']: 1 }; if (attributes.codecs) { m3u8Attributes.CODECS = attributes.codecs; } const vttPlaylist = { attributes: m3u8Attributes, uri: '', endList: attributes.type === 'static', timeline: attributes.periodStart, resolvedUri: attributes.baseUrl || '', targetDuration: attributes.duration, timelineStarts: attributes.timelineStarts, discontinuityStarts, discontinuitySequence, mediaSequence, segments }; if (attributes.serviceLocation) { vttPlaylist.attributes.serviceLocation = attributes.serviceLocation; } return vttPlaylist; }; const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => { let mainPlaylist; const formattedPlaylists = playlists.reduce((a, playlist) => { const role = playlist.attributes.role && playlist.attributes.role.value || ''; const language = playlist.attributes.lang || ''; let label = playlist.attributes.label || 'main'; if (language && !playlist.attributes.label) { const roleLabel = role ? ` (${role})` : ''; label = `${playlist.attributes.lang}${roleLabel}`; } if (!a[label]) { a[label] = { language, autoselect: true, default: role === 'main', playlists: [], uri: '' }; } const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping); a[label].playlists.push(formatted); if (typeof mainPlaylist === 'undefined' && role === 'main') { mainPlaylist = playlist; mainPlaylist.default = true; } return a; }, {}); // if no playlists have role "main", mark the first as main if (!mainPlaylist) { const firstLabel = Object.keys(formattedPlaylists)[0]; formattedPlaylists[firstLabel].default = true; } return formattedPlaylists; }; const organizeVttPlaylists = (playlists, sidxMapping = {}) => { return playlists.reduce((a, playlist) => { const label = playlist.attributes.label || playlist.attributes.lang || 'text'; const language = playlist.attributes.lang || 'und'; if (!a[label]) { a[label] = { language, default: false, autoselect: false, playlists: [], uri: '' }; } a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping)); return a; }, {}); }; const organizeCaptionServices = captionServices => captionServices.reduce((svcObj, svc) => { if (!svc) { return svcObj; } svc.forEach(service => { const { channel, language } = service; svcObj[language] = { autoselect: false, default: false, instreamId: channel, language }; if (service.hasOwnProperty('aspectRatio')) { svcObj[language].aspectRatio = service.aspectRatio; } if (service.hasOwnProperty('easyReader')) { svcObj[language].easyReader = service.easyReader; } if (service.hasOwnProperty('3D')) { svcObj[language]['3D'] = service['3D']; } }); return svcObj; }, {}); const formatVideoPlaylist = ({ attributes, segments, sidx, discontinuityStarts }) => { const playlist = { attributes: { NAME: attributes.id, AUDIO: 'audio', SUBTITLES: 'subs', RESOLUTION: { width: attributes.width, height: attributes.height }, CODECS: attributes.codecs, BANDWIDTH: attributes.bandwidth, ['PROGRAM-ID']: 1 }, uri: '', endList: attributes.type === 'static', timeline: attributes.periodStart, resolvedUri: attributes.baseUrl || '', targetDuration: attributes.duration, discontinuityStarts, timelineStarts: attributes.timelineStarts, segments }; if (attributes.frameRate) { playlist.attributes['FRAME-RATE'] = attributes.frameRate; } if (attributes.contentProtection) { playlist.contentProtection = attributes.contentProtection; } if (attributes.serviceLocation) { playlist.attributes.serviceLocation = attributes.serviceLocation; } if (sidx) { playlist.sidx = sidx; } return playlist; }; const videoOnly = ({ attributes }) => attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video'; const audioOnly = ({ attributes }) => attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio'; const vttOnly = ({ attributes }) => attributes.mimeType === 'text/vtt' || attributes.contentType === 'text'; /** * Contains start and timeline properties denoting a timeline start. For DASH, these will * be the same number. * * @typedef {Object} TimelineStart * @property {number} start - the start time of the timeline * @property {number} timeline - the timeline number */ /** * Adds appropriate media and discontinuity sequence values to the segments and playlists. * * Throughout mpd-parser, the `number` attribute is used in relation to `startNumber`, a * DASH specific attribute used in constructing segment URI's from templates. However, from * an HLS perspective, the `number` attribute on a segment would be its `mediaSequence` * value, which should start at the original media sequence value (or 0) and increment by 1 * for each segment thereafter. Since DASH's `startNumber` values are independent per * period, it doesn't make sense to use it for `number`. Instead, assume everything starts * from a 0 mediaSequence value and increment from there. * * Note that VHS currently doesn't use the `number` property, but it can be helpful for * debugging and making sense of the manifest. * * For live playlists, to account for values increasing in manifests when periods are * removed on refreshes, merging logic should be used to update the numbers to their * appropriate values (to ensure they're sequential and increasing). * * @param {Object[]} playlists - the playlists to update * @param {TimelineStart[]} timelineStarts - the timeline starts for the manifest */ const addMediaSequenceValues = (playlists, timelineStarts) => { // increment all segments sequentially playlists.forEach(playlist => { playlist.mediaSequence = 0; playlist.discontinuitySequence = timelineStarts.findIndex(function ({ timeline }) { return timeline === playlist.timeline; }); if (!playlist.segments) { return; } playlist.segments.forEach((segment, index) => { segment.number = index; }); }); }; /** * Given a media group object, flattens all playlists within the media group into a single * array. * * @param {Object} mediaGroupObject - the media group object * * @return {Object[]} * The media group playlists */ const flattenMediaGroupPlaylists = mediaGroupObject => { if (!mediaGroupObject) { return []; } return Object.keys(mediaGroupObject).reduce((acc, label) => { const labelContents = mediaGroupObject[label]; return acc.concat(labelContents.playlists); }, []); }; const toM3u8 = ({ dashPlaylists, locations, contentSteering, sidxMapping = {}, previousManifest, eventStream }) => { if (!dashPlaylists.length) { return {}; } // grab all main manifest attributes const { sourceDuration: duration, type, suggestedPresentationDelay, minimumUpdatePeriod } = dashPlaylists[0].attributes; const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist); const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly)); const vttPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(vttOnly)); const captions = dashPlaylists.map(playlist => playlist.attributes.captionServices).filter(Boolean); const manifest = { allowCache: true, discontinuityStarts: [], segments: [], endList: true, mediaGroups: { AUDIO: {}, VIDEO: {}, ['CLOSED-CAPTIONS']: {}, SUBTITLES: {} }, uri: '', duration, playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping) }; if (minimumUpdatePeriod >= 0) { manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000; } if (locations) { manifest.locations = locations; } if (contentSteering) { manifest.contentSteering = contentSteering; } if (type === 'dynamic') { manifest.suggestedPresentationDelay = suggestedPresentationDelay; } if (eventStream && eventStream.length > 0) { manifest.eventStream = eventStream; } const isAudioOnly = manifest.playlists.length === 0; const organizedAudioGroup = audioPlaylists.length ? organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly) : null; const organizedVttGroup = vttPlaylists.length ? organizeVttPlaylists(vttPlaylists, sidxMapping) : null; const formattedPlaylists = videoPlaylists.concat(flattenMediaGroupPlaylists(organizedAudioGroup), flattenMediaGroupPlaylists(organizedVttGroup)); const playlistTimelineStarts = formattedPlaylists.map(({ timelineStarts }) => timelineStarts); manifest.timelineStarts = getUniqueTimelineStarts(playlistTimelineStarts); addMediaSequenceValues(formattedPlaylists, manifest.timelineStarts); if (organizedAudioGroup) { manifest.mediaGroups.AUDIO.audio = organizedAudioGroup; } if (organizedVttGroup) { manifest.mediaGroups.SUBTITLES.subs = organizedVttGroup; } if (captions.length) { manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions); } if (previousManifest) { return positionManifestOnTimeline({ oldManifest: previousManifest, newManifest: manifest }); } return manifest; }; /** * Calculates the R (repetition) value for a live stream (for the final segment * in a manifest where the r value is negative 1) * * @param {Object} attributes * Object containing all inherited attributes from parent elements with attribute * names as keys * @param {number} time * current time (typically the total time up until the final segment) * @param {number} duration * duration property for the given * * @return {number} * R value to reach the end of the given period */ const getLiveRValue = (attributes, time, duration) => { const { NOW, clientOffset, availabilityStartTime, timescale = 1, periodStart = 0, minimumUpdatePeriod = 0 } = attributes; const now = (NOW + clientOffset) / 1000; const periodStartWC = availabilityStartTime + periodStart; const periodEndWC = now + minimumUpdatePeriod; const periodDuration = periodEndWC - periodStartWC; return Math.ceil((periodDuration * timescale - time) / duration); }; /** * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment * timing and duration * * @param {Object} attributes * Object containing all inherited attributes from parent elements with attribute * names as keys * @param {Object[]} segmentTimeline * List of objects representing the attributes of each S element contained within * * @return {{number: number, duration: number, time: number, timeline: number}[]} * List of Objects with segment timing and duration info */ const parseByTimeline = (attributes, segmentTimeline) => { const { type, minimumUpdatePeriod = 0, media = '', sourceDuration, timescale = 1, startNumber = 1, periodStart: timeline } = attributes; const segments = []; let time = -1; for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { const S = segmentTimeline[sIndex]; const duration = S.d; const repeat = S.r || 0; const segmentTime = S.t || 0; if (time < 0) { // first segment time = segmentTime; } if (segmentTime && segmentTime > time) { // discontinuity // TODO: How to handle this type of discontinuity // timeline++ here would treat it like HLS discontuity and content would // get appended without gap // E.G. // // // // // would have $Time$ values of [0, 1, 2, 5] // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) // does the value of sourceDuration consider this when calculating arbitrary // negative @r repeat value? // E.G. Same elements as above with this added at the end // // with a sourceDuration of 10 // Would the 2 gaps be included in the time duration calculations resulting in // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? time = segmentTime; } let count; if (repeat < 0) { const nextS = sIndex + 1; if (nextS === segmentTimeline.length) { // last segment if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) { count = getLiveRValue(attributes, time, duration); } else { // TODO: This may be incorrect depending on conclusion of TODO above count = (sourceDuration * timescale - time) / duration; } } else { count = (segmentTimeline[nextS].t - time) / duration; } } else { count = repeat + 1; } const end = startNumber + segments.length + count; let number = startNumber + segments.length; while (number < end) { segments.push({ number, duration: duration / timescale, time, timeline }); time += duration; number++; } } return segments; }; const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; /** * Replaces template identifiers with corresponding values. To be used as the callback * for String.prototype.replace * * @name replaceCallback * @function * @param {string} match * Entire match of identifier * @param {string} identifier * Name of matched identifier * @param {string} format * Format tag string. Its presence indicates that padding is expected * @param {string} width * Desired length of the replaced value. Values less than this width shall be left * zero padded * @return {string} * Replacement for the matched identifier */ /** * Returns a function to be used as a callback for String.prototype.replace to replace * template identifiers * * @param {Obect} values * Object containing values that shall be used to replace known identifiers * @param {number} values.RepresentationID * Value of the Representation@id attribute * @param {number} values.Number * Number of the corresponding segment * @param {number} values.Bandwidth * Value of the Representation@bandwidth attribute. * @param {number} values.Time * Timestamp value of the corresponding segment * @return {replaceCallback} * Callback to be used with String.prototype.replace to replace identifiers */ const identifierReplacement = values => (match, identifier, format, width) => { if (match === '$$') { // escape sequence return '$'; } if (typeof values[identifier] === 'undefined') { return match; } const value = '' + values[identifier]; if (identifier === 'RepresentationID') { // Format tag shall not be present with RepresentationID return value; } if (!format) { width = 1; } else { width = parseInt(width, 10); } if (value.length >= width) { return value; } return `${new Array(width - value.length + 1).join('0')}${value}`; }; /** * Constructs a segment url from a template string * * @param {string} url * Template string to construct url from * @param {Obect} values * Object containing values that shall be used to replace known identifiers * @param {number} values.RepresentationID * Value of the Representation@id attribute * @param {number} values.Number * Number of the corresponding segment * @param {number} values.Bandwidth * Value of the Representation@bandwidth attribute. * @param {number} values.Time * Timestamp value of the corresponding segment * @return {string} * Segment url with identifiers replaced */ const constructTemplateUrl = (url, values) => url.replace(identifierPattern, identifierReplacement(values)); /** * Generates a list of objects containing timing and duration information about each * segment needed to generate segment uris and the complete segment object * * @param {Object} attributes * Object containing all inherited attributes from parent elements with attribute * names as keys * @param {Object[]|undefined} segmentTimeline * List of objects representing the attributes of each S element contained within * the SegmentTimeline element * @return {{number: number, duration: number, time: number, timeline: number}[]} * List of Objects with segment timing and duration info */ const parseTemplateInfo = (attributes, segmentTimeline) => { if (!attributes.duration && !segmentTimeline) { // if neither @duration or SegmentTimeline are present, then there shall be exactly // one media segment return [{ number: attributes.startNumber || 1, duration: attributes.sourceDuration, time: 0, timeline: attributes.periodStart }]; } if (attributes.duration) { return parseByDuration(attributes); } return parseByTimeline(attributes, segmentTimeline); }; /** * Generates a list of segments using information provided by the SegmentTemplate element * * @param {Object} attributes * Object containing all inherited attributes from parent elements with attribute * names as keys * @param {Object[]|undefined} segmentTimeline * List of objects representing the attributes of each S element contained within * the SegmentTimeline element * @return {Object[]} * List of segment objects */ const segmentsFromTemplate = (attributes, segmentTimeline) => { const templateValues = { RepresentationID: attributes.id, Bandwidth: attributes.bandwidth || 0 }; const { initialization = { sourceURL: '', range: '' } } = attributes; const mapSegment = urlTypeToSegment({ baseUrl: attributes.baseUrl, source: constructTemplateUrl(initialization.sourceURL, templateValues), range: initialization.range }); const segments = parseTemplateInfo(attributes, segmentTimeline); return segments.map(segment => { templateValues.Number = segment.number; templateValues.Time = segment.time; const uri = constructTemplateUrl(attributes.media || '', templateValues); // See DASH spec section 5.3.9.2.2 // - if timescale isn't present on any level, default to 1. const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0 const presentationTimeOffset = attributes.presentationTimeOffset || 0; const presentationTime = // Even if the @t attribute is not specified for the segment, segment.time is // calculated in mpd-parser prior to this, so it's assumed to be available. attributes.periodStart + (segment.time - presentationTimeOffset) / timescale; const map = { uri, timeline: segment.timeline, duration: segment.duration, resolvedUri: resolve_url(attributes.baseUrl || '', uri), map: mapSegment, number: segment.number, presentationTime }; return map; }); }; /** * Converts a (of type URLType from the DASH spec 5.3.9.2 Table 14) * to an object that matches the output of a segment in videojs/mpd-parser * * @param {Object} attributes * Object containing all inherited attributes from parent elements with attribute * names as keys * @param {Object} segmentUrl * node to translate into a segment object * @return {Object} translated segment object */ const SegmentURLToSegmentObject = (attributes, segmentUrl) => { const { baseUrl, initialization = {} } = attributes; const initSegment = urlTypeToSegment({ baseUrl, source: initialization.sourceURL, range: initialization.range }); const segment = urlTypeToSegment({ baseUrl, source: segmentUrl.media, range: segmentUrl.mediaRange }); segment.map = initSegment; return segment; }; /** * Generates a list of segments using information provided by the SegmentList element * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of nodes. Each * node should be translated into segment. * * @param {Object} attributes * Object containing all inherited attributes from parent elements with attribute * names as keys * @param {Object[]|undefined} segmentTimeline * List of objects representing the attributes of each S element contained within * the SegmentTimeline element * @return {Object.} list of segments */ const segmentsFromList = (attributes, segmentTimeline) => { const { duration, segmentUrls = [], periodStart } = attributes; // Per spec (5.3.9.2.1) no way to determine segment duration OR // if both SegmentTimeline and @duration are defined, it is outside of spec. if (!duration && !segmentTimeline || duration && segmentTimeline) { throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); } const segmentUrlMap = segmentUrls.map(segmentUrlObject => SegmentURLToSegmentObject(attributes, segmentUrlObject)); let segmentTimeInfo; if (duration) { segmentTimeInfo = parseByDuration(attributes); } if (segmentTimeline) { segmentTimeInfo = parseByTimeline(attributes, segmentTimeline); } const segments = segmentTimeInfo.map((segmentTime, index) => { if (segmentUrlMap[index]) { const segment = segmentUrlMap[index]; // See DASH spec section 5.3.9.2.2 // - if timescale isn't present on any level, default to 1. const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0 const presentationTimeOffset = attributes.presentationTimeOffset || 0; segment.timeline = segmentTime.timeline; segment.duration = segmentTime.duration; segment.number = segmentTime.number; segment.presentationTime = periodStart + (segmentTime.time - presentationTimeOffset) / timescale; return segment; } // Since we're mapping we should get rid of any blank segments (in case // the given SegmentTimeline is handling for more elements than we have // SegmentURLs for). }).filter(segment => segment); return segments; }; const generateSegments = ({ attributes, segmentInfo }) => { let segmentAttributes; let segmentsFn; if (segmentInfo.template) { segmentsFn = segmentsFromTemplate; segmentAttributes = merge(attributes, segmentInfo.template); } else if (segmentInfo.base) { segmentsFn = segmentsFromBase; segmentAttributes = merge(attributes, segmentInfo.base); } else if (segmentInfo.list) { segmentsFn = segmentsFromList; segmentAttributes = merge(attributes, segmentInfo.list); } const segmentsInfo = { attributes }; if (!segmentsFn) { return segmentsInfo; } const segments = segmentsFn(segmentAttributes, segmentInfo.segmentTimeline); // The @duration attribute will be used to determin the playlist's targetDuration which // must be in seconds. Since we've generated the segment list, we no longer need // @duration to be in @timescale units, so we can convert it here. if (segmentAttributes.duration) { const { duration, timescale = 1 } = segmentAttributes; segmentAttributes.duration = duration / timescale; } else if (segments.length) { // if there is no @duration attribute, use the largest segment duration as // as target duration segmentAttributes.duration = segments.reduce((max, segment) => { return Math.max(max, Math.ceil(segment.duration)); }, 0); } else { segmentAttributes.duration = 0; } segmentsInfo.attributes = segmentAttributes; segmentsInfo.segments = segments; // This is a sidx box without actual segment information if (segmentInfo.base && segmentAttributes.indexRange) { segmentsInfo.sidx = segments[0]; segmentsInfo.segments = []; } return segmentsInfo; }; const toPlaylists = representations => representations.map(generateSegments); const findChildren = (element, name) => from(element.childNodes).filter(({ tagName }) => tagName === name); const getContent = element => element.textContent.trim(); /** * Converts the provided string that may contain a division operation to a number. * * @param {string} value - the provided string value * * @return {number} the parsed string value */ const parseDivisionValue = value => { return parseFloat(value.split('/').reduce((prev, current) => prev / current)); }; const parseDuration = str => { const SECONDS_IN_YEAR = 365 * 24 * 60 * 60; const SECONDS_IN_MONTH = 30 * 24 * 60 * 60; const SECONDS_IN_DAY = 24 * 60 * 60; const SECONDS_IN_HOUR = 60 * 60; const SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S const durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/; const match = durationRegex.exec(str); if (!match) { return 0; } const [year, month, day, hour, minute, second] = match.slice(1); return parseFloat(year || 0) * SECONDS_IN_YEAR + parseFloat(month || 0) * SECONDS_IN_MONTH + parseFloat(day || 0) * SECONDS_IN_DAY + parseFloat(hour || 0) * SECONDS_IN_HOUR + parseFloat(minute || 0) * SECONDS_IN_MIN + parseFloat(second || 0); }; const parseDate = str => { // Date format without timezone according to ISO 8601 // YYY-MM-DDThh:mm:ss.ssssss const dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is // expressed by ending with 'Z' if (dateRegex.test(str)) { str += 'Z'; } return Date.parse(str); }; const parsers = { /** * Specifies the duration of the entire Media Presentation. Format is a duration string * as specified in ISO 8601 * * @param {string} value * value of attribute as a string * @return {number} * The duration in seconds */ mediaPresentationDuration(value) { return parseDuration(value); }, /** * Specifies the Segment availability start time for all Segments referred to in this * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability * time. Format is a date string as specified in ISO 8601 * * @param {string} value * value of attribute as a string * @return {number} * The date as seconds from unix epoch */ availabilityStartTime(value) { return parseDate(value) / 1000; }, /** * Specifies the smallest period between potential changes to the MPD. Format is a * duration string as specified in ISO 8601 * * @param {string} value * value of attribute as a string * @return {number} * The duration in seconds */ minimumUpdatePeriod(value) { return parseDuration(value); }, /** * Specifies the suggested presentation delay. Format is a * duration string as specified in ISO 8601 * * @param {string} value * value of attribute as a string * @return {number} * The duration in seconds */ suggestedPresentationDelay(value) { return parseDuration(value); }, /** * specifices the type of mpd. Can be either "static" or "dynamic" * * @param {string} value * value of attribute as a string * * @return {string} * The type as a string */ type(value) { return value; }, /** * Specifies the duration of the smallest time shifting buffer for any Representation * in the MPD. Format is a duration string as specified in ISO 8601 * * @param {string} value * value of attribute as a string * @return {number} * The duration in seconds */ timeShiftBufferDepth(value) { return parseDuration(value); }, /** * Specifies the PeriodStart time of the Period relative to the availabilityStarttime. * Format is a duration string as specified in ISO 8601 * * @param {string} value * value of attribute as a string * @return {number} * The duration in seconds */ start(value) { return parseDuration(value); }, /** * Specifies the width of the visual presentation * * @param {string} value * value of attribute as a string * @return {number} * The parsed width */ width(value) { return parseInt(value, 10); }, /** * Specifies the height of the visual presentation * * @param {string} value * value of attribute as a string * @return {number} * The parsed height */ height(value) { return parseInt(value, 10); }, /** * Specifies the bitrate of the representation * * @param {string} value * value of attribute as a string * @return {number} * The parsed bandwidth */ bandwidth(value) { return parseInt(value, 10); }, /** * Specifies the frame rate of the representation * * @param {string} value * value of attribute as a string * @return {number} * The parsed frame rate */ frameRate(value) { return parseDivisionValue(value); }, /** * Specifies the number of the first Media Segment in this Representation in the Period * * @param {string} value * value of attribute as a string * @return {number} * The parsed number */ startNumber(value) { return parseInt(value, 10); }, /** * Specifies the timescale in units per seconds * * @param {string} value * value of attribute as a string * @return {number} * The parsed timescale */ timescale(value) { return parseInt(value, 10); }, /** * Specifies the presentationTimeOffset. * * @param {string} value * value of the attribute as a string * * @return {number} * The parsed presentationTimeOffset */ presentationTimeOffset(value) { return parseInt(value, 10); }, /** * Specifies the constant approximate Segment duration * NOTE: The element also contains an @duration attribute. This duration * specifies the duration of the Period. This attribute is currently not * supported by the rest of the parser, however we still check for it to prevent * errors. * * @param {string} value * value of attribute as a string * @return {number} * The parsed duration */ duration(value) { const parsedValue = parseInt(value, 10); if (isNaN(parsedValue)) { return parseDuration(value); } return parsedValue; }, /** * Specifies the Segment duration, in units of the value of the @timescale. * * @param {string} value * value of attribute as a string * @return {number} * The parsed duration */ d(value) { return parseInt(value, 10); }, /** * Specifies the MPD start time, in @timescale units, the first Segment in the series * starts relative to the beginning of the Period * * @param {string} value * value of attribute as a string * @return {number} * The parsed time */ t(value) { return parseInt(value, 10); }, /** * Specifies the repeat count of the number of following contiguous Segments with the * same duration expressed by the value of @d * * @param {string} value * value of attribute as a string * @return {number} * The parsed number */ r(value) { return parseInt(value, 10); }, /** * Specifies the presentationTime. * * @param {string} value * value of the attribute as a string * * @return {number} * The parsed presentationTime */ presentationTime(value) { return parseInt(value, 10); }, /** * Default parser for all other attributes. Acts as a no-op and just returns the value * as a string * * @param {string} value * value of attribute as a string * @return {string} * Unparsed value */ DEFAULT(value) { return value; } }; /** * Gets all the attributes and values of the provided node, parses attributes with known * types, and returns an object with attribute names mapped to values. * * @param {Node} el * The node to parse attributes from * @return {Object} * Object with all attributes of el parsed */ const mpd_parser_es_parseAttributes = el => { if (!(el && el.attributes)) { return {}; } return from(el.attributes).reduce((a, e) => { const parseFn = parsers[e.name] || parsers.DEFAULT; a[e.name] = parseFn(e.value); return a; }, {}); }; const keySystemsMap = { 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready', 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime', // ISO_IEC 23009-1_2022 5.8.5.2.2 The mp4 Protection Scheme 'urn:mpeg:dash:mp4protection:2011': 'mp4protection' }; /** * Builds a list of urls that is the product of the reference urls and BaseURL values * * @param {Object[]} references * List of objects containing the reference URL as well as its attributes * @param {Node[]} baseUrlElements * List of BaseURL nodes from the mpd * @return {Object[]} * List of objects with resolved urls and attributes */ const buildBaseUrls = (references, baseUrlElements) => { if (!baseUrlElements.length) { return references; } return flatten(references.map(function (reference) { return baseUrlElements.map(function (baseUrlElement) { const initialBaseUrl = getContent(baseUrlElement); const resolvedBaseUrl = resolve_url(reference.baseUrl, initialBaseUrl); const finalBaseUrl = merge(mpd_parser_es_parseAttributes(baseUrlElement), { baseUrl: resolvedBaseUrl }); // If the URL is resolved, we want to get the serviceLocation from the reference // assuming there is no serviceLocation on the initialBaseUrl if (resolvedBaseUrl !== initialBaseUrl && !finalBaseUrl.serviceLocation && reference.serviceLocation) { finalBaseUrl.serviceLocation = reference.serviceLocation; } return finalBaseUrl; }); })); }; /** * Contains all Segment information for its containing AdaptationSet * * @typedef {Object} SegmentInformation * @property {Object|undefined} template * Contains the attributes for the SegmentTemplate node * @property {Object[]|undefined} segmentTimeline * Contains a list of atrributes for each S node within the SegmentTimeline node * @property {Object|undefined} list * Contains the attributes for the SegmentList node * @property {Object|undefined} base * Contains the attributes for the SegmentBase node */ /** * Returns all available Segment information contained within the AdaptationSet node * * @param {Node} adaptationSet * The AdaptationSet node to get Segment information from * @return {SegmentInformation} * The Segment information contained within the provided AdaptationSet */ const getSegmentInformation = adaptationSet => { const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; const segmentList = findChildren(adaptationSet, 'SegmentList')[0]; const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(s => merge({ tag: 'SegmentURL' }, mpd_parser_es_parseAttributes(s))); const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; const segmentTimelineParentNode = segmentList || segmentTemplate; const segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0]; const segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate; const segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both // @initialization and an node. @initialization can be templated, // while the node can have a url and range specified. If the has // both @initialization and an subelement we opt to override with // the node, as this interaction is not defined in the spec. const template = segmentTemplate && mpd_parser_es_parseAttributes(segmentTemplate); if (template && segmentInitialization) { template.initialization = segmentInitialization && mpd_parser_es_parseAttributes(segmentInitialization); } else if (template && template.initialization) { // If it is @initialization we convert it to an object since this is the format that // later functions will rely on for the initialization segment. This is only valid // for template.initialization = { sourceURL: template.initialization }; } const segmentInfo = { template, segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => mpd_parser_es_parseAttributes(s)), list: segmentList && merge(mpd_parser_es_parseAttributes(segmentList), { segmentUrls, initialization: mpd_parser_es_parseAttributes(segmentInitialization) }), base: segmentBase && merge(mpd_parser_es_parseAttributes(segmentBase), { initialization: mpd_parser_es_parseAttributes(segmentInitialization) }) }; Object.keys(segmentInfo).forEach(key => { if (!segmentInfo[key]) { delete segmentInfo[key]; } }); return segmentInfo; }; /** * Contains Segment information and attributes needed to construct a Playlist object * from a Representation * * @typedef {Object} RepresentationInformation * @property {SegmentInformation} segmentInfo * Segment information for this Representation * @property {Object} attributes * Inherited attributes for this Representation */ /** * Maps a Representation node to an object containing Segment information and attributes * * @name inheritBaseUrlsCallback * @function * @param {Node} representation * Representation node from the mpd * @return {RepresentationInformation} * Representation information needed to construct a Playlist object */ /** * Returns a callback for Array.prototype.map for mapping Representation nodes to * Segment information and attributes using inherited BaseURL nodes. * * @param {Object} adaptationSetAttributes * Contains attributes inherited by the AdaptationSet * @param {Object[]} adaptationSetBaseUrls * List of objects containing resolved base URLs and attributes * inherited by the AdaptationSet * @param {SegmentInformation} adaptationSetSegmentInfo * Contains Segment information for the AdaptationSet * @return {inheritBaseUrlsCallback} * Callback map function */ const inheritBaseUrls = (adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) => representation => { const repBaseUrlElements = findChildren(representation, 'BaseURL'); const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements); const attributes = merge(adaptationSetAttributes, mpd_parser_es_parseAttributes(representation)); const representationSegmentInfo = getSegmentInformation(representation); return repBaseUrls.map(baseUrl => { return { segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo), attributes: merge(attributes, baseUrl) }; }); }; /** * Tranforms a series of content protection nodes to * an object containing pssh data by key system * * @param {Node[]} contentProtectionNodes * Content protection nodes * @return {Object} * Object containing pssh data by key system */ const generateKeySystemInformation = contentProtectionNodes => { return contentProtectionNodes.reduce((acc, node) => { const attributes = mpd_parser_es_parseAttributes(node); // Although it could be argued that according to the UUID RFC spec the UUID string (a-f chars) should be generated // as a lowercase string it also mentions it should be treated as case-insensitive on input. Since the key system // UUIDs in the keySystemsMap are hardcoded as lowercase in the codebase there isn't any reason not to do // .toLowerCase() on the input UUID string from the manifest (at least I could not think of one). if (attributes.schemeIdUri) { attributes.schemeIdUri = attributes.schemeIdUri.toLowerCase(); } const keySystem = keySystemsMap[attributes.schemeIdUri]; if (keySystem) { acc[keySystem] = { attributes }; const psshNode = findChildren(node, 'cenc:pssh')[0]; if (psshNode) { const pssh = getContent(psshNode); acc[keySystem].pssh = pssh && decodeB64ToUint8Array(pssh); } } return acc; }, {}); }; // defined in ANSI_SCTE 214-1 2016 const parseCaptionServiceMetadata = service => { // 608 captions if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') { const values = typeof service.value !== 'string' ? [] : service.value.split(';'); return values.map(value => { let channel; let language; // default language to value language = value; if (/^CC\d=/.test(value)) { [channel, language] = value.split('='); } else if (/^CC\d$/.test(value)) { channel = value; } return { channel, language }; }); } else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') { const values = typeof service.value !== 'string' ? [] : service.value.split(';'); return values.map(value => { const flags = { // service or channel number 1-63 'channel': undefined, // language is a 3ALPHA per ISO 639.2/B // field is required 'language': undefined, // BIT 1/0 or ? // default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown 'aspectRatio': 1, // BIT 1/0 // easy reader flag indicated the text is tailed to the needs of beginning readers // default 0, or off 'easyReader': 0, // BIT 1/0 // If 3d metadata is present (CEA-708.1) then 1 // default 0 '3D': 0 }; if (/=/.test(value)) { const [channel, opts = ''] = value.split('='); flags.channel = channel; flags.language = value; opts.split(',').forEach(opt => { const [name, val] = opt.split(':'); if (name === 'lang') { flags.language = val; // er for easyReadery } else if (name === 'er') { flags.easyReader = Number(val); // war for wide aspect ratio } else if (name === 'war') { flags.aspectRatio = Number(val); } else if (name === '3D') { flags['3D'] = Number(val); } }); } else { flags.language = value; } if (flags.channel) { flags.channel = 'SERVICE' + flags.channel; } return flags; }); } }; /** * A map callback that will parse all event stream data for a collection of periods * DASH ISO_IEC_23009 5.10.2.2 * https://dashif-documents.azurewebsites.net/Events/master/event.html#mpd-event-timing * * @param {PeriodInformation} period object containing necessary period information * @return a collection of parsed eventstream event objects */ const toEventStream = period => { // get and flatten all EventStreams tags and parse attributes and children return flatten(findChildren(period.node, 'EventStream').map(eventStream => { const eventStreamAttributes = mpd_parser_es_parseAttributes(eventStream); const schemeIdUri = eventStreamAttributes.schemeIdUri; // find all Events per EventStream tag and map to return objects return findChildren(eventStream, 'Event').map(event => { const eventAttributes = mpd_parser_es_parseAttributes(event); const presentationTime = eventAttributes.presentationTime || 0; const timescale = eventStreamAttributes.timescale || 1; const duration = eventAttributes.duration || 0; const start = presentationTime / timescale + period.attributes.start; return { schemeIdUri, value: eventStreamAttributes.value, id: eventAttributes.id, start, end: start + duration / timescale, messageData: getContent(event) || eventAttributes.messageData, contentEncoding: eventStreamAttributes.contentEncoding, presentationTimeOffset: eventStreamAttributes.presentationTimeOffset || 0 }; }); })); }; /** * Maps an AdaptationSet node to a list of Representation information objects * * @name toRepresentationsCallback * @function * @param {Node} adaptationSet * AdaptationSet node from the mpd * @return {RepresentationInformation[]} * List of objects containing Representaion information */ /** * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of * Representation information objects * * @param {Object} periodAttributes * Contains attributes inherited by the Period * @param {Object[]} periodBaseUrls * Contains list of objects with resolved base urls and attributes * inherited by the Period * @param {string[]} periodSegmentInfo * Contains Segment Information at the period level * @return {toRepresentationsCallback} * Callback map function */ const toRepresentations = (periodAttributes, periodBaseUrls, periodSegmentInfo) => adaptationSet => { const adaptationSetAttributes = mpd_parser_es_parseAttributes(adaptationSet); const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL')); const role = findChildren(adaptationSet, 'Role')[0]; const roleAttributes = { role: mpd_parser_es_parseAttributes(role) }; let attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes); const accessibility = findChildren(adaptationSet, 'Accessibility')[0]; const captionServices = parseCaptionServiceMetadata(mpd_parser_es_parseAttributes(accessibility)); if (captionServices) { attrs = merge(attrs, { captionServices }); } const label = findChildren(adaptationSet, 'Label')[0]; if (label && label.childNodes.length) { const labelVal = label.childNodes[0].nodeValue.trim(); attrs = merge(attrs, { label: labelVal }); } const contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection')); if (Object.keys(contentProtection).length) { attrs = merge(attrs, { contentProtection }); } const segmentInfo = getSegmentInformation(adaptationSet); const representations = findChildren(adaptationSet, 'Representation'); const adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo); return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo))); }; /** * Contains all period information for mapping nodes onto adaptation sets. * * @typedef {Object} PeriodInformation * @property {Node} period.node * Period node from the mpd * @property {Object} period.attributes * Parsed period attributes from node plus any added */ /** * Maps a PeriodInformation object to a list of Representation information objects for all * AdaptationSet nodes contained within the Period. * * @name toAdaptationSetsCallback * @function * @param {PeriodInformation} period * Period object containing necessary period information * @param {number} periodStart * Start time of the Period within the mpd * @return {RepresentationInformation[]} * List of objects containing Representaion information */ /** * Returns a callback for Array.prototype.map for mapping Period nodes to a list of * Representation information objects * * @param {Object} mpdAttributes * Contains attributes inherited by the mpd * @param {Object[]} mpdBaseUrls * Contains list of objects with resolved base urls and attributes * inherited by the mpd * @return {toAdaptationSetsCallback} * Callback map function */ const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, index) => { const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL')); const periodAttributes = merge(mpdAttributes, { periodStart: period.attributes.start }); if (typeof period.attributes.duration === 'number') { periodAttributes.periodDuration = period.attributes.duration; } const adaptationSets = findChildren(period.node, 'AdaptationSet'); const periodSegmentInfo = getSegmentInformation(period.node); return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo))); }; /** * Tranforms an array of content steering nodes into an object * containing CDN content steering information from the MPD manifest. * * For more information on the DASH spec for Content Steering parsing, see: * https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf * * @param {Node[]} contentSteeringNodes * Content steering nodes * @param {Function} eventHandler * The event handler passed into the parser options to handle warnings * @return {Object} * Object containing content steering data */ const generateContentSteeringInformation = (contentSteeringNodes, eventHandler) => { // If there are more than one ContentSteering tags, throw an error if (contentSteeringNodes.length > 1) { eventHandler({ type: 'warn', message: 'The MPD manifest should contain no more than one ContentSteering tag' }); } // Return a null value if there are no ContentSteering tags if (!contentSteeringNodes.length) { return null; } const infoFromContentSteeringTag = merge({ serverURL: getContent(contentSteeringNodes[0]) }, mpd_parser_es_parseAttributes(contentSteeringNodes[0])); // Converts `queryBeforeStart` to a boolean, as well as setting the default value // to `false` if it doesn't exist infoFromContentSteeringTag.queryBeforeStart = infoFromContentSteeringTag.queryBeforeStart === 'true'; return infoFromContentSteeringTag; }; /** * Gets Period@start property for a given period. * * @param {Object} options * Options object * @param {Object} options.attributes * Period attributes * @param {Object} [options.priorPeriodAttributes] * Prior period attributes (if prior period is available) * @param {string} options.mpdType * The MPD@type these periods came from * @return {number|null} * The period start, or null if it's an early available period or error */ const getPeriodStart = ({ attributes, priorPeriodAttributes, mpdType }) => { // Summary of period start time calculation from DASH spec section 5.3.2.1 // // A period's start is the first period's start + time elapsed after playing all // prior periods to this one. Periods continue one after the other in time (without // gaps) until the end of the presentation. // // The value of Period@start should be: // 1. if Period@start is present: value of Period@start // 2. if previous period exists and it has @duration: previous Period@start + // previous Period@duration // 3. if this is first period and MPD@type is 'static': 0 // 4. in all other cases, consider the period an "early available period" (note: not // currently supported) // (1) if (typeof attributes.start === 'number') { return attributes.start; } // (2) if (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') { return priorPeriodAttributes.start + priorPeriodAttributes.duration; } // (3) if (!priorPeriodAttributes && mpdType === 'static') { return 0; } // (4) // There is currently no logic for calculating the Period@start value if there is // no Period@start or prior Period@start and Period@duration available. This is not made // explicit by the DASH interop guidelines or the DASH spec, however, since there's // nothing about any other resolution strategies, it's implied. Thus, this case should // be considered an early available period, or error, and null should suffice for both // of those cases. return null; }; /** * Traverses the mpd xml tree to generate a list of Representation information objects * that have inherited attributes from parent nodes * * @param {Node} mpd * The root node of the mpd * @param {Object} options * Available options for inheritAttributes * @param {string} options.manifestUri * The uri source of the mpd * @param {number} options.NOW * Current time per DASH IOP. Default is current time in ms since epoch * @param {number} options.clientOffset * Client time difference from NOW (in milliseconds) * @return {RepresentationInformation[]} * List of objects containing Representation information */ const inheritAttributes = (mpd, options = {}) => { const { manifestUri = '', NOW = Date.now(), clientOffset = 0, // TODO: For now, we are expecting an eventHandler callback function // to be passed into the mpd parser as an option. // In the future, we should enable stream parsing by using the Stream class from vhs-utils. // This will support new features including a standardized event handler. // See the m3u8 parser for examples of how stream parsing is currently used for HLS parsing. // https://github.com/videojs/vhs-utils/blob/88d6e10c631e57a5af02c5a62bc7376cd456b4f5/src/stream.js#L9 eventHandler = function () {} } = options; const periodNodes = findChildren(mpd, 'Period'); if (!periodNodes.length) { throw new Error(errors.INVALID_NUMBER_OF_PERIOD); } const locations = findChildren(mpd, 'Location'); const mpdAttributes = mpd_parser_es_parseAttributes(mpd); const mpdBaseUrls = buildBaseUrls([{ baseUrl: manifestUri }], findChildren(mpd, 'BaseURL')); const contentSteeringNodes = findChildren(mpd, 'ContentSteering'); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'. mpdAttributes.type = mpdAttributes.type || 'static'; mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0; mpdAttributes.NOW = NOW; mpdAttributes.clientOffset = clientOffset; if (locations.length) { mpdAttributes.locations = locations.map(getContent); } const periods = []; // Since toAdaptationSets acts on individual periods right now, the simplest approach to // adding properties that require looking at prior periods is to parse attributes and add // missing ones before toAdaptationSets is called. If more such properties are added, it // may be better to refactor toAdaptationSets. periodNodes.forEach((node, index) => { const attributes = mpd_parser_es_parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary // for this period. const priorPeriod = periods[index - 1]; attributes.start = getPeriodStart({ attributes, priorPeriodAttributes: priorPeriod ? priorPeriod.attributes : null, mpdType: mpdAttributes.type }); periods.push({ node, attributes }); }); return { locations: mpdAttributes.locations, contentSteeringInfo: generateContentSteeringInformation(contentSteeringNodes, eventHandler), // TODO: There are occurences where this `representationInfo` array contains undesired // duplicates. This generally occurs when there are multiple BaseURL nodes that are // direct children of the MPD node. When we attempt to resolve URLs from a combination of the // parent BaseURL and a child BaseURL, and the value does not resolve, // we end up returning the child BaseURL multiple times. // We need to determine a way to remove these duplicates in a safe way. // See: https://github.com/videojs/mpd-parser/pull/17#discussion_r162750527 representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))), eventStream: flatten(periods.map(toEventStream)) }; }; const stringToMpdXml = manifestString => { if (manifestString === '') { throw new Error(errors.DASH_EMPTY_MANIFEST); } const parser = new xmldom_lib.DOMParser(); let xml; let mpd; try { xml = parser.parseFromString(manifestString, 'application/xml'); mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null; } catch (e) {// ie 11 throws on invalid xml } if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) { throw new Error(errors.DASH_INVALID_XML); } return mpd; }; /** * Parses the manifest for a UTCTiming node, returning the nodes attributes if found * * @param {string} mpd * XML string of the MPD manifest * @return {Object|null} * Attributes of UTCTiming node specified in the manifest. Null if none found */ const parseUTCTimingScheme = mpd => { const UTCTimingNode = findChildren(mpd, 'UTCTiming')[0]; if (!UTCTimingNode) { return null; } const attributes = mpd_parser_es_parseAttributes(UTCTimingNode); switch (attributes.schemeIdUri) { case 'urn:mpeg:dash:utc:http-head:2014': case 'urn:mpeg:dash:utc:http-head:2012': attributes.method = 'HEAD'; break; case 'urn:mpeg:dash:utc:http-xsdate:2014': case 'urn:mpeg:dash:utc:http-iso:2014': case 'urn:mpeg:dash:utc:http-xsdate:2012': case 'urn:mpeg:dash:utc:http-iso:2012': attributes.method = 'GET'; break; case 'urn:mpeg:dash:utc:direct:2014': case 'urn:mpeg:dash:utc:direct:2012': attributes.method = 'DIRECT'; attributes.value = Date.parse(attributes.value); break; case 'urn:mpeg:dash:utc:http-ntp:2014': case 'urn:mpeg:dash:utc:ntp:2014': case 'urn:mpeg:dash:utc:sntp:2014': default: throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME); } return attributes; }; const VERSION = (/* unused pure expression or super */ null && (version)); /* * Given a DASH manifest string and options, parses the DASH manifest into an object in the * form outputed by m3u8-parser and accepted by videojs/http-streaming. * * For live DASH manifests, if `previousManifest` is provided in options, then the newly * parsed DASH manifest will have its media sequence and discontinuity sequence values * updated to reflect its position relative to the prior manifest. * * @param {string} manifestString - the DASH manifest as a string * @param {options} [options] - any options * * @return {Object} the manifest object */ const parse = (manifestString, options = {}) => { const parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options); const playlists = toPlaylists(parsedManifestInfo.representationInfo); return toM3u8({ dashPlaylists: playlists, locations: parsedManifestInfo.locations, contentSteering: parsedManifestInfo.contentSteeringInfo, sidxMapping: options.sidxMapping, previousManifest: options.previousManifest, eventStream: parsedManifestInfo.eventStream }); }; /** * Parses the manifest for a UTCTiming node, returning the nodes attributes if found * * @param {string} manifestString * XML string of the MPD manifest * @return {Object|null} * Attributes of UTCTiming node specified in the manifest. Null if none found */ const parseUTCTiming = manifestString => parseUTCTimingScheme(stringToMpdXml(manifestString)); // EXTERNAL MODULE: ./node_modules/mux.js/lib/tools/parse-sidx.js var parse_sidx = __webpack_require__(221); var parse_sidx_default = /*#__PURE__*/__webpack_require__.n(parse_sidx); ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/id3-helpers.js var ID3 = byte_helpers_toUint8([0x49, 0x44, 0x33]); var getId3Size = function getId3Size(bytes, offset) { if (offset === void 0) { offset = 0; } bytes = byte_helpers_toUint8(bytes); var flags = bytes[offset + 5]; var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9]; var footerPresent = (flags & 16) >> 4; if (footerPresent) { return returnSize + 20; } return returnSize + 10; }; var getId3Offset = function getId3Offset(bytes, offset) { if (offset === void 0) { offset = 0; } bytes = byte_helpers_toUint8(bytes); if (bytes.length - offset < 10 || !byte_helpers_bytesMatch(bytes, ID3, { offset: offset })) { return offset; } offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files // have multiple ID3 tag sections even though // they should not. return getId3Offset(bytes, offset); }; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/codec-helpers.js // https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter#AV1 var codec_helpers_getAv1Codec = function getAv1Codec(bytes) { var codec = ''; var profile = bytes[1] >>> 3; var level = bytes[1] & 0x1F; var tier = bytes[2] >>> 7; var highBitDepth = (bytes[2] & 0x40) >> 6; var twelveBit = (bytes[2] & 0x20) >> 5; var monochrome = (bytes[2] & 0x10) >> 4; var chromaSubsamplingX = (bytes[2] & 0x08) >> 3; var chromaSubsamplingY = (bytes[2] & 0x04) >> 2; var chromaSamplePosition = bytes[2] & 0x03; codec += profile + "." + padStart(level, 2, '0'); if (tier === 0) { codec += 'M'; } else if (tier === 1) { codec += 'H'; } var bitDepth; if (profile === 2 && highBitDepth) { bitDepth = twelveBit ? 12 : 10; } else { bitDepth = highBitDepth ? 10 : 8; } codec += "." + padStart(bitDepth, 2, '0'); // TODO: can we parse color range?? codec += "." + monochrome; codec += "." + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition; return codec; }; var codec_helpers_getAvcCodec = function getAvcCodec(bytes) { var profileId = toHexString(bytes[1]); var constraintFlags = toHexString(bytes[2] & 0xFC); var levelId = toHexString(bytes[3]); return "" + profileId + constraintFlags + levelId; }; var codec_helpers_getHvcCodec = function getHvcCodec(bytes) { var codec = ''; var profileSpace = bytes[1] >> 6; var profileId = bytes[1] & 0x1F; var tierFlag = (bytes[1] & 0x20) >> 5; var profileCompat = bytes.subarray(2, 6); var constraintIds = bytes.subarray(6, 12); var levelId = bytes[12]; if (profileSpace === 1) { codec += 'A'; } else if (profileSpace === 2) { codec += 'B'; } else if (profileSpace === 3) { codec += 'C'; } codec += profileId + "."; // ffmpeg does this in big endian var profileCompatVal = parseInt(toBinaryString(profileCompat).split('').reverse().join(''), 2); // apple does this in little endian... if (profileCompatVal > 255) { profileCompatVal = parseInt(toBinaryString(profileCompat), 2); } codec += profileCompatVal.toString(16) + "."; if (tierFlag === 0) { codec += 'L'; } else { codec += 'H'; } codec += levelId; var constraints = ''; for (var i = 0; i < constraintIds.length; i++) { var v = constraintIds[i]; if (v) { if (constraints) { constraints += '.'; } constraints += v.toString(16); } } if (constraints) { codec += "." + constraints; } return codec; }; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/opus-helpers.js var OPUS_HEAD = new Uint8Array([// O, p, u, s 0x4f, 0x70, 0x75, 0x73, // H, e, a, d 0x48, 0x65, 0x61, 0x64]); // https://wiki.xiph.org/OggOpus // https://vfrmaniac.fushizen.eu/contents/opus_in_isobmff.html // https://opus-codec.org/docs/opusfile_api-0.7/structOpusHead.html var opus_helpers_parseOpusHead = function parseOpusHead(bytes) { var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); var version = view.getUint8(0); // version 0, from mp4, does not use littleEndian. var littleEndian = version !== 0; var config = { version: version, channels: view.getUint8(1), preSkip: view.getUint16(2, littleEndian), sampleRate: view.getUint32(4, littleEndian), outputGain: view.getUint16(8, littleEndian), channelMappingFamily: view.getUint8(10) }; if (config.channelMappingFamily > 0 && bytes.length > 10) { config.streamCount = view.getUint8(11); config.twoChannelStreamCount = view.getUint8(12); config.channelMapping = []; for (var c = 0; c < config.channels; c++) { config.channelMapping.push(view.getUint8(13 + c)); } } return config; }; var setOpusHead = function setOpusHead(config) { var size = config.channelMappingFamily <= 0 ? 11 : 12 + config.channels; var view = new DataView(new ArrayBuffer(size)); var littleEndian = config.version !== 0; view.setUint8(0, config.version); view.setUint8(1, config.channels); view.setUint16(2, config.preSkip, littleEndian); view.setUint32(4, config.sampleRate, littleEndian); view.setUint16(8, config.outputGain, littleEndian); view.setUint8(10, config.channelMappingFamily); if (config.channelMappingFamily > 0) { view.setUint8(11, config.streamCount); config.channelMapping.foreach(function (cm, i) { view.setUint8(12 + i, cm); }); } return new Uint8Array(view.buffer); }; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/mp4-helpers.js var normalizePath = function normalizePath(path) { if (typeof path === 'string') { return stringToBytes(path); } if (typeof path === 'number') { return path; } return path; }; var normalizePaths = function normalizePaths(paths) { if (!Array.isArray(paths)) { return [normalizePath(paths)]; } return paths.map(function (p) { return normalizePath(p); }); }; var DESCRIPTORS; var parseDescriptors = function parseDescriptors(bytes) { bytes = byte_helpers_toUint8(bytes); var results = []; var i = 0; while (bytes.length > i) { var tag = bytes[i]; var size = 0; var headerSize = 0; // tag headerSize++; var byte = bytes[headerSize]; // first byte headerSize++; while (byte & 0x80) { size = (byte & 0x7F) << 7; byte = bytes[headerSize]; headerSize++; } size += byte & 0x7F; for (var z = 0; z < DESCRIPTORS.length; z++) { var _DESCRIPTORS$z = DESCRIPTORS[z], id = _DESCRIPTORS$z.id, parser = _DESCRIPTORS$z.parser; if (tag === id) { results.push(parser(bytes.subarray(headerSize, headerSize + size))); break; } } i += size + headerSize; } return results; }; DESCRIPTORS = [{ id: 0x03, parser: function parser(bytes) { var desc = { tag: 0x03, id: bytes[0] << 8 | bytes[1], flags: bytes[2], size: 3, dependsOnEsId: 0, ocrEsId: 0, descriptors: [], url: '' }; // depends on es id if (desc.flags & 0x80) { desc.dependsOnEsId = bytes[desc.size] << 8 | bytes[desc.size + 1]; desc.size += 2; } // url if (desc.flags & 0x40) { var len = bytes[desc.size]; desc.url = byte_helpers_bytesToString(bytes.subarray(desc.size + 1, desc.size + 1 + len)); desc.size += len; } // ocr es id if (desc.flags & 0x20) { desc.ocrEsId = bytes[desc.size] << 8 | bytes[desc.size + 1]; desc.size += 2; } desc.descriptors = parseDescriptors(bytes.subarray(desc.size)) || []; return desc; } }, { id: 0x04, parser: function parser(bytes) { // DecoderConfigDescriptor var desc = { tag: 0x04, oti: bytes[0], streamType: bytes[1], bufferSize: bytes[2] << 16 | bytes[3] << 8 | bytes[4], maxBitrate: bytes[5] << 24 | bytes[6] << 16 | bytes[7] << 8 | bytes[8], avgBitrate: bytes[9] << 24 | bytes[10] << 16 | bytes[11] << 8 | bytes[12], descriptors: parseDescriptors(bytes.subarray(13)) }; return desc; } }, { id: 0x05, parser: function parser(bytes) { // DecoderSpecificInfo return { tag: 0x05, bytes: bytes }; } }, { id: 0x06, parser: function parser(bytes) { // SLConfigDescriptor return { tag: 0x06, bytes: bytes }; } }]; /** * find any number of boxes by name given a path to it in an iso bmff * such as mp4. * * @param {TypedArray} bytes * bytes for the iso bmff to search for boxes in * * @param {Uint8Array[]|string[]|string|Uint8Array} name * An array of paths or a single path representing the name * of boxes to search through in bytes. Paths may be * uint8 (character codes) or strings. * * @param {boolean} [complete=false] * Should we search only for complete boxes on the final path. * This is very useful when you do not want to get back partial boxes * in the case of streaming files. * * @return {Uint8Array[]} * An array of the end paths that we found. */ var findBox = function findBox(bytes, paths, complete) { if (complete === void 0) { complete = false; } paths = normalizePaths(paths); bytes = byte_helpers_toUint8(bytes); var results = []; if (!paths.length) { // short-circuit the search for empty paths return results; } var i = 0; while (i < bytes.length) { var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0; var type = bytes.subarray(i + 4, i + 8); // invalid box format. if (size === 0) { break; } var end = i + size; if (end > bytes.length) { // this box is bigger than the number of bytes we have // and complete is set, we cannot find any more boxes. if (complete) { break; } end = bytes.length; } var data = bytes.subarray(i + 8, end); if (byte_helpers_bytesMatch(type, paths[0])) { if (paths.length === 1) { // this is the end of the path and we've found the box we were // looking for results.push(data); } else { // recursively search for the next box along the path results.push.apply(results, findBox(data, paths.slice(1), complete)); } } i = end; } // we've finished searching all of bytes return results; }; /** * Search for a single matching box by name in an iso bmff format like * mp4. This function is useful for finding codec boxes which * can be placed arbitrarily in sample descriptions depending * on the version of the file or file type. * * @param {TypedArray} bytes * bytes for the iso bmff to search for boxes in * * @param {string|Uint8Array} name * The name of the box to find. * * @return {Uint8Array[]} * a subarray of bytes representing the name boxed we found. */ var findNamedBox = function findNamedBox(bytes, name) { name = normalizePath(name); if (!name.length) { // short-circuit the search for empty paths return bytes.subarray(bytes.length); } var i = 0; while (i < bytes.length) { if (bytesMatch(bytes.subarray(i, i + name.length), name)) { var size = (bytes[i - 4] << 24 | bytes[i - 3] << 16 | bytes[i - 2] << 8 | bytes[i - 1]) >>> 0; var end = size > 1 ? i + size : bytes.byteLength; return bytes.subarray(i + 4, end); } i++; } // we've finished searching all of bytes return bytes.subarray(bytes.length); }; var parseSamples = function parseSamples(data, entrySize, parseEntry) { if (entrySize === void 0) { entrySize = 4; } if (parseEntry === void 0) { parseEntry = function parseEntry(d) { return bytesToNumber(d); }; } var entries = []; if (!data || !data.length) { return entries; } var entryCount = bytesToNumber(data.subarray(4, 8)); for (var i = 8; entryCount; i += entrySize, entryCount--) { entries.push(parseEntry(data.subarray(i, i + entrySize))); } return entries; }; var buildFrameTable = function buildFrameTable(stbl, timescale) { var keySamples = parseSamples(findBox(stbl, ['stss'])[0]); var chunkOffsets = parseSamples(findBox(stbl, ['stco'])[0]); var timeToSamples = parseSamples(findBox(stbl, ['stts'])[0], 8, function (entry) { return { sampleCount: bytesToNumber(entry.subarray(0, 4)), sampleDelta: bytesToNumber(entry.subarray(4, 8)) }; }); var samplesToChunks = parseSamples(findBox(stbl, ['stsc'])[0], 12, function (entry) { return { firstChunk: bytesToNumber(entry.subarray(0, 4)), samplesPerChunk: bytesToNumber(entry.subarray(4, 8)), sampleDescriptionIndex: bytesToNumber(entry.subarray(8, 12)) }; }); var stsz = findBox(stbl, ['stsz'])[0]; // stsz starts with a 4 byte sampleSize which we don't need var sampleSizes = parseSamples(stsz && stsz.length && stsz.subarray(4) || null); var frames = []; for (var chunkIndex = 0; chunkIndex < chunkOffsets.length; chunkIndex++) { var samplesInChunk = void 0; for (var i = 0; i < samplesToChunks.length; i++) { var sampleToChunk = samplesToChunks[i]; var isThisOne = chunkIndex + 1 >= sampleToChunk.firstChunk && (i + 1 >= samplesToChunks.length || chunkIndex + 1 < samplesToChunks[i + 1].firstChunk); if (isThisOne) { samplesInChunk = sampleToChunk.samplesPerChunk; break; } } var chunkOffset = chunkOffsets[chunkIndex]; for (var _i = 0; _i < samplesInChunk; _i++) { var frameEnd = sampleSizes[frames.length]; // if we don't have key samples every frame is a keyframe var keyframe = !keySamples.length; if (keySamples.length && keySamples.indexOf(frames.length + 1) !== -1) { keyframe = true; } var frame = { keyframe: keyframe, start: chunkOffset, end: chunkOffset + frameEnd }; for (var k = 0; k < timeToSamples.length; k++) { var _timeToSamples$k = timeToSamples[k], sampleCount = _timeToSamples$k.sampleCount, sampleDelta = _timeToSamples$k.sampleDelta; if (frames.length <= sampleCount) { // ms to ns var lastTimestamp = frames.length ? frames[frames.length - 1].timestamp : 0; frame.timestamp = lastTimestamp + sampleDelta / timescale * 1000; frame.duration = sampleDelta; break; } } frames.push(frame); chunkOffset += frameEnd; } } return frames; }; var addSampleDescription = function addSampleDescription(track, bytes) { var codec = bytesToString(bytes.subarray(0, 4)); if (track.type === 'video') { track.info = track.info || {}; track.info.width = bytes[28] << 8 | bytes[29]; track.info.height = bytes[30] << 8 | bytes[31]; } else if (track.type === 'audio') { track.info = track.info || {}; track.info.channels = bytes[20] << 8 | bytes[21]; track.info.bitDepth = bytes[22] << 8 | bytes[23]; track.info.sampleRate = bytes[28] << 8 | bytes[29]; } if (codec === 'avc1') { var avcC = findNamedBox(bytes, 'avcC'); // AVCDecoderConfigurationRecord codec += "." + getAvcCodec(avcC); track.info.avcC = avcC; // TODO: do we need to parse all this? /* { configurationVersion: avcC[0], profile: avcC[1], profileCompatibility: avcC[2], level: avcC[3], lengthSizeMinusOne: avcC[4] & 0x3 }; let spsNalUnitCount = avcC[5] & 0x1F; const spsNalUnits = track.info.avc.spsNalUnits = []; // past spsNalUnitCount let offset = 6; while (spsNalUnitCount--) { const nalLen = avcC[offset] << 8 | avcC[offset + 1]; spsNalUnits.push(avcC.subarray(offset + 2, offset + 2 + nalLen)); offset += nalLen + 2; } let ppsNalUnitCount = avcC[offset]; const ppsNalUnits = track.info.avc.ppsNalUnits = []; // past ppsNalUnitCount offset += 1; while (ppsNalUnitCount--) { const nalLen = avcC[offset] << 8 | avcC[offset + 1]; ppsNalUnits.push(avcC.subarray(offset + 2, offset + 2 + nalLen)); offset += nalLen + 2; }*/ // HEVCDecoderConfigurationRecord } else if (codec === 'hvc1' || codec === 'hev1') { codec += "." + getHvcCodec(findNamedBox(bytes, 'hvcC')); } else if (codec === 'mp4a' || codec === 'mp4v') { var esds = findNamedBox(bytes, 'esds'); var esDescriptor = parseDescriptors(esds.subarray(4))[0]; var decoderConfig = esDescriptor && esDescriptor.descriptors.filter(function (_ref) { var tag = _ref.tag; return tag === 0x04; })[0]; if (decoderConfig) { // most codecs do not have a further '.' // such as 0xa5 for ac-3 and 0xa6 for e-ac-3 codec += '.' + toHexString(decoderConfig.oti); if (decoderConfig.oti === 0x40) { codec += '.' + (decoderConfig.descriptors[0].bytes[0] >> 3).toString(); } else if (decoderConfig.oti === 0x20) { codec += '.' + decoderConfig.descriptors[0].bytes[4].toString(); } else if (decoderConfig.oti === 0xdd) { codec = 'vorbis'; } } else if (track.type === 'audio') { codec += '.40.2'; } else { codec += '.20.9'; } } else if (codec === 'av01') { // AV1DecoderConfigurationRecord codec += "." + getAv1Codec(findNamedBox(bytes, 'av1C')); } else if (codec === 'vp09') { // VPCodecConfigurationRecord var vpcC = findNamedBox(bytes, 'vpcC'); // https://www.webmproject.org/vp9/mp4/ var profile = vpcC[0]; var level = vpcC[1]; var bitDepth = vpcC[2] >> 4; var chromaSubsampling = (vpcC[2] & 0x0F) >> 1; var videoFullRangeFlag = (vpcC[2] & 0x0F) >> 3; var colourPrimaries = vpcC[3]; var transferCharacteristics = vpcC[4]; var matrixCoefficients = vpcC[5]; codec += "." + padStart(profile, 2, '0'); codec += "." + padStart(level, 2, '0'); codec += "." + padStart(bitDepth, 2, '0'); codec += "." + padStart(chromaSubsampling, 2, '0'); codec += "." + padStart(colourPrimaries, 2, '0'); codec += "." + padStart(transferCharacteristics, 2, '0'); codec += "." + padStart(matrixCoefficients, 2, '0'); codec += "." + padStart(videoFullRangeFlag, 2, '0'); } else if (codec === 'theo') { codec = 'theora'; } else if (codec === 'spex') { codec = 'speex'; } else if (codec === '.mp3') { codec = 'mp4a.40.34'; } else if (codec === 'msVo') { codec = 'vorbis'; } else if (codec === 'Opus') { codec = 'opus'; var dOps = findNamedBox(bytes, 'dOps'); track.info.opus = parseOpusHead(dOps); // TODO: should this go into the webm code?? // Firefox requires a codecDelay for opus playback // see https://bugzilla.mozilla.org/show_bug.cgi?id=1276238 track.info.codecDelay = 6500000; } else { codec = codec.toLowerCase(); } /* eslint-enable */ // flac, ac-3, ec-3, opus track.codec = codec; }; var parseTracks = function parseTracks(bytes, frameTable) { if (frameTable === void 0) { frameTable = true; } bytes = toUint8(bytes); var traks = findBox(bytes, ['moov', 'trak'], true); var tracks = []; traks.forEach(function (trak) { var track = { bytes: trak }; var mdia = findBox(trak, ['mdia'])[0]; var hdlr = findBox(mdia, ['hdlr'])[0]; var trakType = bytesToString(hdlr.subarray(8, 12)); if (trakType === 'soun') { track.type = 'audio'; } else if (trakType === 'vide') { track.type = 'video'; } else { track.type = trakType; } var tkhd = findBox(trak, ['tkhd'])[0]; if (tkhd) { var view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); var tkhdVersion = view.getUint8(0); track.number = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20); } var mdhd = findBox(mdia, ['mdhd'])[0]; if (mdhd) { // mdhd is a FullBox, meaning it will have its own version as the first byte var version = mdhd[0]; var index = version === 0 ? 12 : 20; track.timescale = (mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]) >>> 0; } var stbl = findBox(mdia, ['minf', 'stbl'])[0]; var stsd = findBox(stbl, ['stsd'])[0]; var descriptionCount = bytesToNumber(stsd.subarray(4, 8)); var offset = 8; // add codec and codec info while (descriptionCount--) { var len = bytesToNumber(stsd.subarray(offset, offset + 4)); var sampleDescriptor = stsd.subarray(offset + 4, offset + 4 + len); addSampleDescription(track, sampleDescriptor); offset += 4 + len; } if (frameTable) { track.frameTable = buildFrameTable(stbl, track.timescale); } // codec has no sub parameters tracks.push(track); }); return tracks; }; var parseMediaInfo = function parseMediaInfo(bytes) { var mvhd = findBox(bytes, ['moov', 'mvhd'], true)[0]; if (!mvhd || !mvhd.length) { return; } var info = {}; // ms to ns // mvhd v1 has 8 byte duration and other fields too if (mvhd[0] === 1) { info.timestampScale = bytesToNumber(mvhd.subarray(20, 24)); info.duration = bytesToNumber(mvhd.subarray(24, 32)); } else { info.timestampScale = bytesToNumber(mvhd.subarray(12, 16)); info.duration = bytesToNumber(mvhd.subarray(16, 20)); } info.bytes = mvhd; return info; }; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/ebml-helpers.js // relevant specs for this parser: // https://matroska-org.github.io/libebml/specs.html // https://www.matroska.org/technical/elements.html // https://www.webmproject.org/docs/container/ var EBML_TAGS = { EBML: byte_helpers_toUint8([0x1A, 0x45, 0xDF, 0xA3]), DocType: byte_helpers_toUint8([0x42, 0x82]), Segment: byte_helpers_toUint8([0x18, 0x53, 0x80, 0x67]), SegmentInfo: byte_helpers_toUint8([0x15, 0x49, 0xA9, 0x66]), Tracks: byte_helpers_toUint8([0x16, 0x54, 0xAE, 0x6B]), Track: byte_helpers_toUint8([0xAE]), TrackNumber: byte_helpers_toUint8([0xd7]), DefaultDuration: byte_helpers_toUint8([0x23, 0xe3, 0x83]), TrackEntry: byte_helpers_toUint8([0xAE]), TrackType: byte_helpers_toUint8([0x83]), FlagDefault: byte_helpers_toUint8([0x88]), CodecID: byte_helpers_toUint8([0x86]), CodecPrivate: byte_helpers_toUint8([0x63, 0xA2]), VideoTrack: byte_helpers_toUint8([0xe0]), AudioTrack: byte_helpers_toUint8([0xe1]), // Not used yet, but will be used for live webm/mkv // see https://www.matroska.org/technical/basics.html#block-structure // see https://www.matroska.org/technical/basics.html#simpleblock-structure Cluster: byte_helpers_toUint8([0x1F, 0x43, 0xB6, 0x75]), Timestamp: byte_helpers_toUint8([0xE7]), TimestampScale: byte_helpers_toUint8([0x2A, 0xD7, 0xB1]), BlockGroup: byte_helpers_toUint8([0xA0]), BlockDuration: byte_helpers_toUint8([0x9B]), Block: byte_helpers_toUint8([0xA1]), SimpleBlock: byte_helpers_toUint8([0xA3]) }; /** * This is a simple table to determine the length * of things in ebml. The length is one based (starts at 1, * rather than zero) and for every zero bit before a one bit * we add one to length. We also need this table because in some * case we have to xor all the length bits from another value. */ var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1]; var getLength = function getLength(byte) { var len = 1; for (var i = 0; i < LENGTH_TABLE.length; i++) { if (byte & LENGTH_TABLE[i]) { break; } len++; } return len; }; // length in ebml is stored in the first 4 to 8 bits // of the first byte. 4 for the id length and 8 for the // data size length. Length is measured by converting the number to binary // then 1 + the number of zeros before a 1 is encountered starting // from the left. var getvint = function getvint(bytes, offset, removeLength, signed) { if (removeLength === void 0) { removeLength = true; } if (signed === void 0) { signed = false; } var length = getLength(bytes[offset]); var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes // as they will be modified below to remove the dataSizeLen bits and we do not // want to modify the original data. normally we could just call slice on // uint8array but ie 11 does not support that... if (removeLength) { valueBytes = Array.prototype.slice.call(bytes, offset, offset + length); valueBytes[0] ^= LENGTH_TABLE[length - 1]; } return { length: length, value: byte_helpers_bytesToNumber(valueBytes, { signed: signed }), bytes: valueBytes }; }; var ebml_helpers_normalizePath = function normalizePath(path) { if (typeof path === 'string') { return path.match(/.{1,2}/g).map(function (p) { return normalizePath(p); }); } if (typeof path === 'number') { return numberToBytes(path); } return path; }; var ebml_helpers_normalizePaths = function normalizePaths(paths) { if (!Array.isArray(paths)) { return [ebml_helpers_normalizePath(paths)]; } return paths.map(function (p) { return ebml_helpers_normalizePath(p); }); }; var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) { if (offset >= bytes.length) { return bytes.length; } var innerid = getvint(bytes, offset, false); if (byte_helpers_bytesMatch(id.bytes, innerid.bytes)) { return offset; } var dataHeader = getvint(bytes, offset + innerid.length); return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length); }; /** * Notes on the EBLM format. * * EBLM uses "vints" tags. Every vint tag contains * two parts * * 1. The length from the first byte. You get this by * converting the byte to binary and counting the zeros * before a 1. Then you add 1 to that. Examples * 00011111 = length 4 because there are 3 zeros before a 1. * 00100000 = length 3 because there are 2 zeros before a 1. * 00000011 = length 7 because there are 6 zeros before a 1. * * 2. The bits used for length are removed from the first byte * Then all the bytes are merged into a value. NOTE: this * is not the case for id ebml tags as there id includes * length bits. * */ var findEbml = function findEbml(bytes, paths) { paths = ebml_helpers_normalizePaths(paths); bytes = byte_helpers_toUint8(bytes); var results = []; if (!paths.length) { return results; } var i = 0; while (i < bytes.length) { var id = getvint(bytes, i, false); var dataHeader = getvint(bytes, i + id.length); var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream if (dataHeader.value === 0x7f) { dataHeader.value = getInfinityDataSize(id, bytes, dataStart); if (dataHeader.value !== bytes.length) { dataHeader.value -= dataStart; } } var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value; var data = bytes.subarray(dataStart, dataEnd); if (byte_helpers_bytesMatch(paths[0], id.bytes)) { if (paths.length === 1) { // this is the end of the paths and we've found the tag we were // looking for results.push(data); } else { // recursively search for the next tag inside of the data // of this one results = results.concat(findEbml(data, paths.slice(1))); } } var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it i += totalLength; } return results; }; // see https://www.matroska.org/technical/basics.html#block-structure var decodeBlock = function decodeBlock(block, type, timestampScale, clusterTimestamp) { var duration; if (type === 'group') { duration = findEbml(block, [EBML_TAGS.BlockDuration])[0]; if (duration) { duration = bytesToNumber(duration); duration = 1 / timestampScale * duration * timestampScale / 1000; } block = findEbml(block, [EBML_TAGS.Block])[0]; type = 'block'; // treat data as a block after this point } var dv = new DataView(block.buffer, block.byteOffset, block.byteLength); var trackNumber = getvint(block, 0); var timestamp = dv.getInt16(trackNumber.length, false); var flags = block[trackNumber.length + 2]; var data = block.subarray(trackNumber.length + 3); // pts/dts in seconds var ptsdts = 1 / timestampScale * (clusterTimestamp + timestamp) * timestampScale / 1000; // return the frame var parsed = { duration: duration, trackNumber: trackNumber.value, keyframe: type === 'simple' && flags >> 7 === 1, invisible: (flags & 0x08) >> 3 === 1, lacing: (flags & 0x06) >> 1, discardable: type === 'simple' && (flags & 0x01) === 1, frames: [], pts: ptsdts, dts: ptsdts, timestamp: timestamp }; if (!parsed.lacing) { parsed.frames.push(data); return parsed; } var numberOfFrames = data[0] + 1; var frameSizes = []; var offset = 1; // Fixed if (parsed.lacing === 2) { var sizeOfFrame = (data.length - offset) / numberOfFrames; for (var i = 0; i < numberOfFrames; i++) { frameSizes.push(sizeOfFrame); } } // xiph if (parsed.lacing === 1) { for (var _i = 0; _i < numberOfFrames - 1; _i++) { var size = 0; do { size += data[offset]; offset++; } while (data[offset - 1] === 0xFF); frameSizes.push(size); } } // ebml if (parsed.lacing === 3) { // first vint is unsinged // after that vints are singed and // based on a compounding size var _size = 0; for (var _i2 = 0; _i2 < numberOfFrames - 1; _i2++) { var vint = _i2 === 0 ? getvint(data, offset) : getvint(data, offset, true, true); _size += vint.value; frameSizes.push(_size); offset += vint.length; } } frameSizes.forEach(function (size) { parsed.frames.push(data.subarray(offset, offset + size)); offset += size; }); return parsed; }; // VP9 Codec Feature Metadata (CodecPrivate) // https://www.webmproject.org/docs/container/ var parseVp9Private = function parseVp9Private(bytes) { var i = 0; var params = {}; while (i < bytes.length) { var id = bytes[i] & 0x7f; var len = bytes[i + 1]; var val = void 0; if (len === 1) { val = bytes[i + 2]; } else { val = bytes.subarray(i + 2, i + 2 + len); } if (id === 1) { params.profile = val; } else if (id === 2) { params.level = val; } else if (id === 3) { params.bitDepth = val; } else if (id === 4) { params.chromaSubsampling = val; } else { params[id] = val; } i += 2 + len; } return params; }; var ebml_helpers_parseTracks = function parseTracks(bytes) { bytes = toUint8(bytes); var decodedTracks = []; var tracks = findEbml(bytes, [EBML_TAGS.Segment, EBML_TAGS.Tracks, EBML_TAGS.Track]); if (!tracks.length) { tracks = findEbml(bytes, [EBML_TAGS.Tracks, EBML_TAGS.Track]); } if (!tracks.length) { tracks = findEbml(bytes, [EBML_TAGS.Track]); } if (!tracks.length) { return decodedTracks; } tracks.forEach(function (track) { var trackType = findEbml(track, EBML_TAGS.TrackType)[0]; if (!trackType || !trackType.length) { return; } // 1 is video, 2 is audio, 17 is subtitle // other values are unimportant in this context if (trackType[0] === 1) { trackType = 'video'; } else if (trackType[0] === 2) { trackType = 'audio'; } else if (trackType[0] === 17) { trackType = 'subtitle'; } else { return; } // todo parse language var decodedTrack = { rawCodec: bytesToString(findEbml(track, [EBML_TAGS.CodecID])[0]), type: trackType, codecPrivate: findEbml(track, [EBML_TAGS.CodecPrivate])[0], number: bytesToNumber(findEbml(track, [EBML_TAGS.TrackNumber])[0]), defaultDuration: bytesToNumber(findEbml(track, [EBML_TAGS.DefaultDuration])[0]), default: findEbml(track, [EBML_TAGS.FlagDefault])[0], rawData: track }; var codec = ''; if (/V_MPEG4\/ISO\/AVC/.test(decodedTrack.rawCodec)) { codec = "avc1." + getAvcCodec(decodedTrack.codecPrivate); } else if (/V_MPEGH\/ISO\/HEVC/.test(decodedTrack.rawCodec)) { codec = "hev1." + getHvcCodec(decodedTrack.codecPrivate); } else if (/V_MPEG4\/ISO\/ASP/.test(decodedTrack.rawCodec)) { if (decodedTrack.codecPrivate) { codec = 'mp4v.20.' + decodedTrack.codecPrivate[4].toString(); } else { codec = 'mp4v.20.9'; } } else if (/^V_THEORA/.test(decodedTrack.rawCodec)) { codec = 'theora'; } else if (/^V_VP8/.test(decodedTrack.rawCodec)) { codec = 'vp8'; } else if (/^V_VP9/.test(decodedTrack.rawCodec)) { if (decodedTrack.codecPrivate) { var _parseVp9Private = parseVp9Private(decodedTrack.codecPrivate), profile = _parseVp9Private.profile, level = _parseVp9Private.level, bitDepth = _parseVp9Private.bitDepth, chromaSubsampling = _parseVp9Private.chromaSubsampling; codec = 'vp09.'; codec += padStart(profile, 2, '0') + "."; codec += padStart(level, 2, '0') + "."; codec += padStart(bitDepth, 2, '0') + "."; codec += "" + padStart(chromaSubsampling, 2, '0'); // Video -> Colour -> Ebml name var matrixCoefficients = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xB1]])[0] || []; var videoFullRangeFlag = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xB9]])[0] || []; var transferCharacteristics = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xBA]])[0] || []; var colourPrimaries = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xBB]])[0] || []; // if we find any optional codec parameter specify them all. if (matrixCoefficients.length || videoFullRangeFlag.length || transferCharacteristics.length || colourPrimaries.length) { codec += "." + padStart(colourPrimaries[0], 2, '0'); codec += "." + padStart(transferCharacteristics[0], 2, '0'); codec += "." + padStart(matrixCoefficients[0], 2, '0'); codec += "." + padStart(videoFullRangeFlag[0], 2, '0'); } } else { codec = 'vp9'; } } else if (/^V_AV1/.test(decodedTrack.rawCodec)) { codec = "av01." + getAv1Codec(decodedTrack.codecPrivate); } else if (/A_ALAC/.test(decodedTrack.rawCodec)) { codec = 'alac'; } else if (/A_MPEG\/L2/.test(decodedTrack.rawCodec)) { codec = 'mp2'; } else if (/A_MPEG\/L3/.test(decodedTrack.rawCodec)) { codec = 'mp3'; } else if (/^A_AAC/.test(decodedTrack.rawCodec)) { if (decodedTrack.codecPrivate) { codec = 'mp4a.40.' + (decodedTrack.codecPrivate[0] >>> 3).toString(); } else { codec = 'mp4a.40.2'; } } else if (/^A_AC3/.test(decodedTrack.rawCodec)) { codec = 'ac-3'; } else if (/^A_PCM/.test(decodedTrack.rawCodec)) { codec = 'pcm'; } else if (/^A_MS\/ACM/.test(decodedTrack.rawCodec)) { codec = 'speex'; } else if (/^A_EAC3/.test(decodedTrack.rawCodec)) { codec = 'ec-3'; } else if (/^A_VORBIS/.test(decodedTrack.rawCodec)) { codec = 'vorbis'; } else if (/^A_FLAC/.test(decodedTrack.rawCodec)) { codec = 'flac'; } else if (/^A_OPUS/.test(decodedTrack.rawCodec)) { codec = 'opus'; } decodedTrack.codec = codec; decodedTracks.push(decodedTrack); }); return decodedTracks.sort(function (a, b) { return a.number - b.number; }); }; var parseData = function parseData(data, tracks) { var allBlocks = []; var segment = findEbml(data, [EBML_TAGS.Segment])[0]; var timestampScale = findEbml(segment, [EBML_TAGS.SegmentInfo, EBML_TAGS.TimestampScale])[0]; // in nanoseconds, defaults to 1ms if (timestampScale && timestampScale.length) { timestampScale = bytesToNumber(timestampScale); } else { timestampScale = 1000000; } var clusters = findEbml(segment, [EBML_TAGS.Cluster]); if (!tracks) { tracks = ebml_helpers_parseTracks(segment); } clusters.forEach(function (cluster, ci) { var simpleBlocks = findEbml(cluster, [EBML_TAGS.SimpleBlock]).map(function (b) { return { type: 'simple', data: b }; }); var blockGroups = findEbml(cluster, [EBML_TAGS.BlockGroup]).map(function (b) { return { type: 'group', data: b }; }); var timestamp = findEbml(cluster, [EBML_TAGS.Timestamp])[0] || 0; if (timestamp && timestamp.length) { timestamp = bytesToNumber(timestamp); } // get all blocks then sort them into the correct order var blocks = simpleBlocks.concat(blockGroups).sort(function (a, b) { return a.data.byteOffset - b.data.byteOffset; }); blocks.forEach(function (block, bi) { var decoded = decodeBlock(block.data, block.type, timestampScale, timestamp); allBlocks.push(decoded); }); }); return { tracks: tracks, blocks: allBlocks }; }; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/nal-helpers.js var NAL_TYPE_ONE = byte_helpers_toUint8([0x00, 0x00, 0x00, 0x01]); var NAL_TYPE_TWO = byte_helpers_toUint8([0x00, 0x00, 0x01]); var EMULATION_PREVENTION = byte_helpers_toUint8([0x00, 0x00, 0x03]); /** * Expunge any "Emulation Prevention" bytes from a "Raw Byte * Sequence Payload" * * @param data {Uint8Array} the bytes of a RBSP from a NAL * unit * @return {Uint8Array} the RBSP without any Emulation * Prevention Bytes */ var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(bytes) { var positions = []; var i = 1; // Find all `Emulation Prevention Bytes` while (i < bytes.length - 2) { if (byte_helpers_bytesMatch(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) { positions.push(i + 2); i++; } i++; } // If no Emulation Prevention Bytes were found just return the original // array if (positions.length === 0) { return bytes; } // Create a new array to hold the NAL unit data var newLength = bytes.length - positions.length; var newData = new Uint8Array(newLength); var sourceIndex = 0; for (i = 0; i < newLength; sourceIndex++, i++) { if (sourceIndex === positions[0]) { // Skip this byte sourceIndex++; // Remove this position index positions.shift(); } newData[i] = bytes[sourceIndex]; } return newData; }; var findNal = function findNal(bytes, dataType, types, nalLimit) { if (nalLimit === void 0) { nalLimit = Infinity; } bytes = byte_helpers_toUint8(bytes); types = [].concat(types); var i = 0; var nalStart; var nalsFound = 0; // keep searching until: // we reach the end of bytes // we reach the maximum number of nals they want to seach // NOTE: that we disregard nalLimit when we have found the start // of the nal we want so that we can find the end of the nal we want. while (i < bytes.length && (nalsFound < nalLimit || nalStart)) { var nalOffset = void 0; if (byte_helpers_bytesMatch(bytes.subarray(i), NAL_TYPE_ONE)) { nalOffset = 4; } else if (byte_helpers_bytesMatch(bytes.subarray(i), NAL_TYPE_TWO)) { nalOffset = 3; } // we are unsynced, // find the next nal unit if (!nalOffset) { i++; continue; } nalsFound++; if (nalStart) { return discardEmulationPreventionBytes(bytes.subarray(nalStart, i)); } var nalType = void 0; if (dataType === 'h264') { nalType = bytes[i + nalOffset] & 0x1f; } else if (dataType === 'h265') { nalType = bytes[i + nalOffset] >> 1 & 0x3f; } if (types.indexOf(nalType) !== -1) { nalStart = i + nalOffset; } // nal header is 1 length for h264, and 2 for h265 i += nalOffset + (dataType === 'h264' ? 1 : 2); } return bytes.subarray(0, 0); }; var findH264Nal = function findH264Nal(bytes, type, nalLimit) { return findNal(bytes, 'h264', type, nalLimit); }; var findH265Nal = function findH265Nal(bytes, type, nalLimit) { return findNal(bytes, 'h265', type, nalLimit); }; ;// CONCATENATED MODULE: ./node_modules/@videojs/vhs-utils/es/containers.js var CONSTANTS = { // "webm" string literal in hex 'webm': byte_helpers_toUint8([0x77, 0x65, 0x62, 0x6d]), // "matroska" string literal in hex 'matroska': byte_helpers_toUint8([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]), // "fLaC" string literal in hex 'flac': byte_helpers_toUint8([0x66, 0x4c, 0x61, 0x43]), // "OggS" string literal in hex 'ogg': byte_helpers_toUint8([0x4f, 0x67, 0x67, 0x53]), // ac-3 sync byte, also works for ec-3 as that is simply a codec // of ac-3 'ac3': byte_helpers_toUint8([0x0b, 0x77]), // "RIFF" string literal in hex used for wav and avi 'riff': byte_helpers_toUint8([0x52, 0x49, 0x46, 0x46]), // "AVI" string literal in hex 'avi': byte_helpers_toUint8([0x41, 0x56, 0x49]), // "WAVE" string literal in hex 'wav': byte_helpers_toUint8([0x57, 0x41, 0x56, 0x45]), // "ftyp3g" string literal in hex '3gp': byte_helpers_toUint8([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]), // "ftyp" string literal in hex 'mp4': byte_helpers_toUint8([0x66, 0x74, 0x79, 0x70]), // "styp" string literal in hex 'fmp4': byte_helpers_toUint8([0x73, 0x74, 0x79, 0x70]), // "ftypqt" string literal in hex 'mov': byte_helpers_toUint8([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]), // moov string literal in hex 'moov': byte_helpers_toUint8([0x6D, 0x6F, 0x6F, 0x76]), // moof string literal in hex 'moof': byte_helpers_toUint8([0x6D, 0x6F, 0x6F, 0x66]) }; var _isLikely = { aac: function aac(bytes) { var offset = getId3Offset(bytes); return byte_helpers_bytesMatch(bytes, [0xFF, 0x10], { offset: offset, mask: [0xFF, 0x16] }); }, mp3: function mp3(bytes) { var offset = getId3Offset(bytes); return byte_helpers_bytesMatch(bytes, [0xFF, 0x02], { offset: offset, mask: [0xFF, 0x06] }); }, webm: function webm(bytes) { var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm return byte_helpers_bytesMatch(docType, CONSTANTS.webm); }, mkv: function mkv(bytes) { var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska return byte_helpers_bytesMatch(docType, CONSTANTS.matroska); }, mp4: function mp4(bytes) { // if this file is another base media file format, it is not mp4 if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) { return false; } // if this file starts with a ftyp or styp box its mp4 if (byte_helpers_bytesMatch(bytes, CONSTANTS.mp4, { offset: 4 }) || byte_helpers_bytesMatch(bytes, CONSTANTS.fmp4, { offset: 4 })) { return true; } // if this file starts with a moof/moov box its mp4 if (byte_helpers_bytesMatch(bytes, CONSTANTS.moof, { offset: 4 }) || byte_helpers_bytesMatch(bytes, CONSTANTS.moov, { offset: 4 })) { return true; } }, mov: function mov(bytes) { return byte_helpers_bytesMatch(bytes, CONSTANTS.mov, { offset: 4 }); }, '3gp': function gp(bytes) { return byte_helpers_bytesMatch(bytes, CONSTANTS['3gp'], { offset: 4 }); }, ac3: function ac3(bytes) { var offset = getId3Offset(bytes); return byte_helpers_bytesMatch(bytes, CONSTANTS.ac3, { offset: offset }); }, ts: function ts(bytes) { if (bytes.length < 189 && bytes.length >= 1) { return bytes[0] === 0x47; } var i = 0; // check the first 376 bytes for two matching sync bytes while (i + 188 < bytes.length && i < 188) { if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) { return true; } i += 1; } return false; }, flac: function flac(bytes) { var offset = getId3Offset(bytes); return byte_helpers_bytesMatch(bytes, CONSTANTS.flac, { offset: offset }); }, ogg: function ogg(bytes) { return byte_helpers_bytesMatch(bytes, CONSTANTS.ogg); }, avi: function avi(bytes) { return byte_helpers_bytesMatch(bytes, CONSTANTS.riff) && byte_helpers_bytesMatch(bytes, CONSTANTS.avi, { offset: 8 }); }, wav: function wav(bytes) { return byte_helpers_bytesMatch(bytes, CONSTANTS.riff) && byte_helpers_bytesMatch(bytes, CONSTANTS.wav, { offset: 8 }); }, 'h264': function h264(bytes) { // find seq_parameter_set_rbsp return findH264Nal(bytes, 7, 3).length; }, 'h265': function h265(bytes) { // find video_parameter_set_rbsp or seq_parameter_set_rbsp return findH265Nal(bytes, [32, 33], 3).length; } }; // get all the isLikely functions // but make sure 'ts' is above h264 and h265 // but below everything else as it is the least specific var isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265 .filter(function (t) { return t !== 'ts' && t !== 'h264' && t !== 'h265'; }) // add it back to the bottom .concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data. isLikelyTypes.forEach(function (type) { var isLikelyFn = _isLikely[type]; _isLikely[type] = function (bytes) { return isLikelyFn(byte_helpers_toUint8(bytes)); }; }); // export after wrapping var isLikely = _isLikely; // A useful list of file signatures can be found here // https://en.wikipedia.org/wiki/List_of_file_signatures var detectContainerForBytes = function detectContainerForBytes(bytes) { bytes = byte_helpers_toUint8(bytes); for (var i = 0; i < isLikelyTypes.length; i++) { var type = isLikelyTypes[i]; if (isLikely[type](bytes)) { return type; } } return ''; }; // fmp4 is not a container var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) { return findBox(bytes, ['moof']).length > 0; }; // EXTERNAL MODULE: ./node_modules/mux.js/lib/utils/clock.js var clock = __webpack_require__(489); ;// CONCATENATED MODULE: ./node_modules/video.js/dist/video.es.js /** * @license * Video.js 8.23.7 * Copyright 2010-present Video.js contributors * Available under Apache License Version 2.0 * * * Includes vtt.js * Available under Apache License Version 2.0 * */ var version$6 = "8.23.7"; /** * An Object that contains lifecycle hooks as keys which point to an array * of functions that are run when a lifecycle is triggered * * @private */ const hooks_ = {}; /** * Get a list of hooks for a specific lifecycle * * @param {string} type * the lifecycle to get hooks from * * @param {Function|Function[]} [fn] * Optionally add a hook (or hooks) to the lifecycle that your are getting. * * @return {Array} * an array of hooks, or an empty array if there are none. */ const hooks = function (type, fn) { hooks_[type] = hooks_[type] || []; if (fn) { hooks_[type] = hooks_[type].concat(fn); } return hooks_[type]; }; /** * Add a function hook to a specific videojs lifecycle. * * @param {string} type * the lifecycle to hook the function to. * * @param {Function|Function[]} * The function or array of functions to attach. */ const hook = function (type, fn) { hooks(type, fn); }; /** * Remove a hook from a specific videojs lifecycle. * * @param {string} type * the lifecycle that the function hooked to * * @param {Function} fn * The hooked function to remove * * @return {boolean} * The function that was removed or undef */ const removeHook = function (type, fn) { const index = hooks(type).indexOf(fn); if (index <= -1) { return false; } hooks_[type] = hooks_[type].slice(); hooks_[type].splice(index, 1); return true; }; /** * Add a function hook that will only run once to a specific videojs lifecycle. * * @param {string} type * the lifecycle to hook the function to. * * @param {Function|Function[]} * The function or array of functions to attach. */ const hookOnce = function (type, fn) { hooks(type, [].concat(fn).map(original => { const wrapper = (...args) => { removeHook(type, wrapper); return original(...args); }; return wrapper; })); }; /** * @file fullscreen-api.js * @module fullscreen-api */ /** * Store the browser-specific methods for the fullscreen API. * * @type {Object} * @see [Specification]{@link https://fullscreen.spec.whatwg.org} * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} */ const FullscreenApi = { prefixed: true }; // browser API methods const apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen']]; const specApi = apiMap[0]; let browserApi; // determine the supported set of functions for (let i = 0; i < apiMap.length; i++) { // check for exitFullscreen function if (apiMap[i][1] in (document_default())) { browserApi = apiMap[i]; break; } } // map the browser API names to the spec API names if (browserApi) { for (let i = 0; i < browserApi.length; i++) { FullscreenApi[specApi[i]] = browserApi[i]; } FullscreenApi.prefixed = browserApi[0] !== specApi[0]; } /** * @file create-logger.js * @module create-logger */ // This is the private tracking variable for the logging history. let video_es_history = []; /** * Log messages to the console and history based on the type of message * * @private * @param {string} name * The name of the console method to use. * * @param {Object} log * The arguments to be passed to the matching console method. * * @param {string} [styles] * styles for name */ const LogByTypeFactory = (name, log, styles) => (type, level, args) => { const lvl = log.levels[level]; const lvlRegExp = new RegExp(`^(${lvl})$`); let resultName = name; if (type !== 'log') { // Add the type to the front of the message when it's not "log". args.unshift(type.toUpperCase() + ':'); } if (styles) { resultName = `%c${name}`; args.unshift(styles); } // Add console prefix after adding to history. args.unshift(resultName + ':'); // Add a clone of the args at this point to history. if (video_es_history) { video_es_history.push([].concat(args)); // only store 1000 history entries const splice = video_es_history.length - 1000; video_es_history.splice(0, splice > 0 ? splice : 0); } // If there's no console then don't try to output messages, but they will // still be stored in history. if (!(window_default()).console) { return; } // Was setting these once outside of this function, but containing them // in the function makes it easier to test cases where console doesn't exist // when the module is executed. let fn = (window_default()).console[type]; if (!fn && type === 'debug') { // Certain browsers don't have support for console.debug. For those, we // should default to the closest comparable log. fn = (window_default()).console.info || (window_default()).console.log; } // Bail out if there's no console or if this type is not allowed by the // current logging level. if (!fn || !lvl || !lvlRegExp.test(type)) { return; } fn[Array.isArray(args) ? 'apply' : 'call']((window_default()).console, args); }; function createLogger$1(name, delimiter = ':', styles = '') { // This is the private tracking variable for logging level. let level = 'info'; // the curried logByType bound to the specific log and history let logByType; /** * Logs plain debug messages. Similar to `console.log`. * * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) * of our JSDoc template, we cannot properly document this as both a function * and a namespace, so its function signature is documented here. * * #### Arguments * ##### *args * *[] * * Any combination of values that could be passed to `console.log()`. * * #### Return Value * * `undefined` * * @namespace * @param {...*} args * One or more messages or objects that should be logged. */ function log(...args) { logByType('log', level, args); } // This is the logByType helper that the logging methods below use logByType = LogByTypeFactory(name, log, styles); /** * Create a new subLogger which chains the old name to the new name. * * For example, doing `mylogger = videojs.log.createLogger('player')` and then using that logger will log the following: * ```js * mylogger('foo'); * // > VIDEOJS: player: foo * ``` * * @param {string} subName * The name to add call the new logger * @param {string} [subDelimiter] * Optional delimiter * @param {string} [subStyles] * Optional styles * @return {Object} */ log.createLogger = (subName, subDelimiter, subStyles) => { const resultDelimiter = subDelimiter !== undefined ? subDelimiter : delimiter; const resultStyles = subStyles !== undefined ? subStyles : styles; const resultName = `${name} ${resultDelimiter} ${subName}`; return createLogger$1(resultName, resultDelimiter, resultStyles); }; /** * Create a new logger. * * @param {string} newName * The name for the new logger * @param {string} [newDelimiter] * Optional delimiter * @param {string} [newStyles] * Optional styles * @return {Object} */ log.createNewLogger = (newName, newDelimiter, newStyles) => { return createLogger$1(newName, newDelimiter, newStyles); }; /** * Enumeration of available logging levels, where the keys are the level names * and the values are `|`-separated strings containing logging methods allowed * in that logging level. These strings are used to create a regular expression * matching the function name being called. * * Levels provided by Video.js are: * * - `off`: Matches no calls. Any value that can be cast to `false` will have * this effect. The most restrictive. * - `all`: Matches only Video.js-provided functions (`debug`, `log`, * `log.warn`, and `log.error`). * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. * - `warn`: Matches `log.warn` and `log.error` calls. * - `error`: Matches only `log.error` calls. * * @type {Object} */ log.levels = { all: 'debug|log|warn|error', off: '', debug: 'debug|log|warn|error', info: 'log|warn|error', warn: 'warn|error', error: 'error', DEFAULT: level }; /** * Get or set the current logging level. * * If a string matching a key from {@link module:log.levels} is provided, acts * as a setter. * * @param {'all'|'debug'|'info'|'warn'|'error'|'off'} [lvl] * Pass a valid level to set a new logging level. * * @return {string} * The current logging level. */ log.level = lvl => { if (typeof lvl === 'string') { if (!log.levels.hasOwnProperty(lvl)) { throw new Error(`"${lvl}" in not a valid log level`); } level = lvl; } return level; }; /** * Returns an array containing everything that has been logged to the history. * * This array is a shallow clone of the internal history record. However, its * contents are _not_ cloned; so, mutating objects inside this array will * mutate them in history. * * @return {Array} */ log.history = () => video_es_history ? [].concat(video_es_history) : []; /** * Allows you to filter the history by the given logger name * * @param {string} fname * The name to filter by * * @return {Array} * The filtered list to return */ log.history.filter = fname => { return (video_es_history || []).filter(historyItem => { // if the first item in each historyItem includes `fname`, then it's a match return new RegExp(`.*${fname}.*`).test(historyItem[0]); }); }; /** * Clears the internal history tracking, but does not prevent further history * tracking. */ log.history.clear = () => { if (video_es_history) { video_es_history.length = 0; } }; /** * Disable history tracking if it is currently enabled. */ log.history.disable = () => { if (video_es_history !== null) { video_es_history.length = 0; video_es_history = null; } }; /** * Enable history tracking if it is currently disabled. */ log.history.enable = () => { if (video_es_history === null) { video_es_history = []; } }; /** * Logs error messages. Similar to `console.error`. * * @param {...*} args * One or more messages or objects that should be logged as an error */ log.error = (...args) => logByType('error', level, args); /** * Logs warning messages. Similar to `console.warn`. * * @param {...*} args * One or more messages or objects that should be logged as a warning. */ log.warn = (...args) => logByType('warn', level, args); /** * Logs debug messages. Similar to `console.debug`, but may also act as a comparable * log if `console.debug` is not available * * @param {...*} args * One or more messages or objects that should be logged as debug. */ log.debug = (...args) => logByType('debug', level, args); return log; } /** * @file log.js * @module log */ const log$1 = createLogger$1('VIDEOJS'); const createLogger = log$1.createLogger; /** * @file obj.js * @module obj */ /** * @callback obj:EachCallback * * @param {*} value * The current key for the object that is being iterated over. * * @param {string} key * The current key-value for object that is being iterated over */ /** * @callback obj:ReduceCallback * * @param {*} accum * The value that is accumulating over the reduce loop. * * @param {*} value * The current key for the object that is being iterated over. * * @param {string} key * The current key-value for object that is being iterated over * * @return {*} * The new accumulated value. */ const video_es_toString = Object.prototype.toString; /** * Get the keys of an Object * * @param {Object} * The Object to get the keys from * * @return {string[]} * An array of the keys from the object. Returns an empty array if the * object passed in was invalid or had no keys. * * @private */ const keys = function (object) { return video_es_isObject(object) ? Object.keys(object) : []; }; /** * Array-like iteration for objects. * * @param {Object} object * The object to iterate over * * @param {obj:EachCallback} fn * The callback function which is called for each key in the object. */ function each(object, fn) { keys(object).forEach(key => fn(object[key], key)); } /** * Array-like reduce for objects. * * @param {Object} object * The Object that you want to reduce. * * @param {Function} fn * A callback function which is called for each key in the object. It * receives the accumulated value and the per-iteration value and key * as arguments. * * @param {*} [initial = 0] * Starting value * * @return {*} * The final accumulated value. */ function reduce(object, fn, initial = 0) { return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial); } /** * Returns whether a value is an object of any kind - including DOM nodes, * arrays, regular expressions, etc. Not functions, though. * * This avoids the gotcha where using `typeof` on a `null` value * results in `'object'`. * * @param {Object} value * @return {boolean} */ function video_es_isObject(value) { return !!value && typeof value === 'object'; } /** * Returns whether an object appears to be a "plain" object - that is, a * direct instance of `Object`. * * @param {Object} value * @return {boolean} */ function isPlain(value) { return video_es_isObject(value) && video_es_toString.call(value) === '[object Object]' && value.constructor === Object; } /** * Merge two objects recursively. * * Performs a deep merge like * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges * plain objects (not arrays, elements, or anything else). * * Non-plain object values will be copied directly from the right-most * argument. * * @param {Object[]} sources * One or more objects to merge into a new object. * * @return {Object} * A new object that is the merged result of all sources. */ function merge$1(...sources) { const result = {}; sources.forEach(source => { if (!source) { return; } each(source, (value, key) => { if (!isPlain(value)) { result[key] = value; return; } if (!isPlain(result[key])) { result[key] = {}; } result[key] = merge$1(result[key], value); }); }); return result; } /** * Returns an array of values for a given object * * @param {Object} source - target object * @return {Array} - object values */ function video_es_values(source = {}) { const result = []; for (const key in source) { if (source.hasOwnProperty(key)) { const value = source[key]; result.push(value); } } return result; } /** * Object.defineProperty but "lazy", which means that the value is only set after * it is 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 whether a setter should be allowed or not */ function defineLazyProperty(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); } var Obj = /*#__PURE__*/Object.freeze({ __proto__: null, each: each, reduce: reduce, isObject: video_es_isObject, isPlain: isPlain, merge: merge$1, values: video_es_values, defineLazyProperty: defineLazyProperty }); /** * @file browser.js * @module browser */ /** * Whether or not this device is an iPod. * * @static * @type {Boolean} */ let IS_IPOD = false; /** * The detected iOS version - or `null`. * * @static * @type {string|null} */ let IOS_VERSION = null; /** * Whether or not this is an Android device. * * @static * @type {Boolean} */ let IS_ANDROID = false; /** * The detected Android version - or `null` if not Android or indeterminable. * * @static * @type {number|string|null} */ let ANDROID_VERSION; /** * Whether or not this is Mozilla Firefox. * * @static * @type {Boolean} */ let IS_FIREFOX = false; /** * Whether or not this is Microsoft Edge. * * @static * @type {Boolean} */ let IS_EDGE = false; /** * Whether or not this is any Chromium Browser * * @static * @type {Boolean} */ let IS_CHROMIUM = false; /** * Whether or not this is any Chromium browser that is not Edge. * * This will also be `true` for Chrome on iOS, which will have different support * as it is actually Safari under the hood. * * Deprecated, as the behaviour to not match Edge was to prevent Legacy Edge's UA matching. * IS_CHROMIUM should be used instead. * "Chromium but not Edge" could be explicitly tested with IS_CHROMIUM && !IS_EDGE * * @static * @deprecated * @type {Boolean} */ let IS_CHROME = false; /** * The detected Chromium version - or `null`. * * @static * @type {number|null} */ let CHROMIUM_VERSION = null; /** * The detected Google Chrome version - or `null`. * This has always been the _Chromium_ version, i.e. would return on Chromium Edge. * Deprecated, use CHROMIUM_VERSION instead. * * @static * @deprecated * @type {number|null} */ let CHROME_VERSION = null; /** * Whether or not this is a Chromecast receiver application. * * @static * @type {Boolean} */ const IS_CHROMECAST_RECEIVER = Boolean((window_default()).cast && (window_default()).cast.framework && (window_default()).cast.framework.CastReceiverContext); /** * The detected Internet Explorer version - or `null`. * * @static * @deprecated * @type {number|null} */ let IE_VERSION = null; /** * Whether or not this is desktop Safari. * * @static * @type {Boolean} */ let IS_SAFARI = false; /** * Whether or not this is a Windows machine. * * @static * @type {Boolean} */ let IS_WINDOWS = false; /** * Whether or not this device is an iPad. * * @static * @type {Boolean} */ let IS_IPAD = false; /** * Whether or not this device is an iPhone. * * @static * @type {Boolean} */ // The Facebook app's UIWebView identifies as both an iPhone and iPad, so // to identify iPhones, we need to exclude iPads. // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ let IS_IPHONE = false; /** * Whether or not this is a Tizen device. * * @static * @type {Boolean} */ let IS_TIZEN = false; /** * Whether or not this is a WebOS device. * * @static * @type {Boolean} */ let IS_WEBOS = false; /** * Whether or not this is a Smart TV (Tizen or WebOS) device. * * @static * @type {Boolean} */ let IS_SMART_TV = false; /** * Whether or not this device is touch-enabled. * * @static * @const * @type {Boolean} */ const TOUCH_ENABLED = Boolean(isReal() && ("ontouchstart" in (window_default()) || (window_default()).navigator.maxTouchPoints || (window_default()).DocumentTouch && (window_default()).document instanceof (window_default()).DocumentTouch)); const UAD = (window_default()).navigator && (window_default()).navigator.userAgentData; if (UAD && UAD.platform && UAD.brands) { // If userAgentData is present, use it instead of userAgent to avoid warnings // Currently only implemented on Chromium // userAgentData does not expose Android version, so ANDROID_VERSION remains `null` IS_ANDROID = UAD.platform === 'Android'; IS_EDGE = Boolean(UAD.brands.find(b => b.brand === 'Microsoft Edge')); IS_CHROMIUM = Boolean(UAD.brands.find(b => b.brand === 'Chromium')); IS_CHROME = !IS_EDGE && IS_CHROMIUM; CHROMIUM_VERSION = CHROME_VERSION = (UAD.brands.find(b => b.brand === 'Chromium') || {}).version || null; IS_WINDOWS = UAD.platform === 'Windows'; } // If the browser is not Chromium, either userAgentData is not present which could be an old Chromium browser, // or it's a browser that has added userAgentData since that we don't have tests for yet. In either case, // the checks need to be made agiainst the regular userAgent string. if (!IS_CHROMIUM) { const USER_AGENT = (window_default()).navigator && (window_default()).navigator.userAgent || ''; IS_IPOD = /iPod/i.test(USER_AGENT); IOS_VERSION = function () { const match = USER_AGENT.match(/OS (\d+)_/i); if (match && match[1]) { return match[1]; } return null; }(); IS_ANDROID = /Android/i.test(USER_AGENT); ANDROID_VERSION = function () { // This matches Android Major.Minor.Patch versions // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned const match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); if (!match) { return null; } const major = match[1] && parseFloat(match[1]); const minor = match[2] && parseFloat(match[2]); if (major && minor) { return parseFloat(match[1] + '.' + match[2]); } else if (major) { return major; } return null; }(); IS_FIREFOX = /Firefox/i.test(USER_AGENT); IS_EDGE = /Edg/i.test(USER_AGENT); IS_CHROMIUM = /Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT); IS_CHROME = !IS_EDGE && IS_CHROMIUM; CHROMIUM_VERSION = CHROME_VERSION = function () { const match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); if (match && match[2]) { return parseFloat(match[2]); } return null; }(); IE_VERSION = function () { const result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); let version = result && parseFloat(result[1]); if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { // IE 11 has a different user agent string than other IE versions version = 11.0; } return version; }(); IS_TIZEN = /Tizen/i.test(USER_AGENT); IS_WEBOS = /Web0S/i.test(USER_AGENT); IS_SMART_TV = IS_TIZEN || IS_WEBOS; IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE && !IS_SMART_TV; IS_WINDOWS = /Windows/i.test(USER_AGENT); IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT); IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; } /** * Whether or not this is an iOS device. * * @static * @const * @type {Boolean} */ const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; /** * Whether or not this is any flavor of Safari - including iOS. * * @static * @const * @type {Boolean} */ const IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; var video_es_browser = /*#__PURE__*/Object.freeze({ __proto__: null, get IS_IPOD () { return IS_IPOD; }, get IOS_VERSION () { return IOS_VERSION; }, get IS_ANDROID () { return IS_ANDROID; }, get ANDROID_VERSION () { return ANDROID_VERSION; }, get IS_FIREFOX () { return IS_FIREFOX; }, get IS_EDGE () { return IS_EDGE; }, get IS_CHROMIUM () { return IS_CHROMIUM; }, get IS_CHROME () { return IS_CHROME; }, get CHROMIUM_VERSION () { return CHROMIUM_VERSION; }, get CHROME_VERSION () { return CHROME_VERSION; }, IS_CHROMECAST_RECEIVER: IS_CHROMECAST_RECEIVER, get IE_VERSION () { return IE_VERSION; }, get IS_SAFARI () { return IS_SAFARI; }, get IS_WINDOWS () { return IS_WINDOWS; }, get IS_IPAD () { return IS_IPAD; }, get IS_IPHONE () { return IS_IPHONE; }, get IS_TIZEN () { return IS_TIZEN; }, get IS_WEBOS () { return IS_WEBOS; }, get IS_SMART_TV () { return IS_SMART_TV; }, TOUCH_ENABLED: TOUCH_ENABLED, IS_IOS: IS_IOS, IS_ANY_SAFARI: IS_ANY_SAFARI }); /** * @file dom.js * @module dom */ /** * Detect if a value is a string with any non-whitespace characters. * * @private * @param {string} str * The string to check * * @return {boolean} * Will be `true` if the string is non-blank, `false` otherwise. * */ function isNonBlankString(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()); } /** * Throws an error if the passed string has whitespace. This is used by * class methods to be relatively consistent with the classList API. * * @private * @param {string} str * The string to check for whitespace. * * @throws {Error} * Throws an error if there is whitespace in the string. */ function throwIfWhitespace(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'); } } /** * Whether the current DOM interface appears to be real (i.e. not simulated). * * @return {boolean} * Will be `true` if the DOM appears to be real, `false` otherwise. */ function isReal() { // Both document and window will never be undefined thanks to `global`. return (document_default()) === (window_default()).document; } /** * Determines, via duck typing, whether or not a value is a DOM element. * * @param {*} value * The value to check. * * @return {boolean} * Will be `true` if the value is a DOM element, `false` otherwise. */ function isEl(value) { return video_es_isObject(value) && value.nodeType === 1; } /** * Determines if the current DOM is embedded in an iframe. * * @return {boolean} * Will be `true` if the DOM is embedded in an iframe, `false` * otherwise. */ function isInFrame() { // We need a try/catch here because Safari will throw errors when attempting // to get either `parent` or `self` try { return (window_default()).parent !== (window_default()).self; } catch (x) { return true; } } /** * Creates functions to query the DOM using a given method. * * @private * @param {string} method * The method to create the query with. * * @return {Function} * The query method */ function createQuerier(method) { return function (selector, context) { if (!isNonBlankString(selector)) { return (document_default())[method](null); } if (isNonBlankString(context)) { context = document_default().querySelector(context); } const ctx = isEl(context) ? context : (document_default()); return ctx[method] && ctx[method](selector); }; } /** * Creates an element and applies properties, attributes, and inserts content. * * @param {string} [tagName='div'] * Name of tag to be created. * * @param {Object} [properties={}] * Element properties to be applied. * * @param {Object} [attributes={}] * Element attributes to be applied. * * @param {ContentDescriptor} [content] * A content descriptor object. * * @return {Element} * The element that was created. */ function createEl(tagName = 'div', properties = {}, attributes = {}, content) { const el = document_default().createElement(tagName); Object.getOwnPropertyNames(properties).forEach(function (propName) { const val = properties[propName]; // Handle textContent since it's not supported everywhere and we have a // method for it. if (propName === 'textContent') { textContent(el, val); } else if (el[propName] !== val || propName === 'tabIndex') { el[propName] = val; } }); Object.getOwnPropertyNames(attributes).forEach(function (attrName) { el.setAttribute(attrName, attributes[attrName]); }); if (content) { appendContent(el, content); } return el; } /** * Injects text into an element, replacing any existing contents entirely. * * @param {HTMLElement} el * The element to add text content into * * @param {string} text * The text content to add. * * @return {Element} * The element with added text content. */ function textContent(el, text) { if (typeof el.textContent === 'undefined') { el.innerText = text; } else { el.textContent = text; } return el; } /** * Insert an element as the first child node of another * * @param {Element} child * Element to insert * * @param {Element} parent * Element to insert child into */ function prependTo(child, parent) { if (parent.firstChild) { parent.insertBefore(child, parent.firstChild); } else { parent.appendChild(child); } } /** * Check if an element has a class name. * * @param {Element} element * Element to check * * @param {string} classToCheck * Class name to check for * * @return {boolean} * Will be `true` if the element has a class, `false` otherwise. * * @throws {Error} * Throws an error if `classToCheck` has white space. */ function hasClass(element, classToCheck) { throwIfWhitespace(classToCheck); return element.classList.contains(classToCheck); } /** * Add a class name to an element. * * @param {Element} element * Element to add class name to. * * @param {...string} classesToAdd * One or more class name to add. * * @return {Element} * The DOM element with the added class name. */ function addClass(element, ...classesToAdd) { element.classList.add(...classesToAdd.reduce((prev, current) => prev.concat(current.split(/\s+/)), [])); return element; } /** * Remove a class name from an element. * * @param {Element} element * Element to remove a class name from. * * @param {...string} classesToRemove * One or more class name to remove. * * @return {Element} * The DOM element with class name removed. */ function removeClass(element, ...classesToRemove) { // Protect in case the player gets disposed if (!element) { log$1.warn("removeClass was called with an element that doesn't exist"); return null; } element.classList.remove(...classesToRemove.reduce((prev, current) => prev.concat(current.split(/\s+/)), [])); return element; } /** * The callback definition for toggleClass. * * @callback PredicateCallback * @param {Element} element * The DOM element of the Component. * * @param {string} classToToggle * The `className` that wants to be toggled * * @return {boolean|undefined} * If `true` is returned, the `classToToggle` will be added to the * `element`, but not removed. If `false`, the `classToToggle` will be removed from * the `element`, but not added. If `undefined`, the callback will be ignored. * */ /** * Adds or removes a class name to/from an element depending on an optional * condition or the presence/absence of the class name. * * @param {Element} element * The element to toggle a class name on. * * @param {string} classToToggle * The class that should be toggled. * * @param {boolean|PredicateCallback} [predicate] * See the return value for {@link module:dom~PredicateCallback} * * @return {Element} * The element with a class that has been toggled. */ function toggleClass(element, classToToggle, predicate) { if (typeof predicate === 'function') { predicate = predicate(element, classToToggle); } if (typeof predicate !== 'boolean') { predicate = undefined; } classToToggle.split(/\s+/).forEach(className => element.classList.toggle(className, predicate)); return element; } /** * Apply attributes to an HTML element. * * @param {Element} el * Element to add attributes to. * * @param {Object} [attributes] * Attributes to be applied. */ function setAttributes(el, attributes) { Object.getOwnPropertyNames(attributes).forEach(function (attrName) { const attrValue = attributes[attrName]; if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { el.removeAttribute(attrName); } else { el.setAttribute(attrName, attrValue === true ? '' : attrValue); } }); } /** * Get an element's attribute values, as defined on the HTML tag. * * Attributes are not the same as properties. They're defined on the tag * or with setAttribute. * * @param {Element} tag * Element from which to get tag attributes. * * @return {Object} * All attributes of the element. Boolean attributes will be `true` or * `false`, others will be strings. */ function getAttributes(tag) { const obj = {}; // known boolean attributes // we can check for matching boolean properties, but not all browsers // and not all tags know about these attributes, so, we still want to check them manually const knownBooleans = ['autoplay', 'controls', 'playsinline', 'loop', 'muted', 'default', 'defaultMuted']; if (tag && tag.attributes && tag.attributes.length > 0) { const attrs = tag.attributes; for (let i = attrs.length - 1; i >= 0; i--) { const attrName = attrs[i].name; /** @type {boolean|string} */ let attrVal = attrs[i].value; // check for known booleans // the matching element property will return a value for typeof if (knownBooleans.includes(attrName)) { // the value of an included boolean attribute is typically an empty // string ('') which would equal false if we just check for a false value. // we also don't want support bad code like autoplay='false' attrVal = attrVal !== null ? true : false; } obj[attrName] = attrVal; } } return obj; } /** * Get the value of an element's attribute. * * @param {Element} el * A DOM element. * * @param {string} attribute * Attribute to get the value of. * * @return {string} * The value of the attribute. */ function getAttribute(el, attribute) { return el.getAttribute(attribute); } /** * Set the value of an element's attribute. * * @param {Element} el * A DOM element. * * @param {string} attribute * Attribute to set. * * @param {string} value * Value to set the attribute to. */ function setAttribute(el, attribute, value) { el.setAttribute(attribute, value); } /** * Remove an element's attribute. * * @param {Element} el * A DOM element. * * @param {string} attribute * Attribute to remove. */ function removeAttribute(el, attribute) { el.removeAttribute(attribute); } /** * Attempt to block the ability to select text. */ function blockTextSelection() { document_default().body.focus(); (document_default()).onselectstart = function () { return false; }; } /** * Turn off text selection blocking. */ function unblockTextSelection() { (document_default()).onselectstart = function () { return true; }; } /** * Identical to the native `getBoundingClientRect` function, but ensures that * the method is supported at all (it is in all browsers we claim to support) * and that the element is in the DOM before continuing. * * This wrapper function also shims properties which are not provided by some * older browsers (namely, IE8). * * Additionally, some browsers do not support adding properties to a * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard * properties (except `x` and `y` which are not widely supported). This helps * avoid implementations where keys are non-enumerable. * * @param {Element} el * Element whose `ClientRect` we want to calculate. * * @return {Object|undefined} * Always returns a plain object - or `undefined` if it cannot. */ function getBoundingClientRect(el) { if (el && el.getBoundingClientRect && el.parentNode) { const rect = el.getBoundingClientRect(); const result = {}; ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => { if (rect[k] !== undefined) { result[k] = rect[k]; } }); if (!result.height) { result.height = parseFloat(computedStyle(el, 'height')); } if (!result.width) { result.width = parseFloat(computedStyle(el, 'width')); } return result; } } /** * Represents the position of a DOM element on the page. * * @typedef {Object} module:dom~Position * * @property {number} left * Pixels to the left. * * @property {number} top * Pixels from the top. */ /** * Get the position of an element in the DOM. * * Uses `getBoundingClientRect` technique from John Resig. * * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ * * @param {Element} el * Element from which to get offset. * * @return {module:dom~Position} * The position of the element that was passed in. */ function findPosition(el) { if (!el || el && !el.offsetParent) { return { left: 0, top: 0, width: 0, height: 0 }; } const width = el.offsetWidth; const height = el.offsetHeight; let left = 0; let top = 0; while (el.offsetParent && el !== (document_default())[FullscreenApi.fullscreenElement]) { left += el.offsetLeft; top += el.offsetTop; el = el.offsetParent; } return { left, top, width, height }; } /** * Represents x and y coordinates for a DOM element or mouse pointer. * * @typedef {Object} module:dom~Coordinates * * @property {number} x * x coordinate in pixels * * @property {number} y * y coordinate in pixels */ /** * Get the pointer position within an element. * * The base on the coordinates are the bottom left of the element. * * @param {Element} el * Element on which to get the pointer position on. * * @param {Event} event * Event object. * * @return {module:dom~Coordinates} * A coordinates object corresponding to the mouse position. * */ function getPointerPosition(el, event) { const translated = { x: 0, y: 0 }; if (IS_IOS) { let item = el; while (item && item.nodeName.toLowerCase() !== 'html') { const transform = computedStyle(item, 'transform'); if (/^matrix/.test(transform)) { const values = transform.slice(7, -1).split(/,\s/).map(Number); translated.x += values[4]; translated.y += values[5]; } else if (/^matrix3d/.test(transform)) { const values = transform.slice(9, -1).split(/,\s/).map(Number); translated.x += values[12]; translated.y += values[13]; } if (item.assignedSlot && item.assignedSlot.parentElement && (window_default()).WebKitCSSMatrix) { const transformValue = window_default().getComputedStyle(item.assignedSlot.parentElement).transform; const matrix = new (window_default()).WebKitCSSMatrix(transformValue); translated.x += matrix.m41; translated.y += matrix.m42; } item = item.parentNode || item.host; } } const position = {}; const boxTarget = findPosition(event.target); const box = findPosition(el); const boxW = box.width; const boxH = box.height; let offsetY = event.offsetY - (box.top - boxTarget.top); let offsetX = event.offsetX - (box.left - boxTarget.left); if (event.changedTouches) { offsetX = event.changedTouches[0].pageX - box.left; offsetY = event.changedTouches[0].pageY + box.top; if (IS_IOS) { offsetX -= translated.x; offsetY -= translated.y; } } position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH)); position.x = Math.max(0, Math.min(1, offsetX / boxW)); return position; } /** * Determines, via duck typing, whether or not a value is a text node. * * @param {*} value * Check if this value is a text node. * * @return {boolean} * Will be `true` if the value is a text node, `false` otherwise. */ function isTextNode(value) { return video_es_isObject(value) && value.nodeType === 3; } /** * Empties the contents of an element. * * @param {Element} el * The element to empty children from * * @return {Element} * The element with no children */ function emptyEl(el) { while (el.firstChild) { el.removeChild(el.firstChild); } return el; } /** * This is a mixed value that describes content to be injected into the DOM * via some method. It can be of the following types: * * Type | Description * -----------|------------- * `string` | The value will be normalized into a text node. * `Element` | The value will be accepted as-is. * `Text` | A TextNode. The value will be accepted as-is. * `Array` | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored). * `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes. * * @typedef {string|Element|Text|Array|Function} ContentDescriptor */ /** * Normalizes content for eventual insertion into the DOM. * * This allows a wide range of content definition methods, but helps protect * from falling into the trap of simply writing to `innerHTML`, which could * be an XSS concern. * * The content for an element can be passed in multiple types and * combinations, whose behavior is as follows: * * @param {ContentDescriptor} content * A content descriptor value. * * @return {Array} * All of the content that was passed in, normalized to an array of * elements or text nodes. */ function normalizeContent(content) { // First, invoke content if it is a function. If it produces an array, // that needs to happen before normalization. if (typeof content === 'function') { content = content(); } // Next up, normalize to an array, so one or many items can be normalized, // filtered, and returned. return (Array.isArray(content) ? content : [content]).map(value => { // First, invoke value if it is a function to produce a new value, // which will be subsequently normalized to a Node of some kind. if (typeof value === 'function') { value = value(); } if (isEl(value) || isTextNode(value)) { return value; } if (typeof value === 'string' && /\S/.test(value)) { return document_default().createTextNode(value); } }).filter(value => value); } /** * Normalizes and appends content to an element. * * @param {Element} el * Element to append normalized content to. * * @param {ContentDescriptor} content * A content descriptor value. * * @return {Element} * The element with appended normalized content. */ function appendContent(el, content) { normalizeContent(content).forEach(node => el.appendChild(node)); return el; } /** * Normalizes and inserts content into an element; this is identical to * `appendContent()`, except it empties the element first. * * @param {Element} el * Element to insert normalized content into. * * @param {ContentDescriptor} content * A content descriptor value. * * @return {Element} * The element with inserted normalized content. */ function insertContent(el, content) { return appendContent(emptyEl(el), content); } /** * Check if an event was a single left click. * * @param {MouseEvent} event * Event object. * * @return {boolean} * Will be `true` if a single left click, `false` otherwise. */ function isSingleLeftClick(event) { // Note: if you create something draggable, be sure to // call it on both `mousedown` and `mousemove` event, // otherwise `mousedown` should be enough for a button if (event.button === undefined && event.buttons === undefined) { // Why do we need `buttons` ? // Because, middle mouse sometimes have this: // e.button === 0 and e.buttons === 4 // Furthermore, we want to prevent combination click, something like // HOLD middlemouse then left click, that would be // e.button === 0, e.buttons === 5 // just `button` is not gonna work // Alright, then what this block does ? // this is for chrome `simulate mobile devices` // I want to support this as well return true; } if (event.button === 0 && event.buttons === undefined) { // Touch screen, sometimes on some specific device, `buttons` // doesn't have anything (safari on ios, blackberry...) return true; } // `mouseup` event on a single left click has // `button` and `buttons` equal to 0 if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) { return true; } // MacOS Sonoma trackpad when "tap to click enabled" if (event.type === 'mousedown' && event.button === 0 && event.buttons === 0) { return true; } if (event.button !== 0 || event.buttons !== 1) { // This is the reason we have those if else block above // if any special case we can catch and let it slide // we do it above, when get to here, this definitely // is-not-left-click return false; } return true; } /** * Finds a single DOM element matching `selector` within the optional * `context` of another DOM element (defaulting to `document`). * * @param {string} selector * A valid CSS selector, which will be passed to `querySelector`. * * @param {Element|String} [context=document] * A DOM element within which to query. Can also be a selector * string in which case the first matching element will be used * as context. If missing (or no element matches selector), falls * back to `document`. * * @return {Element|null} * The element that was found or null. */ const $ = createQuerier('querySelector'); /** * Finds a all DOM elements matching `selector` within the optional * `context` of another DOM element (defaulting to `document`). * * @param {string} selector * A valid CSS selector, which will be passed to `querySelectorAll`. * * @param {Element|String} [context=document] * A DOM element within which to query. Can also be a selector * string in which case the first matching element will be used * as context. If missing (or no element matches selector), falls * back to `document`. * * @return {NodeList} * A element list of elements that were found. Will be empty if none * were found. * */ const $$ = createQuerier('querySelectorAll'); /** * A safe getComputedStyle. * * This is needed because in Firefox, if the player is loaded in an iframe with * `display:none`, then `getComputedStyle` returns `null`, so, we do a * null-check to make sure that the player doesn't break in these cases. * * @param {Element} el * The element you want the computed style of * * @param {string} prop * The property name you want * * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 */ function computedStyle(el, prop) { if (!el || !prop) { return ''; } if (typeof (window_default()).getComputedStyle === 'function') { let computedStyleValue; try { computedStyleValue = window_default().getComputedStyle(el); } catch (e) { return ''; } return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : ''; } return ''; } /** * Copy document style sheets to another window. * * @param {Window} win * The window element you want to copy the document style sheets to. * */ function copyStyleSheetsToWindow(win) { [...(document_default()).styleSheets].forEach(styleSheet => { try { const cssRules = [...styleSheet.cssRules].map(rule => rule.cssText).join(''); const style = document_default().createElement('style'); style.textContent = cssRules; win.document.head.appendChild(style); } catch (e) { const link = document_default().createElement('link'); link.rel = 'stylesheet'; link.type = styleSheet.type; // For older Safari this has to be the string; on other browsers setting the MediaList works link.media = styleSheet.media.mediaText; link.href = styleSheet.href; win.document.head.appendChild(link); } }); } var Dom = /*#__PURE__*/Object.freeze({ __proto__: null, isReal: isReal, isEl: isEl, isInFrame: isInFrame, createEl: createEl, textContent: textContent, prependTo: prependTo, hasClass: hasClass, addClass: addClass, removeClass: removeClass, toggleClass: toggleClass, setAttributes: setAttributes, getAttributes: getAttributes, getAttribute: getAttribute, setAttribute: setAttribute, removeAttribute: removeAttribute, blockTextSelection: blockTextSelection, unblockTextSelection: unblockTextSelection, getBoundingClientRect: getBoundingClientRect, findPosition: findPosition, getPointerPosition: getPointerPosition, isTextNode: isTextNode, emptyEl: emptyEl, normalizeContent: normalizeContent, appendContent: appendContent, insertContent: insertContent, isSingleLeftClick: isSingleLeftClick, $: $, $$: $$, computedStyle: computedStyle, copyStyleSheetsToWindow: copyStyleSheetsToWindow }); /** * @file setup.js - Functions for setting up a player without * user interaction based on the data-setup `attribute` of the video tag. * * @module setup */ let _windowLoaded = false; let videojs$1; /** * Set up any tags that have a data-setup `attribute` when the player is started. */ const autoSetup = function () { if (videojs$1.options.autoSetup === false) { return; } const vids = Array.prototype.slice.call(document_default().getElementsByTagName('video')); const audios = Array.prototype.slice.call(document_default().getElementsByTagName('audio')); const divs = Array.prototype.slice.call(document_default().getElementsByTagName('video-js')); const mediaEls = vids.concat(audios, divs); // Check if any media elements exist if (mediaEls && mediaEls.length > 0) { for (let i = 0, e = mediaEls.length; i < e; i++) { const mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. if (mediaEl && mediaEl.getAttribute) { // Make sure this player hasn't already been set up. if (mediaEl.player === undefined) { const options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. // We only auto-setup if they've added the data-setup attr. if (options !== null) { // Create new video.js instance. videojs$1(mediaEl); } } // If getAttribute isn't defined, we need to wait for the DOM. } else { autoSetupTimeout(1); break; } } // No videos were found, so keep looping unless page is finished loading. } else if (!_windowLoaded) { autoSetupTimeout(1); } }; /** * Wait until the page is loaded before running autoSetup. This will be called in * autoSetup if `hasLoaded` returns false. * * @param {number} wait * How long to wait in ms * * @param {module:videojs} [vjs] * The videojs library function */ function autoSetupTimeout(wait, vjs) { // Protect against breakage in non-browser environments if (!isReal()) { return; } if (vjs) { videojs$1 = vjs; } window_default().setTimeout(autoSetup, wait); } /** * Used to set the internal tracking of window loaded state to true. * * @private */ function setWindowLoaded() { _windowLoaded = true; window_default().removeEventListener('load', setWindowLoaded); } if (isReal()) { if ((document_default()).readyState === 'complete') { setWindowLoaded(); } else { /** * Listen for the load event on window, and set _windowLoaded to true. * * We use a standard event listener here to avoid incrementing the GUID * before any players are created. * * @listens load */ window_default().addEventListener('load', setWindowLoaded); } } /** * @file stylesheet.js * @module stylesheet */ /** * Create a DOM style element given a className for it. * * @param {string} className * The className to add to the created style element. * * @return {Element} * The element that was created. */ const createStyleElement = function (className) { const style = document_default().createElement('style'); style.className = className; return style; }; /** * Add text to a DOM element. * * @param {Element} el * The Element to add text content to. * * @param {string} content * The text to add to the element. */ const setTextContent = function (el, content) { if (el.styleSheet) { el.styleSheet.cssText = content; } else { el.textContent = content; } }; /** * @file dom-data.js * @module dom-data */ /** * Element Data Store. * * Allows for binding data to an element without putting it directly on the * element. Ex. Event listeners are stored here. * (also from jsninja.com, slightly modified and updated for closure compiler) * * @type {Object} * @private */ var DomData = new WeakMap(); /** * @file guid.js * @module guid */ // Default value for GUIDs. This allows us to reset the GUID counter in tests. // // The initial GUID is 3 because some users have come to rely on the first // default player ID ending up as `vjs_video_3`. // // See: https://github.com/videojs/video.js/pull/6216 const _initialGuid = 3; /** * Unique ID for an element or function * * @type {Number} */ let _guid = _initialGuid; /** * Get a unique auto-incrementing ID by number that has not been returned before. * * @return {number} * A new unique ID. */ function newGUID() { return _guid++; } /** * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) * This should work very similarly to jQuery's events, however it's based off the book version which isn't as * robust as jquery's, so there's probably some differences. * * @file events.js * @module events */ /** * Clean up the listener cache and dispatchers * * @param {Element|Object} elem * Element to clean up * * @param {string} type * Type of event to clean up */ function _cleanUpEvents(elem, type) { if (!DomData.has(elem)) { return; } const data = DomData.get(elem); // Remove the events of a particular type if there are none left if (data.handlers[type].length === 0) { delete data.handlers[type]; // data.handlers[type] = null; // Setting to null was causing an error with data.handlers // Remove the meta-handler from the element if (elem.removeEventListener) { elem.removeEventListener(type, data.dispatcher, false); } else if (elem.detachEvent) { elem.detachEvent('on' + type, data.dispatcher); } } // Remove the events object if there are no types left if (Object.getOwnPropertyNames(data.handlers).length <= 0) { delete data.handlers; delete data.dispatcher; delete data.disabled; } // Finally remove the element data if there is no data left if (Object.getOwnPropertyNames(data).length === 0) { DomData.delete(elem); } } /** * Loops through an array of event types and calls the requested method for each type. * * @param {Function} fn * The event method we want to use. * * @param {Element|Object} elem * Element or object to bind listeners to * * @param {string[]} types * Type of event to bind to. * * @param {Function} callback * Event listener. */ function _handleMultipleEvents(fn, elem, types, callback) { types.forEach(function (type) { // Call the event method for each one of the types fn(elem, type, callback); }); } /** * Fix a native event to have standard property values * * @param {Object} event * Event object to fix. * * @return {Object} * Fixed event object. */ function fixEvent(event) { if (event.fixed_) { return event; } function returnTrue() { return true; } function returnFalse() { return false; } // Test if fixing up is needed // Used to check if !event.stopPropagation instead of isPropagationStopped // But native events return true for stopPropagation, but don't have // other expected methods like isPropagationStopped. Seems to be a problem // with the Javascript Ninja code. So we're just overriding all events now. if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) { const old = event || (window_default()).event; event = {}; // Clone the old object so that we can modify the values event = {}; // IE8 Doesn't like when you mess with native event properties // Firefox returns false for event.hasOwnProperty('type') and other props // which makes copying more difficult. // TODO: Probably best to create an allowlist of event props const deprecatedProps = ['layerX', 'layerY', 'keyLocation', 'path', 'webkitMovementX', 'webkitMovementY', 'mozPressure', 'mozInputSource']; for (const key in old) { // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation // and webkitMovementX/Y // Lighthouse complains if Event.path is copied if (!deprecatedProps.includes(key)) { // Chrome 32+ warns if you try to copy deprecated returnValue, but // we still want to if preventDefault isn't supported (IE8). if (!(key === 'returnValue' && old.preventDefault)) { event[key] = old[key]; } } } // The event occurred on this element if (!event.target) { event.target = event.srcElement || (document_default()); } // Handle which other element the event is related to if (!event.relatedTarget) { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } // Stop the default browser action event.preventDefault = function () { if (old.preventDefault) { old.preventDefault(); } event.returnValue = false; old.returnValue = false; event.defaultPrevented = true; }; event.defaultPrevented = false; // Stop the event from bubbling event.stopPropagation = function () { if (old.stopPropagation) { old.stopPropagation(); } event.cancelBubble = true; old.cancelBubble = true; event.isPropagationStopped = returnTrue; }; event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers event.stopImmediatePropagation = function () { if (old.stopImmediatePropagation) { old.stopImmediatePropagation(); } event.isImmediatePropagationStopped = returnTrue; event.stopPropagation(); }; event.isImmediatePropagationStopped = returnFalse; // Handle mouse position if (event.clientX !== null && event.clientX !== undefined) { const doc = (document_default()).documentElement; const body = (document_default()).body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } // Handle key presses event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: // 0 == left; 1 == middle; 2 == right if (event.button !== null && event.button !== undefined) { // The following is disabled because it does not pass videojs-standard // and... yikes. /* eslint-disable */ event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; /* eslint-enable */ } } event.fixed_ = true; // Returns fixed-up instance return event; } /** * Whether passive event listeners are supported */ let _supportsPassive; const supportsPassive = function () { if (typeof _supportsPassive !== 'boolean') { _supportsPassive = false; try { const opts = Object.defineProperty({}, 'passive', { get() { _supportsPassive = true; } }); window_default().addEventListener('test', null, opts); window_default().removeEventListener('test', null, opts); } catch (e) { // disregard } } return _supportsPassive; }; /** * Touch events Chrome expects to be passive */ const passiveEvents = ['touchstart', 'touchmove']; /** * Add an event listener to element * It stores the handler function in a separate cache object * and adds a generic handler to the element's event, * along with a unique id (guid) to the element. * * @param {Element|Object} elem * Element or object to bind listeners to * * @param {string|string[]} type * Type of event to bind to. * * @param {Function} fn * Event listener. */ function on(elem, type, fn) { if (Array.isArray(type)) { return _handleMultipleEvents(on, elem, type, fn); } if (!DomData.has(elem)) { DomData.set(elem, {}); } const data = DomData.get(elem); // We need a place to store all our handler data if (!data.handlers) { data.handlers = {}; } if (!data.handlers[type]) { data.handlers[type] = []; } if (!fn.guid) { fn.guid = newGUID(); } data.handlers[type].push(fn); if (!data.dispatcher) { data.disabled = false; data.dispatcher = function (event, hash) { if (data.disabled) { return; } event = fixEvent(event); const handlers = data.handlers[event.type]; if (handlers) { // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. const handlersCopy = handlers.slice(0); for (let m = 0, n = handlersCopy.length; m < n; m++) { if (event.isImmediatePropagationStopped()) { break; } else { try { handlersCopy[m].call(elem, event, hash); } catch (e) { log$1.error(e); } } } } }; } if (data.handlers[type].length === 1) { if (elem.addEventListener) { let options = false; if (supportsPassive() && passiveEvents.indexOf(type) > -1) { options = { passive: true }; } elem.addEventListener(type, data.dispatcher, options); } else if (elem.attachEvent) { elem.attachEvent('on' + type, data.dispatcher); } } } /** * Removes event listeners from an element * * @param {Element|Object} elem * Object to remove listeners from. * * @param {string|string[]} [type] * Type of listener to remove. Don't include to remove all events from element. * * @param {Function} [fn] * Specific listener to remove. Don't include to remove listeners for an event * type. */ function off(elem, type, fn) { // Don't want to add a cache object through getElData if not needed if (!DomData.has(elem)) { return; } const data = DomData.get(elem); // If no events exist, nothing to unbind if (!data.handlers) { return; } if (Array.isArray(type)) { return _handleMultipleEvents(off, elem, type, fn); } // Utility function const removeType = function (el, t) { data.handlers[t] = []; _cleanUpEvents(el, t); }; // Are we removing all bound events? if (type === undefined) { for (const t in data.handlers) { if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { removeType(elem, t); } } return; } const handlers = data.handlers[type]; // If no handlers exist, nothing to unbind if (!handlers) { return; } // If no listener was provided, remove all listeners for type if (!fn) { removeType(elem, type); return; } // We're only removing a single handler if (fn.guid) { for (let n = 0; n < handlers.length; n++) { if (handlers[n].guid === fn.guid) { handlers.splice(n--, 1); } } } _cleanUpEvents(elem, type); } /** * Trigger an event for an element * * @param {Element|Object} elem * Element to trigger an event on * * @param {EventTarget~Event|string} event * A string (the type) or an event object with a type attribute * * @param {Object} [hash] * data hash to pass along with the event * * @return {boolean|undefined} * Returns the opposite of `defaultPrevented` if default was * prevented. Otherwise, returns `undefined` */ function trigger(elem, event, hash) { // Fetches element data and a reference to the parent (for bubbling). // Don't want to add a data object to cache for every parent, // so checking hasElData first. const elemData = DomData.has(elem) ? DomData.get(elem) : {}; const parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, // handler; // If an event name was passed as a string, creates an event out of it if (typeof event === 'string') { event = { type: event, target: elem }; } else if (!event.target) { event.target = elem; } // Normalizes the event properties. event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. if (elemData.dispatcher) { elemData.dispatcher.call(elem, event, hash); } // Unless explicitly stopped or the event does not bubble (e.g. media events) // recursively calls this function to bubble the event up the DOM. if (parent && !event.isPropagationStopped() && event.bubbles === true) { trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled. } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) { if (!DomData.has(event.target)) { DomData.set(event.target, {}); } const targetData = DomData.get(event.target); // Checks if the target has a default action for this event. if (event.target[event.type]) { // Temporarily disables event dispatching on the target as we have already executed the handler. targetData.disabled = true; // Executes the default action. if (typeof event.target[event.type] === 'function') { event.target[event.type](); } // Re-enables event dispatching. targetData.disabled = false; } } // Inform the triggerer if the default was prevented by returning false return !event.defaultPrevented; } /** * Trigger a listener only once for an event. * * @param {Element|Object} elem * Element or object to bind to. * * @param {string|string[]} type * Name/type of event * * @param {Event~EventListener} fn * Event listener function */ function one(elem, type, fn) { if (Array.isArray(type)) { return _handleMultipleEvents(one, elem, type, fn); } const func = function () { off(elem, type, func); fn.apply(this, arguments); }; // copy the guid to the new function so it can removed using the original function's ID func.guid = fn.guid = fn.guid || newGUID(); on(elem, type, func); } /** * Trigger a listener only once and then turn if off for all * configured events * * @param {Element|Object} elem * Element or object to bind to. * * @param {string|string[]} type * Name/type of event * * @param {Event~EventListener} fn * Event listener function */ function any(elem, type, fn) { const func = function () { off(elem, type, func); fn.apply(this, arguments); }; // copy the guid to the new function so it can removed using the original function's ID func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything on(elem, type, func); } var Events = /*#__PURE__*/Object.freeze({ __proto__: null, fixEvent: fixEvent, on: on, off: off, trigger: trigger, one: one, any: any }); /** * @file fn.js * @module fn */ const UPDATE_REFRESH_INTERVAL = 30; /** * A private, internal-only function for changing the context of a function. * * It also stores a unique id on the function so it can be easily removed from * events. * * @private * @function * @param {*} context * The object to bind as scope. * * @param {Function} fn * The function to be bound to a scope. * * @param {number} [uid] * An optional unique ID for the function to be set * * @return {Function} * The new function that will be bound into the context given */ const bind_ = function (context, fn, uid) { // Make sure the function has a unique ID if (!fn.guid) { fn.guid = newGUID(); } // Create the new function that changes the context const bound = fn.bind(context); // Allow for the ability to individualize this function // Needed in the case where multiple objects might share the same prototype // IF both items add an event listener with the same function, then you try to remove just one // it will remove both because they both have the same guid. // when using this, you need to use the bind method when you remove the listener as well. // currently used in text tracks bound.guid = uid ? uid + '_' + fn.guid : fn.guid; return bound; }; /** * Wraps the given function, `fn`, with a new function that only invokes `fn` * at most once per every `wait` milliseconds. * * @function * @param {Function} fn * The function to be throttled. * * @param {number} wait * The number of milliseconds by which to throttle. * * @return {Function} */ const throttle = function (fn, wait) { let last = window_default().performance.now(); const throttled = function (...args) { const now = window_default().performance.now(); if (now - last >= wait) { fn(...args); last = now; } }; return throttled; }; /** * Creates a debounced function that delays invoking `func` until after `wait` * milliseconds have elapsed since the last time the debounced function was * invoked. * * Inspired by lodash and underscore implementations. * * @function * @param {Function} func * The function to wrap with debounce behavior. * * @param {number} wait * The number of milliseconds to wait after the last invocation. * * @param {boolean} [immediate] * Whether or not to invoke the function immediately upon creation. * * @param {Object} [context=window] * The "context" in which the debounced function should debounce. For * example, if this function should be tied to a Video.js player, * the player can be passed here. Alternatively, defaults to the * global `window` object. * * @return {Function} * A debounced function. */ const debounce$1 = function (func, wait, immediate, context = (window_default())) { let timeout; const cancel = () => { context.clearTimeout(timeout); timeout = null; }; /* eslint-disable consistent-this */ const debounced = function () { const self = this; const args = arguments; let later = function () { timeout = null; later = null; if (!immediate) { func.apply(self, args); } }; if (!timeout && immediate) { func.apply(self, args); } context.clearTimeout(timeout); timeout = context.setTimeout(later, wait); }; /* eslint-enable consistent-this */ debounced.cancel = cancel; return debounced; }; var Fn = /*#__PURE__*/Object.freeze({ __proto__: null, UPDATE_REFRESH_INTERVAL: UPDATE_REFRESH_INTERVAL, bind_: bind_, throttle: throttle, debounce: debounce$1 }); /** * @file src/js/event-target.js */ let EVENT_MAP; /** * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It * adds shorthand functions that wrap around lengthy functions. For example: * the `on` function is a wrapper around `addEventListener`. * * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} * @class EventTarget */ class EventTarget$2 { /** * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a * function that will get called when an event with a certain name gets triggered. * * @param {string|string[]} type * An event name or an array of event names. * * @param {Function} fn * The function to call with `EventTarget`s */ on(type, fn) { // Remove the addEventListener alias before calling Events.on // so we don't get into an infinite type loop const ael = this.addEventListener; this.addEventListener = () => {}; on(this, type, fn); this.addEventListener = ael; } /** * Removes an `event listener` for a specific event from an instance of `EventTarget`. * This makes it so that the `event listener` will no longer get called when the * named event happens. * * @param {string|string[]} type * An event name or an array of event names. * * @param {Function} fn * The function to remove. */ off(type, fn) { off(this, type, fn); } /** * This function will add an `event listener` that gets triggered only once. After the * first trigger it will get removed. This is like adding an `event listener` * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. * * @param {string|string[]} type * An event name or an array of event names. * * @param {Function} fn * The function to be called once for each event name. */ one(type, fn) { // Remove the addEventListener aliasing Events.on // so we don't get into an infinite type loop const ael = this.addEventListener; this.addEventListener = () => {}; one(this, type, fn); this.addEventListener = ael; } /** * This function will add an `event listener` that gets triggered only once and is * removed from all events. This is like adding an array of `event listener`s * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the * first time it is triggered. * * @param {string|string[]} type * An event name or an array of event names. * * @param {Function} fn * The function to be called once for each event name. */ any(type, fn) { // Remove the addEventListener aliasing Events.on // so we don't get into an infinite type loop const ael = this.addEventListener; this.addEventListener = () => {}; any(this, type, fn); this.addEventListener = ael; } /** * This function causes an event to happen. This will then cause any `event listeners` * that are waiting for that event, to get called. If there are no `event listeners` * for an event then nothing will happen. * * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. * Trigger will also call the `on` + `uppercaseEventName` function. * * Example: * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call * `onClick` if it exists. * * @param {string|EventTarget~Event|Object} event * The name of the event, an `Event`, or an object with a key of type set to * an event name. */ trigger(event) { const type = event.type || event; // deprecation // In a future version we should default target to `this` // similar to how we default the target to `elem` in // `Events.trigger`. Right now the default `target` will be // `document` due to the `Event.fixEvent` call. if (typeof event === 'string') { event = { type }; } event = fixEvent(event); if (this.allowedEvents_[type] && this['on' + type]) { this['on' + type](event); } trigger(this, event); } queueTrigger(event) { // only set up EVENT_MAP if it'll be used if (!EVENT_MAP) { EVENT_MAP = new Map(); } const type = event.type || event; let map = EVENT_MAP.get(this); if (!map) { map = new Map(); EVENT_MAP.set(this, map); } const oldTimeout = map.get(type); map.delete(type); window_default().clearTimeout(oldTimeout); const timeout = window_default().setTimeout(() => { map.delete(type); // if we cleared out all timeouts for the current target, delete its map if (map.size === 0) { map = null; EVENT_MAP.delete(this); } this.trigger(event); }, 0); map.set(type, timeout); } } /** * A Custom DOM event. * * @typedef {CustomEvent} Event * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} */ /** * All event listeners should follow the following format. * * @callback EventListener * @this {EventTarget} * * @param {Event} event * the event that triggered this function * * @param {Object} [hash] * hash of data sent during the event */ /** * An object containing event names as keys and booleans as values. * * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} * will have extra functionality. See that function for more information. * * @property EventTarget.prototype.allowedEvents_ * @protected */ EventTarget$2.prototype.allowedEvents_ = {}; /** * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic * the standard DOM API. * * @function * @see {@link EventTarget#on} */ EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on; /** * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic * the standard DOM API. * * @function * @see {@link EventTarget#off} */ EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off; /** * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic * the standard DOM API. * * @function * @see {@link EventTarget#trigger} */ EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger; /** * @file mixins/evented.js * @module evented */ const objName = obj => { if (typeof obj.name === 'function') { return obj.name(); } if (typeof obj.name === 'string') { return obj.name; } if (obj.name_) { return obj.name_; } if (obj.constructor && obj.constructor.name) { return obj.constructor.name; } return typeof obj; }; /** * Returns whether or not an object has had the evented mixin applied. * * @param {Object} object * An object to test. * * @return {boolean} * Whether or not the object appears to be evented. */ const isEvented = object => object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(k => typeof object[k] === 'function'); /** * Adds a callback to run after the evented mixin applied. * * @param {Object} target * An object to Add * @param {Function} callback * The callback to run. */ const addEventedCallback = (target, callback) => { if (isEvented(target)) { callback(); } else { if (!target.eventedCallbacks) { target.eventedCallbacks = []; } target.eventedCallbacks.push(callback); } }; /** * Whether a value is a valid event type - non-empty string or array. * * @private * @param {string|Array} type * The type value to test. * * @return {boolean} * Whether or not the type is a valid event type. */ const isValidEventType = type => // The regex here verifies that the `type` contains at least one non- // whitespace character. typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length; /** * Validates a value to determine if it is a valid event target. Throws if not. * * @private * @throws {Error} * If the target does not appear to be a valid event target. * * @param {Object} target * The object to test. * * @param {Object} obj * The evented object we are validating for * * @param {string} fnName * The name of the evented mixin function that called this. */ const validateTarget = (target, obj, fnName) => { if (!target || !target.nodeName && !isEvented(target)) { throw new Error(`Invalid target for ${objName(obj)}#${fnName}; must be a DOM node or evented object.`); } }; /** * Validates a value to determine if it is a valid event target. Throws if not. * * @private * @throws {Error} * If the type does not appear to be a valid event type. * * @param {string|Array} type * The type to test. * * @param {Object} obj * The evented object we are validating for * * @param {string} fnName * The name of the evented mixin function that called this. */ const validateEventType = (type, obj, fnName) => { if (!isValidEventType(type)) { throw new Error(`Invalid event type for ${objName(obj)}#${fnName}; must be a non-empty string or array.`); } }; /** * Validates a value to determine if it is a valid listener. Throws if not. * * @private * @throws {Error} * If the listener is not a function. * * @param {Function} listener * The listener to test. * * @param {Object} obj * The evented object we are validating for * * @param {string} fnName * The name of the evented mixin function that called this. */ const validateListener = (listener, obj, fnName) => { if (typeof listener !== 'function') { throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`); } }; /** * Takes an array of arguments given to `on()` or `one()`, validates them, and * normalizes them into an object. * * @private * @param {Object} self * The evented object on which `on()` or `one()` was called. This * object will be bound as the `this` value for the listener. * * @param {Array} args * An array of arguments passed to `on()` or `one()`. * * @param {string} fnName * The name of the evented mixin function that called this. * * @return {Object} * An object containing useful values for `on()` or `one()` calls. */ const normalizeListenArgs = (self, args, fnName) => { // If the number of arguments is less than 3, the target is always the // evented object itself. const isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; let target; let type; let listener; if (isTargetingSelf) { target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to // the evented object itself. if (args.length >= 3) { args.shift(); } [type, listener] = args; } else { // This was `[target, type, listener] = args;` but this block needs more than // one statement to produce minified output compatible with Chrome 53. // See https://github.com/videojs/video.js/pull/8810 target = args[0]; type = args[1]; listener = args[2]; } validateTarget(target, self, fnName); validateEventType(type, self, fnName); validateListener(listener, self, fnName); listener = bind_(self, listener); return { isTargetingSelf, target, type, listener }; }; /** * Adds the listener to the event type(s) on the target, normalizing for * the type of target. * * @private * @param {Element|Object} target * A DOM node or evented object. * * @param {string} method * The event binding method to use ("on" or "one"). * * @param {string|Array} type * One or more event type(s). * * @param {Function} listener * A listener function. */ const listen = (target, method, type, listener) => { validateTarget(target, target, method); if (target.nodeName) { Events[method](target, type, listener); } else { target[method](type, listener); } }; /** * Contains methods that provide event capabilities to an object which is passed * to {@link module:evented|evented}. * * @mixin EventedMixin */ const EventedMixin = { /** * Add a listener to an event (or events) on this object or another evented * object. * * @param {string|Array|Element|Object} targetOrType * If this is a string or array, it represents the event type(s) * that will trigger the listener. * * Another evented object can be passed here instead, which will * cause the listener to listen for events on _that_ object. * * In either case, the listener's `this` value will be bound to * this object. * * @param {string|Array|Function} typeOrListener * If the first argument was a string or array, this should be the * listener function. Otherwise, this is a string or array of event * type(s). * * @param {Function} [listener] * If the first argument was another evented object, this will be * the listener function. */ on(...args) { const { isTargetingSelf, target, type, listener } = normalizeListenArgs(this, args, 'on'); listen(target, 'on', type, listener); // If this object is listening to another evented object. if (!isTargetingSelf) { // If this object is disposed, remove the listener. const removeListenerOnDispose = () => this.off(target, type, listener); // Use the same function ID as the listener so we can remove it later it // using the ID of the original listener. removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures // that if the target is disposed BEFORE this object, we remove the // removal listener that was just added. Otherwise, we create a memory leak. const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose); // Use the same function ID as the listener so we can remove it later // it using the ID of the original listener. removeRemoverOnTargetDispose.guid = listener.guid; listen(this, 'on', 'dispose', removeListenerOnDispose); listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); } }, /** * Add a listener to an event (or events) on this object or another evented * object. The listener will be called once per event and then removed. * * @param {string|Array|Element|Object} targetOrType * If this is a string or array, it represents the event type(s) * that will trigger the listener. * * Another evented object can be passed here instead, which will * cause the listener to listen for events on _that_ object. * * In either case, the listener's `this` value will be bound to * this object. * * @param {string|Array|Function} typeOrListener * If the first argument was a string or array, this should be the * listener function. Otherwise, this is a string or array of event * type(s). * * @param {Function} [listener] * If the first argument was another evented object, this will be * the listener function. */ one(...args) { const { isTargetingSelf, target, type, listener } = normalizeListenArgs(this, args, 'one'); // Targeting this evented object. if (isTargetingSelf) { listen(target, 'one', type, listener); // Targeting another evented object. } else { // TODO: This wrapper is incorrect! It should only // remove the wrapper for the event type that called it. // Instead all listeners are removed on the first trigger! // see https://github.com/videojs/video.js/issues/5962 const wrapper = (...largs) => { this.off(target, type, wrapper); listener.apply(null, largs); }; // Use the same function ID as the listener so we can remove it later // it using the ID of the original listener. wrapper.guid = listener.guid; listen(target, 'one', type, wrapper); } }, /** * Add a listener to an event (or events) on this object or another evented * object. The listener will only be called once for the first event that is triggered * then removed. * * @param {string|Array|Element|Object} targetOrType * If this is a string or array, it represents the event type(s) * that will trigger the listener. * * Another evented object can be passed here instead, which will * cause the listener to listen for events on _that_ object. * * In either case, the listener's `this` value will be bound to * this object. * * @param {string|Array|Function} typeOrListener * If the first argument was a string or array, this should be the * listener function. Otherwise, this is a string or array of event * type(s). * * @param {Function} [listener] * If the first argument was another evented object, this will be * the listener function. */ any(...args) { const { isTargetingSelf, target, type, listener } = normalizeListenArgs(this, args, 'any'); // Targeting this evented object. if (isTargetingSelf) { listen(target, 'any', type, listener); // Targeting another evented object. } else { const wrapper = (...largs) => { this.off(target, type, wrapper); listener.apply(null, largs); }; // Use the same function ID as the listener so we can remove it later // it using the ID of the original listener. wrapper.guid = listener.guid; listen(target, 'any', type, wrapper); } }, /** * Removes listener(s) from event(s) on an evented object. * * @param {string|Array|Element|Object} [targetOrType] * If this is a string or array, it represents the event type(s). * * Another evented object can be passed here instead, in which case * ALL 3 arguments are _required_. * * @param {string|Array|Function} [typeOrListener] * If the first argument was a string or array, this may be the * listener function. Otherwise, this is a string or array of event * type(s). * * @param {Function} [listener] * If the first argument was another evented object, this will be * the listener function; otherwise, _all_ listeners bound to the * event type(s) will be removed. */ off(targetOrType, typeOrListener, listener) { // Targeting this evented object. if (!targetOrType || isValidEventType(targetOrType)) { off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object. } else { const target = targetOrType; const type = typeOrListener; // Fail fast and in a meaningful way! validateTarget(target, this, 'off'); validateEventType(type, this, 'off'); validateListener(listener, this, 'off'); // Ensure there's at least a guid, even if the function hasn't been used listener = bind_(this, listener); // Remove the dispose listener on this evented object, which was given // the same guid as the event listener in on(). this.off('dispose', listener); if (target.nodeName) { off(target, type, listener); off(target, 'dispose', listener); } else if (isEvented(target)) { target.off(type, listener); target.off('dispose', listener); } } }, /** * Fire an event on this evented object, causing its listeners to be called. * * @param {string|Object} event * An event type or an object with a type property. * * @param {Object} [hash] * An additional object to pass along to listeners. * * @return {boolean} * Whether or not the default behavior was prevented. */ trigger(event, hash) { validateTarget(this.eventBusEl_, this, 'trigger'); const type = event && typeof event !== 'string' ? event.type : event; if (!isValidEventType(type)) { throw new Error(`Invalid event type for ${objName(this)}#trigger; ` + 'must be a non-empty string or object with a type key that has a non-empty value.'); } return trigger(this.eventBusEl_, event, hash); } }; /** * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. * * @param {Object} target * The object to which to add event methods. * * @param {Object} [options={}] * Options for customizing the mixin behavior. * * @param {string} [options.eventBusKey] * By default, adds a `eventBusEl_` DOM element to the target object, * which is used as an event bus. If the target object already has a * DOM element that should be used, pass its key here. * * @return {Object} * The target object. */ function evented(target, options = {}) { const { eventBusKey } = options; // Set or create the eventBusEl_. if (eventBusKey) { if (!target[eventBusKey].nodeName) { throw new Error(`The eventBusKey "${eventBusKey}" does not refer to an element.`); } target.eventBusEl_ = target[eventBusKey]; } else { target.eventBusEl_ = createEl('span', { className: 'vjs-event-bus' }); } Object.assign(target, EventedMixin); if (target.eventedCallbacks) { target.eventedCallbacks.forEach(callback => { callback(); }); } // When any evented object is disposed, it removes all its listeners. target.on('dispose', () => { target.off(); [target, target.el_, target.eventBusEl_].forEach(function (val) { if (val && DomData.has(val)) { DomData.delete(val); } }); window_default().setTimeout(() => { target.eventBusEl_ = null; }, 0); }); return target; } /** * @file mixins/stateful.js * @module stateful */ /** * Contains methods that provide statefulness to an object which is passed * to {@link module:stateful}. * * @mixin StatefulMixin */ const StatefulMixin = { /** * A hash containing arbitrary keys and values representing the state of * the object. * * @type {Object} */ state: {}, /** * Set the state of an object by mutating its * {@link module:stateful~StatefulMixin.state|state} object in place. * * @fires module:stateful~StatefulMixin#statechanged * @param {Object|Function} stateUpdates * A new set of properties to shallow-merge into the plugin state. * Can be a plain object or a function returning a plain object. * * @return {Object|undefined} * An object containing changes that occurred. If no changes * occurred, returns `undefined`. */ setState(stateUpdates) { // Support providing the `stateUpdates` state as a function. if (typeof stateUpdates === 'function') { stateUpdates = stateUpdates(); } let changes; each(stateUpdates, (value, key) => { // Record the change if the value is different from what's in the // current state. if (this.state[key] !== value) { changes = changes || {}; changes[key] = { from: this.state[key], to: value }; } this.state[key] = value; }); // Only trigger "statechange" if there were changes AND we have a trigger // function. This allows us to not require that the target object be an // evented object. if (changes && isEvented(this)) { /** * An event triggered on an object that is both * {@link module:stateful|stateful} and {@link module:evented|evented} * indicating that its state has changed. * * @event module:stateful~StatefulMixin#statechanged * @type {Object} * @property {Object} changes * A hash containing the properties that were changed and * the values they were changed `from` and `to`. */ this.trigger({ changes, type: 'statechanged' }); } return changes; } }; /** * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target * object. * * If the target object is {@link module:evented|evented} and has a * `handleStateChanged` method, that method will be automatically bound to the * `statechanged` event on itself. * * @param {Object} target * The object to be made stateful. * * @param {Object} [defaultState] * A default set of properties to populate the newly-stateful object's * `state` property. * * @return {Object} * Returns the `target`. */ function stateful(target, defaultState) { Object.assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state` // added in that step. target.state = Object.assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists. if (typeof target.handleStateChanged === 'function' && isEvented(target)) { target.on('statechanged', target.handleStateChanged); } return target; } /** * @file str.js * @module to-lower-case */ /** * Lowercase the first letter of a string. * * @param {string} string * String to be lowercased * * @return {string} * The string with a lowercased first letter */ const toLowerCase = function (string) { if (typeof string !== 'string') { return string; } return string.replace(/./, w => w.toLowerCase()); }; /** * Uppercase the first letter of a string. * * @param {string} string * String to be uppercased * * @return {string} * The string with an uppercased first letter */ const toTitleCase$1 = function (string) { if (typeof string !== 'string') { return string; } return string.replace(/./, w => w.toUpperCase()); }; /** * Compares the TitleCase versions of the two strings for equality. * * @param {string} str1 * The first string to compare * * @param {string} str2 * The second string to compare * * @return {boolean} * Whether the TitleCase versions of the strings are equal */ const titleCaseEquals = function (str1, str2) { return toTitleCase$1(str1) === toTitleCase$1(str2); }; var Str = /*#__PURE__*/Object.freeze({ __proto__: null, toLowerCase: toLowerCase, toTitleCase: toTitleCase$1, titleCaseEquals: titleCaseEquals }); /** * Player Component - Base class for all UI objects * * @file component.js */ /** @import Player from './player' */ /** * A callback to be called if and when the component is ready. * `this` will be the Component instance. * * @callback ReadyCallback * @returns {void} */ /** * Base class for all UI Components. * Components are UI objects which represent both a javascript object and an element * in the DOM. They can be children of other components, and can have * children themselves. * * Components can also use methods from {@link EventTarget} */ class Component$1 { /** * Creates an instance of this class. * * @param {Player} player * The `Player` that this class should be attached to. * * @param {Object} [options] * The key/value store of component options. * * @param {Object[]} [options.children] * An array of children objects to initialize this component with. Children objects have * a name property that will be used if more than one component of the same type needs to be * added. * * @param {string} [options.className] * A class or space separated list of classes to add the component * * @param {ReadyCallback} [ready] * Function that gets called when the `Component` is ready. */ constructor(player, options, ready) { // The component might be the player itself and we can't pass `this` to super if (!player && this.play) { this.player_ = player = this; // eslint-disable-line } else { this.player_ = player; } this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults this.options_ = merge$1({}, this.options_); // Updated options with supplied options options = this.options_ = merge$1(this.options_, options); // Get ID from options or options element if one is supplied this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one if (!this.id_) { // Don't require the player ID function in the case of mock players const id = player && player.id && player.id() || 'no_player'; this.id_ = `${id}_component_${newGUID()}`; } this.name_ = options.name || null; // Create element if one wasn't provided in options if (options.el) { this.el_ = options.el; } else if (options.createEl !== false) { this.el_ = this.createEl(); } if (options.className && this.el_) { options.className.split(' ').forEach(c => this.addClass(c)); } // Remove the placeholder event methods. If the component is evented, the // real methods are added next ['on', 'off', 'one', 'any', 'trigger'].forEach(fn => { this[fn] = undefined; }); // if evented is anything except false, we want to mixin in evented if (options.evented !== false) { // Make this an evented object and use `el_`, if available, as its event bus evented(this, { eventBusKey: this.el_ ? 'el_' : null }); this.handleLanguagechange = this.handleLanguagechange.bind(this); this.on(this.player_, 'languagechange', this.handleLanguagechange); } stateful(this, this.constructor.defaultState); this.children_ = []; this.childIndex_ = {}; this.childNameIndex_ = {}; this.setTimeoutIds_ = new Set(); this.setIntervalIds_ = new Set(); this.rafIds_ = new Set(); this.namedRafs_ = new Map(); this.clearingTimersOnDispose_ = false; // Add any child components in options if (options.initChildren !== false) { this.initChildren(); } // Don't want to trigger ready here or it will go before init is actually // finished for all children that run this constructor this.ready(ready); if (options.reportTouchActivity !== false) { this.enableTouchActivity(); } } // `on`, `off`, `one`, `any` and `trigger` are here so tsc includes them in definitions. // They are replaced or removed in the constructor /** * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a * function that will get called when an event with a certain name gets triggered. * * @param {string|string[]} type * An event name or an array of event names. * * @param {Function} fn * The function to call with `EventTarget`s */ /** * Removes an `event listener` for a specific event from an instance of `EventTarget`. * This makes it so that the `event listener` will no longer get called when the * named event happens. * * @param {string|string[]} type * An event name or an array of event names. * * @param {Function} [fn] * The function to remove. If not specified, all listeners managed by Video.js will be removed. */ /** * This function will add an `event listener` that gets triggered only once. After the * first trigger it will get removed. This is like adding an `event listener` * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. * * @param {string|string[]} type * An event name or an array of event names. * * @param {Function} fn * The function to be called once for each event name. */ /** * This function will add an `event listener` that gets triggered only once and is * removed from all events. This is like adding an array of `event listener`s * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the * first time it is triggered. * * @param {string|string[]} type * An event name or an array of event names. * * @param {Function} fn * The function to be called once for each event name. */ /** * This function causes an event to happen. This will then cause any `event listeners` * that are waiting for that event, to get called. If there are no `event listeners` * for an event then nothing will happen. * * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. * Trigger will also call the `on` + `uppercaseEventName` function. * * Example: * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call * `onClick` if it exists. * * @param {string|Event|Object} event * The name of the event, an `Event`, or an object with a key of type set to * an event name. * * @param {Object} [hash] * Optionally extra argument to pass through to an event listener */ /** * Dispose of the `Component` and all child components. * * @fires Component#dispose * * @param {Object} options * @param {Element} options.originalEl element with which to replace player element */ dispose(options = {}) { // Bail out if the component has already been disposed. if (this.isDisposed_) { return; } if (this.readyQueue_) { this.readyQueue_.length = 0; } /** * Triggered when a `Component` is disposed. * * @event Component#dispose * @type {Event} * * @property {boolean} [bubbles=false] * set to false so that the dispose event does not * bubble up */ this.trigger({ type: 'dispose', bubbles: false }); this.isDisposed_ = true; // Dispose all children. if (this.children_) { for (let i = this.children_.length - 1; i >= 0; i--) { if (this.children_[i].dispose) { this.children_[i].dispose(); } } } // Delete child references this.children_ = null; this.childIndex_ = null; this.childNameIndex_ = null; this.parentComponent_ = null; if (this.el_) { // Remove element from DOM if (this.el_.parentNode) { if (options.restoreEl) { this.el_.parentNode.replaceChild(options.restoreEl, this.el_); } else { this.el_.parentNode.removeChild(this.el_); } } this.el_ = null; } // remove reference to the player after disposing of the element this.player_ = null; } /** * Determine whether or not this component has been disposed. * * @return {boolean} * If the component has been disposed, will be `true`. Otherwise, `false`. */ isDisposed() { return Boolean(this.isDisposed_); } /** * Return the {@link Player} that the `Component` has attached to. * * @return {Player} * The player that this `Component` has attached to. */ player() { return this.player_; } /** * Deep merge of options objects with new options. * > Note: When both `obj` and `options` contain properties whose values are objects. * The two properties get merged using {@link module:obj.merge} * * @param {Object} obj * The object that contains new options. * * @return {Object} * A new object of `this.options_` and `obj` merged together. */ options(obj) { if (!obj) { return this.options_; } this.options_ = merge$1(this.options_, obj); return this.options_; } /** * Get the `Component`s DOM element * * @return {Element} * The DOM element for this `Component`. */ el() { return this.el_; } /** * Create the `Component`s DOM element. * * @param {string} [tagName] * Element's DOM node type. e.g. 'div' * * @param {Object} [properties] * An object of properties that should be set. * * @param {Object} [attributes] * An object of attributes that should be set. * * @return {Element} * The element that gets created. */ createEl(tagName, properties, attributes) { return createEl(tagName, properties, attributes); } /** * Localize a string given the string in english. * * If tokens are provided, it'll try and run a simple token replacement on the provided string. * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. * * If a `defaultValue` is provided, it'll use that over `string`, * if a value isn't found in provided language files. * This is useful if you want to have a descriptive key for token replacement * but have a succinct localized string and not require `en.json` to be included. * * Currently, it is used for the progress bar timing. * ```js * { * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" * } * ``` * It is then used like so: * ```js * this.localize('progress bar timing: currentTime={1} duration{2}', * [this.player_.currentTime(), this.player_.duration()], * '{1} of {2}'); * ``` * * Which outputs something like: `01:23 of 24:56`. * * * @param {string} string * The string to localize and the key to lookup in the language files. * @param {string[]} [tokens] * If the current item has token replacements, provide the tokens here. * @param {string} [defaultValue] * Defaults to `string`. Can be a default value to use for token replacement * if the lookup key is needed to be separate. * * @return {string} * The localized string or if no localization exists the english string. */ localize(string, tokens, defaultValue = string) { const code = this.player_.language && this.player_.language(); const languages = this.player_.languages && this.player_.languages(); const language = languages && languages[code]; const primaryCode = code && code.split('-')[0]; const primaryLang = languages && languages[primaryCode]; let localizedString = defaultValue; if (language && language[string]) { localizedString = language[string]; } else if (primaryLang && primaryLang[string]) { localizedString = primaryLang[string]; } if (tokens) { localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { const value = tokens[index - 1]; let ret = value; if (typeof value === 'undefined') { ret = match; } return ret; }); } return localizedString; } /** * Handles language change for the player in components. Should be overridden by sub-components. * * @abstract */ handleLanguagechange() {} /** * Return the `Component`s DOM element. This is where children get inserted. * This will usually be the the same as the element returned in {@link Component#el}. * * @return {Element} * The content element for this `Component`. */ contentEl() { return this.contentEl_ || this.el_; } /** * Get this `Component`s ID * * @return {string} * The id of this `Component` */ id() { return this.id_; } /** * Get the `Component`s name. The name gets used to reference the `Component` * and is set during registration. * * @return {string} * The name of this `Component`. */ name() { return this.name_; } /** * Get an array of all child components * * @return {Array} * The children */ children() { return this.children_; } /** * Returns the child `Component` with the given `id`. * * @param {string} id * The id of the child `Component` to get. * * @return {Component|undefined} * The child `Component` with the given `id` or undefined. */ getChildById(id) { return this.childIndex_[id]; } /** * Returns the child `Component` with the given `name`. * * @param {string} name * The name of the child `Component` to get. * * @return {Component|undefined} * The child `Component` with the given `name` or undefined. */ getChild(name) { if (!name) { return; } return this.childNameIndex_[name]; } /** * Returns the descendant `Component` following the givent * descendant `names`. For instance ['foo', 'bar', 'baz'] would * try to get 'foo' on the current component, 'bar' on the 'foo' * component and 'baz' on the 'bar' component and return undefined * if any of those don't exist. * * @param {...string[]|...string} names * The name of the child `Component` to get. * * @return {Component|undefined} * The descendant `Component` following the given descendant * `names` or undefined. */ getDescendant(...names) { // flatten array argument into the main array names = names.reduce((acc, n) => acc.concat(n), []); let currentChild = this; for (let i = 0; i < names.length; i++) { currentChild = currentChild.getChild(names[i]); if (!currentChild || !currentChild.getChild) { return; } } return currentChild; } /** * Adds an SVG icon element to another element or component. * * @param {string} iconName * The name of icon. A list of all the icon names can be found at 'sandbox/svg-icons.html' * * @param {Element} [el=this.el()] * Element to set the title on. Defaults to the current Component's element. * * @return {Element} * The newly created icon element. */ setIcon(iconName, el = this.el()) { // TODO: In v9 of video.js, we will want to remove font icons entirely. // This means this check, as well as the others throughout the code, and // the unecessary CSS for font icons, will need to be removed. // See https://github.com/videojs/video.js/pull/8260 as to which components // need updating. if (!this.player_.options_.experimentalSvgIcons) { return; } const xmlnsURL = 'http://www.w3.org/2000/svg'; // The below creates an element in the format of: // .... const iconContainer = createEl('span', { className: 'vjs-icon-placeholder vjs-svg-icon' }, { 'aria-hidden': 'true' }); const svgEl = document_default().createElementNS(xmlnsURL, 'svg'); svgEl.setAttributeNS(null, 'viewBox', '0 0 512 512'); const useEl = document_default().createElementNS(xmlnsURL, 'use'); svgEl.appendChild(useEl); useEl.setAttributeNS(null, 'href', `#vjs-icon-${iconName}`); iconContainer.appendChild(svgEl); // Replace a pre-existing icon if one exists. if (this.iconIsSet_) { el.replaceChild(iconContainer, el.querySelector('.vjs-icon-placeholder')); } else { el.appendChild(iconContainer); } this.iconIsSet_ = true; return iconContainer; } /** * Add a child `Component` inside the current `Component`. * * @param {string|Component} child * The name or instance of a child to add. * * @param {Object} [options={}] * The key/value store of options that will get passed to children of * the child. * * @param {number} [index=this.children_.length] * The index to attempt to add a child into. * * * @return {Component} * The `Component` that gets added as a child. When using a string the * `Component` will get created by this process. */ addChild(child, options = {}, index = this.children_.length) { let component; let componentName; // If child is a string, create component with options if (typeof child === 'string') { componentName = toTitleCase$1(child); const componentClassName = options.componentClass || componentName; // Set name through options options.name = componentName; // Create a new object & element for this controls set // If there's no .player_, this is a player const ComponentClass = Component$1.getComponent(componentClassName); if (!ComponentClass) { throw new Error(`Component ${componentClassName} does not exist`); } // data stored directly on the videojs object may be // misidentified as a component to retain // backwards-compatibility with 4.x. check to make sure the // component class can be instantiated. if (typeof ComponentClass !== 'function') { return null; } component = new ComponentClass(this.player_ || this, options); // child is a component instance } else { component = child; } if (component.parentComponent_) { component.parentComponent_.removeChild(component); } this.children_.splice(index, 0, component); component.parentComponent_ = this; if (typeof component.id === 'function') { this.childIndex_[component.id()] = component; } // If a name wasn't used to create the component, check if we can use the // name function of the component componentName = componentName || component.name && toTitleCase$1(component.name()); if (componentName) { this.childNameIndex_[componentName] = component; this.childNameIndex_[toLowerCase(componentName)] = component; } // Add the UI object's element to the container div (box) // Having an element is not required if (typeof component.el === 'function' && component.el()) { // If inserting before a component, insert before that component's element let refNode = null; if (this.children_[index + 1]) { // Most children are components, but the video tech is an HTML element if (this.children_[index + 1].el_) { refNode = this.children_[index + 1].el_; } else if (isEl(this.children_[index + 1])) { refNode = this.children_[index + 1]; } } this.contentEl().insertBefore(component.el(), refNode); } // Return so it can stored on parent object if desired. return component; } /** * Remove a child `Component` from this `Component`s list of children. Also removes * the child `Component`s element from this `Component`s element. * * @param {string|Component} component * The name or instance of a child to remove. */ removeChild(component) { if (typeof component === 'string') { component = this.getChild(component); } if (!component || !this.children_) { return; } let childFound = false; for (let i = this.children_.length - 1; i >= 0; i--) { if (this.children_[i] === component) { childFound = true; this.children_.splice(i, 1); break; } } if (!childFound) { return; } component.parentComponent_ = null; this.childIndex_[component.id()] = null; this.childNameIndex_[toTitleCase$1(component.name())] = null; this.childNameIndex_[toLowerCase(component.name())] = null; const compEl = component.el(); if (compEl && compEl.parentNode === this.contentEl()) { this.contentEl().removeChild(component.el()); } } /** * Add and initialize default child `Component`s based upon options. */ initChildren() { const children = this.options_.children; if (children) { // `this` is `parent` const parentOptions = this.options_; const handleAdd = child => { const name = child.name; let opts = child.opts; // Allow options for children to be set at the parent options // e.g. videojs(id, { controlBar: false }); // instead of videojs(id, { children: { controlBar: false }); if (parentOptions[name] !== undefined) { opts = parentOptions[name]; } // Allow for disabling default components // e.g. options['children']['posterImage'] = false if (opts === false) { return; } // Allow options to be passed as a simple boolean if no configuration // is necessary. if (opts === true) { opts = {}; } // We also want to pass the original player options // to each component as well so they don't need to // reach back into the player for options later. opts.playerOptions = this.options_.playerOptions; // Create and add the child component. // Add a direct reference to the child by name on the parent instance. // If two of the same component are used, different names should be supplied // for each const newChild = this.addChild(name, opts); if (newChild) { this[name] = newChild; } }; // Allow for an array of children details to passed in the options let workingChildren; const Tech = Component$1.getComponent('Tech'); if (Array.isArray(children)) { workingChildren = children; } else { workingChildren = Object.keys(children); } workingChildren // children that are in this.options_ but also in workingChildren would // give us extra children we do not want. So, we want to filter them out. .concat(Object.keys(this.options_).filter(function (child) { return !workingChildren.some(function (wchild) { if (typeof wchild === 'string') { return child === wchild; } return child === wchild.name; }); })).map(child => { let name; let opts; if (typeof child === 'string') { name = child; opts = children[name] || this.options_[name] || {}; } else { name = child.name; opts = child; } return { name, opts }; }).filter(child => { // we have to make sure that child.name isn't in the techOrder since // techs are registered as Components but can't aren't compatible // See https://github.com/videojs/video.js/issues/2772 const c = Component$1.getComponent(child.opts.componentClass || toTitleCase$1(child.name)); return c && !Tech.isTech(c); }).forEach(handleAdd); } } /** * Builds the default DOM class name. Should be overridden by sub-components. * * @return {string} * The DOM class name for this object. * * @abstract */ buildCSSClass() { // Child classes can include a function that does: // return 'CLASS NAME' + this._super(); return ''; } /** * Bind a listener to the component's ready state. * Different from event listeners in that if the ready event has already happened * it will trigger the function immediately. * * @param {ReadyCallback} fn * Function that gets called when the `Component` is ready. */ ready(fn, sync = false) { if (!fn) { return; } if (!this.isReady_) { this.readyQueue_ = this.readyQueue_ || []; this.readyQueue_.push(fn); return; } if (sync) { fn.call(this); } else { // Call the function asynchronously by default for consistency this.setTimeout(fn, 1); } } /** * Trigger all the ready listeners for this `Component`. * * @fires Component#ready */ triggerReady() { this.isReady_ = true; // Ensure ready is triggered asynchronously this.setTimeout(function () { const readyQueue = this.readyQueue_; // Reset Ready Queue this.readyQueue_ = []; if (readyQueue && readyQueue.length > 0) { readyQueue.forEach(function (fn) { fn.call(this); }, this); } // Allow for using event listeners also /** * Triggered when a `Component` is ready. * * @event Component#ready * @type {Event} */ this.trigger('ready'); }, 1); } /** * Find a single DOM element matching a `selector`. This can be within the `Component`s * `contentEl()` or another custom context. * * @param {string} selector * A valid CSS selector, which will be passed to `querySelector`. * * @param {Element|string} [context=this.contentEl()] * A DOM element within which to query. Can also be a selector string in * which case the first matching element will get used as context. If * missing `this.contentEl()` gets used. If `this.contentEl()` returns * nothing it falls back to `document`. * * @return {Element|null} * the dom element that was found, or null * * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) */ $(selector, context) { return $(selector, context || this.contentEl()); } /** * Finds all DOM element matching a `selector`. This can be within the `Component`s * `contentEl()` or another custom context. * * @param {string} selector * A valid CSS selector, which will be passed to `querySelectorAll`. * * @param {Element|string} [context=this.contentEl()] * A DOM element within which to query. Can also be a selector string in * which case the first matching element will get used as context. If * missing `this.contentEl()` gets used. If `this.contentEl()` returns * nothing it falls back to `document`. * * @return {NodeList} * a list of dom elements that were found * * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) */ $$(selector, context) { return $$(selector, context || this.contentEl()); } /** * Check if a component's element has a CSS class name. * * @param {string} classToCheck * CSS class name to check. * * @return {boolean} * - True if the `Component` has the class. * - False if the `Component` does not have the class` */ hasClass(classToCheck) { return hasClass(this.el_, classToCheck); } /** * Add a CSS class name to the `Component`s element. * * @param {...string} classesToAdd * One or more CSS class name to add. */ addClass(...classesToAdd) { addClass(this.el_, ...classesToAdd); } /** * Remove a CSS class name from the `Component`s element. * * @param {...string} classesToRemove * One or more CSS class name to remove. */ removeClass(...classesToRemove) { removeClass(this.el_, ...classesToRemove); } /** * Add or remove a CSS class name from the component's element. * - `classToToggle` gets added when {@link Component#hasClass} would return false. * - `classToToggle` gets removed when {@link Component#hasClass} would return true. * * @param {string} classToToggle * The class to add or remove. Passed to DOMTokenList's toggle() * * @param {boolean|Dom.PredicateCallback} [predicate] * A boolean or function that returns a boolean. Passed to DOMTokenList's toggle(). */ toggleClass(classToToggle, predicate) { toggleClass(this.el_, classToToggle, predicate); } /** * Show the `Component`s element if it is hidden by removing the * 'vjs-hidden' class name from it. */ show() { this.removeClass('vjs-hidden'); } /** * Hide the `Component`s element if it is currently showing by adding the * 'vjs-hidden` class name to it. */ hide() { this.addClass('vjs-hidden'); } /** * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' * class name to it. Used during fadeIn/fadeOut. * * @private */ lockShowing() { this.addClass('vjs-lock-showing'); } /** * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' * class name from it. Used during fadeIn/fadeOut. * * @private */ unlockShowing() { this.removeClass('vjs-lock-showing'); } /** * Get the value of an attribute on the `Component`s element. * * @param {string} attribute * Name of the attribute to get the value from. * * @return {string|null} * - The value of the attribute that was asked for. * - Can be an empty string on some browsers if the attribute does not exist * or has no value * - Most browsers will return null if the attribute does not exist or has * no value. * * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} */ getAttribute(attribute) { return getAttribute(this.el_, attribute); } /** * Set the value of an attribute on the `Component`'s element * * @param {string} attribute * Name of the attribute to set. * * @param {string} value * Value to set the attribute to. * * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} */ setAttribute(attribute, value) { setAttribute(this.el_, attribute, value); } /** * Remove an attribute from the `Component`s element. * * @param {string} attribute * Name of the attribute to remove. * * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} */ removeAttribute(attribute) { removeAttribute(this.el_, attribute); } /** * Get or set the width of the component based upon the CSS styles. * See {@link Component#dimension} for more detailed information. * * @param {number|string} [num] * The width that you want to set postfixed with '%', 'px' or nothing. * * @param {boolean} [skipListeners] * Skip the componentresize event trigger * * @return {number|undefined} * The width when getting, zero if there is no width */ width(num, skipListeners) { return this.dimension('width', num, skipListeners); } /** * Get or set the height of the component based upon the CSS styles. * See {@link Component#dimension} for more detailed information. * * @param {number|string} [num] * The height that you want to set postfixed with '%', 'px' or nothing. * * @param {boolean} [skipListeners] * Skip the componentresize event trigger * * @return {number|undefined} * The height when getting, zero if there is no height */ height(num, skipListeners) { return this.dimension('height', num, skipListeners); } /** * Set both the width and height of the `Component` element at the same time. * * @param {number|string} width * Width to set the `Component`s element to. * * @param {number|string} height * Height to set the `Component`s element to. */ dimensions(width, height) { // Skip componentresize listeners on width for optimization this.width(width, true); this.height(height); } /** * Get or set width or height of the `Component` element. This is the shared code * for the {@link Component#width} and {@link Component#height}. * * Things to know: * - If the width or height in an number this will return the number postfixed with 'px'. * - If the width/height is a percent this will return the percent postfixed with '%' * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} * for more information * - If you want the computed style of the component, use {@link Component#currentWidth} * and {@link {Component#currentHeight} * * @fires Component#componentresize * * @param {string} widthOrHeight 8 'width' or 'height' * * @param {number|string} [num] 8 New dimension * * @param {boolean} [skipListeners] * Skip componentresize event trigger * * @return {number|undefined} * The dimension when getting or 0 if unset */ dimension(widthOrHeight, num, skipListeners) { if (num !== undefined) { // Set to zero if null or literally NaN (NaN !== NaN) if (num === null || num !== num) { num = 0; } // Check if using css width/height (% or px) and adjust if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { this.el_.style[widthOrHeight] = num; } else if (num === 'auto') { this.el_.style[widthOrHeight] = ''; } else { this.el_.style[widthOrHeight] = num + 'px'; } // skipListeners allows us to avoid triggering the resize event when setting both width and height if (!skipListeners) { /** * Triggered when a component is resized. * * @event Component#componentresize * @type {Event} */ this.trigger('componentresize'); } return; } // Not setting a value, so getting it // Make sure element exists if (!this.el_) { return 0; } // Get dimension value from style const val = this.el_.style[widthOrHeight]; const pxIndex = val.indexOf('px'); if (pxIndex !== -1) { // Return the pixel value with no 'px' return parseInt(val.slice(0, pxIndex), 10); } // No px so using % or no style was set, so falling back to offsetWidth/height // If component has display:none, offset will return 0 // TODO: handle display:none and no dimension style using px return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10); } /** * Get the computed width or the height of the component's element. * * Uses `window.getComputedStyle`. * * @param {string} widthOrHeight * A string containing 'width' or 'height'. Whichever one you want to get. * * @return {number} * The dimension that gets asked for or 0 if nothing was set * for that dimension. */ currentDimension(widthOrHeight) { let computedWidthOrHeight = 0; if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { throw new Error('currentDimension only accepts width or height value'); } computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying // and we want to check the offset values. // This code also runs wherever getComputedStyle doesn't exist. if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) { const rule = `offset${toTitleCase$1(widthOrHeight)}`; computedWidthOrHeight = this.el_[rule]; } return computedWidthOrHeight; } /** * An object that contains width and height values of the `Component`s * computed style. Uses `window.getComputedStyle`. * * @typedef {Object} Component~DimensionObject * * @property {number} width * The width of the `Component`s computed style. * * @property {number} height * The height of the `Component`s computed style. */ /** * Get an object that contains computed width and height values of the * component's element. * * Uses `window.getComputedStyle`. * * @return {Component~DimensionObject} * The computed dimensions of the component's element. */ currentDimensions() { return { width: this.currentDimension('width'), height: this.currentDimension('height') }; } /** * Get the computed width of the component's element. * * Uses `window.getComputedStyle`. * * @return {number} * The computed width of the component's element. */ currentWidth() { return this.currentDimension('width'); } /** * Get the computed height of the component's element. * * Uses `window.getComputedStyle`. * * @return {number} * The computed height of the component's element. */ currentHeight() { return this.currentDimension('height'); } /** * Retrieves the position and size information of the component's element. * * @return {Object} An object with `boundingClientRect` and `center` properties. * - `boundingClientRect`: An object with properties `x`, `y`, `width`, * `height`, `top`, `right`, `bottom`, and `left`, representing * the bounding rectangle of the element. * - `center`: An object with properties `x` and `y`, representing * the center point of the element. `width` and `height` are set to 0. */ getPositions() { const rect = this.el_.getBoundingClientRect(); // Creating objects that mirror DOMRectReadOnly for boundingClientRect and center const boundingClientRect = { x: rect.x, y: rect.y, width: rect.width, height: rect.height, top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left }; // Calculating the center position const center = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2, width: 0, height: 0, top: rect.top + rect.height / 2, right: rect.left + rect.width / 2, bottom: rect.top + rect.height / 2, left: rect.left + rect.width / 2 }; return { boundingClientRect, center }; } /** * Set the focus to this component */ focus() { this.el_.focus(); } /** * Remove the focus from this component */ blur() { this.el_.blur(); } /** * When this Component receives a `keydown` event which it does not process, * it passes the event to the Player for handling. * * @param {KeyboardEvent} event * The `keydown` event that caused this function to be called. */ handleKeyDown(event) { if (this.player_) { // We only stop propagation here because we want unhandled events to fall // back to the browser. Exclude Tab for focus trapping, exclude also when spatialNavigation is enabled. if (event.key !== 'Tab' && !(this.player_.options_.playerOptions.spatialNavigation && this.player_.options_.playerOptions.spatialNavigation.enabled)) { event.stopPropagation(); } this.player_.handleKeyDown(event); } } /** * Many components used to have a `handleKeyPress` method, which was poorly * named because it listened to a `keydown` event. This method name now * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` * will not see their method calls stop working. * * @param {KeyboardEvent} event * The event that caused this function to be called. */ handleKeyPress(event) { this.handleKeyDown(event); } /** * Emit a 'tap' events when touch event support gets detected. This gets used to * support toggling the controls through a tap on the video. They get enabled * because every sub-component would have extra overhead otherwise. * * @protected * @fires Component#tap * @listens Component#touchstart * @listens Component#touchmove * @listens Component#touchleave * @listens Component#touchcancel * @listens Component#touchend */ emitTapEvents() { // Track the start time so we can determine how long the touch lasted let touchStart = 0; let firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap // Other popular libs use anywhere from 2 (hammer.js) to 15, // so 10 seems like a nice, round number. const tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap const touchTimeThreshold = 200; let couldBeTap; this.on('touchstart', function (event) { // If more than one finger, don't consider treating this as a click if (event.touches.length === 1) { // Copy pageX/pageY from the object firstTouch = { pageX: event.touches[0].pageX, pageY: event.touches[0].pageY }; // Record start time so we can detect a tap vs. "touch and hold" touchStart = window_default().performance.now(); // Reset couldBeTap tracking couldBeTap = true; } }); this.on('touchmove', function (event) { // If more than one finger, don't consider treating this as a click if (event.touches.length > 1) { couldBeTap = false; } else if (firstTouch) { // Some devices will throw touchmoves for all but the slightest of taps. // So, if we moved only a small distance, this could still be a tap const xdiff = event.touches[0].pageX - firstTouch.pageX; const ydiff = event.touches[0].pageY - firstTouch.pageY; const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); if (touchDistance > tapMovementThreshold) { couldBeTap = false; } } }); const noTap = function () { couldBeTap = false; }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s this.on('touchleave', noTap); this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate // event this.on('touchend', function (event) { firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen if (couldBeTap === true) { // Measure how long the touch lasted const touchTime = window_default().performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap if (touchTime < touchTimeThreshold) { // Don't let browser turn this into a click event.preventDefault(); /** * Triggered when a `Component` is tapped. * * @event Component#tap * @type {MouseEvent} */ this.trigger('tap'); // It may be good to copy the touchend event object and change the // type to tap, if the other event properties aren't exact after // Events.fixEvent runs (e.g. event.target) } } }); } /** * This function reports user activity whenever touch events happen. This can get * turned off by any sub-components that wants touch events to act another way. * * Report user touch activity when touch events occur. User activity gets used to * determine when controls should show/hide. It is simple when it comes to mouse * events, because any mouse event should show the controls. So we capture mouse * events that bubble up to the player and report activity when that happens. * With touch events it isn't as easy as `touchstart` and `touchend` toggle player * controls. So touch events can't help us at the player level either. * * User activity gets checked asynchronously. So what could happen is a tap event * on the video turns the controls off. Then the `touchend` event bubbles up to * the player. Which, if it reported user activity, would turn the controls right * back on. We also don't want to completely block touch events from bubbling up. * Furthermore a `touchmove` event and anything other than a tap, should not turn * controls back on. * * @listens Component#touchstart * @listens Component#touchmove * @listens Component#touchend * @listens Component#touchcancel */ enableTouchActivity() { // Don't continue if the root player doesn't support reporting user activity if (!this.player() || !this.player().reportUserActivity) { return; } // listener for reporting that the user is active const report = bind_(this.player(), this.player().reportUserActivity); let touchHolding; this.on('touchstart', function () { report(); // For as long as the they are touching the device or have their mouse down, // we consider them active even if they're not moving their finger or mouse. // So we want to continue to update that they are active this.clearInterval(touchHolding); // report at the same interval as activityCheck touchHolding = this.setInterval(report, 250); }); const touchEnd = function (event) { report(); // stop the interval that maintains activity if the touch is holding this.clearInterval(touchHolding); }; this.on('touchmove', report); this.on('touchend', touchEnd); this.on('touchcancel', touchEnd); } /** * A callback that has no parameters and is bound into `Component`s context. * * @callback Component~GenericCallback * @this Component */ /** * Creates a function that runs after an `x` millisecond timeout. This function is a * wrapper around `window.setTimeout`. There are a few reasons to use this one * instead though: * 1. It gets cleared via {@link Component#clearTimeout} when * {@link Component#dispose} gets called. * 2. The function callback will gets turned into a {@link Component~GenericCallback} * * > Note: You can't use `window.clearTimeout` on the id returned by this function. This * will cause its dispose listener not to get cleaned up! Please use * {@link Component#clearTimeout} or {@link Component#dispose} instead. * * @param {Component~GenericCallback} fn * The function that will be run after `timeout`. * * @param {number} timeout * Timeout in milliseconds to delay before executing the specified function. * * @return {number} * Returns a timeout ID that gets used to identify the timeout. It can also * get used in {@link Component#clearTimeout} to clear the timeout that * was set. * * @listens Component#dispose * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} */ setTimeout(fn, timeout) { // declare as variables so they are properly available in timeout function // eslint-disable-next-line var timeoutId; fn = bind_(this, fn); this.clearTimersOnDispose_(); timeoutId = window_default().setTimeout(() => { if (this.setTimeoutIds_.has(timeoutId)) { this.setTimeoutIds_.delete(timeoutId); } fn(); }, timeout); this.setTimeoutIds_.add(timeoutId); return timeoutId; } /** * Clears a timeout that gets created via `window.setTimeout` or * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} * use this function instead of `window.clearTimout`. If you don't your dispose * listener will not get cleaned up until {@link Component#dispose}! * * @param {number} timeoutId * The id of the timeout to clear. The return value of * {@link Component#setTimeout} or `window.setTimeout`. * * @return {number} * Returns the timeout id that was cleared. * * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} */ clearTimeout(timeoutId) { if (this.setTimeoutIds_.has(timeoutId)) { this.setTimeoutIds_.delete(timeoutId); window_default().clearTimeout(timeoutId); } return timeoutId; } /** * Creates a function that gets run every `x` milliseconds. This function is a wrapper * around `window.setInterval`. There are a few reasons to use this one instead though. * 1. It gets cleared via {@link Component#clearInterval} when * {@link Component#dispose} gets called. * 2. The function callback will be a {@link Component~GenericCallback} * * @param {Component~GenericCallback} fn * The function to run every `x` seconds. * * @param {number} interval * Execute the specified function every `x` milliseconds. * * @return {number} * Returns an id that can be used to identify the interval. It can also be be used in * {@link Component#clearInterval} to clear the interval. * * @listens Component#dispose * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} */ setInterval(fn, interval) { fn = bind_(this, fn); this.clearTimersOnDispose_(); const intervalId = window_default().setInterval(fn, interval); this.setIntervalIds_.add(intervalId); return intervalId; } /** * Clears an interval that gets created via `window.setInterval` or * {@link Component#setInterval}. If you set an interval via {@link Component#setInterval} * use this function instead of `window.clearInterval`. If you don't your dispose * listener will not get cleaned up until {@link Component#dispose}! * * @param {number} intervalId * The id of the interval to clear. The return value of * {@link Component#setInterval} or `window.setInterval`. * * @return {number} * Returns the interval id that was cleared. * * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} */ clearInterval(intervalId) { if (this.setIntervalIds_.has(intervalId)) { this.setIntervalIds_.delete(intervalId); window_default().clearInterval(intervalId); } return intervalId; } /** * Queues up a callback to be passed to requestAnimationFrame (rAF), but * with a few extra bonuses: * * - Supports browsers that do not support rAF by falling back to * {@link Component#setTimeout}. * * - The callback is turned into a {@link Component~GenericCallback} (i.e. * bound to the component). * * - Automatic cancellation of the rAF callback is handled if the component * is disposed before it is called. * * @param {Component~GenericCallback} fn * A function that will be bound to this component and executed just * before the browser's next repaint. * * @return {number} * Returns an rAF ID that gets used to identify the timeout. It can * also be used in {@link Component#cancelAnimationFrame} to cancel * the animation frame callback. * * @listens Component#dispose * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} */ requestAnimationFrame(fn) { this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function // eslint-disable-next-line var id; fn = bind_(this, fn); id = window_default().requestAnimationFrame(() => { if (this.rafIds_.has(id)) { this.rafIds_.delete(id); } fn(); }); this.rafIds_.add(id); return id; } /** * Request an animation frame, but only one named animation * frame will be queued. Another will never be added until * the previous one finishes. * * @param {string} name * The name to give this requestAnimationFrame * * @param {Component~GenericCallback} fn * A function that will be bound to this component and executed just * before the browser's next repaint. */ requestNamedAnimationFrame(name, fn) { if (this.namedRafs_.has(name)) { this.cancelNamedAnimationFrame(name); } this.clearTimersOnDispose_(); fn = bind_(this, fn); const id = this.requestAnimationFrame(() => { fn(); if (this.namedRafs_.has(name)) { this.namedRafs_.delete(name); } }); this.namedRafs_.set(name, id); return name; } /** * Cancels a current named animation frame if it exists. * * @param {string} name * The name of the requestAnimationFrame to cancel. */ cancelNamedAnimationFrame(name) { if (!this.namedRafs_.has(name)) { return; } this.cancelAnimationFrame(this.namedRafs_.get(name)); this.namedRafs_.delete(name); } /** * Cancels a queued callback passed to {@link Component#requestAnimationFrame} * (rAF). * * If you queue an rAF callback via {@link Component#requestAnimationFrame}, * use this function instead of `window.cancelAnimationFrame`. If you don't, * your dispose listener will not get cleaned up until {@link Component#dispose}! * * @param {number} id * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. * * @return {number} * Returns the rAF ID that was cleared. * * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} */ cancelAnimationFrame(id) { if (this.rafIds_.has(id)) { this.rafIds_.delete(id); window_default().cancelAnimationFrame(id); } return id; } /** * A function to setup `requestAnimationFrame`, `setTimeout`, * and `setInterval`, clearing on dispose. * * > Previously each timer added and removed dispose listeners on it's own. * For better performance it was decided to batch them all, and use `Set`s * to track outstanding timer ids. * * @private */ clearTimersOnDispose_() { if (this.clearingTimersOnDispose_) { return; } this.clearingTimersOnDispose_ = true; this.one('dispose', () => { [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(([idName, cancelName]) => { // for a `Set` key will actually be the value again // so forEach((val, val) =>` but for maps we want to use // the key. this[idName].forEach((val, key) => this[cancelName](key)); }); this.clearingTimersOnDispose_ = false; }); } /** * Decide whether an element is actually disabled or not. * * @function isActuallyDisabled * @param element {Node} * @return {boolean} * * @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled} */ getIsDisabled() { return Boolean(this.el_.disabled); } /** * Decide whether the element is expressly inert or not. * * @see {@link https://html.spec.whatwg.org/multipage/interaction.html#expressly-inert} * @function isExpresslyInert * @param element {Node} * @return {boolean} */ getIsExpresslyInert() { return this.el_.inert && !this.el_.ownerDocument.documentElement.inert; } /** * Determine whether or not this component can be considered as focusable component. * * @param {HTMLElement} el - The HTML element representing the component. * @return {boolean} * If the component can be focused, will be `true`. Otherwise, `false`. */ getIsFocusable(el) { const element = el || this.el_; return element.tabIndex >= 0 && !(this.getIsDisabled() || this.getIsExpresslyInert()); } /** * Determine whether or not this component is currently visible/enabled/etc... * * @param {HTMLElement} el - The HTML element representing the component. * @return {boolean} * If the component can is currently visible & enabled, will be `true`. Otherwise, `false`. */ getIsAvailableToBeFocused(el) { /** * Decide the style property of this element is specified whether it's visible or not. * * @function isVisibleStyleProperty * @param element {CSSStyleDeclaration} * @return {boolean} */ function isVisibleStyleProperty(element) { const elementStyle = window_default().getComputedStyle(element, null); const thisVisibility = elementStyle.getPropertyValue('visibility'); const thisDisplay = elementStyle.getPropertyValue('display'); const invisibleStyle = ['hidden', 'collapse']; return thisDisplay !== 'none' && !invisibleStyle.includes(thisVisibility); } /** * Decide whether the element is being rendered or not. * 1. If an element has the style as "visibility: hidden | collapse" or "display: none", it is not being rendered. * 2. If an element has the style as "opacity: 0", it is not being rendered.(that is, invisible). * 3. If width and height of an element are explicitly set to 0, it is not being rendered. * 4. If a parent element is hidden, an element itself is not being rendered. * (CSS visibility property and display property are inherited.) * * @see {@link https://html.spec.whatwg.org/multipage/rendering.html#being-rendered} * @function isBeingRendered * @param element {Node} * @return {boolean} */ function isBeingRendered(element) { if (!isVisibleStyleProperty(element.parentElement)) { return false; } if (!isVisibleStyleProperty(element) || element.style.opacity === '0' || window_default().getComputedStyle(element).height === '0px' || window_default().getComputedStyle(element).width === '0px') { return false; } return true; } /** * Determine if the element is visible for the user or not. * 1. If an element sum of its offsetWidth, offsetHeight, height and width is less than 1 is not visible. * 2. If elementCenter.x is less than is not visible. * 3. If elementCenter.x is more than the document's width is not visible. * 4. If elementCenter.y is less than 0 is not visible. * 5. If elementCenter.y is the document's height is not visible. * * @function isVisible * @param element {Node} * @return {boolean} */ function isVisible(element) { if (element.offsetWidth + element.offsetHeight + element.getBoundingClientRect().height + element.getBoundingClientRect().width === 0) { return false; } // Define elementCenter object with props of x and y // x: Left position relative to the viewport plus element's width (no margin) divided between 2. // y: Top position relative to the viewport plus element's height (no margin) divided between 2. const elementCenter = { x: element.getBoundingClientRect().left + element.offsetWidth / 2, y: element.getBoundingClientRect().top + element.offsetHeight / 2 }; if (elementCenter.x < 0) { return false; } if (elementCenter.x > ((document_default()).documentElement.clientWidth || (window_default()).innerWidth)) { return false; } if (elementCenter.y < 0) { return false; } if (elementCenter.y > ((document_default()).documentElement.clientHeight || (window_default()).innerHeight)) { return false; } let pointContainer = document_default().elementFromPoint(elementCenter.x, elementCenter.y); while (pointContainer) { if (pointContainer === element) { return true; } if (pointContainer.parentNode) { pointContainer = pointContainer.parentNode; } else { return false; } } } // If no DOM element was passed as argument use this component's element. if (!el) { el = this.el(); } // If element is visible, is being rendered & either does not have a parent element or its tabIndex is not negative. if (isVisible(el) && isBeingRendered(el) && (!el.parentElement || el.tabIndex >= 0)) { return true; } return false; } /** * Register a `Component` with `videojs` given the name and the component. * * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s * should be registered using {@link Tech.registerTech} or * {@link videojs:videojs.registerTech}. * * > NOTE: This function can also be seen on videojs as * {@link videojs:videojs.registerComponent}. * * @param {string} name * The name of the `Component` to register. * * @param {Component} ComponentToRegister * The `Component` class to register. * * @return {Component} * The `Component` that was registered. */ static registerComponent(name, ComponentToRegister) { if (typeof name !== 'string' || !name) { throw new Error(`Illegal component name, "${name}"; must be a non-empty string.`); } const Tech = Component$1.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered. const isTech = Tech && Tech.isTech(ComponentToRegister); const isComp = Component$1 === ComponentToRegister || Component$1.prototype.isPrototypeOf(ComponentToRegister.prototype); if (isTech || !isComp) { let reason; if (isTech) { reason = 'techs must be registered using Tech.registerTech()'; } else { reason = 'must be a Component subclass'; } throw new Error(`Illegal component, "${name}"; ${reason}.`); } name = toTitleCase$1(name); if (!Component$1.components_) { Component$1.components_ = {}; } const Player = Component$1.getComponent('Player'); if (name === 'Player' && Player && Player.players) { const players = Player.players; const playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be // in Players.players. So, we must loop through and verify that the value // for each item is null. This allows registration of the Player component // after all players have been disposed or before any were created. if (players && playerNames.length > 0) { for (let i = 0; i < playerNames.length; i++) { if (players[playerNames[i]] !== null) { throw new Error('Can not register Player component after player has been created.'); } } } } Component$1.components_[name] = ComponentToRegister; Component$1.components_[toLowerCase(name)] = ComponentToRegister; return ComponentToRegister; } /** * Get a `Component` based on the name it was registered with. * * @param {string} name * The Name of the component to get. * * @return {typeof Component} * The `Component` that got registered under the given name. */ static getComponent(name) { if (!name || !Component$1.components_) { return; } return Component$1.components_[name]; } } Component$1.registerComponent('Component', Component$1); /** * @file time.js * @module time */ /** * Returns the time for the specified index at the start or end * of a TimeRange object. * * @typedef {Function} TimeRangeIndex * * @param {number} [index=0] * The range number to return the time for. * * @return {number} * The time offset at the specified index. * * @deprecated The index argument must be provided. * In the future, leaving it out will throw an error. */ /** * An object that contains ranges of time, which mimics {@link TimeRanges}. * * @typedef {Object} TimeRange * * @property {number} length * The number of time ranges represented by this object. * * @property {module:time~TimeRangeIndex} start * Returns the time offset at which a specified time range begins. * * @property {module:time~TimeRangeIndex} end * Returns the time offset at which a specified time range ends. * * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges */ /** * Check if any of the time ranges are over the maximum index. * * @private * @param {string} fnName * The function name to use for logging * * @param {number} index * The index to check * * @param {number} maxIndex * The maximum possible index * * @throws {Error} if the timeRanges provided are over the maxIndex */ function rangeCheck(fnName, index, maxIndex) { if (typeof index !== 'number' || index < 0 || index > maxIndex) { throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is non-numeric or out of bounds (0-${maxIndex}).`); } } /** * Get the time for the specified index at the start or end * of a TimeRange object. * * @private * @param {string} fnName * The function name to use for logging * * @param {string} valueIndex * The property that should be used to get the time. should be * 'start' or 'end' * * @param {Array} ranges * An array of time ranges * * @param {Array} [rangeIndex=0] * The index to start the search at * * @return {number} * The time that offset at the specified index. * * @deprecated rangeIndex must be set to a value, in the future this will throw an error. * @throws {Error} if rangeIndex is more than the length of ranges */ function getRange(fnName, valueIndex, ranges, rangeIndex) { rangeCheck(fnName, rangeIndex, ranges.length - 1); return ranges[rangeIndex][valueIndex]; } /** * Create a time range object given ranges of time. * * @private * @param {Array} [ranges] * An array of time ranges. * * @return {TimeRange} */ function createTimeRangesObj(ranges) { let timeRangesObj; if (ranges === undefined || ranges.length === 0) { timeRangesObj = { length: 0, start() { throw new Error('This TimeRanges object is empty'); }, end() { throw new Error('This TimeRanges object is empty'); } }; } else { timeRangesObj = { length: ranges.length, start: getRange.bind(null, 'start', 0, ranges), end: getRange.bind(null, 'end', 1, ranges) }; } if ((window_default()).Symbol && (window_default()).Symbol.iterator) { timeRangesObj[(window_default()).Symbol.iterator] = () => (ranges || []).values(); } return timeRangesObj; } /** * Create a `TimeRange` object which mimics an * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. * * @param {number|Array[]} start * The start of a single range (a number) or an array of ranges (an * array of arrays of two numbers each). * * @param {number} end * The end of a single range. Cannot be used with the array form of * the `start` argument. * * @return {TimeRange} */ function createTimeRanges$1(start, end) { if (Array.isArray(start)) { return createTimeRangesObj(start); } else if (start === undefined || end === undefined) { return createTimeRangesObj(); } return createTimeRangesObj([[start, end]]); } /** * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in * seconds) will force a number of leading zeros to cover the length of the * guide. * * @private * @param {number} seconds * Number of seconds to be turned into a string * * @param {number} guide * Number (in seconds) to model the string after * * @return {string} * Time formatted as H:MM:SS or M:SS */ const defaultImplementation = function (seconds, guide) { seconds = seconds < 0 ? 0 : seconds; let s = Math.floor(seconds % 60); let m = Math.floor(seconds / 60 % 60); let h = Math.floor(seconds / 3600); const gm = Math.floor(guide / 60 % 60); const gh = Math.floor(guide / 3600); // handle invalid times if (isNaN(seconds) || seconds === Infinity) { // '-' is false for all relational operators (e.g. <, >=) so this setting // will add the minimum number of fields specified by the guide h = m = s = '-'; } // Check if we need to show hours h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero. // Always show at least one digit of minutes. m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds s = s < 10 ? '0' + s : s; return h + m + s; }; // Internal pointer to the current implementation. let implementation = defaultImplementation; /** * Replaces the default formatTime implementation with a custom implementation. * * @param {Function} customImplementation * A function which will be used in place of the default formatTime * implementation. Will receive the current time in seconds and the * guide (in seconds) as arguments. */ function setFormatTime(customImplementation) { implementation = customImplementation; } /** * Resets formatTime to the default implementation. */ function resetFormatTime() { implementation = defaultImplementation; } /** * Delegates to either the default time formatting function or a custom * function supplied via `setFormatTime`. * * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a * guide (in seconds) will force a number of leading zeros to cover the * length of the guide. * * @example formatTime(125, 600) === "02:05" * @param {number} seconds * Number of seconds to be turned into a string * * @param {number} guide * Number (in seconds) to model the string after * * @return {string} * Time formatted as H:MM:SS or M:SS */ function formatTime(seconds, guide = seconds) { return implementation(seconds, guide); } var Time = /*#__PURE__*/Object.freeze({ __proto__: null, createTimeRanges: createTimeRanges$1, createTimeRange: createTimeRanges$1, setFormatTime: setFormatTime, resetFormatTime: resetFormatTime, formatTime: formatTime }); /** * @file buffer.js * @module buffer */ /** @import { TimeRange } from './time' */ /** * Compute the percentage of the media that has been buffered. * * @param {TimeRange} buffered * The current `TimeRanges` object representing buffered time ranges * * @param {number} duration * Total duration of the media * * @return {number} * Percent buffered of the total duration in decimal form. */ function bufferedPercent(buffered, duration) { let bufferedDuration = 0; let start; let end; if (!duration) { return 0; } if (!buffered || !buffered.length) { buffered = createTimeRanges$1(0, 0); } for (let i = 0; i < buffered.length; i++) { start = buffered.start(i); end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction if (end > duration) { end = duration; } bufferedDuration += end - start; } return bufferedDuration / duration; } /** * @file media-error.js */ /** * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. * * @param {number|string|Object|MediaError} value * This can be of multiple types: * - number: should be a standard error code * - string: an error message (the code will be 0) * - Object: arbitrary properties * - `MediaError` (native): used to populate a video.js `MediaError` object * - `MediaError` (video.js): will return itself if it's already a * video.js `MediaError` object. * * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} * * @class MediaError */ function MediaError(value) { // Allow redundant calls to this constructor to avoid having `instanceof` // checks peppered around the code. if (value instanceof MediaError) { return value; } if (typeof value === 'number') { this.code = value; } else if (typeof value === 'string') { // default code is zero, so this is a custom error this.message = value; } else if (video_es_isObject(value)) { // We assign the `code` property manually because native `MediaError` objects // do not expose it as an own/enumerable property of the object. if (typeof value.code === 'number') { this.code = value.code; } Object.assign(this, value); } if (!this.message) { this.message = MediaError.defaultMessages[this.code] || ''; } } /** * The error code that refers two one of the defined `MediaError` types * * @type {Number} */ MediaError.prototype.code = 0; /** * An optional message that to show with the error. Message is not part of the HTML5 * video spec but allows for more informative custom errors. * * @type {String} */ MediaError.prototype.message = ''; /** * An optional status code that can be set by plugins to allow even more detail about * the error. For example a plugin might provide a specific HTTP status code and an * error message for that code. Then when the plugin gets that error this class will * know how to display an error message for it. This allows a custom message to show * up on the `Player` error overlay. * * @type {Array} */ MediaError.prototype.status = null; /** * An object containing an error type, as well as other information regarding the error. * * @typedef {{errorType: string, [key: string]: any}} ErrorMetadata */ /** * An optional object to give more detail about the error. This can be used to give * a higher level of specificity to an error versus the more generic MediaError codes. * `metadata` expects an `errorType` string that should align with the values from videojs.Error. * * @type {ErrorMetadata} */ MediaError.prototype.metadata = null; /** * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the * specification listed under {@link MediaError} for more information. * * @enum {array} * @readonly * @property {string} 0 - MEDIA_ERR_CUSTOM * @property {string} 1 - MEDIA_ERR_ABORTED * @property {string} 2 - MEDIA_ERR_NETWORK * @property {string} 3 - MEDIA_ERR_DECODE * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED * @property {string} 5 - MEDIA_ERR_ENCRYPTED */ MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; /** * The default `MediaError` messages based on the {@link MediaError.errorTypes}. * * @type {Array} * @constant */ MediaError.defaultMessages = { 1: 'You aborted the media playback', 2: 'A network error caused the media download to fail part-way.', 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 5: 'The media is encrypted and we do not have the keys to decrypt it.' }; /** * W3C error code for any custom error. * * @member MediaError#MEDIA_ERR_CUSTOM * @constant {number} * @default 0 */ MediaError.MEDIA_ERR_CUSTOM = 0; /** * W3C error code for any custom error. * * @member MediaError.MEDIA_ERR_CUSTOM * @constant {number} * @default 0 */ MediaError.prototype.MEDIA_ERR_CUSTOM = 0; /** * W3C error code for media error aborted. * * @member MediaError#MEDIA_ERR_ABORTED * @constant {number} * @default 1 */ MediaError.MEDIA_ERR_ABORTED = 1; /** * W3C error code for media error aborted. * * @member MediaError.MEDIA_ERR_ABORTED * @constant {number} * @default 1 */ MediaError.prototype.MEDIA_ERR_ABORTED = 1; /** * W3C error code for any network error. * * @member MediaError#MEDIA_ERR_NETWORK * @constant {number} * @default 2 */ MediaError.MEDIA_ERR_NETWORK = 2; /** * W3C error code for any network error. * * @member MediaError.MEDIA_ERR_NETWORK * @constant {number} * @default 2 */ MediaError.prototype.MEDIA_ERR_NETWORK = 2; /** * W3C error code for any decoding error. * * @member MediaError#MEDIA_ERR_DECODE * @constant {number} * @default 3 */ MediaError.MEDIA_ERR_DECODE = 3; /** * W3C error code for any decoding error. * * @member MediaError.MEDIA_ERR_DECODE * @constant {number} * @default 3 */ MediaError.prototype.MEDIA_ERR_DECODE = 3; /** * W3C error code for any time that a source is not supported. * * @member MediaError#MEDIA_ERR_SRC_NOT_SUPPORTED * @constant {number} * @default 4 */ MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; /** * W3C error code for any time that a source is not supported. * * @member MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED * @constant {number} * @default 4 */ MediaError.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; /** * W3C error code for any time that a source is encrypted. * * @member MediaError#MEDIA_ERR_ENCRYPTED * @constant {number} * @default 5 */ MediaError.MEDIA_ERR_ENCRYPTED = 5; /** * W3C error code for any time that a source is encrypted. * * @member MediaError.MEDIA_ERR_ENCRYPTED * @constant {number} * @default 5 */ MediaError.prototype.MEDIA_ERR_ENCRYPTED = 5; /** * Returns whether an object is `Promise`-like (i.e. has a `then` method). * * @param {Object} value * An object that may or may not be `Promise`-like. * * @return {boolean} * Whether or not the object is `Promise`-like. */ function isPromise(value) { return value !== undefined && value !== null && typeof value.then === 'function'; } /** * Silence a Promise-like object. * * This is useful for avoiding non-harmful, but potentially confusing "uncaught * play promise" rejection error messages. * * @param {Object} value * An object that may or may not be `Promise`-like. */ function silencePromise(value) { if (isPromise(value)) { value.then(null, e => {}); } } /** * @file text-track-list-converter.js Utilities for capturing text track state and * re-creating tracks based on a capture. * * @module text-track-list-converter */ /** @import Tech from '../tech/tech' */ /** * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that * represents the {@link TextTrack}'s state. * * @param {TextTrack} track * The text track to query. * * @return {Object} * A serializable javascript representation of the TextTrack. * @private */ const trackToJson = function (track) { const ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce((acc, prop, i) => { if (track[prop]) { acc[prop] = track[prop]; } return acc; }, { cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { return { startTime: cue.startTime, endTime: cue.endTime, text: cue.text, id: cue.id }; }) }); return ret; }; /** * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the * state of all {@link TextTrack}s currently configured. The return array is compatible with * {@link text-track-list-converter:jsonToTextTracks}. * * @param {Tech} tech * The tech object to query * * @return {Array} * A serializable javascript representation of the {@link Tech}s * {@link TextTrackList}. */ const textTracksToJson = function (tech) { const trackEls = tech.$$('track'); const trackObjs = Array.prototype.map.call(trackEls, t => t.track); const tracks = Array.prototype.map.call(trackEls, function (trackEl) { const json = trackToJson(trackEl.track); if (trackEl.src) { json.src = trackEl.src; } return json; }); return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { return trackObjs.indexOf(track) === -1; }).map(trackToJson)); }; /** * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript * object {@link TextTrack} representations. * * @param {Array} json * An array of `TextTrack` representation objects, like those that would be * produced by `textTracksToJson`. * * @param {Tech} tech * The `Tech` to create the `TextTrack`s on. */ const jsonToTextTracks = function (json, tech) { json.forEach(function (track) { const addedTrack = tech.addRemoteTextTrack(track).track; if (!track.src && track.cues) { track.cues.forEach(cue => addedTrack.addCue(cue)); } }); return tech.textTracks(); }; var textTrackConverter = { textTracksToJson, jsonToTextTracks, trackToJson }; /** * @file modal-dialog.js */ /** @import Player from './player' */ /** @import { ContentDescriptor } from './utils/dom' */ const MODAL_CLASS_NAME = 'vjs-modal-dialog'; /** * The `ModalDialog` displays over the video and its controls, which blocks * interaction with the player until it is closed. * * Modal dialogs include a "Close" button and will close when that button * is activated - or when ESC is pressed anywhere. * * @extends Component */ class ModalDialog extends Component$1 { /** * Creates an instance of this class. * * @param {Player} player * The `Player` that this class should be attached to. * * @param {Object} [options] * The key/value store of player options. * * @param {ContentDescriptor} [options.content=undefined] * Provide customized content for this modal. * * @param {string} [options.description] * A text description for the modal, primarily for accessibility. * * @param {boolean} [options.fillAlways=false] * Normally, modals are automatically filled only the first time * they open. This tells the modal to refresh its content * every time it opens. * * @param {string} [options.label] * A text label for the modal, primarily for accessibility. * * @param {boolean} [options.pauseOnOpen=true] * If `true`, playback will will be paused if playing when * the modal opens, and resumed when it closes. * * @param {boolean} [options.temporary=true] * If `true`, the modal can only be opened once; it will be * disposed as soon as it's closed. * * @param {boolean} [options.uncloseable=false] * If `true`, the user will not be able to close the modal * through the UI in the normal ways. Programmatic closing is * still possible. */ constructor(player, options) { super(player, options); this.handleKeyDown_ = e => this.handleKeyDown(e); this.close_ = e => this.close(e); this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false; this.closeable(!this.options_.uncloseable); this.content(this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized // because we only want the contents of the modal in the contentEl // (not the UI elements like the close button). this.contentEl_ = createEl('div', { className: `${MODAL_CLASS_NAME}-content` }, { role: 'document' }); this.descEl_ = createEl('p', { className: `${MODAL_CLASS_NAME}-description vjs-control-text`, id: this.el().getAttribute('aria-describedby') }); textContent(this.descEl_, this.description()); this.el_.appendChild(this.descEl_); this.el_.appendChild(this.contentEl_); } /** * Create the `ModalDialog`'s DOM element * * @return {Element} * The DOM element that gets created. */ createEl() { return super.createEl('div', { className: this.buildCSSClass(), tabIndex: -1 }, { 'aria-describedby': `${this.id()}_description`, 'aria-hidden': 'true', 'aria-label': this.label(), 'role': 'dialog', 'aria-live': 'polite' }); } dispose() { this.contentEl_ = null; this.descEl_ = null; this.previouslyActiveEl_ = null; super.dispose(); } /** * Builds the default DOM `className`. * * @return {string} * The DOM `className` for this object. */ buildCSSClass() { return `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`; } /** * Returns the label string for this modal. Primarily used for accessibility. * * @return {string} * the localized or raw label of this modal. */ label() { return this.localize(this.options_.label || 'Modal Window'); } /** * Returns the description string for this modal. Primarily used for * accessibility. * * @return {string} * The localized or raw description of this modal. */ description() { let desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable. if (this.closeable()) { desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); } return desc; } /** * Opens the modal. * * @fires ModalDialog#beforemodalopen * @fires ModalDialog#modalopen */ open() { if (this.opened_) { if (this.options_.fillAlways) { this.fill(); } return; } const player = this.player(); /** * Fired just before a `ModalDialog` is opened. * * @event ModalDialog#beforemodalopen * @type {Event} */ this.trigger('beforemodalopen'); this.opened_ = true; // Fill content if the modal has never opened before and // never been filled. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { this.fill(); } // If the player was playing, pause it and take note of its previously // playing state. this.wasPlaying_ = !player.paused(); if (this.options_.pauseOnOpen && this.wasPlaying_) { player.pause(); } this.on('keydown', this.handleKeyDown_); // Hide controls and note if they were enabled. this.hadControls_ = player.controls(); player.controls(false); this.show(); this.conditionalFocus_(); this.el().setAttribute('aria-hidden', 'false'); /** * Fired just after a `ModalDialog` is opened. * * @event ModalDialog#modalopen * @type {Event} */ this.trigger('modalopen'); this.hasBeenOpened_ = true; } /** * If the `ModalDialog` is currently open or closed. * * @param {boolean} [value] * If given, it will open (`true`) or close (`false`) the modal. * * @return {boolean} * the current open state of the modaldialog */ opened(value) { if (typeof value === 'boolean') { this[value ? 'open' : 'close'](); } return this.opened_; } /** * Closes the modal, does nothing if the `ModalDialog` is * not open. * * @fires ModalDialog#beforemodalclose * @fires ModalDialog#modalclose */ close() { if (!this.opened_) { return; } const player = this.player(); /** * Fired just before a `ModalDialog` is closed. * * @event ModalDialog#beforemodalclose * @type {Event} */ this.trigger('beforemodalclose'); this.opened_ = false; if (this.wasPlaying_ && this.options_.pauseOnOpen) { player.play(); } this.off('keydown', this.handleKeyDown_); if (this.hadControls_) { player.controls(true); } this.hide(); this.el().setAttribute('aria-hidden', 'true'); /** * Fired just after a `ModalDialog` is closed. * * @event ModalDialog#modalclose * @type {Event} * * @property {boolean} [bubbles=true] */ this.trigger({ type: 'modalclose', bubbles: true }); this.conditionalBlur_(); if (this.options_.temporary) { this.dispose(); } } /** * Check to see if the `ModalDialog` is closeable via the UI. * * @param {boolean} [value] * If given as a boolean, it will set the `closeable` option. * * @return {boolean} * Returns the final value of the closable option. */ closeable(value) { if (typeof value === 'boolean') { const closeable = this.closeable_ = !!value; let close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one. if (closeable && !close) { // The close button should be a child of the modal - not its // content element, so temporarily change the content element. const temp = this.contentEl_; this.contentEl_ = this.el_; close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' }); this.contentEl_ = temp; this.on(close, 'close', this.close_); } // If this is being made uncloseable and has a close button, remove it. if (!closeable && close) { this.off(close, 'close', this.close_); this.removeChild(close); close.dispose(); } } return this.closeable_; } /** * Fill the modal's content element with the modal's "content" option. * The content element will be emptied before this change takes place. */ fill() { this.fillWith(this.content()); } /** * Fill the modal's content element with arbitrary content. * The content element will be emptied before this change takes place. * * @fires ModalDialog#beforemodalfill * @fires ModalDialog#modalfill * * @param {ContentDescriptor} [content] * The same rules apply to this as apply to the `content` option. */ fillWith(content) { const contentEl = this.contentEl(); const parentEl = contentEl.parentNode; const nextSiblingEl = contentEl.nextSibling; /** * Fired just before a `ModalDialog` is filled with content. * * @event ModalDialog#beforemodalfill * @type {Event} */ this.trigger('beforemodalfill'); this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing // manipulation to avoid modifying the live DOM multiple times. parentEl.removeChild(contentEl); this.empty(); insertContent(contentEl, content); /** * Fired just after a `ModalDialog` is filled with content. * * @event ModalDialog#modalfill * @type {Event} */ this.trigger('modalfill'); // Re-inject the re-filled content element. if (nextSiblingEl) { parentEl.insertBefore(contentEl, nextSiblingEl); } else { parentEl.appendChild(contentEl); } // make sure that the close button is last in the dialog DOM const closeButton = this.getChild('closeButton'); if (closeButton) { parentEl.appendChild(closeButton.el_); } /** * Fired after `ModalDialog` is re-filled with content & close button is appended. * * @event ModalDialog#aftermodalfill * @type {Event} */ this.trigger('aftermodalfill'); } /** * Empties the content element. This happens anytime the modal is filled. * * @fires ModalDialog#beforemodalempty * @fires ModalDialog#modalempty */ empty() { /** * Fired just before a `ModalDialog` is emptied. * * @event ModalDialog#beforemodalempty * @type {Event} */ this.trigger('beforemodalempty'); emptyEl(this.contentEl()); /** * Fired just after a `ModalDialog` is emptied. * * @event ModalDialog#modalempty * @type {Event} */ this.trigger('modalempty'); } /** * Gets or sets the modal content, which gets normalized before being * rendered into the DOM. * * This does not update the DOM or fill the modal, but it is called during * that process. * * @param {ContentDescriptor} [value] * If defined, sets the internal content value to be used on the * next call(s) to `fill`. This value is normalized before being * inserted. To "clear" the internal content value, pass `null`. * * @return {ContentDescriptor} * The current content of the modal dialog */ content(value) { if (typeof value !== 'undefined') { this.content_ = value; } return this.content_; } /** * conditionally focus the modal dialog if focus was previously on the player. * * @private */ conditionalFocus_() { const activeEl = (document_default()).activeElement; const playerEl = this.player_.el_; this.previouslyActiveEl_ = null; if (playerEl.contains(activeEl) || playerEl === activeEl) { this.previouslyActiveEl_ = activeEl; this.focus(); } } /** * conditionally blur the element and refocus the last focused element * * @private */ conditionalBlur_() { if (this.previouslyActiveEl_) { this.previouslyActiveEl_.focus(); this.previouslyActiveEl_ = null; } } /** * Keydown handler. Attached when modal is focused. * * @listens keydown */ handleKeyDown(event) { /** * Fired a custom keyDown event that bubbles. * * @event ModalDialog#modalKeydown * @type {Event} */ this.trigger({ type: 'modalKeydown', originalEvent: event, target: this, bubbles: true }); // Do not allow keydowns to reach out of the modal dialog. event.stopPropagation(); if (event.key === 'Escape' && this.closeable()) { event.preventDefault(); this.close(); return; } // exit early if it isn't a tab key if (event.key !== 'Tab') { return; } const focusableEls = this.focusableEls_(); const activeEl = this.el_.querySelector(':focus'); let focusIndex; for (let i = 0; i < focusableEls.length; i++) { if (activeEl === focusableEls[i]) { focusIndex = i; break; } } if ((document_default()).activeElement === this.el_) { focusIndex = 0; } if (event.shiftKey && focusIndex === 0) { focusableEls[focusableEls.length - 1].focus(); event.preventDefault(); } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { focusableEls[0].focus(); event.preventDefault(); } } /** * get all focusable elements * * @private */ focusableEls_() { const allChildren = this.el_.querySelectorAll('*'); return Array.prototype.filter.call(allChildren, child => { return (child instanceof (window_default()).HTMLAnchorElement || child instanceof (window_default()).HTMLAreaElement) && child.hasAttribute('href') || (child instanceof (window_default()).HTMLInputElement || child instanceof (window_default()).HTMLSelectElement || child instanceof (window_default()).HTMLTextAreaElement || child instanceof (window_default()).HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof (window_default()).HTMLIFrameElement || child instanceof (window_default()).HTMLObjectElement || child instanceof (window_default()).HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); }); } } /** * Default options for `ModalDialog` default options. * * @type {Object} * @private */ ModalDialog.prototype.options_ = { pauseOnOpen: true, temporary: true }; Component$1.registerComponent('ModalDialog', ModalDialog); /** * @file track-list.js */ /** @import Track from './track' */ /** * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and * {@link VideoTrackList} * * @extends EventTarget */ class TrackList extends EventTarget$2 { /** * Create an instance of this class * * @param { Track[] } tracks * A list of tracks to initialize the list with. * * @abstract */ constructor(tracks = []) { super(); this.tracks_ = []; for (let i = 0; i < tracks.length; i++) { this.addTrack(tracks[i]); } } /** * The current number of `Track`s in this TrackList. * * @type {number} */ get length() { return this.tracks_.length; } /** * Add a {@link Track} to the `TrackList` * * @param {Track} track * The audio, video, or text track to add to the list. * * @fires TrackList#addtrack */ addTrack(track) { const index = this.tracks_.length; if (!('' + index in this)) { Object.defineProperty(this, index, { get() { return this.tracks_[index]; } }); } // Do not add duplicate tracks if (this.tracks_.indexOf(track) === -1) { this.tracks_.push(track); /** * Triggered when a track is added to a track list. * * @event TrackList#addtrack * @type {Event} * @property {Track} track * A reference to track that was added. */ this.trigger({ track, type: 'addtrack', target: this }); } /** * Triggered when a track label is changed. * * @event TrackList#labelchange * @type {Event} * @property {Track} track * A reference to track whose label was changed. */ track.labelchange_ = () => { this.trigger({ track, type: 'labelchange', target: this }); }; if (isEvented(track)) { track.addEventListener('labelchange', track.labelchange_); } } /** * Remove a {@link Track} from the `TrackList` * * @param {Track} rtrack * The audio, video, or text track to remove from the list. * * @fires TrackList#removetrack */ removeTrack(rtrack) { let track; for (let i = 0, l = this.length; i < l; i++) { if (this[i] === rtrack) { track = this[i]; if (track.off) { track.off(); } this.tracks_.splice(i, 1); break; } } if (!track) { return; } /** * Triggered when a track is removed from track list. * * @event TrackList#removetrack * @type {Event} * @property {Track} track * A reference to track that was removed. */ this.trigger({ track, type: 'removetrack', target: this }); } /** * Get a Track from the TrackList by a tracks id * * @param {string} id - the id of the track to get * @method getTrackById * @return {Track} * @private */ getTrackById(id) { let result = null; for (let i = 0, l = this.length; i < l; i++) { const track = this[i]; if (track.id === id) { result = track; break; } } return result; } } /** * Triggered when a different track is selected/enabled. * * @event TrackList#change * @type {Event} */ /** * Events that can be called with on + eventName. See {@link EventHandler}. * * @property {Object} TrackList#allowedEvents_ * @protected */ TrackList.prototype.allowedEvents_ = { change: 'change', addtrack: 'addtrack', removetrack: 'removetrack', labelchange: 'labelchange' }; // emulate attribute EventHandler support to allow for feature detection for (const event in TrackList.prototype.allowedEvents_) { TrackList.prototype['on' + event] = null; } /** * @file audio-track-list.js */ /** @import AudioTrack from './audio-track' */ /** * Anywhere we call this function we diverge from the spec * as we only support one enabled audiotrack at a time * * @param {AudioTrackList} list * list to work on * * @param {AudioTrack} track * The track to skip * * @private */ const disableOthers$1 = function (list, track) { for (let i = 0; i < list.length; i++) { if (!Object.keys(list[i]).length || track.id === list[i].id) { continue; } // another audio track is enabled, disable it list[i].enabled = false; } }; /** * The current list of {@link AudioTrack} for a media file. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} * @extends TrackList */ class AudioTrackList extends TrackList { /** * Create an instance of this class. * * @param {AudioTrack[]} [tracks=[]] * A list of `AudioTrack` to instantiate the list with. */ constructor(tracks = []) { // make sure only 1 track is enabled // sorted from last index to first index for (let i = tracks.length - 1; i >= 0; i--) { if (tracks[i].enabled) { disableOthers$1(tracks, tracks[i]); break; } } super(tracks); this.changing_ = false; } /** * Add an {@link AudioTrack} to the `AudioTrackList`. * * @param {AudioTrack} track * The AudioTrack to add to the list * * @fires TrackList#addtrack */ addTrack(track) { if (track.enabled) { disableOthers$1(this, track); } super.addTrack(track); // native tracks don't have this if (!track.addEventListener) { return; } track.enabledChange_ = () => { // when we are disabling other tracks (since we don't support // more than one track at a time) we will set changing_ // to true so that we don't trigger additional change events if (this.changing_) { return; } this.changing_ = true; disableOthers$1(this, track); this.changing_ = false; this.trigger('change'); }; /** * @listens AudioTrack#enabledchange * @fires TrackList#change */ track.addEventListener('enabledchange', track.enabledChange_); } removeTrack(rtrack) { super.removeTrack(rtrack); if (rtrack.removeEventListener && rtrack.enabledChange_) { rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); rtrack.enabledChange_ = null; } } } /** * @file video-track-list.js */ /** @import VideoTrack from './video-track' */ /** * Un-select all other {@link VideoTrack}s that are selected. * * @param {VideoTrackList} list * list to work on * * @param {VideoTrack} track * The track to skip * * @private */ const disableOthers = function (list, track) { for (let i = 0; i < list.length; i++) { if (!Object.keys(list[i]).length || track.id === list[i].id) { continue; } // another video track is enabled, disable it list[i].selected = false; } }; /** * The current list of {@link VideoTrack} for a video. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} * @extends TrackList */ class VideoTrackList extends TrackList { /** * Create an instance of this class. * * @param {VideoTrack[]} [tracks=[]] * A list of `VideoTrack` to instantiate the list with. */ constructor(tracks = []) { // make sure only 1 track is enabled // sorted from last index to first index for (let i = tracks.length - 1; i >= 0; i--) { if (tracks[i].selected) { disableOthers(tracks, tracks[i]); break; } } super(tracks); this.changing_ = false; /** * @member {number} VideoTrackList#selectedIndex * The current index of the selected {@link VideoTrack`}. */ Object.defineProperty(this, 'selectedIndex', { get() { for (let i = 0; i < this.length; i++) { if (this[i].selected) { return i; } } return -1; }, set() {} }); } /** * Add a {@link VideoTrack} to the `VideoTrackList`. * * @param {VideoTrack} track * The VideoTrack to add to the list * * @fires TrackList#addtrack */ addTrack(track) { if (track.selected) { disableOthers(this, track); } super.addTrack(track); // native tracks don't have this if (!track.addEventListener) { return; } track.selectedChange_ = () => { if (this.changing_) { return; } this.changing_ = true; disableOthers(this, track); this.changing_ = false; this.trigger('change'); }; /** * @listens VideoTrack#selectedchange * @fires TrackList#change */ track.addEventListener('selectedchange', track.selectedChange_); } removeTrack(rtrack) { super.removeTrack(rtrack); if (rtrack.removeEventListener && rtrack.selectedChange_) { rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); rtrack.selectedChange_ = null; } } } /** * @file text-track-list.js */ /** @import TextTrack from './text-track' */ /** * The current list of {@link TextTrack} for a media file. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} * @extends TrackList */ class TextTrackList extends TrackList { /** * Add a {@link TextTrack} to the `TextTrackList` * * @param {TextTrack} track * The text track to add to the list. * * @fires TrackList#addtrack */ addTrack(track) { super.addTrack(track); if (!this.queueChange_) { this.queueChange_ = () => this.queueTrigger('change'); } if (!this.triggerSelectedlanguagechange) { this.triggerSelectedlanguagechange_ = () => this.trigger('selectedlanguagechange'); } /** * @listens TextTrack#modechange * @fires TrackList#change */ track.addEventListener('modechange', this.queueChange_); const nonLanguageTextTrackKind = ['metadata', 'chapters']; if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { track.addEventListener('modechange', this.triggerSelectedlanguagechange_); } } removeTrack(rtrack) { super.removeTrack(rtrack); // manually remove the event handlers we added if (rtrack.removeEventListener) { if (this.queueChange_) { rtrack.removeEventListener('modechange', this.queueChange_); } if (this.selectedlanguagechange_) { rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); } } } /** * Creates a serializable array of objects that contains serialized copies * of each text track. * * @return {Object[]} A serializable list of objects for the text track list */ toJSON() { return this.tracks_.map(track => track.toJSON()); } } /** * @file html-track-element-list.js */ /** * The current list of {@link HtmlTrackElement}s. */ class HtmlTrackElementList { /** * Create an instance of this class. * * @param {HtmlTrackElement[]} [tracks=[]] * A list of `HtmlTrackElement` to instantiate the list with. */ constructor(trackElements = []) { this.trackElements_ = []; /** * @memberof HtmlTrackElementList * @member {number} length * The current number of `Track`s in the this Trackist. * @instance */ Object.defineProperty(this, 'length', { get() { return this.trackElements_.length; } }); for (let i = 0, length = trackElements.length; i < length; i++) { this.addTrackElement_(trackElements[i]); } } /** * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` * * @param {HtmlTrackElement} trackElement * The track element to add to the list. * * @private */ addTrackElement_(trackElement) { const index = this.trackElements_.length; if (!('' + index in this)) { Object.defineProperty(this, index, { get() { return this.trackElements_[index]; } }); } // Do not add duplicate elements if (this.trackElements_.indexOf(trackElement) === -1) { this.trackElements_.push(trackElement); } } /** * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an * {@link TextTrack}. * * @param {TextTrack} track * The track associated with a track element. * * @return {HtmlTrackElement|undefined} * The track element that was found or undefined. * * @private */ getTrackElementByTrack_(track) { let trackElement_; for (let i = 0, length = this.trackElements_.length; i < length; i++) { if (track === this.trackElements_[i].track) { trackElement_ = this.trackElements_[i]; break; } } return trackElement_; } /** * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` * * @param {HtmlTrackElement} trackElement * The track element to remove from the list. * * @private */ removeTrackElement_(trackElement) { for (let i = 0, length = this.trackElements_.length; i < length; i++) { if (trackElement === this.trackElements_[i]) { if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { this.trackElements_[i].track.off(); } if (typeof this.trackElements_[i].off === 'function') { this.trackElements_[i].off(); } this.trackElements_.splice(i, 1); break; } } } } /** * @file text-track-cue-list.js */ /** * @typedef {Object} TextTrackCueList~TextTrackCue * * @property {string} id * The unique id for this text track cue * * @property {number} startTime * The start time for this text track cue * * @property {number} endTime * The end time for this text track cue * * @property {boolean} pauseOnExit * Pause when the end time is reached if true. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} */ /** * A List of TextTrackCues. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} */ class TextTrackCueList { /** * Create an instance of this class.. * * @param {Array} cues * A list of cues to be initialized with */ constructor(cues) { TextTrackCueList.prototype.setCues_.call(this, cues); /** * @memberof TextTrackCueList * @member {number} length * The current number of `TextTrackCue`s in the TextTrackCueList. * @instance */ Object.defineProperty(this, 'length', { get() { return this.length_; } }); } /** * A setter for cues in this list. Creates getters * an an index for the cues. * * @param {Array} cues * An array of cues to set * * @private */ setCues_(cues) { const oldLength = this.length || 0; let i = 0; const l = cues.length; this.cues_ = cues; this.length_ = cues.length; const defineProp = function (index) { if (!('' + index in this)) { Object.defineProperty(this, '' + index, { get() { return this.cues_[index]; } }); } }; if (oldLength < l) { i = oldLength; for (; i < l; i++) { defineProp.call(this, i); } } } /** * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. * * @param {string} id * The id of the cue that should be searched for. * * @return {TextTrackCueList~TextTrackCue|null} * A single cue or null if none was found. */ getCueById(id) { let result = null; for (let i = 0, l = this.length; i < l; i++) { const cue = this[i]; if (cue.id === id) { result = cue; break; } } return result; } } /** * @file track-kinds.js */ /** * All possible `VideoTrackKind`s * * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind * @typedef VideoTrack~Kind * @enum */ const VideoTrackKind = { alternative: 'alternative', captions: 'captions', main: 'main', sign: 'sign', subtitles: 'subtitles', commentary: 'commentary' }; /** * All possible `AudioTrackKind`s * * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind * @typedef AudioTrack~Kind * @enum */ const AudioTrackKind = { 'alternative': 'alternative', 'descriptions': 'descriptions', 'main': 'main', 'main-desc': 'main-desc', 'translation': 'translation', 'commentary': 'commentary' }; /** * All possible `TextTrackKind`s * * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind * @typedef TextTrack~Kind * @enum */ const TextTrackKind = { subtitles: 'subtitles', captions: 'captions', descriptions: 'descriptions', chapters: 'chapters', metadata: 'metadata' }; /** * All possible `TextTrackMode`s * * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode * @typedef TextTrack~Mode * @enum */ const TextTrackMode = { disabled: 'disabled', hidden: 'hidden', showing: 'showing' }; /** * @file track.js */ /** * A Track class that contains all of the common functionality for {@link AudioTrack}, * {@link VideoTrack}, and {@link TextTrack}. * * > Note: This class should not be used directly * * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} * @extends EventTarget * @abstract */ class Track extends EventTarget$2 { /** * Create an instance of this class. * * @param {Object} [options={}] * Object of option names and values * * @param {string} [options.kind=''] * A valid kind for the track type you are creating. * * @param {string} [options.id='vjs_track_' + Guid.newGUID()] * A unique id for this AudioTrack. * * @param {string} [options.label=''] * The menu label for this track. * * @param {string} [options.language=''] * A valid two character language code. * * @abstract */ constructor(options = {}) { super(); const trackProps = { id: options.id || 'vjs_track_' + newGUID(), kind: options.kind || '', language: options.language || '' }; let label = options.label || ''; /** * @memberof Track * @member {string} id * The id of this track. Cannot be changed after creation. * @instance * * @readonly */ /** * @memberof Track * @member {string} kind * The kind of track that this is. Cannot be changed after creation. * @instance * * @readonly */ /** * @memberof Track * @member {string} language * The two letter language code for this track. Cannot be changed after * creation. * @instance * * @readonly */ for (const key in trackProps) { Object.defineProperty(this, key, { get() { return trackProps[key]; }, set() {} }); } /** * @memberof Track * @member {string} label * The label of this track. Cannot be changed after creation. * @instance * * @fires Track#labelchange */ Object.defineProperty(this, 'label', { get() { return label; }, set(newLabel) { if (newLabel !== label) { label = newLabel; /** * An event that fires when label changes on this track. * * > Note: This is not part of the spec! * * @event Track#labelchange * @type {Event} */ this.trigger('labelchange'); } } }); } } /** * @file url.js * @module url */ /** * Resolve and parse the elements of a URL. * * @function * @param {string} url * The url to parse * * @return {URL} * An object of url details */ const parseUrl = function (url) { return new URL(url, (document_default()).baseURI); }; /** * Get absolute version of relative URL. * * @function * @param {string} url * URL to make absolute * * @return {string} * Absolute URL */ const getAbsoluteURL = function (url) { return new URL(url, (document_default()).baseURI).href; }; /** * Returns the extension of the passed file name. It will return an empty string * if passed an invalid path. * * @function * @param {string} path * The fileName path like '/path/to/file.mp4' * * @return {string} * The extension in lower case or an empty string if no * extension could be found. */ const getFileExtension = function (path) { if (typeof path === 'string') { const cleanPath = path.split('?')[0].replace(/\/+$/, ''); const match = cleanPath.match(/\.([^.\/]+)$/); return match ? match[1].toLowerCase() : ''; } return ''; }; /** * Returns whether the url passed is a cross domain request or not. * * @function * @param {string} url * The url to check. * * @param {URL} [winLoc] * the domain to check the url against, defaults to window.location * * @return {boolean} * Whether it is a cross domain request or not. */ const isCrossOrigin = function (url, winLoc = (window_default()).location) { return parseUrl(url).origin !== winLoc.origin; }; var Url = /*#__PURE__*/Object.freeze({ __proto__: null, parseUrl: parseUrl, getAbsoluteURL: getAbsoluteURL, getFileExtension: getFileExtension, isCrossOrigin: isCrossOrigin }); /** * @file text-track.js */ /** @import Tech from '../tech/tech' */ /** * Takes a webvtt file contents and parses it into cues * * @param {string} srcContent * webVTT file contents * * @param {TextTrack} track * TextTrack to add cues to. Cues come from the srcContent. * * @private */ const parseCues = function (srcContent, track) { const parser = new (window_default()).WebVTT.Parser((window_default()), (window_default()).vttjs, window_default().WebVTT.StringDecoder()); const errors = []; parser.oncue = function (cue) { track.addCue(cue); }; parser.onparsingerror = function (error) { errors.push(error); }; parser.onflush = function () { track.trigger({ type: 'loadeddata', target: track }); }; parser.parse(srcContent); if (errors.length > 0) { if ((window_default()).console && (window_default()).console.groupCollapsed) { window_default().console.groupCollapsed(`Text Track parsing errors for ${track.src}`); } errors.forEach(error => log$1.error(error)); if ((window_default()).console && (window_default()).console.groupEnd) { window_default().console.groupEnd(); } } parser.flush(); }; /** * Load a `TextTrack` from a specified url. * * @param {string} src * Url to load track from. * * @param {TextTrack} track * Track to add cues to. Comes from the content at the end of `url`. * * @private */ const loadTrack = function (src, track) { const opts = { uri: src }; const crossOrigin = isCrossOrigin(src); if (crossOrigin) { opts.cors = crossOrigin; } const withCredentials = track.tech_.crossOrigin() === 'use-credentials'; if (withCredentials) { opts.withCredentials = withCredentials; } lib_default()(opts, bind_(this, function (err, response, responseBody) { if (err) { return log$1.error(err, response); } track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading // NOTE: this is only used for the alt/video.novtt.js build if (typeof (window_default()).WebVTT !== 'function') { if (track.tech_) { // to prevent use before define eslint error, we define loadHandler // as a let here track.tech_.any(['vttjsloaded', 'vttjserror'], event => { if (event.type === 'vttjserror') { log$1.error(`vttjs failed to load, stopping trying to process ${track.src}`); return; } return parseCues(responseBody, track); }); } } else { parseCues(responseBody, track); } })); }; /** * A representation of a single `TextTrack`. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} * @extends Track */ class TextTrack extends Track { /** * Create an instance of this class. * * @param {Object} options={} * Object of option names and values * * @param {Tech} options.tech * A reference to the tech that owns this TextTrack. * * @param {TextTrack~Kind} [options.kind='subtitles'] * A valid text track kind. * * @param {TextTrack~Mode} [options.mode='disabled'] * A valid text track mode. * * @param {string} [options.id='vjs_track_' + Guid.newGUID()] * A unique id for this TextTrack. * * @param {string} [options.label=''] * The menu label for this track. * * @param {string} [options.language=''] * A valid two character language code. * * @param {string} [options.srclang=''] * A valid two character language code. An alternative, but deprioritized * version of `options.language` * * @param {string} [options.src] * A url to TextTrack cues. * * @param {boolean} [options.default] * If this track should default to on or off. */ constructor(options = {}) { if (!options.tech) { throw new Error('A tech was not provided.'); } const settings = merge$1(options, { kind: TextTrackKind[options.kind] || 'subtitles', language: options.language || options.srclang || '' }); let mode = TextTrackMode[settings.mode] || 'disabled'; const default_ = settings.default; if (settings.kind === 'metadata' || settings.kind === 'chapters') { mode = 'hidden'; } super(settings); this.tech_ = settings.tech; this.cues_ = []; this.activeCues_ = []; this.preload_ = this.tech_.preloadTextTracks !== false; const cues = new TextTrackCueList(this.cues_); const activeCues = new TextTrackCueList(this.activeCues_); let changed = false; this.timeupdateHandler = bind_(this, function (event = {}) { if (this.tech_.isDisposed()) { return; } if (!this.tech_.isReady_) { if (event.type !== 'timeupdate') { this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler); } return; } // Accessing this.activeCues for the side-effects of updating itself // due to its nature as a getter function. Do not remove or cues will // stop updating! // Use the setter to prevent deletion from uglify (pure_getters rule) this.activeCues = this.activeCues; if (changed) { this.trigger('cuechange'); changed = false; } if (event.type !== 'timeupdate') { this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler); } }); const disposeHandler = () => { this.stopTracking(); }; this.tech_.one('dispose', disposeHandler); if (mode !== 'disabled') { this.startTracking(); } Object.defineProperties(this, { /** * @memberof TextTrack * @member {boolean} default * If this track was set to be on or off by default. Cannot be changed after * creation. * @instance * * @readonly */ default: { get() { return default_; }, set() {} }, /** * @memberof TextTrack * @member {string} mode * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will * not be set if setting to an invalid mode. * @instance * * @fires TextTrack#modechange */ mode: { get() { return mode; }, set(newMode) { if (!TextTrackMode[newMode]) { return; } if (mode === newMode) { return; } mode = newMode; if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) { // On-demand load. loadTrack(this.src, this); } this.stopTracking(); if (mode !== 'disabled') { this.startTracking(); } /** * An event that fires when mode changes on this track. This allows * the TextTrackList that holds this track to act accordingly. * * > Note: This is not part of the spec! * * @event TextTrack#modechange * @type {Event} */ this.trigger('modechange'); } }, /** * @memberof TextTrack * @member {TextTrackCueList} cues * The text track cue list for this TextTrack. * @instance */ cues: { get() { if (!this.loaded_) { return null; } return cues; }, set() {} }, /** * @memberof TextTrack * @member {TextTrackCueList} activeCues * The list text track cues that are currently active for this TextTrack. * @instance */ activeCues: { get() { if (!this.loaded_) { return null; } // nothing to do if (this.cues.length === 0) { return activeCues; } const ct = this.tech_.currentTime(); const active = []; for (let i = 0, l = this.cues.length; i < l; i++) { const cue = this.cues[i]; if (cue.startTime <= ct && cue.endTime >= ct) { active.push(cue); } } changed = false; if (active.length !== this.activeCues_.length) { changed = true; } else { for (let i = 0; i < active.length; i++) { if (this.activeCues_.indexOf(active[i]) === -1) { changed = true; } } } this.activeCues_ = active; activeCues.setCues_(this.activeCues_); return activeCues; }, // /!\ Keep this setter empty (see the timeupdate handler above) set() {} } }); if (settings.src) { this.src = settings.src; if (!this.preload_) { // Tracks will load on-demand. // Act like we're loaded for other purposes. this.loaded_ = true; } if (this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') { loadTrack(this.src, this); } } else { this.loaded_ = true; } } startTracking() { // More precise cues based on requestVideoFrameCallback with a requestAnimationFram fallback this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler); // Also listen to timeupdate in case rVFC/rAF stops (window in background, audio in video el) this.tech_.on('timeupdate', this.timeupdateHandler); } stopTracking() { if (this.rvf_) { this.tech_.cancelVideoFrameCallback(this.rvf_); this.rvf_ = undefined; } this.tech_.off('timeupdate', this.timeupdateHandler); } /** * Add a cue to the internal list of cues. * * @param {TextTrack~Cue} cue * The cue to add to our internal list */ addCue(originalCue) { let cue = originalCue; // Testing if the cue is a VTTCue in a way that survives minification if (!('getCueAsHTML' in cue)) { cue = new (window_default()).vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); for (const prop in originalCue) { if (!(prop in cue)) { cue[prop] = originalCue[prop]; } } // make sure that `id` is copied over cue.id = originalCue.id; cue.originalCue_ = originalCue; } const tracks = this.tech_.textTracks(); for (let i = 0; i < tracks.length; i++) { if (tracks[i] !== this) { tracks[i].removeCue(cue); } } this.cues_.push(cue); this.cues.setCues_(this.cues_); } /** * Creates a copy of the text track and makes it serializable * by removing circular dependencies. * * @return {Object} The track information as a serializable object */ toJSON() { return textTrackConverter.trackToJson(this); } /** * Remove a cue from our internal list * * @param {TextTrack~Cue} removeCue * The cue to remove from our internal list */ removeCue(removeCue) { let i = this.cues_.length; while (i--) { const cue = this.cues_[i]; if (cue === removeCue || cue.originalCue_ && cue.originalCue_ === removeCue) { this.cues_.splice(i, 1); this.cues.setCues_(this.cues_); break; } } } } /** * cuechange - One or more cues in the track have become active or stopped being active. * * @protected */ TextTrack.prototype.allowedEvents_ = { cuechange: 'cuechange' }; /** * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} * only one `AudioTrack` in the list will be enabled at a time. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} * @extends Track */ class AudioTrack extends Track { /** * Create an instance of this class. * * @param {Object} [options={}] * Object of option names and values * * @param {AudioTrack~Kind} [options.kind=''] * A valid audio track kind * * @param {string} [options.id='vjs_track_' + Guid.newGUID()] * A unique id for this AudioTrack. * * @param {string} [options.label=''] * The menu label for this track. * * @param {string} [options.language=''] * A valid two character language code. * * @param {boolean} [options.enabled] * If this track is the one that is currently playing. If this track is part of * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. */ constructor(options = {}) { const settings = merge$1(options, { kind: AudioTrackKind[options.kind] || '' }); super(settings); let enabled = false; /** * @memberof AudioTrack * @member {boolean} enabled * If this `AudioTrack` is enabled or not. When setting this will * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. * @instance * * @fires VideoTrack#selectedchange */ Object.defineProperty(this, 'enabled', { get() { return enabled; }, set(newEnabled) { // an invalid or unchanged value if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { return; } enabled = newEnabled; /** * An event that fires when enabled changes on this track. This allows * the AudioTrackList that holds this track to act accordingly. * * > Note: This is not part of the spec! Native tracks will do * this internally without an event. * * @event AudioTrack#enabledchange * @type {Event} */ this.trigger('enabledchange'); } }); // if the user sets this track to selected then // set selected to that true value otherwise // we keep it false if (settings.enabled) { this.enabled = settings.enabled; } this.loaded_ = true; } } /** * A representation of a single `VideoTrack`. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} * @extends Track */ class VideoTrack extends Track { /** * Create an instance of this class. * * @param {Object} [options={}] * Object of option names and values * * @param {string} [options.kind=''] * A valid {@link VideoTrack~Kind} * * @param {string} [options.id='vjs_track_' + Guid.newGUID()] * A unique id for this AudioTrack. * * @param {string} [options.label=''] * The menu label for this track. * * @param {string} [options.language=''] * A valid two character language code. * * @param {boolean} [options.selected] * If this track is the one that is currently playing. */ constructor(options = {}) { const settings = merge$1(options, { kind: VideoTrackKind[options.kind] || '' }); super(settings); let selected = false; /** * @memberof VideoTrack * @member {boolean} selected * If this `VideoTrack` is selected or not. When setting this will * fire {@link VideoTrack#selectedchange} if the state of selected changed. * @instance * * @fires VideoTrack#selectedchange */ Object.defineProperty(this, 'selected', { get() { return selected; }, set(newSelected) { // an invalid or unchanged value if (typeof newSelected !== 'boolean' || newSelected === selected) { return; } selected = newSelected; /** * An event that fires when selected changes on this track. This allows * the VideoTrackList that holds this track to act accordingly. * * > Note: This is not part of the spec! Native tracks will do * this internally without an event. * * @event VideoTrack#selectedchange * @type {Event} */ this.trigger('selectedchange'); } }); // if the user sets this track to selected then // set selected to that true value otherwise // we keep it false if (settings.selected) { this.selected = settings.selected; } } } /** * @file html-track-element.js */ /** @import Tech from '../tech/tech' */ /** * A single track represented in the DOM. * * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement} * @extends EventTarget */ class HTMLTrackElement extends EventTarget$2 { /** * Create an instance of this class. * * @param {Object} options={} * Object of option names and values * * @param {Tech} options.tech * A reference to the tech that owns this HTMLTrackElement. * * @param {TextTrack~Kind} [options.kind='subtitles'] * A valid text track kind. * * @param {TextTrack~Mode} [options.mode='disabled'] * A valid text track mode. * * @param {string} [options.id='vjs_track_' + Guid.newGUID()] * A unique id for this TextTrack. * * @param {string} [options.label=''] * The menu label for this track. * * @param {string} [options.language=''] * A valid two character language code. * * @param {string} [options.srclang=''] * A valid two character language code. An alternative, but deprioritized * version of `options.language` * * @param {string} [options.src] * A url to TextTrack cues. * * @param {boolean} [options.default] * If this track should default to on or off. */ constructor(options = {}) { super(); let readyState; const track = new TextTrack(options); this.kind = track.kind; this.src = track.src; this.srclang = track.language; this.label = track.label; this.default = track.default; Object.defineProperties(this, { /** * @memberof HTMLTrackElement * @member {HTMLTrackElement~ReadyState} readyState * The current ready state of the track element. * @instance */ readyState: { get() { return readyState; } }, /** * @memberof HTMLTrackElement * @member {TextTrack} track * The underlying TextTrack object. * @instance * */ track: { get() { return track; } } }); readyState = HTMLTrackElement.NONE; /** * @listens TextTrack#loadeddata * @fires HTMLTrackElement#load */ track.addEventListener('loadeddata', () => { readyState = HTMLTrackElement.LOADED; this.trigger({ type: 'load', target: this }); }); } } /** * @protected */ HTMLTrackElement.prototype.allowedEvents_ = { load: 'load' }; /** * The text track not loaded state. * * @type {number} * @static */ HTMLTrackElement.NONE = 0; /** * The text track loading state. * * @type {number} * @static */ HTMLTrackElement.LOADING = 1; /** * The text track loaded state. * * @type {number} * @static */ HTMLTrackElement.LOADED = 2; /** * The text track failed to load state. * * @type {number} * @static */ HTMLTrackElement.ERROR = 3; /* * This file contains all track properties that are used in * player.js, tech.js, html5.js and possibly other techs in the future. */ const NORMAL = { audio: { ListClass: AudioTrackList, TrackClass: AudioTrack, capitalName: 'Audio' }, video: { ListClass: VideoTrackList, TrackClass: VideoTrack, capitalName: 'Video' }, text: { ListClass: TextTrackList, TrackClass: TextTrack, capitalName: 'Text' } }; Object.keys(NORMAL).forEach(function (type) { NORMAL[type].getterName = `${type}Tracks`; NORMAL[type].privateName = `${type}Tracks_`; }); const REMOTE = { remoteText: { ListClass: TextTrackList, TrackClass: TextTrack, capitalName: 'RemoteText', getterName: 'remoteTextTracks', privateName: 'remoteTextTracks_' }, remoteTextEl: { ListClass: HtmlTrackElementList, TrackClass: HTMLTrackElement, capitalName: 'RemoteTextTrackEls', getterName: 'remoteTextTrackEls', privateName: 'remoteTextTrackEls_' } }; const ALL = Object.assign({}, NORMAL, REMOTE); REMOTE.names = Object.keys(REMOTE); NORMAL.names = Object.keys(NORMAL); ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); /** * @file tech.js */ /** @import { TimeRange } from '../utils/time' */ /** * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string * that just contains the src url alone. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` * `var SourceString = 'http://example.com/some-video.mp4';` * * @typedef {Object|string} SourceObject * * @property {string} src * The url to the source * * @property {string} type * The mime type of the source */ /** * A function used by {@link Tech} to create a new {@link TextTrack}. * * @private * * @param {Tech} self * An instance of the Tech class. * * @param {string} kind * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) * * @param {string} [label] * Label to identify the text track * * @param {string} [language] * Two letter language abbreviation * * @param {Object} [options={}] * An object with additional text track options * * @return {TextTrack} * The text track that was created. */ function createTrackHelper(self, kind, label, language, options = {}) { const tracks = self.textTracks(); options.kind = kind; if (label) { options.label = label; } if (language) { options.language = language; } options.tech = self; const track = new ALL.text.TrackClass(options); tracks.addTrack(track); return track; } /** * This is the base class for media playback technology controllers, such as * {@link HTML5} * * @extends Component */ class Tech extends Component$1 { /** * Create an instance of this Tech. * * @param {Object} [options] * The key/value store of player options. * * @param {Function} [ready] * Callback function to call when the `HTML5` Tech is ready. */ constructor(options = {}, ready = function () {}) { // we don't want the tech to report user activity automatically. // This is done manually in addControlsListeners options.reportTouchActivity = false; super(null, options, ready); this.onDurationChange_ = e => this.onDurationChange(e); this.trackProgress_ = e => this.trackProgress(e); this.trackCurrentTime_ = e => this.trackCurrentTime(e); this.stopTrackingCurrentTime_ = e => this.stopTrackingCurrentTime(e); this.disposeSourceHandler_ = e => this.disposeSourceHandler(e); this.queuedHanders_ = new Set(); // keep track of whether the current source has played at all to // implement a very limited played() this.hasStarted_ = false; this.on('playing', function () { this.hasStarted_ = true; }); this.on('loadstart', function () { this.hasStarted_ = false; }); ALL.names.forEach(name => { const props = ALL[name]; if (options && options[props.getterName]) { this[props.privateName] = options[props.getterName]; } }); // Manually track progress in cases where the browser/tech doesn't report it. if (!this.featuresProgressEvents) { this.manualProgressOn(); } // Manually track timeupdates in cases where the browser/tech doesn't report it. if (!this.featuresTimeupdateEvents) { this.manualTimeUpdatesOn(); } ['Text', 'Audio', 'Video'].forEach(track => { if (options[`native${track}Tracks`] === false) { this[`featuresNative${track}Tracks`] = false; } }); if (options.nativeCaptions === false || options.nativeTextTracks === false) { this.featuresNativeTextTracks = false; } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { this.featuresNativeTextTracks = true; } if (!this.featuresNativeTextTracks) { this.emulateTextTracks(); } this.preloadTextTracks = options.preloadTextTracks !== false; this.autoRemoteTextTracks_ = new ALL.text.ListClass(); this.initTrackListeners(); // Turn on component tap events only if not using native controls if (!options.nativeControlsForTouch) { this.emitTapEvents(); } if (this.constructor) { this.name_ = this.constructor.name || 'Unknown Tech'; } } /** * A special function to trigger source set in a way that will allow player * to re-trigger if the player or tech are not ready yet. * * @fires Tech#sourceset * @param {string} src The source string at the time of the source changing. */ triggerSourceset(src) { if (!this.isReady_) { // on initial ready we have to trigger source set // 1ms after ready so that player can watch for it. this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1)); } /** * Fired when the source is set on the tech causing the media element * to reload. * * @see {@link Player#event:sourceset} * @event Tech#sourceset * @type {Event} */ this.trigger({ src, type: 'sourceset' }); } /* Fallbacks for unsupported event types ================================================================================ */ /** * Polyfill the `progress` event for browsers that don't support it natively. * * @see {@link Tech#trackProgress} */ manualProgressOn() { this.on('durationchange', this.onDurationChange_); this.manualProgress = true; // Trigger progress watching when a source begins loading this.one('ready', this.trackProgress_); } /** * Turn off the polyfill for `progress` events that was created in * {@link Tech#manualProgressOn} */ manualProgressOff() { this.manualProgress = false; this.stopTrackingProgress(); this.off('durationchange', this.onDurationChange_); } /** * This is used to trigger a `progress` event when the buffered percent changes. It * sets an interval function that will be called every 500 milliseconds to check if the * buffer end percent has changed. * * > This function is called by {@link Tech#manualProgressOn} * * @param {Event} event * The `ready` event that caused this to run. * * @listens Tech#ready * @fires Tech#progress */ trackProgress(event) { this.stopTrackingProgress(); this.progressInterval = this.setInterval(bind_(this, function () { // Don't trigger unless buffered amount is greater than last time const numBufferedPercent = this.bufferedPercent(); if (this.bufferedPercent_ !== numBufferedPercent) { /** * See {@link Player#progress} * * @event Tech#progress * @type {Event} */ this.trigger('progress'); } this.bufferedPercent_ = numBufferedPercent; if (numBufferedPercent === 1) { this.stopTrackingProgress(); } }), 500); } /** * Update our internal duration on a `durationchange` event by calling * {@link Tech#duration}. * * @param {Event} event * The `durationchange` event that caused this to run. * * @listens Tech#durationchange */ onDurationChange(event) { this.duration_ = this.duration(); } /** * Get and create a `TimeRange` object for buffering. * * @return {TimeRange} * The time range object that was created. */ buffered() { return createTimeRanges$1(0, 0); } /** * Get the percentage of the current video that is currently buffered. * * @return {number} * A number from 0 to 1 that represents the decimal percentage of the * video that is buffered. * */ bufferedPercent() { return bufferedPercent(this.buffered(), this.duration_); } /** * Turn off the polyfill for `progress` events that was created in * {@link Tech#manualProgressOn} * Stop manually tracking progress events by clearing the interval that was set in * {@link Tech#trackProgress}. */ stopTrackingProgress() { this.clearInterval(this.progressInterval); } /** * Polyfill the `timeupdate` event for browsers that don't support it. * * @see {@link Tech#trackCurrentTime} */ manualTimeUpdatesOn() { this.manualTimeUpdates = true; this.on('play', this.trackCurrentTime_); this.on('pause', this.stopTrackingCurrentTime_); } /** * Turn off the polyfill for `timeupdate` events that was created in * {@link Tech#manualTimeUpdatesOn} */ manualTimeUpdatesOff() { this.manualTimeUpdates = false; this.stopTrackingCurrentTime(); this.off('play', this.trackCurrentTime_); this.off('pause', this.stopTrackingCurrentTime_); } /** * Sets up an interval function to track current time and trigger `timeupdate` every * 250 milliseconds. * * @listens Tech#play * @triggers Tech#timeupdate */ trackCurrentTime() { if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); } this.currentTimeInterval = this.setInterval(function () { /** * Triggered at an interval of 250ms to indicated that time is passing in the video. * * @event Tech#timeupdate * @type {Event} */ this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 }, 250); } /** * Stop the interval function created in {@link Tech#trackCurrentTime} so that the * `timeupdate` event is no longer triggered. * * @listens {Tech#pause} */ stopTrackingCurrentTime() { this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen, // the progress bar won't make it all the way to the end this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); } /** * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. * * @fires Component#dispose */ dispose() { // clear out all tracks because we can't reuse them between techs this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking if (this.manualProgress) { this.manualProgressOff(); } if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); } super.dispose(); } /** * Clear out a single `TrackList` or an array of `TrackLists` given their names. * * > Note: Techs without source handlers should call this between sources for `video` * & `audio` tracks. You don't want to use them between tracks! * * @param {string[]|string} types * TrackList names to clear, valid names are `video`, `audio`, and * `text`. */ clearTracks(types) { types = [].concat(types); // clear out all tracks because we can't reuse them between techs types.forEach(type => { const list = this[`${type}Tracks`]() || []; let i = list.length; while (i--) { const track = list[i]; if (type === 'text') { this.removeRemoteTextTrack(track); } list.removeTrack(track); } }); } /** * Remove any TextTracks added via addRemoteTextTrack that are * flagged for automatic garbage collection */ cleanupAutoTextTracks() { const list = this.autoRemoteTextTracks_ || []; let i = list.length; while (i--) { const track = list[i]; this.removeRemoteTextTrack(track); } } /** * Reset the tech, which will removes all sources and reset the internal readyState. * * @abstract */ reset() {} /** * Get the value of `crossOrigin` from the tech. * * @abstract * * @see {Html5#crossOrigin} */ crossOrigin() {} /** * Set the value of `crossOrigin` on the tech. * * @abstract * * @param {string} crossOrigin the crossOrigin value * @see {Html5#setCrossOrigin} */ setCrossOrigin() {} /** * Get or set an error on the Tech. * * @param {MediaError} [err] * Error to set on the Tech * * @return {MediaError|null} * The current error object on the tech, or null if there isn't one. */ error(err) { if (err !== undefined) { this.error_ = new MediaError(err); this.trigger('error'); } return this.error_; } /** * Returns the `TimeRange`s that have been played through for the current source. * * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. * It only checks whether the source has played at all or not. * * @return {TimeRange} * - A single time range if this video has played * - An empty set of ranges if not. */ played() { if (this.hasStarted_) { return createTimeRanges$1(0, 0); } return createTimeRanges$1(); } /** * Start playback * * @abstract * * @see {Html5#play} */ play() {} /** * Set whether we are scrubbing or not * * @abstract * @param {boolean} _isScrubbing * - true for we are currently scrubbing * - false for we are no longer scrubbing * * @see {Html5#setScrubbing} */ setScrubbing(_isScrubbing) {} /** * Get whether we are scrubbing or not * * @abstract * * @see {Html5#scrubbing} */ scrubbing() {} /** * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was * previously called. * * @param {number} _seconds * Set the current time of the media to this. * @fires Tech#timeupdate */ setCurrentTime(_seconds) { // improve the accuracy of manual timeupdates if (this.manualTimeUpdates) { /** * A manual `timeupdate` event. * * @event Tech#timeupdate * @type {Event} */ this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); } } /** * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and * {@link TextTrackList} events. * * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. * * @fires Tech#audiotrackchange * @fires Tech#videotrackchange * @fires Tech#texttrackchange */ initTrackListeners() { /** * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} * * @event Tech#audiotrackchange * @type {Event} */ /** * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} * * @event Tech#videotrackchange * @type {Event} */ /** * Triggered when tracks are added or removed on the Tech {@link TextTrackList} * * @event Tech#texttrackchange * @type {Event} */ NORMAL.names.forEach(name => { const props = NORMAL[name]; const trackListChanges = () => { this.trigger(`${name}trackchange`); }; const tracks = this[props.getterName](); tracks.addEventListener('removetrack', trackListChanges); tracks.addEventListener('addtrack', trackListChanges); this.on('dispose', () => { tracks.removeEventListener('removetrack', trackListChanges); tracks.removeEventListener('addtrack', trackListChanges); }); }); } /** * Emulate TextTracks using vtt.js if necessary * * @fires Tech#vttjsloaded * @fires Tech#vttjserror */ addWebVttScript_() { if ((window_default()).WebVTT) { return; } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system // signals that the Tech is ready at which point Tech.el_ is part of the DOM // before inserting the WebVTT script if (document_default().body.contains(this.el())) { // load via require if available and vtt.js script location was not passed in // as an option. novtt builds will turn the above require call into an empty object // which will cause this if check to always fail. if (!this.options_['vtt.js'] && isPlain((browser_index_default())) && Object.keys((browser_index_default())).length > 0) { this.trigger('vttjsloaded'); return; } // load vtt.js via the script location option or the cdn of no location was // passed in const script = document_default().createElement('script'); script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; script.onload = () => { /** * Fired when vtt.js is loaded. * * @event Tech#vttjsloaded * @type {Event} */ this.trigger('vttjsloaded'); }; script.onerror = () => { /** * Fired when vtt.js was not loaded due to an error * * @event Tech#vttjsloaded * @type {Event} */ this.trigger('vttjserror'); }; this.on('dispose', () => { script.onload = null; script.onerror = null; }); // but have not loaded yet and we set it to true before the inject so that // we don't overwrite the injected window.WebVTT if it loads right away (window_default()).WebVTT = true; this.el().parentNode.appendChild(script); } else { this.ready(this.addWebVttScript_); } } /** * Emulate texttracks * */ emulateTextTracks() { const tracks = this.textTracks(); const remoteTracks = this.remoteTextTracks(); const handleAddTrack = e => tracks.addTrack(e.track); const handleRemoveTrack = e => tracks.removeTrack(e.track); remoteTracks.on('addtrack', handleAddTrack); remoteTracks.on('removetrack', handleRemoveTrack); this.addWebVttScript_(); const updateDisplay = () => this.trigger('texttrackchange'); const textTracksChanges = () => { updateDisplay(); for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; track.removeEventListener('cuechange', updateDisplay); if (track.mode === 'showing') { track.addEventListener('cuechange', updateDisplay); } } }; textTracksChanges(); tracks.addEventListener('change', textTracksChanges); tracks.addEventListener('addtrack', textTracksChanges); tracks.addEventListener('removetrack', textTracksChanges); this.on('dispose', function () { remoteTracks.off('addtrack', handleAddTrack); remoteTracks.off('removetrack', handleRemoveTrack); tracks.removeEventListener('change', textTracksChanges); tracks.removeEventListener('addtrack', textTracksChanges); tracks.removeEventListener('removetrack', textTracksChanges); for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; track.removeEventListener('cuechange', updateDisplay); } }); } /** * Create and returns a remote {@link TextTrack} object. * * @param {string} kind * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) * * @param {string} [label] * Label to identify the text track * * @param {string} [language] * Two letter language abbreviation * * @return {TextTrack} * The TextTrack that gets created. */ addTextTrack(kind, label, language) { if (!kind) { throw new Error('TextTrack kind is required but was not provided'); } return createTrackHelper(this, kind, label, language); } /** * Create an emulated TextTrack for use by addRemoteTextTrack * * This is intended to be overridden by classes that inherit from * Tech in order to create native or custom TextTracks. * * @param {Object} options * The object should contain the options to initialize the TextTrack with. * * @param {string} [options.kind] * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). * * @param {string} [options.label]. * Label to identify the text track * * @param {string} [options.language] * Two letter language abbreviation. * * @return {HTMLTrackElement} * The track element that gets created. */ createRemoteTextTrack(options) { const track = merge$1(options, { tech: this }); return new REMOTE.remoteTextEl.TrackClass(track); } /** * Creates a remote text track object and returns an html track element. * * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. * * @param {Object} options * See {@link Tech#createRemoteTextTrack} for more detailed properties. * * @param {boolean} [manualCleanup=false] * - When false: the TextTrack will be automatically removed from the video * element whenever the source changes * - When True: The TextTrack will have to be cleaned up manually * * @return {HTMLTrackElement} * An Html Track Element. * */ addRemoteTextTrack(options = {}, manualCleanup) { const htmlTrackElement = this.createRemoteTextTrack(options); if (typeof manualCleanup !== 'boolean') { manualCleanup = false; } // store HTMLTrackElement and TextTrack to remote list this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); this.remoteTextTracks().addTrack(htmlTrackElement.track); if (manualCleanup === false) { // create the TextTrackList if it doesn't exist this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track)); } return htmlTrackElement; } /** * Remove a remote text track from the remote `TextTrackList`. * * @param {TextTrack} track * `TextTrack` to remove from the `TextTrackList` */ removeRemoteTextTrack(track) { const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list this.remoteTextTrackEls().removeTrackElement_(trackElement); this.remoteTextTracks().removeTrack(track); this.autoRemoteTextTracks_.removeTrack(track); } /** * Gets available media playback quality metrics as specified by the W3C's Media * Playback Quality API. * * @see [Spec]{@link https://wicg.github.io/media-playback-quality} * * @return {Object} * An object with supported media playback quality metrics * * @abstract */ getVideoPlaybackQuality() { return {}; } /** * Attempt to create a floating video window always on top of other windows * so that users may continue consuming media while they interact with other * content sites, or applications on their device. * * @see [Spec]{@link https://wicg.github.io/picture-in-picture} * * @return {Promise|undefined} * A promise with a Picture-in-Picture window if the browser supports * Promises (or one was passed in as an option). It returns undefined * otherwise. * * @abstract */ requestPictureInPicture() { return Promise.reject(); } /** * A method to check for the value of the 'disablePictureInPicture'