@zenvor/hls.js 1.0.0

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.
Files changed (159) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +472 -0
  3. package/dist/hls-demo.js +26995 -0
  4. package/dist/hls-demo.js.map +1 -0
  5. package/dist/hls.d.mts +4204 -0
  6. package/dist/hls.d.ts +4204 -0
  7. package/dist/hls.js +40050 -0
  8. package/dist/hls.js.d.ts +4204 -0
  9. package/dist/hls.js.map +1 -0
  10. package/dist/hls.light.js +27145 -0
  11. package/dist/hls.light.js.map +1 -0
  12. package/dist/hls.light.min.js +2 -0
  13. package/dist/hls.light.min.js.map +1 -0
  14. package/dist/hls.light.mjs +26392 -0
  15. package/dist/hls.light.mjs.map +1 -0
  16. package/dist/hls.min.js +2 -0
  17. package/dist/hls.min.js.map +1 -0
  18. package/dist/hls.mjs +38956 -0
  19. package/dist/hls.mjs.map +1 -0
  20. package/dist/hls.worker.js +2 -0
  21. package/dist/hls.worker.js.map +1 -0
  22. package/package.json +143 -0
  23. package/src/config.ts +794 -0
  24. package/src/controller/abr-controller.ts +1019 -0
  25. package/src/controller/algo-data-controller.ts +794 -0
  26. package/src/controller/audio-stream-controller.ts +1099 -0
  27. package/src/controller/audio-track-controller.ts +454 -0
  28. package/src/controller/base-playlist-controller.ts +438 -0
  29. package/src/controller/base-stream-controller.ts +2526 -0
  30. package/src/controller/buffer-controller.ts +2015 -0
  31. package/src/controller/buffer-operation-queue.ts +159 -0
  32. package/src/controller/cap-level-controller.ts +367 -0
  33. package/src/controller/cmcd-controller.ts +422 -0
  34. package/src/controller/content-steering-controller.ts +622 -0
  35. package/src/controller/eme-controller.ts +1617 -0
  36. package/src/controller/error-controller.ts +627 -0
  37. package/src/controller/fps-controller.ts +146 -0
  38. package/src/controller/fragment-finders.ts +256 -0
  39. package/src/controller/fragment-tracker.ts +567 -0
  40. package/src/controller/gap-controller.ts +719 -0
  41. package/src/controller/id3-track-controller.ts +488 -0
  42. package/src/controller/interstitial-player.ts +302 -0
  43. package/src/controller/interstitials-controller.ts +2895 -0
  44. package/src/controller/interstitials-schedule.ts +698 -0
  45. package/src/controller/latency-controller.ts +294 -0
  46. package/src/controller/level-controller.ts +776 -0
  47. package/src/controller/stream-controller.ts +1597 -0
  48. package/src/controller/subtitle-stream-controller.ts +508 -0
  49. package/src/controller/subtitle-track-controller.ts +617 -0
  50. package/src/controller/timeline-controller.ts +677 -0
  51. package/src/crypt/aes-crypto.ts +36 -0
  52. package/src/crypt/aes-decryptor.ts +339 -0
  53. package/src/crypt/decrypter-aes-mode.ts +4 -0
  54. package/src/crypt/decrypter.ts +225 -0
  55. package/src/crypt/fast-aes-key.ts +39 -0
  56. package/src/define-plugin.d.ts +17 -0
  57. package/src/demux/audio/aacdemuxer.ts +126 -0
  58. package/src/demux/audio/ac3-demuxer.ts +170 -0
  59. package/src/demux/audio/adts.ts +249 -0
  60. package/src/demux/audio/base-audio-demuxer.ts +205 -0
  61. package/src/demux/audio/dolby.ts +21 -0
  62. package/src/demux/audio/mp3demuxer.ts +85 -0
  63. package/src/demux/audio/mpegaudio.ts +177 -0
  64. package/src/demux/chunk-cache.ts +42 -0
  65. package/src/demux/dummy-demuxed-track.ts +13 -0
  66. package/src/demux/inject-worker.ts +75 -0
  67. package/src/demux/mp4demuxer.ts +234 -0
  68. package/src/demux/sample-aes.ts +198 -0
  69. package/src/demux/transmuxer-interface.ts +449 -0
  70. package/src/demux/transmuxer-worker.ts +221 -0
  71. package/src/demux/transmuxer.ts +560 -0
  72. package/src/demux/tsdemuxer.ts +1256 -0
  73. package/src/demux/video/avc-video-parser.ts +401 -0
  74. package/src/demux/video/base-video-parser.ts +198 -0
  75. package/src/demux/video/exp-golomb.ts +153 -0
  76. package/src/demux/video/hevc-video-parser.ts +736 -0
  77. package/src/empty-es.js +5 -0
  78. package/src/empty.js +3 -0
  79. package/src/errors.ts +107 -0
  80. package/src/events.ts +548 -0
  81. package/src/exports-default.ts +3 -0
  82. package/src/exports-named.ts +81 -0
  83. package/src/hls.ts +1613 -0
  84. package/src/is-supported.ts +54 -0
  85. package/src/loader/date-range.ts +207 -0
  86. package/src/loader/fragment-loader.ts +403 -0
  87. package/src/loader/fragment.ts +487 -0
  88. package/src/loader/interstitial-asset-list.ts +162 -0
  89. package/src/loader/interstitial-event.ts +337 -0
  90. package/src/loader/key-loader.ts +439 -0
  91. package/src/loader/level-details.ts +203 -0
  92. package/src/loader/level-key.ts +259 -0
  93. package/src/loader/load-stats.ts +17 -0
  94. package/src/loader/m3u8-parser.ts +1072 -0
  95. package/src/loader/playlist-loader.ts +839 -0
  96. package/src/polyfills/number.ts +15 -0
  97. package/src/remux/aac-helper.ts +81 -0
  98. package/src/remux/mp4-generator.ts +1380 -0
  99. package/src/remux/mp4-remuxer.ts +1261 -0
  100. package/src/remux/passthrough-remuxer.ts +434 -0
  101. package/src/task-loop.ts +130 -0
  102. package/src/types/algo.ts +44 -0
  103. package/src/types/buffer.ts +105 -0
  104. package/src/types/component-api.ts +20 -0
  105. package/src/types/demuxer.ts +208 -0
  106. package/src/types/events.ts +574 -0
  107. package/src/types/fragment-tracker.ts +23 -0
  108. package/src/types/level.ts +268 -0
  109. package/src/types/loader.ts +198 -0
  110. package/src/types/media-playlist.ts +92 -0
  111. package/src/types/network-details.ts +3 -0
  112. package/src/types/remuxer.ts +104 -0
  113. package/src/types/track.ts +12 -0
  114. package/src/types/transmuxer.ts +46 -0
  115. package/src/types/tuples.ts +6 -0
  116. package/src/types/vtt.ts +11 -0
  117. package/src/utils/arrays.ts +22 -0
  118. package/src/utils/attr-list.ts +192 -0
  119. package/src/utils/binary-search.ts +46 -0
  120. package/src/utils/buffer-helper.ts +173 -0
  121. package/src/utils/cea-608-parser.ts +1413 -0
  122. package/src/utils/chunker.ts +41 -0
  123. package/src/utils/codecs.ts +314 -0
  124. package/src/utils/cues.ts +96 -0
  125. package/src/utils/discontinuities.ts +174 -0
  126. package/src/utils/encryption-methods-util.ts +21 -0
  127. package/src/utils/error-helper.ts +95 -0
  128. package/src/utils/event-listener-helper.ts +16 -0
  129. package/src/utils/ewma-bandwidth-estimator.ts +97 -0
  130. package/src/utils/ewma.ts +43 -0
  131. package/src/utils/fetch-loader.ts +331 -0
  132. package/src/utils/global.ts +2 -0
  133. package/src/utils/hash.ts +10 -0
  134. package/src/utils/hdr.ts +67 -0
  135. package/src/utils/hex.ts +32 -0
  136. package/src/utils/imsc1-ttml-parser.ts +261 -0
  137. package/src/utils/keysystem-util.ts +45 -0
  138. package/src/utils/level-helper.ts +629 -0
  139. package/src/utils/logger.ts +120 -0
  140. package/src/utils/media-option-attributes.ts +49 -0
  141. package/src/utils/mediacapabilities-helper.ts +301 -0
  142. package/src/utils/mediakeys-helper.ts +210 -0
  143. package/src/utils/mediasource-helper.ts +37 -0
  144. package/src/utils/mp4-tools.ts +1473 -0
  145. package/src/utils/number.ts +3 -0
  146. package/src/utils/numeric-encoding-utils.ts +26 -0
  147. package/src/utils/output-filter.ts +46 -0
  148. package/src/utils/rendition-helper.ts +505 -0
  149. package/src/utils/safe-json-stringify.ts +22 -0
  150. package/src/utils/texttrack-utils.ts +164 -0
  151. package/src/utils/time-ranges.ts +17 -0
  152. package/src/utils/timescale-conversion.ts +46 -0
  153. package/src/utils/utf8-utils.ts +18 -0
  154. package/src/utils/variable-substitution.ts +105 -0
  155. package/src/utils/vttcue.ts +384 -0
  156. package/src/utils/vttparser.ts +497 -0
  157. package/src/utils/webvtt-parser.ts +166 -0
  158. package/src/utils/xhr-loader.ts +337 -0
  159. package/src/version.ts +1 -0
@@ -0,0 +1,1473 @@
1
+ import { utf8ArrayToStr } from '@svta/common-media-library/utils/utf8ArrayToStr';
2
+ import { arrayToHex } from './hex';
3
+ import { ElementaryStreamTypes } from '../loader/fragment';
4
+ import { logger } from '../utils/logger';
5
+ import type { KeySystemIds } from './mediakeys-helper';
6
+ import type { DecryptData } from '../loader/level-key';
7
+ import type { PassthroughTrack, UserdataSample } from '../types/demuxer';
8
+ import type { ILogger } from '../utils/logger';
9
+
10
+ type BoxDataOrUndefined = Uint8Array<ArrayBuffer> | undefined;
11
+
12
+ const UINT32_MAX = Math.pow(2, 32) - 1;
13
+ const push = [].push;
14
+
15
+ // We are using fixed track IDs for driving the MP4 remuxer
16
+ // instead of following the TS PIDs.
17
+ // There is no reason not to do this and some browsers/SourceBuffer-demuxers
18
+ // may not like if there are TrackID "switches"
19
+ // See https://github.com/video-dev/hls.js/issues/1331
20
+ // Here we are mapping our internal track types to constant MP4 track IDs
21
+ // With MSE currently one can only have one track of each, and we are muxing
22
+ // whatever video/audio rendition in them.
23
+ export const RemuxerTrackIdConfig = {
24
+ video: 1,
25
+ audio: 2,
26
+ id3: 3,
27
+ text: 4,
28
+ };
29
+
30
+ export function bin2str(data: Uint8Array): string {
31
+ return String.fromCharCode.apply(null, data);
32
+ }
33
+
34
+ export function readUint16(buffer: Uint8Array, offset: number): number {
35
+ const val = (buffer[offset] << 8) | buffer[offset + 1];
36
+ return val < 0 ? 65536 + val : val;
37
+ }
38
+
39
+ export function readUint32(buffer: Uint8Array, offset: number): number {
40
+ const val = readSint32(buffer, offset);
41
+ return val < 0 ? 4294967296 + val : val;
42
+ }
43
+
44
+ export function readUint64(buffer: Uint8Array, offset: number) {
45
+ let result = readUint32(buffer, offset);
46
+ result *= Math.pow(2, 32);
47
+ result += readUint32(buffer, offset + 4);
48
+ return result;
49
+ }
50
+
51
+ export function readSint32(buffer: Uint8Array, offset: number): number {
52
+ return (
53
+ (buffer[offset] << 24) |
54
+ (buffer[offset + 1] << 16) |
55
+ (buffer[offset + 2] << 8) |
56
+ buffer[offset + 3]
57
+ );
58
+ }
59
+
60
+ export function writeUint32(buffer: Uint8Array, offset: number, value: number) {
61
+ buffer[offset] = value >> 24;
62
+ buffer[offset + 1] = (value >> 16) & 0xff;
63
+ buffer[offset + 2] = (value >> 8) & 0xff;
64
+ buffer[offset + 3] = value & 0xff;
65
+ }
66
+
67
+ // Find "moof" box
68
+ export function hasMoofData(data: Uint8Array): boolean {
69
+ const end = data.byteLength;
70
+ for (let i = 0; i < end; ) {
71
+ const size = readUint32(data, i);
72
+ if (
73
+ size > 8 &&
74
+ data[i + 4] === 0x6d &&
75
+ data[i + 5] === 0x6f &&
76
+ data[i + 6] === 0x6f &&
77
+ data[i + 7] === 0x66
78
+ ) {
79
+ return true;
80
+ }
81
+ i = size > 1 ? i + size : end;
82
+ }
83
+ return false;
84
+ }
85
+
86
+ // Find the data for a box specified by its path
87
+ export function findBox(data: Uint8Array, path: string[]): Uint8Array[] {
88
+ const results = [] as Uint8Array[];
89
+ if (!path.length) {
90
+ // short-circuit the search for empty paths
91
+ return results;
92
+ }
93
+ const end = data.byteLength;
94
+
95
+ for (let i = 0; i < end; ) {
96
+ const size = readUint32(data, i);
97
+ const type = bin2str(data.subarray(i + 4, i + 8));
98
+ const endbox = size > 1 ? i + size : end;
99
+ if (type === path[0]) {
100
+ if (path.length === 1) {
101
+ // this is the end of the path and we've found the box we were
102
+ // looking for
103
+ results.push(data.subarray(i + 8, endbox));
104
+ } else {
105
+ // recursively search for the next box along the path
106
+ const subresults = findBox(data.subarray(i + 8, endbox), path.slice(1));
107
+ if (subresults.length) {
108
+ push.apply(results, subresults);
109
+ }
110
+ }
111
+ }
112
+ i = endbox;
113
+ }
114
+
115
+ // we've finished searching all of data
116
+ return results;
117
+ }
118
+
119
+ type SidxInfo = {
120
+ earliestPresentationTime: number;
121
+ timescale: number;
122
+ version: number;
123
+ referencesCount: number;
124
+ references: any[];
125
+ };
126
+
127
+ function parseSegmentIndex(sidx: Uint8Array): SidxInfo | null {
128
+ const references: any[] = [];
129
+
130
+ const version = sidx[0];
131
+
132
+ // set initial offset, we skip the reference ID (not needed)
133
+ let index = 8;
134
+
135
+ const timescale = readUint32(sidx, index);
136
+ index += 4;
137
+
138
+ let earliestPresentationTime = 0;
139
+ let firstOffset = 0;
140
+
141
+ if (version === 0) {
142
+ earliestPresentationTime = readUint32(sidx, index);
143
+ firstOffset = readUint32(sidx, index + 4);
144
+ index += 8;
145
+ } else {
146
+ earliestPresentationTime = readUint64(sidx, index);
147
+ firstOffset = readUint64(sidx, index + 8);
148
+ index += 16;
149
+ }
150
+
151
+ // skip reserved
152
+ index += 2;
153
+
154
+ let startByte = sidx.length + firstOffset;
155
+
156
+ const referencesCount = readUint16(sidx, index);
157
+ index += 2;
158
+
159
+ for (let i = 0; i < referencesCount; i++) {
160
+ let referenceIndex = index;
161
+
162
+ const referenceInfo = readUint32(sidx, referenceIndex);
163
+ referenceIndex += 4;
164
+
165
+ const referenceSize = referenceInfo & 0x7fffffff;
166
+ const referenceType = (referenceInfo & 0x80000000) >>> 31;
167
+
168
+ if (referenceType === 1) {
169
+ logger.warn('SIDX has hierarchical references (not supported)');
170
+ return null;
171
+ }
172
+
173
+ const subsegmentDuration = readUint32(sidx, referenceIndex);
174
+ referenceIndex += 4;
175
+
176
+ references.push({
177
+ referenceSize,
178
+ subsegmentDuration, // unscaled
179
+ info: {
180
+ duration: subsegmentDuration / timescale,
181
+ start: startByte,
182
+ end: startByte + referenceSize - 1,
183
+ },
184
+ });
185
+
186
+ startByte += referenceSize;
187
+
188
+ // Skipping 1 bit for |startsWithSap|, 3 bits for |sapType|, and 28 bits
189
+ // for |sapDelta|.
190
+ referenceIndex += 4;
191
+
192
+ // skip to next ref
193
+ index = referenceIndex;
194
+ }
195
+
196
+ return {
197
+ earliestPresentationTime,
198
+ timescale,
199
+ version,
200
+ referencesCount,
201
+ references,
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Parses an MP4 initialization segment and extracts stream type and
207
+ * timescale values for any declared tracks. Timescale values indicate the
208
+ * number of clock ticks per second to assume for time-based values
209
+ * elsewhere in the MP4.
210
+ *
211
+ * To determine the start time of an MP4, you need two pieces of
212
+ * information: the timescale unit and the earliest base media decode
213
+ * time. Multiple timescales can be specified within an MP4 but the
214
+ * base media decode time is always expressed in the timescale from
215
+ * the media header box for the track:
216
+ * ```
217
+ * moov > trak > mdia > mdhd.timescale
218
+ * moov > trak > mdia > hdlr
219
+ * ```
220
+ * @param initSegment the bytes of the init segment
221
+ * @returns a hash of track type to timescale values or null if
222
+ * the init segment is malformed.
223
+ */
224
+
225
+ export interface InitDataTrack {
226
+ timescale: number;
227
+ id: number;
228
+ codec: string;
229
+ encrypted: boolean;
230
+ supplemental: string | undefined;
231
+ }
232
+
233
+ type HdlrMetadata = 'meta';
234
+ type HdlrType =
235
+ | ElementaryStreamTypes.AUDIO
236
+ | ElementaryStreamTypes.VIDEO
237
+ | HdlrMetadata;
238
+
239
+ type StsdData = {
240
+ codec: string;
241
+ encrypted: boolean;
242
+ supplemental: string | undefined;
243
+ };
244
+
245
+ export interface InitData extends Array<any> {
246
+ [index: number]:
247
+ | {
248
+ timescale: number;
249
+ type: HdlrType;
250
+ stsd: StsdData;
251
+ default?: {
252
+ duration: number;
253
+ flags: number;
254
+ };
255
+ }
256
+ | undefined;
257
+ audio?: InitDataTrack;
258
+ video?: InitDataTrack;
259
+ caption?: InitDataTrack;
260
+ }
261
+
262
+ export function parseInitSegment(initSegment: Uint8Array): InitData {
263
+ const result: InitData = [];
264
+ const traks = findBox(initSegment, ['moov', 'trak']);
265
+ for (let i = 0; i < traks.length; i++) {
266
+ const trak = traks[i];
267
+ const tkhd = findBox(trak, ['tkhd'])[0];
268
+ if (tkhd as any) {
269
+ let version = tkhd[0];
270
+ const trackId = readUint32(tkhd, version === 0 ? 12 : 20);
271
+ const mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
272
+ if (mdhd as any) {
273
+ version = mdhd[0];
274
+ const timescale = readUint32(mdhd, version === 0 ? 12 : 20);
275
+ const hdlr = findBox(trak, ['mdia', 'hdlr'])[0];
276
+ if (hdlr as any) {
277
+ const hdlrType = bin2str(hdlr.subarray(8, 12));
278
+ const type: HdlrType | undefined = {
279
+ soun: ElementaryStreamTypes.AUDIO as const,
280
+ vide: ElementaryStreamTypes.VIDEO as const,
281
+ }[hdlrType];
282
+ // Parse codec details
283
+ const stsdBox = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
284
+ const stsd = parseStsd(stsdBox);
285
+ if (type) {
286
+ // Add 'audio', 'video', and 'audiovideo' track records that will map to SourceBuffers
287
+ result[trackId] = { timescale, type, stsd };
288
+ result[type] = { timescale, id: trackId, ...stsd };
289
+ } else {
290
+ // Add 'meta' and other track records
291
+ result[trackId] = {
292
+ timescale,
293
+ type: hdlrType as HdlrType,
294
+ stsd,
295
+ };
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ const trex = findBox(initSegment, ['moov', 'mvex', 'trex']);
303
+ trex.forEach((trex) => {
304
+ const trackId = readUint32(trex, 4);
305
+ const track = result[trackId];
306
+ if (track) {
307
+ track.default = {
308
+ duration: readUint32(trex, 12),
309
+ flags: readUint32(trex, 20),
310
+ };
311
+ }
312
+ });
313
+
314
+ return result;
315
+ }
316
+
317
+ function parseStsd(stsd: Uint8Array): StsdData {
318
+ const sampleEntries = stsd.subarray(8);
319
+ const sampleEntriesEnd = sampleEntries.subarray(8 + 78);
320
+ const fourCC = bin2str(sampleEntries.subarray(4, 8));
321
+ let codec = fourCC;
322
+ let supplemental;
323
+ const encrypted = fourCC === 'enca' || fourCC === 'encv';
324
+ if (encrypted) {
325
+ const encBox = findBox(sampleEntries, [fourCC])[0];
326
+ const encBoxChildren = encBox.subarray(fourCC === 'enca' ? 28 : 78);
327
+ const sinfs = findBox(encBoxChildren, ['sinf']);
328
+ sinfs.forEach((sinf) => {
329
+ const schm = findBox(sinf, ['schm'])[0];
330
+ if (schm as any) {
331
+ const scheme = bin2str(schm.subarray(4, 8));
332
+ if (scheme === 'cbcs' || scheme === 'cenc') {
333
+ const frma = findBox(sinf, ['frma'])[0];
334
+ if (frma as any) {
335
+ // for encrypted content codec fourCC will be in frma
336
+ codec = bin2str(frma);
337
+ }
338
+ }
339
+ }
340
+ });
341
+ }
342
+ const codecFourCC = codec;
343
+ switch (codec) {
344
+ case 'avc1':
345
+ case 'avc2':
346
+ case 'avc3':
347
+ case 'avc4': {
348
+ // extract profile + compatibility + level out of avcC box
349
+ const avcCBox = findBox(sampleEntriesEnd, ['avcC'])[0];
350
+ if ((avcCBox as any) && avcCBox.length > 3) {
351
+ codec +=
352
+ '.' + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]);
353
+ supplemental = parseSupplementalDoViCodec(
354
+ codecFourCC === 'avc1' ? 'dva1' : 'dvav',
355
+ sampleEntriesEnd,
356
+ );
357
+ }
358
+ break;
359
+ }
360
+ case 'mp4a': {
361
+ const codecBox = findBox(sampleEntries, [fourCC])[0];
362
+ const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
363
+ if ((esdsBox as any) && esdsBox.length > 7) {
364
+ let i = 4;
365
+ // ES Descriptor tag
366
+ if (esdsBox[i++] !== 0x03) {
367
+ break;
368
+ }
369
+ i = skipBERInteger(esdsBox, i);
370
+ i += 2; // skip es_id;
371
+ const flags = esdsBox[i++];
372
+ if (flags & 0x80) {
373
+ i += 2; // skip dependency es_id
374
+ }
375
+ if (flags & 0x40) {
376
+ i += esdsBox[i++]; // skip URL
377
+ }
378
+ // Decoder config descriptor
379
+ if (esdsBox[i++] !== 0x04) {
380
+ break;
381
+ }
382
+ i = skipBERInteger(esdsBox, i);
383
+ const objectType = esdsBox[i++];
384
+ if (objectType === 0x40) {
385
+ codec += '.' + toHex(objectType);
386
+ } else {
387
+ break;
388
+ }
389
+ i += 12;
390
+ // Decoder specific info
391
+ if (esdsBox[i++] !== 0x05) {
392
+ break;
393
+ }
394
+ i = skipBERInteger(esdsBox, i);
395
+ const firstByte = esdsBox[i++];
396
+ let audioObjectType = (firstByte & 0xf8) >> 3;
397
+ if (audioObjectType === 31) {
398
+ audioObjectType +=
399
+ 1 + ((firstByte & 0x7) << 3) + ((esdsBox[i] & 0xe0) >> 5);
400
+ }
401
+ codec += '.' + audioObjectType;
402
+ }
403
+ break;
404
+ }
405
+ case 'hvc1':
406
+ case 'hev1': {
407
+ const hvcCBox = findBox(sampleEntriesEnd, ['hvcC'])[0];
408
+ if ((hvcCBox as any) && hvcCBox.length > 12) {
409
+ const profileByte = hvcCBox[1];
410
+ const profileSpace = ['', 'A', 'B', 'C'][profileByte >> 6];
411
+ const generalProfileIdc = profileByte & 0x1f;
412
+ const profileCompat = readUint32(hvcCBox, 2);
413
+ const tierFlag = (profileByte & 0x20) >> 5 ? 'H' : 'L';
414
+ const levelIDC = hvcCBox[12];
415
+ const constraintIndicator = hvcCBox.subarray(6, 12);
416
+ codec += '.' + profileSpace + generalProfileIdc;
417
+ codec +=
418
+ '.' + reverse32BitInt(profileCompat).toString(16).toUpperCase();
419
+ codec += '.' + tierFlag + levelIDC;
420
+ let constraintString = '';
421
+ for (let i = constraintIndicator.length; i--; ) {
422
+ const byte = constraintIndicator[i];
423
+ if (byte || constraintString) {
424
+ const encodedByte = byte.toString(16).toUpperCase();
425
+ constraintString = '.' + encodedByte + constraintString;
426
+ }
427
+ }
428
+ codec += constraintString;
429
+ }
430
+ supplemental = parseSupplementalDoViCodec(
431
+ codecFourCC == 'hev1' ? 'dvhe' : 'dvh1',
432
+ sampleEntriesEnd,
433
+ );
434
+ break;
435
+ }
436
+ case 'dvh1':
437
+ case 'dvhe':
438
+ case 'dvav':
439
+ case 'dva1':
440
+ case 'dav1': {
441
+ codec = parseSupplementalDoViCodec(codec, sampleEntriesEnd) || codec;
442
+ break;
443
+ }
444
+ case 'vp09': {
445
+ const vpcCBox = findBox(sampleEntriesEnd, ['vpcC'])[0];
446
+ if ((vpcCBox as any) && vpcCBox.length > 6) {
447
+ const profile = vpcCBox[4];
448
+ const level = vpcCBox[5];
449
+ const bitDepth = (vpcCBox[6] >> 4) & 0x0f;
450
+ codec +=
451
+ '.' +
452
+ addLeadingZero(profile) +
453
+ '.' +
454
+ addLeadingZero(level) +
455
+ '.' +
456
+ addLeadingZero(bitDepth);
457
+ }
458
+ break;
459
+ }
460
+ case 'av01': {
461
+ const av1CBox = findBox(sampleEntriesEnd, ['av1C'])[0];
462
+ if ((av1CBox as any) && av1CBox.length > 2) {
463
+ const profile = av1CBox[1] >>> 5;
464
+ const level = av1CBox[1] & 0x1f;
465
+ const tierFlag = av1CBox[2] >>> 7 ? 'H' : 'M';
466
+ const highBitDepth = (av1CBox[2] & 0x40) >> 6;
467
+ const twelveBit = (av1CBox[2] & 0x20) >> 5;
468
+ const bitDepth =
469
+ profile === 2 && highBitDepth
470
+ ? twelveBit
471
+ ? 12
472
+ : 10
473
+ : highBitDepth
474
+ ? 10
475
+ : 8;
476
+ const monochrome = (av1CBox[2] & 0x10) >> 4;
477
+ const chromaSubsamplingX = (av1CBox[2] & 0x08) >> 3;
478
+ const chromaSubsamplingY = (av1CBox[2] & 0x04) >> 2;
479
+ const chromaSamplePosition = av1CBox[2] & 0x03;
480
+ // TODO: parse color_description_present_flag
481
+ // default it to BT.709/limited range for now
482
+ // more info https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax
483
+ const colorPrimaries = 1;
484
+ const transferCharacteristics = 1;
485
+ const matrixCoefficients = 1;
486
+ const videoFullRangeFlag = 0;
487
+ codec +=
488
+ '.' +
489
+ profile +
490
+ '.' +
491
+ addLeadingZero(level) +
492
+ tierFlag +
493
+ '.' +
494
+ addLeadingZero(bitDepth) +
495
+ '.' +
496
+ monochrome +
497
+ '.' +
498
+ chromaSubsamplingX +
499
+ chromaSubsamplingY +
500
+ chromaSamplePosition +
501
+ '.' +
502
+ addLeadingZero(colorPrimaries) +
503
+ '.' +
504
+ addLeadingZero(transferCharacteristics) +
505
+ '.' +
506
+ addLeadingZero(matrixCoefficients) +
507
+ '.' +
508
+ videoFullRangeFlag;
509
+ supplemental = parseSupplementalDoViCodec('dav1', sampleEntriesEnd);
510
+ }
511
+ break;
512
+ }
513
+ case 'ac-3':
514
+ case 'ec-3':
515
+ case 'alac':
516
+ case 'fLaC':
517
+ case 'Opus':
518
+ default:
519
+ break;
520
+ }
521
+ return { codec, encrypted, supplemental };
522
+ }
523
+
524
+ function parseSupplementalDoViCodec(
525
+ fourCC: string,
526
+ sampleEntriesEnd: Uint8Array,
527
+ ): string | undefined {
528
+ const dvvCResult = findBox(sampleEntriesEnd, ['dvvC']); // used by DoVi Profile 8 to 10
529
+ const dvXCBox = dvvCResult.length
530
+ ? dvvCResult[0]
531
+ : findBox(sampleEntriesEnd, ['dvcC'])[0]; // used by DoVi Profiles up to 7 and 20
532
+ if (dvXCBox as any) {
533
+ const doViProfile = (dvXCBox[2] >> 1) & 0x7f;
534
+ const doViLevel = ((dvXCBox[2] << 5) & 0x20) | ((dvXCBox[3] >> 3) & 0x1f);
535
+ return (
536
+ fourCC +
537
+ '.' +
538
+ addLeadingZero(doViProfile) +
539
+ '.' +
540
+ addLeadingZero(doViLevel)
541
+ );
542
+ }
543
+ }
544
+
545
+ function reverse32BitInt(val: number) {
546
+ let result = 0;
547
+ for (let i = 0; i < 32; i++) {
548
+ result |= ((val >> i) & 1) << (32 - 1 - i);
549
+ }
550
+ return result >>> 0;
551
+ }
552
+
553
+ function skipBERInteger(bytes: Uint8Array, i: number): number {
554
+ const limit = i + 5;
555
+ while (bytes[i++] & 0x80 && i < limit) {
556
+ /* do nothing */
557
+ }
558
+ return i;
559
+ }
560
+
561
+ function toHex(x: number): string {
562
+ return ('0' + x.toString(16).toUpperCase()).slice(-2);
563
+ }
564
+
565
+ function addLeadingZero(num: number): string {
566
+ return (num < 10 ? '0' : '') + num;
567
+ }
568
+
569
+ export function patchEncyptionData(
570
+ initSegment: Uint8Array<ArrayBuffer> | undefined,
571
+ decryptdata: DecryptData | null,
572
+ ) {
573
+ if (!initSegment || !decryptdata) {
574
+ return;
575
+ }
576
+ const keyId = decryptdata.keyId;
577
+ if (keyId && decryptdata.isCommonEncryption) {
578
+ applyToTencBoxes(initSegment, (tenc, isAudio) => {
579
+ // Look for default key id (keyID offset is always 8 within the tenc box):
580
+ const tencKeyId = tenc.subarray(8, 24);
581
+ if (!tencKeyId.some((b) => b !== 0)) {
582
+ logger.log(
583
+ `[eme] Patching keyId in 'enc${
584
+ isAudio ? 'a' : 'v'
585
+ }>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex(keyId)}`,
586
+ );
587
+ tenc.set(keyId, 8);
588
+ }
589
+ });
590
+ }
591
+ }
592
+
593
+ export function parseKeyIdsFromTenc(
594
+ initSegment: Uint8Array<ArrayBuffer>,
595
+ ): Uint8Array<ArrayBuffer>[] {
596
+ const keyIds: Uint8Array<ArrayBuffer>[] = [];
597
+ applyToTencBoxes(initSegment, (tenc) => keyIds.push(tenc.subarray(8, 24)));
598
+ return keyIds;
599
+ }
600
+
601
+ function applyToTencBoxes(
602
+ initSegment: Uint8Array<ArrayBuffer>,
603
+ predicate: (tenc: Uint8Array<ArrayBuffer>, isAudio: boolean) => void,
604
+ ) {
605
+ const traks = findBox(initSegment, ['moov', 'trak']);
606
+ traks.forEach((trak) => {
607
+ const stsd = findBox(trak, [
608
+ 'mdia',
609
+ 'minf',
610
+ 'stbl',
611
+ 'stsd',
612
+ ])[0] as BoxDataOrUndefined;
613
+ if (!stsd) return;
614
+ const sampleEntries = stsd.subarray(8);
615
+ let encBoxes = findBox(sampleEntries, ['enca']);
616
+ const isAudio = encBoxes.length > 0;
617
+ if (!isAudio) {
618
+ encBoxes = findBox(sampleEntries, ['encv']);
619
+ }
620
+ encBoxes.forEach((enc) => {
621
+ const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78);
622
+ const sinfBoxes = findBox(encBoxChildren, ['sinf']);
623
+ sinfBoxes.forEach((sinf) => {
624
+ const tenc = parseSinf(sinf);
625
+ if (tenc) {
626
+ predicate(tenc, isAudio);
627
+ }
628
+ });
629
+ });
630
+ });
631
+ }
632
+
633
+ export function parseSinf(sinf: Uint8Array): BoxDataOrUndefined {
634
+ const schm = findBox(sinf, ['schm'])[0] as BoxDataOrUndefined;
635
+ if (schm) {
636
+ const scheme = bin2str(schm.subarray(4, 8));
637
+ if (scheme === 'cbcs' || scheme === 'cenc') {
638
+ const tenc = findBox(sinf, ['schi', 'tenc'])[0] as BoxDataOrUndefined;
639
+ if (tenc) {
640
+ return tenc;
641
+ }
642
+ } else if (scheme === 'cbc2') {
643
+ /* no-op */
644
+ }
645
+ }
646
+ }
647
+
648
+ /*
649
+ For Reference:
650
+ aligned(8) class TrackFragmentHeaderBox
651
+ extends FullBox(‘tfhd’, 0, tf_flags){
652
+ unsigned int(32) track_ID;
653
+ // all the following are optional fields
654
+ unsigned int(64) base_data_offset;
655
+ unsigned int(32) sample_description_index;
656
+ unsigned int(32) default_sample_duration;
657
+ unsigned int(32) default_sample_size;
658
+ unsigned int(32) default_sample_flags
659
+ }
660
+ */
661
+ export type TrackTimes = {
662
+ duration: number;
663
+ keyFrameIndex?: number;
664
+ keyFrameStart?: number;
665
+ start: number;
666
+ sampleCount: number;
667
+ timescale: number;
668
+ type: HdlrType;
669
+ };
670
+
671
+ export function getSampleData(
672
+ data: Uint8Array,
673
+ initData: InitData,
674
+ logger: ILogger,
675
+ ): Record<number, TrackTimes> {
676
+ const tracks: Record<number, TrackTimes> = {};
677
+ const trafs = findBox(data, ['moof', 'traf']);
678
+ for (let i = 0; i < trafs.length; i++) {
679
+ const traf = trafs[i];
680
+ // There is only one tfhd & trun per traf
681
+ // This is true for CMAF style content, and we should perhaps check the ftyp
682
+ // and only look for a single trun then, but for ISOBMFF we should check
683
+ // for multiple track runs.
684
+ const tfhd = findBox(traf, ['tfhd'])[0];
685
+ // get the track id from the tfhd
686
+ const id = readUint32(tfhd, 4);
687
+ const track = initData[id];
688
+ if (!track) {
689
+ continue;
690
+ }
691
+ (tracks[id] as any) ||= {
692
+ start: NaN,
693
+ duration: 0,
694
+ sampleCount: 0,
695
+ timescale: track.timescale,
696
+ type: track.type,
697
+ };
698
+ const trackTimes: TrackTimes = tracks[id];
699
+ // get start DTS
700
+ const tfdt = findBox(traf, ['tfdt'])[0];
701
+
702
+ if (tfdt as any) {
703
+ const version = tfdt[0];
704
+ let baseTime = readUint32(tfdt, 4);
705
+ if (version === 1) {
706
+ // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild.
707
+ // This prevents large values from being used for initPTS, which can cause playlist sync issues.
708
+ // https://github.com/video-dev/hls.js/issues/5303
709
+ if (baseTime === UINT32_MAX) {
710
+ logger.warn(
711
+ `[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`,
712
+ );
713
+ } else {
714
+ baseTime *= UINT32_MAX + 1;
715
+ baseTime += readUint32(tfdt, 8);
716
+ }
717
+ }
718
+ if (
719
+ Number.isFinite(baseTime) &&
720
+ (!Number.isFinite(trackTimes.start) || baseTime < trackTimes.start)
721
+ ) {
722
+ trackTimes.start = baseTime;
723
+ }
724
+ }
725
+
726
+ const trackDefault = track.default;
727
+ const tfhdFlags = readUint32(tfhd, 0) | trackDefault?.flags!;
728
+ let defaultSampleDuration: number = trackDefault?.duration || 0;
729
+ if (tfhdFlags & 0x000008) {
730
+ // 0x000008 indicates the presence of the default_sample_duration field
731
+ if (tfhdFlags & 0x000002) {
732
+ // 0x000002 indicates the presence of the sample_description_index field, which precedes default_sample_duration
733
+ // If present, the default_sample_duration exists at byte offset 12
734
+ defaultSampleDuration = readUint32(tfhd, 12);
735
+ } else {
736
+ // Otherwise, the duration is at byte offset 8
737
+ defaultSampleDuration = readUint32(tfhd, 8);
738
+ }
739
+ }
740
+ const truns = findBox(traf, ['trun']);
741
+ let sampleDTS = trackTimes.start || 0;
742
+ let rawDuration = 0;
743
+ let sampleDuration = defaultSampleDuration;
744
+ for (let j = 0; j < truns.length; j++) {
745
+ const trun = truns[j];
746
+ const sampleCount = readUint32(trun, 4);
747
+ const sampleIndex = trackTimes.sampleCount;
748
+ trackTimes.sampleCount += sampleCount;
749
+ // Get duration from samples
750
+ const dataOffsetPresent = trun[3] & 0x01;
751
+ const firstSampleFlagsPresent = trun[3] & 0x04;
752
+ const sampleDurationPresent = trun[2] & 0x01;
753
+ const sampleSizePresent = trun[2] & 0x02;
754
+ const sampleFlagsPresent = trun[2] & 0x04;
755
+ const sampleCompositionTimeOffsetPresent = trun[2] & 0x08;
756
+ let offset = 8;
757
+ let remaining = sampleCount;
758
+ if (dataOffsetPresent) {
759
+ offset += 4;
760
+ }
761
+ if (firstSampleFlagsPresent && sampleCount) {
762
+ const isNonSyncSample = trun[offset + 1] & 0x01;
763
+ if (!isNonSyncSample && trackTimes.keyFrameIndex === undefined) {
764
+ trackTimes.keyFrameIndex = sampleIndex;
765
+ }
766
+ offset += 4;
767
+ if (sampleDurationPresent) {
768
+ sampleDuration = readUint32(trun, offset);
769
+ offset += 4;
770
+ } else {
771
+ sampleDuration = defaultSampleDuration;
772
+ }
773
+ if (sampleSizePresent) {
774
+ offset += 4;
775
+ }
776
+ if (sampleCompositionTimeOffsetPresent) {
777
+ offset += 4;
778
+ }
779
+ sampleDTS += sampleDuration;
780
+ rawDuration += sampleDuration;
781
+ remaining--;
782
+ }
783
+ while (remaining--) {
784
+ if (sampleDurationPresent) {
785
+ sampleDuration = readUint32(trun, offset);
786
+ offset += 4;
787
+ } else {
788
+ sampleDuration = defaultSampleDuration;
789
+ }
790
+ if (sampleSizePresent) {
791
+ offset += 4;
792
+ }
793
+ if (sampleFlagsPresent) {
794
+ const isNonSyncSample = trun[offset + 1] & 0x01;
795
+ if (!isNonSyncSample) {
796
+ if (trackTimes.keyFrameIndex === undefined) {
797
+ trackTimes.keyFrameIndex =
798
+ trackTimes.sampleCount - (remaining + 1);
799
+ trackTimes.keyFrameStart = sampleDTS;
800
+ }
801
+ }
802
+ offset += 4;
803
+ }
804
+ if (sampleCompositionTimeOffsetPresent) {
805
+ offset += 4;
806
+ }
807
+ sampleDTS += sampleDuration;
808
+ rawDuration += sampleDuration;
809
+ }
810
+ if (!rawDuration && defaultSampleDuration) {
811
+ rawDuration += defaultSampleDuration * sampleCount;
812
+ }
813
+ }
814
+ trackTimes.duration += rawDuration;
815
+ }
816
+ if (!Object.keys(tracks).some((trackId) => tracks[trackId].duration)) {
817
+ // If duration samples are not available in the traf use sidx subsegment_duration
818
+ let sidxMinStart = Infinity;
819
+ let sidxMaxEnd = 0;
820
+ const sidxs = findBox(data, ['sidx']);
821
+ for (let i = 0; i < sidxs.length; i++) {
822
+ const sidx = parseSegmentIndex(sidxs[i]);
823
+ if (sidx?.references) {
824
+ sidxMinStart = Math.min(
825
+ sidxMinStart,
826
+ sidx.earliestPresentationTime / sidx.timescale,
827
+ );
828
+ const subSegmentDuration = sidx.references.reduce(
829
+ (dur, ref) => dur + ref.info.duration || 0,
830
+ 0,
831
+ );
832
+ sidxMaxEnd = Math.max(
833
+ sidxMaxEnd,
834
+ subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale,
835
+ );
836
+ }
837
+ }
838
+ if (sidxMaxEnd && Number.isFinite(sidxMaxEnd)) {
839
+ Object.keys(tracks).forEach((trackId) => {
840
+ if (!tracks[trackId].duration) {
841
+ tracks[trackId].duration =
842
+ sidxMaxEnd * tracks[trackId].timescale - tracks[trackId].start;
843
+ }
844
+ });
845
+ }
846
+ }
847
+ return tracks;
848
+ }
849
+
850
+ // TODO: Check if the last moof+mdat pair is part of the valid range
851
+ export function segmentValidRange(
852
+ data: Uint8Array<ArrayBuffer>,
853
+ ): SegmentedRange {
854
+ const segmentedRange: SegmentedRange = {
855
+ valid: null,
856
+ remainder: null,
857
+ };
858
+
859
+ const moofs = findBox(data, ['moof']);
860
+ if (moofs.length < 2) {
861
+ segmentedRange.remainder = data;
862
+ return segmentedRange;
863
+ }
864
+ const last = moofs[moofs.length - 1];
865
+ // Offset by 8 bytes; findBox offsets the start by as much
866
+ segmentedRange.valid = data.slice(0, last.byteOffset - 8);
867
+ segmentedRange.remainder = data.slice(last.byteOffset - 8);
868
+ return segmentedRange;
869
+ }
870
+
871
+ export interface SegmentedRange {
872
+ valid: Uint8Array<ArrayBuffer> | null;
873
+ remainder: Uint8Array<ArrayBuffer> | null;
874
+ }
875
+
876
+ export function appendUint8Array(data1: Uint8Array, data2: Uint8Array) {
877
+ const temp = new Uint8Array(data1.length + data2.length);
878
+ temp.set(data1);
879
+ temp.set(data2, data1.length);
880
+ return temp;
881
+ }
882
+
883
+ export interface IEmsgParsingData {
884
+ schemeIdUri: string;
885
+ value: string;
886
+ timeScale: number;
887
+ presentationTimeDelta?: number;
888
+ presentationTime?: number;
889
+ eventDuration: number;
890
+ id: number;
891
+ payload: Uint8Array;
892
+ }
893
+
894
+ export function parseSamples(
895
+ timeOffset: number,
896
+ track: PassthroughTrack,
897
+ ): UserdataSample[] {
898
+ const seiSamples = [] as UserdataSample[];
899
+ const videoData = track.samples;
900
+ const timescale = track.timescale;
901
+ const trackId = track.id;
902
+ let isHEVCFlavor = false;
903
+
904
+ const moofs = findBox(videoData, ['moof']);
905
+ moofs.map((moof) => {
906
+ const moofOffset = moof.byteOffset - 8;
907
+ const trafs = findBox(moof, ['traf']);
908
+ trafs.map((traf) => {
909
+ // get the base media decode time from the tfdt
910
+ const baseTime = findBox(traf, ['tfdt']).map((tfdt) => {
911
+ const version = tfdt[0];
912
+ let result = readUint32(tfdt, 4);
913
+ if (version === 1) {
914
+ result *= Math.pow(2, 32);
915
+ result += readUint32(tfdt, 8);
916
+ }
917
+ return result / timescale;
918
+ })[0];
919
+
920
+ if ((baseTime as any) !== undefined) {
921
+ timeOffset = baseTime;
922
+ }
923
+
924
+ return findBox(traf, ['tfhd']).map((tfhd) => {
925
+ const id = readUint32(tfhd, 4);
926
+ const tfhdFlags = readUint32(tfhd, 0) & 0xffffff;
927
+ const baseDataOffsetPresent = (tfhdFlags & 0x000001) !== 0;
928
+ const sampleDescriptionIndexPresent = (tfhdFlags & 0x000002) !== 0;
929
+ const defaultSampleDurationPresent = (tfhdFlags & 0x000008) !== 0;
930
+ let defaultSampleDuration = 0;
931
+ const defaultSampleSizePresent = (tfhdFlags & 0x000010) !== 0;
932
+ let defaultSampleSize = 0;
933
+ const defaultSampleFlagsPresent = (tfhdFlags & 0x000020) !== 0;
934
+ let tfhdOffset = 8;
935
+
936
+ if (id === trackId) {
937
+ if (baseDataOffsetPresent) {
938
+ tfhdOffset += 8;
939
+ }
940
+ if (sampleDescriptionIndexPresent) {
941
+ tfhdOffset += 4;
942
+ }
943
+ if (defaultSampleDurationPresent) {
944
+ defaultSampleDuration = readUint32(tfhd, tfhdOffset);
945
+ tfhdOffset += 4;
946
+ }
947
+ if (defaultSampleSizePresent) {
948
+ defaultSampleSize = readUint32(tfhd, tfhdOffset);
949
+ tfhdOffset += 4;
950
+ }
951
+ if (defaultSampleFlagsPresent) {
952
+ tfhdOffset += 4;
953
+ }
954
+ if (track.type === 'video') {
955
+ isHEVCFlavor = isHEVC(track.codec);
956
+ }
957
+
958
+ findBox(traf, ['trun']).map((trun) => {
959
+ const version = trun[0];
960
+ const flags = readUint32(trun, 0) & 0xffffff;
961
+ const dataOffsetPresent = (flags & 0x000001) !== 0;
962
+ let dataOffset = 0;
963
+ const firstSampleFlagsPresent = (flags & 0x000004) !== 0;
964
+ const sampleDurationPresent = (flags & 0x000100) !== 0;
965
+ let sampleDuration = 0;
966
+ const sampleSizePresent = (flags & 0x000200) !== 0;
967
+ let sampleSize = 0;
968
+ const sampleFlagsPresent = (flags & 0x000400) !== 0;
969
+ const sampleCompositionOffsetsPresent = (flags & 0x000800) !== 0;
970
+ let compositionOffset = 0;
971
+ const sampleCount = readUint32(trun, 4);
972
+ let trunOffset = 8; // past version, flags, and sample count
973
+
974
+ if (dataOffsetPresent) {
975
+ dataOffset = readUint32(trun, trunOffset);
976
+ trunOffset += 4;
977
+ }
978
+ if (firstSampleFlagsPresent) {
979
+ trunOffset += 4;
980
+ }
981
+
982
+ let sampleOffset = dataOffset + moofOffset;
983
+
984
+ for (let ix = 0; ix < sampleCount; ix++) {
985
+ if (sampleDurationPresent) {
986
+ sampleDuration = readUint32(trun, trunOffset);
987
+ trunOffset += 4;
988
+ } else {
989
+ sampleDuration = defaultSampleDuration;
990
+ }
991
+ if (sampleSizePresent) {
992
+ sampleSize = readUint32(trun, trunOffset);
993
+ trunOffset += 4;
994
+ } else {
995
+ sampleSize = defaultSampleSize;
996
+ }
997
+ if (sampleFlagsPresent) {
998
+ trunOffset += 4;
999
+ }
1000
+ if (sampleCompositionOffsetsPresent) {
1001
+ if (version === 0) {
1002
+ compositionOffset = readUint32(trun, trunOffset);
1003
+ } else {
1004
+ compositionOffset = readSint32(trun, trunOffset);
1005
+ }
1006
+ trunOffset += 4;
1007
+ }
1008
+ if (track.type === ElementaryStreamTypes.VIDEO) {
1009
+ let naluTotalSize = 0;
1010
+ while (naluTotalSize < sampleSize) {
1011
+ const naluSize = readUint32(videoData, sampleOffset);
1012
+ sampleOffset += 4;
1013
+ if (isSEIMessage(isHEVCFlavor, videoData[sampleOffset])) {
1014
+ const data = videoData.subarray(
1015
+ sampleOffset,
1016
+ sampleOffset + naluSize,
1017
+ );
1018
+ parseSEIMessageFromNALu(
1019
+ data,
1020
+ isHEVCFlavor ? 2 : 1,
1021
+ timeOffset + compositionOffset / timescale,
1022
+ seiSamples,
1023
+ );
1024
+ }
1025
+ sampleOffset += naluSize;
1026
+ naluTotalSize += naluSize + 4;
1027
+ }
1028
+ }
1029
+
1030
+ timeOffset += sampleDuration / timescale;
1031
+ }
1032
+ });
1033
+ }
1034
+ });
1035
+ });
1036
+ });
1037
+ return seiSamples;
1038
+ }
1039
+
1040
+ export function isHEVC(codec: string | undefined) {
1041
+ if (!codec) {
1042
+ return false;
1043
+ }
1044
+ const baseCodec = codec.substring(0, 4);
1045
+ return (
1046
+ baseCodec === 'hvc1' ||
1047
+ baseCodec === 'hev1' ||
1048
+ // Dolby Vision
1049
+ baseCodec === 'dvh1' ||
1050
+ baseCodec === 'dvhe'
1051
+ );
1052
+ }
1053
+
1054
+ function isSEIMessage(isHEVCFlavor: boolean, naluHeader: number) {
1055
+ if (isHEVCFlavor) {
1056
+ const naluType = (naluHeader >> 1) & 0x3f;
1057
+ return naluType === 39 || naluType === 40;
1058
+ } else {
1059
+ const naluType = naluHeader & 0x1f;
1060
+ return naluType === 6;
1061
+ }
1062
+ }
1063
+
1064
+ export function parseSEIMessageFromNALu(
1065
+ unescapedData: Uint8Array,
1066
+ headerSize: number,
1067
+ pts: number,
1068
+ samples: UserdataSample[],
1069
+ ) {
1070
+ const data = discardEPB(unescapedData);
1071
+ let seiPtr = 0;
1072
+ // skip nal header
1073
+ seiPtr += headerSize;
1074
+ let payloadType = 0;
1075
+ let payloadSize = 0;
1076
+ let b = 0;
1077
+
1078
+ while (seiPtr < data.length) {
1079
+ payloadType = 0;
1080
+ do {
1081
+ if (seiPtr >= data.length) {
1082
+ break;
1083
+ }
1084
+ b = data[seiPtr++];
1085
+ payloadType += b;
1086
+ } while (b === 0xff);
1087
+
1088
+ // Parse payload size.
1089
+ payloadSize = 0;
1090
+ do {
1091
+ if (seiPtr >= data.length) {
1092
+ break;
1093
+ }
1094
+ b = data[seiPtr++];
1095
+ payloadSize += b;
1096
+ } while (b === 0xff);
1097
+
1098
+ const leftOver = data.length - seiPtr;
1099
+ // Create a variable to process the payload
1100
+ let payPtr = seiPtr;
1101
+
1102
+ // Increment the seiPtr to the end of the payload
1103
+ if (payloadSize < leftOver) {
1104
+ seiPtr += payloadSize;
1105
+ } else if (payloadSize > leftOver) {
1106
+ // Some type of corruption has happened?
1107
+ logger.error(
1108
+ `Malformed SEI payload. ${payloadSize} is too small, only ${leftOver} bytes left to parse.`,
1109
+ );
1110
+ // We might be able to parse some data, but let's be safe and ignore it.
1111
+ break;
1112
+ }
1113
+
1114
+ if (payloadType === 4) {
1115
+ const countryCode = data[payPtr++];
1116
+ if (countryCode === 181) {
1117
+ const providerCode = readUint16(data, payPtr);
1118
+ payPtr += 2;
1119
+
1120
+ if (providerCode === 49) {
1121
+ const userStructure = readUint32(data, payPtr);
1122
+ payPtr += 4;
1123
+
1124
+ if (userStructure === 0x47413934) {
1125
+ const userDataType = data[payPtr++];
1126
+
1127
+ // Raw CEA-608 bytes wrapped in CEA-708 packet
1128
+ if (userDataType === 3) {
1129
+ const firstByte = data[payPtr++];
1130
+ const totalCCs = 0x1f & firstByte;
1131
+ const enabled = 0x40 & firstByte;
1132
+ const totalBytes = enabled ? 2 + totalCCs * 3 : 0;
1133
+ const byteArray = new Uint8Array(totalBytes);
1134
+ if (enabled) {
1135
+ byteArray[0] = firstByte;
1136
+ for (let i = 1; i < totalBytes; i++) {
1137
+ byteArray[i] = data[payPtr++];
1138
+ }
1139
+ }
1140
+
1141
+ samples.push({
1142
+ type: userDataType,
1143
+ payloadType,
1144
+ pts,
1145
+ bytes: byteArray,
1146
+ });
1147
+ }
1148
+ }
1149
+ }
1150
+ }
1151
+ } else if (payloadType === 5) {
1152
+ if (payloadSize > 16) {
1153
+ const uuidStrArray: Array<string> = [];
1154
+ for (let i = 0; i < 16; i++) {
1155
+ const b = data[payPtr++].toString(16);
1156
+ uuidStrArray.push(b.length == 1 ? '0' + b : b);
1157
+
1158
+ if (i === 3 || i === 5 || i === 7 || i === 9) {
1159
+ uuidStrArray.push('-');
1160
+ }
1161
+ }
1162
+ const length = payloadSize - 16;
1163
+ const userDataBytes = new Uint8Array(length);
1164
+ for (let i = 0; i < length; i++) {
1165
+ userDataBytes[i] = data[payPtr++];
1166
+ }
1167
+
1168
+ samples.push({
1169
+ payloadType,
1170
+ pts,
1171
+ uuid: uuidStrArray.join(''),
1172
+ userData: utf8ArrayToStr(userDataBytes),
1173
+ userDataBytes,
1174
+ });
1175
+ }
1176
+ }
1177
+ }
1178
+ }
1179
+
1180
+ /**
1181
+ * remove Emulation Prevention bytes from a RBSP
1182
+ */
1183
+ export function discardEPB(data: Uint8Array): Uint8Array {
1184
+ const length = data.byteLength;
1185
+ const EPBPositions = [] as Array<number>;
1186
+ let i = 1;
1187
+
1188
+ // Find all `Emulation Prevention Bytes`
1189
+ while (i < length - 2) {
1190
+ if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
1191
+ EPBPositions.push(i + 2);
1192
+ i += 2;
1193
+ } else {
1194
+ i++;
1195
+ }
1196
+ }
1197
+
1198
+ // If no Emulation Prevention Bytes were found just return the original
1199
+ // array
1200
+ if (EPBPositions.length === 0) {
1201
+ return data;
1202
+ }
1203
+
1204
+ // Create a new array to hold the NAL unit data
1205
+ const newLength = length - EPBPositions.length;
1206
+ const newData = new Uint8Array(newLength);
1207
+ let sourceIndex = 0;
1208
+
1209
+ for (i = 0; i < newLength; sourceIndex++, i++) {
1210
+ if (sourceIndex === EPBPositions[0]) {
1211
+ // Skip this byte
1212
+ sourceIndex++;
1213
+ // Remove this position index
1214
+ EPBPositions.shift();
1215
+ }
1216
+ newData[i] = data[sourceIndex];
1217
+ }
1218
+ return newData;
1219
+ }
1220
+
1221
+ export function parseEmsg(data: Uint8Array): IEmsgParsingData {
1222
+ const version = data[0];
1223
+ let schemeIdUri: string = '';
1224
+ let value: string = '';
1225
+ let timeScale: number = 0;
1226
+ let presentationTimeDelta: number = 0;
1227
+ let presentationTime: number = 0;
1228
+ let eventDuration: number = 0;
1229
+ let id: number = 0;
1230
+ let offset: number = 0;
1231
+
1232
+ if (version === 0) {
1233
+ while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
1234
+ schemeIdUri += bin2str(data.subarray(offset, offset + 1));
1235
+ offset += 1;
1236
+ }
1237
+
1238
+ schemeIdUri += bin2str(data.subarray(offset, offset + 1));
1239
+ offset += 1;
1240
+
1241
+ while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
1242
+ value += bin2str(data.subarray(offset, offset + 1));
1243
+ offset += 1;
1244
+ }
1245
+
1246
+ value += bin2str(data.subarray(offset, offset + 1));
1247
+ offset += 1;
1248
+
1249
+ timeScale = readUint32(data, 12);
1250
+ presentationTimeDelta = readUint32(data, 16);
1251
+ eventDuration = readUint32(data, 20);
1252
+ id = readUint32(data, 24);
1253
+ offset = 28;
1254
+ } else if (version === 1) {
1255
+ offset += 4;
1256
+ timeScale = readUint32(data, offset);
1257
+ offset += 4;
1258
+ const leftPresentationTime = readUint32(data, offset);
1259
+ offset += 4;
1260
+ const rightPresentationTime = readUint32(data, offset);
1261
+ offset += 4;
1262
+ presentationTime = 2 ** 32 * leftPresentationTime + rightPresentationTime;
1263
+ if (!Number.isSafeInteger(presentationTime)) {
1264
+ presentationTime = Number.MAX_SAFE_INTEGER;
1265
+ logger.warn(
1266
+ 'Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box',
1267
+ );
1268
+ }
1269
+
1270
+ eventDuration = readUint32(data, offset);
1271
+ offset += 4;
1272
+ id = readUint32(data, offset);
1273
+ offset += 4;
1274
+
1275
+ while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
1276
+ schemeIdUri += bin2str(data.subarray(offset, offset + 1));
1277
+ offset += 1;
1278
+ }
1279
+
1280
+ schemeIdUri += bin2str(data.subarray(offset, offset + 1));
1281
+ offset += 1;
1282
+
1283
+ while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
1284
+ value += bin2str(data.subarray(offset, offset + 1));
1285
+ offset += 1;
1286
+ }
1287
+
1288
+ value += bin2str(data.subarray(offset, offset + 1));
1289
+ offset += 1;
1290
+ }
1291
+ const payload = data.subarray(offset, data.byteLength);
1292
+
1293
+ return {
1294
+ schemeIdUri,
1295
+ value,
1296
+ timeScale,
1297
+ presentationTime,
1298
+ presentationTimeDelta,
1299
+ eventDuration,
1300
+ id,
1301
+ payload,
1302
+ };
1303
+ }
1304
+
1305
+ export function mp4Box(type: ArrayLike<number>, ...payload: Uint8Array[]) {
1306
+ const len = payload.length;
1307
+ let size = 8;
1308
+ let i = len;
1309
+ while (i--) {
1310
+ size += payload[i].byteLength;
1311
+ }
1312
+ const result = new Uint8Array(size);
1313
+ result[0] = (size >> 24) & 0xff;
1314
+ result[1] = (size >> 16) & 0xff;
1315
+ result[2] = (size >> 8) & 0xff;
1316
+ result[3] = size & 0xff;
1317
+ result.set(type, 4);
1318
+ for (i = 0, size = 8; i < len; i++) {
1319
+ result.set(payload[i], size);
1320
+ size += payload[i].byteLength;
1321
+ }
1322
+ return result;
1323
+ }
1324
+
1325
+ export function mp4pssh(
1326
+ systemId: Uint8Array,
1327
+ keyids: Array<Uint8Array> | null,
1328
+ data: Uint8Array,
1329
+ ) {
1330
+ if (systemId.byteLength !== 16) {
1331
+ throw new RangeError('Invalid system id');
1332
+ }
1333
+ let version;
1334
+ let kids;
1335
+ if (keyids) {
1336
+ version = 1;
1337
+ kids = new Uint8Array(keyids.length * 16);
1338
+ for (let ix = 0; ix < keyids.length; ix++) {
1339
+ const k = keyids[ix]; // uint8array
1340
+ if (k.byteLength !== 16) {
1341
+ throw new RangeError('Invalid key');
1342
+ }
1343
+ kids.set(k, ix * 16);
1344
+ }
1345
+ } else {
1346
+ version = 0;
1347
+ kids = new Uint8Array();
1348
+ }
1349
+ let kidCount;
1350
+ if (version > 0) {
1351
+ kidCount = new Uint8Array(4);
1352
+ if (keyids!.length > 0) {
1353
+ new DataView(kidCount.buffer).setUint32(0, keyids!.length, false);
1354
+ }
1355
+ } else {
1356
+ kidCount = new Uint8Array();
1357
+ }
1358
+ const dataSize = new Uint8Array(4);
1359
+ if (data.byteLength > 0) {
1360
+ new DataView(dataSize.buffer).setUint32(0, data.byteLength, false);
1361
+ }
1362
+ return mp4Box(
1363
+ [112, 115, 115, 104],
1364
+ new Uint8Array([
1365
+ version,
1366
+ 0x00,
1367
+ 0x00,
1368
+ 0x00, // Flags
1369
+ ]),
1370
+ systemId, // 16 bytes
1371
+ kidCount,
1372
+ kids,
1373
+ dataSize,
1374
+ data,
1375
+ );
1376
+ }
1377
+
1378
+ export type PsshData = {
1379
+ version: 0 | 1;
1380
+ systemId: KeySystemIds;
1381
+ kids: null | Uint8Array<ArrayBuffer>[];
1382
+ data: null | Uint8Array<ArrayBuffer>;
1383
+ offset: number;
1384
+ size: number;
1385
+ };
1386
+
1387
+ export type PsshInvalidResult = {
1388
+ systemId?: undefined;
1389
+ kids?: undefined;
1390
+ offset: number;
1391
+ size: number;
1392
+ };
1393
+
1394
+ export function parseMultiPssh(
1395
+ initData: ArrayBuffer,
1396
+ ): (PsshData | PsshInvalidResult)[] {
1397
+ const results: (PsshData | PsshInvalidResult)[] = [];
1398
+ if (initData instanceof ArrayBuffer) {
1399
+ const length = initData.byteLength;
1400
+ let offset = 0;
1401
+ while (offset + 32 < length) {
1402
+ const view = new DataView(initData, offset);
1403
+ const pssh = parsePssh(view);
1404
+ results.push(pssh);
1405
+ offset += pssh.size;
1406
+ }
1407
+ }
1408
+ return results;
1409
+ }
1410
+
1411
+ function parsePssh(view: DataView<ArrayBuffer>): PsshData | PsshInvalidResult {
1412
+ const size = view.getUint32(0);
1413
+ const offset = view.byteOffset;
1414
+ const length = view.byteLength;
1415
+ if (length < size) {
1416
+ return {
1417
+ offset,
1418
+ size: length,
1419
+ };
1420
+ }
1421
+ const type = view.getUint32(4);
1422
+ if (type !== 0x70737368) {
1423
+ return { offset, size };
1424
+ }
1425
+ const version = view.getUint32(8) >>> 24;
1426
+ if (version !== 0 && version !== 1) {
1427
+ return { offset, size };
1428
+ }
1429
+ const buffer = view.buffer;
1430
+ const systemId = arrayToHex(
1431
+ new Uint8Array(buffer, offset + 12, 16),
1432
+ ) as KeySystemIds;
1433
+
1434
+ let kids: null | Uint8Array<ArrayBuffer>[] = null;
1435
+ let data: null | Uint8Array<ArrayBuffer> = null;
1436
+ let dataSizeOffset = 0;
1437
+
1438
+ if (version === 0) {
1439
+ dataSizeOffset = 28;
1440
+ } else {
1441
+ const kidCounts = view.getUint32(28);
1442
+ if (!kidCounts || length < 32 + kidCounts * 16) {
1443
+ return { offset, size };
1444
+ }
1445
+ kids = [];
1446
+ for (let i = 0; i < kidCounts; i++) {
1447
+ kids.push(new Uint8Array(buffer, offset + 32 + i * 16, 16));
1448
+ }
1449
+ dataSizeOffset = 32 + kidCounts * 16;
1450
+ }
1451
+
1452
+ if (!dataSizeOffset) {
1453
+ return { offset, size };
1454
+ }
1455
+
1456
+ const dataSizeOrKidCount = view.getUint32(dataSizeOffset);
1457
+ if (size - 32 < dataSizeOrKidCount) {
1458
+ return { offset, size };
1459
+ }
1460
+ data = new Uint8Array(
1461
+ buffer,
1462
+ offset + dataSizeOffset + 4,
1463
+ dataSizeOrKidCount,
1464
+ );
1465
+ return {
1466
+ version,
1467
+ systemId,
1468
+ kids,
1469
+ data,
1470
+ offset,
1471
+ size,
1472
+ };
1473
+ }