@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,488 @@
1
+ import { getId3Frames } from '@svta/common-media-library/id3/getId3Frames';
2
+ import { isId3TimestampFrame } from '@svta/common-media-library/id3/isId3TimestampFrame';
3
+ import { Events } from '../events';
4
+ import {
5
+ isDateRangeCueAttribute,
6
+ isSCTE35Attribute,
7
+ } from '../loader/date-range';
8
+ import { MetadataSchema } from '../types/demuxer';
9
+ import { hexToArrayBuffer } from '../utils/hex';
10
+ import { stringify } from '../utils/safe-json-stringify';
11
+ import { createTrackNode, removeCuesInRange } from '../utils/texttrack-utils';
12
+ import type { MediaFragment } from '../hls';
13
+ import type Hls from '../hls';
14
+ import type { DateRange } from '../loader/date-range';
15
+ import type { LevelDetails } from '../loader/level-details';
16
+ import type { ComponentAPI } from '../types/component-api';
17
+ import type {
18
+ BufferFlushingData,
19
+ FragParsingMetadataData,
20
+ LevelPTSUpdatedData,
21
+ LevelUpdatedData,
22
+ MediaAttachingData,
23
+ MediaDetachingData,
24
+ } from '../types/events';
25
+
26
+ declare global {
27
+ interface Window {
28
+ WebKitDataCue: VTTCue | void;
29
+ }
30
+ }
31
+
32
+ const MIN_CUE_DURATION = 0.25;
33
+
34
+ function getCueClass(): typeof VTTCue | typeof TextTrackCue | undefined {
35
+ if (typeof self === 'undefined') return undefined;
36
+ return (self.VTTCue as typeof VTTCue | undefined) || self.TextTrackCue;
37
+ }
38
+
39
+ function createCueWithDataFields(
40
+ Cue: typeof VTTCue | typeof TextTrackCue,
41
+ startTime: number,
42
+ endTime: number,
43
+ data: Object,
44
+ type?: string,
45
+ ): VTTCue | TextTrackCue | undefined {
46
+ let cue = new Cue(startTime, endTime, '');
47
+ try {
48
+ (cue as any).value = data;
49
+ if (type) {
50
+ (cue as any).type = type;
51
+ }
52
+ } catch (e) {
53
+ cue = new Cue(
54
+ startTime,
55
+ endTime,
56
+ stringify(type ? { type, ...data } : data),
57
+ );
58
+ }
59
+ return cue;
60
+ }
61
+
62
+ // VTTCue latest draft allows an infinite duration, fallback
63
+ // to MAX_VALUE if necessary
64
+ const MAX_CUE_ENDTIME = (() => {
65
+ const Cue = getCueClass();
66
+ try {
67
+ Cue && new Cue(0, Number.POSITIVE_INFINITY, '');
68
+ } catch (e) {
69
+ return Number.MAX_VALUE;
70
+ }
71
+ return Number.POSITIVE_INFINITY;
72
+ })();
73
+
74
+ class ID3TrackController implements ComponentAPI {
75
+ private hls: Hls | null;
76
+ private id3Track: HTMLTrackElement | null = null;
77
+ private media: HTMLMediaElement | null = null;
78
+ private dateRangeCuesAppended: Record<
79
+ string,
80
+ | {
81
+ cues: Record<string, VTTCue | TextTrackCue | undefined>;
82
+ dateRange: DateRange;
83
+ durationKnown: boolean;
84
+ }
85
+ | undefined
86
+ > = {};
87
+ private removeCues: boolean = true;
88
+ private assetCue?: VTTCue | TextTrackCue;
89
+
90
+ constructor(hls) {
91
+ this.hls = hls;
92
+ this._registerListeners();
93
+ }
94
+
95
+ public destroy() {
96
+ this._unregisterListeners();
97
+ this.id3Track = null;
98
+ this.media = null;
99
+ this.dateRangeCuesAppended = {};
100
+ // @ts-ignore
101
+ this.hls = this.onEventCueEnter = null;
102
+ }
103
+
104
+ private _registerListeners() {
105
+ const { hls } = this;
106
+ if (hls) {
107
+ hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
108
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
109
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
110
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
111
+ hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
112
+ hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
113
+ hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
114
+ hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
115
+ }
116
+ }
117
+
118
+ private _unregisterListeners() {
119
+ const { hls } = this;
120
+ if (hls) {
121
+ hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
122
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
123
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
124
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
125
+ hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
126
+ hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
127
+ hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
128
+ hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
129
+ }
130
+ }
131
+
132
+ private onEventCueEnter = () => {
133
+ if (!this.hls) {
134
+ return;
135
+ }
136
+ this.hls.trigger(Events.EVENT_CUE_ENTER, {});
137
+ };
138
+
139
+ // Add ID3 metatadata text track.
140
+ private onMediaAttaching(
141
+ event: Events.MEDIA_ATTACHING,
142
+ data: MediaAttachingData,
143
+ ): void {
144
+ this.media = data.media;
145
+ }
146
+
147
+ private onMediaAttached() {
148
+ const details = this.hls?.latestLevelDetails;
149
+ if (details) {
150
+ this.updateDateRangeCues(details);
151
+ }
152
+ }
153
+
154
+ private onMediaDetaching(
155
+ event: Events.MEDIA_DETACHING,
156
+ data: MediaDetachingData,
157
+ ) {
158
+ this.media = null;
159
+ const transferringMedia = !!data.transferMedia;
160
+ if (transferringMedia) {
161
+ return;
162
+ }
163
+ if (this.id3Track) {
164
+ this.id3Track.remove();
165
+ this.id3Track = null;
166
+ }
167
+ this.dateRangeCuesAppended = {};
168
+ }
169
+
170
+ private onManifestLoading() {
171
+ this.dateRangeCuesAppended = {};
172
+ }
173
+
174
+ private createTrack(media: HTMLMediaElement): HTMLTrackElement {
175
+ return createTrackNode(media, 'metadata', 'id3', '', 'hidden');
176
+ }
177
+
178
+ private onFragParsingMetadata(
179
+ event: Events.FRAG_PARSING_METADATA,
180
+ data: FragParsingMetadataData,
181
+ ) {
182
+ if (!this.media || !this.hls) {
183
+ return;
184
+ }
185
+
186
+ const { enableEmsgMetadataCues, enableID3MetadataCues } = this.hls.config;
187
+ if (!enableEmsgMetadataCues && !enableID3MetadataCues) {
188
+ return;
189
+ }
190
+
191
+ const { samples } = data;
192
+
193
+ // create track dynamically
194
+ if (!this.id3Track) {
195
+ this.id3Track = this.createTrack(this.media);
196
+ }
197
+
198
+ const Cue = getCueClass();
199
+ if (!Cue) {
200
+ return;
201
+ }
202
+
203
+ for (let i = 0; i < samples.length; i++) {
204
+ const type = samples[i].type;
205
+ if (
206
+ (type === MetadataSchema.emsg && !enableEmsgMetadataCues) ||
207
+ !enableID3MetadataCues
208
+ ) {
209
+ continue;
210
+ }
211
+
212
+ const frames = getId3Frames(samples[i].data);
213
+ const startTime = samples[i].pts;
214
+ let endTime: number = startTime + samples[i].duration;
215
+
216
+ if (endTime > MAX_CUE_ENDTIME) {
217
+ endTime = MAX_CUE_ENDTIME;
218
+ }
219
+
220
+ const timeDiff = endTime - startTime;
221
+ if (timeDiff <= 0) {
222
+ endTime = startTime + MIN_CUE_DURATION;
223
+ }
224
+
225
+ for (let j = 0; j < frames.length; j++) {
226
+ const frame = frames[j];
227
+ // Safari doesn't put the timestamp frame in the TextTrack
228
+ if (!isId3TimestampFrame(frame)) {
229
+ // add a bounds to any unbounded cues
230
+ this.updateId3CueEnds(startTime, type);
231
+ const cue = createCueWithDataFields(
232
+ Cue,
233
+ startTime,
234
+ endTime,
235
+ frame,
236
+ type,
237
+ );
238
+ if (cue) {
239
+ this.id3Track.track.addCue(cue);
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ private updateId3CueEnds(startTime: number, type: MetadataSchema) {
247
+ const cues = this.id3Track?.track.cues;
248
+ if (cues) {
249
+ for (let i = cues.length; i--; ) {
250
+ const cue = cues[i] as any;
251
+ if (
252
+ cue.type === type &&
253
+ cue.startTime < startTime &&
254
+ cue.endTime === MAX_CUE_ENDTIME
255
+ ) {
256
+ cue.endTime = startTime;
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ private onBufferFlushing(
263
+ event: Events.BUFFER_FLUSHING,
264
+ { startOffset, endOffset, type }: BufferFlushingData,
265
+ ) {
266
+ const { id3Track, hls } = this;
267
+ if (!hls) {
268
+ return;
269
+ }
270
+
271
+ const {
272
+ config: { enableEmsgMetadataCues, enableID3MetadataCues },
273
+ } = hls;
274
+ if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) {
275
+ let predicate;
276
+
277
+ if (type === 'audio') {
278
+ predicate = (cue) =>
279
+ (cue as any).type === MetadataSchema.audioId3 &&
280
+ enableID3MetadataCues;
281
+ } else if (type === 'video') {
282
+ predicate = (cue) =>
283
+ (cue as any).type === MetadataSchema.emsg && enableEmsgMetadataCues;
284
+ } else {
285
+ predicate = (cue) =>
286
+ ((cue as any).type === MetadataSchema.audioId3 &&
287
+ enableID3MetadataCues) ||
288
+ ((cue as any).type === MetadataSchema.emsg && enableEmsgMetadataCues);
289
+ }
290
+ removeCuesInRange(id3Track.track, startOffset, endOffset, predicate);
291
+ }
292
+ }
293
+
294
+ private onLevelUpdated(
295
+ event: Events.LEVEL_UPDATED,
296
+ { details }: LevelUpdatedData,
297
+ ) {
298
+ this.updateDateRangeCues(details, true);
299
+ }
300
+
301
+ private onLevelPtsUpdated(
302
+ event: Events.LEVEL_PTS_UPDATED,
303
+ data: LevelPTSUpdatedData,
304
+ ) {
305
+ if (Math.abs(data.drift) > 0.01) {
306
+ this.updateDateRangeCues(data.details);
307
+ }
308
+ }
309
+
310
+ private updateDateRangeCues(details: LevelDetails, removeOldCues?: true) {
311
+ if (!this.hls || !this.media) {
312
+ return;
313
+ }
314
+ const {
315
+ assetPlayerId,
316
+ timelineOffset,
317
+ enableDateRangeMetadataCues,
318
+ interstitialsController,
319
+ } = this.hls.config;
320
+ if (!enableDateRangeMetadataCues) {
321
+ return;
322
+ }
323
+
324
+ const Cue = getCueClass();
325
+ if (
326
+ __USE_INTERSTITIALS__ &&
327
+ assetPlayerId &&
328
+ timelineOffset &&
329
+ !interstitialsController
330
+ ) {
331
+ const { fragmentStart, fragmentEnd } = details;
332
+ let cue = this.assetCue;
333
+ if (cue) {
334
+ cue.startTime = fragmentStart;
335
+ cue.endTime = fragmentEnd;
336
+ } else if (Cue) {
337
+ cue = this.assetCue = createCueWithDataFields(
338
+ Cue,
339
+ fragmentStart,
340
+ fragmentEnd,
341
+ { assetPlayerId: this.hls.config.assetPlayerId },
342
+ 'hlsjs.interstitial.asset',
343
+ );
344
+ if (cue) {
345
+ cue.id = assetPlayerId;
346
+ this.id3Track ||= this.createTrack(this.media);
347
+ this.id3Track.track.addCue(cue);
348
+ cue.addEventListener('enter', this.onEventCueEnter);
349
+ }
350
+ }
351
+ }
352
+
353
+ if (!details.hasProgramDateTime) {
354
+ return;
355
+ }
356
+ const { id3Track } = this;
357
+ const { dateRanges } = details;
358
+ const ids = Object.keys(dateRanges);
359
+ let dateRangeCuesAppended = this.dateRangeCuesAppended;
360
+ // Remove cues from track not found in details.dateRanges
361
+ if (id3Track && removeOldCues) {
362
+ if (id3Track.track.cues?.length) {
363
+ const idsToRemove = Object.keys(dateRangeCuesAppended).filter(
364
+ (id) => !ids.includes(id),
365
+ );
366
+ for (let i = idsToRemove.length; i--; ) {
367
+ const id = idsToRemove[i];
368
+ const cues = dateRangeCuesAppended[id]?.cues;
369
+ delete dateRangeCuesAppended[id];
370
+ if (cues) {
371
+ Object.keys(cues).forEach((key) => {
372
+ const cue = cues[key];
373
+ if (cue) {
374
+ cue.removeEventListener('enter', this.onEventCueEnter);
375
+ try {
376
+ id3Track.track.removeCue(cue);
377
+ } catch (e) {
378
+ /* no-op */
379
+ }
380
+ }
381
+ });
382
+ }
383
+ }
384
+ } else {
385
+ dateRangeCuesAppended = this.dateRangeCuesAppended = {};
386
+ }
387
+ }
388
+ // Exit if the playlist does not have Date Ranges or does not have Program Date Time
389
+ const lastFragment = details.fragments[details.fragments.length - 1] as
390
+ | MediaFragment
391
+ | undefined;
392
+ if (ids.length === 0 || !Number.isFinite(lastFragment?.programDateTime)) {
393
+ return;
394
+ }
395
+
396
+ this.id3Track ||= this.createTrack(this.media);
397
+
398
+ for (let i = 0; i < ids.length; i++) {
399
+ const id = ids[i];
400
+ const dateRange = dateRanges[id]!;
401
+ const startTime = dateRange.startTime;
402
+
403
+ // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)
404
+ const appendedDateRangeCues = dateRangeCuesAppended[id];
405
+ const cues = appendedDateRangeCues?.cues || {};
406
+ let durationKnown = appendedDateRangeCues?.durationKnown || false;
407
+ let endTime = MAX_CUE_ENDTIME;
408
+ const { duration, endDate } = dateRange;
409
+ if (endDate && duration !== null) {
410
+ endTime = startTime + duration;
411
+ durationKnown = true;
412
+ } else if (dateRange.endOnNext && !durationKnown) {
413
+ const nextDateRangeWithSameClass = ids.reduce(
414
+ (candidateDateRange: DateRange | null, id) => {
415
+ if (id !== dateRange.id) {
416
+ const otherDateRange = dateRanges[id]!;
417
+ if (
418
+ otherDateRange.class === dateRange.class &&
419
+ otherDateRange.startDate > dateRange.startDate &&
420
+ (!candidateDateRange ||
421
+ dateRange.startDate < candidateDateRange.startDate)
422
+ ) {
423
+ return otherDateRange;
424
+ }
425
+ }
426
+ return candidateDateRange;
427
+ },
428
+ null,
429
+ );
430
+ if (nextDateRangeWithSameClass) {
431
+ endTime = nextDateRangeWithSameClass.startTime;
432
+ durationKnown = true;
433
+ }
434
+ }
435
+
436
+ // Create TextTrack Cues for each MetadataGroup Item (select DateRange attribute)
437
+ // This is to emulate Safari HLS playback handling of DateRange tags
438
+ const attributes = Object.keys(dateRange.attr);
439
+ for (let j = 0; j < attributes.length; j++) {
440
+ const key = attributes[j];
441
+ if (!isDateRangeCueAttribute(key)) {
442
+ continue;
443
+ }
444
+ const cue = cues[key];
445
+ if (cue) {
446
+ if (durationKnown && !appendedDateRangeCues?.durationKnown) {
447
+ cue.endTime = endTime;
448
+ } else if (Math.abs(cue.startTime - startTime) > 0.01) {
449
+ cue.startTime = startTime;
450
+ cue.endTime = endTime;
451
+ }
452
+ } else if (Cue) {
453
+ let data = dateRange.attr[key];
454
+ if (isSCTE35Attribute(key)) {
455
+ data = hexToArrayBuffer(data);
456
+ }
457
+ const payload: any = { key, data };
458
+ const cue = createCueWithDataFields(
459
+ Cue,
460
+ startTime,
461
+ endTime,
462
+ payload,
463
+ MetadataSchema.dateRange,
464
+ );
465
+ if (cue) {
466
+ cue.id = id;
467
+ this.id3Track.track.addCue(cue);
468
+ cues[key] = cue;
469
+ if (__USE_INTERSTITIALS__ && interstitialsController) {
470
+ if (key === 'X-ASSET-LIST' || key === 'X-ASSET-URL') {
471
+ cue.addEventListener('enter', this.onEventCueEnter);
472
+ }
473
+ }
474
+ }
475
+ }
476
+ }
477
+
478
+ // Keep track of processed DateRanges by ID for updating cues with new DateRange tag attributes
479
+ dateRangeCuesAppended[id] = {
480
+ cues,
481
+ dateRange,
482
+ durationKnown,
483
+ };
484
+ }
485
+ }
486
+ }
487
+
488
+ export default ID3TrackController;