dasha 4.0.0-alpha.5 → 4.0.0-alpha.6

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,242 @@ 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 getStreamInfoByCodecs = (codecs) => {
799
+ const videoCodec = tryParseVideoCodec(codecs);
800
+ if (videoCodec) return new VideoStreamInfo({ codec: videoCodec });
801
+ const audioCodec = tryParseAudioCodec(codecs);
802
+ if (audioCodec) return new AudioStreamInfo({ codec: audioCodec });
803
+ const subtitleCodec = tryParseSubtitleCodec(codecs);
804
+ if (subtitleCodec) return new SubtitleStreamInfo({ codec: subtitleCodec });
805
+ return new VideoStreamInfo();
806
+ };
807
+ const selectNonEmpty = (args) => {
808
+ for (const element of args.elements) {
809
+ const results = element.getElementsByTagName(args.tag);
810
+ if (results.length) return results;
811
+ }
812
+ };
813
+ const toSchemeValueArray = (elements) => {
814
+ const results = [];
815
+ if (!elements) return results;
816
+ for (const element of elements) {
817
+ const schemeIdUri = element.getAttribute("schemeIdUri");
818
+ const value = element.getAttribute("value");
819
+ results.push({
820
+ schemeIdUri,
821
+ value
822
+ });
823
+ }
824
+ return results;
825
+ };
826
+ const getTagAttrs = (tag, ...elements) => {
827
+ return pipe(selectNonEmpty, toSchemeValueArray)({
828
+ tag,
829
+ elements
830
+ });
831
+ };
487
832
  var DashExtractor = class DashExtractor {
488
833
  static #DEFAULT_METHOD = ENCRYPT_METHODS.CENC;
489
834
  get extractorType() {
@@ -513,11 +858,10 @@ var DashExtractor = class DashExtractor {
513
858
  return Number(d.toFixed(3));
514
859
  }
515
860
  async extractStreams(rawText) {
516
- const streamList = [];
861
+ const streamInfos = [];
517
862
  this.#mpdContent = rawText;
518
863
  const mpdElement = new __xmldom_xmldom.DOMParser().parseFromString(this.#mpdContent, "text/xml").getElementsByTagName("MPD")[0];
519
864
  const isLive = mpdElement.getAttribute("type") === "dynamic";
520
- mpdElement.getAttribute("maxSegmentDuration");
521
865
  const availabilityStartTime = mpdElement.getAttribute("availabilityStartTime");
522
866
  const timeShiftBufferDepth = mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M";
523
867
  const publishTime = mpdElement.getAttribute("publishTime");
@@ -545,49 +889,58 @@ var DashExtractor = class DashExtractor {
545
889
  for (const representation of representations) {
546
890
  segBaseUrl = this.#extendBaseUrl(representation, segBaseUrl);
547
891
  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;
892
+ const codecParameterString = representation.getAttribute("codecs") || adaptationSet.getAttribute("codecs");
893
+ const widthParameterString = representation.getAttribute("width");
894
+ const heightParameterString = representation.getAttribute("height");
895
+ const roles = getTagAttrs("Role", representation, adaptationSet);
896
+ const supplementalProps = getTagAttrs("SupplementalProperty", representation, adaptationSet);
897
+ const essentialProps = getTagAttrs("EssentialProperty", representation, adaptationSet);
898
+ const accessibilities = getTagAttrs("Accessibility", representation, adaptationSet);
899
+ const channelsString = getTagAttrs("AudioChannelConfiguration", adaptationSet, representation)[0]?.value;
900
+ const streamInfo = getStreamInfoByCodecs(codecParameterString) ?? new VideoStreamInfo();
901
+ const bitrate = Number(representation.getAttribute("bandwidth") ?? "");
902
+ streamInfo.languageCode = this.#filterLanguage(representation.getAttribute("lang") || adaptationSet.getAttribute("lang"));
903
+ if (streamInfo.type === "video") {
904
+ streamInfo.bitrate = bitrate;
905
+ streamInfo.width = Number(widthParameterString);
906
+ streamInfo.height = Number(heightParameterString);
907
+ streamInfo.frameRate = frameRate || this.#getFrameRate(representation);
908
+ if (supplementalProps && essentialProps) streamInfo.dynamicRange = parseDynamicRange(codecParameterString, supplementalProps, essentialProps);
909
+ } else if (streamInfo.type === "audio") {
910
+ streamInfo.bitrate = bitrate;
911
+ if (accessibilities) streamInfo.descriptive = checkIsDescriptive(accessibilities);
912
+ if (supplementalProps) streamInfo.joc = getDolbyDigitalPlusComplexityIndex(supplementalProps);
913
+ if (channelsString) {
914
+ streamInfo.numberOfChannels = parseChannels(channelsString);
915
+ streamInfo.channels = channelsString;
916
+ }
917
+ } else if (streamInfo.type === "subtitle") {
918
+ if (roles) streamInfo.cc = checkIsClosedCaption(roles);
919
+ if (accessibilities) streamInfo.sdh = checkIsSdh(accessibilities);
920
+ }
921
+ streamInfo.url = this.#mpdUrl;
922
+ streamInfo.originalUrl = this.#parserConfig.originalUrl;
923
+ streamInfo.playlist = new Playlist();
924
+ streamInfo.playlist.mediaParts.push(new MediaPart());
925
+ streamInfo.periodId = periodId;
926
+ streamInfo.groupId = representation.getAttribute("id");
927
+ streamInfo.codecs = representation.getAttribute("codecs") || adaptationSet.getAttribute("codecs");
567
928
  const volumeAdjust = representation.getAttribute("volumeAdjust");
568
- if (volumeAdjust) streamSpec.groupId = streamSpec.groupId + "-" + volumeAdjust;
929
+ if (volumeAdjust) streamInfo.groupId = streamInfo.groupId + "-" + volumeAdjust;
569
930
  const mType = representation.getAttribute("mimeType") || adaptationSet.getAttribute("mimeType");
570
931
  if (mType) {
571
932
  const mTypeSplit = mType.split("/");
572
- streamSpec.extension = mTypeSplit.length === 2 ? mTypeSplit[1] : null;
933
+ streamInfo.extension = mTypeSplit.length === 2 ? mTypeSplit[1] : null;
573
934
  }
574
- if (streamSpec.codecs === "stpp" || streamSpec.codecs === "wvtt") streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
575
- const role = representation.getElementsByTagName("Role")[0] || adaptationSet.getElementsByTagName("Role")[0];
935
+ const role = roles?.[0];
576
936
  if (role) {
577
- const roleValue = role.getAttribute("value");
937
+ const roleValue = role.value;
578
938
  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;
939
+ streamInfo.role = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
585
940
  }
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);
941
+ streamInfo.playlist.isLive = isLive;
942
+ if (timeShiftBufferDepth) streamInfo.playlist.refreshIntervalMs = temporal_polyfill.Temporal.Duration.from(timeShiftBufferDepth).total("milliseconds") / 2;
943
+ if (publishTime) streamInfo.publishTime = new Date(publishTime);
591
944
  const segmentBaseElement = representation.getElementsByTagName("SegmentBase")[0];
592
945
  if (segmentBaseElement) {
593
946
  const initialization = segmentBaseElement.getElementsByTagName("Initialization")[0];
@@ -598,7 +951,7 @@ var DashExtractor = class DashExtractor {
598
951
  mediaSegment.index = 0;
599
952
  mediaSegment.url = segBaseUrl;
600
953
  mediaSegment.duration = periodDurationSeconds;
601
- streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
954
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
602
955
  } else {
603
956
  const initUrl = combineUrl(segBaseUrl, sourceUrl);
604
957
  const initRange = initialization.getAttribute("range");
@@ -610,7 +963,7 @@ var DashExtractor = class DashExtractor {
610
963
  initSegment.startRange = start;
611
964
  initSegment.expectLength = expect;
612
965
  }
613
- streamSpec.playlist.mediaInit = initSegment;
966
+ streamInfo.playlist.mediaInit = initSegment;
614
967
  }
615
968
  }
616
969
  }
@@ -630,7 +983,7 @@ var DashExtractor = class DashExtractor {
630
983
  initSegment.startRange = start;
631
984
  initSegment.expectLength = expect;
632
985
  }
633
- streamSpec.playlist.mediaInit = initSegment;
986
+ streamInfo.playlist.mediaInit = initSegment;
634
987
  }
635
988
  const segmentUrls = segmentList.getElementsByTagName("SegmentURL");
636
989
  const timescaleStr = segmentList.getAttribute("timescale") || "1";
@@ -649,7 +1002,7 @@ var DashExtractor = class DashExtractor {
649
1002
  segment.startRange = start;
650
1003
  segment.expectLength = expect;
651
1004
  }
652
- streamSpec.playlist.mediaParts[0].mediaSegments.push(segment);
1005
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(segment);
653
1006
  }
654
1007
  }
655
1008
  const segmentTemplateElementsOuter = adaptationSet.getElementsByTagName("SegmentTemplate");
@@ -658,8 +1011,8 @@ var DashExtractor = class DashExtractor {
658
1011
  const segmentTemplate = segmentTemplateElements[0] || segmentTemplateElementsOuter[0];
659
1012
  const segmentTemplateOuter = segmentTemplateElementsOuter[0] || segmentTemplateElements[0];
660
1013
  const varDic = {};
661
- varDic[DASH_TAGS.TemplateRepresentationID] = streamSpec.groupId;
662
- varDic[DASH_TAGS.TemplateBandwidth] = bandwidth;
1014
+ varDic[DASH_TAGS.TemplateRepresentationID] = streamInfo.groupId;
1015
+ varDic[DASH_TAGS.TemplateBandwidth] = bitrate;
663
1016
  const presentationTimeOffsetStr = segmentTemplate.getAttribute("presentationTimeOffset") || segmentTemplateOuter.getAttribute("presentationTimeOffset") || "0";
664
1017
  const timescaleStr = segmentTemplate.getAttribute("timescale") || segmentTemplateOuter.getAttribute("timescale") || "1";
665
1018
  const durationStr = segmentTemplate.getAttribute("duration") || segmentTemplateOuter.getAttribute("duration");
@@ -671,7 +1024,7 @@ var DashExtractor = class DashExtractor {
671
1024
  const mediaSegment = new MediaSegment();
672
1025
  mediaSegment.index = -1;
673
1026
  mediaSegment.url = initUrl;
674
- streamSpec.playlist.mediaInit = mediaSegment;
1027
+ streamInfo.playlist.mediaInit = mediaSegment;
675
1028
  }
676
1029
  const mediaTemplate = segmentTemplate.getAttribute("media") || segmentTemplateOuter.getAttribute("media");
677
1030
  const segmentTimeline = segmentTemplate.getElementsByTagName("SegmentTimeline")[0];
@@ -698,7 +1051,7 @@ var DashExtractor = class DashExtractor {
698
1051
  if (hasTime) mediaSegment.nameFromVar = currentTime.toString();
699
1052
  mediaSegment.duration = _duration / timescale;
700
1053
  mediaSegment.index = segIndex++;
701
- streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1054
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
702
1055
  if (_repeatCount < 0) _repeatCount = Math.ceil(periodDurationSeconds * timescale / _duration) - 1;
703
1056
  for (let i = 0; i < _repeatCount; i++) {
704
1057
  currentTime += _duration;
@@ -711,7 +1064,7 @@ var DashExtractor = class DashExtractor {
711
1064
  _mediaSegment.index = segIndex++;
712
1065
  _mediaSegment.duration = _duration / timescale;
713
1066
  if (_hashTime) _mediaSegment.nameFromVar = currentTime.toString();
714
- streamSpec.playlist.mediaParts[0].mediaSegments.push(_mediaSegment);
1067
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(_mediaSegment);
715
1068
  }
716
1069
  currentTime += _duration;
717
1070
  }
@@ -740,16 +1093,16 @@ var DashExtractor = class DashExtractor {
740
1093
  if (hasNumber) mediaSegment.nameFromVar = index.toString();
741
1094
  mediaSegment.index = isLive ? index : segIndex;
742
1095
  mediaSegment.duration = duration / timescale;
743
- streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1096
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
744
1097
  }
745
1098
  }
746
1099
  }
747
- if (streamSpec.playlist.mediaParts[0].mediaSegments.length === 0) {
1100
+ if (streamInfo.playlist.mediaParts[0].mediaSegments.length === 0) {
748
1101
  const mediaSegment = new MediaSegment();
749
1102
  mediaSegment.index = 0;
750
1103
  mediaSegment.url = segBaseUrl;
751
1104
  mediaSegment.duration = periodDurationSeconds;
752
- streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1105
+ streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
753
1106
  }
754
1107
  const adaptationSetProtections = adaptationSet.getElementsByTagName("ContentProtection");
755
1108
  const representationProtections = representation.getElementsByTagName("ContentProtection");
@@ -769,69 +1122,69 @@ var DashExtractor = class DashExtractor {
769
1122
  else if (schemeIdUri?.includes(playreadySystemId)) encryptInfo.drm.playready = drmData;
770
1123
  else continue;
771
1124
  }
772
- if (streamSpec.playlist.mediaInit) streamSpec.playlist.mediaInit.encryptInfo = encryptInfo;
773
- const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
1125
+ if (streamInfo.playlist.mediaInit) streamInfo.playlist.mediaInit.encryptInfo = encryptInfo;
1126
+ const segments = streamInfo.playlist.mediaParts[0].mediaSegments;
774
1127
  for (const segment of segments) if (!segment.encryptInfo) segment.encryptInfo = encryptInfo;
775
1128
  }
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;
1129
+ 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));
1130
+ 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) {
1131
+ const startIndex = streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
1132
+ const segments = streamInfo.playlist.mediaParts[0].mediaSegments;
780
1133
  for (const segment of segments) segment.index += startIndex;
781
1134
  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);
1135
+ mediaPart.mediaSegments = streamInfos[_index].playlist.mediaParts[0].mediaSegments;
1136
+ streamInfos[_index].playlist.mediaParts.push(mediaPart);
1137
+ } else streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamInfo.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
785
1138
  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);
1139
+ if (streamInfo.type === "subtitle" && streamInfo.extension === "mp4") streamInfo.extension = "m4s";
1140
+ if (streamInfo.type !== "subtitle" && (streamInfo.extension == null || streamInfo.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamInfo.extension = "m4s";
1141
+ streamInfos.push(streamInfo);
789
1142
  }
790
1143
  segBaseUrl = representationsBaseUrl;
791
1144
  }
792
1145
  segBaseUrl = adaptationSetsBaseUrl;
793
1146
  }
794
1147
  }
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);
1148
+ const audioList = streamInfos.filter((stream) => stream.type === "audio");
1149
+ const subtitleList = streamInfos.filter((stream) => stream.type === "subtitle");
1150
+ const videoList = streamInfos.filter((stream) => stream.type === "video");
798
1151
  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;
1152
+ const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
1153
+ const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
801
1154
  if (audioGroupId) video.audioId = audioGroupId;
802
1155
  if (subtitleGroupId) video.subtitleId = subtitleGroupId;
803
1156
  }
804
- return streamList;
1157
+ return streamInfos;
805
1158
  }
806
1159
  #filterLanguage(v) {
807
1160
  if (!v) return;
808
1161
  return v;
809
1162
  }
810
- async refreshPlayList(streamSpecs) {
811
- if (!streamSpecs.length) return;
1163
+ async refreshPlayList(streamInfos) {
1164
+ if (!streamInfos.length) return;
812
1165
  const response = await fetch(this.#parserConfig.url, this.#parserConfig.headers).catch(() => fetch(this.#parserConfig.originalUrl, this.#parserConfig.headers));
813
1166
  const rawText = await response.text();
814
1167
  const url = response.url;
815
1168
  this.#parserConfig.url = url;
816
1169
  this.#setInitUrl();
817
1170
  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;
1171
+ for (const streamInfo of streamInfos) {
1172
+ let results = newStreams.filter((n) => n.toShortString() === streamInfo.toShortString());
1173
+ if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamInfo.playlist?.mediaInit?.url);
1174
+ if (results.length) streamInfo.playlist.mediaParts = results.at(0).playlist.mediaParts;
822
1175
  }
823
- await this.#processUrl(streamSpecs);
1176
+ await this.#processUrl(streamInfos);
824
1177
  }
825
- async #processUrl(streamSpecs) {
826
- for (const spec of streamSpecs) {
1178
+ async #processUrl(streamInfos) {
1179
+ for (const spec of streamInfos) {
827
1180
  const playlist = spec.playlist;
828
1181
  if (!playlist) continue;
829
1182
  if (playlist.mediaInit) playlist.mediaInit.url = this.preProcessUrl(playlist.mediaInit.url);
830
1183
  for (const part of playlist.mediaParts) for (const segment of part.mediaSegments) segment.url = this.preProcessUrl(segment.url);
831
1184
  }
832
1185
  }
833
- async fetchPlayList(streamSpecs) {
834
- this.#processUrl(streamSpecs);
1186
+ async fetchPlayList(streamInfos) {
1187
+ this.#processUrl(streamInfos);
835
1188
  }
836
1189
  preProcessUrl(url) {
837
1190
  for (const processor of this.#parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.#parserConfig)) url = processor.process(url, this.#parserConfig);
@@ -890,62 +1243,75 @@ var HlsExtractor = class {
890
1243
  }
891
1244
  async #parseMasterList() {
892
1245
  this.#masterM3u8Flag = true;
893
- const streams = [];
1246
+ const streamInfos = [];
894
1247
  let expectPlaylist = false;
895
- let streamSpec = new StreamSpec();
1248
+ let streamInfo = new VideoStreamInfo();
896
1249
  const lines = this.#m3u8Content.split("\n");
897
1250
  for (const line of lines) {
898
1251
  if (!line.trim()) continue;
899
1252
  if (line.startsWith(HLS_TAGS.extXStreamInf)) {
900
- streamSpec = new StreamSpec();
901
- streamSpec.originalUrl = this.parserConfig.originalUrl;
1253
+ streamInfo = new VideoStreamInfo();
1254
+ streamInfo.originalUrl = this.parserConfig.originalUrl;
902
1255
  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");
1256
+ streamInfo.bitrate = Number(bandwidth || 0);
1257
+ streamInfo.codecs = getAttribute(line, "CODECS");
1258
+ const resolution = getAttribute(line, "RESOLUTION");
1259
+ const [widthString, heightString] = resolution.split("x");
1260
+ streamInfo.width = parseInt(widthString);
1261
+ streamInfo.height = parseInt(heightString);
1262
+ streamInfo.resolution = resolution;
906
1263
  const frameRate = getAttribute(line, "FRAME-RATE");
907
- if (frameRate) streamSpec.frameRate = Number(frameRate);
1264
+ if (frameRate) streamInfo.frameRate = Number(frameRate);
908
1265
  const audioId = getAttribute(line, "AUDIO");
909
- if (audioId) streamSpec.audioId = audioId;
1266
+ if (audioId) streamInfo.audioId = audioId;
910
1267
  const videoId = getAttribute(line, "VIDEO");
911
- if (videoId) streamSpec.videoId = videoId;
1268
+ if (videoId) streamInfo.videoId = videoId;
912
1269
  const subtitleId = getAttribute(line, "SUBTITLES");
913
- if (subtitleId) streamSpec.subtitleId = subtitleId;
1270
+ if (subtitleId) streamInfo.subtitleId = subtitleId;
914
1271
  const videoRange = getAttribute(line, "VIDEO-RANGE");
915
- if (videoRange) streamSpec.videoRange = videoRange;
916
- if (streamSpec.codecs && streamSpec.audioId) streamSpec.codecs = streamSpec.codecs.split(",")[0];
1272
+ if (videoRange) streamInfo.videoRange = videoRange;
1273
+ if (streamInfo.codecs && streamInfo.audioId) streamInfo.codecs = streamInfo.codecs.split(",")[0];
917
1274
  expectPlaylist = true;
918
1275
  } 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;
1276
+ streamInfo = new VideoStreamInfo();
1277
+ const type = getAttribute(line, "TYPE");
1278
+ if (type === "VIDEO") streamInfo = new VideoStreamInfo();
1279
+ if (type === "AUDIO") streamInfo = new AudioStreamInfo();
1280
+ if (type === "SUBTITLES") streamInfo = new SubtitleStreamInfo();
1281
+ if (type === "CLOSED-CAPTIONS") {
1282
+ streamInfo = new SubtitleStreamInfo();
1283
+ streamInfo.cc = true;
1284
+ continue;
1285
+ }
923
1286
  let url = getAttribute(line, "URI");
924
1287
  if (!url) continue;
925
1288
  url = combineUrl(this.#baseUrl, url);
926
- streamSpec.url = this.preProcessUrl(url);
1289
+ streamInfo.url = this.preProcessUrl(url);
927
1290
  const groupId = getAttribute(line, "GROUP-ID");
928
- if (groupId) streamSpec.groupId = groupId;
1291
+ if (groupId) streamInfo.groupId = groupId;
929
1292
  const language = getAttribute(line, "LANGUAGE");
930
- if (language) streamSpec.language = language;
1293
+ if (language) streamInfo.languageCode = language;
931
1294
  const name = getAttribute(line, "NAME");
932
- if (name) streamSpec.name = name;
1295
+ if (name) streamInfo.name = name;
933
1296
  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;
1297
+ if (defaultFlag) streamInfo.default = defaultFlag.toLowerCase() === "yes";
1298
+ const channelsString = getAttribute(line, "CHANNELS");
1299
+ if (channelsString) {
1300
+ streamInfo.channels = channelsString;
1301
+ if (streamInfo.type === "audio") streamInfo.numberOfChannels = parseFloat(channelsString);
1302
+ }
937
1303
  const characteristics = getAttribute(line, "CHARACTERISTICS");
938
- if (characteristics) streamSpec.characteristics = characteristics.split(",").at(-1)?.split(".").at(-1);
939
- streams.push(streamSpec);
1304
+ if (characteristics) streamInfo.characteristics = characteristics.split(",").at(-1)?.split(".").at(-1);
1305
+ streamInfos.push(streamInfo);
940
1306
  } else if (line.startsWith("#")) continue;
941
1307
  else if (expectPlaylist) {
942
1308
  const url = combineUrl(this.#baseUrl, line);
943
- streamSpec.url = this.preProcessUrl(url);
1309
+ streamInfo.url = this.preProcessUrl(url);
944
1310
  expectPlaylist = false;
945
- streams.push(streamSpec);
1311
+ streamInfos.push(streamInfo);
946
1312
  }
947
1313
  }
948
- return streams;
1314
+ return streamInfos;
949
1315
  }
950
1316
  async #parseList() {
951
1317
  let hasAd = false;
@@ -1069,7 +1435,7 @@ var HlsExtractor = class {
1069
1435
  this.preProcessContent();
1070
1436
  if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
1071
1437
  const playlist = await this.#parseList();
1072
- const streamSpec = new StreamSpec();
1438
+ const streamSpec = new VideoStreamInfo();
1073
1439
  streamSpec.url = this.parserConfig.url;
1074
1440
  streamSpec.playlist = playlist;
1075
1441
  streamSpec.extension = playlist.mediaInit ? "mp4" : "ts";
@@ -1115,7 +1481,7 @@ var HlsExtractor = class {
1115
1481
  const newPlaylist = await this.#parseList();
1116
1482
  if (list.playlist?.mediaInit) list.playlist.mediaParts = newPlaylist.mediaParts;
1117
1483
  else list.playlist = newPlaylist;
1118
- if (list.mediaType === MEDIA_TYPES.SUBTITLES) {
1484
+ if (list.type === "subtitle") {
1119
1485
  const a = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".ttml")));
1120
1486
  const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
1121
1487
  if (a) list.extension = "ttml";
@@ -1123,8 +1489,8 @@ var HlsExtractor = class {
1123
1489
  } else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
1124
1490
  }
1125
1491
  }
1126
- async refreshPlayList(streamSpecs) {
1127
- await this.fetchPlayList(streamSpecs);
1492
+ async refreshPlayList(streamInfos) {
1493
+ await this.fetchPlayList(streamInfos);
1128
1494
  }
1129
1495
  };
1130
1496
 
@@ -1184,15 +1550,18 @@ var StreamExtractor = class {
1184
1550
  async extractStreams() {
1185
1551
  return this.#extractor.extractStreams(this.#rawText);
1186
1552
  }
1187
- async fetchPlayList(streamSpecs) {
1188
- return this.#extractor.fetchPlayList(streamSpecs);
1553
+ async fetchPlayList(streamInfos) {
1554
+ return this.#extractor.fetchPlayList(streamInfos);
1189
1555
  }
1190
- async refreshPlayList(streamSpecs) {
1191
- return this.#extractor.refreshPlayList(streamSpecs);
1556
+ async refreshPlayList(streamInfos) {
1557
+ return this.#extractor.refreshPlayList(streamInfos);
1192
1558
  }
1193
1559
  };
1194
1560
 
1195
1561
  //#endregion
1562
+ exports.ALL_STREAM_TYPES = ALL_STREAM_TYPES;
1563
+ exports.AUDIO_CODECS = AUDIO_CODECS;
1564
+ exports.AudioStreamInfo = AudioStreamInfo;
1196
1565
  exports.DASH_TAGS = DASH_TAGS;
1197
1566
  exports.DashExtractor = DashExtractor;
1198
1567
  exports.DefaultDashContentProcessor = DefaultDashContentProcessor;
@@ -1204,12 +1573,17 @@ exports.EXTRACTOR_TYPES = EXTRACTOR_TYPES;
1204
1573
  exports.EncryptInfo = EncryptInfo;
1205
1574
  exports.HLS_TAGS = HLS_TAGS;
1206
1575
  exports.HlsExtractor = HlsExtractor;
1207
- exports.MEDIA_TYPES = MEDIA_TYPES;
1208
1576
  exports.MediaPart = MediaPart;
1209
1577
  exports.MediaSegment = MediaSegment;
1210
1578
  exports.ParserConfig = ParserConfig;
1579
+ exports.Playlist = Playlist;
1211
1580
  exports.ROLE_TYPE = ROLE_TYPE;
1581
+ exports.SUBTITLE_CODECS = SUBTITLE_CODECS;
1212
1582
  exports.StreamExtractor = StreamExtractor;
1213
- exports.StreamSpec = StreamSpec;
1583
+ exports.StreamInfo = StreamInfo;
1584
+ exports.SubtitleStreamInfo = SubtitleStreamInfo;
1585
+ exports.VIDEO_CODECS = VIDEO_CODECS;
1586
+ exports.VIDEO_DYNAMIC_RANGES = VIDEO_DYNAMIC_RANGES;
1587
+ exports.VideoStreamInfo = VideoStreamInfo;
1214
1588
  exports.getRange = getRange;
1215
1589
  exports.parseRange = parseRange;