UNPKG

hls-parser

Version:

A simple library to read/write HLS playlists

1,313 lines (1,304 loc) 118 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["HLS"] = factory(); else root["HLS"] = factory(); })(self, () => { return /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "./index.ts": /*!******************!*\ !*** ./index.ts ***! \******************/ /***/ (function(__unused_webpack_module, exports, __webpack_require__) { var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.setOptions = exports.getOptions = exports.types = exports.stringify = exports.parse = void 0; /*! Copyright Kuu Miyazaki. SPDX-License-Identifier: MIT */ const utils_1 = __webpack_require__(/*! ./utils */ "./utils.js"); Object.defineProperty(exports, "getOptions", ({ enumerable: true, get: function () { return utils_1.getOptions; } })); Object.defineProperty(exports, "setOptions", ({ enumerable: true, get: function () { return utils_1.setOptions; } })); const parse_1 = __importDefault(__webpack_require__(/*! ./parse */ "./parse.js")); exports.parse = parse_1.default; const stringify_1 = __importDefault(__webpack_require__(/*! ./stringify */ "./stringify.js")); exports.stringify = stringify_1.default; const types = __importStar(__webpack_require__(/*! ./types */ "./types.js")); exports.types = types; /***/ }), /***/ "./parse.js": /*!******************!*\ !*** ./parse.js ***! \******************/ /***/ (function(__unused_webpack_module, exports, __webpack_require__) { function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function get() { return m[k]; } }; } Object.defineProperty(o, k2, desc); } : function (o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; }); var __setModuleDefault = this && this.__setModuleDefault || (Object.create ? function (o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); } : function (o, v) { o["default"] = v; }); var __importStar = this && this.__importStar || function () { var _ownKeys = function ownKeys(o) { _ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return _ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = _ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; }(); Object.defineProperty(exports, "__esModule", ({ value: true })); var utils = __importStar(__webpack_require__(/*! ./utils */ "./utils.js")); var types_1 = __webpack_require__(/*! ./types */ "./types.js"); function unquote(str) { return utils.trim(str, '"'); } function getTagCategory(tagName) { switch (tagName) { case 'EXTM3U': case 'EXT-X-VERSION': case 'EXT-X-CONTENT-STEERING': return 'Basic'; case 'EXTINF': case 'EXT-X-BYTERANGE': case 'EXT-X-DISCONTINUITY': case 'EXT-X-PREFETCH-DISCONTINUITY': case 'EXT-X-KEY': case 'EXT-X-MAP': case 'EXT-X-PROGRAM-DATE-TIME': case 'EXT-X-DATERANGE': case 'EXT-X-CUE-OUT': case 'EXT-X-CUE-IN': case 'EXT-X-CUE-OUT-CONT': case 'EXT-X-CUE': case 'EXT-OATCLS-SCTE35': case 'EXT-X-ASSET': case 'EXT-X-SCTE35': case 'EXT-X-PART': case 'EXT-X-PRELOAD-HINT': case 'EXT-X-GAP': return 'Segment'; case 'EXT-X-TARGETDURATION': case 'EXT-X-MEDIA-SEQUENCE': case 'EXT-X-DISCONTINUITY-SEQUENCE': case 'EXT-X-ENDLIST': case 'EXT-X-PLAYLIST-TYPE': case 'EXT-X-I-FRAMES-ONLY': case 'EXT-X-SERVER-CONTROL': case 'EXT-X-PART-INF': case 'EXT-X-PREFETCH': case 'EXT-X-RENDITION-REPORT': case 'EXT-X-SKIP': return 'MediaPlaylist'; case 'EXT-X-MEDIA': case 'EXT-X-STREAM-INF': case 'EXT-X-I-FRAME-STREAM-INF': case 'EXT-X-SESSION-DATA': case 'EXT-X-SESSION-KEY': return 'MasterPlaylist'; case 'EXT-X-INDEPENDENT-SEGMENTS': case 'EXT-X-START': return 'MediaorMasterPlaylist'; default: return 'Unknown'; } } function parseEXTINF(param) { var pair = utils.splitAt(param, ','); return { duration: utils.toNumber(pair[0]), title: decodeURIComponent(escape(pair[1])) }; } function parseBYTERANGE(param) { var pair = utils.splitAt(param, '@'); return { length: utils.toNumber(pair[0]), offset: pair[1] ? utils.toNumber(pair[1]) : -1 }; } function parseResolution(str) { var pair = utils.splitAt(str, 'x'); return { width: utils.toNumber(pair[0]), height: utils.toNumber(pair[1]) }; } function parseAllowedCpc(str) { var message = 'ALLOWED-CPC: Each entry must consit of KEYFORMAT and Content Protection Configuration'; var list = str.split(','); if (list.length === 0) { utils.INVALIDPLAYLIST(message); } var allowedCpcList = []; var _iterator = _createForOfIteratorHelper(list), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var item = _step.value; var _utils$splitAt = utils.splitAt(item, ':'), _utils$splitAt2 = _slicedToArray(_utils$splitAt, 2), format = _utils$splitAt2[0], cpcText = _utils$splitAt2[1]; if (!format || !cpcText) { utils.INVALIDPLAYLIST(message); continue; } allowedCpcList.push({ format: format, cpcList: cpcText.split('/') }); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return allowedCpcList; } function parseIV(str) { var iv = utils.hexToByteSequence(str); if (iv.length !== 16) { utils.INVALIDPLAYLIST('IV must be a 128-bit unsigned integer'); } return iv; } function parseUserAttribute(str) { if (str.startsWith('"')) { return unquote(str); } if (str.startsWith('0x') || str.startsWith('0X')) { return utils.hexToByteSequence(str); } return utils.toNumber(str); } function setCompatibleVersionOfKey(params, attributes) { if (attributes['IV'] && params.compatibleVersion < 2) { params.compatibleVersion = 2; } if ((attributes['KEYFORMAT'] || attributes['KEYFORMATVERSIONS']) && params.compatibleVersion < 5) { params.compatibleVersion = 5; } } function parseAttributeList(param) { var attributes = {}; var _iterator2 = _createForOfIteratorHelper(utils.splitByCommaWithPreservingQuotes(param)), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var item = _step2.value; var _utils$splitAt3 = utils.splitAt(item, '='), _utils$splitAt4 = _slicedToArray(_utils$splitAt3, 2), key = _utils$splitAt4[0], value = _utils$splitAt4[1]; var val = unquote(value); switch (key) { case 'URI': attributes[key] = val; break; case 'START-DATE': case 'END-DATE': attributes[key] = new Date(val); break; case 'IV': attributes[key] = parseIV(val); break; case 'BYTERANGE': attributes[key] = parseBYTERANGE(val); break; case 'RESOLUTION': attributes[key] = parseResolution(val); break; case 'ALLOWED-CPC': attributes[key] = parseAllowedCpc(val); break; case 'END-ON-NEXT': case 'DEFAULT': case 'AUTOSELECT': case 'FORCED': case 'PRECISE': case 'CAN-BLOCK-RELOAD': case 'INDEPENDENT': case 'GAP': attributes[key] = val === 'YES'; break; case 'DURATION': case 'PLANNED-DURATION': case 'BANDWIDTH': case 'AVERAGE-BANDWIDTH': case 'FRAME-RATE': case 'TIME-OFFSET': case 'CAN-SKIP-UNTIL': case 'HOLD-BACK': case 'PART-HOLD-BACK': case 'PART-TARGET': case 'BYTERANGE-START': case 'BYTERANGE-LENGTH': case 'LAST-MSN': case 'LAST-PART': case 'SKIPPED-SEGMENTS': case 'SCORE': case 'PROGRAM-ID': attributes[key] = utils.toNumber(val); break; default: if (key.startsWith('SCTE35-')) { attributes[key] = utils.hexToByteSequence(val); } else if (key.startsWith('X-')) { attributes[key] = parseUserAttribute(value); } else { if (key === 'VIDEO-RANGE' && val !== 'SDR' && val !== 'HLG' && val !== 'PQ') { utils.INVALIDPLAYLIST("VIDEO-RANGE: unknown value \"".concat(val, "\"")); } attributes[key] = val; } } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return attributes; } function parseTagParam(name, param) { switch (name) { case 'EXTM3U': case 'EXT-X-DISCONTINUITY': case 'EXT-X-ENDLIST': case 'EXT-X-I-FRAMES-ONLY': case 'EXT-X-INDEPENDENT-SEGMENTS': case 'EXT-X-CUE-IN': case 'EXT-X-GAP': return [null, null]; case 'EXT-X-VERSION': case 'EXT-X-TARGETDURATION': case 'EXT-X-MEDIA-SEQUENCE': case 'EXT-X-DISCONTINUITY-SEQUENCE': return [utils.toNumber(param), null]; case 'EXT-X-CUE-OUT': // For backwards compatibility: attributes list is optional, // if only a number is found, use it as the duration if (!Number.isNaN(Number(param))) { return [utils.toNumber(param), null]; } // If attributes are found, parse them out (i.e. DURATION) return [null, parseAttributeList(param)]; case 'EXT-X-KEY': case 'EXT-X-MAP': case 'EXT-X-DATERANGE': case 'EXT-X-MEDIA': case 'EXT-X-STREAM-INF': case 'EXT-X-I-FRAME-STREAM-INF': case 'EXT-X-SESSION-DATA': case 'EXT-X-SESSION-KEY': case 'EXT-X-START': case 'EXT-X-SERVER-CONTROL': case 'EXT-X-PART-INF': case 'EXT-X-PART': case 'EXT-X-PRELOAD-HINT': case 'EXT-X-RENDITION-REPORT': case 'EXT-X-SKIP': return [null, parseAttributeList(param)]; case 'EXTINF': return [parseEXTINF(param), null]; case 'EXT-X-BYTERANGE': return [parseBYTERANGE(param), null]; case 'EXT-X-PROGRAM-DATE-TIME': return [new Date(param), null]; case 'EXT-X-PLAYLIST-TYPE': return [param, null]; // <EVENT|VOD> default: return [param, null]; // Unknown tag } } function MIXEDTAGS() { utils.INVALIDPLAYLIST("The file contains both media and master playlist tags."); } function splitTag(line) { var index = line.indexOf(':'); if (index === -1) { return [line.slice(1).trim(), null]; } return [line.slice(1, index).trim(), line.slice(index + 1).trim()]; } function parseRendition(_ref) { var attributes = _ref.attributes; var rendition = new types_1.Rendition({ type: attributes['TYPE'], uri: attributes['URI'], groupId: attributes['GROUP-ID'], language: attributes['LANGUAGE'], assocLanguage: attributes['ASSOC-LANGUAGE'], name: attributes['NAME'], isDefault: attributes['DEFAULT'], autoselect: attributes['AUTOSELECT'], forced: attributes['FORCED'], instreamId: attributes['INSTREAM-ID'], characteristics: attributes['CHARACTERISTICS'], channels: attributes['CHANNELS'], pathwayId: attributes['PATHWAY-ID'] }); return rendition; } function checkRedundantRendition(renditions, rendition) { var defaultFound = false; var _iterator3 = _createForOfIteratorHelper(renditions), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var item = _step3.value; if (item.name === rendition.name) { return 'All EXT-X-MEDIA tags in the same Group MUST have different NAME attributes.'; } if (item.isDefault) { defaultFound = true; } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } if (defaultFound && rendition.isDefault) { return 'EXT-X-MEDIA A Group MUST NOT have more than one member with a DEFAULT attribute of YES.'; } return ''; } function addRendition(variant, line, type) { var rendition = parseRendition(line); var renditions = variant[utils.camelify(type)]; var errorMessage = checkRedundantRendition(renditions, rendition); if (errorMessage) { utils.INVALIDPLAYLIST(errorMessage); } renditions.push(rendition); if (rendition.isDefault) { variant.currentRenditions[utils.camelify(type)] = renditions.length - 1; } } function matchTypes(attrs, variant, params) { var _loop = function _loop() { var type = _arr[_i]; if (type === 'CLOSED-CAPTIONS' && attrs[type] === 'NONE') { params.isClosedCaptionsNone = true; variant.closedCaptions = []; } else if (attrs[type] && !variant[utils.camelify(type)].some(function (item) { return item.groupId === attrs[type]; })) { utils.INVALIDPLAYLIST("".concat(type, " attribute MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag whose TYPE attribute is ").concat(type, ".")); } }; for (var _i = 0, _arr = ['AUDIO', 'VIDEO', 'SUBTITLES', 'CLOSED-CAPTIONS']; _i < _arr.length; _i++) { _loop(); } } function parseVariant(lines, variantAttrs, uri, iFrameOnly, params) { var variant = new types_1.Variant({ uri: uri, bandwidth: variantAttrs['BANDWIDTH'], averageBandwidth: variantAttrs['AVERAGE-BANDWIDTH'], score: variantAttrs['SCORE'], codecs: variantAttrs['CODECS'], resolution: variantAttrs['RESOLUTION'], frameRate: variantAttrs['FRAME-RATE'], hdcpLevel: variantAttrs['HDCP-LEVEL'], allowedCpc: variantAttrs['ALLOWED-CPC'], videoRange: variantAttrs['VIDEO-RANGE'], stableVariantId: variantAttrs['STABLE-VARIANT-ID'], pathwayId: variantAttrs['STABLE-PATHWAY-ID'], programId: variantAttrs['PROGRAM-ID'] }); var _iterator4 = _createForOfIteratorHelper(lines), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var line = _step4.value; if (line.name === 'EXT-X-MEDIA') { var renditionAttrs = line.attributes; var renditionType = renditionAttrs['TYPE']; if (!renditionType || !renditionAttrs['GROUP-ID']) { utils.INVALIDPLAYLIST('EXT-X-MEDIA TYPE attribute is REQUIRED.'); } if (variantAttrs[renditionType] === renditionAttrs['GROUP-ID']) { addRendition(variant, line, renditionType); if (renditionType === 'CLOSED-CAPTIONS') { var _iterator5 = _createForOfIteratorHelper(variant.closedCaptions), _step5; try { for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { var instreamId = _step5.value.instreamId; if (instreamId && instreamId.startsWith('SERVICE') && params.compatibleVersion < 7) { params.compatibleVersion = 7; break; } } } catch (err) { _iterator5.e(err); } finally { _iterator5.f(); } } } } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } matchTypes(variantAttrs, variant, params); variant.isIFrameOnly = iFrameOnly; return variant; } function sameKey(key1, key2) { if (key1.method !== key2.method) { return false; } if (key1.uri !== key2.uri) { return false; } if (key1.iv) { if (!key2.iv) { return false; } if (key1.iv.byteLength !== key2.iv.byteLength) { return false; } for (var i = 0; i < key1.iv.byteLength; i++) { if (key1.iv[i] !== key2.iv[i]) { return false; } } } else if (key2.iv) { return false; } if (key1.format !== key2.format) { return false; } if (key1.formatVersion !== key2.formatVersion) { return false; } return true; } function parseMasterPlaylist(lines, params) { var playlist = new types_1.MasterPlaylist(); var variantIsScored = false; var _iterator6 = _createForOfIteratorHelper(lines.entries()), _step6; try { var _loop2 = function _loop2() { var _step6$value = _slicedToArray(_step6.value, 2), index = _step6$value[0], line = _step6$value[1]; var _mapTo = mapTo(line), name = _mapTo.name, value = _mapTo.value, attributes = _mapTo.attributes; if (name === 'EXT-X-VERSION') { playlist.version = value; } else if (name === 'EXT-X-CONTENT-STEERING-SERVER') { var contentSteering = new types_1.ContentSteering({ serverUri: attributes['SERVER-URI'], pathwayId: attributes['PATHWAY-ID'] }); playlist.contentSteering = contentSteering; } else if (name === 'EXT-X-STREAM-INF') { var uri = lines[index + 1]; if (typeof uri !== 'string' || uri.startsWith('#EXT')) { utils.INVALIDPLAYLIST('EXT-X-STREAM-INF must be followed by a URI line'); } var _variant2 = parseVariant(lines, attributes, uri, false, params); if (_variant2) { if (typeof _variant2.score === 'number') { variantIsScored = true; if (_variant2.score < 0) { utils.INVALIDPLAYLIST('SCORE attribute on EXT-X-STREAM-INF must be positive decimal-floating-point number.'); } } playlist.variants.push(_variant2); } } else if (name === 'EXT-X-I-FRAME-STREAM-INF') { var _variant3 = parseVariant(lines, attributes, attributes.URI, true, params); if (_variant3) { playlist.variants.push(_variant3); } } else if (name === 'EXT-X-SESSION-DATA') { var sessionData = new types_1.SessionData({ id: attributes['DATA-ID'], value: attributes['VALUE'], uri: attributes['URI'], language: attributes['LANGUAGE'] }); if (playlist.sessionDataList.some(function (item) { return item.id === sessionData.id && item.language === sessionData.language; })) { utils.INVALIDPLAYLIST('A Playlist MUST NOT contain more than one EXT-X-SESSION-DATA tag with the same DATA-ID attribute and the same LANGUAGE attribute.'); } playlist.sessionDataList.push(sessionData); } else if (name === 'EXT-X-SESSION-KEY') { if (attributes['METHOD'] === 'NONE') { utils.INVALIDPLAYLIST('EXT-X-SESSION-KEY: The value of the METHOD attribute MUST NOT be NONE'); } var sessionKey = new types_1.Key({ method: attributes['METHOD'], uri: attributes['URI'], iv: attributes['IV'], format: attributes['KEYFORMAT'], formatVersion: attributes['KEYFORMATVERSIONS'] }); if (playlist.sessionKeyList.some(function (item) { return sameKey(item, sessionKey); })) { utils.INVALIDPLAYLIST('A Master Playlist MUST NOT contain more than one EXT-X-SESSION-KEY tag with the same METHOD, URI, IV, KEYFORMAT, and KEYFORMATVERSIONS attribute values.'); } setCompatibleVersionOfKey(params, attributes); playlist.sessionKeyList.push(sessionKey); } else if (name === 'EXT-X-INDEPENDENT-SEGMENTS') { if (playlist.independentSegments) { utils.INVALIDPLAYLIST('EXT-X-INDEPENDENT-SEGMENTS tag MUST NOT appear more than once in a Playlist'); } playlist.independentSegments = true; } else if (name === 'EXT-X-START') { if (playlist.start) { utils.INVALIDPLAYLIST('EXT-X-START tag MUST NOT appear more than once in a Playlist'); } if (typeof attributes['TIME-OFFSET'] !== 'number') { utils.INVALIDPLAYLIST('EXT-X-START: TIME-OFFSET attribute is REQUIRED'); } playlist.start = { offset: attributes['TIME-OFFSET'], precise: attributes['PRECISE'] || false }; } }; for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) { _loop2(); } } catch (err) { _iterator6.e(err); } finally { _iterator6.f(); } if (variantIsScored) { var _iterator7 = _createForOfIteratorHelper(playlist.variants), _step7; try { for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) { var variant = _step7.value; if (typeof variant.score !== 'number') { utils.INVALIDPLAYLIST('If any Variant Stream contains the SCORE attribute, then all Variant Streams in the Master Playlist SHOULD have a SCORE attribute'); } } } catch (err) { _iterator7.e(err); } finally { _iterator7.f(); } } if (params.isClosedCaptionsNone) { var _iterator8 = _createForOfIteratorHelper(playlist.variants), _step8; try { for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) { var _variant = _step8.value; if (_variant.closedCaptions.length > 0) { utils.INVALIDPLAYLIST('If there is a variant with CLOSED-CAPTIONS attribute of NONE, all EXT-X-STREAM-INF tags MUST have this attribute with a value of NONE'); } } } catch (err) { _iterator8.e(err); } finally { _iterator8.f(); } } return playlist; } function parseSegment(lines, uri, start, end, mediaSequenceNumber, discontinuitySequence, params) { var segment = new types_1.Segment({ uri: uri, mediaSequenceNumber: mediaSequenceNumber, discontinuitySequence: discontinuitySequence }); var mapHint = false; var partHint = false; for (var i = start; i <= end; i++) { var _mapTo2 = mapTo(lines[i]), name = _mapTo2.name, value = _mapTo2.value, attributes = _mapTo2.attributes; if (name === 'EXTINF') { if (!Number.isInteger(value.duration) && params.compatibleVersion < 3) { params.compatibleVersion = 3; } if (Math.round(value.duration) > params.targetDuration) { utils.INVALIDPLAYLIST('EXTINF duration, when rounded to the nearest integer, MUST be less than or equal to the target duration'); } segment.duration = value.duration; segment.title = value.title; } else if (name === 'EXT-X-BYTERANGE') { if (params.compatibleVersion < 4) { params.compatibleVersion = 4; } segment.byterange = value; } else if (name === 'EXT-X-DISCONTINUITY') { if (segment.parts.length > 0) { utils.INVALIDPLAYLIST('EXT-X-DISCONTINUITY must appear before the first EXT-X-PART tag of the Parent Segment.'); } segment.discontinuity = true; } else if (name === 'EXT-X-GAP') { if (params.compatibleVersion < 8) { params.compatibleVersion = 8; } segment.gap = true; } else if (name === 'EXT-X-KEY') { if (segment.parts.length > 0) { utils.INVALIDPLAYLIST('EXT-X-KEY must appear before the first EXT-X-PART tag of the Parent Segment.'); } setCompatibleVersionOfKey(params, attributes); segment.key = new types_1.Key({ method: attributes['METHOD'], uri: attributes['URI'], iv: attributes['IV'], format: attributes['KEYFORMAT'], formatVersion: attributes['KEYFORMATVERSIONS'] }); } else if (name === 'EXT-X-MAP') { if (segment.parts.length > 0) { utils.INVALIDPLAYLIST('EXT-X-MAP must appear before the first EXT-X-PART tag of the Parent Segment.'); } if (params.compatibleVersion < 5) { params.compatibleVersion = 5; } params.hasMap = true; segment.map = new types_1.MediaInitializationSection({ uri: attributes['URI'], byterange: attributes['BYTERANGE'] }); } else if (name === 'EXT-X-PROGRAM-DATE-TIME') { segment.programDateTime = value; } else if (name === 'EXT-X-DATERANGE') { var attrs = {}; for (var _i2 = 0, _Object$keys = Object.keys(attributes); _i2 < _Object$keys.length; _i2++) { var key = _Object$keys[_i2]; if (key.startsWith('SCTE35-') || key.startsWith('X-')) { attrs[key] = attributes[key]; } } segment.dateRange = new types_1.DateRange({ id: attributes['ID'], classId: attributes['CLASS'], start: attributes['START-DATE'], end: attributes['END-DATE'], duration: attributes['DURATION'], plannedDuration: attributes['PLANNED-DURATION'], endOnNext: attributes['END-ON-NEXT'], attributes: attrs }); } else if (name === 'EXT-X-CUE-OUT') { segment.markers.push(new types_1.SpliceInfo({ type: 'OUT', duration: attributes && attributes.DURATION || value })); } else if (name === 'EXT-X-CUE-IN') { segment.markers.push(new types_1.SpliceInfo({ type: 'IN' })); } else if (name === 'EXT-X-CUE-OUT-CONT' || name === 'EXT-X-CUE' || name === 'EXT-OATCLS-SCTE35' || name === 'EXT-X-ASSET' || name === 'EXT-X-SCTE35') { segment.markers.push(new types_1.SpliceInfo({ type: 'RAW', tagName: name, value: value })); } else if (name === 'EXT-X-PRELOAD-HINT' && !attributes['TYPE']) { utils.INVALIDPLAYLIST('EXT-X-PRELOAD-HINT: TYPE attribute is mandatory'); } else if (name === 'EXT-X-PRELOAD-HINT' && attributes['TYPE'] === 'PART' && partHint) { utils.INVALIDPLAYLIST('Servers should not add more than one EXT-X-PRELOAD-HINT tag with the same TYPE attribute to a Playlist.'); } else if ((name === 'EXT-X-PART' || name === 'EXT-X-PRELOAD-HINT') && !attributes['URI']) { utils.INVALIDPLAYLIST('EXT-X-PART / EXT-X-PRELOAD-HINT: URI attribute is mandatory'); } else if (name === 'EXT-X-PRELOAD-HINT' && attributes['TYPE'] === 'MAP') { if (mapHint) { utils.INVALIDPLAYLIST('Servers should not add more than one EXT-X-PRELOAD-HINT tag with the same TYPE attribute to a Playlist.'); } mapHint = true; params.hasMap = true; segment.map = new types_1.MediaInitializationSection({ hint: true, uri: attributes['URI'], byterange: { length: attributes['BYTERANGE-LENGTH'], offset: attributes['BYTERANGE-START'] || 0 } }); } else if (name === 'EXT-X-PART' || name === 'EXT-X-PRELOAD-HINT' && attributes['TYPE'] === 'PART') { if (name === 'EXT-X-PART' && !attributes['DURATION']) { utils.INVALIDPLAYLIST('EXT-X-PART: DURATION attribute is mandatory'); } if (name === 'EXT-X-PRELOAD-HINT') { partHint = true; } var partialSegment = new types_1.PartialSegment({ hint: name === 'EXT-X-PRELOAD-HINT', uri: attributes['URI'], byterange: name === 'EXT-X-PART' ? attributes['BYTERANGE'] : { length: attributes['BYTERANGE-LENGTH'], offset: attributes['BYTERANGE-START'] || 0 }, duration: attributes['DURATION'], independent: attributes['INDEPENDENT'], gap: attributes['GAP'] }); if (segment.gap && !partialSegment.gap) { // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-6.2.1 utils.INVALIDPLAYLIST('Partial segments must have GAP=YES if they are in a gap (EXT-X-GAP)'); } segment.parts.push(partialSegment); } } return segment; } function parsePrefetchSegment(lines, uri, start, end, mediaSequenceNumber, discontinuitySequence, params) { var segment = new types_1.PrefetchSegment({ uri: uri, mediaSequenceNumber: mediaSequenceNumber, discontinuitySequence: discontinuitySequence }); for (var i = start; i <= end; i++) { var _lines$i = lines[i], name = _lines$i.name, attributes = _lines$i.attributes; if (name === 'EXTINF') { utils.INVALIDPLAYLIST('A prefetch segment must not be advertised with an EXTINF tag.'); } else if (name === 'EXT-X-DISCONTINUITY') { utils.INVALIDPLAYLIST('A prefetch segment must not be advertised with an EXT-X-DISCONTINUITY tag.'); } else if (name === 'EXT-X-PREFETCH-DISCONTINUITY') { segment.discontinuity = true; } else if (name === 'EXT-X-KEY') { setCompatibleVersionOfKey(params, attributes); segment.key = new types_1.Key({ method: attributes['METHOD'], uri: attributes['URI'], iv: attributes['IV'], format: attributes['KEYFORMAT'], formatVersion: attributes['KEYFORMATVERSIONS'] }); } else if (name === 'EXT-X-MAP') { utils.INVALIDPLAYLIST('Prefetch segments must not be advertised with an EXT-X-MAP tag.'); } } return segment; } function parseMediaPlaylist(lines, params) { var playlist = new types_1.MediaPlaylist(); var segmentStart = -1; var mediaSequence = 0; var discontinuityFound = false; var prefetchFound = false; var discontinuitySequence = 0; var currentKey = null; var currentMap = null; var containsParts = false; var _iterator9 = _createForOfIteratorHelper(lines.entries()), _step9; try { for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) { var _step9$value = _slicedToArray(_step9.value, 2), index = _step9$value[0], line = _step9$value[1]; var _mapTo3 = mapTo(line), name = _mapTo3.name, value = _mapTo3.value, attributes = _mapTo3.attributes, category = _mapTo3.category; if (category === 'Segment') { if (segmentStart === -1) { segmentStart = index; } if (name === 'EXT-X-DISCONTINUITY') { discontinuityFound = true; } continue; } if (name === 'EXT-X-VERSION') { if (playlist.version === undefined) { playlist.version = value; } else { utils.INVALIDPLAYLIST('A Playlist file MUST NOT contain more than one EXT-X-VERSION tag.'); } } else if (name === 'EXT-X-TARGETDURATION') { playlist.targetDuration = params.targetDuration = value; } else if (name === 'EXT-X-MEDIA-SEQUENCE') { if (playlist.segments.length > 0) { utils.INVALIDPLAYLIST('The EXT-X-MEDIA-SEQUENCE tag MUST appear before the first Media Segment in the Playlist.'); } playlist.mediaSequenceBase = mediaSequence = value; } else if (name === 'EXT-X-DISCONTINUITY-SEQUENCE') { if (playlist.segments.length > 0) { utils.INVALIDPLAYLIST('The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before the first Media Segment in the Playlist.'); } if (discontinuityFound) { utils.INVALIDPLAYLIST('The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before any EXT-X-DISCONTINUITY tag.'); } playlist.discontinuitySequenceBase = discontinuitySequence = value; } else if (name === 'EXT-X-ENDLIST') { playlist.endlist = true; } else if (name === 'EXT-X-PLAYLIST-TYPE') { playlist.playlistType = value; } else if (name === 'EXT-X-I-FRAMES-ONLY') { if (params.compatibleVersion < 4) { params.compatibleVersion = 4; } playlist.isIFrame = true; } else if (name === 'EXT-X-INDEPENDENT-SEGMENTS') { if (playlist.independentSegments) { utils.INVALIDPLAYLIST('EXT-X-INDEPENDENT-SEGMENTS tag MUST NOT appear more than once in a Playlist'); } playlist.independentSegments = true; } else if (name === 'EXT-X-START') { if (playlist.start) { utils.INVALIDPLAYLIST('EXT-X-START tag MUST NOT appear more than once in a Playlist'); } if (typeof attributes['TIME-OFFSET'] !== 'number') { utils.INVALIDPLAYLIST('EXT-X-START: TIME-OFFSET attribute is REQUIRED'); } playlist.start = { offset: attributes['TIME-OFFSET'], precise: attributes['PRECISE'] || false }; } else if (name === 'EXT-X-SERVER-CONTROL') { if (!attributes['CAN-BLOCK-RELOAD']) { utils.INVALIDPLAYLIST('EXT-X-SERVER-CONTROL: CAN-BLOCK-RELOAD=YES is mandatory for Low-Latency HLS'); } playlist.lowLatencyCompatibility = { canBlockReload: attributes['CAN-BLOCK-RELOAD'], canSkipUntil: attributes['CAN-SKIP-UNTIL'], holdBack: attributes['HOLD-BACK'], partHoldBack: attributes['PART-HOLD-BACK'] }; } else if (name === 'EXT-X-PART-INF') { if (!attributes['PART-TARGET']) { utils.INVALIDPLAYLIST('EXT-X-PART-INF: PART-TARGET attribute is mandatory'); } playlist.partTargetDuration = attributes['PART-TARGET']; } else if (name === 'EXT-X-RENDITION-REPORT') { if (!attributes['URI']) { utils.INVALIDPLAYLIST('EXT-X-RENDITION-REPORT: URI attribute is mandatory'); } if (attributes['URI'].search(/^[a-z]+:/) === 0) { utils.INVALIDPLAYLIST('EXT-X-RENDITION-REPORT: URI must be relative to the playlist uri'); } playlist.renditionReports.push(new types_1.RenditionReport({ uri: attributes['URI'], lastMSN: attributes['LAST-MSN'], lastPart: attributes['LAST-PART'] })); } else if (name === 'EXT-X-SKIP') { if (!attributes['SKIPPED-SEGMENTS']) { utils.INVALIDPLAYLIST('EXT-X-SKIP: SKIPPED-SEGMENTS attribute is mandatory'); } if (params.compatibleVersion < 9) { params.compatibleVersion = 9; } playlist.skip = attributes['SKIPPED-SEGMENTS']; mediaSequence += playlist.skip; } else if (name === 'EXT-X-PREFETCH') { var _segment = parsePrefetchSegment(lines, value, segmentStart === -1 ? index : segmentStart, index - 1, mediaSequence++, discontinuitySequence, params); if (_segment) { if (_segment.discontinuity) { _segment.discontinuitySequence++; discontinuitySequence = _segment.discontinuitySequence; } if (_segment.key) { currentKey = _segment.key; } else { _segment.key = currentKey; } playlist.prefetchSegments.push(_segment); } prefetchFound = true; segmentStart = -1; } else if (typeof line === 'string') { // uri if (segmentStart === -1) { utils.INVALIDPLAYLIST('A URI line is not preceded by any segment tags'); } if (!playlist.targetDuration) { utils.INVALIDPLAYLIST('The EXT-X-TARGETDURATION tag is REQUIRED'); } if (prefetchFound) { utils.INVALIDPLAYLIST('These segments must appear after all complete segments.'); } var _segment2 = parseSegment(lines, line, segmentStart, index - 1, mediaSequence++, discontinuitySequence, params); if (_segment2) { var _addSegment = addSegment(playlist, _segment2, discontinuitySequence, currentKey, currentMap); var _addSegment2 = _slicedToArray(_addSegment, 3); discontinuitySequence = _addSegment2[0]; currentKey = _addSegment2[1]; currentMap = _addSegment2[2]; if (!containsParts && _segment2.parts.length > 0) { containsParts = true; } } segmentStart = -1; } } } catch (err) { _iterator9.e(err); } finally { _iterator9.f(); } if (segmentStart !== -1) { var segment = parseSegment(lines, '', segmentStart, lines.length - 1, mediaSequence++, discontinuitySequence, params); if (segment) { var _parts$at; var parts = segment.parts; if (parts.length > 0 && !playlist.endlist && !((_parts$at = parts.at(-1)) !== null && _parts$at !== void 0 && _parts$at.hint)) { utils.INVALIDPLAYLIST('If the Playlist contains EXT-X-PART tags and does not contain an EXT-X-ENDLIST tag, the Playlist must contain an EXT-X-PRELOAD-HINT tag with a TYPE=PART attribute'); } // @ts-expect-error TODO check if this is not a bug the third argument should be a discontinuitySequence addSegment(playlist, segment, currentKey, currentMap); if (!containsParts && segment.parts.length > 0) { containsParts = true; } } } checkDateRange(playlist.segments); if (playlist.lowLatencyCompatibility) { checkLowLatencyCompatibility(playlist, containsParts); } return playlist; } function addSegment(playlist, segment, discontinuitySequence, currentKey, currentMap) { var discontinuity = segment.discontinuity, key = segment.key, map = segment.map, byterange = segment.byterange, uri = segment.uri; if (discontinuity) { segment.discontinuitySequence = discontinuitySequence + 1; } if (!key) { segment.key = currentKey; } if (!map) { segment.map = currentMap; } if (byterange && byterange.offset === -1) { var segments = playlist.segments; if (segments.length > 0) { var prevSegment = segments.at(-1); if (prevSegment.byterange && prevSegment.uri === uri) { byterange.offset = prevSegment.byterange.offset + prevSegment.byterange.length; } else { utils.INVALIDPLAYLIST('If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST be a sub-range of the same media resource'); } } else { utils.INVALIDPLAYLIST('If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST appear in the Playlist file'); } } playlist.segments.push(segment); return [segment.discontinuitySequence, segment.key, segment.map]; } function checkDateRange(segments) { var earliestDates = new Map(); var rangeList = new Map(); var hasDateRange = false; var hasProgramDateTime = false; for (var i = segments.length - 1; i >= 0; i--) { var _segments$i = segments[i], programDateTime = _segments$i.programDateTime, dateRange = _segments$i.dateRange; if (programDateTime) { hasProgramDateTime = true; } if (dateRange && dateRange.start) { hasDateRange = true; if (dateRange.endOnNext && (dateRange.end || dateRange.duration)) { utils.INVALIDPLAYLIST('An EXT-X-DATERANGE tag with an END-ON-NEXT=YES attribute MUST NOT contain DURATION or END-DATE attributes.'); } var start = dateRange.start.getTime(); var duration = dateRange.duration || 0; if (dateRange.end && dateRange.duration) { if (start + duration * 1000 !== dateRange.end.getTime()) { utils.INVALIDPLAYLIST('END-DATE MUST be equal to the value of the START-DATE attribute plus the value of the DURATION'); } } if (dateRange.endOnNext) { dateRange.end = earliestDates.get(dateRange.classId); } earliestDates.set(dateRange.classId, dateRange.start); var end = dateRange.end ? dateRange.end.getTime() : dateRange.start.getTime() + (dateRange.duration || 0) * 1000; var range = rangeList.get(dateRange.classId); if (range) { var _iterator0 = _createForOfIteratorHelper(range), _step0; try { for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) { var entry = _step0.value; if (entry.start <= start && entry.end > start || entry.start >= start && entry.start < end) { utils.INVALIDPLAYLIST('DATERANGE tags with the same CLASS should not overlap'); } } } catch (err) { _iterator0.e(err); } finally { _iterator0.f(); } range.push({ start: start, end: end }); } else if (dateRange.classId) { rangeList.set(dateRange.classId, [{ start: start, end: end }]); } } } if (hasDateRange && !hasProgramDateTime) { utils.INVALIDPLAYLIST('If a Playlist contains an EXT-X-DATERANGE tag, it MUST also contain at least one EXT-X-PROGRAM-DATE-TIME tag.'); } } function checkLowLatencyCompatibility(_ref2, containsParts) { var lowLatencyCompatibility = _ref2.lowLatencyCompatibility, targetDuration = _ref2.targetDuration, partTargetDuration = _ref2.partTargetDuration, segments = _ref2.segments, renditionReports = _ref2.renditionReports; var canSkipUntil = lowLatencyCompatibility.canSkipUntil, holdBack = lowLatencyCompatibility.holdBack, partHoldBack = lowLatencyCompatibility.partHoldBack; if (canSkipUntil < targetDuration * 6) { utils.INVALIDPLAYLIST('The Skip Boundary must be at least six times the EXT-X-TARGETDURATION.'); } // Its value is a floating-point number of seconds and . if (holdBack < targetDuration * 3) { utils.INVALIDPLAYLIST('HOLD-BACK must be at least three times the EXT-X-TARGETDURATION.'); } if (containsParts) { if (partTargetDuration === undefined) { utils.INVALIDPLAYLIST('EXT-X-PART-INF is required if a Playlist contains one or more EXT-X-PART tags'); } if (partHoldBack === undefined) { utils.INVALIDPLAYLIST('EXT-X-PART: PART-HOLD-BACK attribute is mandatory'); } if (partHoldBack < partTargetDuration) { utils.INVALIDPLAYLIST('PART-HOLD-BACK must be at least PART-TARGET'); } var _iterator1 = _createForOfIteratorHelper(segments.entries()), _step1; try { for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) { var _step1$value = _slicedToArray(_step1.value, 2), segmentIndex = _step1$value[0], parts = _step1$value[1].parts; if (parts.length > 0 && segmentIndex < segments.length - 3) { utils.INVALIDPLAYLIST('Remove EXT-X-PART tags from the Playlist after they are greater than three target durations from the end of the Playlist.'); } var _iterator10 = _createForOfIteratorHelper(parts.entries()), _step10; try { for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) { var _step10$value = _slicedToArray(_step10.value, 2), partIndex = _step10$value[0], duration = _step10$value[1].duration; if (duration === undefined) { continue; } if (duration > partTargetDuration) { utils.INVALIDPLAYLIST('PART-TARGET is the maximum duration of any Partial Segment'); } if (partIndex < parts.length - 1 && duration < partTargetDuration * 0.85) { utils.INVALIDPLAYLIST('All Partial Segments except the last part of a segment must have a duration of at least 85% of PART-TARGET'); } } } catch (err) { _iterator10.e(err); } finally { _iterator10.f(); } } } catch (err) { _iterator1.e(err); } finally { _iterator1.f(); } } var _iterator11 = _createForOfIteratorHelper(renditionReports), _step11; try { for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) { var _report$lastMSN; var report = _step11.value; var lastSegment = segments.at(-1); (_report$lastMSN = report.lastMSN) !== null && _report$lastMSN !== void 0 ? _report$lastMSN : report.lastMSN = lastSegment.mediaSequenceNumber; if ((report.lastPart === null || report.lastPart === undefined) && lastSegment.parts.length > 0) { report.lastPart = lastSegment.parts.length - 1; } } } catch (err) { _iterator11.e(err); } finally { _iterator11.f(); } } function CHECKTAGCATEGORY(category, params) { if (category === 'Segment' || category === 'MediaPlaylist') { if (params.isMasterPlaylist === undefined) { params.isMasterPlaylist = false; return; } if (params.isMasterPlaylist) { MIXEDTAGS(); } return; } if (category === 'MasterPlaylist') { if (params.isMasterPlaylist === undefined) { params.isMasterPlaylist = true; return; } if (params.isMasterPlaylist === false) { MIXEDTAGS(); } } // category === 'Basic' or 'MediaorMasterPlaylist' or 'Unknown' } function parseTag(line, params) { var _splitTag = splitTag(line), _splitTag2 = _slicedToArray(_splitTag, 2), name = _splitTag2[0], param = _splitTag2[1]; var category = getTagCategory(name); CHECKTAGCATEGORY(category, params); if (category === 'Unknown') { return null; } if (category === 'MediaPlaylist' && name !== 'EXT-X-RENDITION-REPORT' && name !== 'EXT-X-PREFETCH') { if (params.hash[name]) { utils.INVALIDPLAYLIST('There MUST NOT be more than one Media Playlist tag of each type in any Media Playlist'); } params.hash[name] = true; } var _parseTagParam = parseTagParam(name, param), _parseTagParam2 = _slicedToArray(_parseTagParam, 2), value = _parseTagParam2[0], attributes = _parseTagParam2[1]; return { name: name, category: category, value: value, attributes: attributes }; } function lexicalParse(text, params) { var lines = []; var _iterator12 = _createForOfIteratorHelper(text.split('\n')), _step12; try { for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) { var l = _step12.value; // V8 has garbage collection issues when cleaning up substrings split from strings greater // than 13 characters so before we continue we need to safely copy over each line so that it // doesn't hold any reference to the containing string. var line = l.trim(); if (!line) { // empty line continue; } if (line.startsWith('#')) { if (line.startsWith('#EXT')) { // tag var tag = parseTag(line, params); if (tag) { lines.push(tag); } } // comment continue; } // uri lines.push(line); } } catch (err) { _iterator12.e(err); } finally { _iterator12.f(); } if (lines.length === 0 || lines[0].name !== 'EXTM3U')