6/18/ds/18js/lib/hls.js

940 lines
44 KiB
JavaScript

/*
* @File : hls.js.js
* @Author : jade
* @Date : 2024/2/5 16:07
* @Email : jadehh@1ive.com
* @Software : Samples
* @Desc :
*/
let t = {};
function e(e) {
if (t.strictMode) throw e;
t.silent || console.error(e.message)
}
function s(t, ...s) {
for (const [i, n] of s.entries()) n || e(new Error(`${t} : Failed at [${i}]`))
}
function i(...t) {
for (const [s, [i, n]] of t.entries()) i && (n || e(new Error(`Conditional Assert : Failed at [${s}]`)))
}
function n(...t) {
for (const [s, i] of t.entries()) void 0 === i && e(new Error(`Param Check : Failed at [${s}]`))
}
function a(...t) {
for (const [s, [i, n]] of t.entries()) i && void 0 === n && e(new Error(`Conditional Param Check : Failed at [${s}]`))
}
function r(t) {
e(new Error(`Invalid Playlist : ${t}`))
}
function o(t, e = 10) {
if ("number" == typeof t) return t;
const s = 10 === e ? Number.parseFloat(t) : Number.parseInt(t, e);
return Number.isNaN(s) ? 0 : s
}
function E(t) {
(t.startsWith("0x") || t.startsWith("0X")) && (t = t.slice(2));
const e = new Uint8Array(t.length / 2);
for (let s = 0; s < t.length; s += 2) e[s / 2] = o(t.slice(s, s + 2), 16);
return e
}
function T(t, s = 0, i = t.length) {
i <= s && e(new Error(`end must be larger than start : start=${s}, end=${i}`));
const n = [];
for (let e = s; e < i; e++) n.push(`0${(255 & t[e]).toString(16).toUpperCase()}`.slice(-2));
return `0x${n.join("")}`
}
function u(t, e, s = 0) {
let i = -1;
for (let n = 0, a = 0; n < t.length; n++) if (t[n] === e) {
if (a++ === s) return [t.slice(0, n), t.slice(n + 1)];
i = n
}
return -1 !== i ? [t.slice(0, i), t.slice(i + 1)] : [t]
}
function c(t) {
const e = [];
let s = !1;
for (const i of t) "-" !== i && "_" !== i ? s ? (e.push(i.toUpperCase()), s = !1) : e.push(i.toLowerCase()) : s = !0;
return e.join("")
}
function l(t) {
return `${t.getUTCFullYear()}-${("0" + (t.getUTCMonth() + 1)).slice(-2)}-${("0" + t.getUTCDate()).slice(-2)}T${("0" + t.getUTCHours()).slice(-2)}:${("0" + t.getUTCMinutes()).slice(-2)}:${("0" + t.getUTCSeconds()).slice(-2)}.${("00" + t.getUTCMilliseconds()).slice(-3)}Z`
}
function h(e = {}) {
t = Object.assign(t, e)
}
function X() {
return Object.assign({}, t)
}
function p(t, e) {
e = Math.trunc(e) || 0;
const s = t.length >>> 0;
if (e < 0 && (e = s + e), !(e < 0 || e >= s)) return t[e]
}
class I {
constructor({
type: t,
uri: e,
groupId: s,
language: a,
assocLanguage: r,
name: o,
isDefault: E,
autoselect: T,
forced: u,
instreamId: c,
characteristics: l,
channels: h
}) {
n(t, s, o), i(["SUBTITLES" === t, e], ["CLOSED-CAPTIONS" === t, c], ["CLOSED-CAPTIONS" === t, !e], [u, "SUBTITLES" === t]), this.type = t, this.uri = e, this.groupId = s, this.language = a, this.assocLanguage = r, this.name = o, this.isDefault = E, this.autoselect = T, this.forced = u, this.instreamId = c, this.characteristics = l, this.channels = h
}
}
class N {
constructor({
uri: t,
isIFrameOnly: e = !1,
bandwidth: s,
averageBandwidth: i,
score: a,
codecs: r,
resolution: o,
frameRate: E,
hdcpLevel: T,
allowedCpc: u,
videoRange: c,
stableVariantId: l,
programId: h,
audio: X = [],
video: p = [],
subtitles: I = [],
closedCaptions: N = [],
currentRenditions: d = {audio: 0, video: 0, subtitles: 0, closedCaptions: 0}
}) {
n(t, s), this.uri = t, this.isIFrameOnly = e, this.bandwidth = s, this.averageBandwidth = i, this.score = a, this.codecs = r, this.resolution = o, this.frameRate = E, this.hdcpLevel = T, this.allowedCpc = u, this.videoRange = c, this.stableVariantId = l, this.programId = h, this.audio = X, this.video = p, this.subtitles = I, this.closedCaptions = N, this.currentRenditions = d
}
}
class d {
constructor({id: t, value: e, uri: i, language: a}) {
n(t, e || i), s("SessionData cannot have both value and uri, shoud be either.", !(e && i)), this.id = t, this.value = e, this.uri = i, this.language = a
}
}
class A {
constructor({method: t, uri: e, iv: s, format: r, formatVersion: o}) {
n(t), a(["NONE" !== t, e]), i(["NONE" === t, !(e || s || r || o)]), this.method = t, this.uri = e, this.iv = s, this.format = r, this.formatVersion = o
}
}
class f {
constructor({hint: t = !1, uri: e, mimeType: s, byterange: i}) {
n(e), this.hint = t, this.uri = e, this.mimeType = s, this.byterange = i
}
}
class S {
constructor({
id: t,
classId: e,
start: s,
end: r,
duration: o,
plannedDuration: E,
endOnNext: T,
attributes: u = {}
}) {
n(t), a([!0 === T, e]), i([r, s], [r, s <= r], [o, o >= 0], [E, E >= 0]), this.id = t, this.classId = e, this.start = s, this.end = r, this.duration = o, this.plannedDuration = E, this.endOnNext = T, this.attributes = u
}
}
class R {
constructor({type: t, duration: e, tagName: s, value: i}) {
n(t), a(["OUT" === t, e]), a(["RAW" === t, s]), this.type = t, this.duration = e, this.tagName = s, this.value = i
}
}
class m {
constructor(t) {
n(t), this.type = t
}
}
class g extends m {
constructor({isMasterPlaylist: t, uri: e, version: s, independentSegments: i = !1, start: a, source: r}) {
super("playlist"), n(t), this.isMasterPlaylist = t, this.uri = e, this.version = s, this.independentSegments = i, this.start = a, this.source = r
}
}
class O extends g {
constructor(t = {}) {
super(Object.assign(Object.assign({}, t), {isMasterPlaylist: !0}));
const {variants: e = [], currentVariant: s, sessionDataList: i = [], sessionKeyList: n = []} = t;
this.variants = e, this.currentVariant = s, this.sessionDataList = i, this.sessionKeyList = n
}
}
class D extends g {
constructor(t = {}) {
super(Object.assign(Object.assign({}, t), {isMasterPlaylist: !1}));
const {
targetDuration: e,
mediaSequenceBase: s = 0,
discontinuitySequenceBase: i = 0,
endlist: n = !1,
playlistType: a,
isIFrame: r,
segments: o = [],
prefetchSegments: E = [],
lowLatencyCompatibility: T,
partTargetDuration: u,
renditionReports: c = [],
skip: l = 0,
hash: h
} = t;
this.targetDuration = e, this.mediaSequenceBase = s, this.discontinuitySequenceBase = i, this.endlist = n, this.playlistType = a, this.isIFrame = r, this.segments = o, this.prefetchSegments = E, this.lowLatencyCompatibility = T, this.partTargetDuration = u, this.renditionReports = c, this.skip = l, this.hash = h
}
}
class P extends m {
constructor({
uri: t,
mimeType: e,
data: s,
duration: i,
title: n,
byterange: a,
discontinuity: r,
mediaSequenceNumber: o = 0,
discontinuitySequence: E = 0,
key: T,
map: u,
programDateTime: c,
dateRange: l,
markers: h = [],
parts: X = []
}) {
super("segment"), this.uri = t, this.mimeType = e, this.data = s, this.duration = i, this.title = n, this.byterange = a, this.discontinuity = r, this.mediaSequenceNumber = o, this.discontinuitySequence = E, this.key = T, this.map = u, this.programDateTime = c, this.dateRange = l, this.markers = h, this.parts = X
}
}
class y extends m {
constructor({hint: t = !1, uri: e, duration: s, independent: i, byterange: a, gap: r}) {
super("part"), n(e), this.hint = t, this.uri = e, this.duration = s, this.independent = i, this.duration = s, this.byterange = a, this.gap = r
}
}
class C extends m {
constructor({uri: t, discontinuity: e, mediaSequenceNumber: s = 0, discontinuitySequence: i = 0, key: a}) {
super("prefetch"), n(t), this.uri = t, this.discontinuity = e, this.mediaSequenceNumber = s, this.discontinuitySequence = i, this.key = a
}
}
class U {
constructor({uri: t, lastMSN: e, lastPart: s}) {
n(t), this.uri = t, this.lastMSN = e, this.lastPart = s
}
}
var M = Object.freeze({
__proto__: null,
Rendition: I,
Variant: N,
SessionData: d,
Key: A,
MediaInitializationSection: f,
DateRange: S,
SpliceInfo: R,
Playlist: g,
MasterPlaylist: O,
MediaPlaylist: D,
Segment: P,
PartialSegment: y,
PrefetchSegment: C,
RenditionReport: U
});
function b(t) {
return function (t, e = " ") {
return t ? (t = t.trim(), " " === e || (t.startsWith(e) && (t = t.slice(1)), t.endsWith(e) && (t = t.slice(0, -1))), t) : t
}(t, '"')
}
function L(t) {
const e = u(t, ",");
return {duration: o(e[0]), title: decodeURIComponent(escape(e[1]))}
}
function v(t) {
const e = u(t, "@");
return {length: o(e[0]), offset: e[1] ? o(e[1]) : -1}
}
function $(t) {
const e = u(t, "x");
return {width: o(e[0]), height: o(e[1])}
}
function Y(t) {
const e = "ALLOWED-CPC: Each entry must consit of KEYFORMAT and Content Protection Configuration", s = t.split(",");
0 === s.length && r(e);
const i = [];
for (const t of s) {
const [s, n] = u(t, ":");
s && n ? i.push({format: s, cpcList: n.split("/")}) : r(e)
}
return i
}
function F(t) {
const e = E(t);
return 16 !== e.length && r("IV must be a 128-bit unsigned integer"), e
}
function G(t, e) {
e.IV && t.compatibleVersion < 2 && (t.compatibleVersion = 2), (e.KEYFORMAT || e.KEYFORMATVERSIONS) && t.compatibleVersion < 5 && (t.compatibleVersion = 5)
}
function V(t) {
const e = {};
for (const i of function (t) {
const e = [];
let s = !0, i = 0;
const n = [];
for (let a = 0; a < t.length; a++) {
const r = t[a];
s && "," === r ? (e.push(t.slice(i, a).trim()), i = a + 1) : '"' !== r && "'" !== r || (s ? (n.push(r), s = !1) : r === p(n, -1) ? (n.pop(), s = !0) : n.push(r))
}
return e.push(t.slice(i).trim()), e
}(t)) {
const [t, n] = u(i, "="), a = b(n);
switch (t) {
case"URI":
e[t] = a;
break;
case"START-DATE":
case"END-DATE":
e[t] = new Date(a);
break;
case"IV":
e[t] = F(a);
break;
case"BYTERANGE":
e[t] = v(a);
break;
case"RESOLUTION":
e[t] = $(a);
break;
case"ALLOWED-CPC":
e[t] = Y(a);
break;
case"END-ON-NEXT":
case"DEFAULT":
case"AUTOSELECT":
case"FORCED":
case"PRECISE":
case"CAN-BLOCK-RELOAD":
case"INDEPENDENT":
case"GAP":
e[t] = "YES" === a;
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":
e[t] = o(a);
break;
default:
t.startsWith("SCTE35-") ? e[t] = E(a) : t.startsWith("X-") ? e[t] = (s = n).startsWith('"') ? b(s) : s.startsWith("0x") || s.startsWith("0X") ? E(s) : o(s) : ("VIDEO-RANGE" === t && "SDR" !== a && "HLG" !== a && "PQ" !== a && r(`VIDEO-RANGE: unknown value "${a}"`), e[t] = a)
}
}
var s;
return e
}
function w() {
r("The file contains both media and master playlist tags.")
}
function B(t, e, s) {
const i = function ({attributes: t}) {
return new I({
type: t.TYPE,
uri: t.URI,
groupId: t["GROUP-ID"],
language: t.LANGUAGE,
assocLanguage: t["ASSOC-LANGUAGE"],
name: t.NAME,
isDefault: t.DEFAULT,
autoselect: t.AUTOSELECT,
forced: t.FORCED,
instreamId: t["INSTREAM-ID"],
characteristics: t.CHARACTERISTICS,
channels: t.CHANNELS
})
}(e), n = t[c(s)], a = function (t, e) {
let s = !1;
for (const i of t) {
if (i.name === e.name) return "All EXT-X-MEDIA tags in the same Group MUST have different NAME attributes.";
i.isDefault && (s = !0)
}
return s && e.isDefault ? "EXT-X-MEDIA A Group MUST NOT have more than one member with a DEFAULT attribute of YES." : ""
}(n, i);
a && r(a), n.push(i), i.isDefault && (t.currentRenditions[c(s)] = n.length - 1)
}
function H(t, e, s, i, n) {
const a = new N({
uri: s,
bandwidth: e.BANDWIDTH,
averageBandwidth: e["AVERAGE-BANDWIDTH"],
score: e.SCORE,
codecs: e.CODECS,
resolution: e.RESOLUTION,
frameRate: e["FRAME-RATE"],
hdcpLevel: e["HDCP-LEVEL"],
allowedCpc: e["ALLOWED-CPC"],
videoRange: e["VIDEO-RANGE"],
stableVariantId: e["STABLE-VARIANT-ID"],
programId: e["PROGRAM-ID"]
});
for (const s of t) if ("EXT-X-MEDIA" === s.name) {
const t = s.attributes, i = t.TYPE;
if (i && t["GROUP-ID"] || r("EXT-X-MEDIA TYPE attribute is REQUIRED."), e[i] === t["GROUP-ID"] && (B(a, s, i), "CLOSED-CAPTIONS" === i)) for (const {instreamId: t} of a.closedCaptions) if (t && t.startsWith("SERVICE") && n.compatibleVersion < 7) {
n.compatibleVersion = 7;
break
}
}
return function (t, e, s) {
for (const i of ["AUDIO", "VIDEO", "SUBTITLES", "CLOSED-CAPTIONS"]) "CLOSED-CAPTIONS" === i && "NONE" === t[i] ? (s.isClosedCaptionsNone = !0, e.closedCaptions = []) : t[i] && !e[c(i)].some((e => e.groupId === t[i])) && r(`${i} attribute MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag whose TYPE attribute is ${i}.`)
}(e, a, n), a.isIFrameOnly = i, a
}
function K(t, e) {
if (t.method !== e.method) return !1;
if (t.uri !== e.uri) return !1;
if (t.iv) {
if (!e.iv) return !1;
if (t.iv.length !== e.iv.length) return !1;
for (let s = 0; s < t.iv.length; s++) if (t.iv[s] !== e.iv[s]) return !1
} else if (e.iv) return !1;
return t.format === e.format && t.formatVersion === e.formatVersion
}
function k(t, e, s, i, n, a, o) {
const E = new P({uri: e, mediaSequenceNumber: n, discontinuitySequence: a});
let T = !1, u = !1;
for (let e = s; e <= i; e++) {
const {name: s, value: i, attributes: n} = t[e];
if ("EXTINF" === s) !Number.isInteger(i.duration) && o.compatibleVersion < 3 && (o.compatibleVersion = 3), Math.round(i.duration) > o.targetDuration && r("EXTINF duration, when rounded to the nearest integer, MUST be less than or equal to the target duration"), E.duration = i.duration, E.title = i.title; else if ("EXT-X-BYTERANGE" === s) o.compatibleVersion < 4 && (o.compatibleVersion = 4), E.byterange = i; else if ("EXT-X-DISCONTINUITY" === s) E.parts.length > 0 && r("EXT-X-DISCONTINUITY must appear before the first EXT-X-PART tag of the Parent Segment."), E.discontinuity = !0; else if ("EXT-X-KEY" === s) E.parts.length > 0 && r("EXT-X-KEY must appear before the first EXT-X-PART tag of the Parent Segment."), G(o, n), E.key = new A({
method: n.METHOD,
uri: n.URI,
iv: n.IV,
format: n.KEYFORMAT,
formatVersion: n.KEYFORMATVERSIONS
}); else if ("EXT-X-MAP" === s) E.parts.length > 0 && r("EXT-X-MAP must appear before the first EXT-X-PART tag of the Parent Segment."), o.compatibleVersion < 5 && (o.compatibleVersion = 5), o.hasMap = !0, E.map = new f({
uri: n.URI,
byterange: n.BYTERANGE
}); else if ("EXT-X-PROGRAM-DATE-TIME" === s) E.programDateTime = i; else if ("EXT-X-DATERANGE" === s) {
const t = {};
for (const e of Object.keys(n)) (e.startsWith("SCTE35-") || e.startsWith("X-")) && (t[e] = n[e]);
E.dateRange = new S({
id: n.ID,
classId: n.CLASS,
start: n["START-DATE"],
end: n["END-DATE"],
duration: n.DURATION,
plannedDuration: n["PLANNED-DURATION"],
endOnNext: n["END-ON-NEXT"],
attributes: t
})
} else if ("EXT-X-CUE-OUT" === s) E.markers.push(new R({
type: "OUT",
duration: n && n.DURATION || i
})); else if ("EXT-X-CUE-IN" === s) E.markers.push(new R({type: "IN"})); else if ("EXT-X-CUE-OUT-CONT" === s || "EXT-X-CUE" === s || "EXT-OATCLS-SCTE35" === s || "EXT-X-ASSET" === s || "EXT-X-SCTE35" === s) E.markers.push(new R({
type: "RAW",
tagName: s,
value: i
})); else if ("EXT-X-PRELOAD-HINT" !== s || n.TYPE) if ("EXT-X-PRELOAD-HINT" === s && "PART" === n.TYPE && u) r("Servers should not add more than one EXT-X-PRELOAD-HINT tag with the same TYPE attribute to a Playlist."); else if ("EXT-X-PART" !== s && "EXT-X-PRELOAD-HINT" !== s || n.URI) {
if ("EXT-X-PRELOAD-HINT" === s && "MAP" === n.TYPE) T && r("Servers should not add more than one EXT-X-PRELOAD-HINT tag with the same TYPE attribute to a Playlist."), T = !0, o.hasMap = !0, E.map = new f({
hint: !0,
uri: n.URI,
byterange: {length: n["BYTERANGE-LENGTH"], offset: n["BYTERANGE-START"] || 0}
}); else if ("EXT-X-PART" === s || "EXT-X-PRELOAD-HINT" === s && "PART" === n.TYPE) {
"EXT-X-PART" !== s || n.DURATION || r("EXT-X-PART: DURATION attribute is mandatory"), "EXT-X-PRELOAD-HINT" === s && (u = !0);
const t = new y({
hint: "EXT-X-PRELOAD-HINT" === s,
uri: n.URI,
byterange: "EXT-X-PART" === s ? n.BYTERANGE : {
length: n["BYTERANGE-LENGTH"],
offset: n["BYTERANGE-START"] || 0
},
duration: n.DURATION,
independent: n.INDEPENDENT,
gap: n.GAP
});
E.parts.push(t)
}
} else r("EXT-X-PART / EXT-X-PRELOAD-HINT: URI attribute is mandatory"); else r("EXT-X-PRELOAD-HINT: TYPE attribute is mandatory")
}
return E
}
function W(t, e, s, i, n, a, o) {
const E = new C({uri: e, mediaSequenceNumber: n, discontinuitySequence: a});
for (let e = s; e <= i; e++) {
const {name: s, attributes: i} = t[e];
"EXTINF" === s ? r("A prefetch segment must not be advertised with an EXTINF tag.") : "EXT-X-DISCONTINUITY" === s ? r("A prefetch segment must not be advertised with an EXT-X-DISCONTINUITY tag.") : "EXT-X-PREFETCH-DISCONTINUITY" === s ? E.discontinuity = !0 : "EXT-X-KEY" === s ? (G(o, i), E.key = new A({
method: i.METHOD,
uri: i.URI,
iv: i.IV,
format: i.KEYFORMAT,
formatVersion: i.KEYFORMATVERSIONS
})) : "EXT-X-MAP" === s && r("Prefetch segments must not be advertised with an EXT-X-MAP tag.")
}
return E
}
function q(t, e) {
var s;
const i = new D;
let n = -1, a = 0, o = !1, E = !1, T = 0, u = null, c = null, l = !1;
for (const [s, h] of t.entries()) {
const {name: X, value: p, attributes: I, category: N} = h;
if ("Segment" !== N) {
if ("EXT-X-VERSION" === X) void 0 === i.version ? i.version = p : r("A Playlist file MUST NOT contain more than one EXT-X-VERSION tag."); else if ("EXT-X-TARGETDURATION" === X) i.targetDuration = e.targetDuration = p; else if ("EXT-X-MEDIA-SEQUENCE" === X) i.segments.length > 0 && r("The EXT-X-MEDIA-SEQUENCE tag MUST appear before the first Media Segment in the Playlist."), i.mediaSequenceBase = a = p; else if ("EXT-X-DISCONTINUITY-SEQUENCE" === X) i.segments.length > 0 && r("The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before the first Media Segment in the Playlist."), o && r("The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before any EXT-X-DISCONTINUITY tag."), i.discontinuitySequenceBase = T = p; else if ("EXT-X-ENDLIST" === X) i.endlist = !0; else if ("EXT-X-PLAYLIST-TYPE" === X) i.playlistType = p; else if ("EXT-X-I-FRAMES-ONLY" === X) e.compatibleVersion < 4 && (e.compatibleVersion = 4), i.isIFrame = !0; else if ("EXT-X-INDEPENDENT-SEGMENTS" === X) i.independentSegments && r("EXT-X-INDEPENDENT-SEGMENTS tag MUST NOT appear more than once in a Playlist"), i.independentSegments = !0; else if ("EXT-X-START" === X) i.start && r("EXT-X-START tag MUST NOT appear more than once in a Playlist"), "number" != typeof I["TIME-OFFSET"] && r("EXT-X-START: TIME-OFFSET attribute is REQUIRED"), i.start = {
offset: I["TIME-OFFSET"],
precise: I.PRECISE || !1
}; else if ("EXT-X-SERVER-CONTROL" === X) I["CAN-BLOCK-RELOAD"] || r("EXT-X-SERVER-CONTROL: CAN-BLOCK-RELOAD=YES is mandatory for Low-Latency HLS"), i.lowLatencyCompatibility = {
canBlockReload: I["CAN-BLOCK-RELOAD"],
canSkipUntil: I["CAN-SKIP-UNTIL"],
holdBack: I["HOLD-BACK"],
partHoldBack: I["PART-HOLD-BACK"]
}; else if ("EXT-X-PART-INF" === X) I["PART-TARGET"] || r("EXT-X-PART-INF: PART-TARGET attribute is mandatory"), i.partTargetDuration = I["PART-TARGET"]; else if ("EXT-X-RENDITION-REPORT" === X) I.URI || r("EXT-X-RENDITION-REPORT: URI attribute is mandatory"), 0 === I.URI.search(/^[a-z]+:/) && r("EXT-X-RENDITION-REPORT: URI must be relative to the playlist uri"), i.renditionReports.push(new U({
uri: I.URI,
lastMSN: I["LAST-MSN"],
lastPart: I["LAST-PART"]
})); else if ("EXT-X-SKIP" === X) I["SKIPPED-SEGMENTS"] || r("EXT-X-SKIP: SKIPPED-SEGMENTS attribute is mandatory"), e.compatibleVersion < 9 && (e.compatibleVersion = 9), i.skip = I["SKIPPED-SEGMENTS"], a += i.skip; else if ("EXT-X-PREFETCH" === X) {
const r = W(t, p, -1 === n ? s : n, s - 1, a++, T, e);
r && (r.discontinuity && (r.discontinuitySequence++, T = r.discontinuitySequence), r.key ? u = r.key : r.key = u, i.prefetchSegments.push(r)), E = !0, n = -1
} else if ("string" == typeof h) {
-1 === n && r("A URI line is not preceded by any segment tags"), i.targetDuration || r("The EXT-X-TARGETDURATION tag is REQUIRED"), E && r("These segments must appear after all complete segments.");
const o = k(t, h, n, s - 1, a++, T, e);
o && ([T, u, c] = x(i, o, T, u, c), !l && o.parts.length > 0 && (l = !0)), n = -1
}
} else -1 === n && (n = s), "EXT-X-DISCONTINUITY" === X && (o = !0)
}
if (-1 !== n) {
const o = k(t, "", n, t.length - 1, a++, T, e);
if (o) {
const {parts: t} = o;
t.length > 0 && !i.endlist && !(null === (s = p(t, -1)) || void 0 === s ? void 0 : s.hint) && r("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"), x(i, o, u, c), !l && o.parts.length > 0 && (l = !0)
}
}
return function (t) {
const e = new Map, s = new Map;
let i = !1, n = !1;
for (let a = t.length - 1; a >= 0; a--) {
const {programDateTime: o, dateRange: E} = t[a];
if (o && (n = !0), E && E.start) {
i = !0, E.endOnNext && (E.end || E.duration) && r("An EXT-X-DATERANGE tag with an END-ON-NEXT=YES attribute MUST NOT contain DURATION or END-DATE attributes.");
const t = E.start.getTime(), n = E.duration || 0;
E.end && E.duration && t + 1e3 * n !== E.end.getTime() && r("END-DATE MUST be equal to the value of the START-DATE attribute plus the value of the DURATION"), E.endOnNext && (E.end = e.get(E.classId)), e.set(E.classId, E.start);
const a = E.end ? E.end.getTime() : E.start.getTime() + 1e3 * (E.duration || 0), o = s.get(E.classId);
if (o) {
for (const e of o) (e.start <= t && e.end > t || e.start >= t && e.start < a) && r("DATERANGE tags with the same CLASS should not overlap");
o.push({start: t, end: a})
} else E.classId && s.set(E.classId, [{start: t, end: a}])
}
}
i && !n && r("If a Playlist contains an EXT-X-DATERANGE tag, it MUST also contain at least one EXT-X-PROGRAM-DATE-TIME tag.")
}(i.segments), i.lowLatencyCompatibility && function ({
lowLatencyCompatibility: t,
targetDuration: e,
partTargetDuration: s,
segments: i,
renditionReports: n
}, a) {
const {canSkipUntil: o, holdBack: E, partHoldBack: T} = t;
o < 6 * e && r("The Skip Boundary must be at least six times the EXT-X-TARGETDURATION.");
E < 3 * e && r("HOLD-BACK must be at least three times the EXT-X-TARGETDURATION.");
if (a) {
void 0 === s && r("EXT-X-PART-INF is required if a Playlist contains one or more EXT-X-PART tags"), void 0 === T && r("EXT-X-PART: PART-HOLD-BACK attribute is mandatory"), T < s && r("PART-HOLD-BACK must be at least PART-TARGET");
for (const [t, {parts: e}] of i.entries()) {
e.length > 0 && t < i.length - 3 && r("Remove EXT-X-PART tags from the Playlist after they are greater than three target durations from the end of the Playlist.");
for (const [t, {duration: i}] of e.entries()) void 0 !== i && (i > s && r("PART-TARGET is the maximum duration of any Partial Segment"), t < e.length - 1 && i < .85 * s && r("All Partial Segments except the last part of a segment must have a duration of at least 85% of PART-TARGET"))
}
}
for (const t of n) {
const e = i.at(-1);
null !== t.lastMSN && void 0 !== t.lastMSN || (t.lastMSN = e.mediaSequenceNumber), (null === t.lastPart || void 0 === t.lastPart) && e.parts.length > 0 && (t.lastPart = e.parts.length - 1)
}
}(i, l), i
}
function x(t, e, s, i, n) {
const {discontinuity: a, key: o, map: E, byterange: T, uri: u} = e;
if (a && (e.discontinuitySequence = s + 1), o || (e.key = i), E || (e.map = n), T && -1 === T.offset) {
const {segments: e} = t;
if (e.length > 0) {
const t = p(e, -1);
t.byterange && t.uri === u ? T.offset = t.byterange.offset + t.byterange.length : r("If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST be a sub-range of the same media resource")
} else r("If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST appear in the Playlist file")
}
return t.segments.push(e), [e.discontinuitySequence, e.key, e.map]
}
function j(t, e) {
const [s, i] = function (t) {
const e = t.indexOf(":");
return -1 === e ? [t.slice(1).trim(), null] : [t.slice(1, e).trim(), t.slice(e + 1).trim()]
}(t), n = function (t) {
switch (t) {
case"EXTM3U":
case"EXT-X-VERSION":
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":
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"
}
}(s);
if (function (t, e) {
if ("Segment" === t || "MediaPlaylist" === t) return void 0 === e.isMasterPlaylist ? void (e.isMasterPlaylist = !1) : void (e.isMasterPlaylist && w());
if ("MasterPlaylist" === t) {
if (void 0 === e.isMasterPlaylist) return void (e.isMasterPlaylist = !0);
!1 === e.isMasterPlaylist && w()
}
}(n, e), "Unknown" === n) return null;
"MediaPlaylist" === n && "EXT-X-RENDITION-REPORT" !== s && "EXT-X-PREFETCH" !== s && (e.hash[s] && r("There MUST NOT be more than one Media Playlist tag of each type in any Media Playlist"), e.hash[s] = !0);
const [a, E] = function (t, e) {
switch (t) {
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":
return [null, null];
case"EXT-X-VERSION":
case"EXT-X-TARGETDURATION":
case"EXT-X-MEDIA-SEQUENCE":
case"EXT-X-DISCONTINUITY-SEQUENCE":
return [o(e), null];
case"EXT-X-CUE-OUT":
return Number.isNaN(Number(e)) ? [null, V(e)] : [o(e), null];
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, V(e)];
case"EXTINF":
return [L(e), null];
case"EXT-X-BYTERANGE":
return [v(e), null];
case"EXT-X-PROGRAM-DATE-TIME":
return [new Date(e), null];
default:
return [e, null]
}
}(s, i);
return {name: s, category: n, value: a, attributes: E}
}
function Q(t, e) {
let s;
return e.isMasterPlaylist ? s = function (t, e) {
const s = new O;
let i = !1;
for (const [n, {
name: a,
value: o,
attributes: E
}] of t.entries()) if ("EXT-X-VERSION" === a) s.version = o; else if ("EXT-X-STREAM-INF" === a) {
const a = t[n + 1];
("string" != typeof a || a.startsWith("#EXT")) && r("EXT-X-STREAM-INF must be followed by a URI line");
const o = H(t, E, a, !1, e);
o && ("number" == typeof o.score && (i = !0, o.score < 0 && r("SCORE attribute on EXT-X-STREAM-INF must be positive decimal-floating-point number.")), s.variants.push(o))
} else if ("EXT-X-I-FRAME-STREAM-INF" === a) {
const i = H(t, E, E.URI, !0, e);
i && s.variants.push(i)
} else if ("EXT-X-SESSION-DATA" === a) {
const t = new d({id: E["DATA-ID"], value: E.VALUE, uri: E.URI, language: E.LANGUAGE});
s.sessionDataList.some((e => e.id === t.id && e.language === t.language)) && r("A Playlist MUST NOT contain more than one EXT-X-SESSION-DATA tag with the same DATA-ID attribute and the same LANGUAGE attribute."), s.sessionDataList.push(t)
} else if ("EXT-X-SESSION-KEY" === a) {
"NONE" === E.METHOD && r("EXT-X-SESSION-KEY: The value of the METHOD attribute MUST NOT be NONE");
const t = new A({
method: E.METHOD,
uri: E.URI,
iv: E.IV,
format: E.KEYFORMAT,
formatVersion: E.KEYFORMATVERSIONS
});
s.sessionKeyList.some((e => K(e, t))) && r("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."), G(e, E), s.sessionKeyList.push(t)
} else "EXT-X-INDEPENDENT-SEGMENTS" === a ? (s.independentSegments && r("EXT-X-INDEPENDENT-SEGMENTS tag MUST NOT appear more than once in a Playlist"), s.independentSegments = !0) : "EXT-X-START" === a && (s.start && r("EXT-X-START tag MUST NOT appear more than once in a Playlist"), "number" != typeof E["TIME-OFFSET"] && r("EXT-X-START: TIME-OFFSET attribute is REQUIRED"), s.start = {
offset: E["TIME-OFFSET"],
precise: E.PRECISE || !1
});
if (i) for (const t of s.variants) "number" != typeof t.score && r("If any Variant Stream contains the SCORE attribute, then all Variant Streams in the Master Playlist SHOULD have a SCORE attribute");
if (e.isClosedCaptionsNone) for (const t of s.variants) t.closedCaptions.length > 0 && r("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");
return s
}(t, e) : (s = q(t, e), !s.isIFrame && e.hasMap && e.compatibleVersion < 6 && (e.compatibleVersion = 6)), e.compatibleVersion > 1 && (!s.version || s.version < e.compatibleVersion) && r(`EXT-X-VERSION needs to be ${e.compatibleVersion} or higher.`), s
}
function _(t) {
const e = {
version: void 0,
isMasterPlaylist: void 0,
hasMap: !1,
targetDuration: 0,
compatibleVersion: 1,
isClosedCaptionsNone: !1,
hash: {}
}, s = function (t, e) {
const s = [];
for (const i of t.split("\n")) {
const t = i.trim();
if (t) if (t.startsWith("#")) {
if (t.startsWith("#EXT")) {
const i = j(t, e);
i && s.push(i)
}
} else s.push(t)
}
return 0 !== s.length && "EXTM3U" === s[0].name || r("The EXTM3U tag MUST be the first line."), s
}(t, e), i = Q(s, e);
return i.source = t, i
}
const z = ["#EXTINF", "#EXT-X-BYTERANGE", "#EXT-X-DISCONTINUITY", "#EXT-X-STREAM-INF", "#EXT-X-CUE-OUT", "#EXT-X-CUE-IN", "#EXT-X-KEY", "#EXT-X-MAP"],
Z = ["#EXT-X-MEDIA"];
class J extends Array {
constructor(t) {
super(), this.baseUri = t
}
push(...t) {
for (const e of t) if (e.startsWith("#")) if (z.some((t => e.startsWith(t)))) super.push(e); else {
if (this.includes(e)) {
if (Z.some((t => e.startsWith(t)))) continue;
r(`Redundant item (${e})`)
}
super.push(e)
} else super.push(e);
return this.length
}
}
function tt(t, e) {
let s = 1e3;
e && (s = Math.pow(10, e));
const i = Math.round(t * s) / s;
return e ? i.toFixed(e) : i
}
function et(t) {
const e = [`DATA-ID="${t.id}"`];
return t.language && e.push(`LANGUAGE="${t.language}"`), t.value ? e.push(`VALUE="${t.value}"`) : t.uri && e.push(`URI="${t.uri}"`), `#EXT-X-SESSION-DATA:${e.join(",")}`
}
function st(t, e) {
const s = e ? "#EXT-X-SESSION-KEY" : "#EXT-X-KEY", i = [`METHOD=${t.method}`];
return t.uri && i.push(`URI="${t.uri}"`), t.iv && (16 !== t.iv.length && r("IV must be a 128-bit unsigned integer"), i.push(`IV=${T(t.iv)}`)), t.format && i.push(`KEYFORMAT="${t.format}"`), t.formatVersion && i.push(`KEYFORMATVERSIONS="${t.formatVersion}"`), `${s}:${i.join(",")}`
}
function it(t, e) {
const s = e.isIFrameOnly ? "#EXT-X-I-FRAME-STREAM-INF" : "#EXT-X-STREAM-INF", i = [`BANDWIDTH=${e.bandwidth}`];
if (e.averageBandwidth && i.push(`AVERAGE-BANDWIDTH=${e.averageBandwidth}`), e.isIFrameOnly && i.push(`URI="${e.uri}"`), e.codecs && i.push(`CODECS="${e.codecs}"`), e.resolution && i.push(`RESOLUTION=${e.resolution.width}x${e.resolution.height}`), e.frameRate && i.push(`FRAME-RATE=${tt(e.frameRate, 3)}`), e.hdcpLevel && i.push(`HDCP-LEVEL=${e.hdcpLevel}`), e.audio.length > 0) {
i.push(`AUDIO="${e.audio[0].groupId}"`);
for (const s of e.audio) t.push(nt(s))
}
if (e.video.length > 0) {
i.push(`VIDEO="${e.video[0].groupId}"`);
for (const s of e.video) t.push(nt(s))
}
if (e.subtitles.length > 0) {
i.push(`SUBTITLES="${e.subtitles[0].groupId}"`);
for (const s of e.subtitles) t.push(nt(s))
}
if (X().allowClosedCaptionsNone && 0 === e.closedCaptions.length) i.push("CLOSED-CAPTIONS=NONE"); else if (e.closedCaptions.length > 0) {
i.push(`CLOSED-CAPTIONS="${e.closedCaptions[0].groupId}"`);
for (const s of e.closedCaptions) t.push(nt(s))
}
if (e.score && i.push(`SCORE=${e.score}`), e.allowedCpc) {
const t = [];
for (const {format: s, cpcList: i} of e.allowedCpc) t.push(`${s}:${i.join("/")}`);
i.push(`ALLOWED-CPC="${t.join(",")}"`)
}
e.videoRange && i.push(`VIDEO-RANGE=${e.videoRange}`), e.stableVariantId && i.push(`STABLE-VARIANT-ID="${e.stableVariantId}"`), e.programId && i.push(`PROGRAM-ID=${e.programId}`), t.push(`${s}:${i.join(",")}`), e.isIFrameOnly || t.push(`${e.uri}`)
}
function nt(t) {
const e = [`TYPE=${t.type}`, `GROUP-ID="${t.groupId}"`, `NAME="${t.name}"`];
return void 0 !== t.isDefault && e.push("DEFAULT=" + (t.isDefault ? "YES" : "NO")), void 0 !== t.autoselect && e.push("AUTOSELECT=" + (t.autoselect ? "YES" : "NO")), void 0 !== t.forced && e.push("FORCED=" + (t.forced ? "YES" : "NO")), t.language && e.push(`LANGUAGE="${t.language}"`), t.assocLanguage && e.push(`ASSOC-LANGUAGE="${t.assocLanguage}"`), t.instreamId && e.push(`INSTREAM-ID="${t.instreamId}"`), t.characteristics && e.push(`CHARACTERISTICS="${t.characteristics}"`), t.channels && e.push(`CHANNELS="${t.channels}"`), t.uri && e.push(`URI="${t.uri}"`), `#EXT-X-MEDIA:${e.join(",")}`
}
function at(t, e, s, i, n = 1, a = null) {
let r = !1, o = "";
if (e.discontinuity && t.push("#EXT-X-DISCONTINUITY"), e.key) {
const i = st(e.key);
i !== s && (t.push(i), s = i)
}
if (e.map) {
const s = function (t) {
const e = [`URI="${t.uri}"`];
t.byterange && e.push(`BYTERANGE="${rt(t.byterange)}"`);
return `#EXT-X-MAP:${e.join(",")}`
}(e.map);
s !== i && (t.push(s), i = s)
}
if (e.programDateTime && t.push(`#EXT-X-PROGRAM-DATE-TIME:${l(e.programDateTime)}`), e.dateRange && t.push(function (t) {
const e = [`ID="${t.id}"`];
t.start && e.push(`START-DATE="${l(t.start)}"`);
t.end && e.push(`END-DATE="${l(t.end)}"`);
t.duration && e.push(`DURATION=${t.duration}`);
t.plannedDuration && e.push(`PLANNED-DURATION=${t.plannedDuration}`);
t.classId && e.push(`CLASS="${t.classId}"`);
t.endOnNext && e.push("END-ON-NEXT=YES");
for (const s of Object.keys(t.attributes)) s.startsWith("X-") ? "number" == typeof t.attributes[s] ? e.push(`${s}=${t.attributes[s]}`) : e.push(`${s}="${t.attributes[s]}"`) : s.startsWith("SCTE35-") && e.push(`${s}=${T(t.attributes[s])}`);
return `#EXT-X-DATERANGE:${e.join(",")}`
}(e.dateRange)), e.markers.length > 0 && (o = function (t, e) {
let s = "";
for (const i of e) if ("OUT" === i.type) s = "OUT", t.push(`#EXT-X-CUE-OUT:DURATION=${i.duration}`); else if ("IN" === i.type) s = "IN", t.push("#EXT-X-CUE-IN"); else if ("RAW" === i.type) {
const e = i.value ? `:${i.value}` : "";
t.push(`#${i.tagName}${e}`)
}
return s
}(t, e.markers)), e.parts.length > 0 && (r = function (t, e) {
let s = !1;
for (const i of e) if (i.hint) {
const e = [];
if (e.push("TYPE=PART", `URI="${i.uri}"`), i.byterange) {
const {offset: t, length: s} = i.byterange;
e.push(`BYTERANGE-START=${t}`), s && e.push(`BYTERANGE-LENGTH=${s}`)
}
t.push(`#EXT-X-PRELOAD-HINT:${e.join(",")}`), s = !0
} else {
const e = [];
e.push(`DURATION=${i.duration}`, `URI="${i.uri}"`), i.byterange && e.push(`BYTERANGE=${rt(i.byterange)}`), i.independent && e.push("INDEPENDENT=YES"), i.gap && e.push("GAP=YES"), t.push(`#EXT-X-PART:${e.join(",")}`)
}
return s
}(t, e.parts)), r) return [s, i];
const E = n < 3 ? Math.round(e.duration) : tt(e.duration, function (t) {
const e = t.toString(10), s = e.indexOf(".");
return -1 === s ? 0 : e.length - s - 1
}(e.duration));
return t.push(`#EXTINF:${E},${unescape(encodeURIComponent(e.title || ""))}`), e.byterange && t.push(`#EXT-X-BYTERANGE:${rt(e.byterange)}`), null != a ? Array.prototype.push.call(t, a(e)) : Array.prototype.push.call(t, `${e.uri}`), [s, i, o]
}
function rt({offset: t, length: e}) {
return `${e}@${t}`
}
function ot(t, e = null) {
n(t), s("Not a playlist", "playlist" === t.type);
const i = new J(t.uri);
return i.push("#EXTM3U"), t.version && i.push(`#EXT-X-VERSION:${t.version}`), t.independentSegments && i.push("#EXT-X-INDEPENDENT-SEGMENTS"), t.start && i.push(`#EXT-X-START:TIME-OFFSET=${tt(t.start.offset)}${t.start.precise ? ",PRECISE=YES" : ""}`), t.isMasterPlaylist ? function (t, e) {
for (const s of e.sessionDataList) t.push(et(s));
for (const s of e.sessionKeyList) t.push(st(s, !0));
for (const s of e.variants) it(t, s)
}(i, t) : function (t, e, s = null) {
let i = "", n = "", a = !1;
if (e.targetDuration && t.push(`#EXT-X-TARGETDURATION:${e.targetDuration}`), e.lowLatencyCompatibility) {
const {canBlockReload: s, canSkipUntil: i, holdBack: n, partHoldBack: a} = e.lowLatencyCompatibility,
r = [];
r.push("CAN-BLOCK-RELOAD=" + (s ? "YES" : "NO")), void 0 !== i && r.push(`CAN-SKIP-UNTIL=${i}`), void 0 !== n && r.push(`HOLD-BACK=${n}`), void 0 !== a && r.push(`PART-HOLD-BACK=${a}`), t.push(`#EXT-X-SERVER-CONTROL:${r.join(",")}`)
}
e.partTargetDuration && t.push(`#EXT-X-PART-INF:PART-TARGET=${e.partTargetDuration}`), e.mediaSequenceBase && t.push(`#EXT-X-MEDIA-SEQUENCE:${e.mediaSequenceBase}`), e.discontinuitySequenceBase && t.push(`#EXT-X-DISCONTINUITY-SEQUENCE:${e.discontinuitySequenceBase}`), e.playlistType && t.push(`#EXT-X-PLAYLIST-TYPE:${e.playlistType}`), e.isIFrame && t.push("#EXT-X-I-FRAMES-ONLY"), e.skip > 0 && t.push(`#EXT-X-SKIP:SKIPPED-SEGMENTS=${e.skip}`);
for (const r of e.segments) {
let o = "";
[i, n, o] = at(t, r, i, n, e.version, s), "OUT" === o ? a = !0 : "IN" === o && a && (a = !1)
}
"VOD" === e.playlistType && a && t.push("#EXT-X-CUE-IN"), e.prefetchSegments.length > 2 && r("The server must deliver no more than two prefetch segments");
for (const s of e.prefetchSegments) s.discontinuity && t.push("#EXT-X-PREFETCH-DISCONTINUITY"), t.push(`#EXT-X-PREFETCH:${s.uri}`);
e.endlist && t.push("#EXT-X-ENDLIST");
for (const s of e.renditionReports) {
const e = [];
e.push(`URI="${s.uri}"`, `LAST-MSN=${s.lastMSN}`), void 0 !== s.lastPart && e.push(`LAST-PART=${s.lastPart}`), t.push(`#EXT-X-RENDITION-REPORT:${e.join(",")}`)
}
}(i, t, e), i.join("\n")
}
export {X as getOptions, _ as parse, h as setOptions, ot as stringify, M as types};