@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,401 @@
1
+ import BaseVideoParser from './base-video-parser';
2
+ import ExpGolomb from './exp-golomb';
3
+ import { parseSEIMessageFromNALu } from '../../utils/mp4-tools';
4
+ import type {
5
+ DemuxedUserdataTrack,
6
+ DemuxedVideoTrack,
7
+ } from '../../types/demuxer';
8
+ import type { PES } from '../tsdemuxer';
9
+
10
+ class AvcVideoParser extends BaseVideoParser {
11
+ public parsePES(
12
+ track: DemuxedVideoTrack,
13
+ textTrack: DemuxedUserdataTrack,
14
+ pes: PES,
15
+ endOfSegment: boolean,
16
+ ) {
17
+ const units = this.parseNALu(track, pes.data, endOfSegment);
18
+ let VideoSample = this.VideoSample;
19
+ let push: boolean;
20
+ let spsfound = false;
21
+ // free pes.data to save up some memory
22
+ (pes as any).data = null;
23
+
24
+ // if new NAL units found and last sample still there, let's push ...
25
+ // this helps parsing streams with missing AUD (only do this if AUD never found)
26
+ if (VideoSample && units.length && !track.audFound) {
27
+ this.pushAccessUnit(VideoSample, track);
28
+ VideoSample = this.VideoSample = this.createVideoSample(
29
+ false,
30
+ pes.pts,
31
+ pes.dts,
32
+ );
33
+ }
34
+
35
+ units.forEach((unit) => {
36
+ switch (unit.type) {
37
+ // NDR
38
+ case 1: {
39
+ let iskey = false;
40
+ push = true;
41
+ const data = unit.data;
42
+ // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
43
+ if (spsfound && data.length > 4) {
44
+ // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
45
+ const sliceType = this.readSliceType(data);
46
+ // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
47
+ // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
48
+ // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
49
+ // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
50
+ // if (sliceType === 2 || sliceType === 7) {
51
+ if (
52
+ sliceType === 2 ||
53
+ sliceType === 4 ||
54
+ sliceType === 7 ||
55
+ sliceType === 9
56
+ ) {
57
+ iskey = true;
58
+ }
59
+ }
60
+
61
+ if (iskey) {
62
+ // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
63
+ if (VideoSample?.frame && !VideoSample.key) {
64
+ this.pushAccessUnit(VideoSample, track);
65
+ VideoSample = this.VideoSample = null;
66
+ }
67
+ }
68
+
69
+ if (!VideoSample) {
70
+ VideoSample = this.VideoSample = this.createVideoSample(
71
+ true,
72
+ pes.pts,
73
+ pes.dts,
74
+ );
75
+ }
76
+ VideoSample.frame = true;
77
+ VideoSample.key = iskey;
78
+
79
+ break;
80
+ // IDR
81
+ }
82
+ case 5:
83
+ push = true;
84
+ // handle PES not starting with AUD
85
+ // if we have frame data already, that cannot belong to the same frame, so force a push
86
+ if (VideoSample?.frame && !VideoSample.key) {
87
+ this.pushAccessUnit(VideoSample, track);
88
+ VideoSample = this.VideoSample = null;
89
+ }
90
+ if (!VideoSample) {
91
+ VideoSample = this.VideoSample = this.createVideoSample(
92
+ true,
93
+ pes.pts,
94
+ pes.dts,
95
+ );
96
+ }
97
+
98
+ VideoSample.key = true;
99
+ VideoSample.frame = true;
100
+ break;
101
+ // SEI
102
+ case 6: {
103
+ push = true;
104
+ parseSEIMessageFromNALu(
105
+ unit.data,
106
+ 1,
107
+ pes.pts as number,
108
+ textTrack.samples,
109
+ );
110
+ break;
111
+ // SPS
112
+ }
113
+ case 7: {
114
+ push = true;
115
+ spsfound = true;
116
+ const sps = unit.data;
117
+ const config = this.readSPS(sps);
118
+ if (
119
+ !track.sps ||
120
+ track.width !== config.width ||
121
+ track.height !== config.height ||
122
+ track.pixelRatio?.[0] !== config.pixelRatio[0] ||
123
+ track.pixelRatio?.[1] !== config.pixelRatio[1]
124
+ ) {
125
+ track.width = config.width;
126
+ track.height = config.height;
127
+ track.pixelRatio = config.pixelRatio;
128
+ track.sps = [sps];
129
+ const codecarray = sps.subarray(1, 4);
130
+ let codecstring = 'avc1.';
131
+ for (let i = 0; i < 3; i++) {
132
+ let h = codecarray[i].toString(16);
133
+ if (h.length < 2) {
134
+ h = '0' + h;
135
+ }
136
+
137
+ codecstring += h;
138
+ }
139
+ track.codec = codecstring;
140
+ }
141
+ break;
142
+ }
143
+ // PPS
144
+ case 8:
145
+ push = true;
146
+
147
+ track.pps = [unit.data];
148
+
149
+ break;
150
+ // AUD
151
+ case 9:
152
+ push = true;
153
+ track.audFound = true;
154
+ if (VideoSample?.frame) {
155
+ this.pushAccessUnit(VideoSample, track);
156
+ VideoSample = null;
157
+ }
158
+ if (!VideoSample) {
159
+ VideoSample = this.VideoSample = this.createVideoSample(
160
+ false,
161
+ pes.pts,
162
+ pes.dts,
163
+ );
164
+ }
165
+ break;
166
+ // Filler Data
167
+ case 12:
168
+ push = true;
169
+ break;
170
+ default:
171
+ push = false;
172
+
173
+ break;
174
+ }
175
+ if (VideoSample && push) {
176
+ const units = VideoSample.units;
177
+ units.push(unit);
178
+ }
179
+ });
180
+ // if last PES packet, push samples
181
+ if (endOfSegment && VideoSample) {
182
+ this.pushAccessUnit(VideoSample, track);
183
+ this.VideoSample = null;
184
+ }
185
+ }
186
+
187
+ protected getNALuType(data: Uint8Array, offset: number): number {
188
+ return data[offset] & 0x1f;
189
+ }
190
+
191
+ readSliceType(data: Uint8Array) {
192
+ const eg = new ExpGolomb(data);
193
+ // skip NALu type
194
+ eg.readUByte();
195
+ // discard first_mb_in_slice
196
+ eg.readUEG();
197
+ // return slice_type
198
+ return eg.readUEG();
199
+ }
200
+
201
+ /**
202
+ * The scaling list is optionally transmitted as part of a sequence parameter
203
+ * set and is not relevant to transmuxing.
204
+ * @param count the number of entries in this scaling list
205
+ * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
206
+ */
207
+ skipScalingList(count: number, reader: ExpGolomb): void {
208
+ let lastScale = 8;
209
+ let nextScale = 8;
210
+ let deltaScale;
211
+ for (let j = 0; j < count; j++) {
212
+ if (nextScale !== 0) {
213
+ deltaScale = reader.readEG();
214
+ nextScale = (lastScale + deltaScale + 256) % 256;
215
+ }
216
+ lastScale = nextScale === 0 ? lastScale : nextScale;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Read a sequence parameter set and return some interesting video
222
+ * properties. A sequence parameter set is the H264 metadata that
223
+ * describes the properties of upcoming video frames.
224
+ * @returns an object with configuration parsed from the
225
+ * sequence parameter set, including the dimensions of the
226
+ * associated video frames.
227
+ */
228
+ readSPS(sps: Uint8Array): {
229
+ width: number;
230
+ height: number;
231
+ pixelRatio: [number, number];
232
+ } {
233
+ const eg = new ExpGolomb(sps);
234
+ let frameCropLeftOffset = 0;
235
+ let frameCropRightOffset = 0;
236
+ let frameCropTopOffset = 0;
237
+ let frameCropBottomOffset = 0;
238
+ let numRefFramesInPicOrderCntCycle;
239
+ let scalingListCount;
240
+ let i;
241
+ const readUByte = eg.readUByte.bind(eg);
242
+ const readBits = eg.readBits.bind(eg);
243
+ const readUEG = eg.readUEG.bind(eg);
244
+ const readBoolean = eg.readBoolean.bind(eg);
245
+ const skipBits = eg.skipBits.bind(eg);
246
+ const skipEG = eg.skipEG.bind(eg);
247
+ const skipUEG = eg.skipUEG.bind(eg);
248
+ const skipScalingList = this.skipScalingList.bind(this);
249
+
250
+ readUByte();
251
+ const profileIdc = readUByte(); // profile_idc
252
+ readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
253
+ skipBits(3); // reserved_zero_3bits u(3),
254
+ readUByte(); // level_idc u(8)
255
+ skipUEG(); // seq_parameter_set_id
256
+ // some profiles have more optional data we don't need
257
+ if (
258
+ profileIdc === 100 ||
259
+ profileIdc === 110 ||
260
+ profileIdc === 122 ||
261
+ profileIdc === 244 ||
262
+ profileIdc === 44 ||
263
+ profileIdc === 83 ||
264
+ profileIdc === 86 ||
265
+ profileIdc === 118 ||
266
+ profileIdc === 128
267
+ ) {
268
+ const chromaFormatIdc = readUEG();
269
+ if (chromaFormatIdc === 3) {
270
+ skipBits(1);
271
+ } // separate_colour_plane_flag
272
+
273
+ skipUEG(); // bit_depth_luma_minus8
274
+ skipUEG(); // bit_depth_chroma_minus8
275
+ skipBits(1); // qpprime_y_zero_transform_bypass_flag
276
+ if (readBoolean()) {
277
+ // seq_scaling_matrix_present_flag
278
+ scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
279
+ for (i = 0; i < scalingListCount; i++) {
280
+ if (readBoolean()) {
281
+ // seq_scaling_list_present_flag[ i ]
282
+ if (i < 6) {
283
+ skipScalingList(16, eg);
284
+ } else {
285
+ skipScalingList(64, eg);
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+ skipUEG(); // log2_max_frame_num_minus4
292
+ const picOrderCntType = readUEG();
293
+ if (picOrderCntType === 0) {
294
+ readUEG(); // log2_max_pic_order_cnt_lsb_minus4
295
+ } else if (picOrderCntType === 1) {
296
+ skipBits(1); // delta_pic_order_always_zero_flag
297
+ skipEG(); // offset_for_non_ref_pic
298
+ skipEG(); // offset_for_top_to_bottom_field
299
+ numRefFramesInPicOrderCntCycle = readUEG();
300
+ for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
301
+ skipEG();
302
+ } // offset_for_ref_frame[ i ]
303
+ }
304
+ skipUEG(); // max_num_ref_frames
305
+ skipBits(1); // gaps_in_frame_num_value_allowed_flag
306
+ const picWidthInMbsMinus1 = readUEG();
307
+ const picHeightInMapUnitsMinus1 = readUEG();
308
+ const frameMbsOnlyFlag = readBits(1);
309
+ if (frameMbsOnlyFlag === 0) {
310
+ skipBits(1);
311
+ } // mb_adaptive_frame_field_flag
312
+
313
+ skipBits(1); // direct_8x8_inference_flag
314
+ if (readBoolean()) {
315
+ // frame_cropping_flag
316
+ frameCropLeftOffset = readUEG();
317
+ frameCropRightOffset = readUEG();
318
+ frameCropTopOffset = readUEG();
319
+ frameCropBottomOffset = readUEG();
320
+ }
321
+ let pixelRatio: [number, number] = [1, 1];
322
+ if (readBoolean()) {
323
+ // vui_parameters_present_flag
324
+ if (readBoolean()) {
325
+ // aspect_ratio_info_present_flag
326
+ const aspectRatioIdc = readUByte();
327
+ switch (aspectRatioIdc) {
328
+ case 1:
329
+ pixelRatio = [1, 1];
330
+ break;
331
+ case 2:
332
+ pixelRatio = [12, 11];
333
+ break;
334
+ case 3:
335
+ pixelRatio = [10, 11];
336
+ break;
337
+ case 4:
338
+ pixelRatio = [16, 11];
339
+ break;
340
+ case 5:
341
+ pixelRatio = [40, 33];
342
+ break;
343
+ case 6:
344
+ pixelRatio = [24, 11];
345
+ break;
346
+ case 7:
347
+ pixelRatio = [20, 11];
348
+ break;
349
+ case 8:
350
+ pixelRatio = [32, 11];
351
+ break;
352
+ case 9:
353
+ pixelRatio = [80, 33];
354
+ break;
355
+ case 10:
356
+ pixelRatio = [18, 11];
357
+ break;
358
+ case 11:
359
+ pixelRatio = [15, 11];
360
+ break;
361
+ case 12:
362
+ pixelRatio = [64, 33];
363
+ break;
364
+ case 13:
365
+ pixelRatio = [160, 99];
366
+ break;
367
+ case 14:
368
+ pixelRatio = [4, 3];
369
+ break;
370
+ case 15:
371
+ pixelRatio = [3, 2];
372
+ break;
373
+ case 16:
374
+ pixelRatio = [2, 1];
375
+ break;
376
+ case 255: {
377
+ pixelRatio = [
378
+ (readUByte() << 8) | readUByte(),
379
+ (readUByte() << 8) | readUByte(),
380
+ ];
381
+ break;
382
+ }
383
+ }
384
+ }
385
+ }
386
+ return {
387
+ width: Math.ceil(
388
+ (picWidthInMbsMinus1 + 1) * 16 -
389
+ frameCropLeftOffset * 2 -
390
+ frameCropRightOffset * 2,
391
+ ),
392
+ height:
393
+ (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 -
394
+ (frameMbsOnlyFlag ? 2 : 4) *
395
+ (frameCropTopOffset + frameCropBottomOffset),
396
+ pixelRatio: pixelRatio,
397
+ };
398
+ }
399
+ }
400
+
401
+ export default AvcVideoParser;
@@ -0,0 +1,198 @@
1
+ import { appendUint8Array } from '../../utils/mp4-tools';
2
+ import type {
3
+ DemuxedUserdataTrack,
4
+ DemuxedVideoTrack,
5
+ VideoSample,
6
+ VideoSampleUnit,
7
+ } from '../../types/demuxer';
8
+ import type { ParsedVideoSample } from '../tsdemuxer';
9
+ import type { PES } from '../tsdemuxer';
10
+
11
+ abstract class BaseVideoParser {
12
+ protected VideoSample: ParsedVideoSample | null = null;
13
+
14
+ protected createVideoSample(
15
+ key: boolean,
16
+ pts: number | undefined,
17
+ dts: number | undefined,
18
+ ): ParsedVideoSample {
19
+ return {
20
+ key,
21
+ frame: false,
22
+ pts,
23
+ dts,
24
+ units: [],
25
+ length: 0,
26
+ };
27
+ }
28
+
29
+ protected getLastNalUnit(
30
+ samples: VideoSample[],
31
+ ): VideoSampleUnit | undefined {
32
+ let VideoSample = this.VideoSample;
33
+ let lastUnit: VideoSampleUnit | undefined;
34
+ // try to fallback to previous sample if current one is empty
35
+ if (!VideoSample || VideoSample.units.length === 0) {
36
+ VideoSample = samples[samples.length - 1];
37
+ }
38
+ if (VideoSample?.units) {
39
+ const units = VideoSample.units;
40
+ lastUnit = units[units.length - 1];
41
+ }
42
+ return lastUnit;
43
+ }
44
+
45
+ protected pushAccessUnit(
46
+ VideoSample: ParsedVideoSample,
47
+ videoTrack: DemuxedVideoTrack,
48
+ ) {
49
+ if (VideoSample.units.length && VideoSample.frame) {
50
+ // if sample does not have PTS/DTS, patch with last sample PTS/DTS
51
+ if (VideoSample.pts === undefined) {
52
+ const samples = videoTrack.samples;
53
+ const nbSamples = samples.length;
54
+ if (nbSamples) {
55
+ const lastSample = samples[nbSamples - 1];
56
+ VideoSample.pts = lastSample.pts;
57
+ VideoSample.dts = lastSample.dts;
58
+ } else {
59
+ // dropping samples, no timestamp found
60
+ videoTrack.dropped++;
61
+ return;
62
+ }
63
+ }
64
+ videoTrack.samples.push(VideoSample as VideoSample);
65
+ }
66
+ }
67
+
68
+ abstract parsePES(
69
+ track: DemuxedVideoTrack,
70
+ textTrack: DemuxedUserdataTrack,
71
+ pes: PES,
72
+ last: boolean,
73
+ );
74
+
75
+ protected abstract getNALuType(data: Uint8Array, offset: number): number;
76
+
77
+ protected parseNALu(
78
+ track: DemuxedVideoTrack,
79
+ array: Uint8Array,
80
+ endOfSegment: boolean,
81
+ ): Array<{
82
+ data: Uint8Array;
83
+ type: number;
84
+ state?: number;
85
+ }> {
86
+ const len = array.byteLength;
87
+ let state = track.naluState || 0;
88
+ const lastState = state;
89
+ const units: VideoSampleUnit[] = [];
90
+ let i = 0;
91
+ let value: number;
92
+ let overflow: number;
93
+ let unitType: number;
94
+ let lastUnitStart = -1;
95
+ let lastUnitType: number = 0;
96
+ // logger.log('PES:' + Hex.hexDump(array));
97
+
98
+ if (state === -1) {
99
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
100
+ lastUnitStart = 0;
101
+ // NALu type is value read from offset 0
102
+ lastUnitType = this.getNALuType(array, 0);
103
+ state = 0;
104
+ i = 1;
105
+ }
106
+
107
+ while (i < len) {
108
+ value = array[i++];
109
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
110
+ if (!state) {
111
+ state = value ? 0 : 1;
112
+ continue;
113
+ }
114
+ if (state === 1) {
115
+ state = value ? 0 : 2;
116
+ continue;
117
+ }
118
+ // here we have state either equal to 2 or 3
119
+ if (!value) {
120
+ state = 3;
121
+ } else if (value === 1) {
122
+ overflow = i - state - 1;
123
+ if (lastUnitStart >= 0) {
124
+ const unit: VideoSampleUnit = {
125
+ data: array.subarray(lastUnitStart, overflow),
126
+ type: lastUnitType,
127
+ };
128
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
129
+ units.push(unit);
130
+ } else {
131
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
132
+ // first check if start code delimiter is overlapping between 2 PES packets,
133
+ // ie it started in last packet (lastState not zero)
134
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
135
+ const lastUnit = this.getLastNalUnit(track.samples);
136
+ if (lastUnit) {
137
+ if (lastState && i <= 4 - lastState) {
138
+ // start delimiter overlapping between PES packets
139
+ // strip start delimiter bytes from the end of last NAL unit
140
+ // check if lastUnit had a state different from zero
141
+ if (lastUnit.state) {
142
+ // strip last bytes
143
+ lastUnit.data = lastUnit.data.subarray(
144
+ 0,
145
+ lastUnit.data.byteLength - lastState,
146
+ );
147
+ }
148
+ }
149
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
150
+
151
+ if (overflow > 0) {
152
+ // logger.log('first NALU found with overflow:' + overflow);
153
+ lastUnit.data = appendUint8Array(
154
+ lastUnit.data,
155
+ array.subarray(0, overflow),
156
+ );
157
+ lastUnit.state = 0;
158
+ }
159
+ }
160
+ }
161
+ // check if we can read unit type
162
+ if (i < len) {
163
+ unitType = this.getNALuType(array, i);
164
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
165
+ lastUnitStart = i;
166
+ lastUnitType = unitType;
167
+ state = 0;
168
+ } else {
169
+ // not enough byte to read unit type. let's read it on next PES parsing
170
+ state = -1;
171
+ }
172
+ } else {
173
+ state = 0;
174
+ }
175
+ }
176
+ if (lastUnitStart >= 0 && state >= 0) {
177
+ const unit: VideoSampleUnit = {
178
+ data: array.subarray(lastUnitStart, len),
179
+ type: lastUnitType,
180
+ state: state,
181
+ };
182
+ units.push(unit);
183
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
184
+ }
185
+ // no NALu found
186
+ if (units.length === 0) {
187
+ // append pes.data to previous NAL unit
188
+ const lastUnit = this.getLastNalUnit(track.samples);
189
+ if (lastUnit) {
190
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
191
+ }
192
+ }
193
+ track.naluState = state;
194
+ return units;
195
+ }
196
+ }
197
+
198
+ export default BaseVideoParser;