dasha 4.0.0-alpha.13 → 4.0.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1692 @@
1
+ import { ADTS, CustomPathedSource, FilePathSource, HLS_FORMATS, Input as Input$1, InputFormat, InputTrack, MATROSKA, MP3, MP4, QTFF, UrlSource, WEBM, asc, desc, prefer } from "mediabunny";
2
+ import { DOMParser } from "@xmldom/xmldom";
3
+ import { Temporal } from "temporal-polyfill";
4
+ import { readFile } from "node:fs/promises";
5
+ import { setTimeout } from "node:timers/promises";
6
+ //#region src/util.ts
7
+ const combineUrl = (baseUrl, relativeUrl) => {
8
+ if (!baseUrl.trim()) return relativeUrl;
9
+ const url1 = new URL(baseUrl);
10
+ return new URL(relativeUrl, url1).toString();
11
+ };
12
+ const parseMimes = (codecs) => codecs.toLowerCase().split(",").map((codec) => codec.trim().split(".")[0]);
13
+ //#endregion
14
+ //#region src/audio.ts
15
+ const parseAudioCodecFromMime = (mime) => {
16
+ switch (mime.toLowerCase().trim().split(".")[0]) {
17
+ case "mp4a": return "aac";
18
+ case "ac-3": return "ac3";
19
+ case "ec-3": return "eac3";
20
+ case "opus": return "opus";
21
+ case "dtsc": return "dts";
22
+ case "alac": return "alac";
23
+ case "flac": return "flac";
24
+ default: throw new Error(`The MIME ${mime} is not supported as audio codec`);
25
+ }
26
+ };
27
+ const parseAudioCodec = (codecs) => {
28
+ const mimes = parseMimes(codecs);
29
+ for (const mime of mimes) try {
30
+ return parseAudioCodecFromMime(mime);
31
+ } catch {
32
+ continue;
33
+ }
34
+ throw new Error(`No MIME types matched any supported Audio Codecs in ${codecs}`);
35
+ };
36
+ const tryParseAudioCodec = (codecs) => {
37
+ try {
38
+ return parseAudioCodec(codecs);
39
+ } catch {
40
+ return;
41
+ }
42
+ };
43
+ const getDolbyDigitalPlusComplexityIndex = (supplementalProps = []) => {
44
+ const targetScheme = "tag:dolby.com,2018:dash:EC3_ExtensionComplexityIndex:2018";
45
+ for (const prop of supplementalProps) if (prop.schemeIdUri === targetScheme && prop.value) return parseInt(prop.value);
46
+ };
47
+ const checkIsDescriptive = (accessibilities = []) => {
48
+ for (const accessibility of accessibilities) {
49
+ const { schemeIdUri, value } = accessibility;
50
+ if (schemeIdUri == "urn:mpeg:dash:role:2011" && value === "descriptive" || schemeIdUri == "urn:tva:metadata:cs:AudioPurposeCS:2007" && value === "1") return true;
51
+ }
52
+ return false;
53
+ };
54
+ const parseChannels = (channels) => {
55
+ const isDigit = (char) => char >= "0" && char <= "9";
56
+ if (typeof channels === "string") {
57
+ if (channels.toUpperCase() == "A000") return 2;
58
+ else if (channels.toUpperCase() == "F801") return 5.1;
59
+ else if (isDigit(channels.replace("ch", "").replace(".", "")[0])) return parseFloat(channels.replace("ch", ""));
60
+ throw new Error(`Unsupported audio channels value, '${channels}'`);
61
+ }
62
+ return parseFloat(channels);
63
+ };
64
+ //#endregion
65
+ //#region src/encrypt-method.ts
66
+ const ENCRYPT_METHODS = {
67
+ NONE: "none",
68
+ AES_128: "aes-128",
69
+ AES_128_ECB: "aes-128-ecb",
70
+ SAMPLE_AES: "sample-aes",
71
+ SAMPLE_AES_CTR: "sample-aes-ctr",
72
+ CENC: "cenc",
73
+ CHACHA20: "chacha20",
74
+ UNKNOWN: "unknown"
75
+ };
76
+ //#endregion
77
+ //#region src/role-type.ts
78
+ const ROLE_TYPE = {
79
+ Subtitle: 0,
80
+ Main: 1,
81
+ Alternate: 2,
82
+ Supplementary: 3,
83
+ Commentary: 4,
84
+ Dub: 5,
85
+ Description: 6,
86
+ Sign: 7,
87
+ Metadata: 8,
88
+ ForcedSubtitle: 9
89
+ };
90
+ //#endregion
91
+ //#region src/subtitle.ts
92
+ const parseSubtitleCodecFromMime = (mime) => {
93
+ switch (mime.toLowerCase().trim().split(".")[0]) {
94
+ case "srt":
95
+ case "x-subrip": return "srt";
96
+ case "ssa": return "ssa";
97
+ case "ass": return "ass";
98
+ case "ttml": return "ttml";
99
+ case "vtt": return "vtt";
100
+ case "stpp": return "stpp";
101
+ case "wvtt": return "wvtt";
102
+ default: throw new Error(`The MIME ${mime} is not supported as subtitle codec`);
103
+ }
104
+ };
105
+ const parseSubtitleCodec = (codecs) => {
106
+ const mimes = parseMimes(codecs);
107
+ for (const mime of mimes) try {
108
+ return parseSubtitleCodecFromMime(mime);
109
+ } catch {
110
+ continue;
111
+ }
112
+ throw new Error(`No MIME types matched any supported Subtitle Codecs in ${codecs}`);
113
+ };
114
+ const tryParseSubtitleCodec = (codecs) => {
115
+ try {
116
+ return parseSubtitleCodec(codecs);
117
+ } catch {
118
+ return;
119
+ }
120
+ };
121
+ const checkIsClosedCaption = (roles = []) => {
122
+ for (const role of roles) if (role.schemeIdUri === "urn:mpeg:dash:role:2011" && role.value === "caption") return true;
123
+ return false;
124
+ };
125
+ const checkIsSdh = (accessibilities = []) => {
126
+ for (const accessibility of accessibilities) {
127
+ const { schemeIdUri, value } = accessibility;
128
+ if (schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && value === "2") return true;
129
+ }
130
+ return false;
131
+ };
132
+ //#endregion
133
+ //#region src/video.ts
134
+ const PRIMARIES = {
135
+ Unspecified: 0,
136
+ BT_709: 1,
137
+ BT_601_625: 5,
138
+ BT_601_525: 6,
139
+ BT_2020_and_2100: 9,
140
+ SMPTE_ST_2113_and_EG_4321: 12
141
+ };
142
+ const TRANSFER = {
143
+ Unspecified: 0,
144
+ BT_709: 1,
145
+ BT_601: 6,
146
+ BT_2020: 14,
147
+ BT_2100: 15,
148
+ BT_2100_PQ: 16,
149
+ BT_2100_HLG: 18
150
+ };
151
+ const MATRIX = {
152
+ RGB: 0,
153
+ YCbCr_BT_709: 1,
154
+ YCbCr_BT_601_625: 5,
155
+ YCbCr_BT_601_525: 6,
156
+ YCbCr_BT_2020_and_2100: 9,
157
+ ICtCp_BT_2100: 14
158
+ };
159
+ const parseVideoCodecFromMime = (mime) => {
160
+ const target = mime.toLowerCase().trim().split(".")[0];
161
+ const avc = [
162
+ "avc1",
163
+ "avc2",
164
+ "avc3",
165
+ "dva1",
166
+ "dvav"
167
+ ];
168
+ const hevc = [
169
+ "hev1",
170
+ "hev2",
171
+ "hev3",
172
+ "hvc1",
173
+ "hvc2",
174
+ "hvc3",
175
+ "dvh1",
176
+ "dvhe",
177
+ "lhv1",
178
+ "lhe1"
179
+ ];
180
+ const vc1 = ["vc-1"];
181
+ const vp8 = ["vp08", "vp8"];
182
+ const vp9 = ["vp09", "vp9"];
183
+ const av1 = ["av01"];
184
+ if (avc.includes(target)) return "avc";
185
+ if (hevc.includes(target)) return "hevc";
186
+ if (vc1.includes(target)) return "vc1";
187
+ if (vp8.includes(target)) return "vp8";
188
+ if (vp9.includes(target)) return "vp9";
189
+ if (av1.includes(target)) return "av1";
190
+ throw new Error(`The MIME ${mime} is not supported as video codec`);
191
+ };
192
+ const parseDynamicRangeFromCicp = (primaries, transfer, matrix) => {
193
+ if (transfer == 5) transfer = TRANSFER.BT_601;
194
+ if (primaries == PRIMARIES.Unspecified && transfer == TRANSFER.Unspecified && matrix == MATRIX.RGB) return "sdr";
195
+ else if ([PRIMARIES.BT_601_625, PRIMARIES.BT_601_525].includes(primaries)) return "sdr";
196
+ else if (TRANSFER.BT_2100_PQ === transfer) return "hdr10";
197
+ else if (TRANSFER.BT_2100_HLG === transfer) return "hlg";
198
+ else return "sdr";
199
+ };
200
+ const parseVideoCodec = (codecs) => {
201
+ for (const codec of codecs.toLowerCase().split(",")) {
202
+ const mime = codec.trim().split(".")[0];
203
+ try {
204
+ return parseVideoCodecFromMime(mime);
205
+ } catch {
206
+ continue;
207
+ }
208
+ }
209
+ throw new Error(`No MIME types matched any supported Video Codecs in ${codecs}`);
210
+ };
211
+ const tryParseVideoCodec = (codecs) => {
212
+ try {
213
+ return parseVideoCodec(codecs);
214
+ } catch {
215
+ return;
216
+ }
217
+ };
218
+ const parseDynamicRange = (codecs, supplementalProps = [], essentialProps = []) => {
219
+ if ([
220
+ "dva1",
221
+ "dvav",
222
+ "dvhe",
223
+ "dvh1"
224
+ ].some((value) => codecs.startsWith(value))) return "dv";
225
+ const primariesScheme = "urn:mpeg:mpegB:cicp:ColourPrimaries";
226
+ const transferScheme = "urn:mpeg:mpegB:cicp:TransferCharacteristics";
227
+ const matrixScheme = "urn:mpeg:mpegB:cicp:MatrixCoefficients";
228
+ const allProps = [...essentialProps, ...supplementalProps];
229
+ const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).flatMap((prop) => prop.value ? [parseInt(prop.value)] : []);
230
+ return parseDynamicRangeFromCicp(getValues(primariesScheme).reduce((acc, current) => acc + current, 0), getValues(transferScheme).reduce((acc, current) => acc + current, 0), getValues(matrixScheme).reduce((acc, current) => acc + current, 0));
231
+ };
232
+ //#endregion
233
+ //#region src/dash/dash-misc.ts
234
+ const DASH_MIME_TYPE = "application/dash+xml";
235
+ const DASH_TEMPLATE_REPRESENTATION_ID = "$RepresentationID$";
236
+ const DASH_TEMPLATE_BANDWIDTH = "$Bandwidth$";
237
+ const DASH_TEMPLATE_NUMBER = "$Number$";
238
+ const DASH_TEMPLATE_TIME = "$Time$";
239
+ const getDashTrackMatchKey = (track) => JSON.stringify({
240
+ type: track.type,
241
+ codecString: track.codecString,
242
+ groupId: track.groupId,
243
+ width: track.type === "video" ? track.width : null,
244
+ height: track.type === "video" ? track.height : null,
245
+ languageCode: track.languageCode ?? null,
246
+ name: track.name,
247
+ role: track.role ?? null,
248
+ extension: track.extension
249
+ });
250
+ const getSourcePath = (source) => {
251
+ if ("rootPath" in source && typeof source.rootPath === "string") return source.rootPath;
252
+ };
253
+ const normalizeHeaders = (headers) => {
254
+ if (!headers) return {};
255
+ if (headers instanceof Headers) return Object.fromEntries(headers.entries());
256
+ if (Array.isArray(headers)) return Object.fromEntries(headers);
257
+ return { ...headers };
258
+ };
259
+ const getSourceHeaders = (source) => {
260
+ const requestHeaders = "_url" in source && source._url instanceof Request ? normalizeHeaders(source._url.headers) : {};
261
+ const optionHeaders = normalizeHeaders(("_options" in source && source._options && typeof source._options === "object" ? source._options : void 0)?.requestInit?.headers);
262
+ return {
263
+ ...requestHeaders,
264
+ ...optionHeaders
265
+ };
266
+ };
267
+ const parseOriginalUrlFromManifest = (text) => text.match(/<!--\s*URL:\s*([^\n]+?)\s*-->/)?.[1]?.trim();
268
+ const loadDashManifest = async (source) => {
269
+ const manifestPath = getSourcePath(source);
270
+ if (!manifestPath) throw new Error("DASH input currently requires a pathed source such as UrlSource.");
271
+ if (manifestPath.startsWith("http://") || manifestPath.startsWith("https://")) {
272
+ const response = await fetch(manifestPath, { headers: getSourceHeaders(source) });
273
+ if (!response.ok) throw new Error(`Failed to fetch DASH manifest: ${response.status} ${response.statusText} (${response.url})`);
274
+ return {
275
+ text: await response.text(),
276
+ url: response.url
277
+ };
278
+ }
279
+ if (manifestPath.startsWith("file:")) {
280
+ const text = await readFile(new URL(manifestPath), "utf8");
281
+ return {
282
+ text,
283
+ url: parseOriginalUrlFromManifest(text) ?? manifestPath
284
+ };
285
+ }
286
+ const text = await readFile(manifestPath, "utf8");
287
+ return {
288
+ text,
289
+ url: parseOriginalUrlFromManifest(text) ?? manifestPath
290
+ };
291
+ };
292
+ const isLikelyDashPath = (source) => {
293
+ const path = getSourcePath(source);
294
+ if (!path) return false;
295
+ return path.toLowerCase().split(/[?#]/, 1)[0]?.endsWith(".mpd") ?? false;
296
+ };
297
+ const isDashManifestText = (text) => /<MPD(?:\s|>)/i.test(text);
298
+ const replaceDashVariables = (text, variables) => {
299
+ let result = "";
300
+ for (let index = 0; index < text.length;) {
301
+ if (text[index] !== "$") {
302
+ result += text[index];
303
+ index += 1;
304
+ continue;
305
+ }
306
+ if (text[index + 1] === "$") {
307
+ result += "$";
308
+ index += 2;
309
+ continue;
310
+ }
311
+ const endIndex = text.indexOf("$", index + 1);
312
+ if (endIndex < 0) {
313
+ result += "$";
314
+ index += 1;
315
+ continue;
316
+ }
317
+ const match = text.slice(index + 1, endIndex).match(/^(RepresentationID|Bandwidth|Number|Time)(?:%([0-9]+)d)?$/);
318
+ if (!match) {
319
+ result += text.slice(index, endIndex + 1);
320
+ index = endIndex + 1;
321
+ continue;
322
+ }
323
+ const [, variableName, width] = match;
324
+ const value = variables[`$${variableName}$`];
325
+ if (value == null) {
326
+ result += text.slice(index, endIndex + 1);
327
+ index = endIndex + 1;
328
+ continue;
329
+ }
330
+ result += !width || variableName === "RepresentationID" ? value : value.padStart(Number.parseInt(width, 10), "0");
331
+ index = endIndex + 1;
332
+ }
333
+ return result;
334
+ };
335
+ const createDashTrackDescriptor = (params) => {
336
+ const shouldUseCodecsFromMime = params.contentType === "text" && !params.mimeType?.includes("mp4");
337
+ const codecString = params.codecs ?? (shouldUseCodecsFromMime ? params.mimeType?.split("/")[1] : null);
338
+ if (codecString) {
339
+ const videoCodec = tryParseVideoCodec(codecString);
340
+ if (videoCodec) return {
341
+ type: "video",
342
+ codec: videoCodec,
343
+ codecString
344
+ };
345
+ const audioCodec = tryParseAudioCodec(codecString);
346
+ if (audioCodec) return {
347
+ type: "audio",
348
+ codec: audioCodec,
349
+ codecString
350
+ };
351
+ const subtitleCodec = tryParseSubtitleCodec(codecString);
352
+ if (subtitleCodec) return {
353
+ type: "subtitle",
354
+ codec: subtitleCodec,
355
+ codecString
356
+ };
357
+ } else {
358
+ const type = params.contentType || params.mimeType?.split("/")[0];
359
+ if (type === "video") return {
360
+ type: "video",
361
+ codecString: null
362
+ };
363
+ if (type === "audio") return {
364
+ type: "audio",
365
+ codecString: null
366
+ };
367
+ if (type === "text") {
368
+ const subtitleCodecString = params.mimeType?.split("/")[1] ?? null;
369
+ return {
370
+ type: "subtitle",
371
+ codec: subtitleCodecString ? tryParseSubtitleCodec(subtitleCodecString) : void 0,
372
+ codecString: subtitleCodecString
373
+ };
374
+ }
375
+ }
376
+ throw new Error("Unable to determine the type of a track, cannot continue...");
377
+ };
378
+ const getDirectDashChildren = (node, tag) => node.getElementsByTagName(tag).filter((child) => !!child.parentNode?.isSameNode(node));
379
+ const getDirectDashChild = (node, tag) => getDirectDashChildren(node, tag)[0];
380
+ const getInheritedDashChild = (tag, ...nodes) => {
381
+ for (const node of nodes) {
382
+ const child = getDirectDashChild(node, tag);
383
+ if (child) return child;
384
+ }
385
+ };
386
+ const getDashTagAttrs = (tag, ...elements) => {
387
+ for (const element of elements) {
388
+ const matches = getDirectDashChildren(element, tag);
389
+ if (!matches.length) continue;
390
+ return matches.flatMap((match) => {
391
+ const schemeIdUri = match.getAttribute("schemeIdUri");
392
+ if (!schemeIdUri) return [];
393
+ return {
394
+ schemeIdUri,
395
+ value: match.getAttribute("value") ?? void 0
396
+ };
397
+ });
398
+ }
399
+ return [];
400
+ };
401
+ const extendDashBaseUrl = (node, baseUrl) => {
402
+ const target = getDirectDashChild(node, "BaseURL");
403
+ if (target?.textContent) return combineUrl(baseUrl, target.textContent);
404
+ return baseUrl;
405
+ };
406
+ const getDashFrameRate = (node) => {
407
+ const frameRate = node.getAttribute("frameRate");
408
+ if (!frameRate || !frameRate.includes("/")) return;
409
+ const value = Number(frameRate.split("/")[0]) / Number(frameRate.split("/")[1]);
410
+ return Number(value.toFixed(3));
411
+ };
412
+ const parseDashRange = (range) => {
413
+ const [startRange, end] = range.split("-").map(Number);
414
+ return [startRange, end - startRange + 1];
415
+ };
416
+ //#endregion
417
+ //#region src/dash/dash-segmented-input.ts
418
+ const roundToDivisor = (value, multiple) => Math.round(value * multiple) / multiple;
419
+ const binarySearchLessOrEqual = (array, value, getValue) => {
420
+ let left = 0;
421
+ let right = array.length - 1;
422
+ let result = -1;
423
+ while (left <= right) {
424
+ const mid = left + right >> 1;
425
+ if (getValue(array[mid]) <= value) {
426
+ result = mid;
427
+ left = mid + 1;
428
+ } else right = mid - 1;
429
+ }
430
+ return result;
431
+ };
432
+ const getLeastRecentlyUsedIndex = (entries) => {
433
+ let bestIndex = -1;
434
+ let bestAge = Infinity;
435
+ for (const [index, entry] of entries.entries()) if (entry.age < bestAge) {
436
+ bestAge = entry.age;
437
+ bestIndex = index;
438
+ }
439
+ return bestIndex;
440
+ };
441
+ const getSegmentLocation = (segment) => ({
442
+ path: segment.url,
443
+ offset: segment.startRange ?? 0,
444
+ length: segment.expectLength ?? null
445
+ });
446
+ const createInitSegment = (segment) => ({
447
+ timestamp: 0,
448
+ duration: 0,
449
+ relativeToUnixEpoch: false,
450
+ firstSegment: null,
451
+ sequenceNumber: segment.sequenceNumber,
452
+ location: getSegmentLocation(segment),
453
+ encryption: segment.encryption,
454
+ initSegment: null,
455
+ lastProgramDateTimeSeconds: null
456
+ });
457
+ const trackToDashSegments = (internalTrack) => {
458
+ const mediaSegments = internalTrack.track.mediaSegments;
459
+ if (mediaSegments.length === 0) return [];
460
+ let nextTimestamp = 0;
461
+ const segments = [];
462
+ for (const mediaSegment of mediaSegments) {
463
+ const timestamp = mediaSegment.timestamp ?? nextTimestamp;
464
+ const dashSegment = {
465
+ timestamp,
466
+ duration: mediaSegment.duration,
467
+ relativeToUnixEpoch: false,
468
+ firstSegment: null,
469
+ sequenceNumber: mediaSegment.sequenceNumber,
470
+ location: getSegmentLocation(mediaSegment),
471
+ encryption: mediaSegment.encryption,
472
+ initSegment: null,
473
+ lastProgramDateTimeSeconds: null
474
+ };
475
+ segments.push(dashSegment);
476
+ nextTimestamp = timestamp + mediaSegment.duration;
477
+ }
478
+ const firstSegment = segments[0] ?? null;
479
+ const initSegment = internalTrack.track.initSegment ? createInitSegment(internalTrack.track.initSegment) : null;
480
+ for (const segment of segments) {
481
+ segment.firstSegment = firstSegment;
482
+ segment.initSegment = initSegment;
483
+ }
484
+ return segments;
485
+ };
486
+ var DashSegmentedInput = class {
487
+ internalTrack;
488
+ demuxer;
489
+ segments = [];
490
+ currentUpdateSegmentsPromise = null;
491
+ lastSegmentUpdateTime = -Infinity;
492
+ nextInputCacheAge = 0;
493
+ inputCache = [];
494
+ firstTrackPromise = null;
495
+ packetInfos = /* @__PURE__ */ new WeakMap();
496
+ firstSegmentFirstTimestamps = /* @__PURE__ */ new WeakMap();
497
+ firstTimestampCache = /* @__PURE__ */ new WeakMap();
498
+ constructor(internalTrack) {
499
+ this.internalTrack = internalTrack;
500
+ this.demuxer = internalTrack.demuxer;
501
+ }
502
+ runUpdateSegments() {
503
+ return this.currentUpdateSegmentsPromise ??= (async () => {
504
+ try {
505
+ const remainingWaitTimeMs = this.getRemainingWaitTimeMs();
506
+ if (remainingWaitTimeMs > 0) await setTimeout(remainingWaitTimeMs);
507
+ this.lastSegmentUpdateTime = performance.now();
508
+ await this.updateSegments();
509
+ } finally {
510
+ this.currentUpdateSegmentsPromise = null;
511
+ }
512
+ })();
513
+ }
514
+ async updateSegments() {
515
+ await this.demuxer.refreshTrackSegments(this.internalTrack);
516
+ this.segments = trackToDashSegments(this.internalTrack);
517
+ }
518
+ getRemainingWaitTimeMs() {
519
+ if (!this.internalTrack.track.isLive) return 0;
520
+ const elapsed = performance.now() - this.lastSegmentUpdateTime;
521
+ const result = Math.max(0, this.internalTrack.track.refreshIntervalMs - elapsed);
522
+ if (result <= 50) return 0;
523
+ return result;
524
+ }
525
+ async getFirstSegment() {
526
+ if (this.segments.length === 0) await this.runUpdateSegments();
527
+ return this.segments[0] ?? null;
528
+ }
529
+ async getSegmentAt(timestamp, options) {
530
+ if (this.segments.length === 0) await this.runUpdateSegments();
531
+ let isLazy = !!options.skipLiveWait && this.getRemainingWaitTimeMs() > 0;
532
+ while (true) {
533
+ const index = binarySearchLessOrEqual(this.segments, timestamp, (segment) => segment.timestamp);
534
+ if (index === -1) return null;
535
+ if (index < this.segments.length - 1 || !this.internalTrack.track.isLive || isLazy) return this.segments[index];
536
+ const segment = this.segments[index];
537
+ if (timestamp < segment.timestamp + segment.duration) return segment;
538
+ await this.runUpdateSegments();
539
+ if (options.skipLiveWait) isLazy = true;
540
+ }
541
+ }
542
+ getNextSegmentIndex(segment) {
543
+ const currentIndex = this.segments.indexOf(segment);
544
+ if (currentIndex !== -1) return currentIndex + 1;
545
+ if (segment.sequenceNumber !== null) {
546
+ const matchingSequenceIndex = this.segments.findIndex((candidate) => candidate.sequenceNumber === segment.sequenceNumber);
547
+ if (matchingSequenceIndex !== -1) return matchingSequenceIndex + 1;
548
+ return this.segments.findIndex((candidate) => candidate.sequenceNumber !== null && candidate.sequenceNumber > segment.sequenceNumber);
549
+ }
550
+ const matchingLocationIndex = this.segments.findIndex((candidate) => candidate.timestamp === segment.timestamp && candidate.duration === segment.duration && candidate.location.path === segment.location.path && candidate.location.offset === segment.location.offset && candidate.location.length === segment.location.length);
551
+ if (matchingLocationIndex !== -1) return matchingLocationIndex + 1;
552
+ return this.segments.findIndex((candidate) => candidate.timestamp > segment.timestamp);
553
+ }
554
+ async getNextSegment(segment, options) {
555
+ let isLazy = !!options.skipLiveWait && this.getRemainingWaitTimeMs() > 0;
556
+ while (true) {
557
+ const nextIndex = this.getNextSegmentIndex(segment);
558
+ if (nextIndex !== -1 && nextIndex < this.segments.length) return this.segments[nextIndex];
559
+ if (!this.internalTrack.track.isLive || isLazy) return null;
560
+ await this.runUpdateSegments();
561
+ if (options.skipLiveWait) isLazy = true;
562
+ }
563
+ }
564
+ async getPreviousSegment(segment) {
565
+ const index = this.segments.indexOf(segment);
566
+ if (index === -1) throw new Error("Segment was not created by this segmented input.");
567
+ return this.segments[index - 1] ?? null;
568
+ }
569
+ getInputForSegment(segment) {
570
+ const input = this.demuxer.input;
571
+ const cacheEntry = this.inputCache.find((entry) => entry.segment === segment);
572
+ if (cacheEntry) {
573
+ cacheEntry.age = this.nextInputCacheAge++;
574
+ return cacheEntry.input;
575
+ }
576
+ let initInput = null;
577
+ if (segment.initSegment && segment.initSegment !== segment) initInput = this.getInputForSegment(segment.initSegment);
578
+ const formatOptions = { ...input._formatOptions };
579
+ const segmentInput = new Input$1({
580
+ source: new CustomPathedSource(segment.location.path, async (request) => {
581
+ if (!request.isRoot) throw new Error("Nested requests are not supported for DASH segments.");
582
+ const proxiedRequest = {
583
+ ...request,
584
+ isRoot: false
585
+ };
586
+ let ref = await input._getSourceCached(proxiedRequest);
587
+ if (segment.location.offset > 0 || segment.location.length !== null) {
588
+ const sliceRef = ref.source.slice(segment.location.offset, segment.location.length ?? void 0).ref();
589
+ ref.free();
590
+ ref = sliceRef;
591
+ }
592
+ return ref;
593
+ }),
594
+ formats: input._formats.filter((format) => format.mimeType !== DASH_MIME_TYPE),
595
+ initInput: initInput ?? void 0,
596
+ formatOptions
597
+ });
598
+ this.inputCache.push({
599
+ age: this.nextInputCacheAge++,
600
+ input: segmentInput,
601
+ segment
602
+ });
603
+ if (this.inputCache.length > 4) {
604
+ const minAgeIndex = getLeastRecentlyUsedIndex(this.inputCache);
605
+ if (minAgeIndex === -1) throw new Error("Failed to evict cached DASH segment input.");
606
+ this.inputCache.splice(minAgeIndex, 1);
607
+ }
608
+ return segmentInput;
609
+ }
610
+ async getTrackForSegment(segment) {
611
+ const matchingType = (await this.getInputForSegment(segment).getTracks()).filter((track) => track.type === this.internalTrack.info.type);
612
+ if (matchingType.length === 1) return matchingType[0];
613
+ if (this.internalTrack.track.codec) {
614
+ for (const track of matchingType) if (await track.getCodec() === this.internalTrack.track.codec) return track;
615
+ }
616
+ return matchingType[0] ?? null;
617
+ }
618
+ async getFirstTrack() {
619
+ return this.firstTrackPromise ??= (async () => {
620
+ const firstSegment = await this.getFirstSegment();
621
+ if (!firstSegment) throw new Error("Missing first DASH segment, cannot hydrate track.");
622
+ const track = await this.getTrackForSegment(firstSegment);
623
+ if (!track) throw new Error("No matching track found in DASH segment media data.");
624
+ return track;
625
+ })();
626
+ }
627
+ async getFirstTimestampForInput(input) {
628
+ const existing = this.firstTimestampCache.get(input);
629
+ if (existing !== void 0) return existing;
630
+ const firstTimestamp = await input.getFirstTimestamp();
631
+ this.firstTimestampCache.set(input, firstTimestamp);
632
+ return firstTimestamp;
633
+ }
634
+ async getMediaOffset(segment, input, track) {
635
+ const firstSegment = segment.firstSegment ?? segment;
636
+ let firstSegmentFirstTimestamp;
637
+ if (this.firstSegmentFirstTimestamps.has(firstSegment)) firstSegmentFirstTimestamp = this.firstSegmentFirstTimestamps.get(firstSegment);
638
+ else {
639
+ const firstInput = this.getInputForSegment(firstSegment);
640
+ firstSegmentFirstTimestamp = await this.getFirstTimestampForInput(firstInput);
641
+ this.firstSegmentFirstTimestamps.set(firstSegment, firstSegmentFirstTimestamp);
642
+ }
643
+ if (firstSegment === segment) return firstSegment.timestamp - firstSegmentFirstTimestamp;
644
+ const segmentFirstTimestamp = await this.getFirstTimestampForInput(input);
645
+ const segmentElapsed = segment.timestamp - firstSegment.timestamp;
646
+ const difference = segmentFirstTimestamp - firstSegmentFirstTimestamp - segmentElapsed;
647
+ if (Math.abs(difference) <= Math.min(.25, segmentElapsed)) return firstSegment.timestamp - firstSegmentFirstTimestamp;
648
+ return segment.timestamp - segmentFirstTimestamp;
649
+ }
650
+ async createAdjustedPacket(packet, segment, track) {
651
+ if (packet.sequenceNumber < 0) throw new Error("DASH packet sequence number must be non-negative.");
652
+ const input = track.input;
653
+ const mediaOffset = await this.getMediaOffset(segment, input, track);
654
+ const firstSegment = segment.firstSegment ?? segment;
655
+ const segmentTimestampRelativeToFirst = segment.timestamp - firstSegment.timestamp;
656
+ const modified = packet.clone({
657
+ timestamp: roundToDivisor(packet.timestamp + mediaOffset, await track.getTimeResolution()),
658
+ sequenceNumber: Math.floor(1e8 * segmentTimestampRelativeToFirst) + packet.sequenceNumber
659
+ });
660
+ this.packetInfos.set(modified, {
661
+ segment,
662
+ track,
663
+ sourcePacket: packet
664
+ });
665
+ return modified;
666
+ }
667
+ async getDecoderConfig() {
668
+ return (await this.getFirstTrack())._backing.getDecoderConfig();
669
+ }
670
+ async getHasOnlyKeyPackets() {
671
+ return await (await this.getFirstTrack())._backing.getHasOnlyKeyPackets?.() ?? null;
672
+ }
673
+ async getFirstPacket(options) {
674
+ const firstSegment = await this.getFirstSegment();
675
+ if (!firstSegment) return null;
676
+ const track = await this.getTrackForSegment(firstSegment);
677
+ if (!track) return null;
678
+ const packet = await track._backing.getFirstPacket(options);
679
+ if (!packet) return null;
680
+ return this.createAdjustedPacket(packet, firstSegment, track);
681
+ }
682
+ getNextPacket(packet, options) {
683
+ return this.getNextPacketInternal(packet, options, false);
684
+ }
685
+ getNextKeyPacket(packet, options) {
686
+ return this.getNextPacketInternal(packet, options, true);
687
+ }
688
+ async getNextPacketInternal(packet, options, keyframesOnly) {
689
+ const info = this.packetInfos.get(packet);
690
+ if (!info) throw new Error("Packet was not created from this DASH track.");
691
+ const nextPacket = keyframesOnly ? await info.track._backing.getNextKeyPacket(info.sourcePacket, options) : await info.track._backing.getNextPacket(info.sourcePacket, options);
692
+ if (nextPacket) return this.createAdjustedPacket(nextPacket, info.segment, info.track);
693
+ let currentSegment = info.segment;
694
+ while (true) {
695
+ const nextSegment = await this.getNextSegment(currentSegment, { skipLiveWait: options.skipLiveWait });
696
+ if (!nextSegment) return null;
697
+ const nextTrack = await this.getTrackForSegment(nextSegment);
698
+ if (!nextTrack) {
699
+ currentSegment = nextSegment;
700
+ continue;
701
+ }
702
+ const firstPacket = await nextTrack._backing.getFirstPacket(options);
703
+ if (!firstPacket) {
704
+ currentSegment = nextSegment;
705
+ continue;
706
+ }
707
+ return this.createAdjustedPacket(firstPacket, nextSegment, nextTrack);
708
+ }
709
+ }
710
+ getPacket(timestamp, options) {
711
+ return this.getPacketInternal(timestamp, options, false);
712
+ }
713
+ getKeyPacket(timestamp, options) {
714
+ return this.getPacketInternal(timestamp, options, true);
715
+ }
716
+ async getPacketInternal(timestamp, options, keyframesOnly) {
717
+ let currentSegment = await this.getSegmentAt(timestamp, { skipLiveWait: options.skipLiveWait });
718
+ if (!currentSegment) return null;
719
+ while (currentSegment) {
720
+ const track = await this.getTrackForSegment(currentSegment);
721
+ if (!track) {
722
+ currentSegment = await this.getPreviousSegment(currentSegment);
723
+ continue;
724
+ }
725
+ const input = track.input;
726
+ const offsetTimestamp = timestamp - await this.getMediaOffset(currentSegment, input, track);
727
+ const packet = keyframesOnly ? await track._backing.getKeyPacket(offsetTimestamp, options) : await track._backing.getPacket(offsetTimestamp, options);
728
+ if (!packet) {
729
+ currentSegment = await this.getPreviousSegment(currentSegment);
730
+ continue;
731
+ }
732
+ return this.createAdjustedPacket(packet, currentSegment, track);
733
+ }
734
+ return null;
735
+ }
736
+ async getLiveRefreshInterval() {
737
+ if (this.getRemainingWaitTimeMs() === 0) await this.runUpdateSegments();
738
+ return this.internalTrack.track.isLive ? this.internalTrack.track.refreshIntervalMs / 1e3 : null;
739
+ }
740
+ };
741
+ //#endregion
742
+ //#region src/dash/dash-demuxer.ts
743
+ const DASH_NAMESPACE_MAP = new Map([
744
+ ["cenc", "urn:mpeg:cenc:2013"],
745
+ ["mspr", "urn:microsoft:playready"],
746
+ ["mas", "urn:marlin:mas:1-0:services:schemas:mpd"]
747
+ ]);
748
+ const WIDEVINE_SYSTEM_ID = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
749
+ const PLAYREADY_SYSTEM_ID = "9a04f079-9840-4286-ab92-e65be0885f95";
750
+ const DEFAULT_TRACK_DISPOSITION = {
751
+ commentary: false,
752
+ default: true,
753
+ forced: false,
754
+ hearingImpaired: false,
755
+ original: false,
756
+ primary: true,
757
+ visuallyImpaired: false
758
+ };
759
+ const isMissingNamespace = (rawText, tag) => !rawText.includes(`xmlns:${tag}`) && rawText.includes(`<${tag}:`);
760
+ const replaceFirst = (source, oldValue, newValue) => {
761
+ const index = source.indexOf(oldValue);
762
+ return index < 0 ? source : source.slice(0, index) + newValue + source.slice(index + oldValue.length);
763
+ };
764
+ const processDashContent = (mpdContent) => {
765
+ const missingNamespaceKeys = Array.from(DASH_NAMESPACE_MAP.keys().filter((key) => isMissingNamespace(mpdContent, key)));
766
+ if (!missingNamespaceKeys.length) return mpdContent;
767
+ return replaceFirst(mpdContent, "<MPD ", `<MPD ${missingNamespaceKeys.map((key) => `xmlns:${key}="${DASH_NAMESPACE_MAP.get(key)}"`).join(" ")} `);
768
+ };
769
+ const getDashRefreshIntervalMs = (timeShiftBufferDepth) => Temporal.Duration.from(timeShiftBufferDepth).total("milliseconds") / 2;
770
+ const getDisposition = (track) => ({
771
+ ...DEFAULT_TRACK_DISPOSITION,
772
+ commentary: track.role === ROLE_TYPE.Commentary,
773
+ default: !!track.default,
774
+ forced: track.role === ROLE_TYPE.ForcedSubtitle || !!(track.type === "subtitle" && track.forced),
775
+ hearingImpaired: !!(track.type === "subtitle" && track.sdh),
776
+ visuallyImpaired: !!(track.type === "audio" && track.descriptive)
777
+ });
778
+ const canPairTracks = (left, right) => {
779
+ if (left === right || left.type === right.type) return false;
780
+ if (left.type === "video" && right.type === "audio") return !left.audioGroupId || left.audioGroupId === right.groupId;
781
+ if (left.type === "audio" && right.type === "video") return !right.audioGroupId || right.audioGroupId === left.groupId;
782
+ if (left.type === "video" && right.type === "subtitle") return !left.subtitleGroupId || left.subtitleGroupId === right.groupId;
783
+ if (left.type === "subtitle" && right.type === "video") return !right.subtitleGroupId || right.subtitleGroupId === left.groupId;
784
+ return false;
785
+ };
786
+ const createPairingMasks = (tracks) => {
787
+ const masks = /* @__PURE__ */ new Map();
788
+ let nextPairIndex = 0;
789
+ for (const [leftIndex, left] of tracks.entries()) for (const right of tracks.slice(leftIndex + 1)) {
790
+ if (!canPairTracks(left, right)) continue;
791
+ const bit = 1n << BigInt(nextPairIndex++);
792
+ masks.set(left, (masks.get(left) ?? 0n) | bit);
793
+ masks.set(right, (masks.get(right) ?? 0n) | bit);
794
+ }
795
+ return masks;
796
+ };
797
+ const createTrackInfo = (track) => {
798
+ if (track.type === "video") return {
799
+ type: "video",
800
+ width: track.width ?? null,
801
+ height: track.height ?? null
802
+ };
803
+ if (track.type === "audio") return {
804
+ type: "audio",
805
+ numberOfChannels: track.numberOfChannels ?? null
806
+ };
807
+ return { type: "subtitle" };
808
+ };
809
+ const createInternalTracks = (demuxer, tracks) => {
810
+ const pairingMasks = createPairingMasks(tracks);
811
+ return tracks.map((track, index) => ({
812
+ id: index + 1,
813
+ demuxer,
814
+ backingTrack: null,
815
+ pairingMask: pairingMasks.get(track) ?? 0n,
816
+ track,
817
+ info: createTrackInfo(track)
818
+ }));
819
+ };
820
+ const getTrackNumber = (internalTrack) => {
821
+ const internalTracks = internalTrack.demuxer.internalTracks;
822
+ if (!internalTracks) return 1;
823
+ let number = 0;
824
+ for (const track of internalTracks) {
825
+ if (track.info.type === internalTrack.info.type) number++;
826
+ if (track === internalTrack) break;
827
+ }
828
+ return number;
829
+ };
830
+ const addWholeResourceSegment = (track, url, duration) => {
831
+ track.mediaSegments.push({
832
+ sequenceNumber: 0,
833
+ duration,
834
+ url,
835
+ encryption: null
836
+ });
837
+ };
838
+ const appendDashSegment = (track, segment) => {
839
+ track.mediaSegments.push(segment);
840
+ };
841
+ const createDashRangedSegment = (url, sequenceNumber, range) => {
842
+ const segment = {
843
+ sequenceNumber,
844
+ duration: 0,
845
+ url,
846
+ encryption: null
847
+ };
848
+ if (range) {
849
+ const [start, expect] = parseDashRange(range);
850
+ segment.startRange = start;
851
+ segment.expectLength = expect;
852
+ }
853
+ return segment;
854
+ };
855
+ const getSegmentTimelineEntries = (timeline, timescale, limit) => {
856
+ const entries = [];
857
+ let currentTime = 0;
858
+ for (const entry of getDirectDashChildren(timeline, "S")) {
859
+ const duration = Number(entry.getAttribute("d"));
860
+ if (!Number.isFinite(duration)) continue;
861
+ const startTime = entry.getAttribute("t");
862
+ if (startTime) currentTime = Number(startTime);
863
+ let repeatCount = Number(entry.getAttribute("r"));
864
+ if (!Number.isFinite(repeatCount)) repeatCount = 0;
865
+ const remaining = limit - entries.length;
866
+ if (remaining <= 0) break;
867
+ const totalEntries = repeatCount < 0 ? remaining : Math.min(repeatCount + 1, remaining);
868
+ for (let i = 0; i < totalEntries; i++) {
869
+ entries.push({
870
+ duration: duration / timescale,
871
+ timestamp: currentTime / timescale
872
+ });
873
+ currentTime += duration;
874
+ }
875
+ }
876
+ return entries;
877
+ };
878
+ const getRoleType = (roleValue) => {
879
+ const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
880
+ return ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
881
+ };
882
+ const createDashTrack = (params) => {
883
+ const { adaptationSet, contentType, frameRate, isLive, mimeType, period, representation, timeShiftBufferDepth } = params;
884
+ const bitrate = params.bitrate;
885
+ const descriptor = createDashTrackDescriptor({
886
+ codecs: representation.getAttribute("codecs") || adaptationSet.getAttribute("codecs"),
887
+ contentType,
888
+ mimeType
889
+ });
890
+ const track = {
891
+ type: descriptor.type,
892
+ codec: descriptor.codec,
893
+ codecString: descriptor.codecString,
894
+ peakBitrate: bitrate,
895
+ averageBitrate: bitrate,
896
+ name: null,
897
+ default: false,
898
+ groupId: representation.getAttribute("id"),
899
+ periodId: period.getAttribute("id"),
900
+ extension: null,
901
+ isLive,
902
+ refreshIntervalMs: getDashRefreshIntervalMs(timeShiftBufferDepth),
903
+ initSegment: null,
904
+ mediaSegments: []
905
+ };
906
+ const roles = getDashTagAttrs("Role", representation, adaptationSet);
907
+ const supplementalProps = getDashTagAttrs("SupplementalProperty", representation, adaptationSet);
908
+ const essentialProps = getDashTagAttrs("EssentialProperty", representation, adaptationSet);
909
+ const accessibilities = getDashTagAttrs("Accessibility", representation, adaptationSet);
910
+ const channelsString = getDashTagAttrs("AudioChannelConfiguration", representation, adaptationSet)[0]?.value;
911
+ const width = representation.getAttribute("width");
912
+ const height = representation.getAttribute("height");
913
+ track.languageCode = representation.getAttribute("lang") || adaptationSet.getAttribute("lang") || void 0;
914
+ const volumeAdjust = representation.getAttribute("volumeAdjust");
915
+ if (volumeAdjust) track.groupId = `${track.groupId}-${volumeAdjust}`;
916
+ const actualMimeType = representation.getAttribute("mimeType") || adaptationSet.getAttribute("mimeType");
917
+ if (actualMimeType) {
918
+ const mimeTypeSplit = actualMimeType.split("/");
919
+ track.extension = mimeTypeSplit.length === 2 ? mimeTypeSplit[1] : null;
920
+ }
921
+ if (track.type === "video") {
922
+ if (width) track.width = Number(width);
923
+ if (height) track.height = Number(height);
924
+ track.frameRate = frameRate ?? getDashFrameRate(representation);
925
+ if (track.codecString && supplementalProps && essentialProps) track.dynamicRange = parseDynamicRange(track.codecString, supplementalProps, essentialProps);
926
+ } else if (track.type === "audio") {
927
+ if (accessibilities) track.descriptive = checkIsDescriptive(accessibilities);
928
+ if (supplementalProps) track.joc = getDolbyDigitalPlusComplexityIndex(supplementalProps);
929
+ if (channelsString) track.numberOfChannels = parseChannels(channelsString);
930
+ } else {
931
+ if (roles) track.cc = checkIsClosedCaption(roles);
932
+ if (accessibilities) track.sdh = checkIsSdh(accessibilities);
933
+ }
934
+ const role = roles[0];
935
+ if (role?.value) track.role = getRoleType(role.value);
936
+ return track;
937
+ };
938
+ const normalizeDashTrackExtension = (track) => {
939
+ if (track.type === "subtitle" && track.extension === "mp4") track.extension = "m4s";
940
+ if (track.type !== "subtitle" && (track.extension == null || track.mediaSegments.length > 1)) track.extension = "m4s";
941
+ };
942
+ const getDashSegmentSourceChild = (tag, representation, adaptationSet, period) => getInheritedDashChild(tag, representation, adaptationSet, period);
943
+ const applySegmentBase = (params) => {
944
+ const { adaptationSet, period, representation, track, segmentBaseUrl } = params;
945
+ const segmentBaseElement = getDashSegmentSourceChild("SegmentBase", representation, adaptationSet, period);
946
+ if (!segmentBaseElement) return;
947
+ const initialization = getDirectDashChild(segmentBaseElement, "Initialization");
948
+ if (!initialization) return;
949
+ track.initSegment = createDashRangedSegment(combineUrl(segmentBaseUrl, initialization.getAttribute("sourceURL") || ""), -1, initialization.getAttribute("range"));
950
+ };
951
+ const applySegmentList = (params) => {
952
+ const { adaptationSet, period, representation, track, segmentBaseUrl } = params;
953
+ const segmentList = getDashSegmentSourceChild("SegmentList", representation, adaptationSet, period);
954
+ if (!segmentList) return;
955
+ const initialization = getDirectDashChild(segmentList, "Initialization");
956
+ if (initialization) track.initSegment = createDashRangedSegment(combineUrl(segmentBaseUrl, initialization.getAttribute("sourceURL") || ""), -1, initialization.getAttribute("range"));
957
+ const timescale = Number(segmentList.getAttribute("timescale") || "1");
958
+ const segmentUrls = getDirectDashChildren(segmentList, "SegmentURL");
959
+ const segmentTimeline = getDirectDashChild(segmentList, "SegmentTimeline");
960
+ const timelineEntries = segmentTimeline ? getSegmentTimelineEntries(segmentTimeline, timescale, segmentUrls.length) : null;
961
+ const fixedDurationAttr = segmentList.getAttribute("duration");
962
+ const fixedDuration = fixedDurationAttr ? Number(fixedDurationAttr) : NaN;
963
+ for (const [segmentIndex, segmentUrl] of segmentUrls.entries()) {
964
+ const media = segmentUrl.getAttribute("media");
965
+ if (!media) continue;
966
+ const duration = timelineEntries?.[segmentIndex]?.duration ?? fixedDuration / timescale;
967
+ if (!Number.isFinite(duration)) break;
968
+ const segment = createDashRangedSegment(combineUrl(segmentBaseUrl, media), segmentIndex, segmentUrl.getAttribute("mediaRange"));
969
+ segment.timestamp = timelineEntries?.[segmentIndex]?.timestamp;
970
+ segment.duration = duration;
971
+ appendDashSegment(track, segment);
972
+ }
973
+ };
974
+ const appendTemplatedSegment = (params) => {
975
+ const { currentTime, duration, index, mediaTemplate, track } = params;
976
+ const { segmentNumber, segBaseUrl, timescale, variables } = params;
977
+ variables[DASH_TEMPLATE_TIME] = String(currentTime);
978
+ variables[DASH_TEMPLATE_NUMBER] = String(segmentNumber);
979
+ appendDashSegment(track, {
980
+ sequenceNumber: index,
981
+ timestamp: currentTime / timescale,
982
+ duration: duration / timescale,
983
+ url: combineUrl(segBaseUrl, replaceDashVariables(mediaTemplate, variables)),
984
+ encryption: null
985
+ });
986
+ };
987
+ const applySegmentTimeline = (params) => {
988
+ const { mediaTemplate, periodDurationSeconds, track, segBaseUrl } = params;
989
+ const { startNumberString, timeline, timescaleString, variables } = params;
990
+ const timelineEntries = getDirectDashChildren(timeline, "S");
991
+ const timescale = Number(timescaleString);
992
+ let segmentNumber = Number(startNumberString);
993
+ let currentTime = 0;
994
+ let segmentIndex = 0;
995
+ for (const entry of timelineEntries) {
996
+ const startTime = entry.getAttribute("t");
997
+ if (startTime) currentTime = Number(startTime);
998
+ const duration = Number(entry.getAttribute("d"));
999
+ let repeatCount = Number(entry.getAttribute("r"));
1000
+ appendTemplatedSegment({
1001
+ currentTime,
1002
+ duration,
1003
+ index: segmentIndex++,
1004
+ mediaTemplate,
1005
+ track,
1006
+ segmentNumber: segmentNumber++,
1007
+ segBaseUrl,
1008
+ timescale,
1009
+ variables
1010
+ });
1011
+ if (repeatCount < 0) repeatCount = Math.ceil(periodDurationSeconds * timescale / duration) - 1;
1012
+ for (let i = 0; i < repeatCount; i++) {
1013
+ currentTime += duration;
1014
+ appendTemplatedSegment({
1015
+ currentTime,
1016
+ duration,
1017
+ index: segmentIndex++,
1018
+ mediaTemplate,
1019
+ track,
1020
+ segmentNumber: segmentNumber++,
1021
+ segBaseUrl,
1022
+ timescale,
1023
+ variables
1024
+ });
1025
+ }
1026
+ currentTime += duration;
1027
+ }
1028
+ };
1029
+ const applyFixedDurationTemplate = (params) => {
1030
+ const { availabilityStartTime, durationString, isLive, mediaTemplate, periodDurationSeconds } = params;
1031
+ const { presentationTimeOffset, segBaseUrl, startNumberString, timeShiftBufferDepth } = params;
1032
+ const { timescaleString, track, variables } = params;
1033
+ const timescale = Number(timescaleString);
1034
+ const duration = Number(durationString);
1035
+ let startNumber = Number(startNumberString);
1036
+ let totalNumber = Math.ceil(periodDurationSeconds * timescale / duration);
1037
+ if (totalNumber === 0 && isLive) {
1038
+ if (!availabilityStartTime) throw new Error("Invalid live MPD: availabilityStartTime is required.");
1039
+ const now = Date.now();
1040
+ const availableTime = new Date(availabilityStartTime);
1041
+ const offsetMs = Number(presentationTimeOffset) / 1e3;
1042
+ availableTime.setUTCMilliseconds(availableTime.getUTCMilliseconds() + offsetMs);
1043
+ const elapsedSeconds = (now - availableTime.getTime()) / 1e3;
1044
+ const updateWindowSeconds = Temporal.Duration.from(timeShiftBufferDepth).total("seconds");
1045
+ startNumber += (elapsedSeconds - updateWindowSeconds) * timescale / duration;
1046
+ totalNumber = updateWindowSeconds * timescale / duration;
1047
+ }
1048
+ for (let number = startNumber, segmentIndex = 0; number < startNumber + totalNumber; number++) {
1049
+ variables[DASH_TEMPLATE_TIME] = String((number - startNumber) * duration);
1050
+ variables[DASH_TEMPLATE_NUMBER] = String(number);
1051
+ appendDashSegment(track, {
1052
+ sequenceNumber: isLive ? number : segmentIndex++,
1053
+ timestamp: (number - startNumber) * (duration / timescale),
1054
+ duration: duration / timescale,
1055
+ url: combineUrl(segBaseUrl, replaceDashVariables(mediaTemplate, variables)),
1056
+ encryption: null
1057
+ });
1058
+ }
1059
+ };
1060
+ const applySegmentTemplate = (params) => {
1061
+ const { adaptationSet, availabilityStartTime, bitrate, isLive, period } = params;
1062
+ const { periodDurationSeconds, representation, representationId, segBaseUrl, track, timeShiftBufferDepth } = params;
1063
+ const segmentTemplates = [
1064
+ getDirectDashChild(representation, "SegmentTemplate"),
1065
+ getDirectDashChild(adaptationSet, "SegmentTemplate"),
1066
+ getDirectDashChild(period, "SegmentTemplate")
1067
+ ].filter((template) => !!template);
1068
+ if (!segmentTemplates[0]) return;
1069
+ const getTemplateAttribute = (name) => {
1070
+ for (const template of segmentTemplates) {
1071
+ const value = template.getAttribute(name);
1072
+ if (value) return value;
1073
+ }
1074
+ return null;
1075
+ };
1076
+ const getTemplateTimeline = () => {
1077
+ for (const template of segmentTemplates) {
1078
+ const timeline = getDirectDashChild(template, "SegmentTimeline");
1079
+ if (timeline) return timeline;
1080
+ }
1081
+ };
1082
+ const variables = {
1083
+ [DASH_TEMPLATE_BANDWIDTH]: String(bitrate),
1084
+ [DASH_TEMPLATE_REPRESENTATION_ID]: representationId ?? ""
1085
+ };
1086
+ const presentationTimeOffset = getTemplateAttribute("presentationTimeOffset") || "0";
1087
+ const timescaleString = getTemplateAttribute("timescale") || "1";
1088
+ const durationString = getTemplateAttribute("duration");
1089
+ const startNumberString = getTemplateAttribute("startNumber") || "1";
1090
+ const initialization = getTemplateAttribute("initialization");
1091
+ if (initialization) track.initSegment = {
1092
+ sequenceNumber: -1,
1093
+ duration: 0,
1094
+ url: combineUrl(segBaseUrl, replaceDashVariables(initialization, variables)),
1095
+ encryption: null
1096
+ };
1097
+ const mediaTemplate = getTemplateAttribute("media");
1098
+ if (!mediaTemplate) return;
1099
+ const segmentTimeline = getTemplateTimeline();
1100
+ if (segmentTimeline) {
1101
+ applySegmentTimeline({
1102
+ mediaTemplate,
1103
+ periodDurationSeconds,
1104
+ track,
1105
+ segBaseUrl,
1106
+ startNumberString,
1107
+ timeline: segmentTimeline,
1108
+ timescaleString,
1109
+ variables
1110
+ });
1111
+ return;
1112
+ }
1113
+ if (!durationString) return;
1114
+ applyFixedDurationTemplate({
1115
+ availabilityStartTime,
1116
+ durationString,
1117
+ isLive,
1118
+ mediaTemplate,
1119
+ periodDurationSeconds,
1120
+ track,
1121
+ presentationTimeOffset,
1122
+ segBaseUrl,
1123
+ startNumberString,
1124
+ timeShiftBufferDepth,
1125
+ timescaleString,
1126
+ variables
1127
+ });
1128
+ };
1129
+ const ensureFallbackMediaSegment = (track, segBaseUrl, periodDurationSeconds) => {
1130
+ if (track.mediaSegments.length > 0) return;
1131
+ addWholeResourceSegment(track, segBaseUrl, periodDurationSeconds);
1132
+ };
1133
+ const cloneEncryption = (encryption) => encryption ? {
1134
+ method: encryption.method,
1135
+ key: encryption.key,
1136
+ iv: encryption.iv,
1137
+ drm: { ...encryption.drm }
1138
+ } : null;
1139
+ const applyContentProtection = (adaptationSet, representation, track) => {
1140
+ const representationProtections = getDirectDashChildren(representation, "ContentProtection");
1141
+ const adaptationSetProtections = getDirectDashChildren(adaptationSet, "ContentProtection");
1142
+ const contentProtections = representationProtections.length ? representationProtections : adaptationSetProtections;
1143
+ if (!contentProtections.length) return;
1144
+ const encryption = {
1145
+ method: ENCRYPT_METHODS.CENC,
1146
+ drm: {}
1147
+ };
1148
+ for (const contentProtection of contentProtections) {
1149
+ const schemeIdUri = contentProtection.getAttribute("schemeIdUri");
1150
+ const drmData = {
1151
+ keyId: contentProtection.getAttribute("cenc:default_KID") || void 0,
1152
+ pssh: getDirectDashChild(contentProtection, "cenc:pssh")?.textContent?.trim() || void 0
1153
+ };
1154
+ if (schemeIdUri?.includes(WIDEVINE_SYSTEM_ID)) encryption.drm.widevine = drmData;
1155
+ else if (schemeIdUri?.includes(PLAYREADY_SYSTEM_ID)) encryption.drm.playready = drmData;
1156
+ }
1157
+ if (track.initSegment) track.initSegment.encryption = cloneEncryption(encryption);
1158
+ for (const segment of track.mediaSegments) if (!segment.encryption) segment.encryption = cloneEncryption(encryption);
1159
+ };
1160
+ const mergeDashPeriodTrack = (tracks, track, isLive) => {
1161
+ const existingTrackIndex = tracks.findIndex((item) => item.type === track.type && item.periodId !== track.periodId && item.groupId === track.groupId && (item.type === "video" && track.type === "video" ? item.width === track.width && item.height === track.height : true));
1162
+ if (existingTrackIndex < 0) {
1163
+ tracks.push(track);
1164
+ return;
1165
+ }
1166
+ if (isLive) return;
1167
+ const existingTrack = tracks[existingTrackIndex];
1168
+ if (!existingTrack) return;
1169
+ const lastSegment = existingTrack.mediaSegments.at(-1);
1170
+ const incomingSegments = track.mediaSegments;
1171
+ const incomingLastSegment = incomingSegments.at(-1);
1172
+ if (!lastSegment || !incomingLastSegment) return;
1173
+ if (lastSegment.url !== incomingLastSegment.url) {
1174
+ const startIndex = (lastSegment.sequenceNumber ?? 0) + 1;
1175
+ for (const segment of incomingSegments) if (segment.sequenceNumber !== null) segment.sequenceNumber += startIndex;
1176
+ existingTrack.mediaSegments.push(...incomingSegments);
1177
+ return;
1178
+ }
1179
+ lastSegment.duration += incomingSegments.reduce((sum, segment) => sum + segment.duration, 0);
1180
+ };
1181
+ const linkDefaultDashGroups = (tracks) => {
1182
+ const audioList = tracks.filter((track) => track.type === "audio");
1183
+ const subtitleList = tracks.filter((track) => track.type === "subtitle");
1184
+ const videoList = tracks.filter((track) => track.type === "video");
1185
+ for (const video of videoList) {
1186
+ const audioGroupId = audioList.toSorted((a, b) => (b.peakBitrate || 0) - (a.peakBitrate || 0)).at(0)?.groupId;
1187
+ const subtitleGroupId = subtitleList.toSorted((a, b) => (b.peakBitrate || 0) - (a.peakBitrate || 0)).at(0)?.groupId;
1188
+ if (audioGroupId) video.audioGroupId = audioGroupId;
1189
+ if (subtitleGroupId) video.subtitleGroupId = subtitleGroupId;
1190
+ }
1191
+ };
1192
+ var DashDemuxer = class {
1193
+ input;
1194
+ metadataPromise = null;
1195
+ trackBackings = null;
1196
+ internalTracks = null;
1197
+ segmentedInputs = [];
1198
+ manifestUrl = "";
1199
+ originalUrl = "";
1200
+ headers;
1201
+ mpdUrl = "";
1202
+ baseUrl = "";
1203
+ constructor(input) {
1204
+ this.input = input;
1205
+ this.headers = getSourceHeaders(input.source);
1206
+ }
1207
+ readMetadata() {
1208
+ return this.metadataPromise ??= (async () => {
1209
+ const { text, url } = await loadDashManifest(this.input.source);
1210
+ this.manifestUrl = url;
1211
+ this.originalUrl = url;
1212
+ this.resetManifestUrls();
1213
+ const tracks = this.extractTracks(text.trim());
1214
+ const internalTracks = createInternalTracks(this, tracks);
1215
+ this.internalTracks = internalTracks;
1216
+ this.trackBackings = createTrackBackings(internalTracks);
1217
+ })();
1218
+ }
1219
+ async getTrackBackings() {
1220
+ await this.readMetadata();
1221
+ if (!this.trackBackings) throw new Error("DASH track metadata did not initialize correctly.");
1222
+ return this.trackBackings;
1223
+ }
1224
+ getSegmentedInputForTrack(track) {
1225
+ let segmentedInput = this.segmentedInputs.find((value) => value.internalTrack === track);
1226
+ if (segmentedInput) return segmentedInput;
1227
+ segmentedInput = new DashSegmentedInput(track);
1228
+ this.segmentedInputs.push(segmentedInput);
1229
+ return segmentedInput;
1230
+ }
1231
+ async refreshTrackSegments(track) {
1232
+ await this.readMetadata();
1233
+ if (!track.track.isLive) return;
1234
+ if (!this.manifestUrl.startsWith("http://") && !this.manifestUrl.startsWith("https://")) return;
1235
+ const tracks = this.internalTracks?.map((internalTrack) => internalTrack.track) ?? [];
1236
+ await this.refreshTracks(tracks);
1237
+ }
1238
+ async getMimeType() {
1239
+ return DASH.mimeType;
1240
+ }
1241
+ async getMetadataTags() {
1242
+ return {};
1243
+ }
1244
+ dispose() {
1245
+ this.segmentedInputs.length = 0;
1246
+ }
1247
+ extractTracks(rawText) {
1248
+ const manifest = this.parseManifest(rawText);
1249
+ const tracks = [];
1250
+ for (const period of getDirectDashChildren(manifest.mpdElement, "Period")) this.appendPeriodTracks(tracks, manifest, period);
1251
+ linkDefaultDashGroups(tracks);
1252
+ return tracks;
1253
+ }
1254
+ parseManifest(rawText) {
1255
+ const mpdContent = processDashContent(rawText);
1256
+ const mpdElement = new DOMParser().parseFromString(mpdContent, "text/xml").getElementsByTagName("MPD")[0];
1257
+ const manifest = {
1258
+ mpdElement,
1259
+ isLive: mpdElement.getAttribute("type") === "dynamic",
1260
+ availabilityStartTime: mpdElement.getAttribute("availabilityStartTime"),
1261
+ timeShiftBufferDepth: mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M",
1262
+ mediaPresentationDuration: mpdElement.getAttribute("mediaPresentationDuration")
1263
+ };
1264
+ const baseUrlElement = getDirectDashChild(mpdElement, "BaseURL");
1265
+ if (baseUrlElement?.textContent) {
1266
+ let baseUrl = baseUrlElement.textContent;
1267
+ if (baseUrl.includes("kkbox.com.tw/")) baseUrl = baseUrl.replace("//https:%2F%2F", "//");
1268
+ this.baseUrl = combineUrl(this.mpdUrl, baseUrl);
1269
+ }
1270
+ return manifest;
1271
+ }
1272
+ appendPeriodTracks(tracks, manifest, period) {
1273
+ const periodDurationSeconds = Temporal.Duration.from(period.getAttribute("duration") || manifest.mediaPresentationDuration || "PT0S").total("seconds");
1274
+ const periodBaseUrl = extendDashBaseUrl(period, this.baseUrl);
1275
+ for (const adaptationSet of getDirectDashChildren(period, "AdaptationSet")) this.appendAdaptationSetTracks({
1276
+ tracks,
1277
+ manifest,
1278
+ period,
1279
+ periodDurationSeconds,
1280
+ periodBaseUrl,
1281
+ adaptationSet
1282
+ });
1283
+ }
1284
+ appendAdaptationSetTracks(params) {
1285
+ const { tracks, manifest, period, periodDurationSeconds, periodBaseUrl, adaptationSet } = params;
1286
+ const adaptationSetBaseUrl = extendDashBaseUrl(adaptationSet, periodBaseUrl);
1287
+ const adaptationSetFrameRate = getDashFrameRate(adaptationSet);
1288
+ let contentType = adaptationSet.getAttribute("contentType");
1289
+ let mimeType = adaptationSet.getAttribute("mimeType");
1290
+ for (const representation of getDirectDashChildren(adaptationSet, "Representation")) {
1291
+ const segmentBaseUrl = extendDashBaseUrl(representation, adaptationSetBaseUrl);
1292
+ contentType ||= representation.getAttribute("contentType");
1293
+ mimeType ||= representation.getAttribute("mimeType");
1294
+ const bitrate = Number(representation.getAttribute("bandwidth") ?? "");
1295
+ const track = this.createTrack({
1296
+ adaptationSet,
1297
+ representation,
1298
+ period,
1299
+ manifest,
1300
+ bitrate,
1301
+ contentType,
1302
+ mimeType,
1303
+ frameRate: adaptationSetFrameRate
1304
+ });
1305
+ this.populateTrackSegments({
1306
+ adaptationSet,
1307
+ period,
1308
+ representation,
1309
+ track,
1310
+ manifest,
1311
+ segmentBaseUrl,
1312
+ periodDurationSeconds,
1313
+ bitrate
1314
+ });
1315
+ mergeDashPeriodTrack(tracks, track, manifest.isLive);
1316
+ }
1317
+ }
1318
+ createTrack(params) {
1319
+ const { adaptationSet, representation, period, manifest, bitrate, contentType, mimeType, frameRate } = params;
1320
+ return createDashTrack({
1321
+ adaptationSet,
1322
+ bitrate,
1323
+ contentType,
1324
+ frameRate,
1325
+ isLive: manifest.isLive,
1326
+ mimeType,
1327
+ period,
1328
+ representation,
1329
+ timeShiftBufferDepth: manifest.timeShiftBufferDepth
1330
+ });
1331
+ }
1332
+ populateTrackSegments(params) {
1333
+ const { adaptationSet, period, representation, track, manifest, segmentBaseUrl, periodDurationSeconds, bitrate } = params;
1334
+ applySegmentBase({
1335
+ adaptationSet,
1336
+ period,
1337
+ representation,
1338
+ track,
1339
+ segmentBaseUrl
1340
+ });
1341
+ applySegmentList({
1342
+ adaptationSet,
1343
+ period,
1344
+ representation,
1345
+ track,
1346
+ segmentBaseUrl
1347
+ });
1348
+ applySegmentTemplate({
1349
+ adaptationSet,
1350
+ availabilityStartTime: manifest.availabilityStartTime,
1351
+ bitrate,
1352
+ isLive: manifest.isLive,
1353
+ period,
1354
+ periodDurationSeconds,
1355
+ representationId: representation.getAttribute("id"),
1356
+ representation,
1357
+ segBaseUrl: segmentBaseUrl,
1358
+ track,
1359
+ timeShiftBufferDepth: manifest.timeShiftBufferDepth
1360
+ });
1361
+ ensureFallbackMediaSegment(track, segmentBaseUrl, periodDurationSeconds);
1362
+ normalizeDashTrackExtension(track);
1363
+ applyContentProtection(adaptationSet, representation, track);
1364
+ }
1365
+ findMatchingTrack(nextTracks, currentTrack) {
1366
+ let matchingTracks = nextTracks.filter((candidate) => getDashTrackMatchKey(candidate) === getDashTrackMatchKey(currentTrack));
1367
+ const currentInitSegmentUrl = currentTrack.initSegment?.url;
1368
+ if (!matchingTracks.length && currentInitSegmentUrl) matchingTracks = nextTracks.filter((candidate) => candidate.initSegment?.url === currentInitSegmentUrl);
1369
+ return matchingTracks[0];
1370
+ }
1371
+ async refreshTracks(tracks) {
1372
+ if (!tracks.length) return;
1373
+ const response = await this.fetchManifest(this.manifestUrl).catch(() => this.fetchManifest(this.originalUrl));
1374
+ const rawText = await response.text();
1375
+ this.manifestUrl = response.url;
1376
+ this.resetManifestUrls();
1377
+ const nextTracks = this.extractTracks(rawText);
1378
+ for (const track of tracks) {
1379
+ const nextTrack = this.findMatchingTrack(nextTracks, track);
1380
+ if (!nextTrack) continue;
1381
+ track.isLive = nextTrack.isLive;
1382
+ track.refreshIntervalMs = nextTrack.refreshIntervalMs;
1383
+ track.periodId = nextTrack.periodId;
1384
+ track.initSegment = nextTrack.initSegment;
1385
+ track.mediaSegments = nextTrack.mediaSegments;
1386
+ track.audioGroupId = nextTrack.audioGroupId;
1387
+ track.subtitleGroupId = nextTrack.subtitleGroupId;
1388
+ }
1389
+ }
1390
+ async fetchManifest(url) {
1391
+ const response = await fetch(url, { headers: this.headers });
1392
+ if (!response.ok) throw new Error(`Failed to fetch DASH manifest: ${response.status} ${response.statusText} (${response.url})`);
1393
+ return response;
1394
+ }
1395
+ resetManifestUrls() {
1396
+ this.mpdUrl = this.manifestUrl;
1397
+ this.baseUrl = this.mpdUrl;
1398
+ }
1399
+ };
1400
+ var DashTrackBackingBase = class {
1401
+ internalTrack;
1402
+ hydratedTrackPromise = null;
1403
+ constructor(internalTrack) {
1404
+ this.internalTrack = internalTrack;
1405
+ }
1406
+ hydrate() {
1407
+ return this.hydratedTrackPromise ??= this.getSegmentedInput().getFirstTrack();
1408
+ }
1409
+ delegate(fn) {
1410
+ if (this.hydratedTrackPromise) return this.hydratedTrackPromise.then(fn);
1411
+ return this.hydrate().then(fn);
1412
+ }
1413
+ getId() {
1414
+ return this.internalTrack.id;
1415
+ }
1416
+ getNumber() {
1417
+ return getTrackNumber(this.internalTrack);
1418
+ }
1419
+ getCodec() {
1420
+ return this.internalTrack.track.codec ?? null;
1421
+ }
1422
+ getInternalCodecId() {
1423
+ return null;
1424
+ }
1425
+ getName() {
1426
+ return this.internalTrack.track.name;
1427
+ }
1428
+ getLanguageCode() {
1429
+ return this.internalTrack.track.languageCode ?? "und";
1430
+ }
1431
+ getTimeResolution() {
1432
+ return this.delegate((track) => track.getTimeResolution());
1433
+ }
1434
+ isRelativeToUnixEpoch() {
1435
+ return false;
1436
+ }
1437
+ getDisposition() {
1438
+ return getDisposition(this.internalTrack.track);
1439
+ }
1440
+ getPairingMask() {
1441
+ return this.internalTrack.pairingMask;
1442
+ }
1443
+ getBitrate() {
1444
+ return this.internalTrack.track.peakBitrate;
1445
+ }
1446
+ getAverageBitrate() {
1447
+ return this.internalTrack.track.averageBitrate;
1448
+ }
1449
+ async getDurationFromMetadata(_options) {
1450
+ return this.internalTrack.track.mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
1451
+ }
1452
+ async getLiveRefreshInterval() {
1453
+ if (!this.internalTrack.track.isLive) return null;
1454
+ return this.internalTrack.track.refreshIntervalMs / 1e3;
1455
+ }
1456
+ getHasOnlyKeyPackets() {
1457
+ return false;
1458
+ }
1459
+ async getDecoderConfig() {
1460
+ return this.getSegmentedInput().getDecoderConfig();
1461
+ }
1462
+ getMetadataCodecParameterString() {
1463
+ return this.internalTrack.track.codecString;
1464
+ }
1465
+ async getFirstPacket(options) {
1466
+ return this.getSegmentedInput().getFirstPacket(options);
1467
+ }
1468
+ async getPacket(timestamp, options) {
1469
+ return this.getSegmentedInput().getPacket(timestamp, options);
1470
+ }
1471
+ async getNextPacket(packet, options) {
1472
+ return this.getSegmentedInput().getNextPacket(packet, options);
1473
+ }
1474
+ async getKeyPacket(timestamp, options) {
1475
+ return this.getSegmentedInput().getKeyPacket(timestamp, options);
1476
+ }
1477
+ async getNextKeyPacket(packet, options) {
1478
+ return this.getSegmentedInput().getNextKeyPacket(packet, options);
1479
+ }
1480
+ getSegmentedInput() {
1481
+ return this.internalTrack.demuxer.getSegmentedInputForTrack(this.internalTrack);
1482
+ }
1483
+ async getSegments() {
1484
+ const segmentedInput = this.getSegmentedInput();
1485
+ await segmentedInput.runUpdateSegments();
1486
+ return segmentedInput.segments;
1487
+ }
1488
+ };
1489
+ var DashInputVideoTrackBacking = class extends DashTrackBackingBase {
1490
+ internalTrack;
1491
+ constructor(internalTrack) {
1492
+ super(internalTrack);
1493
+ this.internalTrack = internalTrack;
1494
+ }
1495
+ getType() {
1496
+ return "video";
1497
+ }
1498
+ getCodec() {
1499
+ return this.internalTrack.track.codec ?? null;
1500
+ }
1501
+ getCodedWidth() {
1502
+ return this.internalTrack.info.width ?? this.delegate((track) => track.getCodedWidth());
1503
+ }
1504
+ getCodedHeight() {
1505
+ return this.internalTrack.info.height ?? this.delegate((track) => track.getCodedHeight());
1506
+ }
1507
+ getSquarePixelWidth() {
1508
+ return this.internalTrack.info.width ?? this.delegate((track) => track.getSquarePixelWidth());
1509
+ }
1510
+ getSquarePixelHeight() {
1511
+ return this.internalTrack.info.height ?? this.delegate((track) => track.getSquarePixelHeight());
1512
+ }
1513
+ getMetadataDisplayWidth() {
1514
+ return this.internalTrack.info.width;
1515
+ }
1516
+ getMetadataDisplayHeight() {
1517
+ return this.internalTrack.info.height;
1518
+ }
1519
+ getRotation() {
1520
+ return 0;
1521
+ }
1522
+ async getColorSpace() {
1523
+ return this.delegate((track) => track.getColorSpace());
1524
+ }
1525
+ async canBeTransparent() {
1526
+ return this.delegate((track) => track.canBeTransparent());
1527
+ }
1528
+ };
1529
+ var DashInputAudioTrackBacking = class extends DashTrackBackingBase {
1530
+ internalTrack;
1531
+ constructor(internalTrack) {
1532
+ super(internalTrack);
1533
+ this.internalTrack = internalTrack;
1534
+ }
1535
+ getType() {
1536
+ return "audio";
1537
+ }
1538
+ getCodec() {
1539
+ return this.internalTrack.track.codec ?? null;
1540
+ }
1541
+ getNumberOfChannels() {
1542
+ return this.internalTrack.info.numberOfChannels ?? this.delegate((track) => track.getNumberOfChannels());
1543
+ }
1544
+ getSampleRate() {
1545
+ return this.internalTrack.track.sampleRate ?? this.delegate((track) => track.getSampleRate());
1546
+ }
1547
+ };
1548
+ var DashInputSubtitleTrackBacking = class extends DashTrackBackingBase {
1549
+ internalTrack;
1550
+ constructor(internalTrack) {
1551
+ super(internalTrack);
1552
+ this.internalTrack = internalTrack;
1553
+ }
1554
+ getType() {
1555
+ return "subtitle";
1556
+ }
1557
+ };
1558
+ const createTrackBackings = (internalTracks) => internalTracks.map((internalTrack) => {
1559
+ const backing = internalTrack.info.type === "video" ? new DashInputVideoTrackBacking(internalTrack) : internalTrack.info.type === "audio" ? new DashInputAudioTrackBacking(internalTrack) : new DashInputSubtitleTrackBacking(internalTrack);
1560
+ internalTrack.backingTrack = backing;
1561
+ return backing;
1562
+ });
1563
+ var DashInputFormat = class extends InputFormat {
1564
+ get name() {
1565
+ return "Dynamic Adaptive Streaming over HTTP (DASH)";
1566
+ }
1567
+ get mimeType() {
1568
+ return DASH_MIME_TYPE;
1569
+ }
1570
+ async _canReadInput(input) {
1571
+ if (isLikelyDashPath(input.source)) return true;
1572
+ try {
1573
+ const { text } = await loadDashManifest(input.source);
1574
+ return isDashManifestText(text);
1575
+ } catch {
1576
+ return false;
1577
+ }
1578
+ }
1579
+ _createDemuxer(input) {
1580
+ return new DashDemuxer(input);
1581
+ }
1582
+ };
1583
+ const DASH = new DashInputFormat();
1584
+ const DASH_FORMATS = [
1585
+ DASH,
1586
+ MP4,
1587
+ QTFF,
1588
+ WEBM,
1589
+ MATROSKA,
1590
+ MP3,
1591
+ ADTS
1592
+ ];
1593
+ //#endregion
1594
+ //#region src/mediabunny-input.ts
1595
+ const requireSync = (value, getterName, asyncName) => {
1596
+ if (value instanceof Promise) throw new Error(`'${getterName}' is not available synchronously for this track. Use '${asyncName}()' instead.`);
1597
+ return value;
1598
+ };
1599
+ const getSegmentedInputForTrack = (track) => {
1600
+ const backing = track._backing;
1601
+ if ("getSegmentedInput" in backing && typeof backing.getSegmentedInput === "function") return backing.getSegmentedInput();
1602
+ const internalTrack = backing.internalTrack;
1603
+ return internalTrack.demuxer.getSegmentedInputForPath(internalTrack.fullPath);
1604
+ };
1605
+ const addSegmentAccess = (track) => new Proxy(track, { get(target, prop) {
1606
+ if (prop === "getSegmentedInput") return () => getSegmentedInputForTrack(target);
1607
+ if (prop === "getSegments") return async () => {
1608
+ const segmentedInput = getSegmentedInputForTrack(target);
1609
+ await segmentedInput.runUpdateSegments();
1610
+ return segmentedInput.segments;
1611
+ };
1612
+ const value = Reflect.get(target, prop, target);
1613
+ return typeof value === "function" ? value.bind(target) : value;
1614
+ } });
1615
+ var MediabunnyInputSubtitleTrack = class extends InputTrack {
1616
+ #backing;
1617
+ constructor(input, backing) {
1618
+ super();
1619
+ Object.assign(this, {
1620
+ input,
1621
+ _backing: backing
1622
+ });
1623
+ this.#backing = backing;
1624
+ }
1625
+ get type() {
1626
+ return "subtitle";
1627
+ }
1628
+ async getCodec() {
1629
+ return this.#backing.getCodec();
1630
+ }
1631
+ get codec() {
1632
+ return requireSync(this.#backing.getCodec(), "codec", "getCodec");
1633
+ }
1634
+ async getCodecParameterString() {
1635
+ return await this.#backing.getMetadataCodecParameterString?.() ?? null;
1636
+ }
1637
+ async canDecode() {
1638
+ return true;
1639
+ }
1640
+ async determinePacketType(_packet) {
1641
+ return null;
1642
+ }
1643
+ async hasOnlyKeyPackets() {
1644
+ return true;
1645
+ }
1646
+ };
1647
+ var SegmentedMediabunnyInput = class extends Input$1 {
1648
+ #trackCache = /* @__PURE__ */ new WeakMap();
1649
+ #subtitleTrackCache = /* @__PURE__ */ new WeakMap();
1650
+ _wrapBackingAsTrack(backing) {
1651
+ const track = backing.getType?.() === "subtitle" ? this.#wrapSubtitleBacking(backing) : super._wrapBackingAsTrack(backing);
1652
+ const existing = this.#trackCache.get(track);
1653
+ if (existing) return existing;
1654
+ const wrapped = addSegmentAccess(track);
1655
+ this.#trackCache.set(track, wrapped);
1656
+ return wrapped;
1657
+ }
1658
+ #wrapSubtitleBacking(backing) {
1659
+ const existing = this.#subtitleTrackCache.get(backing);
1660
+ if (existing) return existing;
1661
+ const track = new MediabunnyInputSubtitleTrack(this, backing);
1662
+ this.#subtitleTrackCache.set(backing, track);
1663
+ return track;
1664
+ }
1665
+ async getTracks(query) {
1666
+ return await super.getTracks(query);
1667
+ }
1668
+ async getVideoTracks(query) {
1669
+ return await super.getVideoTracks(query);
1670
+ }
1671
+ async getAudioTracks(query) {
1672
+ return await super.getAudioTracks(query);
1673
+ }
1674
+ async getPrimaryVideoTrack(query) {
1675
+ return await super.getPrimaryVideoTrack(query);
1676
+ }
1677
+ async getPrimaryAudioTrack(query) {
1678
+ return await super.getPrimaryAudioTrack(query);
1679
+ }
1680
+ };
1681
+ //#endregion
1682
+ //#region src/index.ts
1683
+ var Input = class extends SegmentedMediabunnyInput {
1684
+ constructor(options) {
1685
+ super(options);
1686
+ }
1687
+ };
1688
+ const isInput = (value) => value instanceof Input;
1689
+ const getSegmentedInput = (track) => track.getSegmentedInput();
1690
+ const getSegments = async (track) => track.getSegments();
1691
+ //#endregion
1692
+ export { DASH, DASH_FORMATS, FilePathSource, HLS_FORMATS, Input, UrlSource, asc, desc, getSegmentedInput, getSegments, isInput, prefer };