dasha 4.1.0 → 4.2.1

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/README.md CHANGED
@@ -42,6 +42,7 @@ async function saveVideo() {
42
42
  });
43
43
 
44
44
  const bestVideoTrack = videoTracks[0];
45
+ console.log('Dynamic range:', await bestVideoTrack.getDynamicRange()); // sdr
45
46
 
46
47
  const segments = await bestVideoTrack.getSegments();
47
48
 
@@ -93,6 +94,43 @@ async function saveDashVideo() {
93
94
  }
94
95
  ```
95
96
 
97
+ ### Adding external subtitle tracks
98
+
99
+ `addSubtitleTrack()` is useful when subtitle URLs are provided separately from the HLS/DASH manifest. Added subtitles become part of the same `Input`, so they can be queried, filtered and downloaded through the regular subtitle track API.
100
+
101
+ ```ts
102
+ import { DASH_FORMATS, Input, UrlSource } from 'dasha';
103
+
104
+ async function getEnglishSubtitles() {
105
+ const input = new Input({
106
+ source: new UrlSource('https://example.com/manifest.mpd'),
107
+ formats: DASH_FORMATS,
108
+ });
109
+
110
+ const primaryVideoTrack = await input.getPrimaryVideoTrack();
111
+ if (!primaryVideoTrack) {
112
+ throw new Error('No video tracks found');
113
+ }
114
+
115
+ input.addSubtitleTrack(new UrlSource('https://cdn.example.com/subtitles/en.vtt'), {
116
+ languageCode: 'en',
117
+ name: 'English',
118
+ pairWith: primaryVideoTrack,
119
+ });
120
+
121
+ const englishSubtitleTracks = await input.getSubtitleTracks({
122
+ filter: async (track) => (await track.getLanguageCode()) === 'en',
123
+ });
124
+
125
+ const englishSubtitleTrack = englishSubtitleTracks[0];
126
+ if (!englishSubtitleTrack) {
127
+ return [];
128
+ }
129
+
130
+ return await englishSubtitleTrack.getSegments();
131
+ }
132
+ ```
133
+
96
134
  ### Mediabunny with DASH support
97
135
 
98
136
  > Only reading is supported
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _$mediabunny from "mediabunny";
2
- import { AudioCodec, DurationMetadataRequestOptions, EncodedPacket, FilePathSource, HLS_FORMATS, Input as Input$1, InputAudioTrack as InputAudioTrack$1, InputFormat, InputOptions, InputTrack as InputTrack$2, InputTrackQuery, InputTrackQuery as InputTrackQuery$1, InputVideoTrack as InputVideoTrack$1, MediaCodec as MediaCodec$1, MetadataTags, PacketRetrievalOptions, Source, TrackDisposition, UrlSource, VideoCodec, asc, desc, prefer } from "mediabunny";
2
+ import { AudioCodec as AudioCodec$1, DurationMetadataRequestOptions, EncodedPacket, FilePathSource, HLS, HLS_FORMATS, Input as Input$1, InputAudioTrack as InputAudioTrack$1, InputFormat, InputOptions, InputTrack as InputTrack$2, InputTrackQuery, InputTrackQuery as InputTrackQuery$1, InputVideoTrack as InputVideoTrack$1, MP3, MP4, MaybePromise, MediaCodec as MediaCodec$1, MetadataTags, PacketRetrievalOptions, PathedSource, Source, SourceRef, SubtitleCodec as SubtitleCodec$1, TrackDisposition, UrlSource, VideoCodec as VideoCodec$1, asc, desc, prefer } from "mediabunny";
3
3
  import { Element } from "@xmldom/xmldom";
4
4
 
5
5
  //#region src/mediabunny.d.ts
@@ -46,60 +46,47 @@ type InputTrackWithBacking = InputTrack & {
46
46
  };
47
47
  //#endregion
48
48
  //#region src/codec.d.ts
49
- /**
50
- * List of known video codecs, ordered by encoding preference.
51
- * @group Codecs
52
- * @public
53
- */
54
- declare const VIDEO_CODECS: readonly ["avc", "hevc", "vp8", "vp9", "av1", "vc1"];
55
- /**
56
- * List of known video dynamic ranges.
57
- * @group Codecs
58
- * @public
59
- */
60
- declare const VIDEO_DYNAMIC_RANGES: readonly ["sdr", "hlg", "hdr10", "hdr10+", "dv"];
61
- /**
62
- * List of known audio codecs, ordered by encoding preference.
63
- * @group Codecs
64
- * @public
65
- */
66
- declare const AUDIO_CODECS: readonly ["aac", "opus", "mp3", "vorbis", "flac", "alac", "ac3", "eac3", "dts"];
67
- /**
68
- * List of known subtitle codecs, ordered by encoding preference.
69
- * @group Codecs
70
- * @public
71
- */
72
- declare const SUBTITLE_CODECS: readonly ["srt", "webvtt", "ttml", "dfxp", "ssa", "ass", "stpp"];
73
49
  /**
74
50
  * Union type of known video codecs.
75
51
  * @group Codecs
76
52
  * @public
77
53
  */
78
- type VideoCodec$1 = (typeof VIDEO_CODECS)[number];
54
+ type VideoCodec = VideoCodec$1 | 'vc1';
79
55
  /**
80
56
  * Union type of known video dynamic ranges.
57
+ *
58
+ * `sdr`: Standard Dynamic Range
59
+ *
60
+ * `hlg`: High-Luminance Gamma
61
+ *
62
+ * `hdr10`: High-Dynamic Range 10
63
+ *
64
+ * `hdr10+`: High-Dynamic Range 10+
65
+ *
66
+ * `dv`: Dolby Vision
67
+ *
81
68
  * @group Codecs
82
69
  * @public
83
70
  */
84
- type VideoDynamicRange = (typeof VIDEO_DYNAMIC_RANGES)[number];
71
+ type VideoDynamicRange = 'sdr' | 'hlg' | 'hdr10' | 'hdr10+' | 'dv';
85
72
  /**
86
73
  * Union type of known audio codecs.
87
74
  * @group Codecs
88
75
  * @public
89
76
  */
90
- type AudioCodec$1 = (typeof AUDIO_CODECS)[number];
77
+ type AudioCodec = AudioCodec$1 | 'dts' | 'alac';
91
78
  /**
92
79
  * Union type of known subtitle codecs.
93
80
  * @group Codecs
94
81
  * @public
95
82
  */
96
- type SubtitleCodec = (typeof SUBTITLE_CODECS)[number];
83
+ type SubtitleCodec = SubtitleCodec$1 | 'srt' | 'ttml' | 'dfxp' | 'ssa' | 'ass' | 'stpp';
97
84
  /**
98
85
  * Union type of known media codecs.
99
86
  * @group Codecs
100
87
  * @public
101
88
  */
102
- type MediaCodec = VideoCodec$1 | AudioCodec$1 | SubtitleCodec;
89
+ type MediaCodec = VideoCodec | AudioCodec | SubtitleCodec;
103
90
  //#endregion
104
91
  //#region src/role-type.d.ts
105
92
  declare const ROLE_TYPE: {
@@ -171,6 +158,7 @@ type DashParsedVideoTrack = DashTrackCommon & {
171
158
  width?: number;
172
159
  height?: number;
173
160
  frameRate?: number;
161
+ colorSpace?: VideoColorSpaceInit;
174
162
  dynamicRange?: VideoDynamicRange;
175
163
  };
176
164
  type DashParsedAudioTrack = DashTrackCommon & {
@@ -412,7 +400,7 @@ declare class DashInputVideoTrackBacking extends DashTrackBackingBase {
412
400
  internalTrack: InternalVideoTrack;
413
401
  constructor(internalTrack: InternalVideoTrack);
414
402
  getType(): "video";
415
- getCodec(): VideoCodec | null;
403
+ getCodec(): VideoCodec$1 | null;
416
404
  getCodedWidth(): number | Promise<any>;
417
405
  getCodedHeight(): number | Promise<any>;
418
406
  getSquarePixelWidth(): number | Promise<any>;
@@ -427,7 +415,7 @@ declare class DashInputAudioTrackBacking extends DashTrackBackingBase {
427
415
  internalTrack: InternalAudioTrack;
428
416
  constructor(internalTrack: InternalAudioTrack);
429
417
  getType(): "audio";
430
- getCodec(): AudioCodec | null;
418
+ getCodec(): AudioCodec$1 | null;
431
419
  getNumberOfChannels(): number | Promise<any>;
432
420
  getSampleRate(): number | Promise<any>;
433
421
  }
@@ -475,6 +463,15 @@ declare class HlsSubtitlePlaylist implements HlsSegmentedInput {
475
463
  getLiveRefreshInterval(): Promise<number | null>;
476
464
  isRelativeToUnixEpoch(): Promise<boolean>;
477
465
  }
466
+ declare class ExternalSubtitleSegmentedInput implements HlsSegmentedInput {
467
+ #private;
468
+ segments: HlsSegment[];
469
+ constructor(source: SourceWithRootPath, rootPath: string);
470
+ runUpdateSegments(): Promise<void>;
471
+ getDurationFromMetadata(options: DurationMetadataRequestOptions): Promise<number | null>;
472
+ getLiveRefreshInterval(): Promise<number | null>;
473
+ isRelativeToUnixEpoch(): Promise<boolean>;
474
+ }
478
475
  declare class HlsSubtitleTrackBacking {
479
476
  #private;
480
477
  constructor(params: {
@@ -509,10 +506,49 @@ declare class HlsSubtitleTrackBacking {
509
506
  getNextKeyPacket(_packet: EncodedPacket, _options: unknown): Promise<EncodedPacket | null>;
510
507
  getSegmentedInput(): HlsSubtitlePlaylist;
511
508
  }
509
+ declare class ExternalSubtitleTrackBacking {
510
+ #private;
511
+ constructor(params: {
512
+ id: number;
513
+ number: number;
514
+ pairingMask: bigint;
515
+ source: SourceWithRootPath;
516
+ codec?: SubtitleCodec | null;
517
+ codecString?: string | null;
518
+ languageCode?: string;
519
+ name?: string | null;
520
+ disposition?: Partial<TrackDisposition>;
521
+ });
522
+ getType(): "subtitle";
523
+ getId(): number;
524
+ getNumber(): number;
525
+ getCodec(): Promise<never>;
526
+ getInternalCodecId(): null;
527
+ getName(): string | null;
528
+ getLanguageCode(): string;
529
+ getTimeResolution(): number;
530
+ isRelativeToUnixEpoch(): Promise<boolean>;
531
+ getDisposition(): TrackDisposition;
532
+ getPairingMask(): bigint;
533
+ getBitrate(): null;
534
+ getAverageBitrate(): null;
535
+ getDurationFromMetadata(options: DurationMetadataRequestOptions): Promise<number | null>;
536
+ getLiveRefreshInterval(): Promise<number | null>;
537
+ getHasOnlyKeyPackets(): boolean;
538
+ getDecoderConfig(): Promise<null>;
539
+ getMetadataCodecParameterString(): Promise<string | null>;
540
+ getFirstPacket(_options: unknown): Promise<EncodedPacket | null>;
541
+ getPacket(_timestamp: number, _options: unknown): Promise<EncodedPacket | null>;
542
+ getNextPacket(_packet: EncodedPacket, _options: unknown): Promise<EncodedPacket | null>;
543
+ getKeyPacket(_timestamp: number, _options: unknown): Promise<EncodedPacket | null>;
544
+ getNextKeyPacket(_packet: EncodedPacket, _options: unknown): Promise<EncodedPacket | null>;
545
+ getSegmentedInput(): ExternalSubtitleSegmentedInput;
546
+ }
512
547
  //#endregion
513
548
  //#region src/mediabunny-input.d.ts
514
549
  declare module 'mediabunny' {
515
550
  interface Input<S extends Source = Source> {
551
+ _getDemuxer(): Promise<unknown>;
516
552
  _getTrackBackings(): Promise<unknown[]>;
517
553
  _wrapBackingAsTrack(backing: unknown): InputTrack$2;
518
554
  }
@@ -521,10 +557,25 @@ type SegmentAccessMethods = {
521
557
  getSegmentedInput(): HlsSegmentedInput | DashSegmentedInput;
522
558
  getSegments(): Promise<(HlsSegment | DashSegment)[]>;
523
559
  };
524
- type MediabunnyTrackWithSegments = InputTrack$2 & SegmentAccessMethods;
525
- type MediabunnyVideoTrackWithSegments = InputVideoTrack$1 & SegmentAccessMethods;
526
- type MediabunnyAudioTrackWithSegments = InputAudioTrack$1 & SegmentAccessMethods;
527
- type NativeTrackBacking = Parameters<Input$1<Source>['_wrapBackingAsTrack']>[0];
560
+ type VideoDynamicRangeMethods = {
561
+ getDynamicRange(): Promise<VideoDynamicRange>;
562
+ };
563
+ type MediabunnySubtitleTrackLike = InputTrack$2 & {
564
+ type: 'subtitle';
565
+ };
566
+ type InputTrack$1 = InputTrack$2 & SegmentAccessMethods;
567
+ type InputVideoTrack = InputVideoTrack$1 & SegmentAccessMethods & VideoDynamicRangeMethods;
568
+ type InputAudioTrack = InputAudioTrack$1 & SegmentAccessMethods;
569
+ type InputSubtitleTrack = MediabunnySubtitleTrackLike & SegmentAccessMethods;
570
+ type InputSubtitleSource = PathedSource | SourceRef<PathedSource>;
571
+ type InputSubtitleTrackMetadata = {
572
+ codec?: SubtitleCodec | null;
573
+ codecString?: string | null;
574
+ disposition?: Partial<TrackDisposition>;
575
+ languageCode?: string;
576
+ name?: string | null;
577
+ pairWith?: InputVideoTrack | Iterable<InputVideoTrack>;
578
+ };
528
579
  type SegmentableBacking = {
529
580
  getId(): number;
530
581
  getNumber(): number;
@@ -545,6 +596,7 @@ type SegmentableBacking = {
545
596
  getMetadataCodecParameterString?(): string | null | Promise<string | null>;
546
597
  getSegmentedInput?(): HlsSegmentedInput | DashSegmentedInput;
547
598
  };
599
+ type NativeTrackBacking = SegmentableBacking;
548
600
  type TrackBacking = NativeTrackBacking | SegmentableBacking;
549
601
  declare const BACKING_TYPE_SUBTITLE = "subtitle";
550
602
  declare const BACKING_TYPE_AUDIO = "audio";
@@ -552,13 +604,15 @@ declare const BACKING_TYPE_VIDEO = "video";
552
604
  declare const preserveSubtitleBackingsOnInput: (input: Input$1) => Input$1<Source>;
553
605
  declare class SegmentedMediabunnyInput<S extends Source = Source> extends Input$1<S> {
554
606
  #private;
555
- _wrapBackingAsTrack(backing: TrackBacking): MediabunnyTrackWithSegments;
556
- _getSyntheticTrackBackings(type?: typeof BACKING_TYPE_VIDEO | typeof BACKING_TYPE_AUDIO | typeof BACKING_TYPE_SUBTITLE): Promise<HlsSubtitleTrackBacking[]>;
557
- getTracks(query?: InputTrackQuery$1<MediabunnyTrackWithSegments>): Promise<MediabunnyTrackWithSegments[]>;
558
- getVideoTracks(query?: InputTrackQuery$1<MediabunnyVideoTrackWithSegments>): Promise<MediabunnyVideoTrackWithSegments[]>;
559
- getAudioTracks(query?: InputTrackQuery$1<MediabunnyAudioTrackWithSegments>): Promise<MediabunnyAudioTrackWithSegments[]>;
560
- getPrimaryVideoTrack(query?: InputTrackQuery$1<MediabunnyVideoTrackWithSegments>): Promise<MediabunnyVideoTrackWithSegments | null>;
561
- getPrimaryAudioTrack(query?: InputTrackQuery$1<MediabunnyAudioTrackWithSegments>): Promise<MediabunnyAudioTrackWithSegments | null>;
607
+ _wrapBackingAsTrack(backing: TrackBacking): InputTrack$1;
608
+ _getSyntheticTrackBackings(type?: typeof BACKING_TYPE_VIDEO | typeof BACKING_TYPE_AUDIO | typeof BACKING_TYPE_SUBTITLE): Promise<(ExternalSubtitleTrackBacking | HlsSubtitleTrackBacking)[]>;
609
+ getTracks(query?: InputTrackQuery$1<InputTrack$1>): Promise<InputTrack$1[]>;
610
+ getVideoTracks(query?: InputTrackQuery$1<InputVideoTrack>): Promise<InputVideoTrack[]>;
611
+ getAudioTracks(query?: InputTrackQuery$1<InputAudioTrack>): Promise<InputAudioTrack[]>;
612
+ getSubtitleTracks(query?: InputTrackQuery$1<InputSubtitleTrack>): Promise<InputSubtitleTrack[]>;
613
+ getPrimaryVideoTrack(query?: InputTrackQuery$1<InputVideoTrack>): Promise<InputVideoTrack | null>;
614
+ getPrimaryAudioTrack(query?: InputTrackQuery$1<InputAudioTrack>): Promise<InputAudioTrack | null>;
615
+ addSubtitleTrack(source: InputSubtitleSource, metadata?: InputSubtitleTrackMetadata): InputSubtitleTrack;
562
616
  }
563
617
  //#endregion
564
618
  //#region src/index.d.ts
@@ -567,14 +621,12 @@ type DashaInputOptions<S extends Source = Source> = Omit<InputOptions<S>, 'forma
567
621
  };
568
622
  type InputSegment = HlsSegment | DashSegment;
569
623
  type InputSegmentedInput = HlsSegmentedInput | DashSegmentedInput;
570
- type InputTrack$1 = MediabunnyTrackWithSegments;
571
- type InputVideoTrack = MediabunnyVideoTrackWithSegments;
572
- type InputAudioTrack = MediabunnyAudioTrackWithSegments;
573
624
  declare class Input<S extends Source = Source> extends SegmentedMediabunnyInput<S> {
574
625
  constructor(options: DashaInputOptions<S>);
575
626
  }
576
627
  declare const isInput: (value: unknown) => value is Input;
577
628
  declare const getSegmentedInput: (track: InputTrack$1) => InputSegmentedInput;
578
629
  declare const getSegments: (track: InputTrack$1) => Promise<InputSegment[]>;
630
+ declare const ALL_FORMATS: InputFormat[];
579
631
  //#endregion
580
- export { DASH, DASH_FORMATS, type DashSegment, type DashSegmentedInput, FilePathSource, HLS_FORMATS, type HlsSegment, type HlsSegmentedInput, Input, InputAudioTrack, InputSegment, InputSegmentedInput, InputTrack$1 as InputTrack, type InputTrackQuery, type InputTrackWithBacking, InputVideoTrack, type MediaCodec, type SubtitleCodec, UrlSource, asc, desc, getSegmentedInput, getSegments, isInput, prefer, preserveSubtitleBackingsOnInput };
632
+ export { ALL_FORMATS, type AudioCodec, DASH, DASH_FORMATS, type DashSegment, type DashSegmentedInput, FilePathSource, HLS, HLS_FORMATS, type HlsSegment, type HlsSegmentedInput, Input, type InputAudioTrack, InputSegment, InputSegmentedInput, type InputSubtitleSource, type InputSubtitleTrack, type InputSubtitleTrackMetadata, type InputTrack$1 as InputTrack, type InputTrackQuery, type InputTrackWithBacking, type InputVideoTrack, MP3, MP4, type MaybePromise, type MediaCodec, type SubtitleCodec, UrlSource, type VideoCodec, type VideoDynamicRange, asc, desc, getSegmentedInput, getSegments, isInput, prefer, preserveSubtitleBackingsOnInput };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ADTS, CustomPathedSource, FilePathSource, HLS, HLS_FORMATS, Input as Input$1, InputFormat, InputTrack, MATROSKA, MP3, MP4, QTFF, UrlSource, WEBM, asc, desc, prefer } from "mediabunny";
1
+ import { ADTS, ALL_FORMATS as ALL_FORMATS$1, CustomPathedSource, FilePathSource, HLS, HLS as HLS$1, HLS_FORMATS, Input as Input$1, InputFormat, InputTrack, InputVideoTrack, MATROSKA, MP3, MP3 as MP3$1, MP4, MP4 as MP4$1, QTFF, SourceRef, UrlSource, WEBM, asc, desc, prefer } from "mediabunny";
2
2
  import { DOMParser } from "@xmldom/xmldom";
3
3
  import { Temporal } from "temporal-polyfill";
4
4
  import { readFile } from "node:fs/promises";
@@ -158,6 +158,32 @@ const MATRIX = {
158
158
  YCbCr_BT_2020_and_2100: 9,
159
159
  ICtCp_BT_2100: 14
160
160
  };
161
+ const DOLBY_VISION_CODECS = [
162
+ "dva1",
163
+ "dvav",
164
+ "dvhe",
165
+ "dvh1"
166
+ ];
167
+ const COLOR_PRIMARIES_MAP = new Map([
168
+ [PRIMARIES.BT_709, "bt709"],
169
+ [PRIMARIES.BT_601_625, "bt470bg"],
170
+ [PRIMARIES.BT_601_525, "smpte170m"],
171
+ [PRIMARIES.BT_2020_and_2100, "bt2020"],
172
+ [PRIMARIES.SMPTE_ST_2113_and_EG_4321, "smpte432"]
173
+ ]);
174
+ const TRANSFER_CHARACTERISTICS_MAP = new Map([
175
+ [TRANSFER.BT_709, "bt709"],
176
+ [TRANSFER.BT_601, "smpte170m"],
177
+ [TRANSFER.BT_2100_PQ, "pq"],
178
+ [TRANSFER.BT_2100_HLG, "hlg"]
179
+ ]);
180
+ const MATRIX_COEFFICIENTS_MAP = new Map([
181
+ [MATRIX.RGB, "rgb"],
182
+ [MATRIX.YCbCr_BT_709, "bt709"],
183
+ [MATRIX.YCbCr_BT_601_625, "bt470bg"],
184
+ [MATRIX.YCbCr_BT_601_525, "smpte170m"],
185
+ [MATRIX.YCbCr_BT_2020_and_2100, "bt2020-ncl"]
186
+ ]);
161
187
  const parseVideoCodecFromMime = (mime) => {
162
188
  const target = mime.toLowerCase().trim().split(".")[0];
163
189
  const avc = [
@@ -199,6 +225,52 @@ const parseDynamicRangeFromCicp = (primaries, transfer, matrix) => {
199
225
  else if (TRANSFER.BT_2100_HLG === transfer) return "hlg";
200
226
  else return "sdr";
201
227
  };
228
+ const parseColorSpaceFromCicp = (primaries, transfer, matrix) => {
229
+ const normalizedTransfer = transfer == 5 ? TRANSFER.BT_601 : transfer;
230
+ return {
231
+ primaries: COLOR_PRIMARIES_MAP.get(primaries),
232
+ transfer: TRANSFER_CHARACTERISTICS_MAP.get(normalizedTransfer),
233
+ matrix: MATRIX_COEFFICIENTS_MAP.get(matrix)
234
+ };
235
+ };
236
+ const parseDynamicRangeFromCodecString = (codecs) => {
237
+ const normalized = codecs?.trim().toLowerCase();
238
+ if (!normalized) return;
239
+ return DOLBY_VISION_CODECS.some((value) => normalized.startsWith(value)) ? "dv" : void 0;
240
+ };
241
+ const getCicpValues = (supplementalProps = [], essentialProps = []) => {
242
+ const primariesScheme = "urn:mpeg:mpegB:cicp:ColourPrimaries";
243
+ const transferScheme = "urn:mpeg:mpegB:cicp:TransferCharacteristics";
244
+ const matrixScheme = "urn:mpeg:mpegB:cicp:MatrixCoefficients";
245
+ const allProps = [...essentialProps, ...supplementalProps];
246
+ const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).flatMap((prop) => prop.value ? [Number.parseInt(prop.value, 10)] : []);
247
+ return {
248
+ primaries: getValues(primariesScheme)[0] ?? 0,
249
+ transfer: getValues(transferScheme)[0] ?? 0,
250
+ matrix: getValues(matrixScheme)[0] ?? 0
251
+ };
252
+ };
253
+ const parseColorSpace = (supplementalProps = [], essentialProps = []) => {
254
+ const { primaries, transfer, matrix } = getCicpValues(supplementalProps, essentialProps);
255
+ return parseColorSpaceFromCicp(primaries, transfer, matrix);
256
+ };
257
+ const parseDynamicRangeFromHlsVideoRange = (videoRange, codecs) => {
258
+ const normalized = videoRange?.replaceAll("\"", "").trim().toUpperCase();
259
+ if (!normalized) return;
260
+ const codecDynamicRange = parseDynamicRangeFromCodecString(codecs);
261
+ if (normalized === "PQ") return codecDynamicRange ?? "hdr10";
262
+ if (normalized === "HLG") return "hlg";
263
+ if (normalized === "SDR") return "sdr";
264
+ };
265
+ const parseDynamicRangeFromColorSpace = (colorSpace) => {
266
+ if (!colorSpace) return;
267
+ if (colorSpace.transfer === "pq") return "hdr10";
268
+ if (colorSpace.transfer === "hlg") return "hlg";
269
+ if (colorSpace.primaries != null || colorSpace.transfer != null || colorSpace.matrix != null || colorSpace.fullRange != null) return "sdr";
270
+ };
271
+ const inferDynamicRange = (params) => {
272
+ return parseDynamicRangeFromCodecString(params.codecs) ?? parseDynamicRangeFromHlsVideoRange(params.videoRange, params.codecs) ?? parseDynamicRangeFromColorSpace(params.colorSpace) ?? "sdr";
273
+ };
202
274
  const parseVideoCodec = (codecs) => {
203
275
  for (const codec of codecs.toLowerCase().split(",")) {
204
276
  const mime = codec.trim().split(".")[0];
@@ -218,18 +290,10 @@ const tryParseVideoCodec = (codecs) => {
218
290
  }
219
291
  };
220
292
  const parseDynamicRange = (codecs, supplementalProps = [], essentialProps = []) => {
221
- if ([
222
- "dva1",
223
- "dvav",
224
- "dvhe",
225
- "dvh1"
226
- ].some((value) => codecs.startsWith(value))) return "dv";
227
- const primariesScheme = "urn:mpeg:mpegB:cicp:ColourPrimaries";
228
- const transferScheme = "urn:mpeg:mpegB:cicp:TransferCharacteristics";
229
- const matrixScheme = "urn:mpeg:mpegB:cicp:MatrixCoefficients";
230
- const allProps = [...essentialProps, ...supplementalProps];
231
- const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).flatMap((prop) => prop.value ? [parseInt(prop.value)] : []);
232
- 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));
293
+ const codecDynamicRange = parseDynamicRangeFromCodecString(codecs);
294
+ if (codecDynamicRange) return codecDynamicRange;
295
+ const { primaries, transfer, matrix } = getCicpValues(supplementalProps, essentialProps);
296
+ return parseDynamicRangeFromCicp(primaries, transfer, matrix);
233
297
  };
234
298
  //#endregion
235
299
  //#region src/dash/dash-misc.ts
@@ -838,6 +902,74 @@ var HlsSubtitlePlaylist = class {
838
902
  this.#streamHasEnded = streamHasEnded;
839
903
  }
840
904
  };
905
+ var ExternalSubtitleSegmentedInput = class {
906
+ segments = [];
907
+ #source;
908
+ #rootPath;
909
+ #delegate = null;
910
+ #loadPromise = null;
911
+ constructor(source, rootPath) {
912
+ this.#source = source;
913
+ this.#rootPath = rootPath;
914
+ }
915
+ runUpdateSegments() {
916
+ return this.#loadPromise ??= (async () => {
917
+ if (this.#delegate) {
918
+ await this.#delegate.runUpdateSegments();
919
+ this.segments = this.#delegate.segments;
920
+ return;
921
+ }
922
+ const loaded = await loadPlaylistText(this.#source, this.#rootPath);
923
+ if (splitPlaylistLines(loaded.text)[0] === "#EXTM3U") {
924
+ this.#delegate = new HlsSubtitlePlaylist(this.#source, loaded.path);
925
+ await this.#delegate.runUpdateSegments();
926
+ this.segments = this.#delegate.segments;
927
+ return;
928
+ }
929
+ const segment = {
930
+ timestamp: 0,
931
+ duration: 0,
932
+ relativeToUnixEpoch: false,
933
+ firstSegment: null,
934
+ sequenceNumber: 0,
935
+ location: {
936
+ path: toSegmentPath(loaded.path),
937
+ offset: 0,
938
+ length: null
939
+ },
940
+ encryption: null,
941
+ initSegment: null,
942
+ lastProgramDateTimeSeconds: null
943
+ };
944
+ segment.firstSegment = segment;
945
+ this.segments = [segment];
946
+ this.#delegate = {
947
+ segments: this.segments,
948
+ runUpdateSegments: async () => {},
949
+ getDurationFromMetadata: async (_options) => null,
950
+ getLiveRefreshInterval: async () => null,
951
+ isRelativeToUnixEpoch: async () => false
952
+ };
953
+ })().finally(() => {
954
+ this.#loadPromise = null;
955
+ });
956
+ }
957
+ async getDurationFromMetadata(options) {
958
+ await this.runUpdateSegments();
959
+ if (this.#delegate) return this.#delegate.getDurationFromMetadata(options);
960
+ return null;
961
+ }
962
+ async getLiveRefreshInterval() {
963
+ await this.runUpdateSegments();
964
+ if (this.#delegate) return this.#delegate.getLiveRefreshInterval();
965
+ return null;
966
+ }
967
+ async isRelativeToUnixEpoch() {
968
+ await this.runUpdateSegments();
969
+ if (this.#delegate) return this.#delegate.isRelativeToUnixEpoch();
970
+ return false;
971
+ }
972
+ };
841
973
  var HlsSubtitleTrackBacking = class {
842
974
  #id;
843
975
  #number;
@@ -930,6 +1062,118 @@ var HlsSubtitleTrackBacking = class {
930
1062
  return this.#segmentedInput;
931
1063
  }
932
1064
  };
1065
+ var ExternalSubtitleTrackBacking = class {
1066
+ #id;
1067
+ #number;
1068
+ #pairingMask;
1069
+ #source;
1070
+ #segmentedInput;
1071
+ #languageCode;
1072
+ #name;
1073
+ #disposition;
1074
+ #codec;
1075
+ #codecString;
1076
+ #codecInfoPromise = null;
1077
+ constructor(params) {
1078
+ this.#id = params.id;
1079
+ this.#number = params.number;
1080
+ this.#pairingMask = params.pairingMask;
1081
+ this.#source = params.source;
1082
+ this.#segmentedInput = new ExternalSubtitleSegmentedInput(params.source, params.source.rootPath);
1083
+ this.#languageCode = params.languageCode ?? "und";
1084
+ this.#name = params.name ?? null;
1085
+ this.#codec = params.codec ?? tryParseSubtitleCodec(params.codecString ?? "") ?? void 0;
1086
+ this.#codecString = params.codecString ?? params.codec ?? void 0;
1087
+ this.#disposition = {
1088
+ ...DEFAULT_TRACK_DISPOSITION$1,
1089
+ ...params.disposition
1090
+ };
1091
+ }
1092
+ getType() {
1093
+ return "subtitle";
1094
+ }
1095
+ getId() {
1096
+ return this.#id;
1097
+ }
1098
+ getNumber() {
1099
+ return this.#number;
1100
+ }
1101
+ async #getCodecInfo() {
1102
+ if (this.#codec !== void 0 && this.#codecString !== void 0) return {
1103
+ codec: this.#codec,
1104
+ codecString: this.#codecString
1105
+ };
1106
+ const info = await (this.#codecInfoPromise ??= detectSubtitleCodecFromUri(this.#source, this.#source.rootPath));
1107
+ this.#codec = info.codec;
1108
+ this.#codecString = info.codecString;
1109
+ return info;
1110
+ }
1111
+ getCodec() {
1112
+ if (this.#codec !== void 0) return this.#codec;
1113
+ return this.#getCodecInfo().then((info) => info.codec);
1114
+ }
1115
+ getInternalCodecId() {
1116
+ return null;
1117
+ }
1118
+ getName() {
1119
+ return this.#name;
1120
+ }
1121
+ getLanguageCode() {
1122
+ return this.#languageCode;
1123
+ }
1124
+ getTimeResolution() {
1125
+ return 1e3;
1126
+ }
1127
+ isRelativeToUnixEpoch() {
1128
+ return this.#segmentedInput.isRelativeToUnixEpoch();
1129
+ }
1130
+ getDisposition() {
1131
+ return this.#disposition;
1132
+ }
1133
+ getPairingMask() {
1134
+ return this.#pairingMask;
1135
+ }
1136
+ getBitrate() {
1137
+ return null;
1138
+ }
1139
+ getAverageBitrate() {
1140
+ return null;
1141
+ }
1142
+ getDurationFromMetadata(options) {
1143
+ return this.#segmentedInput.getDurationFromMetadata(options);
1144
+ }
1145
+ getLiveRefreshInterval() {
1146
+ return this.#segmentedInput.getLiveRefreshInterval();
1147
+ }
1148
+ getHasOnlyKeyPackets() {
1149
+ return true;
1150
+ }
1151
+ async getDecoderConfig() {
1152
+ return null;
1153
+ }
1154
+ async getMetadataCodecParameterString() {
1155
+ if (this.#codecString !== void 0) return this.#codecString;
1156
+ return this.#getCodecInfo().then((info) => info.codecString);
1157
+ }
1158
+ async getFirstPacket(_options) {
1159
+ return null;
1160
+ }
1161
+ async getPacket(_timestamp, _options) {
1162
+ return null;
1163
+ }
1164
+ async getNextPacket(_packet, _options) {
1165
+ return null;
1166
+ }
1167
+ async getKeyPacket(_timestamp, _options) {
1168
+ return null;
1169
+ }
1170
+ async getNextKeyPacket(_packet, _options) {
1171
+ return null;
1172
+ }
1173
+ getSegmentedInput() {
1174
+ return this.#segmentedInput;
1175
+ }
1176
+ };
933
1177
  const parseMasterPlaylistSubtitles = async (input) => {
934
1178
  const source = input.source;
935
1179
  if (!("rootPath" in source) || typeof source.rootPath !== "string") return [];
@@ -992,6 +1236,116 @@ const getHlsSubtitleTrackBackings = (input) => {
992
1236
  };
993
1237
  //#endregion
994
1238
  //#region src/mediabunny-input.ts
1239
+ const CUSTOM_SUBTITLE_TRACK_ID_OFFSET = 1e9;
1240
+ const CUSTOM_PAIRING_BIT_START = 1024n;
1241
+ const EXTRA_PAIRING_MASK = Symbol.for("dasha.extra-pairing-mask");
1242
+ const ORIGINAL_GET_PAIRING_MASK = Symbol.for("dasha.original-get-pairing-mask");
1243
+ const HLS_VARIANT_INF_LINE = "#EXT-X-STREAM-INF:";
1244
+ const HLS_DEMUXER_PATCHED = Symbol.for("dasha.hls-demuxer-patched");
1245
+ const HLS_DEMUXER_METADATA_PATCH = Symbol.for("dasha.hls-demuxer-metadata-patch");
1246
+ const HLS_VIDEO_RANGE_APPLIED = Symbol.for("dasha.hls-video-range-applied");
1247
+ var HlsAttributeList = class {
1248
+ #attributes = {};
1249
+ constructor(str) {
1250
+ let key = "";
1251
+ let value = "";
1252
+ let inValue = false;
1253
+ let inQuotes = false;
1254
+ for (const char of str) if (char === "\"") inQuotes = !inQuotes;
1255
+ else if (char === "=" && !inValue && !inQuotes) inValue = true;
1256
+ else if (char === "," && !inQuotes) {
1257
+ if (key) this.#attributes[key.trim().toLowerCase()] = value;
1258
+ key = "";
1259
+ value = "";
1260
+ inValue = false;
1261
+ } else if (inValue) value += char;
1262
+ else key += char;
1263
+ if (key) this.#attributes[key.trim().toLowerCase()] = value;
1264
+ }
1265
+ get(name) {
1266
+ return this.#attributes[name.toLowerCase()] ?? null;
1267
+ }
1268
+ };
1269
+ const extractHlsVideoRanges = (text) => {
1270
+ const lines = text.split(/\r?\n/);
1271
+ const videoRanges = [];
1272
+ for (const rawLine of lines) {
1273
+ const line = rawLine.trim();
1274
+ if (!line.startsWith(HLS_VARIANT_INF_LINE)) continue;
1275
+ const attributes = new HlsAttributeList(line.slice(18));
1276
+ videoRanges.push(attributes.get("video-range"));
1277
+ }
1278
+ return videoRanges;
1279
+ };
1280
+ const getPairingMaskIndexes = (pairingMask) => {
1281
+ const indexes = [];
1282
+ let value = pairingMask;
1283
+ let index = 0;
1284
+ while (value > 0n) {
1285
+ if ((value & 1n) === 1n) indexes.push(index);
1286
+ value >>= 1n;
1287
+ index++;
1288
+ }
1289
+ return indexes;
1290
+ };
1291
+ const applyHlsVideoRangeMetadata = async (demuxer) => {
1292
+ if (demuxer[HLS_VIDEO_RANGE_APPLIED] || !demuxer.hasMasterPlaylist || !demuxer.internalTracks?.length) {
1293
+ demuxer[HLS_VIDEO_RANGE_APPLIED] = true;
1294
+ return;
1295
+ }
1296
+ const slice = await demuxer.input?._reader?.requestEntireFile();
1297
+ if (!slice) {
1298
+ demuxer[HLS_VIDEO_RANGE_APPLIED] = true;
1299
+ return;
1300
+ }
1301
+ const bytes = slice instanceof Uint8Array ? slice : "bytes" in slice ? slice.bytes : slice.data;
1302
+ const videoRanges = extractHlsVideoRanges(new TextDecoder().decode(bytes));
1303
+ if (!videoRanges.length) {
1304
+ demuxer[HLS_VIDEO_RANGE_APPLIED] = true;
1305
+ return;
1306
+ }
1307
+ for (const track of demuxer.internalTracks) {
1308
+ if (track.info.type !== "video") continue;
1309
+ const matchedRanges = getPairingMaskIndexes(track.pairingMask).map((index) => videoRanges[index] ?? null).filter((value) => value != null);
1310
+ const uniqueRanges = [...new Set(matchedRanges)];
1311
+ track.videoRange = uniqueRanges.length === 1 ? uniqueRanges[0] : null;
1312
+ }
1313
+ demuxer[HLS_VIDEO_RANGE_APPLIED] = true;
1314
+ };
1315
+ const patchHlsDemuxer = (demuxer) => {
1316
+ if (!(demuxer instanceof Object) || !("readMetadata" in demuxer) || typeof demuxer.readMetadata !== "function" || !("input" in demuxer) || demuxer[HLS_DEMUXER_PATCHED]) return demuxer;
1317
+ const candidate = demuxer;
1318
+ if (candidate.constructor?.name !== "HlsDemuxer") return demuxer;
1319
+ const originalReadMetadata = candidate.readMetadata.bind(candidate);
1320
+ candidate.readMetadata = () => {
1321
+ if (candidate[HLS_DEMUXER_METADATA_PATCH]) return candidate[HLS_DEMUXER_METADATA_PATCH];
1322
+ const promise = Promise.resolve(originalReadMetadata()).then(() => applyHlsVideoRangeMetadata(candidate));
1323
+ candidate[HLS_DEMUXER_METADATA_PATCH] = promise.catch((error) => {
1324
+ if (candidate[HLS_DEMUXER_METADATA_PATCH] === promise) candidate[HLS_DEMUXER_METADATA_PATCH] = void 0;
1325
+ throw error;
1326
+ });
1327
+ return candidate[HLS_DEMUXER_METADATA_PATCH];
1328
+ };
1329
+ candidate[HLS_DEMUXER_PATCHED] = true;
1330
+ return demuxer;
1331
+ };
1332
+ const getDynamicRangeForTrack = async (track) => {
1333
+ const backing = track._backing;
1334
+ const manifestDynamicRange = backing?.internalTrack?.track?.dynamicRange;
1335
+ if (manifestDynamicRange) return manifestDynamicRange;
1336
+ const codecString = await track.getCodecParameterString().catch(() => null);
1337
+ const videoRange = backing?.internalTrack?.videoRange ?? null;
1338
+ const fromMetadata = inferDynamicRange({
1339
+ codecs: codecString,
1340
+ videoRange
1341
+ });
1342
+ if (fromMetadata !== "sdr" || videoRange != null) return fromMetadata;
1343
+ return inferDynamicRange({
1344
+ codecs: codecString,
1345
+ colorSpace: await track.getColorSpace().catch(() => null),
1346
+ videoRange
1347
+ });
1348
+ };
995
1349
  const requireSync = (value, getterName, asyncName) => {
996
1350
  if (value instanceof Promise) throw new Error(`'${getterName}' is not available synchronously for this track. Use '${asyncName}()' instead.`);
997
1351
  return value;
@@ -1045,6 +1399,10 @@ const patchBaseMediabunnyInput = () => {
1045
1399
  prototype.getAudioTracks = function(query) {
1046
1400
  return getTrackBackingsByType(this, BACKING_TYPE_AUDIO).then((backings) => queryWrappedTracks(this, backings, query));
1047
1401
  };
1402
+ const originalGetDemuxer = prototype._getDemuxer;
1403
+ prototype._getDemuxer = function() {
1404
+ return originalGetDemuxer.call(this).then((demuxer) => patchHlsDemuxer(demuxer));
1405
+ };
1048
1406
  prototype[BASE_INPUT_PATCHED] = true;
1049
1407
  };
1050
1408
  const getSegmentedInputForTrack = (track) => {
@@ -1054,6 +1412,7 @@ const getSegmentedInputForTrack = (track) => {
1054
1412
  return internalTrack.demuxer.getSegmentedInputForPath(internalTrack.fullPath);
1055
1413
  };
1056
1414
  const addSegmentAccess = (track) => new Proxy(track, { get(target, prop) {
1415
+ if (prop === "getDynamicRange" && target instanceof InputVideoTrack) return () => getDynamicRangeForTrack(target);
1057
1416
  if (prop === "getSegmentedInput") return () => getSegmentedInputForTrack(target);
1058
1417
  if (prop === "getSegments") return async () => {
1059
1418
  const segmentedInput = getSegmentedInputForTrack(target);
@@ -1104,9 +1463,13 @@ var SegmentedMediabunnyInput = class extends Input$1 {
1104
1463
  #trackCache = /* @__PURE__ */ new WeakMap();
1105
1464
  #subtitleTrackCache = /* @__PURE__ */ new WeakMap();
1106
1465
  #hlsSubtitleBackingsPromise = null;
1466
+ #customSubtitleBackings = [];
1467
+ #nextCustomSubtitleTrackId = CUSTOM_SUBTITLE_TRACK_ID_OFFSET;
1468
+ #nextCustomSubtitleTrackNumber = CUSTOM_SUBTITLE_TRACK_ID_OFFSET;
1469
+ #nextPairingBitIndex = null;
1107
1470
  async #queryTracks(query, type) {
1108
- const backings = await getTrackBackingsByType(this, type);
1109
- return queryWrappedTracks(this, backings, query);
1471
+ const internalInput = this;
1472
+ return queryWrappedTracks(internalInput, await getTrackBackingsByType(internalInput, type), query);
1110
1473
  }
1111
1474
  _wrapBackingAsTrack(backing) {
1112
1475
  const track = backing.getType?.() === "subtitle" ? this.#wrapSubtitleBacking(backing) : super._wrapBackingAsTrack(backing);
@@ -1118,7 +1481,8 @@ var SegmentedMediabunnyInput = class extends Input$1 {
1118
1481
  }
1119
1482
  async _getSyntheticTrackBackings(type) {
1120
1483
  if (type && type !== BACKING_TYPE_SUBTITLE) return [];
1121
- if (await this.getFormat() !== HLS) return [];
1484
+ const backings = [...this.#customSubtitleBackings];
1485
+ if (await this.getFormat() !== HLS$1) return backings;
1122
1486
  if (!this.#hlsSubtitleBackingsPromise) {
1123
1487
  const promise = getHlsSubtitleTrackBackings(this).catch((error) => {
1124
1488
  if (this.#hlsSubtitleBackingsPromise === promise) this.#hlsSubtitleBackingsPromise = null;
@@ -1126,7 +1490,7 @@ var SegmentedMediabunnyInput = class extends Input$1 {
1126
1490
  });
1127
1491
  this.#hlsSubtitleBackingsPromise = promise;
1128
1492
  }
1129
- return this.#hlsSubtitleBackingsPromise;
1493
+ return [...backings, ...await this.#hlsSubtitleBackingsPromise];
1130
1494
  }
1131
1495
  #wrapSubtitleBacking(backing) {
1132
1496
  const existing = this.#subtitleTrackCache.get(backing);
@@ -1144,12 +1508,83 @@ var SegmentedMediabunnyInput = class extends Input$1 {
1144
1508
  async getAudioTracks(query) {
1145
1509
  return await this.#queryTracks(query, BACKING_TYPE_AUDIO);
1146
1510
  }
1511
+ async getSubtitleTracks(query) {
1512
+ return await this.#queryTracks(query, BACKING_TYPE_SUBTITLE);
1513
+ }
1147
1514
  async getPrimaryVideoTrack(query) {
1148
1515
  return await super.getPrimaryVideoTrack(query);
1149
1516
  }
1150
1517
  async getPrimaryAudioTrack(query) {
1151
1518
  return await super.getPrimaryAudioTrack(query);
1152
1519
  }
1520
+ addSubtitleTrack(source, metadata = {}) {
1521
+ const sourceWithRootPath = this.#takeSubtitleSourceRef(source).source;
1522
+ if (typeof sourceWithRootPath.rootPath !== "string") throw new TypeError("source must provide a string rootPath.");
1523
+ const pairWith = this.#toPairableVideoTracks(metadata.pairWith);
1524
+ const backing = new ExternalSubtitleTrackBacking({
1525
+ id: this.#nextCustomSubtitleTrackId++,
1526
+ number: this.#nextCustomSubtitleTrackNumber++,
1527
+ pairingMask: 0n,
1528
+ source: sourceWithRootPath,
1529
+ codec: metadata.codec,
1530
+ codecString: metadata.codecString,
1531
+ disposition: metadata.disposition,
1532
+ languageCode: metadata.languageCode,
1533
+ name: metadata.name
1534
+ });
1535
+ this.#pairSubtitleBacking(backing, pairWith);
1536
+ this.#customSubtitleBackings.push(backing);
1537
+ return this._wrapBackingAsTrack(backing);
1538
+ }
1539
+ #takeSubtitleSourceRef(source) {
1540
+ const rawSource = source instanceof SourceRef ? source.source : source;
1541
+ if (!(rawSource instanceof Object) || !("rootPath" in rawSource) || !("ref" in rawSource) || typeof rawSource.ref !== "function") throw new TypeError("source must be a pathed source such as UrlSource or FilePathSource.");
1542
+ const ref = rawSource.ref();
1543
+ this._sourceRefs.push(ref);
1544
+ return ref;
1545
+ }
1546
+ #toPairableVideoTracks(pairWith) {
1547
+ if (!pairWith) return [];
1548
+ const tracks = this.#isIterable(pairWith) ? [...pairWith] : [pairWith];
1549
+ for (const track of tracks) {
1550
+ if (track.input !== this) throw new TypeError("pairWith tracks must belong to the same input instance.");
1551
+ if (track.type !== "video") throw new TypeError("pairWith only accepts video tracks.");
1552
+ }
1553
+ return tracks;
1554
+ }
1555
+ #isIterable(value) {
1556
+ return typeof value === "object" && value !== null && Symbol.iterator in value;
1557
+ }
1558
+ #pairSubtitleBacking(subtitleBacking, videoTracks) {
1559
+ for (const track of videoTracks) {
1560
+ const bit = this.#allocatePairingBit();
1561
+ this.#appendPairingMask(subtitleBacking, bit);
1562
+ this.#appendPairingMask(track._backing, bit);
1563
+ }
1564
+ }
1565
+ #allocatePairingBit() {
1566
+ const nextIndex = this.#nextPairingBitIndex ?? this.#getInitialPairingBitIndex();
1567
+ this.#nextPairingBitIndex = nextIndex + 1n;
1568
+ return 1n << nextIndex;
1569
+ }
1570
+ #getInitialPairingBitIndex() {
1571
+ const loadedBackings = [...this._trackBackingsCache ?? []];
1572
+ let maxBitIndex = -1n;
1573
+ for (const backing of [...loadedBackings, ...this.#customSubtitleBackings]) {
1574
+ const mask = backing.getPairingMask?.() ?? 0n;
1575
+ if (mask === 0n) continue;
1576
+ const bitIndex = BigInt(mask.toString(2).length - 1);
1577
+ if (bitIndex > maxBitIndex) maxBitIndex = bitIndex;
1578
+ }
1579
+ return maxBitIndex >= 0n ? maxBitIndex + 1n : CUSTOM_PAIRING_BIT_START;
1580
+ }
1581
+ #appendPairingMask(backing, mask) {
1582
+ const patchedBacking = backing;
1583
+ patchedBacking[EXTRA_PAIRING_MASK] = (patchedBacking[EXTRA_PAIRING_MASK] ?? 0n) | mask;
1584
+ if (patchedBacking[ORIGINAL_GET_PAIRING_MASK]) return;
1585
+ patchedBacking[ORIGINAL_GET_PAIRING_MASK] = backing.getPairingMask?.bind(backing) ?? (() => 0n);
1586
+ Object.assign(backing, { getPairingMask: () => (patchedBacking[ORIGINAL_GET_PAIRING_MASK]?.() ?? 0n) | (patchedBacking[EXTRA_PAIRING_MASK] ?? 0n) });
1587
+ }
1153
1588
  };
1154
1589
  //#endregion
1155
1590
  //#region src/dash/dash-segmented-input.ts
@@ -1660,7 +2095,8 @@ const createDashTrack = (params) => {
1660
2095
  if (width) track.width = Number(width);
1661
2096
  if (height) track.height = Number(height);
1662
2097
  track.frameRate = frameRate ?? getDashFrameRate(representation);
1663
- if (track.codecString && supplementalProps && essentialProps) track.dynamicRange = parseDynamicRange(track.codecString, supplementalProps, essentialProps);
2098
+ if (supplementalProps.length > 0 || essentialProps.length > 0) track.colorSpace = parseColorSpace(supplementalProps, essentialProps);
2099
+ if (track.codecString && (supplementalProps.length > 0 || essentialProps.length > 0)) track.dynamicRange = parseDynamicRange(track.codecString, supplementalProps, essentialProps);
1664
2100
  } else if (track.type === "audio") {
1665
2101
  if (accessibilities) track.descriptive = checkIsDescriptive(accessibilities);
1666
2102
  if (supplementalProps) track.joc = getDolbyDigitalPlusComplexityIndex(supplementalProps);
@@ -2258,7 +2694,7 @@ var DashInputVideoTrackBacking = class extends DashTrackBackingBase {
2258
2694
  return 0;
2259
2695
  }
2260
2696
  async getColorSpace() {
2261
- return this.delegate((track) => track.getColorSpace());
2697
+ return this.internalTrack.track.colorSpace ?? this.delegate((track) => track.getColorSpace());
2262
2698
  }
2263
2699
  async canBeTransparent() {
2264
2700
  return this.delegate((track) => track.canBeTransparent());
@@ -2321,11 +2757,11 @@ var DashInputFormat = class extends InputFormat {
2321
2757
  const DASH = new DashInputFormat();
2322
2758
  const DASH_FORMATS = [
2323
2759
  DASH,
2324
- MP4,
2760
+ MP4$1,
2325
2761
  QTFF,
2326
2762
  WEBM,
2327
2763
  MATROSKA,
2328
- MP3,
2764
+ MP3$1,
2329
2765
  ADTS
2330
2766
  ];
2331
2767
  //#endregion
@@ -2338,5 +2774,6 @@ var Input = class extends SegmentedMediabunnyInput {
2338
2774
  const isInput = (value) => value instanceof Input;
2339
2775
  const getSegmentedInput = (track) => track.getSegmentedInput();
2340
2776
  const getSegments = async (track) => track.getSegments();
2777
+ const ALL_FORMATS = [...ALL_FORMATS$1, DASH];
2341
2778
  //#endregion
2342
- export { DASH, DASH_FORMATS, FilePathSource, HLS_FORMATS, Input, UrlSource, asc, desc, getSegmentedInput, getSegments, isInput, prefer, preserveSubtitleBackingsOnInput };
2779
+ export { ALL_FORMATS, DASH, DASH_FORMATS, FilePathSource, HLS, HLS_FORMATS, Input, MP3, MP4, UrlSource, asc, desc, getSegmentedInput, getSegments, isInput, prefer, preserveSubtitleBackingsOnInput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dasha",
3
- "version": "4.1.0",
3
+ "version": "4.2.1",
4
4
  "description": "Streaming manifest parser",
5
5
  "files": [
6
6
  "dist"