dasha 4.0.0-alpha.5 → 4.0.0-alpha.7

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/dasha.cjs CHANGED
@@ -32,6 +32,65 @@ node_path = __toESM(node_path);
32
32
  let temporal_polyfill = require("temporal-polyfill");
33
33
  let __xmldom_xmldom = require("@xmldom/xmldom");
34
34
 
35
+ //#region lib/shared/codec.ts
36
+ /**
37
+ * List of known video codecs, ordered by encoding preference.
38
+ * @group Codecs
39
+ * @public
40
+ */
41
+ const VIDEO_CODECS = [
42
+ "avc",
43
+ "hevc",
44
+ "vp8",
45
+ "vp9",
46
+ "av1",
47
+ "vc1"
48
+ ];
49
+ /**
50
+ * List of known video dynamic ranges.
51
+ * @group Codecs
52
+ * @public
53
+ */
54
+ const VIDEO_DYNAMIC_RANGES = [
55
+ "sdr",
56
+ "hlg",
57
+ "hdr10",
58
+ "hdr10+",
59
+ "dv"
60
+ ];
61
+ /**
62
+ * List of known audio codecs, ordered by encoding preference.
63
+ * @group Codecs
64
+ * @public
65
+ */
66
+ const AUDIO_CODECS = [
67
+ "aac",
68
+ "opus",
69
+ "mp3",
70
+ "vorbis",
71
+ "flac",
72
+ "alac",
73
+ "ac3",
74
+ "eac3",
75
+ "dts"
76
+ ];
77
+ /**
78
+ * List of known subtitle codecs, ordered by encoding preference.
79
+ * @group Codecs
80
+ * @public
81
+ */
82
+ const SUBTITLE_CODECS = [
83
+ "srt",
84
+ "vtt",
85
+ "ttml",
86
+ "dfxp",
87
+ "ssa",
88
+ "ass",
89
+ "stpp",
90
+ "wvtt"
91
+ ];
92
+
93
+ //#endregion
35
94
  //#region lib/shared/encrypt-method.ts
36
95
  const ENCRYPT_METHODS = {
37
96
  NONE: "none",
@@ -116,99 +175,166 @@ var MediaSegment = class MediaSegment {
116
175
  };
117
176
 
118
177
  //#endregion
119
- //#region lib/shared/media-type.ts
120
- const MEDIA_TYPES = {
121
- VIDEO: "video",
122
- AUDIO: "audio",
123
- SUBTITLES: "subtitle",
124
- CLOSED_CAPTIONS: "closed-captions"
178
+ //#region lib/shared/playlist.ts
179
+ var Playlist = class {
180
+ url = "";
181
+ isLive = false;
182
+ refreshIntervalMs = 15e3;
183
+ get totalDuration() {
184
+ let result = 0;
185
+ for (const part of this.mediaParts) for (const segment of part.mediaSegments) result += segment.duration;
186
+ return result;
187
+ }
188
+ targetDuration;
189
+ mediaInit;
190
+ mediaParts = [];
191
+ };
192
+
193
+ //#endregion
194
+ //#region lib/shared/role-type.ts
195
+ const ROLE_TYPE = {
196
+ Subtitle: 0,
197
+ Main: 1,
198
+ Alternate: 2,
199
+ Supplementary: 3,
200
+ Commentary: 4,
201
+ Dub: 5,
202
+ Description: 6,
203
+ Sign: 7,
204
+ Metadata: 8,
205
+ ForcedSubtitle: 9
125
206
  };
126
207
 
127
208
  //#endregion
128
- //#region lib/shared/stream-spec.ts
129
- var StreamSpec = class {
130
- mediaType;
131
- groupId = null;
132
- language;
209
+ //#region lib/shared/stream-info.ts
210
+ /**
211
+ * List of all stream types.
212
+ * @group Miscellaneous
213
+ * @public
214
+ */
215
+ const ALL_STREAM_TYPES = [
216
+ "video",
217
+ "audio",
218
+ "subtitle"
219
+ ];
220
+ var StreamInfo = class {
221
+ codec;
222
+ languageCode;
223
+ bitrate;
133
224
  name;
225
+ url = "";
226
+ originalUrl = "";
227
+ playlist;
134
228
  default;
135
229
  skippedDuration;
136
- bandwidth;
137
- codecs = null;
138
- resolution;
139
- frameRate;
140
- channels = null;
141
- extension = null;
142
230
  role;
143
231
  videoRange;
144
232
  characteristics;
145
233
  publishTime;
234
+ groupId = null;
146
235
  audioId;
147
236
  videoId;
148
237
  subtitleId;
149
238
  periodId = null;
150
- url = "";
151
- originalUrl = "";
152
- playlist;
239
+ extension = null;
240
+ /**
241
+ * @deprecated Use `codec`
242
+ */
243
+ codecs = null;
244
+ /**
245
+ * @deprecated Use `numberOfChannels` in `AudioStreamInfo`
246
+ */
247
+ channels = null;
248
+ /**
249
+ * @deprecated Use `width` and `height` in `VideoStreamInfo`
250
+ */
251
+ resolution;
252
+ /**
253
+ * @deprecated Use `bitrate`
254
+ */
255
+ bandwidth;
153
256
  get segmentsCount() {
154
257
  return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
155
258
  }
259
+ };
260
+ var VideoStreamInfo = class extends StreamInfo {
261
+ codec;
262
+ width;
263
+ height;
264
+ frameRate;
265
+ dynamicRange;
266
+ dolbyVisionProfile;
267
+ get type() {
268
+ return "video";
269
+ }
270
+ constructor(info) {
271
+ super();
272
+ this.codec = info?.codec;
273
+ }
156
274
  toShortString() {
157
- let prefixStr = "";
158
- let returnStr = "";
159
- const encStr = "";
160
- const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
161
- const channels = this.channels ? `${this.channels}CH` : "";
162
- if (this.mediaType === MEDIA_TYPES.AUDIO) {
163
- prefixStr = `Aud ${encStr}`;
164
- returnStr = [
165
- this.groupId,
166
- bandwidth,
167
- this.name,
168
- this.codecs,
169
- this.language,
170
- channels,
171
- this.role
172
- ].filter(Boolean).join(" | ");
173
- } else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
174
- prefixStr = `Sub ${encStr}`;
175
- returnStr = [
176
- this.groupId,
177
- this.language,
178
- this.name,
179
- this.codecs,
180
- this.role
181
- ].filter(Boolean).join(" | ");
182
- } else {
183
- prefixStr = `Vid ${encStr}`;
184
- returnStr = [
185
- this.resolution,
186
- bandwidth,
187
- this.groupId,
188
- this.frameRate,
189
- this.codecs,
190
- this.videoRange,
191
- this.role
192
- ].filter(Boolean).join(" | ");
193
- }
194
- returnStr = `${prefixStr} | ${returnStr}`;
195
- return returnStr.trim();
275
+ const prefix = `Vid `;
276
+ const bitrate = this.bitrate ? `${this.bitrate / 1e3} Kbps` : "";
277
+ return `${prefix} | ${[
278
+ this.width ? `${this.width}x${this.height}` : "",
279
+ bitrate,
280
+ this.groupId,
281
+ this.frameRate,
282
+ this.codec ?? this.codecs,
283
+ this.videoRange,
284
+ this.role
285
+ ].filter(Boolean).join(" | ")}`.trim();
196
286
  }
197
287
  };
198
-
199
- //#endregion
200
- //#region lib/shared/role-type.ts
201
- const ROLE_TYPE = {
202
- Subtitle: 0,
203
- Main: 1,
204
- Alternate: 2,
205
- Supplementary: 3,
206
- Commentary: 4,
207
- Dub: 5,
208
- Description: 6,
209
- Sign: 7,
210
- Metadata: 8,
211
- ForcedSubtitle: 9
288
+ var AudioStreamInfo = class extends StreamInfo {
289
+ codec;
290
+ numberOfChannels;
291
+ sampleRate;
292
+ atmos;
293
+ descriptive;
294
+ joc;
295
+ get type() {
296
+ return "audio";
297
+ }
298
+ constructor(info) {
299
+ super();
300
+ this.codec = info?.codec;
301
+ }
302
+ toShortString() {
303
+ const prefix = `Aud `;
304
+ const bitrate = this.bitrate ? `${this.bitrate / 1e3} Kbps` : "";
305
+ const channels = this.numberOfChannels || this.channels ? `${this.numberOfChannels ?? this.channels}CH` : "";
306
+ return `${prefix} | ${[
307
+ this.groupId,
308
+ bitrate,
309
+ this.name,
310
+ this.codec ?? this.codecs,
311
+ this.languageCode,
312
+ channels,
313
+ this.role
314
+ ].filter(Boolean).join(" | ")}`.trim();
315
+ }
316
+ };
317
+ var SubtitleStreamInfo = class extends StreamInfo {
318
+ codec;
319
+ cc;
320
+ sdh;
321
+ forced;
322
+ get type() {
323
+ return "subtitle";
324
+ }
325
+ constructor(info) {
326
+ super();
327
+ this.codec = info?.codec;
328
+ }
329
+ toShortString() {
330
+ return `Sub | ${[
331
+ this.groupId,
332
+ this.languageCode,
333
+ this.name,
334
+ this.codecs,
335
+ this.role
336
+ ].filter(Boolean).join(" | ")}`.trim();
337
+ }
212
338
  };
213
339
 
214
340
  //#endregion
@@ -453,22 +579,7 @@ const distinctBy = (array, callbackfn) => {
453
579
  return true;
454
580
  });
455
581
  };
456
-
457
- //#endregion
458
- //#region lib/shared/playlist.ts
459
- var Playlist = class {
460
- url = "";
461
- isLive = false;
462
- refreshIntervalMs = 15e3;
463
- get totalDuration() {
464
- let result = 0;
465
- for (const part of this.mediaParts) for (const segment of part.mediaSegments) result += segment.duration;
466
- return result;
467
- }
468
- targetDuration;
469
- mediaInit;
470
- mediaParts = [];
471
- };
582
+ const parseMimes = (codecs) => codecs.toLowerCase().split(",").map((codec) => codec.trim().split(".")[0]);
472
583
 
473
584
  //#endregion
474
585
  //#region lib/dash/dash-utils.ts
@@ -482,8 +593,251 @@ const parseRange = (range) => {
482
593
  return [startRange, end - startRange + 1];
483
594
  };
484
595
 
596
+ //#endregion
597
+ //#region lib/dash/dash-video.ts
598
+ const PRIMARIES = {
599
+ Unspecified: 0,
600
+ BT_709: 1,
601
+ BT_601_625: 5,
602
+ BT_601_525: 6,
603
+ BT_2020_and_2100: 9,
604
+ SMPTE_ST_2113_and_EG_4321: 12
605
+ };
606
+ const TRANSFER = {
607
+ Unspecified: 0,
608
+ BT_709: 1,
609
+ BT_601: 6,
610
+ BT_2020: 14,
611
+ BT_2100: 15,
612
+ BT_2100_PQ: 16,
613
+ BT_2100_HLG: 18
614
+ };
615
+ const MATRIX = {
616
+ RGB: 0,
617
+ YCbCr_BT_709: 1,
618
+ YCbCr_BT_601_625: 5,
619
+ YCbCr_BT_601_525: 6,
620
+ YCbCr_BT_2020_and_2100: 9,
621
+ ICtCp_BT_2100: 14
622
+ };
623
+ const parseVideoCodecFromMime = (mime) => {
624
+ const target = mime.toLowerCase().trim().split(".")[0];
625
+ const avc = [
626
+ "avc1",
627
+ "avc2",
628
+ "avc3",
629
+ "dva1",
630
+ "dvav"
631
+ ];
632
+ const hevc = [
633
+ "hev1",
634
+ "hev2",
635
+ "hev3",
636
+ "hvc1",
637
+ "hvc2",
638
+ "hvc3",
639
+ "dvh1",
640
+ "dvhe",
641
+ "lhv1",
642
+ "lhe1"
643
+ ];
644
+ const vc1 = ["vc-1"];
645
+ const vp8 = ["vp08", "vp8"];
646
+ const vp9 = ["vp09", "vp9"];
647
+ const av1 = ["av01"];
648
+ if (avc.includes(target)) return "avc";
649
+ if (hevc.includes(target)) return "hevc";
650
+ if (vc1.includes(target)) return "vc1";
651
+ if (vp8.includes(target)) return "vp8";
652
+ if (vp9.includes(target)) return "vp9";
653
+ if (av1.includes(target)) return "av1";
654
+ throw new Error(`The MIME ${mime} is not supported as video codec`);
655
+ };
656
+ const parseDynamicRangeFromCicp = (primaries, transfer, matrix) => {
657
+ if (transfer == 5) transfer = TRANSFER.BT_601;
658
+ if (primaries == PRIMARIES.Unspecified && transfer == TRANSFER.Unspecified && matrix == MATRIX.RGB) return "sdr";
659
+ else if ([PRIMARIES.BT_601_625, PRIMARIES.BT_601_525].includes(primaries)) return "sdr";
660
+ else if (TRANSFER.BT_2100_PQ === transfer) return "hdr10";
661
+ else if (TRANSFER.BT_2100_HLG === transfer) return "hlg";
662
+ else return "sdr";
663
+ };
664
+ const parseVideoCodec = (codecs) => {
665
+ for (const codec of codecs.toLowerCase().split(",")) {
666
+ const mime = codec.trim().split(".")[0];
667
+ try {
668
+ return parseVideoCodecFromMime(mime);
669
+ } catch (e) {
670
+ continue;
671
+ }
672
+ }
673
+ throw new Error(`No MIME types matched any supported Video Codecs in ${codecs}`);
674
+ };
675
+ const tryParseVideoCodec = (codecs) => {
676
+ try {
677
+ return parseVideoCodec(codecs);
678
+ } catch (e) {
679
+ return;
680
+ }
681
+ };
682
+ const parseDynamicRange = (codecs, supplementalProps = [], essentialProps = []) => {
683
+ if ([
684
+ "dva1",
685
+ "dvav",
686
+ "dvhe",
687
+ "dvh1"
688
+ ].some((value) => codecs.startsWith(value))) return "dv";
689
+ const primariesScheme = "urn:mpeg:mpegB:cicp:ColourPrimaries";
690
+ const transferScheme = "urn:mpeg:mpegB:cicp:TransferCharacteristics";
691
+ const matrixScheme = "urn:mpeg:mpegB:cicp:MatrixCoefficients";
692
+ const allProps = [...essentialProps, ...supplementalProps];
693
+ const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).map((prop) => parseInt(prop.value));
694
+ 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));
695
+ };
696
+
697
+ //#endregion
698
+ //#region lib/dash/dash-subtitle.ts
699
+ const parseSubtitleCodecFromMime = (mime) => {
700
+ switch (mime.toLowerCase().trim().split(".")[0]) {
701
+ case "srt":
702
+ case "x-subrip": return "srt";
703
+ case "ssa": return "ssa";
704
+ case "ass": return "ass";
705
+ case "ttml": return "ttml";
706
+ case "vtt": return "vtt";
707
+ case "stpp": return "stpp";
708
+ case "wvtt": return "wvtt";
709
+ default: throw new Error(`The MIME ${mime} is not supported as subtitle codec`);
710
+ }
711
+ };
712
+ const parseSubtitleCodec = (codecs) => {
713
+ const mimes = parseMimes(codecs);
714
+ for (const mime of mimes) try {
715
+ return parseSubtitleCodecFromMime(mime);
716
+ } catch (e) {
717
+ continue;
718
+ }
719
+ throw new Error(`No MIME types matched any supported Subtitle Codecs in ${codecs}`);
720
+ };
721
+ const tryParseSubtitleCodec = (codecs) => {
722
+ try {
723
+ return parseSubtitleCodec(codecs);
724
+ } catch (e) {
725
+ return;
726
+ }
727
+ };
728
+ const checkIsClosedCaption = (roles = []) => {
729
+ for (const role of roles) if (role.schemeIdUri === "urn:mpeg:dash:role:2011" && role.value === "caption") return true;
730
+ return false;
731
+ };
732
+ const checkIsSdh = (accessibilities = []) => {
733
+ for (const accessibility of accessibilities) {
734
+ const { schemeIdUri, value } = accessibility;
735
+ if (schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && value === "2") return true;
736
+ }
737
+ return false;
738
+ };
739
+
740
+ //#endregion
741
+ //#region lib/dash/dash-audio.ts
742
+ const parseAudioCodecFromMime = (mime) => {
743
+ switch (mime.toLowerCase().trim().split(".")[0]) {
744
+ case "mp4a": return "aac";
745
+ case "ac-3": return "ac3";
746
+ case "ec-3": return "eac3";
747
+ case "opus": return "opus";
748
+ case "dtsc": return "dts";
749
+ case "alac": return "alac";
750
+ case "flac": return "flac";
751
+ default: throw new Error(`The MIME ${mime} is not supported as audio codec`);
752
+ }
753
+ };
754
+ const parseAudioCodec = (codecs) => {
755
+ const mimes = parseMimes(codecs);
756
+ for (const mime of mimes) try {
757
+ return parseAudioCodecFromMime(mime);
758
+ } catch (e) {
759
+ continue;
760
+ }
761
+ throw new Error(`No MIME types matched any supported Audio Codecs in ${codecs}`);
762
+ };
763
+ const tryParseAudioCodec = (codecs) => {
764
+ try {
765
+ return parseAudioCodec(codecs);
766
+ } catch (e) {
767
+ return;
768
+ }
769
+ };
770
+ const getDolbyDigitalPlusComplexityIndex = (supplementalProps = []) => {
771
+ const targetScheme = "tag:dolby.com,2018:dash:EC3_ExtensionComplexityIndex:2018";
772
+ for (const prop of supplementalProps) if (prop.schemeIdUri === targetScheme) return parseInt(prop.value);
773
+ };
774
+ const checkIsDescriptive = (accessibilities = []) => {
775
+ for (const accessibility of accessibilities) {
776
+ const { schemeIdUri, value } = accessibility;
777
+ if (schemeIdUri == "urn:mpeg:dash:role:2011" && value === "descriptive" || schemeIdUri == "urn:tva:metadata:cs:AudioPurposeCS:2007" && value === "1") return true;
778
+ }
779
+ return false;
780
+ };
781
+ const parseChannels = (channels) => {
782
+ const isDigit = (char) => char >= "0" && char <= "9";
783
+ if (typeof channels === "string") {
784
+ if (channels.toUpperCase() == "A000") return 2;
785
+ else if (channels.toUpperCase() == "F801") return 5.1;
786
+ else if (isDigit(channels.replace("ch", "").replace(".", "")[0])) return parseFloat(channels.replace("ch", ""));
787
+ throw new Error(`Unsupported audio channels value, '${channels}'`);
788
+ }
789
+ return parseFloat(channels);
790
+ };
791
+
792
+ //#endregion
793
+ //#region lib/shared/pipe.ts
794
+ const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
795
+
485
796
  //#endregion
486
797
  //#region lib/dash/dash-extractor.ts
798
+ const createMediaStreamInfo = (params) => {
799
+ const codecs = params.contentType === "text" && !params.mimeType?.includes("mp4") ? params.mimeType?.split("/")[1] : params.codecs;
800
+ if (!params.codecs && codecs) params.codecs = codecs;
801
+ if (params.codecs) {
802
+ const videoCodec = tryParseVideoCodec(params.codecs);
803
+ if (videoCodec) return new VideoStreamInfo({ codec: videoCodec });
804
+ const audioCodec = tryParseAudioCodec(params.codecs);
805
+ if (audioCodec) return new AudioStreamInfo({ codec: audioCodec });
806
+ const subtitleCodec = tryParseSubtitleCodec(params.codecs);
807
+ if (subtitleCodec) return new SubtitleStreamInfo({ codec: subtitleCodec });
808
+ } else {
809
+ const type = params.contentType || params.mimeType?.split("/")[0];
810
+ if (type === "video") return new VideoStreamInfo();
811
+ if (type === "audio") return new AudioStreamInfo();
812
+ if (type === "text") return new SubtitleStreamInfo();
813
+ }
814
+ throw new Error("Unable to determine the type of a track, cannot continue...");
815
+ };
816
+ const selectNonEmpty = (args) => {
817
+ for (const element of args.elements) {
818
+ const results = element.getElementsByTagName(args.tag);
819
+ if (results.length) return results;
820
+ }
821
+ };
822
+ const toSchemeValueArray = (elements) => {
823
+ const results = [];
824
+ if (!elements) return results;
825
+ for (const element of elements) {
826
+ const schemeIdUri = element.getAttribute("schemeIdUri");
827
+ const value = element.getAttribute("value");
828
+ results.push({
829
+ schemeIdUri,
830
+ value
831
+ });
832
+ }
833
+ return results;
834
+ };
835
+ const getTagAttrs = (tag, ...elements) => {
836
+ return pipe(selectNonEmpty, toSchemeValueArray)({
837
+ tag,
838
+ elements
839
+ });
840
+ };
487
841
  var DashExtractor = class DashExtractor {
488
842
  static #DEFAULT_METHOD = ENCRYPT_METHODS.CENC;
489
843
  get extractorType() {
@@ -513,11 +867,10 @@ var DashExtractor = class DashExtractor {
513
867
  return Number(d.toFixed(3));
514
868
  }
515
869
  async extractStreams(rawText) {
516
- const streamList = [];
870
+ const streamInfos = [];
517
871
  this.#mpdContent = rawText;
518
872
  const mpdElement = new __xmldom_xmldom.DOMParser().parseFromString(this.#mpdContent, "text/xml").getElementsByTagName("MPD")[0];
519
873
  const isLive = mpdElement.getAttribute("type") === "dynamic";
520
- mpdElement.getAttribute("maxSegmentDuration");
521
874
  const availabilityStartTime = mpdElement.getAttribute("availabilityStartTime");
522
875
  const timeShiftBufferDepth = mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M";
523
876
  const publishTime = mpdElement.getAttribute("publishTime");
@@ -539,55 +892,70 @@ var DashExtractor = class DashExtractor {
539
892
  for (const adaptationSet of adaptationSets) {
540
893
  segBaseUrl = this.#extendBaseUrl(adaptationSet, segBaseUrl);
541
894
  const representationsBaseUrl = segBaseUrl;
542
- let mimeType = adaptationSet.getAttribute("contentType") || adaptationSet.getAttribute("mimeType");
895
+ let contentType = adaptationSet.getAttribute("contentType");
896
+ let mimeType = adaptationSet.getAttribute("mimeType");
543
897
  const frameRate = this.#getFrameRate(adaptationSet);
544
898
  const representations = adaptationSet.getElementsByTagName("Representation");
545
899
  for (const representation of representations) {
546
900
  segBaseUrl = this.#extendBaseUrl(representation, segBaseUrl);
547
- if (!mimeType) mimeType = representation.getAttribute("contentType") || representation.getAttribute("mimeType") || "";
548
- const bandwidth = representation.getAttribute("bandwidth");
549
- const streamSpec = new StreamSpec();
550
- streamSpec.originalUrl = this.#parserConfig.originalUrl;
551
- streamSpec.periodId = periodId;
552
- streamSpec.playlist = new Playlist();
553
- streamSpec.playlist.mediaParts.push(new MediaPart());
554
- streamSpec.groupId = representation.getAttribute("id");
555
- streamSpec.bandwidth = Number(bandwidth || 0);
556
- streamSpec.codecs = representation.getAttribute("codecs") || adaptationSet.getAttribute("codecs");
557
- streamSpec.language = this.#filterLanguage(representation.getAttribute("lang") || adaptationSet.getAttribute("lang"));
558
- streamSpec.frameRate = frameRate || this.#getFrameRate(representation);
559
- const width = representation.getAttribute("width");
560
- const height = representation.getAttribute("height");
561
- streamSpec.resolution = width && height ? `${width}x${height}` : void 0;
562
- streamSpec.url = this.#mpdUrl;
563
- const mimeTypePart = mimeType.split("/")[0];
564
- if (mimeTypePart === "text") streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
565
- else if (mimeTypePart === "audio") streamSpec.mediaType = MEDIA_TYPES.AUDIO;
566
- else if (mimeTypePart === "video" || !!streamSpec.resolution) streamSpec.mediaType = MEDIA_TYPES.VIDEO;
901
+ if (!contentType) contentType = representation.getAttribute("contentType");
902
+ if (!mimeType) mimeType = representation.getAttribute("mimeType");
903
+ const codecs = representation.getAttribute("codecs") || adaptationSet.getAttribute("codecs");
904
+ const widthParameterString = representation.getAttribute("width");
905
+ const heightParameterString = representation.getAttribute("height");
906
+ const roles = getTagAttrs("Role", representation, adaptationSet);
907
+ const supplementalProps = getTagAttrs("SupplementalProperty", representation, adaptationSet);
908
+ const essentialProps = getTagAttrs("EssentialProperty", representation, adaptationSet);
909
+ const accessibilities = getTagAttrs("Accessibility", representation, adaptationSet);
910
+ const channelsString = getTagAttrs("AudioChannelConfiguration", adaptationSet, representation)[0]?.value;
911
+ const streamInfo = createMediaStreamInfo({
912
+ codecs,
913
+ contentType,
914
+ mimeType
915
+ });
916
+ const bitrate = Number(representation.getAttribute("bandwidth") ?? "");
917
+ streamInfo.languageCode = this.#filterLanguage(representation.getAttribute("lang") || adaptationSet.getAttribute("lang"));
918
+ if (streamInfo.type === "video") {
919
+ streamInfo.bitrate = bitrate;
920
+ streamInfo.width = Number(widthParameterString);
921
+ streamInfo.height = Number(heightParameterString);
922
+ streamInfo.frameRate = frameRate || this.#getFrameRate(representation);
923
+ if (supplementalProps && essentialProps) streamInfo.dynamicRange = parseDynamicRange(codecs, supplementalProps, essentialProps);
924
+ } else if (streamInfo.type === "audio") {
925
+ streamInfo.bitrate = bitrate;
926
+ if (accessibilities) streamInfo.descriptive = checkIsDescriptive(accessibilities);
927
+ if (supplementalProps) streamInfo.joc = getDolbyDigitalPlusComplexityIndex(supplementalProps);
928
+ if (channelsString) {
929
+ streamInfo.numberOfChannels = parseChannels(channelsString);
930
+ streamInfo.channels = channelsString;
931
+ }
932
+ } else if (streamInfo.type === "subtitle") {
933
+ if (roles) streamInfo.cc = checkIsClosedCaption(roles);
934
+ if (accessibilities) streamInfo.sdh = checkIsSdh(accessibilities);
935
+ }
936
+ streamInfo.url = this.#mpdUrl;
937
+ streamInfo.originalUrl = this.#parserConfig.originalUrl;
938
+ streamInfo.playlist = new Playlist();
939
+ streamInfo.playlist.mediaParts.push(new MediaPart());
940
+ streamInfo.periodId = periodId;
941
+ streamInfo.groupId = representation.getAttribute("id");
942
+ streamInfo.codecs = codecs;
567
943
  const volumeAdjust = representation.getAttribute("volumeAdjust");
568
- if (volumeAdjust) streamSpec.groupId = streamSpec.groupId + "-" + volumeAdjust;
944
+ if (volumeAdjust) streamInfo.groupId = streamInfo.groupId + "-" + volumeAdjust;
569
945
  const mType = representation.getAttribute("mimeType") || adaptationSet.getAttribute("mimeType");
570
946
  if (mType) {
571
947
  const mTypeSplit = mType.split("/");
572
- streamSpec.extension = mTypeSplit.length === 2 ? mTypeSplit[1] : null;
948
+ streamInfo.extension = mTypeSplit.length === 2 ? mTypeSplit[1] : null;
573
949
  }
574
- if (streamSpec.codecs === "stpp" || streamSpec.codecs === "wvtt") streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
575
- const role = representation.getElementsByTagName("Role")[0] || adaptationSet.getElementsByTagName("Role")[0];
950
+ const role = roles?.[0];
576
951
  if (role) {
577
- const roleValue = role.getAttribute("value");
952
+ const roleValue = role.value;
578
953
  const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
579
- const roleType = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
580
- streamSpec.role = roleType;
581
- if (roleType === ROLE_TYPE.Subtitle) {
582
- streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
583
- if (mType?.includes("ttml")) streamSpec.extension = "ttml";
584
- } else if (roleType === ROLE_TYPE.ForcedSubtitle) streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
954
+ streamInfo.role = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
585
955
  }
586
- streamSpec.playlist.isLive = isLive;
587
- if (timeShiftBufferDepth) streamSpec.playlist.refreshIntervalMs = temporal_polyfill.Temporal.Duration.from(timeShiftBufferDepth).total("milliseconds") / 2;
588
- const audioChannelConfiguration = adaptationSet.getElementsByTagName("AudioChannelConfiguration")[0] || representation.getElementsByTagName("AudioChannelConfiguration")[0];
589
- if (audioChannelConfiguration) streamSpec.channels = audioChannelConfiguration.getAttribute("value");
590
- if (publishTime) streamSpec.publishTime = new Date(publishTime);
956
+ streamInfo.playlist.isLive = isLive;
957
+ if (timeShiftBufferDepth) streamInfo.playlist.refreshIntervalMs = temporal_polyfill.Temporal.Duration.from(timeShiftBufferDepth).total("milliseconds") / 2;
958
+ if (publishTime) streamInfo.publishTime = new Date(publishTime);
591
959
  const segmentBaseElement = representation.getElementsByTagName("SegmentBase")[0];
592
960
  if (segmentBaseElement) {
593
961
  const initialization = segmentBaseElement.getElementsByTagName("Initialization")[0];
@@ -598,7 +966,7 @@ var DashExtractor = class DashExtractor {
598
966
  mediaSegment.index = 0;
599
967
  mediaSegment.url = segBaseUrl;
600
968
  mediaSegment.duration = periodDurationSeconds;
601
- streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
969
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
602
970
  } else {
603
971
  const initUrl = combineUrl(segBaseUrl, sourceUrl);
604
972
  const initRange = initialization.getAttribute("range");
@@ -610,7 +978,7 @@ var DashExtractor = class DashExtractor {
610
978
  initSegment.startRange = start;
611
979
  initSegment.expectLength = expect;
612
980
  }
613
- streamSpec.playlist.mediaInit = initSegment;
981
+ streamInfo.playlist.mediaInit = initSegment;
614
982
  }
615
983
  }
616
984
  }
@@ -630,7 +998,7 @@ var DashExtractor = class DashExtractor {
630
998
  initSegment.startRange = start;
631
999
  initSegment.expectLength = expect;
632
1000
  }
633
- streamSpec.playlist.mediaInit = initSegment;
1001
+ streamInfo.playlist.mediaInit = initSegment;
634
1002
  }
635
1003
  const segmentUrls = segmentList.getElementsByTagName("SegmentURL");
636
1004
  const timescaleStr = segmentList.getAttribute("timescale") || "1";
@@ -649,7 +1017,7 @@ var DashExtractor = class DashExtractor {
649
1017
  segment.startRange = start;
650
1018
  segment.expectLength = expect;
651
1019
  }
652
- streamSpec.playlist.mediaParts[0].mediaSegments.push(segment);
1020
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(segment);
653
1021
  }
654
1022
  }
655
1023
  const segmentTemplateElementsOuter = adaptationSet.getElementsByTagName("SegmentTemplate");
@@ -658,8 +1026,8 @@ var DashExtractor = class DashExtractor {
658
1026
  const segmentTemplate = segmentTemplateElements[0] || segmentTemplateElementsOuter[0];
659
1027
  const segmentTemplateOuter = segmentTemplateElementsOuter[0] || segmentTemplateElements[0];
660
1028
  const varDic = {};
661
- varDic[DASH_TAGS.TemplateRepresentationID] = streamSpec.groupId;
662
- varDic[DASH_TAGS.TemplateBandwidth] = bandwidth;
1029
+ varDic[DASH_TAGS.TemplateRepresentationID] = streamInfo.groupId;
1030
+ varDic[DASH_TAGS.TemplateBandwidth] = bitrate;
663
1031
  const presentationTimeOffsetStr = segmentTemplate.getAttribute("presentationTimeOffset") || segmentTemplateOuter.getAttribute("presentationTimeOffset") || "0";
664
1032
  const timescaleStr = segmentTemplate.getAttribute("timescale") || segmentTemplateOuter.getAttribute("timescale") || "1";
665
1033
  const durationStr = segmentTemplate.getAttribute("duration") || segmentTemplateOuter.getAttribute("duration");
@@ -671,7 +1039,7 @@ var DashExtractor = class DashExtractor {
671
1039
  const mediaSegment = new MediaSegment();
672
1040
  mediaSegment.index = -1;
673
1041
  mediaSegment.url = initUrl;
674
- streamSpec.playlist.mediaInit = mediaSegment;
1042
+ streamInfo.playlist.mediaInit = mediaSegment;
675
1043
  }
676
1044
  const mediaTemplate = segmentTemplate.getAttribute("media") || segmentTemplateOuter.getAttribute("media");
677
1045
  const segmentTimeline = segmentTemplate.getElementsByTagName("SegmentTimeline")[0];
@@ -698,7 +1066,7 @@ var DashExtractor = class DashExtractor {
698
1066
  if (hasTime) mediaSegment.nameFromVar = currentTime.toString();
699
1067
  mediaSegment.duration = _duration / timescale;
700
1068
  mediaSegment.index = segIndex++;
701
- streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1069
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
702
1070
  if (_repeatCount < 0) _repeatCount = Math.ceil(periodDurationSeconds * timescale / _duration) - 1;
703
1071
  for (let i = 0; i < _repeatCount; i++) {
704
1072
  currentTime += _duration;
@@ -711,7 +1079,7 @@ var DashExtractor = class DashExtractor {
711
1079
  _mediaSegment.index = segIndex++;
712
1080
  _mediaSegment.duration = _duration / timescale;
713
1081
  if (_hashTime) _mediaSegment.nameFromVar = currentTime.toString();
714
- streamSpec.playlist.mediaParts[0].mediaSegments.push(_mediaSegment);
1082
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(_mediaSegment);
715
1083
  }
716
1084
  currentTime += _duration;
717
1085
  }
@@ -740,16 +1108,16 @@ var DashExtractor = class DashExtractor {
740
1108
  if (hasNumber) mediaSegment.nameFromVar = index.toString();
741
1109
  mediaSegment.index = isLive ? index : segIndex;
742
1110
  mediaSegment.duration = duration / timescale;
743
- streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1111
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
744
1112
  }
745
1113
  }
746
1114
  }
747
- if (streamSpec.playlist.mediaParts[0].mediaSegments.length === 0) {
1115
+ if (streamInfo.playlist.mediaParts[0].mediaSegments.length === 0) {
748
1116
  const mediaSegment = new MediaSegment();
749
1117
  mediaSegment.index = 0;
750
1118
  mediaSegment.url = segBaseUrl;
751
1119
  mediaSegment.duration = periodDurationSeconds;
752
- streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1120
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
753
1121
  }
754
1122
  const adaptationSetProtections = adaptationSet.getElementsByTagName("ContentProtection");
755
1123
  const representationProtections = representation.getElementsByTagName("ContentProtection");
@@ -769,69 +1137,69 @@ var DashExtractor = class DashExtractor {
769
1137
  else if (schemeIdUri?.includes(playreadySystemId)) encryptInfo.drm.playready = drmData;
770
1138
  else continue;
771
1139
  }
772
- if (streamSpec.playlist.mediaInit) streamSpec.playlist.mediaInit.encryptInfo = encryptInfo;
773
- const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
1140
+ if (streamInfo.playlist.mediaInit) streamInfo.playlist.mediaInit.encryptInfo = encryptInfo;
1141
+ const segments = streamInfo.playlist.mediaParts[0].mediaSegments;
774
1142
  for (const segment of segments) if (!segment.encryptInfo) segment.encryptInfo = encryptInfo;
775
1143
  }
776
- const _index = streamList.findIndex((item) => item.periodId !== streamSpec.periodId && item.groupId === streamSpec.groupId && item.resolution === streamSpec.resolution && item.mediaType === streamSpec.mediaType);
777
- if (_index > -1) if (isLive) {} else if (streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).url !== streamSpec.playlist.mediaParts[0].mediaSegments.at(-1)?.url) {
778
- const startIndex = streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
779
- const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
1144
+ const _index = streamInfos.findIndex((item) => item.type === streamInfo.type && item.periodId !== streamInfo.periodId && item.groupId === streamInfo.groupId && (item.type === "video" && streamInfo.type === "video" ? item.width === streamInfo.width && item.height === streamInfo.height : true));
1145
+ if (_index > -1) if (isLive) {} else if (streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).url !== streamInfo.playlist.mediaParts[0].mediaSegments.at(-1)?.url) {
1146
+ const startIndex = streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
1147
+ const segments = streamInfo.playlist.mediaParts[0].mediaSegments;
780
1148
  for (const segment of segments) segment.index += startIndex;
781
1149
  const mediaPart = new MediaPart();
782
- mediaPart.mediaSegments = streamList[_index].playlist.mediaParts[0].mediaSegments;
783
- streamList[_index].playlist.mediaParts.push(mediaPart);
784
- } else streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamSpec.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
1150
+ mediaPart.mediaSegments = streamInfos[_index].playlist.mediaParts[0].mediaSegments;
1151
+ streamInfos[_index].playlist.mediaParts.push(mediaPart);
1152
+ } else streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamInfo.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
785
1153
  else {
786
- if (streamSpec.mediaType === MEDIA_TYPES.SUBTITLES && streamSpec.extension === "mp4") streamSpec.extension = "m4s";
787
- if (streamSpec.mediaType !== MEDIA_TYPES.SUBTITLES && (streamSpec.extension == null || streamSpec.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamSpec.extension = "m4s";
788
- streamList.push(streamSpec);
1154
+ if (streamInfo.type === "subtitle" && streamInfo.extension === "mp4") streamInfo.extension = "m4s";
1155
+ if (streamInfo.type !== "subtitle" && (streamInfo.extension == null || streamInfo.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamInfo.extension = "m4s";
1156
+ streamInfos.push(streamInfo);
789
1157
  }
790
1158
  segBaseUrl = representationsBaseUrl;
791
1159
  }
792
1160
  segBaseUrl = adaptationSetsBaseUrl;
793
1161
  }
794
1162
  }
795
- const audioList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.AUDIO);
796
- const subtitleList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.SUBTITLES);
797
- const videoList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.VIDEO);
1163
+ const audioList = streamInfos.filter((stream) => stream.type === "audio");
1164
+ const subtitleList = streamInfos.filter((stream) => stream.type === "subtitle");
1165
+ const videoList = streamInfos.filter((stream) => stream.type === "video");
798
1166
  for (const video of videoList) {
799
- const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
800
- const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
1167
+ const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
1168
+ const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
801
1169
  if (audioGroupId) video.audioId = audioGroupId;
802
1170
  if (subtitleGroupId) video.subtitleId = subtitleGroupId;
803
1171
  }
804
- return streamList;
1172
+ return streamInfos;
805
1173
  }
806
1174
  #filterLanguage(v) {
807
1175
  if (!v) return;
808
1176
  return v;
809
1177
  }
810
- async refreshPlayList(streamSpecs) {
811
- if (!streamSpecs.length) return;
1178
+ async refreshPlayList(streamInfos) {
1179
+ if (!streamInfos.length) return;
812
1180
  const response = await fetch(this.#parserConfig.url, this.#parserConfig.headers).catch(() => fetch(this.#parserConfig.originalUrl, this.#parserConfig.headers));
813
1181
  const rawText = await response.text();
814
1182
  const url = response.url;
815
1183
  this.#parserConfig.url = url;
816
1184
  this.#setInitUrl();
817
1185
  const newStreams = await this.extractStreams(rawText);
818
- for (const streamSpec of streamSpecs) {
819
- let results = newStreams.filter((n) => n.toShortString() === streamSpec.toShortString());
820
- if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamSpec.playlist?.mediaInit?.url);
821
- if (results.length) streamSpec.playlist.mediaParts = results.at(0).playlist.mediaParts;
1186
+ for (const streamInfo of streamInfos) {
1187
+ let results = newStreams.filter((n) => n.toShortString() === streamInfo.toShortString());
1188
+ if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamInfo.playlist?.mediaInit?.url);
1189
+ if (results.length) streamInfo.playlist.mediaParts = results.at(0).playlist.mediaParts;
822
1190
  }
823
- await this.#processUrl(streamSpecs);
1191
+ await this.#processUrl(streamInfos);
824
1192
  }
825
- async #processUrl(streamSpecs) {
826
- for (const spec of streamSpecs) {
1193
+ async #processUrl(streamInfos) {
1194
+ for (const spec of streamInfos) {
827
1195
  const playlist = spec.playlist;
828
1196
  if (!playlist) continue;
829
1197
  if (playlist.mediaInit) playlist.mediaInit.url = this.preProcessUrl(playlist.mediaInit.url);
830
1198
  for (const part of playlist.mediaParts) for (const segment of part.mediaSegments) segment.url = this.preProcessUrl(segment.url);
831
1199
  }
832
1200
  }
833
- async fetchPlayList(streamSpecs) {
834
- this.#processUrl(streamSpecs);
1201
+ async fetchPlayList(streamInfos) {
1202
+ this.#processUrl(streamInfos);
835
1203
  }
836
1204
  preProcessUrl(url) {
837
1205
  for (const processor of this.#parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.#parserConfig)) url = processor.process(url, this.#parserConfig);
@@ -890,62 +1258,75 @@ var HlsExtractor = class {
890
1258
  }
891
1259
  async #parseMasterList() {
892
1260
  this.#masterM3u8Flag = true;
893
- const streams = [];
1261
+ const streamInfos = [];
894
1262
  let expectPlaylist = false;
895
- let streamSpec = new StreamSpec();
1263
+ let streamInfo = new VideoStreamInfo();
896
1264
  const lines = this.#m3u8Content.split("\n");
897
1265
  for (const line of lines) {
898
1266
  if (!line.trim()) continue;
899
1267
  if (line.startsWith(HLS_TAGS.extXStreamInf)) {
900
- streamSpec = new StreamSpec();
901
- streamSpec.originalUrl = this.parserConfig.originalUrl;
1268
+ streamInfo = new VideoStreamInfo();
1269
+ streamInfo.originalUrl = this.parserConfig.originalUrl;
902
1270
  const bandwidth = getAttribute(line, "AVERAGE-BANDWIDTH") || getAttribute(line, "BANDWIDTH");
903
- streamSpec.bandwidth = Number(bandwidth || 0);
904
- streamSpec.codecs = getAttribute(line, "CODECS");
905
- streamSpec.resolution = getAttribute(line, "RESOLUTION");
1271
+ streamInfo.bitrate = Number(bandwidth || 0);
1272
+ streamInfo.codecs = getAttribute(line, "CODECS");
1273
+ const resolution = getAttribute(line, "RESOLUTION");
1274
+ const [widthString, heightString] = resolution.split("x");
1275
+ streamInfo.width = parseInt(widthString);
1276
+ streamInfo.height = parseInt(heightString);
1277
+ streamInfo.resolution = resolution;
906
1278
  const frameRate = getAttribute(line, "FRAME-RATE");
907
- if (frameRate) streamSpec.frameRate = Number(frameRate);
1279
+ if (frameRate) streamInfo.frameRate = Number(frameRate);
908
1280
  const audioId = getAttribute(line, "AUDIO");
909
- if (audioId) streamSpec.audioId = audioId;
1281
+ if (audioId) streamInfo.audioId = audioId;
910
1282
  const videoId = getAttribute(line, "VIDEO");
911
- if (videoId) streamSpec.videoId = videoId;
1283
+ if (videoId) streamInfo.videoId = videoId;
912
1284
  const subtitleId = getAttribute(line, "SUBTITLES");
913
- if (subtitleId) streamSpec.subtitleId = subtitleId;
1285
+ if (subtitleId) streamInfo.subtitleId = subtitleId;
914
1286
  const videoRange = getAttribute(line, "VIDEO-RANGE");
915
- if (videoRange) streamSpec.videoRange = videoRange;
916
- if (streamSpec.codecs && streamSpec.audioId) streamSpec.codecs = streamSpec.codecs.split(",")[0];
1287
+ if (videoRange) streamInfo.videoRange = videoRange;
1288
+ if (streamInfo.codecs && streamInfo.audioId) streamInfo.codecs = streamInfo.codecs.split(",")[0];
917
1289
  expectPlaylist = true;
918
1290
  } else if (line.startsWith(HLS_TAGS.extXMedia)) {
919
- streamSpec = new StreamSpec();
920
- const mediaType = MEDIA_TYPES[getAttribute(line, "TYPE").replace("-", "_")];
921
- if (mediaType) streamSpec.mediaType = mediaType;
922
- if (mediaType === MEDIA_TYPES.CLOSED_CAPTIONS) continue;
1291
+ streamInfo = new VideoStreamInfo();
1292
+ const type = getAttribute(line, "TYPE");
1293
+ if (type === "VIDEO") streamInfo = new VideoStreamInfo();
1294
+ if (type === "AUDIO") streamInfo = new AudioStreamInfo();
1295
+ if (type === "SUBTITLES") streamInfo = new SubtitleStreamInfo();
1296
+ if (type === "CLOSED-CAPTIONS") {
1297
+ streamInfo = new SubtitleStreamInfo();
1298
+ streamInfo.cc = true;
1299
+ continue;
1300
+ }
923
1301
  let url = getAttribute(line, "URI");
924
1302
  if (!url) continue;
925
1303
  url = combineUrl(this.#baseUrl, url);
926
- streamSpec.url = this.preProcessUrl(url);
1304
+ streamInfo.url = this.preProcessUrl(url);
927
1305
  const groupId = getAttribute(line, "GROUP-ID");
928
- if (groupId) streamSpec.groupId = groupId;
1306
+ if (groupId) streamInfo.groupId = groupId;
929
1307
  const language = getAttribute(line, "LANGUAGE");
930
- if (language) streamSpec.language = language;
1308
+ if (language) streamInfo.languageCode = language;
931
1309
  const name = getAttribute(line, "NAME");
932
- if (name) streamSpec.name = name;
1310
+ if (name) streamInfo.name = name;
933
1311
  const defaultFlag = getAttribute(line, "DEFAULT");
934
- if (defaultFlag) streamSpec.default = defaultFlag.toLowerCase() === "yes";
935
- const channels = getAttribute(line, "CHANNELS");
936
- if (channels) streamSpec.channels = channels;
1312
+ if (defaultFlag) streamInfo.default = defaultFlag.toLowerCase() === "yes";
1313
+ const channelsString = getAttribute(line, "CHANNELS");
1314
+ if (channelsString) {
1315
+ streamInfo.channels = channelsString;
1316
+ if (streamInfo.type === "audio") streamInfo.numberOfChannels = parseFloat(channelsString);
1317
+ }
937
1318
  const characteristics = getAttribute(line, "CHARACTERISTICS");
938
- if (characteristics) streamSpec.characteristics = characteristics.split(",").at(-1)?.split(".").at(-1);
939
- streams.push(streamSpec);
1319
+ if (characteristics) streamInfo.characteristics = characteristics.split(",").at(-1)?.split(".").at(-1);
1320
+ streamInfos.push(streamInfo);
940
1321
  } else if (line.startsWith("#")) continue;
941
1322
  else if (expectPlaylist) {
942
1323
  const url = combineUrl(this.#baseUrl, line);
943
- streamSpec.url = this.preProcessUrl(url);
1324
+ streamInfo.url = this.preProcessUrl(url);
944
1325
  expectPlaylist = false;
945
- streams.push(streamSpec);
1326
+ streamInfos.push(streamInfo);
946
1327
  }
947
1328
  }
948
- return streams;
1329
+ return streamInfos;
949
1330
  }
950
1331
  async #parseList() {
951
1332
  let hasAd = false;
@@ -1069,7 +1450,7 @@ var HlsExtractor = class {
1069
1450
  this.preProcessContent();
1070
1451
  if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
1071
1452
  const playlist = await this.#parseList();
1072
- const streamSpec = new StreamSpec();
1453
+ const streamSpec = new VideoStreamInfo();
1073
1454
  streamSpec.url = this.parserConfig.url;
1074
1455
  streamSpec.playlist = playlist;
1075
1456
  streamSpec.extension = playlist.mediaInit ? "mp4" : "ts";
@@ -1115,7 +1496,7 @@ var HlsExtractor = class {
1115
1496
  const newPlaylist = await this.#parseList();
1116
1497
  if (list.playlist?.mediaInit) list.playlist.mediaParts = newPlaylist.mediaParts;
1117
1498
  else list.playlist = newPlaylist;
1118
- if (list.mediaType === MEDIA_TYPES.SUBTITLES) {
1499
+ if (list.type === "subtitle") {
1119
1500
  const a = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".ttml")));
1120
1501
  const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
1121
1502
  if (a) list.extension = "ttml";
@@ -1123,8 +1504,8 @@ var HlsExtractor = class {
1123
1504
  } else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
1124
1505
  }
1125
1506
  }
1126
- async refreshPlayList(streamSpecs) {
1127
- await this.fetchPlayList(streamSpecs);
1507
+ async refreshPlayList(streamInfos) {
1508
+ await this.fetchPlayList(streamInfos);
1128
1509
  }
1129
1510
  };
1130
1511
 
@@ -1184,15 +1565,18 @@ var StreamExtractor = class {
1184
1565
  async extractStreams() {
1185
1566
  return this.#extractor.extractStreams(this.#rawText);
1186
1567
  }
1187
- async fetchPlayList(streamSpecs) {
1188
- return this.#extractor.fetchPlayList(streamSpecs);
1568
+ async fetchPlayList(streamInfos) {
1569
+ return this.#extractor.fetchPlayList(streamInfos);
1189
1570
  }
1190
- async refreshPlayList(streamSpecs) {
1191
- return this.#extractor.refreshPlayList(streamSpecs);
1571
+ async refreshPlayList(streamInfos) {
1572
+ return this.#extractor.refreshPlayList(streamInfos);
1192
1573
  }
1193
1574
  };
1194
1575
 
1195
1576
  //#endregion
1577
+ exports.ALL_STREAM_TYPES = ALL_STREAM_TYPES;
1578
+ exports.AUDIO_CODECS = AUDIO_CODECS;
1579
+ exports.AudioStreamInfo = AudioStreamInfo;
1196
1580
  exports.DASH_TAGS = DASH_TAGS;
1197
1581
  exports.DashExtractor = DashExtractor;
1198
1582
  exports.DefaultDashContentProcessor = DefaultDashContentProcessor;
@@ -1204,12 +1588,17 @@ exports.EXTRACTOR_TYPES = EXTRACTOR_TYPES;
1204
1588
  exports.EncryptInfo = EncryptInfo;
1205
1589
  exports.HLS_TAGS = HLS_TAGS;
1206
1590
  exports.HlsExtractor = HlsExtractor;
1207
- exports.MEDIA_TYPES = MEDIA_TYPES;
1208
1591
  exports.MediaPart = MediaPart;
1209
1592
  exports.MediaSegment = MediaSegment;
1210
1593
  exports.ParserConfig = ParserConfig;
1594
+ exports.Playlist = Playlist;
1211
1595
  exports.ROLE_TYPE = ROLE_TYPE;
1596
+ exports.SUBTITLE_CODECS = SUBTITLE_CODECS;
1212
1597
  exports.StreamExtractor = StreamExtractor;
1213
- exports.StreamSpec = StreamSpec;
1598
+ exports.StreamInfo = StreamInfo;
1599
+ exports.SubtitleStreamInfo = SubtitleStreamInfo;
1600
+ exports.VIDEO_CODECS = VIDEO_CODECS;
1601
+ exports.VIDEO_DYNAMIC_RANGES = VIDEO_DYNAMIC_RANGES;
1602
+ exports.VideoStreamInfo = VideoStreamInfo;
1214
1603
  exports.getRange = getRange;
1215
1604
  exports.parseRange = parseRange;