@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,434 @@
1
+ import {
2
+ flushTextTrackMetadataCueSamples,
3
+ flushTextTrackUserdataCueSamples,
4
+ } from './mp4-remuxer';
5
+ import { ElementaryStreamTypes } from '../loader/fragment';
6
+ import { getCodecCompatibleName } from '../utils/codecs';
7
+ import { type ILogger, Logger } from '../utils/logger';
8
+ import { patchEncyptionData } from '../utils/mp4-tools';
9
+ import { getSampleData, parseInitSegment } from '../utils/mp4-tools';
10
+ import type { HlsConfig } from '../config';
11
+ import type { HlsEventEmitter } from '../events';
12
+ import type { DecryptData } from '../loader/level-key';
13
+ import type {
14
+ DemuxedAudioTrack,
15
+ DemuxedMetadataTrack,
16
+ DemuxedUserdataTrack,
17
+ PassthroughTrack,
18
+ } from '../types/demuxer';
19
+ import type {
20
+ InitSegmentData,
21
+ RemuxedTrack,
22
+ Remuxer,
23
+ RemuxerResult,
24
+ } from '../types/remuxer';
25
+ import type { TrackSet } from '../types/track';
26
+ import type { TypeSupported } from '../utils/codecs';
27
+ import type { InitData, InitDataTrack, TrackTimes } from '../utils/mp4-tools';
28
+ import type { TimestampOffset } from '../utils/timescale-conversion';
29
+
30
+ class PassThroughRemuxer extends Logger implements Remuxer {
31
+ private emitInitSegment: boolean = false;
32
+ private audioCodec?: string;
33
+ private videoCodec?: string;
34
+ private initData?: InitData;
35
+ private initPTS: TimestampOffset | null = null;
36
+ private initTracks?: TrackSet;
37
+ private lastEndTime: number | null = null;
38
+ private isVideoContiguous: boolean = false;
39
+
40
+ constructor(
41
+ observer: HlsEventEmitter,
42
+ config: HlsConfig,
43
+ typeSupported: TypeSupported,
44
+ logger: ILogger,
45
+ ) {
46
+ super('passthrough-remuxer', logger);
47
+ }
48
+
49
+ public destroy() {}
50
+
51
+ public resetTimeStamp(defaultInitPTS: TimestampOffset | null) {
52
+ this.lastEndTime = null;
53
+ const initPTS = this.initPTS;
54
+ if (initPTS && defaultInitPTS) {
55
+ if (
56
+ initPTS.baseTime === defaultInitPTS.baseTime &&
57
+ initPTS.timescale === defaultInitPTS.timescale
58
+ ) {
59
+ return;
60
+ }
61
+ }
62
+ this.initPTS = defaultInitPTS;
63
+ }
64
+
65
+ public resetNextTimestamp() {
66
+ this.isVideoContiguous = false;
67
+ this.lastEndTime = null;
68
+ }
69
+
70
+ public resetInitSegment(
71
+ initSegment: Uint8Array<ArrayBuffer> | undefined,
72
+ audioCodec: string | undefined,
73
+ videoCodec: string | undefined,
74
+ decryptdata: DecryptData | null,
75
+ ) {
76
+ this.audioCodec = audioCodec;
77
+ this.videoCodec = videoCodec;
78
+ this.generateInitSegment(initSegment, decryptdata);
79
+ this.emitInitSegment = true;
80
+ }
81
+
82
+ private generateInitSegment(
83
+ initSegment: Uint8Array<ArrayBuffer> | undefined,
84
+ decryptdata?: DecryptData | null,
85
+ ) {
86
+ let { audioCodec, videoCodec } = this;
87
+ if (!initSegment?.byteLength) {
88
+ this.initTracks = undefined;
89
+ this.initData = undefined;
90
+ return;
91
+ }
92
+ const { audio, video } = (this.initData = parseInitSegment(initSegment));
93
+
94
+ if (decryptdata) {
95
+ patchEncyptionData(initSegment, decryptdata);
96
+ } else {
97
+ const eitherTrack = audio || video;
98
+ if (eitherTrack?.encrypted) {
99
+ this.warn(
100
+ `Init segment with encrypted track with has no key ("${eitherTrack.codec}")!`,
101
+ );
102
+ }
103
+ }
104
+
105
+ // Get codec from initSegment
106
+ if (audio) {
107
+ audioCodec = getParsedTrackCodec(
108
+ audio,
109
+ ElementaryStreamTypes.AUDIO,
110
+ this,
111
+ );
112
+ }
113
+
114
+ if (video) {
115
+ videoCodec = getParsedTrackCodec(
116
+ video,
117
+ ElementaryStreamTypes.VIDEO,
118
+ this,
119
+ );
120
+ }
121
+
122
+ const tracks: TrackSet = {};
123
+ if (audio && video) {
124
+ tracks.audiovideo = {
125
+ container: 'video/mp4',
126
+ codec: audioCodec + ',' + videoCodec,
127
+ supplemental: video.supplemental,
128
+ encrypted: video.encrypted,
129
+ initSegment,
130
+ id: 'main',
131
+ };
132
+ } else if (audio) {
133
+ tracks.audio = {
134
+ container: 'audio/mp4',
135
+ codec: audioCodec,
136
+ encrypted: audio.encrypted,
137
+ initSegment,
138
+ id: 'audio',
139
+ };
140
+ } else if (video) {
141
+ tracks.video = {
142
+ container: 'video/mp4',
143
+ codec: videoCodec,
144
+ supplemental: video.supplemental,
145
+ encrypted: video.encrypted,
146
+ initSegment,
147
+ id: 'main',
148
+ };
149
+ } else {
150
+ this.warn('initSegment does not contain moov or trak boxes.');
151
+ }
152
+ this.initTracks = tracks;
153
+ }
154
+
155
+ public remux(
156
+ audioTrack: DemuxedAudioTrack,
157
+ videoTrack: PassthroughTrack,
158
+ id3Track: DemuxedMetadataTrack,
159
+ textTrack: DemuxedUserdataTrack,
160
+ timeOffset: number,
161
+ accurateTimeOffset: boolean,
162
+ ): RemuxerResult {
163
+ let { initPTS, lastEndTime } = this;
164
+ const result: RemuxerResult = {
165
+ audio: undefined,
166
+ video: undefined,
167
+ text: undefined,
168
+ id3: id3Track,
169
+ initSegment: undefined,
170
+ };
171
+
172
+ // If we haven't yet set a lastEndDTS, or it was reset, set it to the provided timeOffset. We want to use the
173
+ // lastEndDTS over timeOffset whenever possible; during progressive playback, the media source will not update
174
+ // the media duration (which is what timeOffset is provided as) before we need to process the next chunk.
175
+ if (!Number.isFinite(lastEndTime!)) {
176
+ lastEndTime = this.lastEndTime = timeOffset || 0;
177
+ }
178
+
179
+ // The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only
180
+ // audio or video (or both); adding it to video was an arbitrary choice.
181
+ const data = videoTrack.samples;
182
+ if (!data.length) {
183
+ return result;
184
+ }
185
+
186
+ const initSegment: InitSegmentData = {
187
+ initPTS: undefined,
188
+ timescale: undefined,
189
+ trackId: undefined,
190
+ };
191
+ let initData = this.initData;
192
+ if (!initData?.length) {
193
+ this.generateInitSegment(data);
194
+ initData = this.initData;
195
+ }
196
+ if (!initData?.length) {
197
+ // We can't remux if the initSegment could not be generated
198
+ this.warn('Failed to generate initSegment.');
199
+ return result;
200
+ }
201
+ if (this.emitInitSegment) {
202
+ initSegment.tracks = this.initTracks;
203
+ this.emitInitSegment = false;
204
+ }
205
+
206
+ const trackSampleData = getSampleData(data, initData, this);
207
+ const audioSampleTimestamps = initData.audio
208
+ ? trackSampleData[initData.audio.id]
209
+ : null;
210
+ const videoSampleTimestamps = initData.video
211
+ ? trackSampleData[initData.video.id]
212
+ : null;
213
+
214
+ const videoStartTime = toStartEndOrDefault(videoSampleTimestamps, Infinity);
215
+ const audioStartTime = toStartEndOrDefault(audioSampleTimestamps, Infinity);
216
+ const videoEndTime = toStartEndOrDefault(videoSampleTimestamps, 0, true);
217
+ const audioEndTime = toStartEndOrDefault(audioSampleTimestamps, 0, true);
218
+
219
+ let decodeTime = timeOffset;
220
+ let duration = 0;
221
+
222
+ const syncOnAudio =
223
+ audioSampleTimestamps &&
224
+ (!videoSampleTimestamps ||
225
+ (!initPTS && audioStartTime < videoStartTime) ||
226
+ (initPTS && initPTS.trackId === initData.audio!.id));
227
+ const baseOffsetSamples = syncOnAudio
228
+ ? audioSampleTimestamps
229
+ : videoSampleTimestamps;
230
+
231
+ if (baseOffsetSamples) {
232
+ const timescale = baseOffsetSamples.timescale;
233
+ const baseTime = baseOffsetSamples.start - timeOffset * timescale;
234
+ const trackId = syncOnAudio ? initData.audio!.id : initData.video!.id;
235
+
236
+ decodeTime = baseOffsetSamples.start / timescale;
237
+ duration = syncOnAudio
238
+ ? audioEndTime - audioStartTime
239
+ : videoEndTime - videoStartTime;
240
+
241
+ if (
242
+ (accurateTimeOffset || !initPTS) &&
243
+ (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
244
+ timescale !== initPTS.timescale)
245
+ ) {
246
+ if (initPTS) {
247
+ this.warn(
248
+ `Timestamps at playlist time: ${accurateTimeOffset ? '' : '~'}${timeOffset} ${baseTime / timescale} != initPTS: ${initPTS.baseTime / initPTS.timescale} (${initPTS.baseTime}/${initPTS.timescale}) trackId: ${initPTS.trackId}`,
249
+ );
250
+ }
251
+ this.log(
252
+ `Found initPTS at playlist time: ${timeOffset} offset: ${decodeTime - timeOffset} (${baseTime}/${timescale}) trackId: ${trackId}`,
253
+ );
254
+ initPTS = null;
255
+ initSegment.initPTS = baseTime;
256
+ initSegment.timescale = timescale;
257
+ initSegment.trackId = trackId;
258
+ }
259
+ } else {
260
+ this.warn(
261
+ `No audio or video samples found for initPTS at playlist time: ${timeOffset}`,
262
+ );
263
+ }
264
+ if (!initPTS) {
265
+ if (
266
+ !initSegment.timescale ||
267
+ initSegment.trackId === undefined ||
268
+ initSegment.initPTS === undefined
269
+ ) {
270
+ this.warn('Could not set initPTS');
271
+ initSegment.initPTS = decodeTime;
272
+ initSegment.timescale = 1;
273
+ initSegment.trackId = -1;
274
+ }
275
+ this.initPTS = initPTS = {
276
+ baseTime: initSegment.initPTS,
277
+ timescale: initSegment.timescale,
278
+ trackId: initSegment.trackId,
279
+ };
280
+ } else {
281
+ initSegment.initPTS = initPTS.baseTime;
282
+ initSegment.timescale = initPTS.timescale;
283
+ initSegment.trackId = initPTS.trackId;
284
+ }
285
+
286
+ const startTime = decodeTime - initPTS.baseTime / initPTS.timescale;
287
+ const endTime = startTime + duration;
288
+
289
+ if (duration > 0) {
290
+ this.lastEndTime = endTime;
291
+ } else {
292
+ this.warn('Duration parsed from mp4 should be greater than zero');
293
+ this.resetNextTimestamp();
294
+ }
295
+
296
+ const hasAudio = !!initData.audio;
297
+ const hasVideo = !!initData.video;
298
+
299
+ let type: any = '';
300
+ if (hasAudio) {
301
+ type += 'audio';
302
+ }
303
+
304
+ if (hasVideo) {
305
+ type += 'video';
306
+ }
307
+
308
+ const encrypted =
309
+ (initData.audio ? initData.audio.encrypted : false) ||
310
+ (initData.video ? initData.video.encrypted : false);
311
+
312
+ const track: RemuxedTrack = {
313
+ data1: data,
314
+ startPTS: startTime,
315
+ startDTS: startTime,
316
+ endPTS: endTime,
317
+ endDTS: endTime,
318
+ type,
319
+ hasAudio,
320
+ hasVideo,
321
+ nb: 1,
322
+ dropped: 0,
323
+ encrypted,
324
+ };
325
+
326
+ result.audio = hasAudio && !hasVideo ? track : undefined;
327
+ result.video = hasVideo ? track : undefined;
328
+ const videoSampleCount = videoSampleTimestamps?.sampleCount;
329
+ if (videoSampleCount) {
330
+ const firstKeyFrame = videoSampleTimestamps.keyFrameIndex;
331
+ const independent = firstKeyFrame !== -1;
332
+ track.nb = videoSampleCount;
333
+ track.dropped =
334
+ firstKeyFrame === 0 || this.isVideoContiguous
335
+ ? 0
336
+ : independent
337
+ ? firstKeyFrame
338
+ : videoSampleCount;
339
+ track.independent = independent;
340
+ track.firstKeyFrame = firstKeyFrame;
341
+ if (independent && videoSampleTimestamps.keyFrameStart) {
342
+ track.firstKeyFramePTS =
343
+ (videoSampleTimestamps.keyFrameStart - initPTS.baseTime) /
344
+ initPTS.timescale;
345
+ }
346
+ if (!this.isVideoContiguous) {
347
+ result.independent = independent;
348
+ }
349
+ this.isVideoContiguous ||= independent;
350
+ if (track.dropped) {
351
+ this.warn(
352
+ `fmp4 does not start with IDR: firstIDR ${firstKeyFrame}/${videoSampleCount} dropped: ${track.dropped} start: ${track.firstKeyFramePTS || 'NA'}`,
353
+ );
354
+ }
355
+ }
356
+
357
+ result.initSegment = initSegment;
358
+ result.id3 = flushTextTrackMetadataCueSamples(
359
+ id3Track,
360
+ timeOffset,
361
+ initPTS,
362
+ initPTS,
363
+ );
364
+
365
+ if (textTrack.samples.length) {
366
+ result.text = flushTextTrackUserdataCueSamples(
367
+ textTrack,
368
+ timeOffset,
369
+ initPTS,
370
+ );
371
+ }
372
+
373
+ return result;
374
+ }
375
+ }
376
+
377
+ function toStartEndOrDefault(
378
+ trackTimes: TrackTimes | null,
379
+ defaultValue: number,
380
+ end: boolean = false,
381
+ ): number {
382
+ return trackTimes?.start !== undefined
383
+ ? (trackTimes.start + (end ? trackTimes.duration : 0)) /
384
+ trackTimes.timescale
385
+ : defaultValue;
386
+ }
387
+
388
+ function isInvalidInitPts(
389
+ initPTS: TimestampOffset | null,
390
+ startDTS: number,
391
+ timeOffset: number,
392
+ duration: number,
393
+ ): initPTS is null {
394
+ if (initPTS === null) {
395
+ return true;
396
+ }
397
+ // InitPTS is invalid when distance from program would be more than or equal to segment duration or a minimum of one second
398
+ const minDuration = Math.max(duration, 1);
399
+ const startTime = startDTS - initPTS.baseTime / initPTS.timescale;
400
+ return Math.abs(startTime - timeOffset) >= minDuration;
401
+ }
402
+
403
+ function getParsedTrackCodec(
404
+ track: InitDataTrack,
405
+ type: ElementaryStreamTypes.AUDIO | ElementaryStreamTypes.VIDEO,
406
+ logger: ILogger,
407
+ ): string {
408
+ const parsedCodec = track.codec;
409
+ if (parsedCodec && parsedCodec.length > 4) {
410
+ return parsedCodec;
411
+ }
412
+ if (type === ElementaryStreamTypes.AUDIO) {
413
+ if (
414
+ parsedCodec === 'ec-3' ||
415
+ parsedCodec === 'ac-3' ||
416
+ parsedCodec === 'alac'
417
+ ) {
418
+ return parsedCodec;
419
+ }
420
+ if (parsedCodec === 'fLaC' || parsedCodec === 'Opus') {
421
+ // Opting not to get `preferManagedMediaSource` from player config for isSupported() check for simplicity
422
+ const preferManagedMediaSource = false;
423
+ return getCodecCompatibleName(parsedCodec, preferManagedMediaSource);
424
+ }
425
+
426
+ logger.warn(`Unhandled audio codec "${parsedCodec}" in mp4 MAP`);
427
+ return parsedCodec || 'mp4a';
428
+ }
429
+ // Provide defaults based on codec type
430
+ // This allows for some playback of some fmp4 playlists without CODECS defined in manifest
431
+ logger.warn(`Unhandled video codec "${parsedCodec}" in mp4 MAP`);
432
+ return parsedCodec || 'avc1';
433
+ }
434
+ export default PassThroughRemuxer;
@@ -0,0 +1,130 @@
1
+ import { type ILogger, Logger } from './utils/logger';
2
+
3
+ /**
4
+ * @ignore
5
+ * Sub-class specialization of EventHandler base class.
6
+ *
7
+ * TaskLoop allows to schedule a task function being called (optionnaly repeatedly) on the main loop,
8
+ * scheduled asynchroneously, avoiding recursive calls in the same tick.
9
+ *
10
+ * The task itself is implemented in `doTick`. It can be requested and called for single execution
11
+ * using the `tick` method.
12
+ *
13
+ * It will be assured that the task execution method (`tick`) only gets called once per main loop "tick",
14
+ * no matter how often it gets requested for execution. Execution in further ticks will be scheduled accordingly.
15
+ *
16
+ * If further execution requests have already been scheduled on the next tick, it can be checked with `hasNextTick`,
17
+ * and cancelled with `clearNextTick`.
18
+ *
19
+ * The task can be scheduled as an interval repeatedly with a period as parameter (see `setInterval`, `clearInterval`).
20
+ *
21
+ * Sub-classes need to implement the `doTick` method which will effectively have the task execution routine.
22
+ *
23
+ * Further explanations:
24
+ *
25
+ * The baseclass has a `tick` method that will schedule the doTick call. It may be called synchroneously
26
+ * only for a stack-depth of one. On re-entrant calls, sub-sequent calls are scheduled for next main loop ticks.
27
+ *
28
+ * When the task execution (`tick` method) is called in re-entrant way this is detected and
29
+ * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
30
+ * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
31
+ */
32
+ export default class TaskLoop extends Logger {
33
+ private readonly _boundTick: () => void;
34
+ private _tickTimer: number | null = null;
35
+ private _tickInterval: number | null = null;
36
+ private _tickCallCount = 0;
37
+
38
+ constructor(label: string, logger: ILogger) {
39
+ super(label, logger);
40
+ this._boundTick = this.tick.bind(this);
41
+ }
42
+
43
+ public destroy() {
44
+ this.onHandlerDestroying();
45
+ this.onHandlerDestroyed();
46
+ }
47
+
48
+ protected onHandlerDestroying() {
49
+ // clear all timers before unregistering from event bus
50
+ this.clearNextTick();
51
+ this.clearInterval();
52
+ }
53
+
54
+ protected onHandlerDestroyed() {}
55
+
56
+ public hasInterval(): boolean {
57
+ return !!this._tickInterval;
58
+ }
59
+
60
+ public hasNextTick(): boolean {
61
+ return !!this._tickTimer;
62
+ }
63
+
64
+ /**
65
+ * @param millis - Interval time (ms)
66
+ * @eturns True when interval has been scheduled, false when already scheduled (no effect)
67
+ */
68
+ public setInterval(millis: number): boolean {
69
+ if (!this._tickInterval) {
70
+ this._tickCallCount = 0;
71
+ this._tickInterval = self.setInterval(this._boundTick, millis);
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+
77
+ /**
78
+ * @returns True when interval was cleared, false when none was set (no effect)
79
+ */
80
+ public clearInterval(): boolean {
81
+ if (this._tickInterval) {
82
+ self.clearInterval(this._tickInterval);
83
+ this._tickInterval = null;
84
+ return true;
85
+ }
86
+ return false;
87
+ }
88
+
89
+ /**
90
+ * @returns True when timeout was cleared, false when none was set (no effect)
91
+ */
92
+ public clearNextTick(): boolean {
93
+ if (this._tickTimer) {
94
+ self.clearTimeout(this._tickTimer);
95
+ this._tickTimer = null;
96
+ return true;
97
+ }
98
+ return false;
99
+ }
100
+
101
+ /**
102
+ * Will call the subclass doTick implementation in this main loop tick
103
+ * or in the next one (via setTimeout(,0)) in case it has already been called
104
+ * in this tick (in case this is a re-entrant call).
105
+ */
106
+ public tick(): void {
107
+ this._tickCallCount++;
108
+ if (this._tickCallCount === 1) {
109
+ this.doTick();
110
+ // re-entrant call to tick from previous doTick call stack
111
+ // -> schedule a call on the next main loop iteration to process this task processing request
112
+ if (this._tickCallCount > 1) {
113
+ // make sure only one timer exists at any time at max
114
+ this.tickImmediate();
115
+ }
116
+ this._tickCallCount = 0;
117
+ }
118
+ }
119
+
120
+ public tickImmediate(): void {
121
+ this.clearNextTick();
122
+ this._tickTimer = self.setTimeout(this._boundTick, 0);
123
+ }
124
+
125
+ /**
126
+ * For subclass to implement task logic
127
+ * @abstract
128
+ */
129
+ protected doTick(): void {}
130
+ }
@@ -0,0 +1,44 @@
1
+ export type AutoCameraItem = {
2
+ x: number;
3
+ y: number;
4
+ focus: number;
5
+ reserved: [number, number, number, number];
6
+ };
7
+
8
+ export type TrackItem = {
9
+ trackId: number;
10
+ score: number;
11
+ box: [number, number, number, number];
12
+ reserved: [number, number, number, number];
13
+ };
14
+
15
+ export type DetItem = {
16
+ classId: number;
17
+ score: number;
18
+ box: [number, number, number, number];
19
+ reserved: [number, number, number, number];
20
+ };
21
+
22
+ export type FrameItem = {
23
+ frameIdx: number;
24
+ autoCameras: AutoCameraItem;
25
+ tracks: TrackItem[];
26
+ detections: DetItem[];
27
+ };
28
+
29
+ export type AipdMessage = {
30
+ version: number;
31
+ chunkIndex: number;
32
+ frameSize: number;
33
+ frames: FrameItem[];
34
+ };
35
+
36
+ export type AlgoChunk = {
37
+ fragSn: number;
38
+ algoUrl: string;
39
+ chunkIndex: number;
40
+ frameSize: number;
41
+ frameRate: number;
42
+ startFrameIndex: number;
43
+ frames: FrameItem[];
44
+ };
@@ -0,0 +1,105 @@
1
+ declare global {
2
+ interface ArrayBuffer {
3
+ ' buffer_kind'?: 'array';
4
+ }
5
+ interface Uint8Array {
6
+ ' buffer_kind'?: 'uint8';
7
+ }
8
+ }
9
+
10
+ export type SourceBufferName = 'video' | 'audio' | 'audiovideo';
11
+
12
+ /* eslint-disable no-restricted-globals */
13
+ // `SourceBuffer` global is restricted for is-supported check with prefixed MSE
14
+ export type ExtendedSourceBuffer = SourceBuffer & {
15
+ onbufferedchange?: ((this: SourceBuffer, ev: Event) => any) | null;
16
+ };
17
+ /* eslint-enable no-restricted-globals */
18
+
19
+ export interface BaseTrack {
20
+ id: 'audio' | 'main';
21
+ container: string;
22
+ codec?: string;
23
+ supplemental?: string;
24
+ encrypted?: boolean;
25
+ levelCodec?: string;
26
+ pendingCodec?: string;
27
+ metadata?: {
28
+ channelCount?: number;
29
+ width?: number;
30
+ height?: number;
31
+ };
32
+ }
33
+
34
+ export interface ParsedTrack extends BaseTrack {
35
+ initSegment?: Uint8Array;
36
+ }
37
+ export interface SourceBufferTrack extends BaseTrack {
38
+ buffer?: ExtendedSourceBuffer;
39
+ bufferAppendTimeoutId?: number;
40
+ listeners: SourceBufferListener[];
41
+ ending?: boolean;
42
+ ended?: boolean;
43
+ }
44
+
45
+ export interface PendingSourceBufferTrack extends SourceBufferTrack {
46
+ buffer: undefined;
47
+ }
48
+
49
+ export interface BufferCreatedTrack extends BaseTrack {
50
+ buffer: ExtendedSourceBuffer;
51
+ }
52
+
53
+ export type ParsedTrackSet = Partial<Record<SourceBufferName, ParsedTrack>>;
54
+
55
+ export type BaseTrackSet = Partial<Record<SourceBufferName, BaseTrack>>;
56
+
57
+ export type SourceBufferTrackSet = Partial<
58
+ Record<SourceBufferName, SourceBufferTrack>
59
+ >;
60
+
61
+ export type BufferCreatedTrackSet = Partial<
62
+ Record<SourceBufferName, BufferCreatedTrack>
63
+ >;
64
+
65
+ export type EmptyTuple = [null, null];
66
+
67
+ export type SourceBuffersTuple = [
68
+ (
69
+ | [Extract<SourceBufferName, 'video' | 'audiovideo'>, ExtendedSourceBuffer]
70
+ | EmptyTuple
71
+ ),
72
+ [Extract<SourceBufferName, 'audio'>, ExtendedSourceBuffer] | EmptyTuple,
73
+ ];
74
+
75
+ export type MediaOverrides = {
76
+ duration?: number;
77
+ endOfStream?: boolean;
78
+ };
79
+
80
+ export interface BufferOperationQueues {
81
+ video: Array<BufferOperation>;
82
+ audio: Array<BufferOperation>;
83
+ audiovideo: Array<BufferOperation>;
84
+ }
85
+
86
+ export interface BufferOperation {
87
+ label: string;
88
+ execute: Function;
89
+ onStart: Function;
90
+ onComplete: Function;
91
+ onError: Function;
92
+ start?: number;
93
+ end?: number;
94
+ }
95
+
96
+ export interface SourceBufferListener {
97
+ event: string;
98
+ listener: EventListener;
99
+ }
100
+
101
+ export type AttachMediaSourceData = {
102
+ media: HTMLMediaElement;
103
+ mediaSource: MediaSource | null;
104
+ tracks: SourceBufferTrackSet;
105
+ };