БестВей, Коминтерна, 47А1, Нижний Новгород
diff --git a/app/js/main.js b/app/js/main.js
index 3aa2fcb..72a91f2 100644
--- a/app/js/main.js
+++ b/app/js/main.js
@@ -20,10541 +20,8144 @@ const t=t=>"object"==typeof t&&null!==t&&t.constructor===Object&&"[object Object
/***/ }),
-/***/ "./node_modules/@videojs/vhs-utils/es/byte-helpers.js":
-/*!************************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/byte-helpers.js ***!
- \************************************************************/
+/***/ "./node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js":
+/*!*************************************************************************!*\
+ !*** ./node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js ***!
+ \*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ ENDIANNESS: () => (/* binding */ ENDIANNESS),
-/* harmony export */ IS_BIG_ENDIAN: () => (/* binding */ IS_BIG_ENDIAN),
-/* harmony export */ IS_LITTLE_ENDIAN: () => (/* binding */ IS_LITTLE_ENDIAN),
-/* harmony export */ bytesMatch: () => (/* binding */ bytesMatch),
-/* harmony export */ bytesToNumber: () => (/* binding */ bytesToNumber),
-/* harmony export */ bytesToString: () => (/* binding */ bytesToString),
-/* harmony export */ concatTypedArrays: () => (/* binding */ concatTypedArrays),
-/* harmony export */ countBits: () => (/* binding */ countBits),
-/* harmony export */ countBytes: () => (/* binding */ countBytes),
-/* harmony export */ isArrayBufferView: () => (/* binding */ isArrayBufferView),
-/* harmony export */ isTypedArray: () => (/* binding */ isTypedArray),
-/* harmony export */ numberToBytes: () => (/* binding */ numberToBytes),
-/* harmony export */ padStart: () => (/* binding */ padStart),
-/* harmony export */ reverseBytes: () => (/* binding */ reverseBytes),
-/* harmony export */ sliceBytes: () => (/* binding */ sliceBytes),
-/* harmony export */ stringToBytes: () => (/* binding */ stringToBytes),
-/* harmony export */ toBinaryString: () => (/* binding */ toBinaryString),
-/* harmony export */ toHexString: () => (/* binding */ toHexString),
-/* harmony export */ toUint8: () => (/* binding */ toUint8)
+/* harmony export */ "default": () => (/* binding */ decodeB64ToUint8Array)
/* harmony export */ });
/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_0__);
- // 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 atob = function atob(s) {
+ return (global_window__WEBPACK_IMPORTED_MODULE_0___default().atob) ? global_window__WEBPACK_IMPORTED_MODULE_0___default().atob(s) : Buffer.from(s, 'base64').toString('binary');
+};
-var countBits = function countBits(x) {
- return x.toString(2).length;
-}; // count the number of whole bytes it would take to represent a number
+function decodeB64ToUint8Array(b64Text) {
+ var decodedString = atob(b64Text);
+ var array = new Uint8Array(decodedString.length);
-var countBytes = function countBytes(x) {
- return Math.ceil(countBits(x) / 8);
-};
-var padStart = function padStart(b, len, str) {
- if (str === void 0) {
- str = ' ';
+ for (var i = 0; i < decodedString.length; i++) {
+ array[i] = decodedString.charCodeAt(i);
}
- return (repeat(str, len) + b.toString()).slice(-len);
-};
-var isArrayBufferView = function isArrayBufferView(obj) {
- if (ArrayBuffer.isView === 'function') {
- return ArrayBuffer.isView(obj);
- }
+ return array;
+}
- return obj && obj.buffer instanceof ArrayBuffer;
-};
-var isTypedArray = function isTypedArray(obj) {
- return isArrayBufferView(obj);
-};
-var 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];
- }
- }
+/***/ "./node_modules/@videojs/vhs-utils/es/media-groups.js":
+/*!************************************************************!*\
+ !*** ./node_modules/@videojs/vhs-utils/es/media-groups.js ***!
+ \************************************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ forEachMediaGroup: () => (/* binding */ forEachMediaGroup)
+/* harmony export */ });
+/**
+ * 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);
+ }
+ }
+ });
};
-var toHexString = function toHexString(bytes) {
- bytes = toUint8(bytes);
- var str = '';
- for (var i = 0; i < bytes.length; i++) {
- str += padStart(bytes[i].toString(16), 2, '0');
- }
+/***/ }),
- return str;
-};
-var toBinaryString = function toBinaryString(bytes) {
- bytes = toUint8(bytes);
- var str = '';
+/***/ "./node_modules/@videojs/vhs-utils/es/resolve-url.js":
+/*!***********************************************************!*\
+ !*** ./node_modules/@videojs/vhs-utils/es/resolve-url.js ***!
+ \***********************************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- for (var i = 0; i < bytes.length; i++) {
- str += padStart(bytes[i].toString(2), 8, '0');
- }
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
+/* harmony export */ });
+/* harmony import */ var url_toolkit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! url-toolkit */ "./node_modules/url-toolkit/src/url-toolkit.js");
+/* harmony import */ var url_toolkit__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(url_toolkit__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
+/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_1__);
- return str;
-};
-var BigInt = (global_window__WEBPACK_IMPORTED_MODULE_0___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';
- }
+var DEFAULT_LOCATION = 'http://example.com';
- if (b[0] === 0xCC) {
- return 'little';
- }
+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
- return 'unknown';
-}();
-var IS_BIG_ENDIAN = ENDIANNESS === 'big';
-var IS_LITTLE_ENDIAN = ENDIANNESS === 'little';
-var 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 = 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 (/^data:/.test(baseUrl)) {
+ baseUrl = (global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && (global_window__WEBPACK_IMPORTED_MODULE_1___default().location).href || '';
+ } // IE11 supports URL but not the URL constructor
+ // feature detect the behavior we want
- 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);
- }
- }
+ var nativeURL = typeof (global_window__WEBPACK_IMPORTED_MODULE_1___default().URL) === 'function';
+ 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
- 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;
+ var removeLocation = !(global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
- // eslint-disable-next-line
- if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
- number = 0;
+ if (nativeURL) {
+ baseUrl = new (global_window__WEBPACK_IMPORTED_MODULE_1___default().URL)(baseUrl, (global_window__WEBPACK_IMPORTED_MODULE_1___default().location) || DEFAULT_LOCATION);
+ } else if (!/\/\//i.test(baseUrl)) {
+ baseUrl = url_toolkit__WEBPACK_IMPORTED_MODULE_0___default().buildAbsoluteURL((global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && (global_window__WEBPACK_IMPORTED_MODULE_1___default().location).href || '', baseUrl);
}
- 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 (nativeURL) {
+ 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 (number < 0) {
- bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
- bytes[byteIndex] -= i === 0 ? 1 : 2;
+ if (removeLocation) {
+ return newUrl.href.slice(DEFAULT_LOCATION.length);
+ } else if (protocolLess) {
+ return newUrl.href.slice(newUrl.protocol.length);
}
+
+ return newUrl.href;
}
- return bytes;
+ return url_toolkit__WEBPACK_IMPORTED_MODULE_0___default().buildAbsoluteURL(baseUrl, relativeUrl);
};
-var 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?
+/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (resolveUrl);
- bytes = Array.prototype.slice.call(bytes);
- var string = String.fromCharCode.apply(null, 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.
- }
+/***/ "./node_modules/@videojs/vhs-utils/es/stream.js":
+/*!******************************************************!*\
+ !*** ./node_modules/@videojs/vhs-utils/es/stream.js ***!
+ \******************************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- return string;
-};
-var stringToBytes = function stringToBytes(string, stringIsBytes) {
- if (typeof string !== 'string' && string && typeof string.toString === 'function') {
- string = string.toString();
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ "default": () => (/* binding */ Stream)
+/* harmony export */ });
+/**
+ * @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
+ */
- 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
+ var _proto = Stream.prototype;
- if (!stringIsBytes) {
- string = unescape(encodeURIComponent(string));
+ _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
+ */
+ ;
- var view = new Uint8Array(string.length);
+ _proto.off = function off(type, listener) {
+ if (!this.listeners[type]) {
+ return false;
+ }
- for (var i = 0; i < string.length; i++) {
- view[i] = string.charCodeAt(i);
- }
+ 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.
- return view;
-};
-var concatTypedArrays = function concatTypedArrays() {
- for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
- buffers[_key] = arguments[_key];
+ 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
+ */
+ ;
- buffers = buffers.filter(function (b) {
- return b && (b.byteLength || b.length) && typeof b !== 'string';
- });
+ _proto.trigger = function trigger(type) {
+ var callbacks = this.listeners[type];
- if (buffers.length <= 1) {
- // for 0 length we will return empty uint8
- // for 1 length we return the first uint8
- return toUint8(buffers[0]);
- }
+ 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
- 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 = 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 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;
+ if (arguments.length === 2) {
+ var length = callbacks.length;
- a = toUint8(a);
- b = toUint8(b); // ie 11 does not support uint8 every
+ 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;
- 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);
+ for (var _i = 0; _i < _length; ++_i) {
+ callbacks[_i].apply(this, args);
+ }
+ }
}
+ /**
+ * Destroys the stream and cleans up.
+ */
+ ;
- return new Uint8Array(Array.prototype.slice.call(src, start, end));
-};
-var reverseBytes = function reverseBytes(src) {
- if (src.reverse) {
- return src.reverse();
+ _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;
+}();
+
- return Array.prototype.reverse.call(src);
-};
/***/ }),
-/***/ "./node_modules/@videojs/vhs-utils/es/codec-helpers.js":
-/*!*************************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/codec-helpers.js ***!
- \*************************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+/***/ "./node_modules/@videojs/xhr/lib/http-handler.js":
+/*!*******************************************************!*\
+ !*** ./node_modules/@videojs/xhr/lib/http-handler.js ***!
+ \*******************************************************/
+/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ getAv1Codec: () => (/* binding */ getAv1Codec),
-/* harmony export */ getAvcCodec: () => (/* binding */ getAvcCodec),
-/* harmony export */ getHvcCodec: () => (/* binding */ getHvcCodec)
-/* harmony export */ });
-/* harmony import */ var _byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./byte-helpers.js */ "./node_modules/@videojs/vhs-utils/es/byte-helpers.js");
- // https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax
-// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter#AV1
-var 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 + "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(level, 2, '0');
- if (tier === 0) {
- codec += 'M';
- } else if (tier === 1) {
- codec += 'H';
+var window = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
+
+var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) {
+ if (decodeResponseBody === void 0) {
+ decodeResponseBody = false;
}
- var bitDepth;
+ 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 (profile === 2 && highBitDepth) {
- bitDepth = twelveBit ? 12 : 10;
- } else {
- bitDepth = highBitDepth ? 10 : 8;
- }
- codec += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(bitDepth, 2, '0'); // TODO: can we parse color range??
+ if (response.statusCode >= 400 && response.statusCode <= 599) {
+ var cause = responseBody;
- codec += "." + monochrome;
- codec += "." + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition;
- return codec;
-};
-var getAvcCodec = function getAvcCodec(bytes) {
- var profileId = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toHexString)(bytes[1]);
- var constraintFlags = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toHexString)(bytes[2] & 0xFC);
- var levelId = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toHexString)(bytes[3]);
- return "" + profileId + constraintFlags + levelId;
-};
-var 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 (decodeResponseBody) {
+ if (window.TextDecoder) {
+ var charset = getCharset(response.headers && response.headers['content-type']);
- 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
+ try {
+ cause = new TextDecoder(charset).decode(responseBody);
+ } catch (e) {}
+ } else {
+ cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
+ }
+ }
- var profileCompatVal = parseInt((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toBinaryString)(profileCompat).split('').reverse().join(''), 2); // apple does this in little endian...
+ callback({
+ cause: cause
+ });
+ return;
+ } // otherwise, request succeeded
- if (profileCompatVal > 255) {
- profileCompatVal = parseInt((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toBinaryString)(profileCompat), 2);
- }
- codec += profileCompatVal.toString(16) + ".";
+ callback(null, responseBody);
+ };
+};
- if (tierFlag === 0) {
- codec += 'L';
- } else {
- codec += 'H';
+function getCharset(contentTypeHeader) {
+ if (contentTypeHeader === void 0) {
+ contentTypeHeader = '';
}
- codec += levelId;
- var constraints = '';
-
- for (var i = 0; i < constraintIds.length; i++) {
- var v = constraintIds[i];
-
- if (v) {
- if (constraints) {
- constraints += '.';
- }
+ return contentTypeHeader.toLowerCase().split(';').reduce(function (charset, contentType) {
+ var _contentType$split = contentType.split('='),
+ type = _contentType$split[0],
+ value = _contentType$split[1];
- constraints += v.toString(16);
+ if (type.trim() === 'charset') {
+ return value.trim();
}
- }
- if (constraints) {
- codec += "." + constraints;
- }
+ return charset;
+ }, 'utf-8');
+}
- return codec;
-};
+module.exports = httpResponseHandler;
/***/ }),
-/***/ "./node_modules/@videojs/vhs-utils/es/codecs.js":
-/*!******************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/codecs.js ***!
- \******************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+/***/ "./node_modules/@videojs/xhr/lib/index.js":
+/*!************************************************!*\
+ !*** ./node_modules/@videojs/xhr/lib/index.js ***!
+ \************************************************/
+/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ DEFAULT_AUDIO_CODEC: () => (/* binding */ DEFAULT_AUDIO_CODEC),
-/* harmony export */ DEFAULT_VIDEO_CODEC: () => (/* binding */ DEFAULT_VIDEO_CODEC),
-/* harmony export */ browserSupportsCodec: () => (/* binding */ browserSupportsCodec),
-/* harmony export */ codecsFromDefault: () => (/* binding */ codecsFromDefault),
-/* harmony export */ getMimeForCodec: () => (/* binding */ getMimeForCodec),
-/* harmony export */ isAudioCodec: () => (/* binding */ isAudioCodec),
-/* harmony export */ isTextCodec: () => (/* binding */ isTextCodec),
-/* harmony export */ isVideoCodec: () => (/* binding */ isVideoCodec),
-/* harmony export */ mapLegacyAvcCodecs: () => (/* binding */ mapLegacyAvcCodecs),
-/* harmony export */ muxerSupportsCodec: () => (/* binding */ muxerSupportsCodec),
-/* harmony export */ parseCodecs: () => (/* binding */ parseCodecs),
-/* harmony export */ translateLegacyCodec: () => (/* binding */ translateLegacyCodec),
-/* harmony export */ translateLegacyCodecs: () => (/* binding */ translateLegacyCodecs)
-/* harmony export */ });
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_0__);
-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'];
+
+var window = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
+
+var _extends = __webpack_require__(/*! @babel/runtime/helpers/extends */ "./node_modules/@babel/runtime/helpers/extends.js");
+
+var isFunction = __webpack_require__(/*! is-function */ "./node_modules/is-function/index.js");
+
+createXHR.httpHandler = __webpack_require__(/*! ./http-handler.js */ "./node_modules/@videojs/xhr/lib/http-handler.js");
/**
- * 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
+ * @license
+ * slighly modified parse-headers 2.0.2
+ * Copyright (c) 2014 David Björklund
+ * Available under the MIT license
+ *
*/
-var translateLegacyCodec = function translateLegacyCodec(codec) {
- if (!codec) {
- return codec;
+var parseHeaders = function parseHeaders(headers) {
+ var result = {};
+
+ if (!headers) {
+ return result;
}
- 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;
+ 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;
};
-/**
- * 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
- */
+module.exports = createXHR; // Allow use of default import syntax in TypeScript
-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
- */
+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);
+ };
+});
-/**
- * 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
- */
+function forEachArray(array, iterator) {
+ for (var i = 0; i < array.length; i++) {
+ iterator(array[i]);
+ }
+}
-var parseCodecs = function parseCodecs(codecString) {
- if (codecString === void 0) {
- codecString = '';
+function isEmpty(obj) {
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) return false;
}
- 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());
+ return true;
+}
- if (!match || match.length <= 1) {
- return;
- }
+function initParams(uri, options, callback) {
+ var params = uri;
- codecType = name; // maintain codec case
+ if (isFunction(options)) {
+ callback = options;
- var type = codec.substring(0, match[1].length);
- var details = codec.replace(type, '');
- result.push({
- type: type,
- details: details,
- mediaType: name
- });
+ if (typeof uri === "string") {
+ params = {
+ uri: uri
+ };
+ }
+ } else {
+ params = _extends({}, options, {
+ uri: uri
});
+ }
- 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
- */
+ params.callback = callback;
+ return params;
+}
-var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
- if (!master.mediaGroups.AUDIO || !audioGroupId) {
- return null;
+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");
}
- var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
+ var called = false;
- if (!audioGroup) {
- return null;
+ var callback = function cbOnce(err, response, body) {
+ if (!called) {
+ called = true;
+ options.callback(err, response, body);
+ }
+ };
+
+ function readystatechange() {
+ if (xhr.readyState === 4) {
+ setTimeout(loadFunc, 0);
+ }
}
- for (var name in audioGroup) {
- var audioType = audioGroup[name];
+ function getBody() {
+ // Chrome with requestType=blob throws errors arround when even testing access to responseText
+ var body = undefined;
- if (audioType.default && audioType.playlists) {
- // codec should be the same for all playlists within the audio type
- return parseCodecs(audioType.playlists[0].attributes.CODECS);
+ if (xhr.response) {
+ body = xhr.response;
+ } else {
+ body = xhr.responseText || getXml(xhr);
}
- }
- return null;
-};
-var isVideoCodec = function isVideoCodec(codec) {
- if (codec === void 0) {
- codec = '';
- }
+ if (isJson) {
+ try {
+ body = JSON.parse(body);
+ } catch (e) {}
+ }
- return regexs.video.test(codec.trim().toLowerCase());
-};
-var isAudioCodec = function isAudioCodec(codec) {
- if (codec === void 0) {
- codec = '';
+ return body;
}
- return regexs.audio.test(codec.trim().toLowerCase());
-};
-var isTextCodec = function isTextCodec(codec) {
- if (codec === void 0) {
- codec = '';
- }
+ function errorFunc(evt) {
+ clearTimeout(timeoutTimer);
- return regexs.text.test(codec.trim().toLowerCase());
-};
-var getMimeForCodec = function getMimeForCodec(codecString) {
- if (!codecString || typeof codecString !== 'string') {
- return;
- }
+ if (!(evt instanceof Error)) {
+ evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
+ }
- var codecs = codecString.toLowerCase().split(',').map(function (c) {
- return translateLegacyCodec(c.trim());
- }); // default to video type
+ evt.statusCode = 0;
+ return callback(evt, failureResponse);
+ } // will load the data & process the response in a special response object
- 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
+ function loadFunc() {
+ if (aborted) return;
+ var status;
+ clearTimeout(timeoutTimer);
+ 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 container = 'mp4'; // every codec must be able to go into the container
- // for that container to be the correct one
+ var response = failureResponse;
+ var err = null;
- 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';
- }
+ if (status !== 0) {
+ response = {
+ body: getBody(),
+ statusCode: status,
+ method: method,
+ headers: {},
+ url: uri,
+ rawRequest: xhr
+ };
- return type + "/" + container + ";codecs=\"" + codecString + "\"";
-};
-var browserSupportsCodec = function browserSupportsCodec(codecString) {
- if (codecString === void 0) {
- codecString = '';
+ 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");
+ }
+
+ return callback(err, response, response.body);
}
- return (global_window__WEBPACK_IMPORTED_MODULE_0___default().MediaSource) && (global_window__WEBPACK_IMPORTED_MODULE_0___default().MediaSource).isTypeSupported && global_window__WEBPACK_IMPORTED_MODULE_0___default().MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
-};
-var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
- if (codecString === void 0) {
- codecString = '';
+ var xhr = options.xhr || null;
+
+ if (!xhr) {
+ if (options.cors || options.useXDR) {
+ xhr = new createXHR.XDomainRequest();
+ } else {
+ xhr = new createXHR.XMLHttpRequest();
+ }
}
- return codecString.toLowerCase().split(',').every(function (codec) {
- codec = codec.trim(); // any match is supported.
+ 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
+ };
- for (var i = 0; i < upperMediaTypes.length; i++) {
- var type = upperMediaTypes[i];
+ 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 (regexs["muxer" + type].test(codec)) {
- return true;
- }
+ 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);
}
+ }
- return false;
- });
-};
-var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
-var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
+ 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
+ };
-/***/ "./node_modules/@videojs/vhs-utils/es/containers.js":
-/*!**********************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/containers.js ***!
- \**********************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ xhr.onabort = function () {
+ aborted = true;
+ };
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ detectContainerForBytes: () => (/* binding */ detectContainerForBytes),
-/* harmony export */ isLikely: () => (/* binding */ isLikely),
-/* harmony export */ isLikelyFmp4MediaSegment: () => (/* binding */ isLikelyFmp4MediaSegment)
-/* harmony export */ });
-/* harmony import */ var _byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./byte-helpers.js */ "./node_modules/@videojs/vhs-utils/es/byte-helpers.js");
-/* harmony import */ var _mp4_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mp4-helpers.js */ "./node_modules/@videojs/vhs-utils/es/mp4-helpers.js");
-/* harmony import */ var _ebml_helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ebml-helpers.js */ "./node_modules/@videojs/vhs-utils/es/ebml-helpers.js");
-/* harmony import */ var _id3_helpers_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./id3-helpers.js */ "./node_modules/@videojs/vhs-utils/es/id3-helpers.js");
-/* harmony import */ var _nal_helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./nal-helpers.js */ "./node_modules/@videojs/vhs-utils/es/nal-helpers.js");
+ 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);
+ }
-var CONSTANTS = {
- // "webm" string literal in hex
- 'webm': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x77, 0x65, 0x62, 0x6d]),
- // "matroska" string literal in hex
- 'matroska': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]),
- // "fLaC" string literal in hex
- 'flac': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x66, 0x4c, 0x61, 0x43]),
- // "OggS" string literal in hex
- 'ogg': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x4f, 0x67, 0x67, 0x53]),
- // ac-3 sync byte, also works for ec-3 as that is simply a codec
- // of ac-3
- 'ac3': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x0b, 0x77]),
- // "RIFF" string literal in hex used for wav and avi
- 'riff': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x52, 0x49, 0x46, 0x46]),
- // "AVI" string literal in hex
- 'avi': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x41, 0x56, 0x49]),
- // "WAVE" string literal in hex
- 'wav': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x57, 0x41, 0x56, 0x45]),
- // "ftyp3g" string literal in hex
- '3gp': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]),
- // "ftyp" string literal in hex
- 'mp4': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x66, 0x74, 0x79, 0x70]),
- // "styp" string literal in hex
- 'fmp4': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x73, 0x74, 0x79, 0x70]),
- // "ftypqt" string literal in hex
- 'mov': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]),
- // moov string literal in hex
- 'moov': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x6D, 0x6F, 0x6F, 0x76]),
- // moof string literal in hex
- 'moof': (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x6D, 0x6F, 0x6F, 0x66])
-};
-var _isLikely = {
- aac: function aac(bytes) {
- var offset = (0,_id3_helpers_js__WEBPACK_IMPORTED_MODULE_3__.getId3Offset)(bytes);
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, [0xFF, 0x10], {
- offset: offset,
- mask: [0xFF, 0x16]
- });
- },
- mp3: function mp3(bytes) {
- var offset = (0,_id3_helpers_js__WEBPACK_IMPORTED_MODULE_3__.getId3Offset)(bytes);
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, [0xFF, 0x02], {
- offset: offset,
- mask: [0xFF, 0x06]
- });
- },
- webm: function webm(bytes) {
- var docType = (0,_ebml_helpers_js__WEBPACK_IMPORTED_MODULE_2__.findEbml)(bytes, [_ebml_helpers_js__WEBPACK_IMPORTED_MODULE_2__.EBML_TAGS.EBML, _ebml_helpers_js__WEBPACK_IMPORTED_MODULE_2__.EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm
-
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(docType, CONSTANTS.webm);
- },
- mkv: function mkv(bytes) {
- var docType = (0,_ebml_helpers_js__WEBPACK_IMPORTED_MODULE_2__.findEbml)(bytes, [_ebml_helpers_js__WEBPACK_IMPORTED_MODULE_2__.EBML_TAGS.EBML, _ebml_helpers_js__WEBPACK_IMPORTED_MODULE_2__.EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska
-
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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 ((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.mp4, {
- offset: 4
- }) || (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.fmp4, {
- offset: 4
- })) {
- return true;
- } // if this file starts with a moof/moov box its mp4
-
-
- if ((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.moof, {
- offset: 4
- }) || (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.moov, {
- offset: 4
- })) {
- return true;
- }
- },
- mov: function mov(bytes) {
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.mov, {
- offset: 4
- });
- },
- '3gp': function gp(bytes) {
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS['3gp'], {
- offset: 4
- });
- },
- ac3: function ac3(bytes) {
- var offset = (0,_id3_helpers_js__WEBPACK_IMPORTED_MODULE_3__.getId3Offset)(bytes);
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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;
+ if (xhr.setRequestHeader) {
+ for (key in headers) {
+ if (headers.hasOwnProperty(key)) {
+ xhr.setRequestHeader(key, headers[key]);
}
-
- i += 1;
}
-
- return false;
- },
- flac: function flac(bytes) {
- var offset = (0,_id3_helpers_js__WEBPACK_IMPORTED_MODULE_3__.getId3Offset)(bytes);
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.flac, {
- offset: offset
- });
- },
- ogg: function ogg(bytes) {
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.ogg);
- },
- avi: function avi(bytes) {
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.riff) && (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.avi, {
- offset: 8
- });
- },
- wav: function wav(bytes) {
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.riff) && (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, CONSTANTS.wav, {
- offset: 8
- });
- },
- 'h264': function h264(bytes) {
- // find seq_parameter_set_rbsp
- return (0,_nal_helpers_js__WEBPACK_IMPORTED_MODULE_4__.findH264Nal)(bytes, 7, 3).length;
- },
- 'h265': function h265(bytes) {
- // find video_parameter_set_rbsp or seq_parameter_set_rbsp
- return (0,_nal_helpers_js__WEBPACK_IMPORTED_MODULE_4__.findH265Nal)(bytes, [32, 33], 3).length;
+ } else if (options.headers && !isEmpty(options.headers)) {
+ throw new Error("Headers cannot be set on an XDomainRequest object");
}
-}; // 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.
+ if ("responseType" in options) {
+ xhr.responseType = options.responseType;
+ }
-isLikelyTypes.forEach(function (type) {
- var isLikelyFn = _isLikely[type];
+ 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.
- _isLikely[type] = function (bytes) {
- return isLikelyFn((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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
+ xhr.send(body || null);
+ return xhr;
+}
-var detectContainerForBytes = function detectContainerForBytes(bytes) {
- bytes = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)(bytes);
+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;
+ }
- for (var i = 0; i < isLikelyTypes.length; i++) {
- var type = isLikelyTypes[i];
+ var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
- if (isLikely[type](bytes)) {
- return type;
+ if (xhr.responseType === "" && !firefoxBugTakenEffect) {
+ return xhr.responseXML;
}
- }
+ } catch (e) {}
- return '';
-}; // fmp4 is not a container
+ return null;
+}
-var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) {
- return (0,_mp4_helpers_js__WEBPACK_IMPORTED_MODULE_1__.findBox)(bytes, ['moof']).length > 0;
-};
+function noop() {}
/***/ }),
-/***/ "./node_modules/@videojs/vhs-utils/es/ebml-helpers.js":
-/*!************************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/ebml-helpers.js ***!
- \************************************************************/
+/***/ "./src/js/components/accordion.js":
+/*!****************************************!*\
+ !*** ./src/js/components/accordion.js ***!
+ \****************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ EBML_TAGS: () => (/* binding */ EBML_TAGS),
-/* harmony export */ decodeBlock: () => (/* binding */ decodeBlock),
-/* harmony export */ findEbml: () => (/* binding */ findEbml),
-/* harmony export */ parseData: () => (/* binding */ parseData),
-/* harmony export */ parseTracks: () => (/* binding */ parseTracks)
+/* harmony export */ toggleAccordion: () => (/* binding */ toggleAccordion)
/* harmony export */ });
-/* harmony import */ var _byte_helpers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./byte-helpers */ "./node_modules/@videojs/vhs-utils/es/byte-helpers.js");
-/* harmony import */ var _codec_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./codec-helpers.js */ "./node_modules/@videojs/vhs-utils/es/codec-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: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x1A, 0x45, 0xDF, 0xA3]),
- DocType: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x42, 0x82]),
- Segment: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x18, 0x53, 0x80, 0x67]),
- SegmentInfo: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x15, 0x49, 0xA9, 0x66]),
- Tracks: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x16, 0x54, 0xAE, 0x6B]),
- Track: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0xAE]),
- TrackNumber: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0xd7]),
- DefaultDuration: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x23, 0xe3, 0x83]),
- TrackEntry: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0xAE]),
- TrackType: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x83]),
- FlagDefault: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x88]),
- CodecID: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x86]),
- CodecPrivate: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x63, 0xA2]),
- VideoTrack: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0xe0]),
- AudioTrack: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.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: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x1F, 0x43, 0xB6, 0x75]),
- Timestamp: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0xE7]),
- TimestampScale: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x2A, 0xD7, 0xB1]),
- BlockGroup: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0xA0]),
- BlockDuration: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x9B]),
- Block: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0xA1]),
- SimpleBlock: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0xA3])
+let items = document.querySelectorAll('.accordion__item');
+let title = document.querySelectorAll('.accordion__title-wrapper');
+const toggleAccordion = () => {
+ title.forEach(question => question.addEventListener('click', function () {
+ let thisItem = this.parentNode;
+ items.forEach(item => {
+ if (thisItem == item) {
+ thisItem.classList.toggle('active');
+ return;
+ }
+ item.classList.remove('active');
+ });
+ }));
};
-/**
- * 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;
+/***/ "./src/js/components/burger-menu.js":
+/*!******************************************!*\
+ !*** ./src/js/components/burger-menu.js ***!
+ \******************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- for (var i = 0; i < LENGTH_TABLE.length; i++) {
- if (byte & LENGTH_TABLE[i]) {
- break;
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ changeMenu: () => (/* binding */ changeMenu)
+/* harmony export */ });
+const burger = document.querySelector('.burger-js');
+const menu = document.querySelector('.menu-js');
+const menuLinks = document.querySelectorAll('.nav__link');
+const changeMenu = () => {
+ burger?.addEventListener('click', () => {
+ menu?.classList.toggle('active');
+ burger?.classList.toggle('active');
+ if (menu?.classList.contains('active')) {
+ document.body.style.overflowY = 'hidden';
+ } else {
+ document.body.style.overflowY = 'auto';
}
-
- 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: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(valueBytes, {
- signed: signed
- }),
- bytes: valueBytes
- };
-};
-
-var normalizePath = function normalizePath(path) {
- if (typeof path === 'string') {
- return path.match(/.{1,2}/g).map(function (p) {
- return normalizePath(p);
+ });
+ menuLinks.forEach(el => {
+ el.addEventListener('click', () => {
+ menu?.classList.remove('active');
+ document.body.style.overflowY = 'auto';
+ burger?.classList.remove('active');
});
- }
-
- if (typeof path === 'number') {
- return (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.numberToBytes)(path);
- }
-
- return path;
-};
-
-var normalizePaths = function normalizePaths(paths) {
- if (!Array.isArray(paths)) {
- return [normalizePath(paths)];
- }
-
- return paths.map(function (p) {
- return normalizePath(p);
});
};
-var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) {
- if (offset >= bytes.length) {
- return bytes.length;
- }
-
- var innerid = getvint(bytes, offset, false);
-
- if ((0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.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 = normalizePaths(paths);
- bytes = (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.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);
+/***/ "./src/js/components/paint-btn.js":
+/*!****************************************!*\
+ !*** ./src/js/components/paint-btn.js ***!
+ \****************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- if ((0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.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);
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ paintBtn: () => (/* binding */ paintBtn)
+/* harmony export */ });
+const btnNext = document.querySelector('.nav-history__next');
+const paintBtn = () => {
+ try {
+ const scrollToTop = () => {
+ const top = window.scrollY;
+ if (top >= 100) {
+ btnNext.classList.add('active');
} else {
- // recursively search for the next tag inside of the data
- // of this one
- results = results.concat(findEbml(data, paths.slice(1)));
+ btnNext.classList.remove('active');
}
- }
-
- var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
-
- i += totalLength;
+ };
+ scrollToTop();
+ window.addEventListener('scroll', () => {
+ scrollToTop();
+ });
+ } catch (e) {
+ console.log(e);
}
+};
- return results;
-}; // see https://www.matroska.org/technical/basics.html#block-structure
+/***/ }),
-var decodeBlock = function decodeBlock(block, type, timestampScale, clusterTimestamp) {
- var duration;
+/***/ "./src/js/components/scroll-smooth.js":
+/*!********************************************!*\
+ !*** ./src/js/components/scroll-smooth.js ***!
+ \********************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- if (type === 'group') {
- duration = findEbml(block, [EBML_TAGS.BlockDuration])[0];
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ addSmoothScroll: () => (/* binding */ addSmoothScroll)
+/* harmony export */ });
+/* harmony import */ var _node_modules_smooth_scroll_dist_smooth_scroll_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../node_modules/smooth-scroll/dist/smooth-scroll.js */ "./node_modules/smooth-scroll/dist/smooth-scroll.js");
+/* harmony import */ var _node_modules_smooth_scroll_dist_smooth_scroll_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_smooth_scroll_dist_smooth_scroll_js__WEBPACK_IMPORTED_MODULE_0__);
- if (duration) {
- duration = (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(duration);
- duration = 1 / timestampScale * duration * timestampScale / 1000;
+const header = document.querySelector('header');
+const btnUpWrapper = document.querySelector('.btn-up-wrapper');
+const addSmoothScroll = () => {
+ const scroll = new (_node_modules_smooth_scroll_dist_smooth_scroll_js__WEBPACK_IMPORTED_MODULE_0___default())('a[href*="#"]', {
+ header: '.header',
+ speed: 500
+ });
+ const scrollToTop = () => {
+ const top = window.scrollY;
+ if (top >= 100) {
+ header.classList.add('active');
+ } else {
+ header.classList.remove('active');
}
+ if (top >= 300) {
+ btnUpWrapper.classList.add('active');
+ } else {
+ btnUpWrapper.classList.remove('active');
+ }
+ };
+ scrollToTop();
+ window.addEventListener('scroll', () => {
+ scrollToTop();
+ });
+};
- 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
+/***/ "./src/js/components/sliders.js":
+/*!**************************************!*\
+ !*** ./src/js/components/sliders.js ***!
+ \**************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- var ptsdts = 1 / timestampScale * (clusterTimestamp + timestamp) * timestampScale / 1000; // return the frame
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ initializationSliders: () => (/* binding */ initializationSliders)
+/* harmony export */ });
+/* harmony import */ var swiper__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! swiper */ "./node_modules/swiper/swiper.esm.js");
- 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;
+swiper__WEBPACK_IMPORTED_MODULE_0__["default"].use([swiper__WEBPACK_IMPORTED_MODULE_0__.Navigation, swiper__WEBPACK_IMPORTED_MODULE_0__.Pagination, swiper__WEBPACK_IMPORTED_MODULE_0__.Autoplay]);
+const initializationSliders = () => {
+ try {
+ const recomendationsSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.recommendations__slider', {
+ slidesPerView: '1',
+ navigation: {
+ nextEl: '.recommendations__slider-navigation-next',
+ prevEl: '.recommendations__slider-navigation-prev'
+ },
+ loop: true
+ });
+ const goodsSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.goods__slider', {
+ slidesPerView: 3,
+ loop: true,
+ navigation: {
+ nextEl: '.goods__slider-next',
+ prevEl: '.goods__slider-prev'
+ },
+ pagination: {
+ clickable: true,
+ el: '.goods__slider-pagination',
+ clickable: true
+ },
+ breakpoints: {
+ 940: {
+ slidesPerView: 4
+ },
+ 764: {
+ slidesPerView: 3
+ },
+ 600: {
+ slidesPerView: 2
+ },
+ 0: {
+ slidesPerView: 1
+ }
+ }
+ });
+ const professionalSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.professional__slider', {
+ navigation: {
+ nextEl: '.professional__slider-navigation-next',
+ prevEl: '.professional__slider-navigation-prev'
+ },
+ paginationClickable: true,
+ // loop: true,
+ initialSlide: 1,
+ centeredSlides: true,
+ slidesPerView: 2,
+ speed: 1000,
+ zoom: {
+ maxRatio: 1.6
+ },
+ pagination: {
+ clickable: true,
+ el: '.professional__slider-pagination',
+ clickable: true
+ },
+ breakpoints: {
+ 993: {
+ spaceBetween: -180
+ },
+ 860: {
+ spaceBetween: -260,
+ slidesPerView: 2
+ },
+ 768: {
+ spaceBetween: -200
+ },
+ 500: {
+ spaceBetween: -80
+ },
+ 320: {
+ slidesPerView: 1.6,
+ spaceBetween: -80
+ }
+ },
+ allowTouchMove: false
+ });
+ const interviewSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.interview__slider', {
+ navigation: {
+ nextEl: '.interview__slider-navigation-next',
+ prevEl: '.interview__slider-navigation-prev'
+ },
+ pagination: {
+ el: '.interview__slider-pagination',
+ clickable: true
+ },
+ allowTouchMove: false,
+ paginationClickable: true,
+ centeredSlides: true,
+ loop: true,
+ speed: 1000
+ });
+ const partnersSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.partners__wrapper', {
+ freeMode: true,
+ grabCursor: true,
+ centeredSlides: true,
+ slidesPerView: 'auto',
+ loop: true,
+ speed: 2000,
+ autoplay: {
+ enabled: true,
+ delay: 1,
+ disableOnInteraction: true
+ },
+ freeModeMomentum: true
+ });
+ document.querySelector('.partners__wrapper').addEventListener('mouseover', function () {
+ partnersSlider.autoplay.stop();
+ });
+ document.querySelector('.partners__wrapper').addEventListener('mouseout', function () {
+ partnersSlider.autoplay.start();
+ });
+ window.addEventListener('scroll', () => {
+ if (!partnersSlider.autoplay.running) {
+ partnersSlider.autoplay.start();
+ }
+ });
+ } catch (e) {
+ console.error(e);
}
+};
- var numberOfFrames = data[0] + 1;
- var frameSizes = [];
- var offset = 1; // Fixed
+/***/ }),
- if (parsed.lacing === 2) {
- var sizeOfFrame = (data.length - offset) / numberOfFrames;
+/***/ "./src/js/components/video-player.js":
+/*!*******************************************!*\
+ !*** ./src/js/components/video-player.js ***!
+ \*******************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- for (var i = 0; i < numberOfFrames; i++) {
- frameSizes.push(sizeOfFrame);
- }
- } // xiph
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ addVideoPlayer: () => (/* binding */ addVideoPlayer)
+/* harmony export */ });
+/* harmony import */ var video_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! video.js */ "./node_modules/video.js/dist/video.es.js");
+const playBtn = document.querySelector('.video__btn');
+const videoTitle = document.querySelector('.video__title');
+const videoText = document.querySelector('.video__text');
+const videoLink = document.querySelector('.video__link');
+const videoGradient = document.querySelector('.video__gradient');
+const videoIntro = document.querySelector('.video__intro');
+const videoMask = document.querySelector('.video__mask');
+const videoJsTech = document.querySelector('.vjs-tech');
+const addVideoPlayer = () => {
+ try {
+ let isPreviewVideo = true;
+ const videoPlayer = (0,video_js__WEBPACK_IMPORTED_MODULE_0__["default"])(document.getElementById('video__player'), {
+ autoplay: true,
+ loop: true,
+ muted: true,
+ controls: false,
+ playToggle: false
+ });
+ const loadMainVideo = (desktopUrl, mobileUrl) => {
+ if (window.screen.width > 768) {
+ videoPlayer.src({
+ type: 'video/mp4',
+ src: `./videos/${desktopUrl}`
+ });
+ } else {
+ videoPlayer.src({
+ type: 'video/mp4',
+ src: `./videos/${mobileUrl}`
+ });
+ }
+ };
+ loadMainVideo('preview.mp4', 'preview-mobile.mp4');
+ videoPlayer.volume(0.7);
+ const changePlayerStatus = () => {
+ videoPlayer.play();
+ videoPlayer.addClass('play');
+ videoPlayer.muted(false);
+ videoPlayer.controls(true);
+ videoPlayer.loop(false);
+ playBtn.classList.add('hide');
+ videoTitle.classList.add('hide');
+ videoText.classList.add('hide');
+ videoLink.classList.add('hide');
+ };
+ videoPlayer.on('play', () => {
+ if (!videoPlayer.played()) {
+ videoPlayer.addClass('play');
+ videoPlayer.removeClass('play');
+ playBtn?.classList.add('hide');
+ } else if (videoPlayer.played() && videoPlayer.loop()) {
+ videoPlayer.removeClass('play');
+ } else if (videoPlayer.played() && !videoPlayer.loop()) {
+ videoPlayer.removeClass('play');
+ playBtn?.classList.add('hide');
+ }
+ });
+ videoPlayer.on('pause', () => {
+ playBtn?.classList.remove('hide');
+ });
+ videoPlayer.on('ended', () => {
+ playBtn?.classList.remove('hide');
+ videoMask?.classList.remove('visible');
+ });
+ videoMask.addEventListener('click', () => {
+ playBtn?.classList.remove('hide');
+ videoPlayer.pause();
+ videoMask?.classList.remove('visible');
+ });
+ playBtn.addEventListener('click', () => {
+ if (isPreviewVideo === true) {
+ loadMainVideo('video.mp4', 'video-mobile.mp4');
+ isPreviewVideo = false;
+ }
+ document.querySelector('.video__inner').classList.add('active');
+ document.querySelector('.video-js').classList.add('active');
+ document.querySelector('.vjs-poster').classList.add('active');
+ videoPlayer.fluid(true);
+ videoGradient?.classList.add('hide');
+ videoMask?.classList.add('visible');
+ playBtn?.classList.add('center-position');
+ document.querySelector('video').style.objectFit = 'contain';
+ if (videoPlayer.muted()) {
+ videoPlayer.currentTime(0);
+ changePlayerStatus();
+ } else {
+ changePlayerStatus();
+ }
+ });
+ } catch (e) {
+ console.log(e);
+ }
+};
- 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);
+/***/ "./node_modules/global/document.js":
+/*!*****************************************!*\
+ !*** ./node_modules/global/document.js ***!
+ \*****************************************/
+/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
- frameSizes.push(size);
- }
- } // ebml
+var topLevel = typeof __webpack_require__.g !== 'undefined' ? __webpack_require__.g :
+ typeof window !== 'undefined' ? window : {}
+var minDoc = __webpack_require__(/*! min-document */ "?34aa");
+var doccy;
- if (parsed.lacing === 3) {
- // first vint is unsinged
- // after that vints are singed and
- // based on a compounding size
- var _size = 0;
+if (typeof document !== 'undefined') {
+ doccy = document;
+} else {
+ doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
- 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;
+ if (!doccy) {
+ doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
}
- }
+}
- 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/
+module.exports = doccy;
-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);
- }
+/***/ "./node_modules/global/window.js":
+/*!***************************************!*\
+ !*** ./node_modules/global/window.js ***!
+ \***************************************/
+/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
- 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;
- }
+var win;
- i += 2 + len;
- }
+if (typeof window !== "undefined") {
+ win = window;
+} else if (typeof __webpack_require__.g !== "undefined") {
+ win = __webpack_require__.g;
+} else if (typeof self !== "undefined"){
+ win = self;
+} else {
+ win = {};
+}
- return params;
-};
+module.exports = win;
-var parseTracks = function parseTracks(bytes) {
- bytes = (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.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]);
- }
+/***/ "./node_modules/is-function/index.js":
+/*!*******************************************!*\
+ !*** ./node_modules/is-function/index.js ***!
+ \*******************************************/
+/***/ ((module) => {
- if (!tracks.length) {
- return decodedTracks;
+module.exports = isFunction
+
+var toString = Object.prototype.toString
+
+function isFunction (fn) {
+ if (!fn) {
+ return false
}
+ var string = toString.call(fn)
+ return string === '[object Function]' ||
+ (typeof fn === 'function' && string !== '[object RegExp]') ||
+ (typeof window !== 'undefined' &&
+ // IE8 and below
+ (fn === window.setTimeout ||
+ fn === window.alert ||
+ fn === window.confirm ||
+ fn === window.prompt))
+};
- 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
+/***/ }),
+/***/ "./node_modules/keycode/index.js":
+/*!***************************************!*\
+ !*** ./node_modules/keycode/index.js ***!
+ \***************************************/
+/***/ ((module, exports) => {
- 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
+// Source: http://jsfiddle.net/vWx8V/
+// http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
+/**
+ * Conenience method returns corresponding value for given keyName or keyCode.
+ *
+ * @param {Mixed} keyCode {Number} or keyName {String}
+ * @return {Mixed}
+ * @api public
+ */
- var decodedTrack = {
- rawCodec: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.bytesToString)(findEbml(track, [EBML_TAGS.CodecID])[0]),
- type: trackType,
- codecPrivate: findEbml(track, [EBML_TAGS.CodecPrivate])[0],
- number: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(findEbml(track, [EBML_TAGS.TrackNumber])[0]),
- defaultDuration: (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(findEbml(track, [EBML_TAGS.DefaultDuration])[0]),
- default: findEbml(track, [EBML_TAGS.FlagDefault])[0],
- rawData: track
- };
- var codec = '';
+function keyCode(searchInput) {
+ // Keyboard Events
+ if (searchInput && 'object' === typeof searchInput) {
+ var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode
+ if (hasKeyCode) searchInput = hasKeyCode
+ }
- if (/V_MPEG4\/ISO\/AVC/.test(decodedTrack.rawCodec)) {
- codec = "avc1." + (0,_codec_helpers_js__WEBPACK_IMPORTED_MODULE_1__.getAvcCodec)(decodedTrack.codecPrivate);
- } else if (/V_MPEGH\/ISO\/HEVC/.test(decodedTrack.rawCodec)) {
- codec = "hev1." + (0,_codec_helpers_js__WEBPACK_IMPORTED_MODULE_1__.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;
+ // Numbers
+ if ('number' === typeof searchInput) return names[searchInput]
- codec = 'vp09.';
- codec += (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.padStart)(profile, 2, '0') + ".";
- codec += (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.padStart)(level, 2, '0') + ".";
- codec += (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.padStart)(bitDepth, 2, '0') + ".";
- codec += "" + (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.padStart)(chromaSubsampling, 2, '0'); // Video -> Colour -> Ebml name
+ // Everything else (cast to string)
+ var search = String(searchInput)
- 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.
+ // check codes
+ var foundNamedKey = codes[search.toLowerCase()]
+ if (foundNamedKey) return foundNamedKey
- if (matrixCoefficients.length || videoFullRangeFlag.length || transferCharacteristics.length || colourPrimaries.length) {
- codec += "." + (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.padStart)(colourPrimaries[0], 2, '0');
- codec += "." + (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.padStart)(transferCharacteristics[0], 2, '0');
- codec += "." + (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.padStart)(matrixCoefficients[0], 2, '0');
- codec += "." + (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.padStart)(videoFullRangeFlag[0], 2, '0');
- }
- } else {
- codec = 'vp9';
- }
- } else if (/^V_AV1/.test(decodedTrack.rawCodec)) {
- codec = "av01." + (0,_codec_helpers_js__WEBPACK_IMPORTED_MODULE_1__.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';
- }
+ // check aliases
+ var foundNamedKey = aliases[search.toLowerCase()]
+ if (foundNamedKey) return foundNamedKey
- 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
+ // weird character?
+ if (search.length === 1) return search.charCodeAt(0)
- if (timestampScale && timestampScale.length) {
- timestampScale = (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(timestampScale);
- } else {
- timestampScale = 1000000;
- }
-
- var clusters = findEbml(segment, [EBML_TAGS.Cluster]);
+ return undefined
+}
- if (!tracks) {
- tracks = parseTracks(segment);
+/**
+ * Compares a keyboard event with a given keyCode or keyName.
+ *
+ * @param {Event} event Keyboard event that should be tested
+ * @param {Mixed} keyCode {Number} or keyName {String}
+ * @return {Boolean}
+ * @api public
+ */
+keyCode.isEventKey = function isEventKey(event, nameOrCode) {
+ if (event && 'object' === typeof event) {
+ var keyCode = event.which || event.keyCode || event.charCode
+ if (keyCode === null || keyCode === undefined) { return false; }
+ if (typeof nameOrCode === 'string') {
+ // check codes
+ var foundNamedKey = codes[nameOrCode.toLowerCase()]
+ if (foundNamedKey) { return foundNamedKey === keyCode; }
+
+ // check aliases
+ var foundNamedKey = aliases[nameOrCode.toLowerCase()]
+ if (foundNamedKey) { return foundNamedKey === keyCode; }
+ } else if (typeof nameOrCode === 'number') {
+ return nameOrCode === keyCode;
+ }
+ return false;
}
+}
- 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;
+exports = module.exports = keyCode;
- if (timestamp && timestamp.length) {
- timestamp = (0,_byte_helpers__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(timestamp);
- } // get all blocks then sort them into the correct order
+/**
+ * Get by name
+ *
+ * exports.code['enter'] // => 13
+ */
+var codes = exports.code = exports.codes = {
+ 'backspace': 8,
+ 'tab': 9,
+ 'enter': 13,
+ 'shift': 16,
+ 'ctrl': 17,
+ 'alt': 18,
+ 'pause/break': 19,
+ 'caps lock': 20,
+ 'esc': 27,
+ 'space': 32,
+ 'page up': 33,
+ 'page down': 34,
+ 'end': 35,
+ 'home': 36,
+ 'left': 37,
+ 'up': 38,
+ 'right': 39,
+ 'down': 40,
+ 'insert': 45,
+ 'delete': 46,
+ 'command': 91,
+ 'left command': 91,
+ 'right command': 93,
+ 'numpad *': 106,
+ 'numpad +': 107,
+ 'numpad -': 109,
+ 'numpad .': 110,
+ 'numpad /': 111,
+ 'num lock': 144,
+ 'scroll lock': 145,
+ 'my computer': 182,
+ 'my calculator': 183,
+ ';': 186,
+ '=': 187,
+ ',': 188,
+ '-': 189,
+ '.': 190,
+ '/': 191,
+ '`': 192,
+ '[': 219,
+ '\\': 220,
+ ']': 221,
+ "'": 222
+}
- 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
- };
-};
+// Helper aliases
-/***/ }),
+var aliases = exports.aliases = {
+ 'windows': 91,
+ '⇧': 16,
+ '⌥': 18,
+ '⌃': 17,
+ '⌘': 91,
+ 'ctl': 17,
+ 'control': 17,
+ 'option': 18,
+ 'pause': 19,
+ 'break': 19,
+ 'caps': 20,
+ 'return': 13,
+ 'escape': 27,
+ 'spc': 32,
+ 'spacebar': 32,
+ 'pgup': 33,
+ 'pgdn': 34,
+ 'ins': 45,
+ 'del': 46,
+ 'cmd': 91
+}
-/***/ "./node_modules/@videojs/vhs-utils/es/id3-helpers.js":
-/*!***********************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/id3-helpers.js ***!
- \***********************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+/*!
+ * Programatically add the following
+ */
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ getId3Offset: () => (/* binding */ getId3Offset),
-/* harmony export */ getId3Size: () => (/* binding */ getId3Size)
-/* harmony export */ });
-/* harmony import */ var _byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./byte-helpers.js */ "./node_modules/@videojs/vhs-utils/es/byte-helpers.js");
+// lower case chars
+for (i = 97; i < 123; i++) codes[String.fromCharCode(i)] = i - 32
-var ID3 = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x49, 0x44, 0x33]);
-var getId3Size = function getId3Size(bytes, offset) {
- if (offset === void 0) {
- offset = 0;
- }
+// numbers
+for (var i = 48; i < 58; i++) codes[i - 48] = i
- bytes = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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;
+// function keys
+for (i = 1; i < 13; i++) codes['f'+i] = i + 111
- if (footerPresent) {
- return returnSize + 20;
- }
+// numpad keys
+for (i = 0; i < 10; i++) codes['numpad '+i] = i + 96
- return returnSize + 10;
-};
-var getId3Offset = function getId3Offset(bytes, offset) {
- if (offset === void 0) {
- offset = 0;
- }
+/**
+ * Get by code
+ *
+ * exports.name[13] // => 'Enter'
+ */
- bytes = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)(bytes);
+var names = exports.names = exports.title = {} // title for backward compat
- if (bytes.length - offset < 10 || !(0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes, ID3, {
- offset: offset
- })) {
- return offset;
- }
+// Create reverse mapping
+for (i in codes) names[codes[i]] = i
- offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files
- // have multiple ID3 tag sections even though
- // they should not.
+// Add aliases
+for (var alias in aliases) {
+ codes[alias] = aliases[alias]
+}
- return getId3Offset(bytes, offset);
-};
/***/ }),
-/***/ "./node_modules/@videojs/vhs-utils/es/media-types.js":
-/*!***********************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/media-types.js ***!
- \***********************************************************/
+/***/ "./node_modules/m3u8-parser/dist/m3u8-parser.es.js":
+/*!*********************************************************!*\
+ !*** ./node_modules/m3u8-parser/dist/m3u8-parser.es.js ***!
+ \*********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ simpleTypeFromSourceType: () => (/* binding */ simpleTypeFromSourceType)
+/* harmony export */ LineStream: () => (/* binding */ LineStream),
+/* harmony export */ ParseStream: () => (/* binding */ ParseStream),
+/* harmony export */ Parser: () => (/* binding */ Parser)
/* harmony export */ });
-var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
-var DASH_REGEX = /^application\/dash\+xml/i;
+/* harmony import */ var _videojs_vhs_utils_es_stream_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @videojs/vhs-utils/es/stream.js */ "./node_modules/@videojs/vhs-utils/es/stream.js");
+/* harmony import */ var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/extends */ "./node_modules/@babel/runtime/helpers/esm/extends.js");
+/* harmony import */ var _videojs_vhs_utils_es_decode_b64_to_uint8_array_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @videojs/vhs-utils/es/decode-b64-to-uint8-array.js */ "./node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js");
+/*! @name m3u8-parser @version 6.2.0 @license Apache-2.0 */
+
+
+
+
/**
- * 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}
+ * @file m3u8/line-stream.js
+ */
+/**
+ * A stream that buffers string input and generates a `data` event for each
+ * line.
*
- * @param {string} type
- * Video source object media type
- * @return {('hls'|'dash'|'vhs-json'|null)}
- * VHS source type string
+ * @class LineStream
+ * @extends Stream
*/
-var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
- if (MPEGURL_REGEX.test(type)) {
- return 'hls';
+class LineStream extends _videojs_vhs_utils_es_stream_js__WEBPACK_IMPORTED_MODULE_0__["default"] {
+ constructor() {
+ super();
+ this.buffer = '';
}
+ /**
+ * Add new data to be parsed.
+ *
+ * @param {string} data the text to process
+ */
- 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.
+ push(data) {
+ let nextNewline;
+ this.buffer += data;
+ nextNewline = this.buffer.indexOf('\n');
- if (type === 'application/vnd.videojs.vhs+json') {
- return 'vhs-json';
+ for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
+ this.trigger('data', this.buffer.substring(0, nextNewline));
+ this.buffer = this.buffer.substring(nextNewline + 1);
+ }
}
- return null;
-};
-
-/***/ }),
-
-/***/ "./node_modules/@videojs/vhs-utils/es/mp4-helpers.js":
-/*!***********************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/mp4-helpers.js ***!
- \***********************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ addSampleDescription: () => (/* binding */ addSampleDescription),
-/* harmony export */ buildFrameTable: () => (/* binding */ buildFrameTable),
-/* harmony export */ findBox: () => (/* binding */ findBox),
-/* harmony export */ findNamedBox: () => (/* binding */ findNamedBox),
-/* harmony export */ parseDescriptors: () => (/* binding */ parseDescriptors),
-/* harmony export */ parseMediaInfo: () => (/* binding */ parseMediaInfo),
-/* harmony export */ parseTracks: () => (/* binding */ parseTracks)
-/* harmony export */ });
-/* harmony import */ var _byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./byte-helpers.js */ "./node_modules/@videojs/vhs-utils/es/byte-helpers.js");
-/* harmony import */ var _codec_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./codec-helpers.js */ "./node_modules/@videojs/vhs-utils/es/codec-helpers.js");
-/* harmony import */ var _opus_helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./opus-helpers.js */ "./node_modules/@videojs/vhs-utils/es/opus-helpers.js");
-
+}
+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 = {};
-var normalizePath = function normalizePath(path) {
- if (typeof path === 'string') {
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.stringToBytes)(path);
+ if (match[1]) {
+ result.length = parseInt(match[1], 10);
}
- if (typeof path === 'number') {
- return path;
+ if (match[2]) {
+ result.offset = parseInt(match[2], 10);
}
- return path;
+ return result;
};
+/**
+ * "forgiving" attribute list psuedo-grammar:
+ * attributes -> keyvalue (',' keyvalue)*
+ * keyvalue -> key '=' value
+ * key -> [^=]*
+ * value -> '"' [^"]* '"' | [^,]*
+ */
-var normalizePaths = function normalizePaths(paths) {
- if (!Array.isArray(paths)) {
- return [normalizePath(paths)];
- }
- return paths.map(function (p) {
- return normalizePath(p);
- });
+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
+ */
-var DESCRIPTORS;
-var parseDescriptors = function parseDescriptors(bytes) {
- bytes = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)(bytes);
- var results = [];
- var i = 0;
- while (bytes.length > i) {
- var tag = bytes[i];
- var size = 0;
- var headerSize = 0; // tag
+const parseAttributes = function (attributes) {
+ const result = {};
- headerSize++;
- var byte = bytes[headerSize]; // first byte
+ if (!attributes) {
+ return result;
+ } // split the string using attributes as the separator
- headerSize++;
- while (byte & 0x80) {
- size = (byte & 0x7F) << 7;
- byte = bytes[headerSize];
- headerSize++;
- }
+ const attrs = attributes.split(attributeSeparator());
+ let i = attrs.length;
+ let attr;
- size += byte & 0x7F;
+ while (i--) {
+ // filter out unmatched portions of the string
+ if (attrs[i] === '') {
+ continue;
+ } // split the key and value
- 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;
- }
- }
+ attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
- i += size + headerSize;
+ 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 results;
+ return result;
};
-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 = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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
+ * 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.
*
- * @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.
+ * 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 `#`.
*
- * @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.
+ * 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.
*
- * @return {Uint8Array[]}
- * An array of the end paths that we found.
+ * @class ParseStream
+ * @extends Stream
*/
-var findBox = function findBox(bytes, paths, complete) {
- if (complete === void 0) {
- complete = false;
- }
-
- paths = normalizePaths(paths);
- bytes = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)(bytes);
- var results = [];
- if (!paths.length) {
- // short-circuit the search for empty paths
- return results;
+class ParseStream extends _videojs_vhs_utils_es_stream_js__WEBPACK_IMPORTED_MODULE_0__["default"] {
+ constructor() {
+ super();
+ this.customParsers = [];
+ this.tagMappers = [];
}
+ /**
+ * Parses an additional line of input.
+ *
+ * @param {string} line a single line of an M3U8 file to parse
+ */
- 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.
+ push(line) {
+ let match;
+ let event; // strip whitespace
- if (size === 0) {
- break;
- }
+ line = line.trim();
- 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;
- }
+ if (line.length === 0) {
+ // ignore empty lines
+ return;
+ } // URIs
- end = bytes.length;
- }
- var data = bytes.subarray(i + 8, end);
+ if (line[0] !== '#') {
+ this.trigger('data', {
+ type: 'uri',
+ uri: line
+ });
+ return;
+ } // map tags
- if ((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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
+ const newLines = this.tagMappers.reduce((acc, mapper) => {
+ const mappedLine = mapper(line); // skip if unchanged
+ if (mappedLine === line) {
+ return acc;
+ }
- 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.
- */
+ 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
-var findNamedBox = function findNamedBox(bytes, name) {
- name = normalizePath(name);
- if (!name.length) {
- // short-circuit the search for empty paths
- return bytes.subarray(bytes.length);
- }
+ 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.
- var i = 0;
- while (i < bytes.length) {
- if ((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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);
- }
+ newLine = newLine.replace('\r', ''); // Tags
- i++;
- } // we've finished searching all of bytes
+ match = /^#EXTM3U/.exec(newLine);
+ if (match) {
+ this.trigger('data', {
+ type: 'tag',
+ tagType: 'm3u'
+ });
+ return;
+ }
- return bytes.subarray(bytes.length);
-};
+ match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine);
-var parseSamples = function parseSamples(data, entrySize, parseEntry) {
- if (entrySize === void 0) {
- entrySize = 4;
- }
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'inf'
+ };
- if (parseEntry === void 0) {
- parseEntry = function parseEntry(d) {
- return (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(d);
- };
- }
+ if (match[1]) {
+ event.duration = parseFloat(match[1]);
+ }
- var entries = [];
+ if (match[2]) {
+ event.title = match[2];
+ }
- if (!data || !data.length) {
- return entries;
- }
+ this.trigger('data', event);
+ return;
+ }
- var entryCount = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(data.subarray(4, 8));
+ match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
- for (var i = 8; entryCount; i += entrySize, entryCount--) {
- entries.push(parseEntry(data.subarray(i, i + entrySize)));
- }
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'targetduration'
+ };
- return entries;
-};
+ if (match[1]) {
+ event.duration = parseInt(match[1], 10);
+ }
-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: (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(entry.subarray(0, 4)),
- sampleDelta: (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(entry.subarray(4, 8))
- };
- });
- var samplesToChunks = parseSamples(findBox(stbl, ['stsc'])[0], 12, function (entry) {
- return {
- firstChunk: (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(entry.subarray(0, 4)),
- samplesPerChunk: (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(entry.subarray(4, 8)),
- sampleDescriptionIndex: (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(entry.subarray(8, 12))
- };
- });
- var stsz = findBox(stbl, ['stsz'])[0]; // stsz starts with a 4 byte sampleSize which we don't need
+ this.trigger('data', event);
+ return;
+ }
- var sampleSizes = parseSamples(stsz && stsz.length && stsz.subarray(4) || null);
- var frames = [];
+ match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
- for (var chunkIndex = 0; chunkIndex < chunkOffsets.length; chunkIndex++) {
- var samplesInChunk = void 0;
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'version'
+ };
- 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 (match[1]) {
+ event.version = parseInt(match[1], 10);
+ }
- if (isThisOne) {
- samplesInChunk = sampleToChunk.samplesPerChunk;
- break;
+ this.trigger('data', event);
+ return;
}
- }
- var chunkOffset = chunkOffsets[chunkIndex];
+ match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
- for (var _i = 0; _i < samplesInChunk; _i++) {
- var frameEnd = sampleSizes[frames.length]; // if we don't have key samples every frame is a keyframe
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'media-sequence'
+ };
- var keyframe = !keySamples.length;
+ if (match[1]) {
+ event.number = parseInt(match[1], 10);
+ }
- if (keySamples.length && keySamples.indexOf(frames.length + 1) !== -1) {
- keyframe = true;
+ this.trigger('data', event);
+ return;
}
- var frame = {
- keyframe: keyframe,
- start: chunkOffset,
- end: chunkOffset + frameEnd
- };
+ match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
- for (var k = 0; k < timeToSamples.length; k++) {
- var _timeToSamples$k = timeToSamples[k],
- sampleCount = _timeToSamples$k.sampleCount,
- sampleDelta = _timeToSamples$k.sampleDelta;
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'discontinuity-sequence'
+ };
- 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;
+ if (match[1]) {
+ event.number = parseInt(match[1], 10);
}
+
+ this.trigger('data', event);
+ return;
}
- frames.push(frame);
- chunkOffset += frameEnd;
- }
- }
+ match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
- return frames;
-};
-var addSampleDescription = function addSampleDescription(track, bytes) {
- var codec = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToString)(bytes.subarray(0, 4));
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'playlist-type'
+ };
- 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 (match[1]) {
+ event.playlistType = match[1];
+ }
- if (codec === 'avc1') {
- var avcC = findNamedBox(bytes, 'avcC'); // AVCDecoderConfigurationRecord
+ this.trigger('data', event);
+ return;
+ }
- codec += "." + (0,_codec_helpers_js__WEBPACK_IMPORTED_MODULE_1__.getAvcCodec)(avcC);
- track.info.avcC = avcC; // TODO: do we need to parse all this?
+ match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
- /* {
- 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 += "." + (0,_codec_helpers_js__WEBPACK_IMPORTED_MODULE_1__.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 (match) {
+ event = (0,_babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_1__["default"])(parseByterange(match[1]), {
+ type: 'tag',
+ tagType: 'byterange'
+ });
+ this.trigger('data', event);
+ return;
+ }
- if (decoderConfig) {
- // most codecs do not have a further '.'
- // such as 0xa5 for ac-3 and 0xa6 for e-ac-3
- codec += '.' + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toHexString)(decoderConfig.oti);
+ match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
- 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';
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'allow-cache'
+ };
+
+ if (match[1]) {
+ event.allowed = !/NO/.test(match[1]);
+ }
+
+ this.trigger('data', event);
+ return;
}
- } else if (track.type === 'audio') {
- codec += '.40.2';
- } else {
- codec += '.20.9';
- }
- } else if (codec === 'av01') {
- // AV1DecoderConfigurationRecord
- codec += "." + (0,_codec_helpers_js__WEBPACK_IMPORTED_MODULE_1__.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 += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(profile, 2, '0');
- codec += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(level, 2, '0');
- codec += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(bitDepth, 2, '0');
- codec += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(chromaSubsampling, 2, '0');
- codec += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(colourPrimaries, 2, '0');
- codec += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(transferCharacteristics, 2, '0');
- codec += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.padStart)(matrixCoefficients, 2, '0');
- codec += "." + (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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 = (0,_opus_helpers_js__WEBPACK_IMPORTED_MODULE_2__.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
+ match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
- track.info.codecDelay = 6500000;
- } else {
- codec = codec.toLowerCase();
- }
- /* eslint-enable */
- // flac, ac-3, ec-3, opus
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'map'
+ };
+ if (match[1]) {
+ const attributes = parseAttributes(match[1]);
- track.codec = codec;
-};
-var parseTracks = function parseTracks(bytes, frameTable) {
- if (frameTable === void 0) {
- frameTable = true;
- }
+ if (attributes.URI) {
+ event.uri = attributes.URI;
+ }
- bytes = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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 = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToString)(hdlr.subarray(8, 12));
+ if (attributes.BYTERANGE) {
+ event.byterange = parseByterange(attributes.BYTERANGE);
+ }
+ }
- if (trakType === 'soun') {
- track.type = 'audio';
- } else if (trakType === 'vide') {
- track.type = 'video';
- } else {
- track.type = trakType;
- }
+ this.trigger('data', event);
+ return;
+ }
- var tkhd = findBox(trak, ['tkhd'])[0];
+ match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
- 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);
- }
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'stream-inf'
+ };
- var mdhd = findBox(mdia, ['mdhd'])[0];
+ if (match[1]) {
+ event.attributes = parseAttributes(match[1]);
- 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;
- }
+ if (event.attributes.RESOLUTION) {
+ const split = event.attributes.RESOLUTION.split('x');
+ const resolution = {};
- var stbl = findBox(mdia, ['minf', 'stbl'])[0];
- var stsd = findBox(stbl, ['stsd'])[0];
- var descriptionCount = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(stsd.subarray(4, 8));
- var offset = 8; // add codec and codec info
+ if (split[0]) {
+ resolution.width = parseInt(split[0], 10);
+ }
- while (descriptionCount--) {
- var len = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(stsd.subarray(offset, offset + 4));
- var sampleDescriptor = stsd.subarray(offset + 4, offset + 4 + len);
- addSampleDescription(track, sampleDescriptor);
- offset += 4 + len;
- }
+ if (split[1]) {
+ resolution.height = parseInt(split[1], 10);
+ }
- if (frameTable) {
- track.frameTable = buildFrameTable(stbl, track.timescale);
- } // codec has no sub parameters
+ event.attributes.RESOLUTION = resolution;
+ }
+ if (event.attributes.BANDWIDTH) {
+ event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
+ }
- tracks.push(track);
- });
- return tracks;
-};
-var parseMediaInfo = function parseMediaInfo(bytes) {
- var mvhd = findBox(bytes, ['moov', 'mvhd'], true)[0];
+ if (event.attributes['FRAME-RATE']) {
+ event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
+ }
- if (!mvhd || !mvhd.length) {
- return;
- }
+ if (event.attributes['PROGRAM-ID']) {
+ event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
+ }
+ }
- var info = {}; // ms to ns
- // mvhd v1 has 8 byte duration and other fields too
+ this.trigger('data', event);
+ return;
+ }
- if (mvhd[0] === 1) {
- info.timestampScale = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(mvhd.subarray(20, 24));
- info.duration = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(mvhd.subarray(24, 32));
- } else {
- info.timestampScale = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(mvhd.subarray(12, 16));
- info.duration = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesToNumber)(mvhd.subarray(16, 20));
- }
+ match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
- info.bytes = mvhd;
- return info;
-};
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'media'
+ };
-/***/ }),
+ if (match[1]) {
+ event.attributes = parseAttributes(match[1]);
+ }
-/***/ "./node_modules/@videojs/vhs-utils/es/nal-helpers.js":
-/*!***********************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/nal-helpers.js ***!
- \***********************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ this.trigger('data', event);
+ return;
+ }
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ EMULATION_PREVENTION: () => (/* binding */ EMULATION_PREVENTION),
-/* harmony export */ NAL_TYPE_ONE: () => (/* binding */ NAL_TYPE_ONE),
-/* harmony export */ NAL_TYPE_TWO: () => (/* binding */ NAL_TYPE_TWO),
-/* harmony export */ discardEmulationPreventionBytes: () => (/* binding */ discardEmulationPreventionBytes),
-/* harmony export */ findH264Nal: () => (/* binding */ findH264Nal),
-/* harmony export */ findH265Nal: () => (/* binding */ findH265Nal),
-/* harmony export */ findNal: () => (/* binding */ findNal)
-/* harmony export */ });
-/* harmony import */ var _byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./byte-helpers.js */ "./node_modules/@videojs/vhs-utils/es/byte-helpers.js");
-
-var NAL_TYPE_ONE = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x00, 0x00, 0x00, 0x01]);
-var NAL_TYPE_TWO = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toUint8)([0x00, 0x00, 0x01]);
-var EMULATION_PREVENTION = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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`
+ match = /^#EXT-X-ENDLIST/.exec(newLine);
- while (i < bytes.length - 2) {
- if ((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) {
- positions.push(i + 2);
- i++;
- }
+ if (match) {
+ this.trigger('data', {
+ type: 'tag',
+ tagType: 'endlist'
+ });
+ return;
+ }
- i++;
- } // If no Emulation Prevention Bytes were found just return the original
- // array
+ match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
+ if (match) {
+ this.trigger('data', {
+ type: 'tag',
+ tagType: 'discontinuity'
+ });
+ return;
+ }
- if (positions.length === 0) {
- return bytes;
- } // Create a new array to hold the NAL unit data
+ match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'program-date-time'
+ };
- var newLength = bytes.length - positions.length;
- var newData = new Uint8Array(newLength);
- var sourceIndex = 0;
+ if (match[1]) {
+ event.dateTimeString = match[1];
+ event.dateTimeObject = new Date(match[1]);
+ }
- for (i = 0; i < newLength; sourceIndex++, i++) {
- if (sourceIndex === positions[0]) {
- // Skip this byte
- sourceIndex++; // Remove this position index
+ this.trigger('data', event);
+ return;
+ }
- positions.shift();
- }
+ match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
- newData[i] = bytes[sourceIndex];
- }
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'key'
+ };
- return newData;
-};
-var findNal = function findNal(bytes, dataType, types, nalLimit) {
- if (nalLimit === void 0) {
- nalLimit = Infinity;
- }
+ if (match[1]) {
+ event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
- bytes = (0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.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.
+ if (event.attributes.IV) {
+ if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
+ event.attributes.IV = event.attributes.IV.substring(2);
+ }
- while (i < bytes.length && (nalsFound < nalLimit || nalStart)) {
- var nalOffset = void 0;
+ 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);
+ }
+ }
- if ((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes.subarray(i), NAL_TYPE_ONE)) {
- nalOffset = 4;
- } else if ((0,_byte_helpers_js__WEBPACK_IMPORTED_MODULE_0__.bytesMatch)(bytes.subarray(i), NAL_TYPE_TWO)) {
- nalOffset = 3;
- } // we are unsynced,
- // find the next nal unit
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-START:(.*)$/.exec(newLine);
- if (!nalOffset) {
- i++;
- continue;
- }
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'start'
+ };
- nalsFound++;
+ 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);
+ }
- if (nalStart) {
- return discardEmulationPreventionBytes(bytes.subarray(nalStart, i));
- }
+ this.trigger('data', event);
+ return;
+ }
- var nalType = void 0;
+ match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
- if (dataType === 'h264') {
- nalType = bytes[i + nalOffset] & 0x1f;
- } else if (dataType === 'h265') {
- nalType = bytes[i + nalOffset] >> 1 & 0x3f;
- }
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'cue-out-cont'
+ };
- if (types.indexOf(nalType) !== -1) {
- nalStart = i + nalOffset;
- } // nal header is 1 length for h264, and 2 for h265
+ if (match[1]) {
+ event.data = match[1];
+ } else {
+ event.data = '';
+ }
+ this.trigger('data', event);
+ return;
+ }
- i += nalOffset + (dataType === 'h264' ? 1 : 2);
- }
+ match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
- 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);
-};
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'cue-out'
+ };
-/***/ }),
+ if (match[1]) {
+ event.data = match[1];
+ } else {
+ event.data = '';
+ }
-/***/ "./node_modules/@videojs/vhs-utils/es/opus-helpers.js":
-/*!************************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/opus-helpers.js ***!
- \************************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ this.trigger('data', event);
+ return;
+ }
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ OPUS_HEAD: () => (/* binding */ OPUS_HEAD),
-/* harmony export */ parseOpusHead: () => (/* binding */ parseOpusHead),
-/* harmony export */ setOpusHead: () => (/* binding */ setOpusHead)
-/* harmony export */ });
-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
+ match = /^#EXT-X-CUE-IN:(.*)?$/.exec(newLine);
-var 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.
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'cue-in'
+ };
- 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 (match[1]) {
+ event.data = match[1];
+ } else {
+ event.data = '';
+ }
- if (config.channelMappingFamily > 0 && bytes.length > 10) {
- config.streamCount = view.getUint8(11);
- config.twoChannelStreamCount = view.getUint8(12);
- config.channelMapping = [];
+ this.trigger('data', event);
+ return;
+ }
- for (var c = 0; c < config.channels; c++) {
- config.channelMapping.push(view.getUint8(13 + c));
- }
- }
+ match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
- 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 (match && match[1]) {
+ event = {
+ type: 'tag',
+ tagType: 'skip'
+ };
+ event.attributes = parseAttributes(match[1]);
- if (config.channelMappingFamily > 0) {
- view.setUint8(11, config.streamCount);
- config.channelMapping.foreach(function (cm, i) {
- view.setUint8(12 + i, cm);
- });
- }
+ if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
+ event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
+ }
- return new Uint8Array(view.buffer);
-};
+ if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
+ event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
+ }
-/***/ }),
+ this.trigger('data', event);
+ return;
+ }
-/***/ "./node_modules/@videojs/vhs-utils/es/resolve-url.js":
-/*!***********************************************************!*\
- !*** ./node_modules/@videojs/vhs-utils/es/resolve-url.js ***!
- \***********************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ match = /^#EXT-X-PART:(.*)$/.exec(newLine);
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
-/* harmony export */ });
-/* harmony import */ var url_toolkit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! url-toolkit */ "./node_modules/url-toolkit/src/url-toolkit.js");
-/* harmony import */ var url_toolkit__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(url_toolkit__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_1__);
+ 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);
+ }
-var DEFAULT_LOCATION = 'http://example.com';
+ this.trigger('data', event);
+ return;
+ }
-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
+ 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;
+ }
- if (/^data:/.test(baseUrl)) {
- baseUrl = (global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && (global_window__WEBPACK_IMPORTED_MODULE_1___default().location).href || '';
- } // IE11 supports URL but not the URL constructor
- // feature detect the behavior we want
+ 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;
+ }
- var nativeURL = typeof (global_window__WEBPACK_IMPORTED_MODULE_1___default().URL) === 'function';
- 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
+ match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine);
- var removeLocation = !(global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
+ 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.
- if (nativeURL) {
- baseUrl = new (global_window__WEBPACK_IMPORTED_MODULE_1___default().URL)(baseUrl, (global_window__WEBPACK_IMPORTED_MODULE_1___default().location) || DEFAULT_LOCATION);
- } else if (!/\/\//i.test(baseUrl)) {
- baseUrl = url_toolkit__WEBPACK_IMPORTED_MODULE_0___default().buildAbsoluteURL((global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && (global_window__WEBPACK_IMPORTED_MODULE_1___default().location).href || '', baseUrl);
- }
+ delete event.attributes[key];
+ }
+ });
+ this.trigger('data', event);
+ return;
+ }
- if (nativeURL) {
- 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
+ match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
- if (removeLocation) {
- return newUrl.href.slice(DEFAULT_LOCATION.length);
- } else if (protocolLess) {
- return newUrl.href.slice(newUrl.protocol.length);
- }
+ 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;
+ }
- return newUrl.href;
- }
+ match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine);
- return url_toolkit__WEBPACK_IMPORTED_MODULE_0___default().buildAbsoluteURL(baseUrl, relativeUrl);
-};
+ 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]+$/;
-/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (resolveUrl);
+ 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]);
+ }
-/***/ "./node_modules/@videojs/xhr/lib/http-handler.js":
-/*!*******************************************************!*\
- !*** ./node_modules/@videojs/xhr/lib/http-handler.js ***!
- \*******************************************************/
-/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
+ this.trigger('data', event);
+ return;
+ }
-"use strict";
+ match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine);
+ if (match) {
+ this.trigger('data', {
+ type: 'tag',
+ tagType: 'independent-segments'
+ });
+ return;
+ } // unknown tag type
-var window = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
-var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) {
- if (decodeResponseBody === void 0) {
- decodeResponseBody = false;
+ 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
+ */
- 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
+ addParser({
+ expression,
+ customType,
+ dataParser,
+ segment
+ }) {
+ if (typeof dataParser !== 'function') {
+ dataParser = line => line;
+ }
- if (response.statusCode >= 400 && response.statusCode <= 599) {
- var cause = responseBody;
-
- if (decodeResponseBody) {
- if (window.TextDecoder) {
- var charset = getCharset(response.headers && response.headers['content-type']);
+ this.customParsers.push(line => {
+ const match = expression.exec(line);
- try {
- cause = new TextDecoder(charset).decode(responseBody);
- } catch (e) {}
- } else {
- cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
- }
+ 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
+ */
- callback({
- cause: cause
- });
- return;
- } // otherwise, request succeeded
+ addTagMapper({
+ expression,
+ map
+ }) {
+ const mapFn = line => {
+ if (expression.test(line)) {
+ return map(line);
+ }
- callback(null, responseBody);
- };
-};
+ return line;
+ };
-function getCharset(contentTypeHeader) {
- if (contentTypeHeader === void 0) {
- contentTypeHeader = '';
+ this.tagMappers.push(mapFn);
}
- 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();
- }
+const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
- return charset;
- }, 'utf-8');
-}
+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.
-module.exports = httpResponseHandler;
-/***/ }),
+const setHoldBack = function (manifest) {
+ const {
+ serverControl,
+ targetDuration,
+ partTargetDuration
+ } = manifest;
-/***/ "./node_modules/@videojs/xhr/lib/index.js":
-/*!************************************************!*\
- !*** ./node_modules/@videojs/xhr/lib/index.js ***!
- \************************************************/
-/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
+ if (!serverControl) {
+ return;
+ }
-"use strict";
+ 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
-var window = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
-var _extends = __webpack_require__(/*! @babel/runtime/helpers/extends */ "./node_modules/@babel/runtime/helpers/extends.js");
+ 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
-var isFunction = __webpack_require__(/*! is-function */ "./node_modules/is-function/index.js");
-createXHR.httpHandler = __webpack_require__(/*! ./http-handler.js */ "./node_modules/@videojs/xhr/lib/http-handler.js");
+ if (partTargetDuration && serverControl[phb] < minPartDuration) {
+ this.trigger('warn', {
+ message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
+ });
+ serverControl[phb] = minPartDuration;
+ }
+};
/**
- * @license
- * slighly modified parse-headers 2.0.2
- * Copyright (c) 2014 David Björklund
- * Available under the MIT license
- *
+ * 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
+ * @extends Stream
*/
-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();
+class Parser extends _videojs_vhs_utils_es_stream_js__WEBPACK_IMPORTED_MODULE_0__["default"] {
+ constructor() {
+ super();
+ this.lineStream = new LineStream();
+ this.parseStream = new ParseStream();
+ this.lineStream.pipe(this.parseStream);
+ /* eslint-disable consistent-this */
- 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;
-};
+ const self = this;
+ /* eslint-enable consistent-this */
-module.exports = createXHR; // Allow use of default import syntax in TypeScript
+ const uris = [];
+ let currentUri = {}; // if specified, the active EXT-X-MAP definition
-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);
- };
-});
+ let currentMap; // if specified, the active decryption key
-function forEachArray(array, iterator) {
- for (var i = 0; i < array.length; i++) {
- iterator(array[i]);
- }
-}
+ let key;
+ let hasParts = false;
-function isEmpty(obj) {
- for (var i in obj) {
- if (obj.hasOwnProperty(i)) return false;
- }
+ const noop = function () {};
- return true;
-}
+ 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.
-function initParams(uri, options, callback) {
- var params = uri;
+ const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
- if (isFunction(options)) {
- callback = options;
+ let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
- if (typeof uri === "string") {
- params = {
- uri: uri
- };
- }
- } else {
- params = _extends({}, options, {
- uri: uri
- });
- }
+ this.manifest = {
+ allowCache: true,
+ discontinuityStarts: [],
+ 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
- params.callback = callback;
- return params;
-}
+ let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
-function createXHR(uri, options, callback) {
- options = initParams(uri, options, callback);
- return _createXHR(options);
-}
+ 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;
+ }
-function _createXHR(options) {
- if (typeof options.callback === "undefined") {
- throw new Error("callback argument missing");
- }
+ if (!currentUri.map && currentMap) {
+ currentUri.map = currentMap;
+ }
- var called = false;
+ if (!currentUri.key && key) {
+ currentUri.key = key;
+ }
- var callback = function cbOnce(err, response, body) {
- if (!called) {
- called = true;
- options.callback(err, response, body);
- }
- };
+ if (!currentUri.timeline && typeof currentTimeline === 'number') {
+ currentUri.timeline = currentTimeline;
+ }
- function readystatechange() {
- if (xhr.readyState === 4) {
- setTimeout(loadFunc, 0);
- }
- }
+ this.manifest.preloadSegment = currentUri;
+ }); // update the manifest with the m3u8 entry from the parse stream
- function getBody() {
- // Chrome with requestType=blob throws errors arround when even testing access to responseText
- var body = undefined;
+ this.parseStream.on('data', function (entry) {
+ let mediaGroup;
+ let rendition;
+ ({
+ tag() {
+ // switch based on the tag type
+ (({
+ version() {
+ if (entry.version) {
+ this.manifest.version = entry.version;
+ }
+ },
- if (xhr.response) {
- body = xhr.response;
- } else {
- body = xhr.responseText || getXml(xhr);
- }
+ 'allow-cache'() {
+ this.manifest.allowCache = entry.allowed;
- if (isJson) {
- try {
- body = JSON.parse(body);
- } catch (e) {}
- }
+ if (!('allowed' in entry)) {
+ this.trigger('info', {
+ message: 'defaulting allowCache to YES'
+ });
+ this.manifest.allowCache = true;
+ }
+ },
- return body;
- }
+ byterange() {
+ const byterange = {};
- function errorFunc(evt) {
- clearTimeout(timeoutTimer);
+ if ('length' in entry) {
+ currentUri.byterange = byterange;
+ byterange.length = entry.length;
- if (!(evt instanceof Error)) {
- evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
- }
+ 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;
+ }
+ }
- evt.statusCode = 0;
- return callback(evt, failureResponse);
- } // will load the data & process the response in a special response object
+ if ('offset' in entry) {
+ currentUri.byterange = byterange;
+ byterange.offset = entry.offset;
+ }
+ lastByterangeEnd = byterange.offset + byterange.length;
+ },
- function loadFunc() {
- if (aborted) return;
- var status;
- clearTimeout(timeoutTimer);
+ endlist() {
+ this.manifest.endList = true;
+ },
- 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;
- }
+ inf() {
+ if (!('mediaSequence' in this.manifest)) {
+ this.manifest.mediaSequence = 0;
+ this.trigger('info', {
+ message: 'defaulting media sequence to zero'
+ });
+ }
- var response = failureResponse;
- var err = null;
+ if (!('discontinuitySequence' in this.manifest)) {
+ this.manifest.discontinuitySequence = 0;
+ this.trigger('info', {
+ message: 'defaulting discontinuity sequence to zero'
+ });
+ }
- if (status !== 0) {
- response = {
- body: getBody(),
- statusCode: status,
- method: method,
- headers: {},
- url: uri,
- rawRequest: xhr
- };
+ if (entry.duration > 0) {
+ currentUri.duration = entry.duration;
+ }
- 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");
- }
+ if (entry.duration === 0) {
+ currentUri.duration = 0.01;
+ this.trigger('info', {
+ message: 'updating zero segment duration to a small value'
+ });
+ }
- return callback(err, response, response.body);
- }
+ this.manifest.segments = uris;
+ },
- var xhr = options.xhr || null;
+ key() {
+ if (!entry.attributes) {
+ this.trigger('warn', {
+ message: 'ignoring key declaration without attribute list'
+ });
+ return;
+ } // clear the active encryption key
- 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 (entry.attributes.METHOD === 'NONE') {
+ key = null;
+ return;
+ }
- 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 (!entry.attributes.URI) {
+ this.trigger('warn', {
+ message: 'ignoring key declaration without URI'
+ });
+ return;
+ }
- 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
+ if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
+ this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
- body = JSON.stringify(options.json === true ? body : options.json);
- }
- }
+ this.manifest.contentProtection['com.apple.fps.1_0'] = {
+ attributes: entry.attributes
+ };
+ return;
+ }
- xhr.onreadystatechange = readystatechange;
- xhr.onload = loadFunc;
- xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
+ if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
+ this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
- xhr.onprogress = function () {// IE must die
- };
+ 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
- xhr.onabort = function () {
- aborted = true;
- };
- xhr.ontimeout = errorFunc;
- xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
+ if (entry.attributes.KEYFORMAT === widevineUuid) {
+ const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
- 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 (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 (!sync && options.timeout > 0) {
- timeoutTimer = setTimeout(function () {
- if (aborted) return;
- aborted = true; //IE9 may still call readystatechange
+ if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
+ this.trigger('warn', {
+ message: 'invalid key URI provided for Widevine'
+ });
+ return;
+ }
- xhr.abort("timeout");
- var e = new Error("XMLHttpRequest timeout");
- e.code = "ETIMEDOUT";
- errorFunc(e);
- }, options.timeout);
- }
+ 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
- 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;
- }
+ 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: (0,_videojs_vhs_utils_es_decode_b64_to_uint8_array_js__WEBPACK_IMPORTED_MODULE_2__["default"])(entry.attributes.URI.split(',')[1])
+ };
+ return;
+ }
- 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.
+ if (!entry.attributes.METHOD) {
+ this.trigger('warn', {
+ message: 'defaulting key method to AES-128'
+ });
+ } // setup an encryption key for upcoming segments
- xhr.send(body || null);
- return xhr;
-}
+ key = {
+ method: entry.attributes.METHOD || 'AES-128',
+ uri: entry.attributes.URI
+ };
-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;
- }
+ if (typeof entry.attributes.IV !== 'undefined') {
+ key.iv = entry.attributes.IV;
+ }
+ },
- var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
+ 'media-sequence'() {
+ if (!isFinite(entry.number)) {
+ this.trigger('warn', {
+ message: 'ignoring invalid media sequence: ' + entry.number
+ });
+ return;
+ }
- if (xhr.responseType === "" && !firefoxBugTakenEffect) {
- return xhr.responseXML;
- }
- } catch (e) {}
+ this.manifest.mediaSequence = entry.number;
+ },
- return null;
-}
+ 'discontinuity-sequence'() {
+ if (!isFinite(entry.number)) {
+ this.trigger('warn', {
+ message: 'ignoring invalid discontinuity sequence: ' + entry.number
+ });
+ return;
+ }
-function noop() {}
+ 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;
+ }
-/***/ "./src/js/components/accordion.js":
-/*!****************************************!*\
- !*** ./src/js/components/accordion.js ***!
- \****************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ this.manifest.playlistType = entry.playlistType;
+ },
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ toggleAccordion: () => (/* binding */ toggleAccordion)
-/* harmony export */ });
-let items = document.querySelectorAll('.accordion__item');
-let title = document.querySelectorAll('.accordion__title-wrapper');
-const toggleAccordion = () => {
- title.forEach(question => question.addEventListener('click', function () {
- let thisItem = this.parentNode;
- items.forEach(item => {
- if (thisItem == item) {
- thisItem.classList.toggle('active');
- return;
- }
- item.classList.remove('active');
- });
- }));
-};
+ map() {
+ currentMap = {};
-/***/ }),
+ if (entry.uri) {
+ currentMap.uri = entry.uri;
+ }
-/***/ "./src/js/components/burger-menu.js":
-/*!******************************************!*\
- !*** ./src/js/components/burger-menu.js ***!
- \******************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ if (entry.byterange) {
+ currentMap.byterange = entry.byterange;
+ }
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ changeMenu: () => (/* binding */ changeMenu)
-/* harmony export */ });
-const burger = document.querySelector('.burger-js');
-const menu = document.querySelector('.menu-js');
-const menuLinks = document.querySelectorAll('.nav__link');
-const changeMenu = () => {
- burger?.addEventListener('click', () => {
- menu?.classList.toggle('active');
- burger?.classList.toggle('active');
- if (menu?.classList.contains('active')) {
- document.body.style.overflowY = 'hidden';
- } else {
- document.body.style.overflowY = 'auto';
- }
- });
- menuLinks.forEach(el => {
- el.addEventListener('click', () => {
- menu?.classList.remove('active');
- document.body.style.overflowY = 'auto';
- burger?.classList.remove('active');
- });
- });
-};
+ if (key) {
+ currentMap.key = key;
+ }
+ },
-/***/ }),
+ 'stream-inf'() {
+ this.manifest.playlists = uris;
+ this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
-/***/ "./src/js/components/paint-btn.js":
-/*!****************************************!*\
- !*** ./src/js/components/paint-btn.js ***!
- \****************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ if (!entry.attributes) {
+ this.trigger('warn', {
+ message: 'ignoring empty stream-inf attributes'
+ });
+ return;
+ }
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ paintBtn: () => (/* binding */ paintBtn)
-/* harmony export */ });
-const btnNext = document.querySelector('.nav-history__next');
-const paintBtn = () => {
- try {
- const scrollToTop = () => {
- const top = window.scrollY;
- if (top >= 100) {
- btnNext.classList.add('active');
- } else {
- btnNext.classList.remove('active');
- }
- };
- scrollToTop();
- window.addEventListener('scroll', () => {
- scrollToTop();
- });
- } catch (e) {
- console.log(e);
- }
-};
+ if (!currentUri.attributes) {
+ currentUri.attributes = {};
+ }
-/***/ }),
+ (0,_babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_1__["default"])(currentUri.attributes, entry.attributes);
+ },
-/***/ "./src/js/components/scroll-smooth.js":
-/*!********************************************!*\
- !*** ./src/js/components/scroll-smooth.js ***!
- \********************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ media() {
+ this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ addSmoothScroll: () => (/* binding */ addSmoothScroll)
-/* harmony export */ });
-/* harmony import */ var _node_modules_smooth_scroll_dist_smooth_scroll_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../node_modules/smooth-scroll/dist/smooth-scroll.js */ "./node_modules/smooth-scroll/dist/smooth-scroll.js");
-/* harmony import */ var _node_modules_smooth_scroll_dist_smooth_scroll_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_smooth_scroll_dist_smooth_scroll_js__WEBPACK_IMPORTED_MODULE_0__);
+ 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 header = document.querySelector('header');
-const btnUpWrapper = document.querySelector('.btn-up-wrapper');
-const addSmoothScroll = () => {
- const scroll = new (_node_modules_smooth_scroll_dist_smooth_scroll_js__WEBPACK_IMPORTED_MODULE_0___default())('a[href*="#"]', {
- header: '.header',
- speed: 500
- });
- const scrollToTop = () => {
- const top = window.scrollY;
- if (top >= 100) {
- header.classList.add('active');
- } else {
- header.classList.remove('active');
- }
- if (top >= 300) {
- btnUpWrapper.classList.add('active');
- } else {
- btnUpWrapper.classList.remove('active');
- }
- };
- scrollToTop();
- window.addEventListener('scroll', () => {
- scrollToTop();
- });
-};
-/***/ }),
+ 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
-/***/ "./src/js/components/sliders.js":
-/*!**************************************!*\
- !*** ./src/js/components/sliders.js ***!
- \**************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ rendition = {
+ default: /yes/i.test(entry.attributes.DEFAULT)
+ };
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ initializationSliders: () => (/* binding */ initializationSliders)
-/* harmony export */ });
-/* harmony import */ var swiper__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! swiper */ "./node_modules/swiper/swiper.esm.js");
+ if (rendition.default) {
+ rendition.autoselect = true;
+ } else {
+ rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
+ }
-swiper__WEBPACK_IMPORTED_MODULE_0__["default"].use([swiper__WEBPACK_IMPORTED_MODULE_0__.Navigation, swiper__WEBPACK_IMPORTED_MODULE_0__.Pagination, swiper__WEBPACK_IMPORTED_MODULE_0__.Autoplay]);
-const initializationSliders = () => {
- try {
- const recomendationsSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.recommendations__slider', {
- slidesPerView: '1',
- navigation: {
- nextEl: '.recommendations__slider-navigation-next',
- prevEl: '.recommendations__slider-navigation-prev'
- },
- loop: true
- });
- const goodsSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.goods__slider', {
- slidesPerView: 3,
- loop: true,
- navigation: {
- nextEl: '.goods__slider-next',
- prevEl: '.goods__slider-prev'
- },
- pagination: {
- clickable: true,
- el: '.goods__slider-pagination',
- clickable: true
- },
- breakpoints: {
- 940: {
- slidesPerView: 4
- },
- 764: {
- slidesPerView: 3
- },
- 600: {
- slidesPerView: 2
- },
- 0: {
- slidesPerView: 1
- }
- }
- });
- const professionalSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.professional__slider', {
- navigation: {
- nextEl: '.professional__slider-navigation-next',
- prevEl: '.professional__slider-navigation-prev'
- },
- paginationClickable: true,
- // loop: true,
- initialSlide: 1,
- centeredSlides: true,
- slidesPerView: 2,
- speed: 1000,
- zoom: {
- maxRatio: 1.6
- },
- pagination: {
- clickable: true,
- el: '.professional__slider-pagination',
- clickable: true
- },
- breakpoints: {
- 993: {
- spaceBetween: -180
- },
- 860: {
- spaceBetween: -260,
- slidesPerView: 2
- },
- 768: {
- spaceBetween: -200
- },
- 500: {
- spaceBetween: -80
- },
- 320: {
- slidesPerView: 1.6,
- spaceBetween: -80
- }
- },
- allowTouchMove: false
- });
- const interviewSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.interview__slider', {
- navigation: {
- nextEl: '.interview__slider-navigation-next',
- prevEl: '.interview__slider-navigation-prev'
- },
- pagination: {
- el: '.interview__slider-pagination',
- clickable: true
- },
- allowTouchMove: false,
- paginationClickable: true,
- centeredSlides: true,
- loop: true,
- speed: 1000
- });
- const partnersSlider = new swiper__WEBPACK_IMPORTED_MODULE_0__["default"]('.partners__wrapper', {
- freeMode: true,
- grabCursor: true,
- centeredSlides: true,
- slidesPerView: 'auto',
- loop: true,
- speed: 2000,
- autoplay: {
- enabled: true,
- delay: 1,
- disableOnInteraction: true
- },
- freeModeMomentum: true
- });
- document.querySelector('.partners__wrapper').addEventListener('mouseover', function () {
- partnersSlider.autoplay.stop();
- });
- document.querySelector('.partners__wrapper').addEventListener('mouseout', function () {
- partnersSlider.autoplay.start();
- });
- window.addEventListener('scroll', () => {
- if (!partnersSlider.autoplay.running) {
- partnersSlider.autoplay.start();
- }
- });
- } catch (e) {
- console.error(e);
- }
-};
+ if (entry.attributes.LANGUAGE) {
+ rendition.language = entry.attributes.LANGUAGE;
+ }
-/***/ }),
+ if (entry.attributes.URI) {
+ rendition.uri = entry.attributes.URI;
+ }
-/***/ "./src/js/components/video-player.js":
-/*!*******************************************!*\
- !*** ./src/js/components/video-player.js ***!
- \*******************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ if (entry.attributes['INSTREAM-ID']) {
+ rendition.instreamId = entry.attributes['INSTREAM-ID'];
+ }
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ addVideoPlayer: () => (/* binding */ addVideoPlayer)
-/* harmony export */ });
-/* harmony import */ var video_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! video.js */ "./node_modules/video.js/dist/video.es.js");
+ if (entry.attributes.CHARACTERISTICS) {
+ rendition.characteristics = entry.attributes.CHARACTERISTICS;
+ }
-const playBtn = document.querySelector('.video__btn');
-const videoTitle = document.querySelector('.video__title');
-const videoText = document.querySelector('.video__text');
-const videoLink = document.querySelector('.video__link');
-const videoGradient = document.querySelector('.video__gradient');
-const videoIntro = document.querySelector('.video__intro');
-const videoMask = document.querySelector('.video__mask');
-const videoJsTech = document.querySelector('.vjs-tech');
-const addVideoPlayer = () => {
- try {
- let isPreviewVideo = true;
- const videoPlayer = (0,video_js__WEBPACK_IMPORTED_MODULE_0__["default"])(document.getElementById('video__player'), {
- autoplay: true,
- loop: true,
- muted: true,
- controls: false,
- playToggle: false
- });
- const loadMainVideo = (desktopUrl, mobileUrl) => {
- if (window.screen.width > 768) {
- videoPlayer.src({
- type: 'video/mp4',
- src: `./videos/${desktopUrl}`
- });
- } else {
- videoPlayer.src({
- type: 'video/mp4',
- src: `./videos/${mobileUrl}`
- });
- }
- };
- loadMainVideo('preview.mp4', 'preview-mobile.mp4');
- videoPlayer.volume(0.7);
- const changePlayerStatus = () => {
- videoPlayer.play();
- videoPlayer.addClass('play');
- videoPlayer.muted(false);
- videoPlayer.controls(true);
- videoPlayer.loop(false);
- playBtn.classList.add('hide');
- videoTitle.classList.add('hide');
- videoText.classList.add('hide');
- videoLink.classList.add('hide');
- };
- videoPlayer.on('play', () => {
- if (!videoPlayer.played()) {
- videoPlayer.addClass('play');
- videoPlayer.removeClass('play');
- playBtn?.classList.add('hide');
- } else if (videoPlayer.played() && videoPlayer.loop()) {
- videoPlayer.removeClass('play');
- } else if (videoPlayer.played() && !videoPlayer.loop()) {
- videoPlayer.removeClass('play');
- playBtn?.classList.add('hide');
- }
- });
- videoPlayer.on('pause', () => {
- playBtn?.classList.remove('hide');
- });
- videoPlayer.on('ended', () => {
- playBtn?.classList.remove('hide');
- videoMask?.classList.remove('visible');
- });
- videoMask.addEventListener('click', () => {
- playBtn?.classList.remove('hide');
- videoPlayer.pause();
- videoMask?.classList.remove('visible');
- });
- playBtn.addEventListener('click', () => {
- if (isPreviewVideo === true) {
- loadMainVideo('video.mp4', 'video-mobile.mp4');
- isPreviewVideo = false;
- }
- document.querySelector('.video__inner').classList.add('active');
- document.querySelector('.video-js').classList.add('active');
- document.querySelector('.vjs-poster').classList.add('active');
- videoPlayer.fluid(true);
- videoGradient?.classList.add('hide');
- videoMask?.classList.add('visible');
- playBtn?.classList.add('center-position');
- document.querySelector('video').style.objectFit = 'contain';
- if (videoPlayer.muted()) {
- videoPlayer.currentTime(0);
- changePlayerStatus();
- } else {
- changePlayerStatus();
- }
- });
- } catch (e) {
- console.log(e);
- }
-};
+ if (entry.attributes.FORCED) {
+ rendition.forced = /yes/i.test(entry.attributes.FORCED);
+ } // insert the new rendition
-/***/ }),
-/***/ "./node_modules/global/document.js":
-/*!*****************************************!*\
- !*** ./node_modules/global/document.js ***!
- \*****************************************/
-/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
+ mediaGroup[entry.attributes.NAME] = rendition;
+ },
-var topLevel = typeof __webpack_require__.g !== 'undefined' ? __webpack_require__.g :
- typeof window !== 'undefined' ? window : {}
-var minDoc = __webpack_require__(/*! min-document */ "?34aa");
+ discontinuity() {
+ currentTimeline += 1;
+ currentUri.discontinuity = true;
+ this.manifest.discontinuityStarts.push(uris.length);
+ },
-var doccy;
+ '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;
+ }
-if (typeof document !== 'undefined') {
- doccy = document;
-} else {
- doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
+ currentUri.dateTimeString = entry.dateTimeString;
+ currentUri.dateTimeObject = entry.dateTimeObject;
+ },
- if (!doccy) {
- doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
- }
-}
+ targetduration() {
+ if (!isFinite(entry.duration) || entry.duration < 0) {
+ this.trigger('warn', {
+ message: 'ignoring invalid target duration: ' + entry.duration
+ });
+ return;
+ }
-module.exports = doccy;
+ 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
+ };
+ },
-/***/ "./node_modules/global/window.js":
-/*!***************************************!*\
- !*** ./node_modules/global/window.js ***!
- \***************************************/
-/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
+ 'cue-out'() {
+ currentUri.cueOut = entry.data;
+ },
-var win;
+ 'cue-out-cont'() {
+ currentUri.cueOutCont = entry.data;
+ },
-if (typeof window !== "undefined") {
- win = window;
-} else if (typeof __webpack_require__.g !== "undefined") {
- win = __webpack_require__.g;
-} else if (typeof self !== "undefined"){
- win = self;
-} else {
- win = {};
-}
+ 'cue-in'() {
+ currentUri.cueIn = entry.data;
+ },
-module.exports = win;
+ '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);
-/***/ "./node_modules/is-function/index.js":
-/*!*******************************************!*\
- !*** ./node_modules/is-function/index.js ***!
- \*******************************************/
-/***/ ((module) => {
+ if (part.byterange) {
+ if (!part.byterange.hasOwnProperty('offset')) {
+ part.byterange.offset = lastPartByterangeEnd;
+ }
-module.exports = isFunction
+ lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
+ }
-var toString = Object.prototype.toString
+ const partIndex = currentUri.parts.length - 1;
+ this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']);
-function isFunction (fn) {
- if (!fn) {
- return false
- }
- var string = toString.call(fn)
- return string === '[object Function]' ||
- (typeof fn === 'function' && string !== '[object RegExp]') ||
- (typeof window !== 'undefined' &&
- // IE8 and below
- (fn === window.setTimeout ||
- fn === window.alert ||
- fn === window.confirm ||
- fn === window.prompt))
-};
+ 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'
+ });
+ }
-/***/ "./node_modules/keycode/index.js":
-/*!***************************************!*\
- !*** ./node_modules/keycode/index.js ***!
- \***************************************/
-/***/ ((module, exports) => {
+ setHoldBack.call(this, this.manifest);
-// Source: http://jsfiddle.net/vWx8V/
-// http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
+ 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'
+ });
+ }
+ },
-/**
- * Conenience method returns corresponding value for given keyName or keyCode.
- *
- * @param {Mixed} keyCode {Number} or keyName {String}
- * @return {Mixed}
- * @api public
- */
+ '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);
-function keyCode(searchInput) {
- // Keyboard Events
- if (searchInput && 'object' === typeof searchInput) {
- var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode
- if (hasKeyCode) searchInput = hasKeyCode
- }
+ 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;
- // Numbers
- if ('number' === typeof searchInput) return names[searchInput]
+ if (isPart) {
+ lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
+ }
+ }
+ }
- // Everything else (cast to string)
- var search = String(searchInput)
+ const index = currentUri.preloadHints.length - 1;
+ this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']);
- // check codes
- var foundNamedKey = codes[search.toLowerCase()]
- if (foundNamedKey) return foundNamedKey
+ if (!hint.type) {
+ return;
+ } // search through all preload hints except for the current one for
+ // a duplicate type.
- // check aliases
- var foundNamedKey = aliases[search.toLowerCase()]
- if (foundNamedKey) return foundNamedKey
- // weird character?
- if (search.length === 1) return search.charCodeAt(0)
+ for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
+ const otherHint = currentUri.preloadHints[i];
- return undefined
-}
+ if (!otherHint.type) {
+ continue;
+ }
-/**
- * Compares a keyboard event with a given keyCode or keyName.
- *
- * @param {Event} event Keyboard event that should be tested
- * @param {Mixed} keyCode {Number} or keyName {String}
- * @return {Boolean}
- * @api public
- */
-keyCode.isEventKey = function isEventKey(event, nameOrCode) {
- if (event && 'object' === typeof event) {
- var keyCode = event.which || event.keyCode || event.charCode
- if (keyCode === null || keyCode === undefined) { return false; }
- if (typeof nameOrCode === 'string') {
- // check codes
- var foundNamedKey = codes[nameOrCode.toLowerCase()]
- if (foundNamedKey) { return foundNamedKey === keyCode; }
-
- // check aliases
- var foundNamedKey = aliases[nameOrCode.toLowerCase()]
- if (foundNamedKey) { return foundNamedKey === keyCode; }
- } else if (typeof nameOrCode === 'number') {
- return nameOrCode === keyCode;
- }
- return false;
- }
-}
+ 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}`
+ });
+ }
+ }
+ },
-exports = module.exports = keyCode;
+ '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'];
-/**
- * Get by name
- *
- * exports.code['enter'] // => 13
- */
+ if (hasParts) {
+ required.push('LAST-PART');
+ }
-var codes = exports.code = exports.codes = {
- 'backspace': 8,
- 'tab': 9,
- 'enter': 13,
- 'shift': 16,
- 'ctrl': 17,
- 'alt': 18,
- 'pause/break': 19,
- 'caps lock': 20,
- 'esc': 27,
- 'space': 32,
- 'page up': 33,
- 'page down': 34,
- 'end': 35,
- 'home': 36,
- 'left': 37,
- 'up': 38,
- 'right': 39,
- 'down': 40,
- 'insert': 45,
- 'delete': 46,
- 'command': 91,
- 'left command': 91,
- 'right command': 93,
- 'numpad *': 106,
- 'numpad +': 107,
- 'numpad -': 109,
- 'numpad .': 110,
- 'numpad /': 111,
- 'num lock': 144,
- 'scroll lock': 145,
- 'my computer': 182,
- 'my calculator': 183,
- ';': 186,
- '=': 187,
- ',': 188,
- '-': 189,
- '.': 190,
- '/': 191,
- '`': 192,
- '[': 219,
- '\\': 220,
- ']': 221,
- "'": 222
-}
+ this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
+ },
-// Helper aliases
+ 'part-inf'() {
+ this.manifest.partInf = camelCaseKeys(entry.attributes);
+ this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
-var aliases = exports.aliases = {
- 'windows': 91,
- '⇧': 16,
- '⌥': 18,
- '⌃': 17,
- '⌘': 91,
- 'ctl': 17,
- 'control': 17,
- 'option': 18,
- 'pause': 19,
- 'break': 19,
- 'caps': 20,
- 'return': 13,
- 'escape': 27,
- 'spc': 32,
- 'spacebar': 32,
- 'pgup': 33,
- 'pgdn': 34,
- 'ins': 45,
- 'del': 46,
- 'cmd': 91
-}
+ if (this.manifest.partInf.partTarget) {
+ this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
+ }
-/*!
- * Programatically add the following
- */
+ setHoldBack.call(this, this.manifest);
+ },
-// lower case chars
-for (i = 97; i < 123; i++) codes[String.fromCharCode(i)] = i - 32
+ 'daterange'() {
+ this.manifest.daterange = this.manifest.daterange || [];
+ this.manifest.daterange.push(camelCaseKeys(entry.attributes));
+ const index = this.manifest.daterange.length - 1;
+ this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']);
+ const daterange = this.manifest.daterange[index];
-// numbers
-for (var i = 48; i < 58; i++) codes[i - 48] = i
+ 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'
+ });
+ }
-// function keys
-for (i = 1; i < 13; i++) codes['f'+i] = i + 111
+ if (daterange.duration && daterange.duration < 0) {
+ this.trigger('warn', {
+ message: 'EXT-X-DATERANGE DURATION must not be negative'
+ });
+ }
-// numpad keys
-for (i = 0; i < 10; i++) codes['numpad '+i] = i + 96
+ if (daterange.plannedDuration && daterange.plannedDuration < 0) {
+ this.trigger('warn', {
+ message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
+ });
+ }
-/**
- * Get by code
- *
- * exports.name[13] // => 'Enter'
- */
+ const endOnNextYes = !!daterange.endOnNext;
-var names = exports.names = exports.title = {} // title for backward compat
+ if (endOnNextYes && !daterange.class) {
+ this.trigger('warn', {
+ message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
+ });
+ }
-// Create reverse mapping
-for (i in codes) names[codes[i]] = i
+ 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'
+ });
+ }
-// Add aliases
-for (var alias in aliases) {
- codes[alias] = aliases[alias]
-}
+ if (daterange.duration && daterange.endDate) {
+ const startDate = daterange.startDate;
+ const newDateInSeconds = startDate.setSeconds(startDate.getSeconds() + daterange.duration);
+ this.manifest.daterange[index].endDate = new Date(newDateInSeconds);
+ }
+ if (daterange && !this.manifest.dateTimeString) {
+ this.trigger('warn', {
+ message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
+ });
+ }
-/***/ }),
+ if (!daterangeTags[daterange.id]) {
+ daterangeTags[daterange.id] = daterange;
+ } else {
+ for (const attribute in daterangeTags[daterange.id]) {
+ if (daterangeTags[daterange.id][attribute] !== daterange[attribute]) {
+ this.trigger('warn', {
+ message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes and same attribute values'
+ });
+ break;
+ }
+ }
+ }
+ },
-/***/ "./node_modules/m3u8-parser/dist/m3u8-parser.es.js":
-/*!*********************************************************!*\
- !*** ./node_modules/m3u8-parser/dist/m3u8-parser.es.js ***!
- \*********************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ 'independent-segments'() {
+ this.manifest.independentSegments = true;
+ }
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ LineStream: () => (/* binding */ LineStream),
-/* harmony export */ ParseStream: () => (/* binding */ ParseStream),
-/* harmony export */ Parser: () => (/* binding */ Parser)
-/* harmony export */ });
-/* harmony import */ var _videojs_vhs_utils_es_stream_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @videojs/vhs-utils/es/stream.js */ "./node_modules/m3u8-parser/node_modules/@videojs/vhs-utils/es/stream.js");
-/* harmony import */ var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/helpers/extends */ "./node_modules/@babel/runtime/helpers/esm/extends.js");
-/* harmony import */ var _videojs_vhs_utils_es_decode_b64_to_uint8_array_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @videojs/vhs-utils/es/decode-b64-to-uint8-array.js */ "./node_modules/m3u8-parser/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js");
-/*! @name m3u8-parser @version 6.2.0 @license Apache-2.0 */
+ })[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
-/**
- * @file m3u8/line-stream.js
- */
-/**
- * A stream that buffers string input and generates a `data` event for each
- * line.
- *
- * @class LineStream
- * @extends Stream
- */
+ if (key) {
+ currentUri.key = key;
+ }
-class LineStream extends _videojs_vhs_utils_es_stream_js__WEBPACK_IMPORTED_MODULE_0__["default"] {
- constructor() {
- super();
- this.buffer = '';
- }
- /**
- * Add new data to be parsed.
- *
- * @param {string} data the text to process
- */
+ 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
- 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);
- }
- }
+ lastPartByterangeEnd = 0; // prepare for the next URI
-}
+ currentUri = {};
+ },
-const TAB = String.fromCharCode(0x09);
+ comment() {// comments are not important for playback
+ },
-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 = {};
+ 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;
+ }
+ }
- if (match[1]) {
- result.length = parseInt(match[1], 10);
- }
-
- if (match[2]) {
- result.offset = parseInt(match[2], 10);
+ })[entry.type].call(self);
+ });
}
- 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
+ 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
+ */
- 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];
+ push(chunk) {
+ this.lineStream.push(chunk);
}
-
- 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
- */
+ /**
+ * 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.
+ */
-class ParseStream extends _videojs_vhs_utils_es_stream_js__WEBPACK_IMPORTED_MODULE_0__["default"] {
- constructor() {
- super();
- this.customParsers = [];
- this.tagMappers = [];
+ end() {
+ // flush any buffered input
+ this.lineStream.push('\n');
+ this.trigger('end');
}
/**
- * Parses an additional line of input.
+ * Add an additional parser for non-standard tags
*
- * @param {string} line a single line of an M3U8 file to parse
+ * @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.type the 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
*/
- push(line) {
- let match;
- let event; // strip whitespace
-
- line = line.trim();
+ 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
+ */
- if (line.length === 0) {
- // ignore empty lines
- return;
- } // URIs
+ addTagMapper(options) {
+ this.parseStream.addTagMapper(options);
+ }
- 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
+/***/ }),
+/***/ "./node_modules/mpd-parser/dist/mpd-parser.es.js":
+/*!*******************************************************!*\
+ !*** ./node_modules/mpd-parser/dist/mpd-parser.es.js ***!
+ \*******************************************************/
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
- 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.
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
+/* harmony export */ VERSION: () => (/* binding */ VERSION),
+/* harmony export */ addSidxSegmentsToPlaylist: () => (/* binding */ addSidxSegmentsToPlaylist$1),
+/* harmony export */ generateSidxKey: () => (/* binding */ generateSidxKey),
+/* harmony export */ inheritAttributes: () => (/* binding */ inheritAttributes),
+/* harmony export */ parse: () => (/* binding */ parse),
+/* harmony export */ parseUTCTiming: () => (/* binding */ parseUTCTiming),
+/* harmony export */ stringToMpdXml: () => (/* binding */ stringToMpdXml),
+/* harmony export */ toM3u8: () => (/* binding */ toM3u8),
+/* harmony export */ toPlaylists: () => (/* binding */ toPlaylists)
+/* harmony export */ });
+/* harmony import */ var _videojs_vhs_utils_es_resolve_url__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @videojs/vhs-utils/es/resolve-url */ "./node_modules/@videojs/vhs-utils/es/resolve-url.js");
+/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
+/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_1__);
+/* harmony import */ var _videojs_vhs_utils_es_media_groups__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @videojs/vhs-utils/es/media-groups */ "./node_modules/@videojs/vhs-utils/es/media-groups.js");
+/* harmony import */ var _videojs_vhs_utils_es_decode_b64_to_uint8_array__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @videojs/vhs-utils/es/decode-b64-to-uint8-array */ "./node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js");
+/* harmony import */ var _xmldom_xmldom__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @xmldom/xmldom */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/index.js");
+/*! @name mpd-parser @version 1.2.2 @license Apache-2.0 */
- 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'
- };
+var version = "1.2.2";
- if (match[1]) {
- event.duration = parseFloat(match[1]);
- }
+const isObject = obj => {
+ return !!obj && typeof obj === 'object';
+};
- if (match[2]) {
- event.title = match[2];
- }
+const merge = (...objects) => {
+ return objects.reduce((result, source) => {
+ if (typeof source !== 'object') {
+ return result;
+ }
- this.trigger('data', event);
- return;
+ Object.keys(source).forEach(key => {
+ if (Array.isArray(result[key]) && Array.isArray(source[key])) {
+ result[key] = result[key].concat(source[key]);
+ } else if (isObject(result[key]) && 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]);
- match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
-
- if (match) {
- event = {
- type: 'tag',
- tagType: 'targetduration'
- };
+const range = (start, end) => {
+ const result = [];
- if (match[1]) {
- event.duration = parseInt(match[1], 10);
- }
+ for (let i = start; i < end; i++) {
+ result.push(i);
+ }
- this.trigger('data', event);
- return;
- }
+ return result;
+};
+const flatten = lists => lists.reduce((x, y) => x.concat(y), []);
+const from = list => {
+ if (!list.length) {
+ return [];
+ }
- match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
+ const result = [];
- if (match) {
- event = {
- type: 'tag',
- tagType: 'version'
- };
+ for (let i = 0; i < list.length; i++) {
+ result.push(list[i]);
+ }
- if (match[1]) {
- event.version = parseInt(match[1], 10);
- }
+ return result;
+};
+const findIndexes = (l, key) => l.reduce((a, e, i) => {
+ if (e[key]) {
+ a.push(i);
+ }
- this.trigger('data', event);
- return;
- }
+ 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
+ */
- match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
+const union = (lists, keyFunction) => {
+ return values(lists.reduce((acc, list) => {
+ list.forEach(el => {
+ acc[keyFunction(el)] = el;
+ });
+ return acc;
+ }, {}));
+};
- if (match) {
- event = {
- type: 'tag',
- tagType: 'media-sequence'
- };
+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'
+};
- if (match[1]) {
- event.number = parseInt(match[1], 10);
- }
+/**
+ * @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
+ */
- this.trigger('data', event);
- return;
- }
+/**
+ * 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
+ */
- match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
+const urlTypeToSegment = ({
+ baseUrl = '',
+ source = '',
+ range = '',
+ indexRange = ''
+}) => {
+ const segment = {
+ uri: source,
+ resolvedUri: (0,_videojs_vhs_utils_es_resolve_url__WEBPACK_IMPORTED_MODULE_0__["default"])(baseUrl || '', source)
+ };
- if (match) {
- event = {
- type: 'tag',
- tagType: 'discontinuity-sequence'
- };
+ if (range || indexRange) {
+ const rangeStr = range ? range : indexRange;
+ const ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible
- if (match[1]) {
- event.number = parseInt(match[1], 10);
- }
+ let startRange = (global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt) ? global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(ranges[0]) : parseInt(ranges[0], 10);
+ let endRange = (global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt) ? global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER
- this.trigger('data', event);
- return;
- }
+ if (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') {
+ startRange = Number(startRange);
+ }
- match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
+ if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') {
+ endRange = Number(endRange);
+ }
- if (match) {
- event = {
- type: 'tag',
- tagType: 'playlist-type'
- };
+ let length;
- if (match[1]) {
- event.playlistType = match[1];
- }
+ if (typeof endRange === 'bigint' || typeof startRange === 'bigint') {
+ length = global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(endRange) - global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(startRange) + global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(1);
+ } else {
+ length = endRange - startRange + 1;
+ }
- this.trigger('data', event);
- return;
- }
+ if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) {
+ length = Number(length);
+ } // byterange should be inclusive according to
+ // RFC 2616, Clause 14.35.1
- match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
- if (match) {
- event = (0,_babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_1__["default"])(parseByterange(match[1]), {
- type: 'tag',
- tagType: 'byterange'
- });
- this.trigger('data', event);
- return;
- }
+ segment.byterange = {
+ length,
+ offset: startRange
+ };
+ }
- match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
+ return segment;
+};
+const byteRangeToString = byterange => {
+ // `endRange` is one less than `offset + length` because the HTTP range
+ // header uses inclusive ranges
+ let endRange;
- if (match) {
- event = {
- type: 'tag',
- tagType: 'allow-cache'
- };
+ if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
+ endRange = global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(byterange.offset) + global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(byterange.length) - global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(1);
+ } else {
+ endRange = byterange.offset + byterange.length - 1;
+ }
- if (match[1]) {
- event.allowed = !/NO/.test(match[1]);
- }
+ return `${byterange.offset}-${endRange}`;
+};
- this.trigger('data', event);
- return;
- }
+/**
+ * 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.
+ */
- match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
+const parseEndNumber = endNumber => {
+ if (endNumber && typeof endNumber !== 'number') {
+ endNumber = parseInt(endNumber, 10);
+ }
- if (match) {
- event = {
- type: 'tag',
- tagType: 'map'
- };
+ if (isNaN(endNumber)) {
+ return null;
+ }
- if (match[1]) {
- const attributes = parseAttributes(match[1]);
+ return endNumber;
+};
+/**
+ * Functions for calculating the range of available segments in static and dynamic
+ * manifests.
+ */
- if (attributes.URI) {
- event.uri = attributes.URI;
- }
- if (attributes.BYTERANGE) {
- event.byterange = parseByterange(attributes.BYTERANGE);
- }
- }
+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;
- this.trigger('data', event);
- return;
- }
+ if (typeof endNumber === 'number') {
+ return {
+ start: 0,
+ end: endNumber
+ };
+ }
- match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
+ if (typeof periodDuration === 'number') {
+ return {
+ start: 0,
+ end: periodDuration / segmentDuration
+ };
+ }
- if (match) {
- event = {
- type: 'tag',
- tagType: 'stream-inf'
- };
+ return {
+ start: 0,
+ end: sourceDuration / segmentDuration
+ };
+ },
- if (match[1]) {
- event.attributes = parseAttributes(match[1]);
+ /**
+ * 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.
- if (event.attributes.RESOLUTION) {
- const split = event.attributes.RESOLUTION.split('x');
- const resolution = {};
+ const now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock.
+ // Convert the period start time to EPOCH.
- if (split[0]) {
- resolution.width = parseInt(split[0], 10);
- }
+ const periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update.
- if (split[1]) {
- resolution.height = parseInt(split[1], 10);
- }
+ 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)
+ };
+ }
- event.attributes.RESOLUTION = resolution;
- }
+};
+/**
+ * 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
+ */
- if (event.attributes.BANDWIDTH) {
- event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
- }
+/**
+ * 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
+ */
- if (event.attributes['FRAME-RATE']) {
- event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
- }
+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
+ */
- if (event.attributes['PROGRAM-ID']) {
- event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
- }
- }
+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));
- this.trigger('data', event);
- return;
- }
+ if (type === 'static') {
+ const index = segments.length - 1; // section is either a period or the full source
- match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
+ const sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration
- if (match) {
- event = {
- type: 'tag',
- tagType: 'media'
- };
+ segments[index].duration = sectionDuration - duration / timescale * index;
+ }
- if (match[1]) {
- event.attributes = parseAttributes(match[1]);
- }
+ return segments;
+};
- this.trigger('data', event);
- return;
- }
+/**
+ * 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
+ */
- match = /^#EXT-X-ENDLIST/.exec(newLine);
+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 (match) {
- this.trigger('data', {
- type: 'tag',
- tagType: 'endlist'
- });
- return;
- }
+ if (!baseUrl) {
+ throw new Error(errors.NO_BASE_URL);
+ }
- match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
+ 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 (match) {
- this.trigger('data', {
- type: 'tag',
- tagType: 'discontinuity'
- });
- return;
- }
+ if (duration) {
+ const segmentTimeInfo = parseByDuration(attributes);
- match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
+ 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.
- if (match) {
- event = {
- type: 'tag',
- tagType: 'program-date-time'
- };
- if (match[1]) {
- event.dateTimeString = match[1];
- event.dateTimeObject = new Date(match[1]);
- }
+ 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
+ */
- this.trigger('data', event);
- return;
- }
+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
- match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
+ const sourceDuration = playlist.sidx.duration; // Retain source timeline
- if (match) {
- event = {
- type: 'tag',
- tagType: 'key'
- };
+ const timeline = playlist.timeline || 0;
+ const sidxByteRange = playlist.sidx.byterange;
+ const sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
- if (match[1]) {
- event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
+ const timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
- if (event.attributes.IV) {
- if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
- event.attributes.IV = event.attributes.IV.substring(2);
- }
+ 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
- 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);
- }
- }
+ let startIndex; // eslint-disable-next-line
- this.trigger('data', event);
- return;
- }
+ if (typeof sidx.firstOffset === 'bigint') {
+ startIndex = global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(sidxEnd) + sidx.firstOffset;
+ } else {
+ startIndex = sidxEnd + sidx.firstOffset;
+ }
- match = /^#EXT-X-START:(.*)$/.exec(newLine);
+ for (let i = 0; i < mediaReferences.length; i++) {
+ const reference = sidx.references[i]; // size of the referenced (sub)segment
- if (match) {
- event = {
- type: 'tag',
- tagType: 'start'
- };
+ const size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale
+ // this will be converted to seconds when generating segments
- 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);
- }
+ const duration = reference.subsegmentDuration; // should be an inclusive range
- this.trigger('data', event);
- return;
- }
+ let endIndex; // eslint-disable-next-line
- match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
+ if (typeof startIndex === 'bigint') {
+ endIndex = startIndex + global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(size) - global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(1);
+ } else {
+ endIndex = startIndex + size - 1;
+ }
- if (match) {
- event = {
- type: 'tag',
- tagType: 'cue-out-cont'
- };
+ const indexRange = `${startIndex}-${endIndex}`;
+ const attributes = {
+ baseUrl,
+ timescale,
+ timeline,
+ periodStart,
+ presentationTime,
+ number,
+ duration,
+ sourceDuration,
+ indexRange,
+ type
+ };
+ const segment = segmentsFromBase(attributes)[0];
- if (match[1]) {
- event.data = match[1];
- } else {
- event.data = '';
- }
+ if (initSegment) {
+ segment.map = initSegment;
+ }
- this.trigger('data', event);
- return;
- }
+ segments.push(segment);
- match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
+ if (typeof startIndex === 'bigint') {
+ startIndex += global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(size);
+ } else {
+ startIndex += size;
+ }
- if (match) {
- event = {
- type: 'tag',
- tagType: 'cue-out'
- };
+ presentationTime += duration / timescale;
+ number++;
+ }
- if (match[1]) {
- event.data = match[1];
- } else {
- event.data = '';
- }
+ playlist.segments = segments;
+ return playlist;
+};
- this.trigger('data', event);
- return;
- }
+const SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen)
- match = /^#EXT-X-CUE-IN:(.*)?$/.exec(newLine);
+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
+ */
- if (match) {
- event = {
- type: 'tag',
- tagType: 'cue-in'
- };
+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
+ */
- if (match[1]) {
- event.data = match[1];
- } else {
- event.data = '';
- }
+const findPlaylistWithName = (playlists, name) => {
+ for (let i = 0; i < playlists.length; i++) {
+ if (playlists[i].attributes.NAME === name) {
+ return playlists[i];
+ }
+ }
- this.trigger('data', event);
- return;
- }
+ return null;
+};
+/**
+ * Gets a flattened array of media group playlists.
+ *
+ * @param {Object} manifest - the main manifest object
+ *
+ * @return {Array} the media group playlists
+ */
- match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
+const getMediaGroupPlaylists = manifest => {
+ let mediaGroupPlaylists = [];
+ (0,_videojs_vhs_utils_es_media_groups__WEBPACK_IMPORTED_MODULE_2__.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
+ */
- if (match && match[1]) {
- event = {
- type: 'tag',
- tagType: 'skip'
- };
- event.attributes = parseAttributes(match[1]);
+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
+ */
- if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
- event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
- }
+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.
- if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
- event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
- }
+ const oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME);
- this.trigger('data', event);
- return;
- }
+ 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.
- 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 (playlist.sidx) {
+ return;
+ } // Since we don't yet support early available timelines, we don't need to support
+ // playlists with no segments.
- if (event.attributes.hasOwnProperty('BYTERANGE')) {
- event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
- }
- this.trigger('data', event);
- return;
- }
+ 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.
- match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine);
+ 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 (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;
+ if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) {
+ playlist.discontinuitySequence--;
}
- 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;
- }
+ 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.
- 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.
+ const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
- delete event.attributes[key];
- }
- });
- this.trigger('data', event);
- return;
- }
+ if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) {
+ firstNewSegment.discontinuity = true;
+ playlist.discontinuityStarts.unshift(0);
+ playlist.discontinuitySequence--;
+ }
- match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
+ 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
+ */
- 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;
- }
+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.
- match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine);
+ newManifest.timelineStarts = getUniqueTimelineStarts([oldManifest.timelineStarts, newManifest.timelineStarts]);
+ updateSequenceNumbers({
+ oldPlaylists,
+ newPlaylists,
+ timelineStarts: newManifest.timelineStarts
+ });
+ return newManifest;
+};
- 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]+$/;
+const generateSidxKey = sidx => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
- for (const key in event.attributes) {
- if (!clientAttributePattern.test(key)) {
- continue;
- }
+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] = [];
+ }
- 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]);
- }
+ 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 || '');
- this.trigger('data', event);
- return;
- }
+ 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;
+ }
- match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine);
+ acc[name].segments.push(...playlist.segments);
+ } // bubble up contentProtection, this assumes all DRM content
+ // has the same contentProtection
- if (match) {
- this.trigger('data', {
- type: 'tag',
- tagType: 'independent-segments'
- });
- return;
- } // unknown tag type
+ if (playlist.attributes.contentProtection) {
+ acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
+ }
+ }
- this.trigger('data', {
- type: 'tag',
- data: newLine.slice(4)
+ 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
});
- });
- }
- /**
- * 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
- */
-
+ return acc;
+ }, {}));
+ allPlaylists = allPlaylists.concat(mergedPlaylists);
+ });
+ return allPlaylists.map(playlist => {
+ playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity');
+ return playlist;
+ });
+};
- addParser({
- expression,
- customType,
- dataParser,
- segment
- }) {
- if (typeof dataParser !== 'function') {
- dataParser = line => line;
- }
+const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
+ const sidxKey = generateSidxKey(playlist.sidx);
+ const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
- this.customParsers.push(line => {
- const match = expression.exec(line);
+ if (sidxMatch) {
+ addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri);
+ }
- if (match) {
- this.trigger('data', {
- type: 'custom',
- data: dataParser(line),
- customType,
- segment
- });
- return true;
- }
- });
+ return playlist;
+};
+const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
+ if (!Object.keys(sidxMapping).length) {
+ return playlists;
}
- /**
- * 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
- */
+ for (const i in playlists) {
+ playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
+ }
- addTagMapper({
- expression,
- map
- }) {
- const mapFn = line => {
- if (expression.test(line)) {
- return map(line);
- }
+ 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
+ };
- return line;
- };
+ if (attributes.contentProtection) {
+ playlist.contentProtection = attributes.contentProtection;
+ }
- this.tagMappers.push(mapFn);
+ if (attributes.serviceLocation) {
+ playlist.attributes.serviceLocation = attributes.serviceLocation;
}
-}
+ if (sidx) {
+ playlist.sidx = sidx;
+ }
-const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
+ if (isAudioOnly) {
+ playlist.attributes.AUDIO = 'audio';
+ playlist.attributes.SUBTITLES = 'subs';
+ }
-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.
+ 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 setHoldBack = function (manifest) {
- const {
- serverControl,
- targetDuration,
- partTargetDuration
- } = manifest;
+ const m3u8Attributes = {
+ NAME: attributes.id,
+ BANDWIDTH: attributes.bandwidth,
+ ['PROGRAM-ID']: 1
+ };
- if (!serverControl) {
- return;
+ if (attributes.codecs) {
+ m3u8Attributes.CODECS = attributes.codecs;
}
- const tag = '#EXT-X-SERVER-CONTROL';
- const hb = 'holdBack';
- const phb = 'partHoldBack';
- const minTargetDuration = targetDuration && targetDuration * 3;
- const minPartDuration = partTargetDuration && partTargetDuration * 2;
+ 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 (targetDuration && !serverControl.hasOwnProperty(hb)) {
- serverControl[hb] = minTargetDuration;
- this.trigger('info', {
- message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
- });
+ if (attributes.serviceLocation) {
+ vttPlaylist.attributes.serviceLocation = attributes.serviceLocation;
}
- 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
+ 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 (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 (!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 (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
- * @extends Stream
- */
-
+ if (typeof mainPlaylist === 'undefined' && role === 'main') {
+ mainPlaylist = playlist;
+ mainPlaylist.default = true;
+ }
-class Parser extends _videojs_vhs_utils_es_stream_js__WEBPACK_IMPORTED_MODULE_0__["default"] {
- constructor() {
- super();
- this.lineStream = new LineStream();
- this.parseStream = new ParseStream();
- this.lineStream.pipe(this.parseStream);
- /* eslint-disable consistent-this */
+ return a;
+ }, {}); // if no playlists have role "main", mark the first as main
- const self = this;
- /* eslint-enable consistent-this */
+ if (!mainPlaylist) {
+ const firstLabel = Object.keys(formattedPlaylists)[0];
+ formattedPlaylists[firstLabel].default = true;
+ }
- const uris = [];
- let currentUri = {}; // if specified, the active EXT-X-MAP definition
+ return formattedPlaylists;
+};
+const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
+ return playlists.reduce((a, playlist) => {
+ const label = playlist.attributes.label || playlist.attributes.lang || 'text';
- let currentMap; // if specified, the active decryption key
+ if (!a[label]) {
+ a[label] = {
+ language: label,
+ default: false,
+ autoselect: false,
+ playlists: [],
+ uri: ''
+ };
+ }
- let key;
- let hasParts = false;
+ a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
+ return a;
+ }, {});
+};
- const noop = function () {};
+const organizeCaptionServices = captionServices => captionServices.reduce((svcObj, svc) => {
+ if (!svc) {
+ return svcObj;
+ }
- 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.
+ svc.forEach(service => {
+ const {
+ channel,
+ language
+ } = service;
+ svcObj[language] = {
+ autoselect: false,
+ default: false,
+ instreamId: channel,
+ language
+ };
- const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
+ if (service.hasOwnProperty('aspectRatio')) {
+ svcObj[language].aspectRatio = service.aspectRatio;
+ }
- let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
+ if (service.hasOwnProperty('easyReader')) {
+ svcObj[language].easyReader = service.easyReader;
+ }
- this.manifest = {
- allowCache: true,
- discontinuityStarts: [],
- 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
+ if (service.hasOwnProperty('3D')) {
+ svcObj[language]['3D'] = service['3D'];
+ }
+ });
+ return svcObj;
+}, {});
- let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
+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
+ };
- 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 (attributes.frameRate) {
+ playlist.attributes['FRAME-RATE'] = attributes.frameRate;
+ }
- if (!currentUri.map && currentMap) {
- currentUri.map = currentMap;
- }
+ if (attributes.contentProtection) {
+ playlist.contentProtection = attributes.contentProtection;
+ }
- if (!currentUri.key && key) {
- currentUri.key = key;
- }
+ if (attributes.serviceLocation) {
+ playlist.attributes.serviceLocation = attributes.serviceLocation;
+ }
- if (!currentUri.timeline && typeof currentTimeline === 'number') {
- currentUri.timeline = currentTimeline;
- }
+ if (sidx) {
+ playlist.sidx = sidx;
+ }
- this.manifest.preloadSegment = currentUri;
- }); // update the manifest with the m3u8 entry from the parse stream
+ return playlist;
+};
- this.parseStream.on('data', function (entry) {
- let mediaGroup;
- let rendition;
- ({
- tag() {
- // switch based on the tag type
- (({
- version() {
- if (entry.version) {
- this.manifest.version = entry.version;
- }
- },
+const videoOnly = ({
+ attributes
+}) => attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
- 'allow-cache'() {
- this.manifest.allowCache = entry.allowed;
+const audioOnly = ({
+ attributes
+}) => attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
- if (!('allowed' in entry)) {
- this.trigger('info', {
- message: 'defaulting allowCache to YES'
- });
- this.manifest.allowCache = true;
- }
- },
+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
+ */
- byterange() {
- const byterange = {};
+/**
+ * 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
+ */
- 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;
- }
- }
+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 ('offset' in entry) {
- currentUri.byterange = byterange;
- byterange.offset = entry.offset;
- }
+ if (!playlist.segments) {
+ return;
+ }
- lastByterangeEnd = byterange.offset + byterange.length;
- },
+ 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
+ */
- endlist() {
- this.manifest.endList = true;
- },
+const flattenMediaGroupPlaylists = mediaGroupObject => {
+ if (!mediaGroupObject) {
+ return [];
+ }
- inf() {
- if (!('mediaSequence' in this.manifest)) {
- this.manifest.mediaSequence = 0;
- this.trigger('info', {
- message: 'defaulting media sequence to zero'
- });
- }
+ 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
- if (!('discontinuitySequence' in this.manifest)) {
- this.manifest.discontinuitySequence = 0;
- this.trigger('info', {
- message: 'defaulting discontinuity sequence to zero'
- });
- }
- if (entry.duration > 0) {
- currentUri.duration = entry.duration;
- }
+ 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 (entry.duration === 0) {
- currentUri.duration = 0.01;
- this.trigger('info', {
- message: 'updating zero segment duration to a small value'
- });
- }
+ if (minimumUpdatePeriod >= 0) {
+ manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
+ }
- this.manifest.segments = uris;
- },
+ if (locations) {
+ manifest.locations = locations;
+ }
- key() {
- if (!entry.attributes) {
- this.trigger('warn', {
- message: 'ignoring key declaration without attribute list'
- });
- return;
- } // clear the active encryption key
+ if (contentSteering) {
+ manifest.contentSteering = contentSteering;
+ }
+ if (type === 'dynamic') {
+ manifest.suggestedPresentationDelay = suggestedPresentationDelay;
+ }
- if (entry.attributes.METHOD === 'NONE') {
- key = null;
- return;
- }
+ if (eventStream && eventStream.length > 0) {
+ manifest.eventStream = eventStream;
+ }
- if (!entry.attributes.URI) {
- this.trigger('warn', {
- message: 'ignoring key declaration without URI'
- });
- return;
- }
+ 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 (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
- this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
+ if (organizedAudioGroup) {
+ manifest.mediaGroups.AUDIO.audio = organizedAudioGroup;
+ }
- this.manifest.contentProtection['com.apple.fps.1_0'] = {
- attributes: entry.attributes
- };
- return;
- }
+ if (organizedVttGroup) {
+ manifest.mediaGroups.SUBTITLES.subs = organizedVttGroup;
+ }
- if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
- this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
+ if (captions.length) {
+ manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
+ }
- 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 (previousManifest) {
+ return positionManifestOnTimeline({
+ oldManifest: previousManifest,
+ newManifest: manifest
+ });
+ }
+ return manifest;
+};
- if (entry.attributes.KEYFORMAT === widevineUuid) {
- const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
+/**
+ * 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
+ */
- 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'
- });
- }
+const parseByTimeline = (attributes, segmentTimeline) => {
+ const {
+ type,
+ minimumUpdatePeriod = 0,
+ media = '',
+ sourceDuration,
+ timescale = 1,
+ startNumber = 1,
+ periodStart: timeline
+ } = attributes;
+ const segments = [];
+ let time = -1;
- if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
- this.trigger('warn', {
- message: 'invalid key URI provided for Widevine'
- });
- return;
- }
+ 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 (!(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
+ 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;
+ }
- 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: (0,_videojs_vhs_utils_es_decode_b64_to_uint8_array_js__WEBPACK_IMPORTED_MODULE_2__["default"])(entry.attributes.URI.split(',')[1])
- };
- return;
- }
+ let count;
- if (!entry.attributes.METHOD) {
- this.trigger('warn', {
- message: 'defaulting key method to AES-128'
- });
- } // setup an encryption key for upcoming segments
+ 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;
+ }
- key = {
- method: entry.attributes.METHOD || 'AES-128',
- uri: entry.attributes.URI
- };
+ const end = startNumber + segments.length + count;
+ let number = startNumber + segments.length;
- if (typeof entry.attributes.IV !== 'undefined') {
- key.iv = entry.attributes.IV;
- }
- },
+ while (number < end) {
+ segments.push({
+ number,
+ duration: duration / timescale,
+ time,
+ timeline
+ });
+ time += duration;
+ number++;
+ }
+ }
- 'media-sequence'() {
- if (!isFinite(entry.number)) {
- this.trigger('warn', {
- message: 'ignoring invalid media sequence: ' + entry.number
- });
- return;
- }
+ return segments;
+};
- this.manifest.mediaSequence = entry.number;
- },
+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
+ */
- 'discontinuity-sequence'() {
- if (!isFinite(entry.number)) {
- this.trigger('warn', {
- message: 'ignoring invalid discontinuity sequence: ' + entry.number
- });
- return;
- }
+/**
+ * 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
+ */
- this.manifest.discontinuitySequence = entry.number;
- currentTimeline = entry.number;
- },
+const identifierReplacement = values => (match, identifier, format, width) => {
+ if (match === '$$') {
+ // escape sequence
+ return '$';
+ }
- 'playlist-type'() {
- if (!/VOD|EVENT/.test(entry.playlistType)) {
- this.trigger('warn', {
- message: 'ignoring unknown playlist type: ' + entry.playlist
- });
- return;
- }
+ if (typeof values[identifier] === 'undefined') {
+ return match;
+ }
- this.manifest.playlistType = entry.playlistType;
- },
+ const value = '' + values[identifier];
- map() {
- currentMap = {};
+ if (identifier === 'RepresentationID') {
+ // Format tag shall not be present with RepresentationID
+ return value;
+ }
- if (entry.uri) {
- currentMap.uri = entry.uri;
- }
+ if (!format) {
+ width = 1;
+ } else {
+ width = parseInt(width, 10);
+ }
- if (entry.byterange) {
- currentMap.byterange = entry.byterange;
- }
+ if (value.length >= width) {
+ return value;
+ }
- if (key) {
- currentMap.key = key;
- }
- },
+ 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
+ */
- 'stream-inf'() {
- this.manifest.playlists = uris;
- this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
+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
+ */
- if (!entry.attributes) {
- this.trigger('warn', {
- message: 'ignoring empty stream-inf attributes'
- });
- return;
- }
+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 (!currentUri.attributes) {
- currentUri.attributes = {};
- }
+ if (attributes.duration) {
+ return parseByDuration(attributes);
+ }
- (0,_babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_1__["default"])(currentUri.attributes, entry.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
+ */
- media() {
- this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
+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.
- 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 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: (0,_videojs_vhs_utils_es_resolve_url__WEBPACK_IMPORTED_MODULE_0__["default"])(attributes.baseUrl || '', uri),
+ map: mapSegment,
+ number: segment.number,
+ presentationTime
+ };
+ return map;
+ });
+};
- 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
+/**
+ * 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
+ */
- rendition = {
- default: /yes/i.test(entry.attributes.DEFAULT)
- };
+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
+ */
- if (rendition.default) {
- rendition.autoselect = true;
- } else {
- rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
- }
- if (entry.attributes.LANGUAGE) {
- rendition.language = entry.attributes.LANGUAGE;
- }
+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 (entry.attributes.URI) {
- rendition.uri = entry.attributes.URI;
- }
+ if (!duration && !segmentTimeline || duration && segmentTimeline) {
+ throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
+ }
- if (entry.attributes['INSTREAM-ID']) {
- rendition.instreamId = entry.attributes['INSTREAM-ID'];
- }
+ const segmentUrlMap = segmentUrls.map(segmentUrlObject => SegmentURLToSegmentObject(attributes, segmentUrlObject));
+ let segmentTimeInfo;
- if (entry.attributes.CHARACTERISTICS) {
- rendition.characteristics = entry.attributes.CHARACTERISTICS;
- }
+ if (duration) {
+ segmentTimeInfo = parseByDuration(attributes);
+ }
- if (entry.attributes.FORCED) {
- rendition.forced = /yes/i.test(entry.attributes.FORCED);
- } // insert the new rendition
+ 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.
- mediaGroup[entry.attributes.NAME] = rendition;
- },
+ const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
- discontinuity() {
- currentTimeline += 1;
- currentUri.discontinuity = true;
- this.manifest.discontinuityStarts.push(uris.length);
- },
+ 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).
- '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;
- }
+ }).filter(segment => segment);
+ return segments;
+};
- currentUri.dateTimeString = entry.dateTimeString;
- currentUri.dateTimeObject = entry.dateTimeObject;
- },
+const generateSegments = ({
+ attributes,
+ segmentInfo
+}) => {
+ let segmentAttributes;
+ let segmentsFn;
- targetduration() {
- if (!isFinite(entry.duration) || entry.duration < 0) {
- this.trigger('warn', {
- message: 'ignoring invalid target duration: ' + entry.duration
- });
- return;
- }
+ 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);
+ }
- this.manifest.targetDuration = entry.duration;
- setHoldBack.call(this, this.manifest);
- },
+ const segmentsInfo = {
+ attributes
+ };
- start() {
- if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
- this.trigger('warn', {
- message: 'ignoring start declaration without appropriate attribute list'
- });
- return;
- }
+ if (!segmentsFn) {
+ return segmentsInfo;
+ }
- this.manifest.start = {
- timeOffset: entry.attributes['TIME-OFFSET'],
- precise: entry.attributes.PRECISE
- };
- },
+ 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.
- 'cue-out'() {
- currentUri.cueOut = entry.data;
- },
+ 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;
+ }
- 'cue-out-cont'() {
- currentUri.cueOutCont = entry.data;
- },
+ segmentsInfo.attributes = segmentAttributes;
+ segmentsInfo.segments = segments; // This is a sidx box without actual segment information
- 'cue-in'() {
- currentUri.cueIn = entry.data;
- },
+ if (segmentInfo.base && segmentAttributes.indexRange) {
+ segmentsInfo.sidx = segments[0];
+ segmentsInfo.segments = [];
+ }
- '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.daterange = this.manifest.daterange || [];
- this.manifest.daterange.push(camelCaseKeys(entry.attributes));
- const index = this.manifest.daterange.length - 1;
- this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']);
- const daterange = this.manifest.daterange[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.setSeconds(startDate.getSeconds() + daterange.duration);
- this.manifest.daterange[index].endDate = new Date(newDateInSeconds);
- }
-
- if (daterange && !this.manifest.dateTimeString) {
- this.trigger('warn', {
- message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
- });
- }
-
- if (!daterangeTags[daterange.id]) {
- daterangeTags[daterange.id] = daterange;
- } else {
- for (const attribute in daterangeTags[daterange.id]) {
- if (daterangeTags[daterange.id][attribute] !== daterange[attribute]) {
- this.trigger('warn', {
- message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes and same attribute values'
- });
- break;
- }
- }
- }
- },
-
- 'independent-segments'() {
- this.manifest.independentSegments = true;
- }
-
- })[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
+ return segmentsInfo;
+};
+const toPlaylists = representations => representations.map(generateSegments);
- if (currentMap) {
- currentUri.map = currentMap;
- } // reset the last byterange end as it needs to be 0 between parts
+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));
+};
- lastPartByterangeEnd = 0; // prepare for the next URI
+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
- currentUri = {};
- },
+ const durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
+ const match = durationRegex.exec(str);
- comment() {// comments are not important for playback
- },
+ if (!match) {
+ return 0;
+ }
- 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;
- }
- }
+ 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'
- })[entry.type].call(self);
- });
+ if (dateRegex.test(str)) {
+ str += 'Z';
}
- warnOnMissingAttributes_(identifier, attributes, required) {
- const missing = [];
- required.forEach(function (key) {
- if (!attributes.hasOwnProperty(key)) {
- missing.push(key);
- }
- });
+ return Date.parse(str);
+};
- if (missing.length) {
- this.trigger('warn', {
- message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`
- });
- }
- }
+const parsers = {
/**
- * Parse the input string and update the manifest object.
+ * Specifies the duration of the entire Media Presentation. Format is a duration string
+ * as specified in ISO 8601
*
- * @param {string} chunk a potentially incomplete portion of the manifest
+ * @param {string} value
+ * value of attribute as a string
+ * @return {number}
+ * The duration in seconds
*/
+ mediaPresentationDuration(value) {
+ return parseDuration(value);
+ },
-
- 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.
+ * 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;
+ },
-
- end() {
- // flush any buffered input
- this.lineStream.push('\n');
- this.trigger('end');
- }
/**
- * Add an additional parser for non-standard tags
+ * Specifies the smallest period between potential changes to the MPD. Format is a
+ * duration string as specified in ISO 8601
*
- * @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.type the 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
+ * @param {string} value
+ * value of attribute as a string
+ * @return {number}
+ * The duration in seconds
*/
+ minimumUpdatePeriod(value) {
+ return parseDuration(value);
+ },
-
- addParser(options) {
- this.parseStream.addParser(options);
- }
/**
- * Add a custom header mapper
+ * Specifies the suggested presentation delay. Format is a
+ * duration string as specified in ISO 8601
*
- * @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
+ * @param {string} value
+ * value of attribute as a string
+ * @return {number}
+ * The duration in seconds
*/
+ suggestedPresentationDelay(value) {
+ return parseDuration(value);
+ },
-
- addTagMapper(options) {
- this.parseStream.addTagMapper(options);
- }
-
-}
-
-
-
-
-/***/ }),
-
-/***/ "./node_modules/m3u8-parser/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js":
-/*!**************************************************************************************************!*\
- !*** ./node_modules/m3u8-parser/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js ***!
- \**************************************************************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (/* binding */ decodeB64ToUint8Array)
-/* harmony export */ });
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_0__);
-
-
-var atob = function atob(s) {
- return (global_window__WEBPACK_IMPORTED_MODULE_0___default().atob) ? global_window__WEBPACK_IMPORTED_MODULE_0___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;
-}
-
-/***/ }),
-
-/***/ "./node_modules/m3u8-parser/node_modules/@videojs/vhs-utils/es/stream.js":
-/*!*******************************************************************************!*\
- !*** ./node_modules/m3u8-parser/node_modules/@videojs/vhs-utils/es/stream.js ***!
- \*******************************************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (/* binding */ Stream)
-/* harmony export */ });
-/**
- * @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.
+ * specifices the type of mpd. Can be either "static" or "dynamic"
*
- * @param {string} type the event name
- * @param {Function} listener the callback to be invoked when an event of
- * the specified type occurs
+ * @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);
+ },
- 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.
+ * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
+ * Format is a duration string as specified in ISO 8601
*
- * @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
+ * @param {string} value
+ * value of attribute as a string
+ * @return {number}
+ * The duration in seconds
*/
- ;
+ start(value) {
+ return parseDuration(value);
+ },
- _proto.off = function off(type, listener) {
- if (!this.listeners[type]) {
- return false;
- }
+ /**
+ * 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);
+ },
- 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.
+ /**
+ * 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);
+ },
- 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.
+ * Specifies the bitrate of the representation
*
- * @param {string} type the event name
+ * @param {string} value
+ * value of attribute as a string
+ * @return {number}
+ * The parsed bandwidth
*/
- ;
+ bandwidth(value) {
+ return parseInt(value, 10);
+ },
- _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.
+ * 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);
+ },
- _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.
+ * Specifies the number of the first Media Segment in this Representation in the Period
*
- * @param {Stream} destination the stream that will receive all `data` events
- * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
+ * @param {string} value
+ * value of attribute as a string
+ * @return {number}
+ * The parsed number
*/
- ;
-
- _proto.pipe = function pipe(destination) {
- this.on('data', function (data) {
- destination.push(data);
- });
- };
-
- return Stream;
-}();
-
+ 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);
+ },
-/***/ "./node_modules/mpd-parser/dist/mpd-parser.es.js":
-/*!*******************************************************!*\
- !*** ./node_modules/mpd-parser/dist/mpd-parser.es.js ***!
- \*******************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
+ /**
+ * 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);
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ VERSION: () => (/* binding */ VERSION),
-/* harmony export */ addSidxSegmentsToPlaylist: () => (/* binding */ addSidxSegmentsToPlaylist$1),
-/* harmony export */ generateSidxKey: () => (/* binding */ generateSidxKey),
-/* harmony export */ inheritAttributes: () => (/* binding */ inheritAttributes),
-/* harmony export */ parse: () => (/* binding */ parse),
-/* harmony export */ parseUTCTiming: () => (/* binding */ parseUTCTiming),
-/* harmony export */ stringToMpdXml: () => (/* binding */ stringToMpdXml),
-/* harmony export */ toM3u8: () => (/* binding */ toM3u8),
-/* harmony export */ toPlaylists: () => (/* binding */ toPlaylists)
-/* harmony export */ });
-/* harmony import */ var _videojs_vhs_utils_es_resolve_url__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @videojs/vhs-utils/es/resolve-url */ "./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/resolve-url.js");
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var _videojs_vhs_utils_es_media_groups__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @videojs/vhs-utils/es/media-groups */ "./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/media-groups.js");
-/* harmony import */ var _videojs_vhs_utils_es_decode_b64_to_uint8_array__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @videojs/vhs-utils/es/decode-b64-to-uint8-array */ "./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js");
-/* harmony import */ var _xmldom_xmldom__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @xmldom/xmldom */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/index.js");
-/*! @name mpd-parser @version 1.2.2 @license Apache-2.0 */
+ 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);
+ },
-var version = "1.2.2";
+ /**
+ * 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;
+ }
-const isObject = obj => {
- return !!obj && typeof obj === 'object';
};
+/**
+ * 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 merge = (...objects) => {
- return objects.reduce((result, source) => {
- if (typeof source !== 'object') {
- return result;
- }
+const parseAttributes = el => {
+ if (!(el && el.attributes)) {
+ return {};
+ }
- Object.keys(source).forEach(key => {
- if (Array.isArray(result[key]) && Array.isArray(source[key])) {
- result[key] = result[key].concat(source[key]);
- } else if (isObject(result[key]) && isObject(source[key])) {
- result[key] = merge(result[key], source[key]);
- } else {
- result[key] = source[key];
- }
- });
- return result;
+ return from(el.attributes).reduce((a, e) => {
+ const parseFn = parsers[e.name] || parsers.DEFAULT;
+ a[e.name] = parseFn(e.value);
+ return a;
}, {});
};
-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 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'
};
-const flatten = lists => lists.reduce((x, y) => x.concat(y), []);
-const from = list => {
- if (!list.length) {
- return [];
+/**
+ * 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;
}
- const result = [];
+ return flatten(references.map(function (reference) {
+ return baseUrlElements.map(function (baseUrlElement) {
+ const initialBaseUrl = getContent(baseUrlElement);
+ const resolvedBaseUrl = (0,_videojs_vhs_utils_es_resolve_url__WEBPACK_IMPORTED_MODULE_0__["default"])(reference.baseUrl, initialBaseUrl);
+ const finalBaseUrl = merge(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
- for (let i = 0; i < list.length; i++) {
- result.push(list[i]);
- }
+ if (resolvedBaseUrl !== initialBaseUrl && !finalBaseUrl.serviceLocation && reference.serviceLocation) {
+ finalBaseUrl.serviceLocation = reference.serviceLocation;
+ }
- return result;
+ return finalBaseUrl;
+ });
+ }));
};
-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.
+ * Contains all Segment information for its containing AdaptationSet
*
- * @param {Array} list - list of lists to get the union of
- * @param {Function} keyFunction - the function to use as a key for each element
+ * @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
*
- * @return {Array} the union of the arrays
+ * @param {Node} adaptationSet
+ * The AdaptationSet node to get Segment information from
+ * @return {SegmentInformation}
+ * The Segment information contained within the provided AdaptationSet
*/
-const union = (lists, keyFunction) => {
- return values(lists.reduce((acc, list) => {
- list.forEach(el => {
- acc[keyFunction(el)] = el;
- });
- return acc;
- }, {}));
-};
+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'
+ }, 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.
-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'
-};
+ const template = segmentTemplate && parseAttributes(segmentTemplate);
+
+ if (template && segmentInitialization) {
+ template.initialization = segmentInitialization && 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 => parseAttributes(s)),
+ list: segmentList && merge(parseAttributes(segmentList), {
+ segmentUrls,
+ initialization: parseAttributes(segmentInitialization)
+ }),
+ base: segmentBase && merge(parseAttributes(segmentBase), {
+ initialization: parseAttributes(segmentInitialization)
+ })
+ };
+ Object.keys(segmentInfo).forEach(key => {
+ if (!segmentInfo[key]) {
+ delete segmentInfo[key];
+ }
+ });
+ return segmentInfo;
+};
/**
- * @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
+ * Contains Segment information and attributes needed to construct a Playlist object
+ * from a Representation
*
- * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
+ * @typedef {Object} RepresentationInformation
+ * @property {SegmentInformation} segmentInfo
+ * Segment information for this Representation
+ * @property {Object} attributes
+ * Inherited attributes for this Representation
*/
/**
- * 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
+ * Maps a Representation node to an object containing Segment information and attributes
*
- * @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
+ * @name inheritBaseUrlsCallback
+ * @function
+ * @param {Node} representation
+ * Representation node from the mpd
+ * @return {RepresentationInformation}
+ * Representation information needed to construct a Playlist object
*/
-const urlTypeToSegment = ({
- baseUrl = '',
- source = '',
- range = '',
- indexRange = ''
-}) => {
- const segment = {
- uri: source,
- resolvedUri: (0,_videojs_vhs_utils_es_resolve_url__WEBPACK_IMPORTED_MODULE_0__["default"])(baseUrl || '', source)
- };
+/**
+ * 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
+ */
- if (range || indexRange) {
- const rangeStr = range ? range : indexRange;
- const ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible
+const inheritBaseUrls = (adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) => representation => {
+ const repBaseUrlElements = findChildren(representation, 'BaseURL');
+ const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
+ const attributes = merge(adaptationSetAttributes, 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
+ */
- let startRange = (global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt) ? global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(ranges[0]) : parseInt(ranges[0], 10);
- let endRange = (global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt) ? global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER
+const generateKeySystemInformation = contentProtectionNodes => {
+ return contentProtectionNodes.reduce((acc, node) => {
+ const attributes = 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 (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') {
- startRange = Number(startRange);
+ if (attributes.schemeIdUri) {
+ attributes.schemeIdUri = attributes.schemeIdUri.toLowerCase();
}
- if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') {
- endRange = Number(endRange);
- }
+ const keySystem = keySystemsMap[attributes.schemeIdUri];
- let length;
+ if (keySystem) {
+ acc[keySystem] = {
+ attributes
+ };
+ const psshNode = findChildren(node, 'cenc:pssh')[0];
- if (typeof endRange === 'bigint' || typeof startRange === 'bigint') {
- length = global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(endRange) - global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(startRange) + global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(1);
- } else {
- length = endRange - startRange + 1;
+ if (psshNode) {
+ const pssh = getContent(psshNode);
+ acc[keySystem].pssh = pssh && (0,_videojs_vhs_utils_es_decode_b64_to_uint8_array__WEBPACK_IMPORTED_MODULE_3__["default"])(pssh);
+ }
}
- if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) {
- length = Number(length);
- } // byterange should be inclusive according to
- // RFC 2616, Clause 14.35.1
+ return acc;
+ }, {});
+}; // defined in ANSI_SCTE 214-1 2016
- segment.byterange = {
- length,
- offset: startRange
- };
- }
+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
- return segment;
-};
-const byteRangeToString = byterange => {
- // `endRange` is one less than `offset + length` because the HTTP range
- // header uses inclusive ranges
- let endRange;
+ language = value;
- if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
- endRange = global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(byterange.offset) + global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(byterange.length) - global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(1);
- } else {
- endRange = byterange.offset + byterange.length - 1;
- }
+ if (/^CC\d=/.test(value)) {
+ [channel, language] = value.split('=');
+ } else if (/^CC\d$/.test(value)) {
+ channel = value;
+ }
- return `${byterange.offset}-${endRange}`;
-};
+ 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
+ };
-/**
- * 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.
- */
+ if (/=/.test(value)) {
+ const [channel, opts = ''] = value.split('=');
+ flags.channel = channel;
+ flags.language = value;
+ opts.split(',').forEach(opt => {
+ const [name, val] = opt.split(':');
-const parseEndNumber = endNumber => {
- if (endNumber && typeof endNumber !== 'number') {
- endNumber = parseInt(endNumber, 10);
- }
+ 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 (isNaN(endNumber)) {
- return null;
- }
+ if (flags.channel) {
+ flags.channel = 'SERVICE' + flags.channel;
+ }
- return endNumber;
+ return flags;
+ });
+ }
};
/**
- * Functions for calculating the range of available segments in static and dynamic
- * manifests.
+ * 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 = parseAttributes(eventStream);
+ const schemeIdUri = eventStreamAttributes.schemeIdUri; // find all Events per EventStream tag and map to return objects
-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 findChildren(eventStream, 'Event').map(event => {
+ const eventAttributes = 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 {
- start: 0,
- end: endNumber
+ 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
+ */
- if (typeof periodDuration === 'number') {
- return {
- start: 0,
- end: periodDuration / segmentDuration
- };
- }
+/**
+ * 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
+ */
- return {
- start: 0,
- end: sourceDuration / segmentDuration
- };
- },
+const toRepresentations = (periodAttributes, periodBaseUrls, periodSegmentInfo) => adaptationSet => {
+ const adaptationSetAttributes = parseAttributes(adaptationSet);
+ const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
+ const role = findChildren(adaptationSet, 'Role')[0];
+ const roleAttributes = {
+ role: parseAttributes(role)
+ };
+ let attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
+ const accessibility = findChildren(adaptationSet, 'Accessibility')[0];
+ const captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility));
- /**
- * 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.
+ if (captionServices) {
+ attrs = merge(attrs, {
+ captionServices
+ });
+ }
- const now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock.
- // Convert the period start time to EPOCH.
+ const label = findChildren(adaptationSet, 'Label')[0];
- const periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update.
+ if (label && label.childNodes.length) {
+ const labelVal = label.childNodes[0].nodeValue.trim();
+ attrs = merge(attrs, {
+ label: labelVal
+ });
+ }
- 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)
- };
+ 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)));
};
/**
- * Maps a range of numbers to objects with information needed to build the corresponding
- * segment list
+ * Contains all period information for mapping nodes onto adaptation sets.
*
- * @name toSegmentsCallback
+ * @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 {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
+ * @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 a range of numbers to
- * information needed to build the segment list.
+ * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
+ * Representation information objects
*
- * @param {Object} attributes
- * Inherited MPD attributes
- * @return {toSegmentsCallback}
+ * @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 toSegments = attributes => number => {
- const {
- duration,
- timescale = 1,
- periodStart,
- startNumber = 1
- } = attributes;
- return {
- number: startNumber + number,
- duration: duration / timescale,
- timeline: periodStart,
- time: number * duration
- };
+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)));
};
/**
- * 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.
+ * Tranforms an array of content steering nodes into an object
+ * containing CDN content steering information from the MPD manifest.
*
- * @param {Object} attributes
- * Inherited MPD attributes
- * @return {{number: number, duration: number, time: number, timeline: number}[]}
- * List of Objects with segment timing and duration info
+ * 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 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 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
- const sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration
- segments[index].duration = sectionDuration - duration / timescale * index;
+ if (!contentSteeringNodes.length) {
+ return null;
}
- return segments;
-};
+ const infoFromContentSteeringTag = merge({
+ serverURL: getContent(contentSteeringNodes[0])
+ }, 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;
+};
/**
- * 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.
+ * Gets Period@start property for a given period.
*
- * @param {Object} attributes
- * Object containing all inherited attributes from parent elements with attribute
- * names as keys
- * @return {Object.} list of segments
+ * @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 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)
+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 (!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 (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') {
+ return priorPeriodAttributes.start + priorPeriodAttributes.duration;
+ } // (3)
- 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.
+ 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.
- segment.presentationTime = presentationTime || periodStart;
- segment.number = number;
- return [segment];
+ return null;
};
/**
- * 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.
+ * Traverses the mpd xml tree to generate a list of Representation information objects
+ * that have inherited attributes from parent nodes
*
- * @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
+ * @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 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 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');
- const timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
+ if (!periodNodes.length) {
+ throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
+ }
- 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
+ const locations = findChildren(mpd, 'Location');
+ const mpdAttributes = 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'.
- let startIndex; // eslint-disable-next-line
+ mpdAttributes.type = mpdAttributes.type || 'static';
+ mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
+ mpdAttributes.NOW = NOW;
+ mpdAttributes.clientOffset = clientOffset;
- if (typeof sidx.firstOffset === 'bigint') {
- startIndex = global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(sidxEnd) + sidx.firstOffset;
- } else {
- startIndex = sidxEnd + sidx.firstOffset;
+ if (locations.length) {
+ mpdAttributes.locations = locations.map(getContent);
}
- for (let i = 0; i < mediaReferences.length; i++) {
- const reference = sidx.references[i]; // size of the referenced (sub)segment
+ 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.
- const size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale
- // this will be converted to seconds when generating segments
+ periodNodes.forEach((node, index) => {
+ const attributes = parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary
+ // for this period.
- const duration = reference.subsegmentDuration; // should be an inclusive range
+ 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))
+ };
+};
- let endIndex; // eslint-disable-next-line
+const stringToMpdXml = manifestString => {
+ if (manifestString === '') {
+ throw new Error(errors.DASH_EMPTY_MANIFEST);
+ }
- if (typeof startIndex === 'bigint') {
- endIndex = startIndex + global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(size) - global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(1);
- } else {
- endIndex = startIndex + size - 1;
- }
+ const parser = new _xmldom_xmldom__WEBPACK_IMPORTED_MODULE_4__.DOMParser();
+ let xml;
+ let mpd;
- const indexRange = `${startIndex}-${endIndex}`;
- const attributes = {
- baseUrl,
- timescale,
- timeline,
- periodStart,
- presentationTime,
- number,
- duration,
- sourceDuration,
- indexRange,
- type
- };
- const segment = segmentsFromBase(attributes)[0];
+ 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 (initSegment) {
- segment.map = initSegment;
- }
+ if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
+ throw new Error(errors.DASH_INVALID_XML);
+ }
- segments.push(segment);
+ return mpd;
+};
- if (typeof startIndex === 'bigint') {
- startIndex += global_window__WEBPACK_IMPORTED_MODULE_1___default().BigInt(size);
- } else {
- startIndex += size;
- }
+/**
+ * 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
+ */
- presentationTime += duration / timescale;
- number++;
+const parseUTCTimingScheme = mpd => {
+ const UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
+
+ if (!UTCTimingNode) {
+ return null;
}
- playlist.segments = segments;
- return playlist;
-};
+ const attributes = parseAttributes(UTCTimingNode);
-const SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen)
+ switch (attributes.schemeIdUri) {
+ case 'urn:mpeg:dash:utc:http-head:2014':
+ case 'urn:mpeg:dash:utc:http-head:2012':
+ attributes.method = 'HEAD';
+ break;
-const TIME_FUDGE = 1 / 60;
-/**
- * Given a list of timelineStarts, combines, dedupes, and sorts them.
+ 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 = 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.
*
- * @param {TimelineStart[]} timelineStarts - list of timeline starts
+ * 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.
*
- * @return {TimelineStart[]} the combined and deduped timeline starts
+ * @param {string} manifestString - the DASH manifest as a string
+ * @param {options} [options] - any options
+ *
+ * @return {Object} the manifest object
*/
-const getUniqueTimelineStarts = timelineStarts => {
- return union(timelineStarts, ({
- timeline
- }) => timeline).sort((a, b) => a.timeline > b.timeline ? 1 : -1);
+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
+ });
};
/**
- * Finds the playlist with the matching NAME attribute.
- *
- * @param {Array} playlists - playlists to search through
- * @param {string} name - the NAME attribute to search for
+ * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
*
- * @return {Object|null} the matching playlist object, or null
+ * @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 findPlaylistWithName = (playlists, name) => {
- for (let i = 0; i < playlists.length; i++) {
- if (playlists[i].attributes.NAME === name) {
- return playlists[i];
- }
- }
- return null;
-};
+const parseUTCTiming = manifestString => parseUTCTimingScheme(stringToMpdXml(manifestString));
+
+
+
+
+/***/ }),
+
+/***/ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js":
+/*!********************************************************************************!*\
+ !*** ./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js ***!
+ \********************************************************************************/
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+
/**
- * Gets a flattened array of media group playlists.
+ * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
*
- * @param {Object} manifest - the main manifest object
+ * Works with anything that has a `length` property and index access properties, including NodeList.
*
- * @return {Array} the media group playlists
+ * @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;
+ }
+ }
+ }
+}
-const getMediaGroupPlaylists = manifest => {
- let mediaGroupPlaylists = [];
- (0,_videojs_vhs_utils_es_media_groups__WEBPACK_IMPORTED_MODULE_2__.forEachMediaGroup)(manifest, SUPPORTED_MEDIA_TYPES, (properties, type, group, label) => {
- mediaGroupPlaylists = mediaGroupPlaylists.concat(properties.playlists || []);
- });
- return mediaGroupPlaylists;
-};
/**
- * Updates the playlist's media sequence numbers.
+ * "Shallow freezes" an object to render it immutable.
+ * Uses `Object.freeze` if available,
+ * otherwise the immutability is only in the type.
*
- * @param {Object} config - options object
- * @param {Object} config.playlist - the playlist to update
- * @param {number} config.mediaSequence - the mediaSequence number to start with
+ * 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
+}
-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.
+ * Since we can not rely on `Object.assign` we provide a simplified version
+ * that is sufficient for our needs.
*
- * If no matching playlist is found, only the discontinuity sequence number of the playlist
- * will be updated.
+ * @param {Object} target
+ * @param {Object | null | undefined} source
*
- * Since early available timelines are not supported, at least one segment must be present.
+ * @returns {Object} target
+ * @throws TypeError if target is not an object
*
- * @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
+ * @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
+}
-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.
+/**
+ * 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',
- const oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME);
+ /**
+ * 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
+ },
- 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.
+ /**
+ * `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',
- if (playlist.sidx) {
- return;
- } // Since we don't yet support early available timelines, we don't need to support
- // playlists with no segments.
+ /**
+ * `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',
+})
- 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.
+/**
+ * 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',
- 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.
+ /**
+ * Checks if `uri` equals `NAMESPACE.HTML`.
+ *
+ * @param {string} [uri]
+ *
+ * @see NAMESPACE.HTML
+ */
+ isHTML: function (uri) {
+ return uri === NAMESPACE.HTML
+ },
- if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) {
- playlist.discontinuitySequence--;
- }
+ /**
+ * The SVG namespace.
+ *
+ * @see http://www.w3.org/2000/svg
+ */
+ SVG: 'http://www.w3.org/2000/svg',
- 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.
+ /**
+ * 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/',
+})
- const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
+exports.assign = assign;
+exports.find = find;
+exports.freeze = freeze;
+exports.MIME_TYPE = MIME_TYPE;
+exports.NAMESPACE = NAMESPACE;
- 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] = [];
- }
+/***/ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom-parser.js":
+/*!*******************************************************************************!*\
+ !*** ./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom-parser.js ***!
+ \*******************************************************************************/
+/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- 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 || '');
+var conventions = __webpack_require__(/*! ./conventions */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js");
+var dom = __webpack_require__(/*! ./dom */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom.js")
+var entities = __webpack_require__(/*! ./entities */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/entities.js");
+var sax = __webpack_require__(/*! ./sax */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/sax.js");
- 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;
- }
+var DOMImplementation = dom.DOMImplementation;
- acc[name].segments.push(...playlist.segments);
- } // bubble up contentProtection, this assumes all DRM content
- // has the same contentProtection
+var NAMESPACE = conventions.NAMESPACE;
+var ParseError = sax.ParseError;
+var XMLReader = sax.XMLReader;
- if (playlist.attributes.contentProtection) {
- acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
- }
- }
+/**
+ * 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')
+}
- 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;
- });
-};
+/**
+ * @typedef Locator
+ * @property {number} [columnNumber]
+ * @property {number} [lineNumber]
+ */
-const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
- const sidxKey = generateSidxKey(playlist.sidx);
- const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
+/**
+ * @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
+ */
- if (sidxMatch) {
- addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri);
- }
+/**
+ * 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:{}};
+}
- return playlist;
-};
-const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
- if (!Object.keys(sidxMapping).length) {
- return playlists;
- }
+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)
+ }
- for (const i in playlists) {
- playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
- }
+ 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;
+}
- 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
- };
+//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;
- if (attributes.contentProtection) {
- playlist.contentProtection = attributes.contentProtection;
- }
+ 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);
+ },
- if (attributes.serviceLocation) {
- playlist.attributes.serviceLocation = attributes.serviceLocation;
- }
+ startCDATA:function() {
+ //used in characters() methods
+ this.cdata = true;
+ },
+ endCDATA:function() {
+ this.cdata = false;
+ },
- if (sidx) {
- playlist.sidx = sidx;
- }
+ 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;
+ }
+}
- if (isAudioOnly) {
- playlist.attributes.AUDIO = 'audio';
- playlist.attributes.SUBTITLES = 'subs';
- }
+/*
+ * @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}
+})
- 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
+/* 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
- attributes.duration = attributes.sourceDuration;
- }
+exports.__DOMHandler = DOMHandler;
+exports.normalizeLineEndings = normalizeLineEndings;
+exports.DOMParser = DOMParser;
- 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
- };
+/***/ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom.js":
+/*!************************************************************************!*\
+ !*** ./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom.js ***!
+ \************************************************************************/
+/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- if (attributes.serviceLocation) {
- vttPlaylist.attributes.serviceLocation = attributes.serviceLocation;
- }
+var conventions = __webpack_require__(/*! ./conventions */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js");
- 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';
+var find = conventions.find;
+var NAMESPACE = conventions.NAMESPACE;
- if (language && !playlist.attributes.label) {
- const roleLabel = role ? ` (${role})` : '';
- label = `${playlist.attributes.lang}${roleLabel}`;
- }
+/**
+ * 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) : []
+}
- if (!a[label]) {
- a[label] = {
- language,
- autoselect: true,
- default: role === 'main',
- playlists: [],
- uri: ''
- };
- }
+/**
+ * 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;
+}
- const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
- a[label].playlists.push(formatted);
+/**
+ * @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, {}))
+}
- if (typeof mainPlaylist === 'undefined' && role === 'main') {
- mainPlaylist = playlist;
- mainPlaylist.default = true;
- }
+/**
+ * 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;
+ }
+}
- return a;
- }, {}); // if no playlists have role "main", mark the first as main
+function copy(src,dest){
+ for(var p in src){
+ if (Object.prototype.hasOwnProperty.call(src, p)) {
+ dest[p] = src[p];
+ }
+ }
+}
- if (!mainPlaylist) {
- const firstLabel = Object.keys(formattedPlaylists)[0];
- formattedPlaylists[firstLabel].default = true;
- }
+/**
+^\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
+ }
+}
- return formattedPlaylists;
-};
-const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
- return playlists.reduce((a, playlist) => {
- const label = playlist.attributes.label || playlist.attributes.lang || 'text';
+// 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;
- if (!a[label]) {
- a[label] = {
- language: label,
- default: false,
- autoselect: false,
- playlists: [],
- uri: ''
- };
- }
+// 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);
- a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
- return a;
- }, {});
+/**
+ * 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)
-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;
+/**
+ * @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 attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
+function LiveNodeList(node,refresh){
+ this._node = node;
+ this._refresh = refresh
+ _updateLiveList(this);
+}
+function _updateLiveList(list){
+ var inc = list._node._inc || list._node.ownerDocument._inc;
+ if (list._inc !== inc) {
+ var ls = list._refresh(list._node);
+ __set__(list,'length',ls.length);
+ if (!list.$$length || ls.length < list.$$length) {
+ for (var i = ls.length; i in list; i++) {
+ if (Object.prototype.hasOwnProperty.call(list, i)) {
+ delete list[i];
+ }
+ }
+ }
+ copy(ls,list);
+ list._inc = inc;
+ }
+}
+LiveNodeList.prototype.item = function(i){
+ _updateLiveList(this);
+ return this[i] || null;
+}
-const audioOnly = ({
- attributes
-}) => attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
+_extends(LiveNodeList,NodeList);
-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
+ * Objects implementing the NamedNodeMap interface are used
+ * to represent collections of nodes that can be accessed by name.
+ * Note that NamedNodeMap does not inherit from NodeList;
+ * NamedNodeMaps are not maintained in any particular order.
+ * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index,
+ * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap,
+ * and does not imply that the DOM specifies an order to these Nodes.
+ * NamedNodeMap objects in the DOM are live.
+ * used for attributes or DocumentType entities
*/
+function NamedNodeMap() {
+};
-/**
- * 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
- */
+function _findNodeIndex(list,node){
+ var i = list.length;
+ while(i--){
+ if(list[i] === node){return i}
+ }
+}
+
+function _addNamedNode(el,list,newAttr,oldAttr){
+ if(oldAttr){
+ list[_findNodeIndex(list,oldAttr)] = newAttr;
+ }else{
+ list[list.length++] = newAttr;
+ }
+ if(el){
+ newAttr.ownerElement = el;
+ var doc = el.ownerDocument;
+ if(doc){
+ oldAttr && _onRemoveAttribute(doc,el,oldAttr);
+ _onAddAttribute(doc,el,newAttr);
+ }
+ }
+}
+function _removeNamedNode(el,list,attr){
+ //console.log('remove attr:'+attr)
+ var i = _findNodeIndex(list,attr);
+ if(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;
-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;
- }
+ },// raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
- playlist.segments.forEach((segment, index) => {
- segment.number = index;
- });
- });
+ //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;
+ }
};
+
/**
- * Given a media group object, flattens all playlists within the media group into a single
- * array.
+ * 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.
*
- * @param {Object} mediaGroupObject - the media group object
+ * __The individual methods describe the differences compared to the specs.__
*
- * @return {Object[]}
- * The media group playlists
+ * @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() {
+}
-const flattenMediaGroupPlaylists = mediaGroupObject => {
- if (!mediaGroupObject) {
- return [];
- }
+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 Object.keys(mediaGroupObject).reduce((acc, label) => {
- const labelContents = mediaGroupObject[label];
- return acc.concat(labelContents.playlists);
- }, []);
+ return node;
+ }
};
-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)
- };
+/**
+ * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247
+ */
- if (minimumUpdatePeriod >= 0) {
- manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
- }
+function Node() {
+};
- if (locations) {
- manifest.locations = locations;
- }
+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;
+ }
+};
- if (contentSteering) {
- manifest.contentSteering = contentSteering;
- }
- if (type === 'dynamic') {
- manifest.suggestedPresentationDelay = suggestedPresentationDelay;
- }
+function _xmlEncoder(c){
+ return c == '<' && '<' ||
+ c == '>' && '>' ||
+ c == '&' && '&' ||
+ c == '"' && '"' ||
+ ''+c.charCodeAt()+';'
+}
- 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);
+copy(NodeType,Node);
+copy(NodeType,Node.prototype);
- if (organizedAudioGroup) {
- manifest.mediaGroups.AUDIO.audio = organizedAudioGroup;
- }
+/**
+ * @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)
+ }
+}
- 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
- });
- }
+function Document(){
+ this.ownerDocument = this;
+}
- return manifest;
-};
+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:'']
+ }
+}
/**
- * 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
+ * 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.
*
- * @return {number}
- * R value to reach the end of the given period
+ * @param {Document} doc
+ * @param {Node} el
+ * @param {Node} [newChild]
+ * @private
*/
-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);
-};
+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];
+ }
+ }
+}
+
/**
- * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
- * timing and duration
+ * Removes the connections between `parentNode` and `child`
+ * and any existing `child.previousSibling` or `child.nextSibling`.
*
- * @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
+ * @see https://github.com/xmldom/xmldom/issues/135
+ * @see https://github.com/xmldom/xmldom/issues/145
*
- * @return {{number: number, duration: number, time: number, timeline: number}[]}
- * List of Objects with segment timing and duration info
+ * @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)
+ );
+}
-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;
- }
+/**
+ * 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)
+ );
+}
- 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;
- }
+/**
+ * Returns true if `node` is a DOCTYPE node
+ * @param {Node} node
+ * @returns {boolean}
+ */
+function isDocTypeNode(node) {
+ return node && node.nodeType === Node.DOCUMENT_TYPE_NODE;
+}
- let count;
+/**
+ * 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;
+}
- 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
+ * 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.
*
- * @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
+ * @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));
+}
/**
- * Returns a function to be used as a callback for String.prototype.replace to replace
- * template identifiers
+ * 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 {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
+ * @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 || [];
-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);
- }
+ function hasElementChildThatIsNotChild(node) {
+ return isElementNode(node) && node !== child;
+ }
- if (value.length >= width) {
- return value;
- }
+ if (find(parentChildNodes, hasElementChildThatIsNotChild)) {
+ return false;
+ }
+ var docTypeNode = find(parentChildNodes, isDocTypeNode);
+ return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
+}
- return `${new Array(width - value.length + 1).join('0')}${value}`;
-};
/**
- * Constructs a segment url from a template string
+ * @private
+ * Steps 1-5 of the checks before inserting and before replacing a child are the same.
*
- * @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
+ * @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
+ );
+ }
+}
-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
+ * @private
+ * Step 6 of the checks before inserting and before replacing a child are different.
*
- * @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
+ * @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 || [];
-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);
- }
+ // 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');
+ }
+ }
+}
- return parseByTimeline(attributes, segmentTimeline);
-};
/**
- * Generates a list of segments using information provided by the SegmentTemplate element
+ * @private
+ * Step 6 of the checks before inserting and before replacing a child are different.
*
- * @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
+ * @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 || [];
-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: (0,_videojs_vhs_utils_es_resolve_url__WEBPACK_IMPORTED_MODULE_0__["default"])(attributes.baseUrl || '', uri),
- map: mapSegment,
- number: segment.number,
- presentationTime
- };
- return map;
- });
-};
+ // 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;
+ }
-/**
- * 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
- */
+ // `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');
+ }
+ }
+}
-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
+ * @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);
+ }
-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);
- }
+ 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;
- const segmentUrlMap = segmentUrls.map(segmentUrlObject => SegmentURLToSegmentObject(attributes, segmentUrlObject));
- let segmentTimeInfo;
+ newFirst.previousSibling = pre;
+ newLast.nextSibling = child;
- if (duration) {
- segmentTimeInfo = parseByDuration(attributes);
- }
- if (segmentTimeline) {
- segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
- }
+ if(pre){
+ pre.nextSibling = newFirst;
+ }else{
+ parent.firstChild = newFirst;
+ }
+ if(child == null){
+ parent.lastChild = newLast;
+ }else{
+ child.previousSibling = newLast;
+ }
+ do{
+ newFirst.parentNode = parent;
+ }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;
+}
- 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.
+/**
+ * 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);
+ return newChild;
+}
- const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
+Document.prototype = {
+ //implementation : null,
+ nodeName : '#document',
+ nodeType : DOCUMENT_NODE,
+ /**
+ * The DocumentType node of the document.
+ *
+ * @readonly
+ * @type DocumentType
+ */
+ doctype : null,
+ documentElement : null,
+ _inc : 1,
- 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;
-};
+ 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);
+ newChild.ownerDocument = this;
+ if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) {
+ this.documentElement = newChild;
+ }
-const generateSegments = ({
- attributes,
- segmentInfo
-}) => {
- let segmentAttributes;
- let segmentsFn;
+ 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);
+ newChild.ownerDocument = 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;
+ },
- 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);
- }
+ /**
+ * 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;
+ });
+ },
- const segmentsInfo = {
- attributes
- };
+ //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);
- 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.
+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);
+ },
- 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;
- }
+ //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);
+ },
- segmentsInfo.attributes = segmentAttributes;
- segmentsInfo.segments = segments; // This is a sidx box without actual segment information
+ 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);
+ },
- if (segmentInfo.base && segmentAttributes.indexRange) {
- segmentsInfo.sidx = segments[0];
- segmentsInfo.segments = [];
- }
+ 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;
- return segmentsInfo;
+ });
+ }
};
-const toPlaylists = representations => representations.map(generateSegments);
+Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName;
+Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
-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));
+_extends(Element,Node);
+function Attr() {
};
+Attr.prototype.nodeType = ATTRIBUTE_NODE;
+_extends(Attr,Node);
-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);
+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);
- if (!match) {
- return 0;
- }
+ },
+ 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);
- 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);
+function CDATASection() {
};
-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'
+CDATASection.prototype = {
+ nodeName : "#cdata-section",
+ nodeType : CDATA_SECTION_NODE
+}
+_extends(CDATASection,CharacterData);
- if (dateRegex.test(str)) {
- str += 'Z';
- }
- return Date.parse(str);
+function DocumentType() {
};
+DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
+_extends(DocumentType,Node);
-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);
- },
+function Notation() {
+};
+Notation.prototype.nodeType = NOTATION_NODE;
+_extends(Notation,Node);
- /**
- * 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;
- },
+function Entity() {
+};
+Entity.prototype.nodeType = ENTITY_NODE;
+_extends(Entity,Node);
- /**
- * 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);
- },
+function EntityReference() {
+};
+EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE;
+_extends(EntityReference,Node);
- /**
- * 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);
- },
+function DocumentFragment() {
+};
+DocumentFragment.prototype.nodeName = "#document-fragment";
+DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE;
+_extends(DocumentFragment,Node);
- /**
- * 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
- */
+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;
-const parseAttributes = el => {
- if (!(el && el.attributes)) {
- return {};
- }
+ 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('');
+}
- return from(el.attributes).reduce((a, e) => {
- const parseFn = parsers[e.name] || parsers.DEFAULT;
- a[e.name] = parseFn(e.value);
- return a;
- }, {});
-};
+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;
+ }
-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'
-};
+ var i = visibleNamespaces.length
+ while (i--) {
+ var ns = visibleNamespaces[i];
+ // get namespace prefix
+ if (ns.prefix === prefix) {
+ return ns.namespace !== uri;
+ }
+ }
+ return true;
+}
/**
- * Builds a list of urls that is the product of the reference urls and BaseURL values
+ * 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
*
- * @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
+ * 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), '"')
+}
-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 = (0,_videojs_vhs_utils_es_resolve_url__WEBPACK_IMPORTED_MODULE_0__["default"])(reference.baseUrl, initialBaseUrl);
- const finalBaseUrl = merge(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;
- }
+function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
+ if (!visibleNamespaces) {
+ visibleNamespaces = [];
+ }
- 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
- */
+ if(nodeFilter){
+ node = nodeFilter(node);
+ if(node){
+ if(typeof node == 'string'){
+ buf.push(node);
+ return;
+ }
+ }else{
+ return;
+ }
+ //buf.sort.apply(attrs, attributeSorter);
+ }
-/**
- * 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
- */
+ switch(node.nodeType){
+ case ELEMENT_NODE:
+ var attrs = node.attributes;
+ var len = attrs.length;
+ var child = node.firstChild;
+ var nodeName = node.tagName;
-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'
- }, 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.
+ isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML
- const template = segmentTemplate && parseAttributes(segmentTemplate);
+ 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
+ }
+ }
+ }
+ }
- if (template && segmentInitialization) {
- template.initialization = segmentInitialization && 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
- };
- }
+ buf.push('<', prefixedNodeName);
- const segmentInfo = {
- template,
- segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => parseAttributes(s)),
- list: segmentList && merge(parseAttributes(segmentList), {
- segmentUrls,
- initialization: parseAttributes(segmentInitialization)
- }),
- base: segmentBase && merge(parseAttributes(segmentBase), {
- initialization: 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
- */
+ for(var i=0;i representation => {
- const repBaseUrlElements = findChildren(representation, 'BaseURL');
- const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
- const attributes = merge(adaptationSetAttributes, 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 = 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 && (0,_videojs_vhs_utils_es_decode_b64_to_uint8_array__WEBPACK_IMPORTED_MODULE_3__["default"])(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(child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)){
+ buf.push('>');
+ //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('',prefixedNodeName,'>');
+ }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( "",node.target," ",node.data,"?>");
+ 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 {
- // get and flatten all EventStreams tags and parse attributes and children
- return flatten(findChildren(period.node, 'EventStream').map(eventStream => {
- const eventStreamAttributes = parseAttributes(eventStream);
- const schemeIdUri = eventStreamAttributes.schemeIdUri; // find all Events per EventStream tag and map to return objects
+ default:
+ this.data = data;
+ this.value = data;
+ this.nodeValue = data;
+ }
+ }
+ })
- return findChildren(eventStream, 'Event').map(event => {
- const eventAttributes = 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
- */
+ function getTextContent(node){
+ switch(node.nodeType){
+ case ELEMENT_NODE:
+ case DOCUMENT_FRAGMENT_NODE:
+ var buf = [];
+ node = node.firstChild;
+ while(node){
+ if(node.nodeType!==7 && node.nodeType !==8){
+ buf.push(getTextContent(node));
+ }
+ node = node.nextSibling;
+ }
+ return buf.join('');
+ default:
+ return node.nodeValue;
+ }
+ }
-/**
- * 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
- */
+ __set__ = function(object,key,value){
+ //console.log(value)
+ object['$$'+key] = value
+ }
+ }
+}catch(e){//ie8
+}
-const toRepresentations = (periodAttributes, periodBaseUrls, periodSegmentInfo) => adaptationSet => {
- const adaptationSetAttributes = parseAttributes(adaptationSet);
- const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
- const role = findChildren(adaptationSet, 'Role')[0];
- const roleAttributes = {
- role: parseAttributes(role)
- };
- let attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
- const accessibility = findChildren(adaptationSet, 'Accessibility')[0];
- const captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility));
+//if(typeof require == 'function'){
+ exports.DocumentType = DocumentType;
+ exports.DOMException = DOMException;
+ exports.DOMImplementation = DOMImplementation;
+ exports.Element = Element;
+ exports.Node = Node;
+ exports.NodeList = NodeList;
+ exports.XMLSerializer = XMLSerializer;
+//}
- 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
- });
- }
+/***/ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/entities.js":
+/*!*****************************************************************************!*\
+ !*** ./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/entities.js ***!
+ \*****************************************************************************/
+/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- const contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
+"use strict";
- 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
- */
+var freeze = (__webpack_require__(/*! ./conventions */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js").freeze);
/**
- * Maps a PeriodInformation object to a list of Representation information objects for all
- * AdaptationSet nodes contained within the Period.
+ * The entities that are predefined in every XML document.
*
- * @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
+ * @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: '"',
+});
/**
- * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
- * Representation information objects
+ * A map of all entities that are detected in an HTML document.
+ * They contain all entries from `XML_ENTITIES`.
*
- * @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])
- }, 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 = 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 = 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_xmldom__WEBPACK_IMPORTED_MODULE_4__.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 = 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 = 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));
-
-
-
-
-/***/ }),
-
-/***/ "./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js":
-/*!*************************************************************************************************!*\
- !*** ./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js ***!
- \*************************************************************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (/* binding */ decodeB64ToUint8Array)
-/* harmony export */ });
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_0__);
-
-
-var atob = function atob(s) {
- return (global_window__WEBPACK_IMPORTED_MODULE_0___default().atob) ? global_window__WEBPACK_IMPORTED_MODULE_0___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;
-}
-
-/***/ }),
-
-/***/ "./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/media-groups.js":
-/*!************************************************************************************!*\
- !*** ./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/media-groups.js ***!
- \************************************************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ forEachMediaGroup: () => (/* binding */ forEachMediaGroup)
-/* harmony export */ });
-/**
- * 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);
- }
- }
- });
-};
-
-/***/ }),
-
-/***/ "./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/resolve-url.js":
-/*!***********************************************************************************!*\
- !*** ./node_modules/mpd-parser/node_modules/@videojs/vhs-utils/es/resolve-url.js ***!
- \***********************************************************************************/
-/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
-/* harmony export */ });
-/* harmony import */ var url_toolkit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! url-toolkit */ "./node_modules/url-toolkit/src/url-toolkit.js");
-/* harmony import */ var url_toolkit__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(url_toolkit__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! global/window */ "./node_modules/global/window.js");
-/* harmony import */ var global_window__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(global_window__WEBPACK_IMPORTED_MODULE_1__);
-
-
-var DEFAULT_LOCATION = 'http://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 = (global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && (global_window__WEBPACK_IMPORTED_MODULE_1___default().location).href || '';
- } // IE11 supports URL but not the URL constructor
- // feature detect the behavior we want
-
-
- var nativeURL = typeof (global_window__WEBPACK_IMPORTED_MODULE_1___default().URL) === 'function';
- 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 = !(global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
-
- if (nativeURL) {
- baseUrl = new (global_window__WEBPACK_IMPORTED_MODULE_1___default().URL)(baseUrl, (global_window__WEBPACK_IMPORTED_MODULE_1___default().location) || DEFAULT_LOCATION);
- } else if (!/\/\//i.test(baseUrl)) {
- baseUrl = url_toolkit__WEBPACK_IMPORTED_MODULE_0___default().buildAbsoluteURL((global_window__WEBPACK_IMPORTED_MODULE_1___default().location) && (global_window__WEBPACK_IMPORTED_MODULE_1___default().location).href || '', baseUrl);
- }
-
- if (nativeURL) {
- 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;
- }
-
- return url_toolkit__WEBPACK_IMPORTED_MODULE_0___default().buildAbsoluteURL(baseUrl, relativeUrl);
-};
-
-/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (resolveUrl);
-
-/***/ }),
-
-/***/ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js":
-/*!********************************************************************************!*\
- !*** ./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js ***!
- \********************************************************************************/
-/***/ ((__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;
-
-
-/***/ }),
-
-/***/ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom-parser.js":
-/*!*******************************************************************************!*\
- !*** ./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom-parser.js ***!
- \*******************************************************************************/
-/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-
-var conventions = __webpack_require__(/*! ./conventions */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js");
-var dom = __webpack_require__(/*! ./dom */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom.js")
-var entities = __webpack_require__(/*! ./entities */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/entities.js");
-var sax = __webpack_require__(/*! ./sax */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/sax.js");
-
-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
-
-exports.__DOMHandler = DOMHandler;
-exports.normalizeLineEndings = normalizeLineEndings;
-exports.DOMParser = DOMParser;
-
-
-/***/ }),
-
-/***/ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom.js":
-/*!************************************************************************!*\
- !*** ./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/dom.js ***!
- \************************************************************************/
-/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-
-var conventions = __webpack_require__(/*! ./conventions */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js");
-
-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;
- }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;
-}
-
-/**
- * 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);
- 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);
- newChild.ownerDocument = 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);
- newChild.ownerDocument = 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('',prefixedNodeName,'>');
- }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( "",node.target," ",node.data,"?>");
- 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__(/*! ./conventions */ "./node_modules/mpd-parser/node_modules/@xmldom/xmldom/lib/conventions.js").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)
+ * @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',
@@ -14419,17 +12022,17 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _videojs_xhr__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_videojs_xhr__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var videojs_vtt_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! videojs-vtt.js */ "./node_modules/videojs-vtt.js/lib/browser-index.js");
/* harmony import */ var videojs_vtt_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(videojs_vtt_js__WEBPACK_IMPORTED_MODULE_5__);
-/* harmony import */ var _videojs_vhs_utils_es_resolve_url_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @videojs/vhs-utils/es/resolve-url.js */ "./node_modules/@videojs/vhs-utils/es/resolve-url.js");
+/* harmony import */ var _videojs_vhs_utils_es_resolve_url_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @videojs/vhs-utils/es/resolve-url.js */ "./node_modules/video.js/node_modules/@videojs/vhs-utils/es/resolve-url.js");
/* harmony import */ var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @babel/runtime/helpers/extends */ "./node_modules/@babel/runtime/helpers/esm/extends.js");
/* harmony import */ var m3u8_parser__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! m3u8-parser */ "./node_modules/m3u8-parser/dist/m3u8-parser.es.js");
-/* harmony import */ var _videojs_vhs_utils_es_codecs_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @videojs/vhs-utils/es/codecs.js */ "./node_modules/@videojs/vhs-utils/es/codecs.js");
-/* harmony import */ var _videojs_vhs_utils_es_media_types_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @videojs/vhs-utils/es/media-types.js */ "./node_modules/@videojs/vhs-utils/es/media-types.js");
-/* harmony import */ var _videojs_vhs_utils_es_byte_helpers__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @videojs/vhs-utils/es/byte-helpers */ "./node_modules/@videojs/vhs-utils/es/byte-helpers.js");
+/* harmony import */ var _videojs_vhs_utils_es_codecs_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @videojs/vhs-utils/es/codecs.js */ "./node_modules/video.js/node_modules/@videojs/vhs-utils/es/codecs.js");
+/* harmony import */ var _videojs_vhs_utils_es_media_types_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @videojs/vhs-utils/es/media-types.js */ "./node_modules/video.js/node_modules/@videojs/vhs-utils/es/media-types.js");
+/* harmony import */ var _videojs_vhs_utils_es_byte_helpers__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @videojs/vhs-utils/es/byte-helpers */ "./node_modules/video.js/node_modules/@videojs/vhs-utils/es/byte-helpers.js");
/* harmony import */ var mpd_parser__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! mpd-parser */ "./node_modules/mpd-parser/dist/mpd-parser.es.js");
/* harmony import */ var mux_js_lib_tools_parse_sidx__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! mux.js/lib/tools/parse-sidx */ "./node_modules/mux.js/lib/tools/parse-sidx.js");
/* harmony import */ var mux_js_lib_tools_parse_sidx__WEBPACK_IMPORTED_MODULE_13___default = /*#__PURE__*/__webpack_require__.n(mux_js_lib_tools_parse_sidx__WEBPACK_IMPORTED_MODULE_13__);
-/* harmony import */ var _videojs_vhs_utils_es_id3_helpers__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! @videojs/vhs-utils/es/id3-helpers */ "./node_modules/@videojs/vhs-utils/es/id3-helpers.js");
-/* harmony import */ var _videojs_vhs_utils_es_containers__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! @videojs/vhs-utils/es/containers */ "./node_modules/@videojs/vhs-utils/es/containers.js");
+/* harmony import */ var _videojs_vhs_utils_es_id3_helpers__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! @videojs/vhs-utils/es/id3-helpers */ "./node_modules/video.js/node_modules/@videojs/vhs-utils/es/id3-helpers.js");
+/* harmony import */ var _videojs_vhs_utils_es_containers__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! @videojs/vhs-utils/es/containers */ "./node_modules/video.js/node_modules/@videojs/vhs-utils/es/containers.js");
/* harmony import */ var mux_js_lib_utils_clock__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! mux.js/lib/utils/clock */ "./node_modules/mux.js/lib/utils/clock.js");
/* harmony import */ var mux_js_lib_utils_clock__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(mux_js_lib_utils_clock__WEBPACK_IMPORTED_MODULE_16__);
/**
@@ -34097,11591 +31700,13865 @@ Html5.resetMediaElement = function (el) {
* The value of `networkState` from the media element. This will be a number
* from the list in the description.
*
- * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
+ * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
+ */
+'networkState',
+/**
+ * Get the value of `readyState` from the media element. `readyState` indicates
+ * the current state of the media element. It returns an enumeration from the
+ * following list:
+ * - 0: HAVE_NOTHING
+ * - 1: HAVE_METADATA
+ * - 2: HAVE_CURRENT_DATA
+ * - 3: HAVE_FUTURE_DATA
+ * - 4: HAVE_ENOUGH_DATA
+ *
+ * @method Html5#readyState
+ * @return {number}
+ * The value of `readyState` from the media element. This will be a number
+ * from the list in the description.
+ *
+ * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
+ */
+'readyState',
+/**
+ * Get the value of `videoWidth` from the video element. `videoWidth` indicates
+ * the current width of the video in css pixels.
+ *
+ * @method Html5#videoWidth
+ * @return {number}
+ * The value of `videoWidth` from the video element. This will be a number
+ * in css pixels.
+ *
+ * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
+ */
+'videoWidth',
+/**
+ * Get the value of `videoHeight` from the video element. `videoHeight` indicates
+ * the current height of the video in css pixels.
+ *
+ * @method Html5#videoHeight
+ * @return {number}
+ * The value of `videoHeight` from the video element. This will be a number
+ * in css pixels.
+ *
+ * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
+ */
+'videoHeight',
+/**
+ * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
+ * to the browser that should sent the cookies along with the requests for the
+ * different assets/playlists
+ *
+ * @method Html5#crossOrigin
+ * @return {string}
+ * - anonymous indicates that the media should not sent cookies.
+ * - use-credentials indicates that the media should sent cookies along the requests.
+ *
+ * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
+ */
+'crossOrigin'].forEach(function (prop) {
+ Html5.prototype[prop] = function () {
+ return this.el_[prop];
+ };
+});
+
+// Wrap native properties with a setter in this format:
+// set + toTitleCase(name)
+// The list is as follows:
+// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
+// setDisablePictureInPicture, setCrossOrigin
+[
+/**
+ * Set the value of `volume` on the media element. `volume` indicates the current
+ * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
+ * so on.
+ *
+ * @method Html5#setVolume
+ * @param {number} percentAsDecimal
+ * The volume percent as a decimal. Valid range is from 0-1.
+ *
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
+ */
+'volume',
+/**
+ * Set the value of `src` on the media element. `src` indicates the current
+ * {@link Tech~SourceObject} for the media.
+ *
+ * @method Html5#setSrc
+ * @param {Tech~SourceObject} src
+ * The source object to set as the current source.
+ *
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
+ */
+'src',
+/**
+ * Set the value of `poster` on the media element. `poster` is the url to
+ * an image file that can/will be shown when no media data is available.
+ *
+ * @method Html5#setPoster
+ * @param {string} poster
+ * The url to an image that should be used as the `poster` for the media
+ * element.
+ *
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
+ */
+'poster',
+/**
+ * Set the value of `preload` on the media element. `preload` indicates
+ * what should download before the media is interacted with. It can have the following
+ * values:
+ * - none: nothing should be downloaded
+ * - metadata: poster and the first few frames of the media may be downloaded to get
+ * media dimensions and other metadata
+ * - auto: allow the media and metadata for the media to be downloaded before
+ * interaction
+ *
+ * @method Html5#setPreload
+ * @param {string} preload
+ * The value of `preload` to set on the media element. Must be 'none', 'metadata',
+ * or 'auto'.
+ *
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
+ */
+'preload',
+/**
+ * Set the value of `playbackRate` on the media element. `playbackRate` indicates
+ * the rate at which the media should play back. Examples:
+ * - if playbackRate is set to 2, media will play twice as fast.
+ * - if playbackRate is set to 0.5, media will play half as fast.
+ *
+ * @method Html5#setPlaybackRate
+ * @return {number}
+ * The value of `playbackRate` from the media element. A number indicating
+ * the current playback speed of the media, where 1 is normal speed.
+ *
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
+ */
+'playbackRate',
+/**
+ * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
+ * the rate at which the media should play back upon initial startup. Changing this value
+ * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
+ *
+ * Example Values:
+ * - if playbackRate is set to 2, media will play twice as fast.
+ * - if playbackRate is set to 0.5, media will play half as fast.
+ *
+ * @method Html5.prototype.setDefaultPlaybackRate
+ * @return {number}
+ * The value of `defaultPlaybackRate` from the media element. A number indicating
+ * the current playback speed of the media, where 1 is normal speed.
+ *
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
+ */
+'defaultPlaybackRate',
+/**
+ * Prevents the browser from suggesting a Picture-in-Picture context menu
+ * or to request Picture-in-Picture automatically in some cases.
+ *
+ * @method Html5#setDisablePictureInPicture
+ * @param {boolean} value
+ * The true value will disable Picture-in-Picture mode.
+ *
+ * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
+ */
+'disablePictureInPicture',
+/**
+ * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
+ * to the browser that should sent the cookies along with the requests for the
+ * different assets/playlists
+ *
+ * @method Html5#setCrossOrigin
+ * @param {string} crossOrigin
+ * - anonymous indicates that the media should not sent cookies.
+ * - use-credentials indicates that the media should sent cookies along the requests.
+ *
+ * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
+ */
+'crossOrigin'].forEach(function (prop) {
+ Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
+ this.el_[prop] = v;
+ };
+});
+
+// wrap native functions with a function
+// The list is as follows:
+// pause, load, play
+[
+/**
+ * A wrapper around the media elements `pause` function. This will call the `HTML5`
+ * media elements `pause` function.
+ *
+ * @method Html5#pause
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
+ */
+'pause',
+/**
+ * A wrapper around the media elements `load` function. This will call the `HTML5`s
+ * media element `load` function.
+ *
+ * @method Html5#load
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
+ */
+'load',
+/**
+ * A wrapper around the media elements `play` function. This will call the `HTML5`s
+ * media element `play` function.
+ *
+ * @method Html5#play
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
+ */
+'play'].forEach(function (prop) {
+ Html5.prototype[prop] = function () {
+ return this.el_[prop]();
+ };
+});
+Tech.withSourceHandlers(Html5);
+
+/**
+ * Native source handler for Html5, simply passes the source to the media element.
+ *
+ * @property {Tech~SourceObject} source
+ * The source object
+ *
+ * @property {Html5} tech
+ * The instance of the HTML5 tech.
+ */
+Html5.nativeSourceHandler = {};
+
+/**
+ * Check if the media element can play the given mime type.
+ *
+ * @param {string} type
+ * The mimetype to check
+ *
+ * @return {string}
+ * 'probably', 'maybe', or '' (empty string)
+ */
+Html5.nativeSourceHandler.canPlayType = function (type) {
+ // IE without MediaPlayer throws an error (#519)
+ try {
+ return Html5.TEST_VID.canPlayType(type);
+ } catch (e) {
+ return '';
+ }
+};
+
+/**
+ * Check if the media element can handle a source natively.
+ *
+ * @param {Tech~SourceObject} source
+ * The source object
+ *
+ * @param {Object} [options]
+ * Options to be passed to the tech.
+ *
+ * @return {string}
+ * 'probably', 'maybe', or '' (empty string).
+ */
+Html5.nativeSourceHandler.canHandleSource = function (source, options) {
+ // If a type was provided we should rely on that
+ if (source.type) {
+ return Html5.nativeSourceHandler.canPlayType(source.type);
+
+ // If no type, fall back to checking 'video/[EXTENSION]'
+ } else if (source.src) {
+ const ext = getFileExtension(source.src);
+ return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
+ }
+ return '';
+};
+
+/**
+ * Pass the source to the native media element.
+ *
+ * @param {Tech~SourceObject} source
+ * The source object
+ *
+ * @param {Html5} tech
+ * The instance of the Html5 tech
+ *
+ * @param {Object} [options]
+ * The options to pass to the source
+ */
+Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
+ tech.setSrc(source.src);
+};
+
+/**
+ * A noop for the native dispose function, as cleanup is not needed.
+ */
+Html5.nativeSourceHandler.dispose = function () {};
+
+// Register the native source handler
+Html5.registerSourceHandler(Html5.nativeSourceHandler);
+Tech.registerTech('Html5', Html5);
+
+/**
+ * @file player.js
+ */
+
+// The following tech events are simply re-triggered
+// on the player when they happen
+const TECH_EVENTS_RETRIGGER = [
+/**
+ * Fired while the user agent is downloading media data.
+ *
+ * @event Player#progress
+ * @type {Event}
+ */
+/**
+ * Retrigger the `progress` event that was triggered by the {@link Tech}.
+ *
+ * @private
+ * @method Player#handleTechProgress_
+ * @fires Player#progress
+ * @listens Tech#progress
+ */
+'progress',
+/**
+ * Fires when the loading of an audio/video is aborted.
+ *
+ * @event Player#abort
+ * @type {Event}
+ */
+/**
+ * Retrigger the `abort` event that was triggered by the {@link Tech}.
+ *
+ * @private
+ * @method Player#handleTechAbort_
+ * @fires Player#abort
+ * @listens Tech#abort
+ */
+'abort',
+/**
+ * Fires when the browser is intentionally not getting media data.
+ *
+ * @event Player#suspend
+ * @type {Event}
+ */
+/**
+ * Retrigger the `suspend` event that was triggered by the {@link Tech}.
+ *
+ * @private
+ * @method Player#handleTechSuspend_
+ * @fires Player#suspend
+ * @listens Tech#suspend
+ */
+'suspend',
+/**
+ * Fires when the current playlist is empty.
+ *
+ * @event Player#emptied
+ * @type {Event}
+ */
+/**
+ * Retrigger the `emptied` event that was triggered by the {@link Tech}.
+ *
+ * @private
+ * @method Player#handleTechEmptied_
+ * @fires Player#emptied
+ * @listens Tech#emptied
+ */
+'emptied',
+/**
+ * Fires when the browser is trying to get media data, but data is not available.
+ *
+ * @event Player#stalled
+ * @type {Event}
+ */
+/**
+ * Retrigger the `stalled` event that was triggered by the {@link Tech}.
+ *
+ * @private
+ * @method Player#handleTechStalled_
+ * @fires Player#stalled
+ * @listens Tech#stalled
+ */
+'stalled',
+/**
+ * Fires when the browser has loaded meta data for the audio/video.
+ *
+ * @event Player#loadedmetadata
+ * @type {Event}
+ */
+/**
+ * Retrigger the `loadedmetadata` event that was triggered by the {@link Tech}.
+ *
+ * @private
+ * @method Player#handleTechLoadedmetadata_
+ * @fires Player#loadedmetadata
+ * @listens Tech#loadedmetadata
+ */
+'loadedmetadata',
+/**
+ * Fires when the browser has loaded the current frame of the audio/video.
+ *
+ * @event Player#loadeddata
+ * @type {event}
+ */
+/**
+ * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
+ *
+ * @private
+ * @method Player#handleTechLoaddeddata_
+ * @fires Player#loadeddata
+ * @listens Tech#loadeddata
+ */
+'loadeddata',
+/**
+ * Fires when the current playback position has changed.
+ *
+ * @event Player#timeupdate
+ * @type {event}
+ */
+/**
+ * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
+ *
+ * @private
+ * @method Player#handleTechTimeUpdate_
+ * @fires Player#timeupdate
+ * @listens Tech#timeupdate
*/
-'networkState',
+'timeupdate',
/**
- * Get the value of `readyState` from the media element. `readyState` indicates
- * the current state of the media element. It returns an enumeration from the
- * following list:
- * - 0: HAVE_NOTHING
- * - 1: HAVE_METADATA
- * - 2: HAVE_CURRENT_DATA
- * - 3: HAVE_FUTURE_DATA
- * - 4: HAVE_ENOUGH_DATA
+ * Fires when the video's intrinsic dimensions change
*
- * @method Html5#readyState
- * @return {number}
- * The value of `readyState` from the media element. This will be a number
- * from the list in the description.
+ * @event Player#resize
+ * @type {event}
+ */
+/**
+ * Retrigger the `resize` event that was triggered by the {@link Tech}.
*
- * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
+ * @private
+ * @method Player#handleTechResize_
+ * @fires Player#resize
+ * @listens Tech#resize
*/
-'readyState',
+'resize',
/**
- * Get the value of `videoWidth` from the video element. `videoWidth` indicates
- * the current width of the video in css pixels.
+ * Fires when the volume has been changed
*
- * @method Html5#videoWidth
- * @return {number}
- * The value of `videoWidth` from the video element. This will be a number
- * in css pixels.
+ * @event Player#volumechange
+ * @type {event}
+ */
+/**
+ * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
*
- * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
+ * @private
+ * @method Player#handleTechVolumechange_
+ * @fires Player#volumechange
+ * @listens Tech#volumechange
*/
-'videoWidth',
+'volumechange',
/**
- * Get the value of `videoHeight` from the video element. `videoHeight` indicates
- * the current height of the video in css pixels.
+ * Fires when the text track has been changed
*
- * @method Html5#videoHeight
- * @return {number}
- * The value of `videoHeight` from the video element. This will be a number
- * in css pixels.
+ * @event Player#texttrackchange
+ * @type {event}
+ */
+/**
+ * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
*
- * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
+ * @private
+ * @method Player#handleTechTexttrackchange_
+ * @fires Player#texttrackchange
+ * @listens Tech#texttrackchange
*/
-'videoHeight',
+'texttrackchange'];
+
+// events to queue when playback rate is zero
+// this is a hash for the sole purpose of mapping non-camel-cased event names
+// to camel-cased function names
+const TECH_EVENTS_QUEUE = {
+ canplay: 'CanPlay',
+ canplaythrough: 'CanPlayThrough',
+ playing: 'Playing',
+ seeked: 'Seeked'
+};
+const BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
+const BREAKPOINT_CLASSES = {};
+
+// grep: vjs-layout-tiny
+// grep: vjs-layout-x-small
+// grep: vjs-layout-small
+// grep: vjs-layout-medium
+// grep: vjs-layout-large
+// grep: vjs-layout-x-large
+// grep: vjs-layout-huge
+BREAKPOINT_ORDER.forEach(k => {
+ const v = k.charAt(0) === 'x' ? `x-${k.substring(1)}` : k;
+ BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
+});
+const DEFAULT_BREAKPOINTS = {
+ tiny: 210,
+ xsmall: 320,
+ small: 425,
+ medium: 768,
+ large: 1440,
+ xlarge: 2560,
+ huge: Infinity
+};
+
/**
- * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
- * to the browser that should sent the cookies along with the requests for the
- * different assets/playlists
+ * An instance of the `Player` class is created when any of the Video.js setup methods
+ * are used to initialize a video.
*
- * @method Html5#crossOrigin
- * @return {string}
- * - anonymous indicates that the media should not sent cookies.
- * - use-credentials indicates that the media should sent cookies along the requests.
+ * After an instance has been created it can be accessed globally in three ways:
+ * 1. By calling `videojs.getPlayer('example_video_1');`
+ * 2. By calling `videojs('example_video_1');` (not recommended)
+ * 2. By using it directly via `videojs.players.example_video_1;`
*
- * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
+ * @extends Component
+ * @global
*/
-'crossOrigin'].forEach(function (prop) {
- Html5.prototype[prop] = function () {
- return this.el_[prop];
- };
-});
+class Player extends Component$1 {
+ /**
+ * Create an instance of this class.
+ *
+ * @param {Element} tag
+ * The original video DOM element used for configuring options.
+ *
+ * @param {Object} [options]
+ * Object of option names and values.
+ *
+ * @param {Function} [ready]
+ * Ready callback function.
+ */
+ constructor(tag, options, ready) {
+ // Make sure tag ID exists
+ // also here.. probably better
+ tag.id = tag.id || options.id || `vjs_video_${newGUID()}`;
+
+ // Set Options
+ // The options argument overrides options set in the video tag
+ // which overrides globally set options.
+ // This latter part coincides with the load order
+ // (tag must exist before Player)
+ options = Object.assign(Player.getTagSettings(tag), options);
+
+ // Delay the initialization of children because we need to set up
+ // player properties first, and can't use `this` before `super()`
+ options.initChildren = false;
+
+ // Same with creating the element
+ options.createEl = false;
+
+ // don't auto mixin the evented mixin
+ options.evented = false;
+
+ // we don't want the player to report touch activity on itself
+ // see enableTouchActivity in Component
+ options.reportTouchActivity = false;
+
+ // If language is not set, get the closest lang attribute
+ if (!options.language) {
+ const closest = tag.closest('[lang]');
+ if (closest) {
+ options.language = closest.getAttribute('lang');
+ }
+ }
+
+ // Run base component initializing with new options
+ super(null, options, ready);
+
+ // Create bound methods for document listeners.
+ this.boundDocumentFullscreenChange_ = e => this.documentFullscreenChange_(e);
+ this.boundFullWindowOnEscKey_ = e => this.fullWindowOnEscKey(e);
+ this.boundUpdateStyleEl_ = e => this.updateStyleEl_(e);
+ this.boundApplyInitTime_ = e => this.applyInitTime_(e);
+ this.boundUpdateCurrentBreakpoint_ = e => this.updateCurrentBreakpoint_(e);
+ this.boundHandleTechClick_ = e => this.handleTechClick_(e);
+ this.boundHandleTechDoubleClick_ = e => this.handleTechDoubleClick_(e);
+ this.boundHandleTechTouchStart_ = e => this.handleTechTouchStart_(e);
+ this.boundHandleTechTouchMove_ = e => this.handleTechTouchMove_(e);
+ this.boundHandleTechTouchEnd_ = e => this.handleTechTouchEnd_(e);
+ this.boundHandleTechTap_ = e => this.handleTechTap_(e);
+
+ // default isFullscreen_ to false
+ this.isFullscreen_ = false;
+
+ // create logger
+ this.log = createLogger(this.id_);
+
+ // Hold our own reference to fullscreen api so it can be mocked in tests
+ this.fsApi_ = FullscreenApi;
+
+ // Tracks when a tech changes the poster
+ this.isPosterFromTech_ = false;
+
+ // Holds callback info that gets queued when playback rate is zero
+ // and a seek is happening
+ this.queuedCallbacks_ = [];
+
+ // Turn off API access because we're loading a new tech that might load asynchronously
+ this.isReady_ = false;
+
+ // Init state hasStarted_
+ this.hasStarted_ = false;
+
+ // Init state userActive_
+ this.userActive_ = false;
+
+ // Init debugEnabled_
+ this.debugEnabled_ = false;
+
+ // Init state audioOnlyMode_
+ this.audioOnlyMode_ = false;
+
+ // Init state audioPosterMode_
+ this.audioPosterMode_ = false;
+
+ // Init state audioOnlyCache_
+ this.audioOnlyCache_ = {
+ playerHeight: null,
+ hiddenChildren: []
+ };
+
+ // if the global option object was accidentally blown away by
+ // someone, bail early with an informative error
+ if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
+ throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
+ }
+
+ // Store the original tag used to set options
+ this.tag = tag;
+
+ // Store the tag attributes used to restore html5 element
+ this.tagAttributes = tag && getAttributes(tag);
+
+ // Update current language
+ this.language(this.options_.language);
+
+ // Update Supported Languages
+ if (options.languages) {
+ // Normalise player option languages to lowercase
+ const languagesToLower = {};
+ Object.getOwnPropertyNames(options.languages).forEach(function (name) {
+ languagesToLower[name.toLowerCase()] = options.languages[name];
+ });
+ this.languages_ = languagesToLower;
+ } else {
+ this.languages_ = Player.prototype.options_.languages;
+ }
+ this.resetCache_();
+
+ // Set poster
+ /** @type string */
+ this.poster_ = options.poster || '';
+
+ // Set controls
+ /** @type {boolean} */
+ this.controls_ = !!options.controls;
+
+ // Original tag settings stored in options
+ // now remove immediately so native controls don't flash.
+ // May be turned back on by HTML5 tech if nativeControlsForTouch is true
+ tag.controls = false;
+ tag.removeAttribute('controls');
+ this.changingSrc_ = false;
+ this.playCallbacks_ = [];
+ this.playTerminatedQueue_ = [];
+
+ // the attribute overrides the option
+ if (tag.hasAttribute('autoplay')) {
+ this.autoplay(true);
+ } else {
+ // otherwise use the setter to validate and
+ // set the correct value.
+ this.autoplay(this.options_.autoplay);
+ }
+
+ // check plugins
+ if (options.plugins) {
+ Object.keys(options.plugins).forEach(name => {
+ if (typeof this[name] !== 'function') {
+ throw new Error(`plugin "${name}" does not exist`);
+ }
+ });
+ }
+
+ /*
+ * Store the internal state of scrubbing
+ *
+ * @private
+ * @return {Boolean} True if the user is scrubbing
+ */
+ this.scrubbing_ = false;
+ this.el_ = this.createEl();
+
+ // Make this an evented object and use `el_` as its event bus.
+ evented(this, {
+ eventBusKey: 'el_'
+ });
+
+ // listen to document and player fullscreenchange handlers so we receive those events
+ // before a user can receive them so we can update isFullscreen appropriately.
+ // make sure that we listen to fullscreenchange events before everything else to make sure that
+ // our isFullscreen method is updated properly for internal components as well as external.
+ if (this.fsApi_.requestFullscreen) {
+ on((global_document__WEBPACK_IMPORTED_MODULE_1___default()), this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
+ this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
+ }
+ if (this.fluid_) {
+ this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
+ }
+ // We also want to pass the original player options to each component and plugin
+ // as well so they don't need to reach back into the player for options later.
+ // We also need to do another copy of this.options_ so we don't end up with
+ // an infinite loop.
+ const playerOptionsCopy = merge$1(this.options_);
+
+ // Load plugins
+ if (options.plugins) {
+ Object.keys(options.plugins).forEach(name => {
+ this[name](options.plugins[name]);
+ });
+ }
+
+ // Enable debug mode to fire debugon event for all plugins.
+ if (options.debug) {
+ this.debug(true);
+ }
+ this.options_.playerOptions = playerOptionsCopy;
+ this.middleware_ = [];
+ this.playbackRates(options.playbackRates);
+ if (options.experimentalSvgIcons) {
+ // Add SVG Sprite to the DOM
+ const parser = new (global_window__WEBPACK_IMPORTED_MODULE_0___default().DOMParser)();
+ const parsedSVG = parser.parseFromString(icons, 'image/svg+xml');
+ const errorNode = parsedSVG.querySelector('parsererror');
+ if (errorNode) {
+ log$1.warn('Failed to load SVG Icons. Falling back to Font Icons.');
+ this.options_.experimentalSvgIcons = null;
+ } else {
+ const sprite = parsedSVG.documentElement;
+ sprite.style.display = 'none';
+ this.el_.appendChild(sprite);
+ this.addClass('vjs-svg-icons-enabled');
+ }
+ }
+ this.initChildren();
+
+ // Set isAudio based on whether or not an audio tag was used
+ this.isAudio(tag.nodeName.toLowerCase() === 'audio');
+
+ // Update controls className. Can't do this when the controls are initially
+ // set because the element doesn't exist yet.
+ if (this.controls()) {
+ this.addClass('vjs-controls-enabled');
+ } else {
+ this.addClass('vjs-controls-disabled');
+ }
+
+ // Set ARIA label and region role depending on player type
+ this.el_.setAttribute('role', 'region');
+ if (this.isAudio()) {
+ this.el_.setAttribute('aria-label', this.localize('Audio Player'));
+ } else {
+ this.el_.setAttribute('aria-label', this.localize('Video Player'));
+ }
+ if (this.isAudio()) {
+ this.addClass('vjs-audio');
+ }
+
+ // TODO: Make this smarter. Toggle user state between touching/mousing
+ // using events, since devices can have both touch and mouse events.
+ // TODO: Make this check be performed again when the window switches between monitors
+ // (See https://github.com/videojs/video.js/issues/5683)
+ if (TOUCH_ENABLED) {
+ this.addClass('vjs-touch-enabled');
+ }
+
+ // iOS Safari has broken hover handling
+ if (!IS_IOS) {
+ this.addClass('vjs-workinghover');
+ }
+
+ // Make player easily findable by ID
+ Player.players[this.id_] = this;
+
+ // Add a major version class to aid css in plugins
+ const majorVersion = version$6.split('.')[0];
+ this.addClass(`vjs-v${majorVersion}`);
+
+ // When the player is first initialized, trigger activity so components
+ // like the control bar show themselves if needed
+ this.userActive(true);
+ this.reportUserActivity();
+ this.one('play', e => this.listenForUserActivity_(e));
+ this.on('keydown', e => this.handleKeyDown(e));
+ this.on('languagechange', e => this.handleLanguagechange(e));
+ this.breakpoints(this.options_.breakpoints);
+ this.responsive(this.options_.responsive);
+
+ // Calling both the audio mode methods after the player is fully
+ // setup to be able to listen to the events triggered by them
+ this.on('ready', () => {
+ // Calling the audioPosterMode method first so that
+ // the audioOnlyMode can take precedence when both options are set to true
+ this.audioPosterMode(this.options_.audioPosterMode);
+ this.audioOnlyMode(this.options_.audioOnlyMode);
+ });
+ }
+
+ /**
+ * Destroys the video player and does any necessary cleanup.
+ *
+ * This is especially helpful if you are dynamically adding and removing videos
+ * to/from the DOM.
+ *
+ * @fires Player#dispose
+ */
+ dispose() {
+ /**
+ * Called when the player is being disposed of.
+ *
+ * @event Player#dispose
+ * @type {Event}
+ */
+ this.trigger('dispose');
+ // prevent dispose from being called twice
+ this.off('dispose');
+
+ // Make sure all player-specific document listeners are unbound. This is
+ off((global_document__WEBPACK_IMPORTED_MODULE_1___default()), this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
+ off((global_document__WEBPACK_IMPORTED_MODULE_1___default()), 'keydown', this.boundFullWindowOnEscKey_);
+ if (this.styleEl_ && this.styleEl_.parentNode) {
+ this.styleEl_.parentNode.removeChild(this.styleEl_);
+ this.styleEl_ = null;
+ }
+
+ // Kill reference to this player
+ Player.players[this.id_] = null;
+ if (this.tag && this.tag.player) {
+ this.tag.player = null;
+ }
+ if (this.el_ && this.el_.player) {
+ this.el_.player = null;
+ }
+ if (this.tech_) {
+ this.tech_.dispose();
+ this.isPosterFromTech_ = false;
+ this.poster_ = '';
+ }
+ if (this.playerElIngest_) {
+ this.playerElIngest_ = null;
+ }
+ if (this.tag) {
+ this.tag = null;
+ }
+ clearCacheForPlayer(this);
+
+ // remove all event handlers for track lists
+ // all tracks and track listeners are removed on
+ // tech dispose
+ ALL.names.forEach(name => {
+ const props = ALL[name];
+ const list = this[props.getterName]();
+
+ // if it is not a native list
+ // we have to manually remove event listeners
+ if (list && list.off) {
+ list.off();
+ }
+ });
+
+ // the actual .el_ is removed here, or replaced if
+ super.dispose({
+ restoreEl: this.options_.restoreEl
+ });
+ }
+
+ /**
+ * Create the `Player`'s DOM element.
+ *
+ * @return {Element}
+ * The DOM element that gets created.
+ */
+ createEl() {
+ let tag = this.tag;
+ let el;
+ let playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
+ const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
+ if (playerElIngest) {
+ el = this.el_ = tag.parentNode;
+ } else if (!divEmbed) {
+ el = this.el_ = super.createEl('div');
+ }
+
+ // Copy over all the attributes from the tag, including ID and class
+ // ID will now reference player box, not the video tag
+ const attrs = getAttributes(tag);
+ if (divEmbed) {
+ el = this.el_ = tag;
+ tag = this.tag = global_document__WEBPACK_IMPORTED_MODULE_1___default().createElement('video');
+ while (el.children.length) {
+ tag.appendChild(el.firstChild);
+ }
+ if (!hasClass(el, 'video-js')) {
+ addClass(el, 'video-js');
+ }
+ el.appendChild(tag);
+ playerElIngest = this.playerElIngest_ = el;
+ // move properties over from our custom `video-js` element
+ // to our new `video` element. This will move things like
+ // `src` or `controls` that were set via js before the player
+ // was initialized.
+ Object.keys(el).forEach(k => {
+ try {
+ tag[k] = el[k];
+ } catch (e) {
+ // we got a a property like outerHTML which we can't actually copy, ignore it
+ }
+ });
+ }
-// Wrap native properties with a setter in this format:
-// set + toTitleCase(name)
-// The list is as follows:
-// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
-// setDisablePictureInPicture, setCrossOrigin
-[
-/**
- * Set the value of `volume` on the media element. `volume` indicates the current
- * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
- * so on.
- *
- * @method Html5#setVolume
- * @param {number} percentAsDecimal
- * The volume percent as a decimal. Valid range is from 0-1.
- *
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
- */
-'volume',
-/**
- * Set the value of `src` on the media element. `src` indicates the current
- * {@link Tech~SourceObject} for the media.
- *
- * @method Html5#setSrc
- * @param {Tech~SourceObject} src
- * The source object to set as the current source.
- *
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
- */
-'src',
-/**
- * Set the value of `poster` on the media element. `poster` is the url to
- * an image file that can/will be shown when no media data is available.
- *
- * @method Html5#setPoster
- * @param {string} poster
- * The url to an image that should be used as the `poster` for the media
- * element.
- *
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
- */
-'poster',
-/**
- * Set the value of `preload` on the media element. `preload` indicates
- * what should download before the media is interacted with. It can have the following
- * values:
- * - none: nothing should be downloaded
- * - metadata: poster and the first few frames of the media may be downloaded to get
- * media dimensions and other metadata
- * - auto: allow the media and metadata for the media to be downloaded before
- * interaction
- *
- * @method Html5#setPreload
- * @param {string} preload
- * The value of `preload` to set on the media element. Must be 'none', 'metadata',
- * or 'auto'.
- *
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
- */
-'preload',
-/**
- * Set the value of `playbackRate` on the media element. `playbackRate` indicates
- * the rate at which the media should play back. Examples:
- * - if playbackRate is set to 2, media will play twice as fast.
- * - if playbackRate is set to 0.5, media will play half as fast.
- *
- * @method Html5#setPlaybackRate
- * @return {number}
- * The value of `playbackRate` from the media element. A number indicating
- * the current playback speed of the media, where 1 is normal speed.
- *
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
- */
-'playbackRate',
-/**
- * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
- * the rate at which the media should play back upon initial startup. Changing this value
- * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
- *
- * Example Values:
- * - if playbackRate is set to 2, media will play twice as fast.
- * - if playbackRate is set to 0.5, media will play half as fast.
- *
- * @method Html5.prototype.setDefaultPlaybackRate
- * @return {number}
- * The value of `defaultPlaybackRate` from the media element. A number indicating
- * the current playback speed of the media, where 1 is normal speed.
- *
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
- */
-'defaultPlaybackRate',
-/**
- * Prevents the browser from suggesting a Picture-in-Picture context menu
- * or to request Picture-in-Picture automatically in some cases.
- *
- * @method Html5#setDisablePictureInPicture
- * @param {boolean} value
- * The true value will disable Picture-in-Picture mode.
- *
- * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
- */
-'disablePictureInPicture',
-/**
- * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
- * to the browser that should sent the cookies along with the requests for the
- * different assets/playlists
- *
- * @method Html5#setCrossOrigin
- * @param {string} crossOrigin
- * - anonymous indicates that the media should not sent cookies.
- * - use-credentials indicates that the media should sent cookies along the requests.
- *
- * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
- */
-'crossOrigin'].forEach(function (prop) {
- Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
- this.el_[prop] = v;
- };
-});
+ // set tabindex to -1 to remove the video element from the focus order
+ tag.setAttribute('tabindex', '-1');
+ attrs.tabindex = '-1';
-// wrap native functions with a function
-// The list is as follows:
-// pause, load, play
-[
-/**
- * A wrapper around the media elements `pause` function. This will call the `HTML5`
- * media elements `pause` function.
- *
- * @method Html5#pause
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
- */
-'pause',
-/**
- * A wrapper around the media elements `load` function. This will call the `HTML5`s
- * media element `load` function.
- *
- * @method Html5#load
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
- */
-'load',
-/**
- * A wrapper around the media elements `play` function. This will call the `HTML5`s
- * media element `play` function.
- *
- * @method Html5#play
- * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
- */
-'play'].forEach(function (prop) {
- Html5.prototype[prop] = function () {
- return this.el_[prop]();
- };
-});
-Tech.withSourceHandlers(Html5);
+ // Workaround for #4583 on Chrome (on Windows) with JAWS.
+ // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
+ // Note that we can't detect if JAWS is being used, but this ARIA attribute
+ // doesn't change behavior of Chrome if JAWS is not being used
+ if (IS_CHROME && IS_WINDOWS) {
+ tag.setAttribute('role', 'application');
+ attrs.role = 'application';
+ }
-/**
- * Native source handler for Html5, simply passes the source to the media element.
- *
- * @property {Tech~SourceObject} source
- * The source object
- *
- * @property {Html5} tech
- * The instance of the HTML5 tech.
- */
-Html5.nativeSourceHandler = {};
+ // Remove width/height attrs from tag so CSS can make it 100% width/height
+ tag.removeAttribute('width');
+ tag.removeAttribute('height');
+ if ('width' in attrs) {
+ delete attrs.width;
+ }
+ if ('height' in attrs) {
+ delete attrs.height;
+ }
+ Object.getOwnPropertyNames(attrs).forEach(function (attr) {
+ // don't copy over the class attribute to the player element when we're in a div embed
+ // the class is already set up properly in the divEmbed case
+ // and we want to make sure that the `video-js` class doesn't get lost
+ if (!(divEmbed && attr === 'class')) {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ if (divEmbed) {
+ tag.setAttribute(attr, attrs[attr]);
+ }
+ });
-/**
- * Check if the media element can play the given mime type.
- *
- * @param {string} type
- * The mimetype to check
- *
- * @return {string}
- * 'probably', 'maybe', or '' (empty string)
- */
-Html5.nativeSourceHandler.canPlayType = function (type) {
- // IE without MediaPlayer throws an error (#519)
- try {
- return Html5.TEST_VID.canPlayType(type);
- } catch (e) {
- return '';
+ // Update tag id/class for use as HTML5 playback tech
+ // Might think we should do this after embedding in container so .vjs-tech class
+ // doesn't flash 100% width/height, but class only applies with .video-js parent
+ tag.playerId = tag.id;
+ tag.id += '_html5_api';
+ tag.className = 'vjs-tech';
+
+ // Make player findable on elements
+ tag.player = el.player = this;
+ // Default state of video is paused
+ this.addClass('vjs-paused');
+
+ // Add a style element in the player that we'll use to set the width/height
+ // of the player in a way that's still overridable by CSS, just like the
+ // video element
+ if ((global_window__WEBPACK_IMPORTED_MODULE_0___default().VIDEOJS_NO_DYNAMIC_STYLE) !== true) {
+ this.styleEl_ = createStyleElement('vjs-styles-dimensions');
+ const defaultsStyleEl = $('.vjs-styles-defaults');
+ const head = $('head');
+ head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
+ }
+ this.fill_ = false;
+ this.fluid_ = false;
+
+ // Pass in the width/height/aspectRatio options which will update the style el
+ this.width(this.options_.width);
+ this.height(this.options_.height);
+ this.fill(this.options_.fill);
+ this.fluid(this.options_.fluid);
+ this.aspectRatio(this.options_.aspectRatio);
+ // support both crossOrigin and crossorigin to reduce confusion and issues around the name
+ this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin);
+
+ // Hide any links within the video/audio tag,
+ // because IE doesn't hide them completely from screen readers.
+ const links = tag.getElementsByTagName('a');
+ for (let i = 0; i < links.length; i++) {
+ const linkEl = links.item(i);
+ addClass(linkEl, 'vjs-hidden');
+ linkEl.setAttribute('hidden', 'hidden');
+ }
+
+ // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
+ // keep track of the original for later so we can know if the source originally failed
+ tag.initNetworkState_ = tag.networkState;
+
+ // Wrap video tag in div (el/box) container
+ if (tag.parentNode && !playerElIngest) {
+ tag.parentNode.insertBefore(el, tag);
+ }
+
+ // insert the tag as the first child of the player element
+ // then manually add it to the children array so that this.addChild
+ // will work properly for other components
+ //
+ // Breaks iPhone, fixed in HTML5 setup.
+ prependTo(tag, el);
+ this.children_.unshift(tag);
+
+ // Set lang attr on player to ensure CSS :lang() in consistent with player
+ // if it's been set to something different to the doc
+ this.el_.setAttribute('lang', this.language_);
+ this.el_.setAttribute('translate', 'no');
+ this.el_ = el;
+ return el;
}
-};
-/**
- * Check if the media element can handle a source natively.
- *
- * @param {Tech~SourceObject} source
- * The source object
- *
- * @param {Object} [options]
- * Options to be passed to the tech.
- *
- * @return {string}
- * 'probably', 'maybe', or '' (empty string).
- */
-Html5.nativeSourceHandler.canHandleSource = function (source, options) {
- // If a type was provided we should rely on that
- if (source.type) {
- return Html5.nativeSourceHandler.canPlayType(source.type);
+ /**
+ * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
+ * sets the `crossOrigin` property on the `