@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,487 @@
1
+ import { buildAbsoluteURL } from 'url-toolkit';
2
+ import { LoadStats } from './load-stats';
3
+ import { PlaylistLevelType } from '../types/loader';
4
+ import type { LevelKey } from './level-key';
5
+ import type {
6
+ FragmentLoaderContext,
7
+ KeyLoaderContext,
8
+ Loader,
9
+ } from '../types/loader';
10
+ import type { AttrList } from '../utils/attr-list';
11
+ import type { KeySystemFormats } from '../utils/mediakeys-helper';
12
+
13
+ export const enum ElementaryStreamTypes {
14
+ AUDIO = 'audio',
15
+ VIDEO = 'video',
16
+ AUDIOVIDEO = 'audiovideo',
17
+ }
18
+
19
+ export interface ElementaryStreamInfo {
20
+ startPTS: number;
21
+ endPTS: number;
22
+ startDTS: number;
23
+ endDTS: number;
24
+ partial?: boolean;
25
+ }
26
+
27
+ export type ElementaryStreams = Record<
28
+ ElementaryStreamTypes,
29
+ ElementaryStreamInfo | null
30
+ >;
31
+
32
+ export type Base = {
33
+ url: string;
34
+ };
35
+
36
+ export class BaseSegment {
37
+ private _byteRange: [number, number] | null = null;
38
+ private _url: string | null = null;
39
+ private _stats: LoadStats | null = null;
40
+ private _streams: ElementaryStreams | null = null;
41
+
42
+ // baseurl is the URL to the playlist
43
+ public readonly base: Base;
44
+
45
+ // relurl is the portion of the URL that comes from inside the playlist.
46
+ public relurl?: string;
47
+
48
+ public algoRelurl?: string;
49
+
50
+ constructor(base: Base | string) {
51
+ if (typeof base === 'string') {
52
+ base = { url: base };
53
+ }
54
+ this.base = base;
55
+ makeEnumerable(this, 'stats');
56
+ }
57
+
58
+ // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
59
+ setByteRange(value: string, previous?: BaseSegment) {
60
+ const params = value.split('@', 2);
61
+ let start: number;
62
+ if (params.length === 1) {
63
+ start = previous?.byteRangeEndOffset || 0;
64
+ } else {
65
+ start = parseInt(params[1]);
66
+ }
67
+ this._byteRange = [start, parseInt(params[0]) + start];
68
+ }
69
+
70
+ get baseurl(): string {
71
+ return this.base.url;
72
+ }
73
+
74
+ get byteRange(): [number, number] | [] {
75
+ if (this._byteRange === null) {
76
+ return [];
77
+ }
78
+
79
+ return this._byteRange;
80
+ }
81
+
82
+ get byteRangeStartOffset(): number | undefined {
83
+ return this.byteRange[0];
84
+ }
85
+
86
+ get byteRangeEndOffset(): number | undefined {
87
+ return this.byteRange[1];
88
+ }
89
+
90
+ get elementaryStreams(): ElementaryStreams {
91
+ if (this._streams === null) {
92
+ this._streams = {
93
+ [ElementaryStreamTypes.AUDIO]: null,
94
+ [ElementaryStreamTypes.VIDEO]: null,
95
+ [ElementaryStreamTypes.AUDIOVIDEO]: null,
96
+ };
97
+ }
98
+ return this._streams;
99
+ }
100
+
101
+ set elementaryStreams(value: ElementaryStreams) {
102
+ this._streams = value;
103
+ }
104
+
105
+ get hasStats(): boolean {
106
+ return this._stats !== null;
107
+ }
108
+
109
+ get hasStreams(): boolean {
110
+ return this._streams !== null;
111
+ }
112
+
113
+ get stats(): LoadStats {
114
+ if (this._stats === null) {
115
+ this._stats = new LoadStats();
116
+ }
117
+ return this._stats;
118
+ }
119
+
120
+ set stats(value: LoadStats) {
121
+ this._stats = value;
122
+ }
123
+
124
+ get url(): string {
125
+ if (!this._url && this.baseurl && this.relurl) {
126
+ this._url = buildAbsoluteURL(this.baseurl, this.relurl, {
127
+ alwaysNormalize: true,
128
+ });
129
+ }
130
+ return this._url || '';
131
+ }
132
+
133
+ set url(value: string) {
134
+ this._url = value;
135
+ }
136
+
137
+ clearElementaryStreamInfo() {
138
+ const { elementaryStreams } = this;
139
+ elementaryStreams[ElementaryStreamTypes.AUDIO] = null;
140
+ elementaryStreams[ElementaryStreamTypes.VIDEO] = null;
141
+ elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null;
142
+ }
143
+ }
144
+
145
+ export interface MediaFragment extends Fragment {
146
+ sn: number;
147
+ ref: MediaFragmentRef;
148
+ }
149
+
150
+ export type MediaFragmentRef = {
151
+ base: Base;
152
+ start: number;
153
+ duration: number;
154
+ sn: number;
155
+ programDateTime: number | null;
156
+ };
157
+
158
+ export function isMediaFragment(frag: Fragment): frag is MediaFragment {
159
+ return frag.sn !== 'initSegment';
160
+ }
161
+
162
+ /**
163
+ * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
164
+ */
165
+ export class Fragment extends BaseSegment {
166
+ private _decryptdata: LevelKey | null = null;
167
+ private _programDateTime: number | null = null;
168
+ private _ref: MediaFragmentRef | null = null;
169
+ // Approximate bit rate of the fragment expressed in bits per second (bps) as indicated by the last EXT-X-BITRATE (kbps) tag
170
+ private _bitrate?: number;
171
+
172
+ public rawProgramDateTime: string | null = null;
173
+ public tagList: Array<string[]> = [];
174
+
175
+ // EXTINF has to be present for a m3u8 to be considered valid
176
+ public duration: number = 0;
177
+ // sn notates the sequence number for a segment, and if set to a string can be 'initSegment'
178
+ public sn: number | 'initSegment' = 0;
179
+ // levelkeys are the EXT-X-KEY tags that apply to this segment for decryption
180
+ // core difference from the private field _decryptdata is the lack of the initialized IV
181
+ // _decryptdata will set the IV for this segment based on the segment number in the fragment
182
+ public levelkeys?: { [key: string]: LevelKey | undefined };
183
+ // A string representing the fragment type
184
+ public readonly type: PlaylistLevelType;
185
+ // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading
186
+ public loader: Loader<FragmentLoaderContext> | null = null;
187
+ // A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading
188
+ public keyLoader: Loader<KeyLoaderContext> | null = null;
189
+ // The level/track index to which the fragment belongs
190
+ public level: number = -1;
191
+ // The continuity counter of the fragment
192
+ public cc: number = 0;
193
+ // The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
194
+ public startPTS?: number;
195
+ // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
196
+ public endPTS?: number;
197
+ // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
198
+ public startDTS?: number;
199
+ // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
200
+ public endDTS?: number;
201
+ // The start time of the fragment, as listed in the manifest. Updated after transmux complete.
202
+ public start: number = 0;
203
+ // The offset time (seconds) of the fragment from the start of the Playlist
204
+ public playlistOffset: number = 0;
205
+ // Set by `updateFragPTSDTS` in level-helper
206
+ public deltaPTS?: number;
207
+ // The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
208
+ public maxStartPTS?: number;
209
+ // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
210
+ public minEndPTS?: number;
211
+ // Init Segment bytes (unset for media segments)
212
+ public data?: Uint8Array;
213
+ // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered
214
+ public bitrateTest: boolean = false;
215
+ // #EXTINF segment title
216
+ public title: string | null = null;
217
+ // The Media Initialization Section for this segment
218
+ public initSegment: Fragment | null = null;
219
+ // Fragment is the last fragment in the media playlist
220
+ public endList?: boolean;
221
+ // Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded
222
+ public gap?: boolean;
223
+ // Deprecated
224
+ public urlId: number = 0;
225
+
226
+ constructor(type: PlaylistLevelType, base: Base | string) {
227
+ super(base);
228
+ this.type = type;
229
+ }
230
+
231
+ get byteLength(): number | null {
232
+ if (this.hasStats) {
233
+ const total = this.stats.total;
234
+ if (total) {
235
+ return total;
236
+ }
237
+ }
238
+ if (this.byteRange.length) {
239
+ const start = this.byteRange[0];
240
+ const end = this.byteRange[1];
241
+ if (Number.isFinite(start) && Number.isFinite(end)) {
242
+ return (end as number) - (start as number);
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+
248
+ get bitrate(): number | null {
249
+ if (this.byteLength) {
250
+ return (this.byteLength * 8) / this.duration;
251
+ }
252
+ if (this._bitrate) {
253
+ return this._bitrate;
254
+ }
255
+ return null;
256
+ }
257
+
258
+ set bitrate(value: number) {
259
+ this._bitrate = value;
260
+ }
261
+
262
+ get decryptdata(): LevelKey | null {
263
+ const { levelkeys } = this;
264
+
265
+ if (!levelkeys || levelkeys.NONE) {
266
+ return null;
267
+ }
268
+
269
+ if (levelkeys.identity) {
270
+ if (!this._decryptdata) {
271
+ this._decryptdata = levelkeys.identity.getDecryptData(this.sn);
272
+ }
273
+ } else if (!this._decryptdata?.keyId) {
274
+ const keyFormats = Object.keys(levelkeys);
275
+ if (keyFormats.length === 1) {
276
+ const levelKey = (this._decryptdata = levelkeys[keyFormats[0]] || null);
277
+ if (levelKey) {
278
+ this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys);
279
+ }
280
+ } else {
281
+ // Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system.
282
+ }
283
+ }
284
+
285
+ return this._decryptdata;
286
+ }
287
+
288
+ get end(): number {
289
+ return this.start + this.duration;
290
+ }
291
+
292
+ get endProgramDateTime() {
293
+ if (this.programDateTime === null) {
294
+ return null;
295
+ }
296
+
297
+ const duration = !Number.isFinite(this.duration) ? 0 : this.duration;
298
+
299
+ return this.programDateTime + duration * 1000;
300
+ }
301
+
302
+ get encrypted() {
303
+ // At the m3u8-parser level we need to add support for manifest signalled keyformats
304
+ // when we want the fragment to start reporting that it is encrypted.
305
+ // Currently, keyFormat will only be set for identity keys
306
+ if (this._decryptdata?.encrypted) {
307
+ return true;
308
+ } else if (this.levelkeys) {
309
+ const keyFormats = Object.keys(this.levelkeys);
310
+ const len = keyFormats.length;
311
+ if (len > 1 || (len === 1 && this.levelkeys[keyFormats[0]]?.encrypted)) {
312
+ return true;
313
+ }
314
+ }
315
+ return false;
316
+ }
317
+
318
+ get programDateTime(): number | null {
319
+ if (this._programDateTime === null && this.rawProgramDateTime) {
320
+ this.programDateTime = Date.parse(this.rawProgramDateTime);
321
+ }
322
+ return this._programDateTime;
323
+ }
324
+
325
+ set programDateTime(value: number | null) {
326
+ if (!Number.isFinite(value)) {
327
+ this._programDateTime = this.rawProgramDateTime = null;
328
+ return;
329
+ }
330
+ this._programDateTime = value;
331
+ }
332
+
333
+ get ref(): MediaFragmentRef | null {
334
+ if (!isMediaFragment(this)) {
335
+ return null;
336
+ }
337
+ if (!this._ref) {
338
+ this._ref = {
339
+ base: this.base,
340
+ start: this.start,
341
+ duration: this.duration,
342
+ sn: this.sn,
343
+ programDateTime: this.programDateTime,
344
+ };
345
+ }
346
+ return this._ref;
347
+ }
348
+
349
+ addStart(value: number) {
350
+ this.setStart(this.start + value);
351
+ }
352
+
353
+ setStart(value: number) {
354
+ this.start = value;
355
+ if (this._ref) {
356
+ this._ref.start = value;
357
+ }
358
+ }
359
+
360
+ setDuration(value: number) {
361
+ this.duration = value;
362
+ if (this._ref) {
363
+ this._ref.duration = value;
364
+ }
365
+ }
366
+
367
+ setKeyFormat(keyFormat: KeySystemFormats) {
368
+ const levelkeys = this.levelkeys;
369
+ if (levelkeys) {
370
+ const key = levelkeys[keyFormat];
371
+ if (key && !this._decryptdata?.keyId) {
372
+ this._decryptdata = key.getDecryptData(this.sn, levelkeys);
373
+ }
374
+ }
375
+ }
376
+
377
+ abortRequests(): void {
378
+ this.loader?.abort();
379
+ this.keyLoader?.abort();
380
+ }
381
+
382
+ setElementaryStreamInfo(
383
+ type: ElementaryStreamTypes,
384
+ startPTS: number,
385
+ endPTS: number,
386
+ startDTS: number,
387
+ endDTS: number,
388
+ partial: boolean = false,
389
+ ) {
390
+ const { elementaryStreams } = this;
391
+ const info = elementaryStreams[type];
392
+ if (!info) {
393
+ elementaryStreams[type] = {
394
+ startPTS,
395
+ endPTS,
396
+ startDTS,
397
+ endDTS,
398
+ partial,
399
+ };
400
+ return;
401
+ }
402
+
403
+ info.startPTS = Math.min(info.startPTS, startPTS);
404
+ info.endPTS = Math.max(info.endPTS, endPTS);
405
+ info.startDTS = Math.min(info.startDTS, startDTS);
406
+ info.endDTS = Math.max(info.endDTS, endDTS);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}.
412
+ */
413
+ export class Part extends BaseSegment {
414
+ public readonly fragOffset: number = 0;
415
+ public readonly duration: number = 0;
416
+ public readonly gap: boolean = false;
417
+ public readonly independent: boolean = false;
418
+ public readonly relurl: string;
419
+ public readonly fragment: MediaFragment;
420
+ public readonly index: number;
421
+
422
+ constructor(
423
+ partAttrs: AttrList,
424
+ frag: MediaFragment,
425
+ base: Base | string,
426
+ index: number,
427
+ previous?: Part,
428
+ ) {
429
+ super(base);
430
+ this.duration = partAttrs.decimalFloatingPoint('DURATION');
431
+ this.gap = partAttrs.bool('GAP');
432
+ this.independent = partAttrs.bool('INDEPENDENT');
433
+ this.relurl = partAttrs.enumeratedString('URI') as string;
434
+ this.fragment = frag;
435
+ this.index = index;
436
+ const byteRange = partAttrs.enumeratedString('BYTERANGE');
437
+ if (byteRange) {
438
+ this.setByteRange(byteRange, previous);
439
+ }
440
+ if (previous) {
441
+ this.fragOffset = previous.fragOffset + previous.duration;
442
+ }
443
+ }
444
+
445
+ get start(): number {
446
+ return this.fragment.start + this.fragOffset;
447
+ }
448
+
449
+ get end(): number {
450
+ return this.start + this.duration;
451
+ }
452
+
453
+ get loaded(): boolean {
454
+ const { elementaryStreams } = this;
455
+ return !!(
456
+ elementaryStreams.audio ||
457
+ elementaryStreams.video ||
458
+ elementaryStreams.audiovideo ||
459
+ (this.fragment.type === PlaylistLevelType.SUBTITLE && this.stats.loaded)
460
+ );
461
+ }
462
+ }
463
+
464
+ function getOwnPropertyDescriptorFromPrototypeChain(
465
+ object: Object | undefined,
466
+ property: string,
467
+ ) {
468
+ const prototype = Object.getPrototypeOf(object);
469
+ if (prototype) {
470
+ const propertyDescriptor = Object.getOwnPropertyDescriptor(
471
+ prototype,
472
+ property,
473
+ );
474
+ if (propertyDescriptor) {
475
+ return propertyDescriptor;
476
+ }
477
+ return getOwnPropertyDescriptorFromPrototypeChain(prototype, property);
478
+ }
479
+ }
480
+
481
+ function makeEnumerable(object: Object, property: string) {
482
+ const d = getOwnPropertyDescriptorFromPrototypeChain(object, property);
483
+ if (d) {
484
+ d.enumerable = true;
485
+ Object.defineProperty(object, property, d);
486
+ }
487
+ }
@@ -0,0 +1,162 @@
1
+ import {
2
+ type AssetListJSON,
3
+ getInterstitialUrl,
4
+ type InterstitialEventWithAssetList,
5
+ } from './interstitial-event';
6
+ import { ErrorDetails, ErrorTypes } from '../errors';
7
+ import { Events } from '../events';
8
+ import type { InterstitialEvent } from './interstitial-event';
9
+ import type Hls from '../hls';
10
+ import type { ErrorData } from '../types/events';
11
+ import type {
12
+ Loader,
13
+ LoaderCallbacks,
14
+ LoaderConfiguration,
15
+ LoaderContext,
16
+ LoaderResponse,
17
+ LoaderStats,
18
+ } from '../types/loader';
19
+ import type { NullableNetworkDetails } from '../types/network-details';
20
+
21
+ export class AssetListLoader {
22
+ private hls: Hls;
23
+
24
+ constructor(hls: Hls) {
25
+ this.hls = hls;
26
+ }
27
+
28
+ destroy() {
29
+ // @ts-ignore
30
+ this.hls = null;
31
+ }
32
+
33
+ loadAssetList(
34
+ interstitial: InterstitialEventWithAssetList,
35
+ hlsStartOffset: number | undefined,
36
+ ): Loader<LoaderContext> | undefined {
37
+ const assetListUrl = interstitial.assetListUrl;
38
+ let url: URL;
39
+ try {
40
+ url = getInterstitialUrl(
41
+ assetListUrl,
42
+ this.hls.sessionId,
43
+ interstitial.baseUrl,
44
+ );
45
+ } catch (error) {
46
+ const errorData = this.assignAssetListError(
47
+ interstitial,
48
+ ErrorDetails.ASSET_LIST_LOAD_ERROR,
49
+ error,
50
+ assetListUrl,
51
+ );
52
+ this.hls.trigger(Events.ERROR, errorData);
53
+ return;
54
+ }
55
+ if (hlsStartOffset && url.protocol !== 'data:') {
56
+ url.searchParams.set('_HLS_start_offset', '' + hlsStartOffset);
57
+ }
58
+ const config = this.hls.config;
59
+ const Loader = config.loader;
60
+ const loader = new Loader(config) as Loader<LoaderContext>;
61
+ const context: LoaderContext = {
62
+ responseType: 'json',
63
+ url: url.href,
64
+ };
65
+ const loadPolicy = config.interstitialAssetListLoadPolicy.default;
66
+ const loaderConfig: LoaderConfiguration = {
67
+ loadPolicy,
68
+ timeout: loadPolicy.maxLoadTimeMs,
69
+ maxRetry: 0,
70
+ retryDelay: 0,
71
+ maxRetryDelay: 0,
72
+ };
73
+ const callbacks: LoaderCallbacks<LoaderContext> = {
74
+ onSuccess: (
75
+ response: LoaderResponse,
76
+ stats: LoaderStats,
77
+ context: LoaderContext,
78
+ networkDetails: NullableNetworkDetails,
79
+ ) => {
80
+ const assetListResponse = response.data as AssetListJSON;
81
+ const assets = assetListResponse?.ASSETS;
82
+ if (!Array.isArray(assets)) {
83
+ const errorData = this.assignAssetListError(
84
+ interstitial,
85
+ ErrorDetails.ASSET_LIST_PARSING_ERROR,
86
+ new Error(`Invalid interstitial asset list`),
87
+ context.url,
88
+ stats,
89
+ networkDetails,
90
+ );
91
+ this.hls.trigger(Events.ERROR, errorData);
92
+ return;
93
+ }
94
+ interstitial.assetListResponse = assetListResponse;
95
+ this.hls.trigger(Events.ASSET_LIST_LOADED, {
96
+ event: interstitial,
97
+ assetListResponse,
98
+ networkDetails,
99
+ });
100
+ },
101
+ onError: (
102
+ error: { code: number; text: string },
103
+ context: LoaderContext,
104
+ networkDetails: NullableNetworkDetails,
105
+ stats: LoaderStats,
106
+ ) => {
107
+ const errorData = this.assignAssetListError(
108
+ interstitial,
109
+ ErrorDetails.ASSET_LIST_LOAD_ERROR,
110
+ new Error(
111
+ `Error loading X-ASSET-LIST: HTTP status ${error.code} ${error.text} (${context.url})`,
112
+ ),
113
+ context.url,
114
+ stats,
115
+ networkDetails,
116
+ );
117
+ this.hls.trigger(Events.ERROR, errorData);
118
+ },
119
+ onTimeout: (
120
+ stats: LoaderStats,
121
+ context: LoaderContext,
122
+ networkDetails: NullableNetworkDetails,
123
+ ) => {
124
+ const errorData = this.assignAssetListError(
125
+ interstitial,
126
+ ErrorDetails.ASSET_LIST_LOAD_TIMEOUT,
127
+ new Error(`Timeout loading X-ASSET-LIST (${context.url})`),
128
+ context.url,
129
+ stats,
130
+ networkDetails,
131
+ );
132
+ this.hls.trigger(Events.ERROR, errorData);
133
+ },
134
+ };
135
+ loader.load(context, loaderConfig, callbacks);
136
+ this.hls.trigger(Events.ASSET_LIST_LOADING, {
137
+ event: interstitial,
138
+ });
139
+ return loader;
140
+ }
141
+
142
+ assignAssetListError(
143
+ interstitial: InterstitialEvent,
144
+ details: ErrorDetails,
145
+ error: Error,
146
+ url: string,
147
+ stats?: LoaderStats,
148
+ networkDetails?: NullableNetworkDetails,
149
+ ): ErrorData {
150
+ interstitial.error = error;
151
+ return {
152
+ type: ErrorTypes.NETWORK_ERROR,
153
+ details,
154
+ fatal: false,
155
+ interstitial,
156
+ url,
157
+ error,
158
+ networkDetails,
159
+ stats,
160
+ };
161
+ }
162
+ }