@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,438 @@
1
+ import { NetworkErrorAction } from './error-controller';
2
+ import { ErrorDetails, ErrorTypes } from '../errors';
3
+ import { Events } from '../events';
4
+ import {
5
+ getSkipValue,
6
+ HlsSkip,
7
+ HlsUrlParameters,
8
+ type Level,
9
+ } from '../types/level';
10
+ import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
11
+ import { computeReloadInterval, mergeDetails } from '../utils/level-helper';
12
+ import { Logger } from '../utils/logger';
13
+ import type Hls from '../hls';
14
+ import type { LevelDetails } from '../loader/level-details';
15
+ import type { NetworkComponentAPI } from '../types/component-api';
16
+ import type { ErrorData } from '../types/events';
17
+ import type {
18
+ AudioTrackLoadedData,
19
+ LevelLoadedData,
20
+ TrackLoadedData,
21
+ } from '../types/events';
22
+ import type { MediaPlaylist } from '../types/media-playlist';
23
+
24
+ export default class BasePlaylistController
25
+ extends Logger
26
+ implements NetworkComponentAPI
27
+ {
28
+ protected hls: Hls;
29
+ protected canLoad: boolean = false;
30
+ private timer: number = -1;
31
+
32
+ constructor(hls: Hls, logPrefix: string) {
33
+ super(logPrefix, hls.logger);
34
+ this.hls = hls;
35
+ }
36
+
37
+ public destroy() {
38
+ this.clearTimer();
39
+ // @ts-ignore
40
+ this.hls = this.log = this.warn = null;
41
+ }
42
+
43
+ private clearTimer() {
44
+ if (this.timer !== -1) {
45
+ self.clearTimeout(this.timer);
46
+ this.timer = -1;
47
+ }
48
+ }
49
+
50
+ public startLoad() {
51
+ this.canLoad = true;
52
+ this.loadPlaylist();
53
+ }
54
+
55
+ public stopLoad() {
56
+ this.canLoad = false;
57
+ this.clearTimer();
58
+ }
59
+
60
+ protected switchParams(
61
+ playlistUri: string,
62
+ previous: LevelDetails | undefined,
63
+ current: LevelDetails | undefined,
64
+ ): HlsUrlParameters | undefined {
65
+ const renditionReports = previous?.renditionReports;
66
+ if (renditionReports) {
67
+ let foundIndex = -1;
68
+ for (let i = 0; i < renditionReports.length; i++) {
69
+ const attr = renditionReports[i];
70
+ let uri: string;
71
+ try {
72
+ uri = new self.URL(attr.URI, previous.url).href;
73
+ } catch (error) {
74
+ this.warn(
75
+ `Could not construct new URL for Rendition Report: ${error}`,
76
+ );
77
+ uri = attr.URI || '';
78
+ }
79
+ // Use exact match. Otherwise, the last partial match, if any, will be used
80
+ // (Playlist URI includes a query string that the Rendition Report does not)
81
+ if (uri === playlistUri) {
82
+ foundIndex = i;
83
+ break;
84
+ } else if (uri === playlistUri.substring(0, uri.length)) {
85
+ foundIndex = i;
86
+ }
87
+ }
88
+ if (foundIndex !== -1) {
89
+ const attr = renditionReports[foundIndex];
90
+ const msn = parseInt(attr['LAST-MSN']) || previous.lastPartSn;
91
+ let part = parseInt(attr['LAST-PART']) || previous.lastPartIndex;
92
+ if (this.hls.config.lowLatencyMode) {
93
+ const currentGoal = Math.min(
94
+ previous.age - previous.partTarget,
95
+ previous.targetduration,
96
+ );
97
+ if (part >= 0 && currentGoal > previous.partTarget) {
98
+ part += 1;
99
+ }
100
+ }
101
+ const skip = current && getSkipValue(current);
102
+ return new HlsUrlParameters(msn, part >= 0 ? part : undefined, skip);
103
+ }
104
+ }
105
+ }
106
+
107
+ protected loadPlaylist(hlsUrlParameters?: HlsUrlParameters) {
108
+ // Loading is handled by the subclasses
109
+ this.clearTimer();
110
+ }
111
+
112
+ protected loadingPlaylist(
113
+ playlist: Level | MediaPlaylist,
114
+ hlsUrlParameters?: HlsUrlParameters,
115
+ ) {
116
+ // Loading is handled by the subclasses
117
+ this.clearTimer();
118
+ }
119
+
120
+ protected shouldLoadPlaylist(
121
+ playlist: Level | MediaPlaylist | null | undefined,
122
+ ): playlist is Level | MediaPlaylist {
123
+ return (
124
+ this.canLoad &&
125
+ !!playlist &&
126
+ !!playlist.url &&
127
+ (!playlist.details || playlist.details.live)
128
+ );
129
+ }
130
+
131
+ protected getUrlWithDirectives(
132
+ uri: string,
133
+ hlsUrlParameters: HlsUrlParameters | undefined,
134
+ ): string {
135
+ if (hlsUrlParameters) {
136
+ try {
137
+ return hlsUrlParameters.addDirectives(uri);
138
+ } catch (error) {
139
+ this.warn(
140
+ `Could not construct new URL with HLS Delivery Directives: ${error}`,
141
+ );
142
+ }
143
+ }
144
+ return uri;
145
+ }
146
+
147
+ protected playlistLoaded(
148
+ index: number,
149
+ data: LevelLoadedData | AudioTrackLoadedData | TrackLoadedData,
150
+ previousDetails?: LevelDetails,
151
+ ) {
152
+ const { details, stats } = data;
153
+
154
+ // Set last updated date-time
155
+ const now = self.performance.now();
156
+ const elapsed = stats.loading.first
157
+ ? Math.max(0, now - stats.loading.first)
158
+ : 0;
159
+ details.advancedDateTime = Date.now() - elapsed;
160
+
161
+ // shift fragment starts with timelineOffset
162
+ const timelineOffset = this.hls.config.timelineOffset;
163
+ if (timelineOffset !== details.appliedTimelineOffset) {
164
+ const offset = Math.max(timelineOffset || 0, 0);
165
+ details.appliedTimelineOffset = offset;
166
+ details.fragments.forEach((frag) => {
167
+ frag.setStart(frag.playlistOffset + offset);
168
+ });
169
+ }
170
+
171
+ // if current playlist is a live playlist, arm a timer to reload it
172
+ if (details.live || previousDetails?.live) {
173
+ const levelOrTrack = 'levelInfo' in data ? data.levelInfo : data.track;
174
+ details.reloaded(previousDetails);
175
+
176
+ // TODO: consider a separate flow for low-latency blocking reload requests with delivery directives
177
+ if (details.misses >= this.hls.config.liveMaxUnchangedPlaylistRefresh) {
178
+ const error = new Error(
179
+ `levelOrTrack (${levelOrTrack.id}) hits max allowed unchanged reloads.`,
180
+ );
181
+ this.warn(error);
182
+ const { networkDetails, context } = data;
183
+ this.hls.trigger(Events.ERROR, {
184
+ type: ErrorTypes.NETWORK_ERROR,
185
+ details: ErrorDetails.PLAYLIST_UNCHANGED_ERROR,
186
+ fatal: false,
187
+ url: details.url,
188
+ error,
189
+ reason: error.message,
190
+ level: (data as LevelLoadedData).level ?? undefined,
191
+ parent: details.fragments[0]?.type,
192
+ context,
193
+ networkDetails,
194
+ stats,
195
+ });
196
+ return;
197
+ }
198
+
199
+ // Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments
200
+ if (previousDetails && details.fragments.length > 0) {
201
+ mergeDetails(previousDetails, details, this);
202
+ const error = details.playlistParsingError;
203
+ if (error) {
204
+ this.warn(error);
205
+ const hls = this.hls;
206
+ if (!hls.config.ignorePlaylistParsingErrors) {
207
+ const { networkDetails } = data;
208
+ hls.trigger(Events.ERROR, {
209
+ type: ErrorTypes.NETWORK_ERROR,
210
+ details: ErrorDetails.LEVEL_PARSING_ERROR,
211
+ fatal: false,
212
+ url: details.url,
213
+ error,
214
+ reason: error.message,
215
+ level: (data as any).level || undefined,
216
+ parent: details.fragments[0]?.type,
217
+ networkDetails,
218
+ stats,
219
+ });
220
+ return;
221
+ }
222
+ details.playlistParsingError = null;
223
+ }
224
+ }
225
+ if (details.requestScheduled === -1) {
226
+ details.requestScheduled = stats.loading.start;
227
+ }
228
+ const bufferInfo = this.hls.mainForwardBufferInfo;
229
+ const position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0;
230
+ const distanceToLiveEdgeMs = (details.edge - position) * 1000;
231
+ const reloadInterval = computeReloadInterval(
232
+ details,
233
+ distanceToLiveEdgeMs,
234
+ );
235
+ if (details.requestScheduled + reloadInterval < now) {
236
+ details.requestScheduled = now;
237
+ } else {
238
+ details.requestScheduled += reloadInterval;
239
+ }
240
+ this.log(
241
+ `live playlist ${index} ${
242
+ details.advanced
243
+ ? 'REFRESHED ' + details.lastPartSn + '-' + details.lastPartIndex
244
+ : details.updated
245
+ ? 'UPDATED'
246
+ : 'MISSED'
247
+ }`,
248
+ );
249
+ if (!this.canLoad || !details.live) {
250
+ return;
251
+ }
252
+ let deliveryDirectives: HlsUrlParameters | undefined;
253
+ let msn: number | undefined = undefined;
254
+ let part: number | undefined = undefined;
255
+ if (details.canBlockReload && details.endSN && details.advanced) {
256
+ // Load level with LL-HLS delivery directives
257
+ const lowLatencyMode = this.hls.config.lowLatencyMode;
258
+ const lastPartSn = details.lastPartSn;
259
+ const endSn = details.endSN;
260
+ const lastPartIndex = details.lastPartIndex;
261
+ const hasParts = lastPartIndex !== -1;
262
+ const atLastPartOfSegment = lastPartSn === endSn;
263
+ if (hasParts) {
264
+ // When low latency mode is disabled, request the last part of the next segment
265
+ if (atLastPartOfSegment) {
266
+ msn = endSn + 1;
267
+ part = lowLatencyMode ? 0 : lastPartIndex;
268
+ } else {
269
+ msn = lastPartSn;
270
+ part = lowLatencyMode ? lastPartIndex + 1 : details.maxPartIndex;
271
+ }
272
+ } else {
273
+ msn = endSn + 1;
274
+ }
275
+ // Low-Latency CDN Tune-in: "age" header and time since load indicates we're behind by more than one part
276
+ // Update directives to obtain the Playlist that has the estimated additional duration of media
277
+ const lastAdvanced = details.age;
278
+ const cdnAge = lastAdvanced + details.ageHeader;
279
+ let currentGoal = Math.min(
280
+ cdnAge - details.partTarget,
281
+ details.targetduration * 1.5,
282
+ );
283
+ if (currentGoal > 0) {
284
+ if (cdnAge > details.targetduration * 3) {
285
+ // Omit segment and part directives when the last response was more than 3 target durations ago,
286
+ this.log(
287
+ `Playlist last advanced ${lastAdvanced.toFixed(
288
+ 2,
289
+ )}s ago. Omitting segment and part directives.`,
290
+ );
291
+ msn = undefined;
292
+ part = undefined;
293
+ } else if (
294
+ previousDetails?.tuneInGoal &&
295
+ cdnAge - details.partTarget > previousDetails.tuneInGoal
296
+ ) {
297
+ // If we attempted to get the next or latest playlist update, but currentGoal increased,
298
+ // then we either can't catchup, or the "age" header cannot be trusted.
299
+ this.warn(
300
+ `CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`,
301
+ );
302
+ currentGoal = 0;
303
+ } else {
304
+ const segments = Math.floor(currentGoal / details.targetduration);
305
+ msn += segments;
306
+ if (part !== undefined) {
307
+ const parts = Math.round(
308
+ (currentGoal % details.targetduration) / details.partTarget,
309
+ );
310
+ part += parts;
311
+ }
312
+ this.log(
313
+ `CDN Tune-in age: ${
314
+ details.ageHeader
315
+ }s last advanced ${lastAdvanced.toFixed(
316
+ 2,
317
+ )}s goal: ${currentGoal} skip sn ${segments} to part ${part}`,
318
+ );
319
+ }
320
+ details.tuneInGoal = currentGoal;
321
+ }
322
+ deliveryDirectives = this.getDeliveryDirectives(
323
+ details,
324
+ data.deliveryDirectives,
325
+ msn,
326
+ part,
327
+ );
328
+ if (lowLatencyMode || !atLastPartOfSegment) {
329
+ details.requestScheduled = now;
330
+ this.loadingPlaylist(levelOrTrack, deliveryDirectives);
331
+ return;
332
+ }
333
+ } else if (details.canBlockReload || details.canSkipUntil) {
334
+ deliveryDirectives = this.getDeliveryDirectives(
335
+ details,
336
+ data.deliveryDirectives,
337
+ msn,
338
+ part,
339
+ );
340
+ }
341
+ if (deliveryDirectives && msn !== undefined && details.canBlockReload) {
342
+ details.requestScheduled =
343
+ stats.loading.first +
344
+ Math.max(reloadInterval - elapsed * 2, reloadInterval / 2);
345
+ }
346
+ this.scheduleLoading(levelOrTrack, deliveryDirectives, details);
347
+ } else {
348
+ this.clearTimer();
349
+ }
350
+ }
351
+
352
+ protected scheduleLoading(
353
+ levelOrTrack: Level | MediaPlaylist,
354
+ deliveryDirectives?: HlsUrlParameters,
355
+ updatedDetails?: LevelDetails,
356
+ ) {
357
+ const details = updatedDetails || levelOrTrack.details;
358
+ if (!details) {
359
+ this.loadingPlaylist(levelOrTrack, deliveryDirectives);
360
+ return;
361
+ }
362
+ const now = self.performance.now();
363
+ const requestScheduled = details.requestScheduled;
364
+ if (now >= requestScheduled) {
365
+ this.loadingPlaylist(levelOrTrack, deliveryDirectives);
366
+ return;
367
+ }
368
+
369
+ const estimatedTimeUntilUpdate = requestScheduled - now;
370
+ this.log(
371
+ `reload live playlist ${levelOrTrack.name || levelOrTrack.bitrate + 'bps'} in ${Math.round(
372
+ estimatedTimeUntilUpdate,
373
+ )} ms`,
374
+ );
375
+
376
+ this.clearTimer();
377
+ this.timer = self.setTimeout(
378
+ () => this.loadingPlaylist(levelOrTrack, deliveryDirectives),
379
+ estimatedTimeUntilUpdate,
380
+ );
381
+ }
382
+
383
+ private getDeliveryDirectives(
384
+ details: LevelDetails,
385
+ previousDeliveryDirectives: HlsUrlParameters | null,
386
+ msn?: number,
387
+ part?: number,
388
+ ): HlsUrlParameters {
389
+ let skip = getSkipValue(details);
390
+ if (previousDeliveryDirectives?.skip && details.deltaUpdateFailed) {
391
+ msn = previousDeliveryDirectives.msn;
392
+ part = previousDeliveryDirectives.part;
393
+ skip = HlsSkip.No;
394
+ }
395
+ return new HlsUrlParameters(msn, part, skip);
396
+ }
397
+
398
+ protected checkRetry(errorEvent: ErrorData): boolean {
399
+ const errorDetails = errorEvent.details;
400
+ const isTimeout = isTimeoutError(errorEvent);
401
+ const errorAction = errorEvent.errorAction;
402
+ const { action, retryCount = 0, retryConfig } = errorAction || {};
403
+ const retry =
404
+ !!errorAction &&
405
+ !!retryConfig &&
406
+ (action === NetworkErrorAction.RetryRequest ||
407
+ (!errorAction.resolved &&
408
+ action === NetworkErrorAction.SendAlternateToPenaltyBox));
409
+ if (retry) {
410
+ if (retryCount >= retryConfig.maxNumRetry) {
411
+ return false;
412
+ }
413
+ if (isTimeout && errorEvent.context?.deliveryDirectives) {
414
+ // The LL-HLS request already timed out so retry immediately
415
+ this.warn(
416
+ `Retrying playlist loading ${retryCount + 1}/${
417
+ retryConfig.maxNumRetry
418
+ } after "${errorDetails}" without delivery-directives`,
419
+ );
420
+ this.loadPlaylist();
421
+ } else {
422
+ const delay = getRetryDelay(retryConfig, retryCount);
423
+ // Schedule level/track reload
424
+ this.clearTimer();
425
+ this.timer = self.setTimeout(() => this.loadPlaylist(), delay);
426
+ this.warn(
427
+ `Retrying playlist loading ${retryCount + 1}/${
428
+ retryConfig.maxNumRetry
429
+ } after "${errorDetails}" in ${delay}ms`,
430
+ );
431
+ }
432
+ // `levelRetry = true` used to inform other controllers that a retry is happening
433
+ errorEvent.levelRetry = true;
434
+ errorAction.resolved = true;
435
+ }
436
+ return retry;
437
+ }
438
+ }