@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,508 @@
1
+ import BaseStreamController, { State } from './base-stream-controller';
2
+ import { FragmentState } from './fragment-tracker';
3
+ import { ErrorDetails, ErrorTypes } from '../errors';
4
+ import { Events } from '../events';
5
+ import {
6
+ type Fragment,
7
+ isMediaFragment,
8
+ type MediaFragment,
9
+ } from '../loader/fragment';
10
+ import { Level } from '../types/level';
11
+ import { PlaylistLevelType } from '../types/loader';
12
+ import { BufferHelper } from '../utils/buffer-helper';
13
+ import { alignStream } from '../utils/discontinuities';
14
+ import {
15
+ getAesModeFromFullSegmentMethod,
16
+ isFullSegmentEncryption,
17
+ } from '../utils/encryption-methods-util';
18
+ import { subtitleOptionsIdentical } from '../utils/media-option-attributes';
19
+ import type { FragmentTracker } from './fragment-tracker';
20
+ import type Hls from '../hls';
21
+ import type KeyLoader from '../loader/key-loader';
22
+ import type { LevelDetails } from '../loader/level-details';
23
+ import type { NetworkComponentAPI } from '../types/component-api';
24
+ import type {
25
+ BufferFlushingData,
26
+ ErrorData,
27
+ FragLoadedData,
28
+ LevelLoadedData,
29
+ MediaDetachingData,
30
+ SubtitleFragProcessed,
31
+ SubtitleTracksUpdatedData,
32
+ TrackLoadedData,
33
+ TrackSwitchedData,
34
+ } from '../types/events';
35
+ import type { Bufferable } from '../utils/buffer-helper';
36
+
37
+ const TICK_INTERVAL = 500; // how often to tick in ms
38
+
39
+ interface TimeRange {
40
+ start: number;
41
+ end: number;
42
+ }
43
+
44
+ export class SubtitleStreamController
45
+ extends BaseStreamController
46
+ implements NetworkComponentAPI
47
+ {
48
+ private currentTrackId: number = -1;
49
+ private tracksBuffered: Array<TimeRange[] | undefined> = [];
50
+ private mainDetails: LevelDetails | null = null;
51
+
52
+ constructor(
53
+ hls: Hls,
54
+ fragmentTracker: FragmentTracker,
55
+ keyLoader: KeyLoader,
56
+ ) {
57
+ super(
58
+ hls,
59
+ fragmentTracker,
60
+ keyLoader,
61
+ 'subtitle-stream-controller',
62
+ PlaylistLevelType.SUBTITLE,
63
+ );
64
+ this.registerListeners();
65
+ }
66
+
67
+ protected onHandlerDestroying() {
68
+ this.unregisterListeners();
69
+ super.onHandlerDestroying();
70
+ this.mainDetails = null;
71
+ }
72
+
73
+ protected registerListeners() {
74
+ super.registerListeners();
75
+ const { hls } = this;
76
+ hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
77
+ hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
78
+ hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
79
+ hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
80
+ hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
81
+ hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
82
+ }
83
+
84
+ protected unregisterListeners() {
85
+ super.unregisterListeners();
86
+ const { hls } = this;
87
+ hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
88
+ hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
89
+ hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
90
+ hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
91
+ hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
92
+ hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
93
+ }
94
+
95
+ startLoad(startPosition: number, skipSeekToStartPosition?: boolean) {
96
+ this.stopLoad();
97
+ this.state = State.IDLE;
98
+
99
+ this.setInterval(TICK_INTERVAL);
100
+
101
+ this.nextLoadPosition = this.lastCurrentTime =
102
+ startPosition + this.timelineOffset;
103
+ this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
104
+
105
+ this.tick();
106
+ }
107
+
108
+ protected onManifestLoading() {
109
+ super.onManifestLoading();
110
+ this.mainDetails = null;
111
+ }
112
+
113
+ protected onMediaDetaching(
114
+ event: Events.MEDIA_DETACHING,
115
+ data: MediaDetachingData,
116
+ ) {
117
+ this.tracksBuffered = [];
118
+ super.onMediaDetaching(event, data);
119
+ }
120
+
121
+ private onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
122
+ this.mainDetails = data.details;
123
+ }
124
+
125
+ private onSubtitleFragProcessed(
126
+ event: Events.SUBTITLE_FRAG_PROCESSED,
127
+ data: SubtitleFragProcessed,
128
+ ) {
129
+ const { frag, part, success } = data;
130
+ if (!success) {
131
+ return;
132
+ }
133
+
134
+ const buffered = this.tracksBuffered[this.currentTrackId];
135
+ if (!buffered) {
136
+ return;
137
+ }
138
+
139
+ // Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo
140
+ // so we can re-use the logic used to detect how much has been buffered
141
+ let timeRange: TimeRange | undefined;
142
+ const start = (part || frag).start;
143
+ for (let i = 0; i < buffered.length; i++) {
144
+ if (start >= buffered[i].start && start <= buffered[i].end) {
145
+ timeRange = buffered[i];
146
+ break;
147
+ }
148
+ }
149
+
150
+ const end = start + (part || frag).duration;
151
+ if (timeRange) {
152
+ timeRange.end = end;
153
+ } else {
154
+ timeRange = { start, end };
155
+ buffered.push(timeRange);
156
+ }
157
+ if (!part || end >= frag.end) {
158
+ this.fragmentTracker.fragBuffered(frag as MediaFragment);
159
+ if (!this.fragContextChanged(frag)) {
160
+ if (isMediaFragment(frag)) {
161
+ this.fragPrevious = frag;
162
+ }
163
+ }
164
+ this.fragBufferedComplete(frag, part);
165
+ if (this.media) {
166
+ this.tickImmediate();
167
+ }
168
+ }
169
+ }
170
+
171
+ private onBufferFlushing(
172
+ event: Events.BUFFER_FLUSHING,
173
+ data: BufferFlushingData,
174
+ ) {
175
+ const { startOffset, endOffset } = data;
176
+ if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
177
+ const endOffsetSubtitles = endOffset - 1;
178
+ if (endOffsetSubtitles <= 0) {
179
+ return;
180
+ }
181
+ data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles);
182
+ this.tracksBuffered.forEach((buffered) => {
183
+ if (!buffered) return;
184
+ for (let i = 0; i < buffered.length; ) {
185
+ if (buffered[i].end <= endOffsetSubtitles) {
186
+ buffered.shift();
187
+ continue;
188
+ } else if (buffered[i].start < endOffsetSubtitles) {
189
+ buffered[i].start = endOffsetSubtitles;
190
+ } else {
191
+ break;
192
+ }
193
+ i++;
194
+ }
195
+ });
196
+ this.fragmentTracker.removeFragmentsInRange(
197
+ startOffset,
198
+ endOffsetSubtitles,
199
+ PlaylistLevelType.SUBTITLE,
200
+ );
201
+ }
202
+ }
203
+
204
+ // If something goes wrong, proceed to next frag, if we were processing one.
205
+ protected onError(event: Events.ERROR, data: ErrorData) {
206
+ const frag = data.frag;
207
+
208
+ if (frag?.type === PlaylistLevelType.SUBTITLE) {
209
+ if (data.details === ErrorDetails.FRAG_GAP) {
210
+ this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
211
+ }
212
+ if (this.fragCurrent) {
213
+ this.fragCurrent.abortRequests();
214
+ }
215
+ if (this.state !== State.STOPPED) {
216
+ this.state = State.IDLE;
217
+ }
218
+ }
219
+ }
220
+
221
+ // Got all new subtitle levels.
222
+ private onSubtitleTracksUpdated(
223
+ event: Events.SUBTITLE_TRACKS_UPDATED,
224
+ { subtitleTracks }: SubtitleTracksUpdatedData,
225
+ ) {
226
+ if (this.levels && subtitleOptionsIdentical(this.levels, subtitleTracks)) {
227
+ this.levels = subtitleTracks.map(
228
+ (mediaPlaylist) => new Level(mediaPlaylist),
229
+ );
230
+ return;
231
+ }
232
+ this.tracksBuffered = [];
233
+ this.levels = subtitleTracks.map((mediaPlaylist) => {
234
+ const level = new Level(mediaPlaylist);
235
+ this.tracksBuffered[level.id] = [];
236
+ return level;
237
+ });
238
+ this.fragmentTracker.removeFragmentsInRange(
239
+ 0,
240
+ Number.POSITIVE_INFINITY,
241
+ PlaylistLevelType.SUBTITLE,
242
+ );
243
+ this.fragPrevious = null;
244
+ this.mediaBuffer = null;
245
+ }
246
+
247
+ private onSubtitleTrackSwitch(
248
+ event: Events.SUBTITLE_TRACK_SWITCH,
249
+ data: TrackSwitchedData,
250
+ ) {
251
+ this.currentTrackId = data.id;
252
+
253
+ if (!this.levels?.length || this.currentTrackId === -1) {
254
+ this.clearInterval();
255
+ return;
256
+ }
257
+
258
+ // Check if track has the necessary details to load fragments
259
+ const currentTrack = this.levels[this.currentTrackId] as Level | undefined;
260
+ if (!currentTrack?.details) {
261
+ this.mediaBuffer = null;
262
+ return;
263
+ }
264
+ this.mediaBuffer = this.mediaBufferTimeRanges;
265
+ if (this.state !== State.STOPPED) {
266
+ this.setInterval(TICK_INTERVAL);
267
+ }
268
+ }
269
+
270
+ // Got a new set of subtitle fragments.
271
+ private onSubtitleTrackLoaded(
272
+ event: Events.SUBTITLE_TRACK_LOADED,
273
+ data: TrackLoadedData,
274
+ ) {
275
+ const { currentTrackId, levels } = this;
276
+ const { details: newDetails, id: trackId } = data;
277
+ if (!levels) {
278
+ this.warn(`Subtitle tracks were reset while loading level ${trackId}`);
279
+ return;
280
+ }
281
+ const track = levels[trackId] as Level | undefined;
282
+ if (trackId >= levels.length || !track) {
283
+ return;
284
+ }
285
+ this.log(
286
+ `Subtitle track ${trackId} loaded [${newDetails.startSN},${
287
+ newDetails.endSN
288
+ }]${
289
+ newDetails.lastPartSn
290
+ ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]`
291
+ : ''
292
+ },duration:${newDetails.totalduration}`,
293
+ );
294
+ this.mediaBuffer = this.mediaBufferTimeRanges;
295
+
296
+ const mainDetails = this.mainDetails;
297
+ let sliding = 0;
298
+ if (newDetails.live || track.details?.live) {
299
+ if (newDetails.deltaUpdateFailed) {
300
+ return;
301
+ }
302
+ if (!mainDetails) {
303
+ this.startFragRequested = false;
304
+ return;
305
+ }
306
+ if (track.details) {
307
+ sliding = this.alignPlaylists(
308
+ newDetails,
309
+ track.details,
310
+ this.levelLastLoaded?.details,
311
+ );
312
+ }
313
+ if (!newDetails.alignedSliding) {
314
+ // line up live playlist with main so that fragments in range are loaded
315
+ alignStream(mainDetails, newDetails, this);
316
+ sliding = newDetails.fragmentStart;
317
+ }
318
+ }
319
+
320
+ track.details = newDetails;
321
+ this.levelLastLoaded = track;
322
+
323
+ // compute start position if we are aligned with the main playlist
324
+ if (mainDetails && !this.startFragRequested) {
325
+ this.setStartPosition(mainDetails, sliding);
326
+ }
327
+
328
+ if (trackId !== currentTrackId) {
329
+ return;
330
+ }
331
+
332
+ this.hls.trigger(Events.SUBTITLE_TRACK_UPDATED, {
333
+ details: newDetails,
334
+ id: trackId,
335
+ groupId: data.groupId,
336
+ });
337
+
338
+ // trigger handler right now
339
+ this.tickImmediate();
340
+ }
341
+
342
+ _handleFragmentLoadComplete(fragLoadedData: FragLoadedData) {
343
+ const { frag, payload } = fragLoadedData;
344
+ const decryptData = frag.decryptdata;
345
+ const hls = this.hls;
346
+
347
+ if (this.fragContextChanged(frag)) {
348
+ return;
349
+ }
350
+ // check to see if the payload needs to be decrypted
351
+ if (
352
+ payload &&
353
+ payload.byteLength > 0 &&
354
+ decryptData?.key &&
355
+ decryptData.iv &&
356
+ isFullSegmentEncryption(decryptData.method)
357
+ ) {
358
+ const startTime = performance.now();
359
+ // decrypt the subtitles
360
+ this.decrypter
361
+ .decrypt(
362
+ new Uint8Array(payload),
363
+ decryptData.key.buffer,
364
+ decryptData.iv.buffer,
365
+ getAesModeFromFullSegmentMethod(decryptData.method),
366
+ )
367
+ .catch((err) => {
368
+ hls.trigger(Events.ERROR, {
369
+ type: ErrorTypes.MEDIA_ERROR,
370
+ details: ErrorDetails.FRAG_DECRYPT_ERROR,
371
+ fatal: false,
372
+ error: err,
373
+ reason: err.message,
374
+ frag,
375
+ });
376
+ throw err;
377
+ })
378
+ .then((decryptedData) => {
379
+ const endTime = performance.now();
380
+ hls.trigger(Events.FRAG_DECRYPTED, {
381
+ frag,
382
+ payload: decryptedData,
383
+ stats: {
384
+ tstart: startTime,
385
+ tdecrypt: endTime,
386
+ },
387
+ });
388
+ })
389
+ .catch((err) => {
390
+ this.warn(`${err.name}: ${err.message}`);
391
+ this.state = State.IDLE;
392
+ });
393
+ }
394
+ }
395
+
396
+ doTick() {
397
+ if (this.state === State.IDLE) {
398
+ if (
399
+ !this.media &&
400
+ !this.primaryPrefetch &&
401
+ (this.startFragRequested || !this.config.startFragPrefetch)
402
+ ) {
403
+ return;
404
+ }
405
+ const { currentTrackId, levels } = this;
406
+ const track = levels?.[currentTrackId];
407
+ const trackDetails = track?.details;
408
+ if (
409
+ !trackDetails ||
410
+ this.waitForLive(track) ||
411
+ this.waitForCdnTuneIn(trackDetails)
412
+ ) {
413
+ this.startFragRequested = false;
414
+ return;
415
+ }
416
+ const { config } = this;
417
+ const currentTime = this.getLoadPosition();
418
+ const bufferedInfo = BufferHelper.bufferedInfo(
419
+ this.tracksBuffered[this.currentTrackId] || [],
420
+ currentTime,
421
+ config.maxBufferHole,
422
+ );
423
+ const { end: targetBufferTime, len: bufferLen } = bufferedInfo;
424
+ const maxBufLen =
425
+ this.hls.maxBufferLength + trackDetails.levelTargetDuration;
426
+
427
+ if (bufferLen > maxBufLen || (bufferLen && !this.buffering)) {
428
+ return;
429
+ }
430
+ let frag = this.getNextFragment(currentTime, trackDetails);
431
+ if (!frag) {
432
+ return;
433
+ }
434
+ // Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment
435
+ if (isMediaFragment(frag)) {
436
+ const curSNIdx = frag.sn - trackDetails.startSN;
437
+ const prevFrag = trackDetails.fragments[curSNIdx - 1] as
438
+ | MediaFragment
439
+ | undefined;
440
+ if (
441
+ prevFrag &&
442
+ prevFrag.cc === frag.cc &&
443
+ !trackDetails.partList?.length &&
444
+ this.fragmentTracker.getState(prevFrag) === FragmentState.NOT_LOADED
445
+ ) {
446
+ frag = prevFrag;
447
+ }
448
+ }
449
+ this.loadFragment(frag, track, targetBufferTime);
450
+ }
451
+ }
452
+
453
+ protected loadFragment(
454
+ frag: Fragment,
455
+ level: Level,
456
+ targetBufferTime: number,
457
+ ) {
458
+ // Check if fragment is not loaded
459
+ const fragState = this.fragmentTracker.getState(frag);
460
+ if (
461
+ fragState === FragmentState.NOT_LOADED ||
462
+ fragState === FragmentState.PARTIAL
463
+ ) {
464
+ if (!isMediaFragment(frag)) {
465
+ this._loadInitSegment(frag, level);
466
+ } else {
467
+ super.loadFragment(frag, level, targetBufferTime);
468
+ }
469
+ }
470
+ }
471
+
472
+ get mediaBufferTimeRanges(): Bufferable {
473
+ return new BufferableInstance(
474
+ this.tracksBuffered[this.currentTrackId] || [],
475
+ );
476
+ }
477
+ }
478
+
479
+ class BufferableInstance implements Bufferable {
480
+ public readonly buffered: TimeRanges;
481
+
482
+ constructor(timeranges: TimeRange[]) {
483
+ const getRange = (
484
+ name: 'start' | 'end',
485
+ index: number,
486
+ length: number,
487
+ ): number => {
488
+ index = index >>> 0;
489
+ if (index > length - 1) {
490
+ throw new DOMException(
491
+ `Failed to execute '${name}' on 'TimeRanges': The index provided (${index}) is greater than the maximum bound (${length})`,
492
+ );
493
+ }
494
+ return timeranges[index][name];
495
+ };
496
+ this.buffered = {
497
+ get length() {
498
+ return timeranges.length;
499
+ },
500
+ end(index: number): number {
501
+ return getRange('end', index, timeranges.length);
502
+ },
503
+ start(index: number): number {
504
+ return getRange('start', index, timeranges.length);
505
+ },
506
+ };
507
+ }
508
+ }