hls.js 1.5.12-0.canary.10398 → 1.5.12

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 (103) hide show
  1. package/README.md +3 -4
  2. package/dist/hls-demo.js +38 -41
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2678 -4226
  5. package/dist/hls.js.d.ts +109 -174
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1964 -2900
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +3409 -4360
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +4451 -6014
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +38 -38
  20. package/src/config.ts +2 -5
  21. package/src/controller/abr-controller.ts +25 -39
  22. package/src/controller/audio-stream-controller.ts +137 -141
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +10 -27
  25. package/src/controller/base-stream-controller.ts +97 -223
  26. package/src/controller/buffer-controller.ts +97 -250
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -3
  29. package/src/controller/cmcd-controller.ts +14 -51
  30. package/src/controller/content-steering-controller.ts +15 -29
  31. package/src/controller/eme-controller.ts +23 -10
  32. package/src/controller/error-controller.ts +22 -28
  33. package/src/controller/fps-controller.ts +3 -8
  34. package/src/controller/fragment-finders.ts +16 -44
  35. package/src/controller/fragment-tracker.ts +25 -58
  36. package/src/controller/gap-controller.ts +16 -43
  37. package/src/controller/id3-track-controller.ts +35 -45
  38. package/src/controller/latency-controller.ts +13 -18
  39. package/src/controller/level-controller.ts +19 -37
  40. package/src/controller/stream-controller.ts +83 -100
  41. package/src/controller/subtitle-stream-controller.ts +47 -35
  42. package/src/controller/subtitle-track-controller.ts +3 -5
  43. package/src/controller/timeline-controller.ts +22 -20
  44. package/src/crypt/aes-crypto.ts +2 -21
  45. package/src/crypt/decrypter.ts +16 -32
  46. package/src/crypt/fast-aes-key.ts +5 -28
  47. package/src/demux/audio/aacdemuxer.ts +2 -2
  48. package/src/demux/audio/ac3-demuxer.ts +3 -4
  49. package/src/demux/audio/adts.ts +4 -9
  50. package/src/demux/audio/base-audio-demuxer.ts +14 -16
  51. package/src/demux/audio/mp3demuxer.ts +3 -4
  52. package/src/demux/audio/mpegaudio.ts +1 -1
  53. package/src/demux/id3.ts +411 -0
  54. package/src/demux/mp4demuxer.ts +7 -7
  55. package/src/demux/sample-aes.ts +0 -2
  56. package/src/demux/transmuxer-interface.ts +16 -8
  57. package/src/demux/transmuxer-worker.ts +4 -4
  58. package/src/demux/transmuxer.ts +3 -16
  59. package/src/demux/tsdemuxer.ts +38 -75
  60. package/src/demux/video/avc-video-parser.ts +121 -210
  61. package/src/demux/video/base-video-parser.ts +2 -135
  62. package/src/demux/video/exp-golomb.ts +208 -0
  63. package/src/events.ts +1 -8
  64. package/src/exports-named.ts +1 -1
  65. package/src/hls.ts +43 -73
  66. package/src/loader/date-range.ts +5 -71
  67. package/src/loader/fragment-loader.ts +21 -23
  68. package/src/loader/fragment.ts +4 -8
  69. package/src/loader/key-loader.ts +1 -3
  70. package/src/loader/level-details.ts +6 -6
  71. package/src/loader/level-key.ts +9 -10
  72. package/src/loader/m3u8-parser.ts +144 -138
  73. package/src/loader/playlist-loader.ts +7 -5
  74. package/src/remux/mp4-generator.ts +1 -196
  75. package/src/remux/mp4-remuxer.ts +16 -36
  76. package/src/remux/passthrough-remuxer.ts +1 -1
  77. package/src/task-loop.ts +2 -5
  78. package/src/types/component-api.ts +1 -3
  79. package/src/types/demuxer.ts +0 -3
  80. package/src/types/events.ts +6 -19
  81. package/src/types/fragment-tracker.ts +2 -2
  82. package/src/types/general.ts +6 -0
  83. package/src/types/media-playlist.ts +1 -9
  84. package/src/types/remuxer.ts +1 -1
  85. package/src/utils/attr-list.ts +9 -96
  86. package/src/utils/buffer-helper.ts +31 -12
  87. package/src/utils/cea-608-parser.ts +3 -1
  88. package/src/utils/codecs.ts +5 -34
  89. package/src/utils/fetch-loader.ts +1 -1
  90. package/src/utils/hdr.ts +7 -4
  91. package/src/utils/imsc1-ttml-parser.ts +1 -1
  92. package/src/utils/keysystem-util.ts +6 -1
  93. package/src/utils/level-helper.ts +44 -71
  94. package/src/utils/logger.ts +23 -58
  95. package/src/utils/mp4-tools.ts +3 -5
  96. package/src/utils/rendition-helper.ts +74 -100
  97. package/src/utils/variable-substitution.ts +19 -0
  98. package/src/utils/webvtt-parser.ts +12 -2
  99. package/src/crypt/decrypter-aes-mode.ts +0 -4
  100. package/src/demux/video/hevc-video-parser.ts +0 -749
  101. package/src/utils/encryption-methods-util.ts +0 -21
  102. package/src/utils/hash.ts +0 -10
  103. package/src/utils/utf8-utils.ts +0 -18
@@ -1,15 +1,12 @@
1
1
  import TaskLoop from '../task-loop';
2
2
  import { FragmentState } from './fragment-tracker';
3
3
  import { Bufferable, BufferHelper, BufferInfo } from '../utils/buffer-helper';
4
+ import { logger } from '../utils/logger';
4
5
  import { Events } from '../events';
5
6
  import { ErrorDetails, ErrorTypes } from '../errors';
6
7
  import { ChunkMetadata } from '../types/transmuxer';
7
8
  import { appendUint8Array } from '../utils/mp4-tools';
8
9
  import { alignStream } from '../utils/discontinuities';
9
- import {
10
- isFullSegmentEncryption,
11
- getAesModeFromFullSegmentMethod,
12
- } from '../utils/encryption-methods-util';
13
10
  import {
14
11
  findFragmentByPDT,
15
12
  findFragmentByPTS,
@@ -22,7 +19,7 @@ import {
22
19
  updateFragPTSDTS,
23
20
  } from '../utils/level-helper';
24
21
  import TransmuxerInterface from '../demux/transmuxer-interface';
25
- import { Fragment, MediaFragment, Part } from '../loader/fragment';
22
+ import { Fragment, Part } from '../loader/fragment';
26
23
  import FragmentLoader, {
27
24
  FragmentLoadProgressCallback,
28
25
  LoadError,
@@ -77,7 +74,7 @@ export default class BaseStreamController
77
74
  {
78
75
  protected hls: Hls;
79
76
 
80
- protected fragPrevious: MediaFragment | null = null;
77
+ protected fragPrevious: Fragment | null = null;
81
78
  protected fragCurrent: Fragment | null = null;
82
79
  protected fragmentTracker: FragmentTracker;
83
80
  protected transmuxer: TransmuxerInterface | null = null;
@@ -100,9 +97,12 @@ export default class BaseStreamController
100
97
  protected startFragRequested: boolean = false;
101
98
  protected decrypter: Decrypter;
102
99
  protected initPTS: RationalTimestamp[] = [];
103
- protected buffering: boolean = true;
104
- private loadingParts: boolean = false;
105
- private loopSn?: string | number;
100
+ protected onvseeking: EventListener | null = null;
101
+ protected onvended: EventListener | null = null;
102
+
103
+ private readonly logPrefix: string = '';
104
+ protected log: (msg: any) => void;
105
+ protected warn: (msg: any) => void;
106
106
 
107
107
  constructor(
108
108
  hls: Hls,
@@ -111,32 +111,18 @@ export default class BaseStreamController
111
111
  logPrefix: string,
112
112
  playlistType: PlaylistLevelType,
113
113
  ) {
114
- super(logPrefix, hls.logger);
114
+ super();
115
115
  this.playlistType = playlistType;
116
+ this.logPrefix = logPrefix;
117
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
118
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
116
119
  this.hls = hls;
117
120
  this.fragmentLoader = new FragmentLoader(hls.config);
118
121
  this.keyLoader = keyLoader;
119
122
  this.fragmentTracker = fragmentTracker;
120
123
  this.config = hls.config;
121
124
  this.decrypter = new Decrypter(hls.config);
122
- }
123
-
124
- protected registerListeners() {
125
- const { hls } = this;
126
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
127
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
128
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
129
125
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
130
- hls.on(Events.ERROR, this.onError, this);
131
- }
132
-
133
- protected unregisterListeners() {
134
- const { hls } = this;
135
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
136
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
137
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
138
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
139
- hls.off(Events.ERROR, this.onError, this);
140
126
  }
141
127
 
142
128
  protected doTick() {
@@ -149,9 +135,6 @@ export default class BaseStreamController
149
135
  public startLoad(startPosition: number): void {}
150
136
 
151
137
  public stopLoad() {
152
- if (this.state === State.STOPPED) {
153
- return;
154
- }
155
138
  this.fragmentLoader.abort();
156
139
  this.keyLoader.abort(this.playlistType);
157
140
  const frag = this.fragCurrent;
@@ -167,14 +150,6 @@ export default class BaseStreamController
167
150
  this.state = State.STOPPED;
168
151
  }
169
152
 
170
- public pauseBuffering() {
171
- this.buffering = false;
172
- }
173
-
174
- public resumeBuffering() {
175
- this.buffering = true;
176
- }
177
-
178
153
  protected _streamEnded(
179
154
  bufferInfo: BufferInfo,
180
155
  levelDetails: LevelDetails,
@@ -222,8 +197,10 @@ export default class BaseStreamController
222
197
  data: MediaAttachedData,
223
198
  ) {
224
199
  const media = (this.media = this.mediaBuffer = data.media);
225
- media.addEventListener('seeking', this.onMediaSeeking);
226
- media.addEventListener('ended', this.onMediaEnded);
200
+ this.onvseeking = this.onMediaSeeking.bind(this) as EventListener;
201
+ this.onvended = this.onMediaEnded.bind(this) as EventListener;
202
+ media.addEventListener('seeking', this.onvseeking);
203
+ media.addEventListener('ended', this.onvended);
227
204
  const config = this.config;
228
205
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
229
206
  this.startLoad(config.startPosition);
@@ -238,30 +215,21 @@ export default class BaseStreamController
238
215
  }
239
216
 
240
217
  // remove video listeners
241
- if (media) {
242
- media.removeEventListener('seeking', this.onMediaSeeking);
243
- media.removeEventListener('ended', this.onMediaEnded);
218
+ if (media && this.onvseeking && this.onvended) {
219
+ media.removeEventListener('seeking', this.onvseeking);
220
+ media.removeEventListener('ended', this.onvended);
221
+ this.onvseeking = this.onvended = null;
244
222
  }
245
223
  if (this.keyLoader) {
246
224
  this.keyLoader.detach();
247
225
  }
248
226
  this.media = this.mediaBuffer = null;
249
- this.loopSn = undefined;
250
- this.startFragRequested = this.loadedmetadata = this.loadingParts = false;
227
+ this.loadedmetadata = false;
251
228
  this.fragmentTracker.removeAllFragments();
252
229
  this.stopLoad();
253
230
  }
254
231
 
255
- protected onManifestLoading() {
256
- this.initPTS = [];
257
- this.levels = this.levelLastLoaded = this.fragCurrent = null;
258
- this.lastCurrentTime = this.startPosition = 0;
259
- this.startFragRequested = false;
260
- }
261
-
262
- protected onError(event: Events.ERROR, data: ErrorData) {}
263
-
264
- protected onMediaSeeking = () => {
232
+ protected onMediaSeeking() {
265
233
  const { config, fragCurrent, media, mediaBuffer, state } = this;
266
234
  const currentTime: number = media ? media.currentTime : 0;
267
235
  const bufferInfo = BufferHelper.bufferInfo(
@@ -315,21 +283,6 @@ export default class BaseStreamController
315
283
  );
316
284
 
317
285
  this.lastCurrentTime = currentTime;
318
- if (!this.loadingParts) {
319
- const bufferEnd = Math.max(bufferInfo.end, currentTime);
320
- const shouldLoadParts = this.shouldLoadParts(
321
- this.getLevelDetails(),
322
- bufferEnd,
323
- );
324
- if (shouldLoadParts) {
325
- this.log(
326
- `LL-Part loading ON after seeking to ${currentTime.toFixed(
327
- 2,
328
- )} with buffer @${bufferEnd.toFixed(2)}`,
329
- );
330
- this.loadingParts = shouldLoadParts;
331
- }
332
- }
333
286
  }
334
287
 
335
288
  // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
@@ -339,30 +292,27 @@ export default class BaseStreamController
339
292
 
340
293
  // Async tick to speed up processing
341
294
  this.tickImmediate();
342
- };
295
+ }
343
296
 
344
- protected onMediaEnded = () => {
297
+ protected onMediaEnded() {
345
298
  // reset startPosition and lastCurrentTime to restart playback @ stream beginning
346
299
  this.startPosition = this.lastCurrentTime = 0;
347
- if (this.playlistType === PlaylistLevelType.MAIN) {
348
- this.hls.trigger(Events.MEDIA_ENDED, {
349
- stalled: false,
350
- });
351
- }
352
- };
300
+ }
353
301
 
354
302
  protected onManifestLoaded(
355
303
  event: Events.MANIFEST_LOADED,
356
304
  data: ManifestLoadedData,
357
305
  ): void {
358
306
  this.startTimeOffset = data.startTimeOffset;
307
+ this.initPTS = [];
359
308
  }
360
309
 
361
310
  protected onHandlerDestroying() {
311
+ this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
362
312
  this.stopLoad();
363
313
  super.onHandlerDestroying();
364
314
  // @ts-ignore
365
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
315
+ this.hls = null;
366
316
  }
367
317
 
368
318
  protected onHandlerDestroyed() {
@@ -393,7 +343,6 @@ export default class BaseStreamController
393
343
  level: Level,
394
344
  targetBufferTime: number,
395
345
  ) {
396
- this.startFragRequested = true;
397
346
  this._loadFragForPlayback(frag, level, targetBufferTime);
398
347
  }
399
348
 
@@ -476,7 +425,7 @@ export default class BaseStreamController
476
425
  : 0;
477
426
  if (
478
427
  backtracked === 1 ||
479
- this.reduceMaxBufferLength(minForwardBufferLength)
428
+ this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)
480
429
  ) {
481
430
  fragmentTracker.removeFragment(frag);
482
431
  }
@@ -547,7 +496,7 @@ export default class BaseStreamController
547
496
  payload.byteLength > 0 &&
548
497
  decryptData?.key &&
549
498
  decryptData.iv &&
550
- isFullSegmentEncryption(decryptData.method)
499
+ decryptData.method === 'AES-128'
551
500
  ) {
552
501
  const startTime = self.performance.now();
553
502
  // decrypt init segment data
@@ -556,7 +505,6 @@ export default class BaseStreamController
556
505
  new Uint8Array(payload),
557
506
  decryptData.key.buffer,
558
507
  decryptData.iv.buffer,
559
- getAesModeFromFullSegmentMethod(decryptData.method),
560
508
  )
561
509
  .catch((err) => {
562
510
  hls.trigger(Events.ERROR, {
@@ -600,9 +548,7 @@ export default class BaseStreamController
600
548
  throw new Error('init load aborted, missing levels');
601
549
  }
602
550
  const stats = data.frag.stats;
603
- if (this.state !== State.STOPPED) {
604
- this.state = State.IDLE;
605
- }
551
+ this.state = State.IDLE;
606
552
  data.frag.data = new Uint8Array(data.payload);
607
553
  stats.parsing.start = stats.buffering.start = self.performance.now();
608
554
  stats.parsing.end = stats.buffering.end = self.performance.now();
@@ -702,7 +648,6 @@ export default class BaseStreamController
702
648
  targetBufferTime: number | null = null,
703
649
  progressCallback?: FragmentLoadProgressCallback,
704
650
  ): Promise<PartsLoadedData | FragLoadedData | null> {
705
- this.fragCurrent = frag;
706
651
  const details = level?.details;
707
652
  if (!this.levels || !details) {
708
653
  throw new Error(
@@ -714,7 +659,7 @@ export default class BaseStreamController
714
659
  if (frag.encrypted && !frag.decryptdata?.key) {
715
660
  this.log(
716
661
  `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
717
- this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
662
+ this.logPrefix === '[stream-controller]' ? 'level' : 'track'
718
663
  } ${frag.level}`,
719
664
  );
720
665
  this.state = State.KEY_LOADING;
@@ -738,23 +683,8 @@ export default class BaseStreamController
738
683
  this.keyLoader.loadClear(frag, details.encryptedFragments);
739
684
  }
740
685
 
741
- const fragPrevious = this.fragPrevious;
742
- if (
743
- frag.sn !== 'initSegment' &&
744
- (!fragPrevious || frag.sn !== fragPrevious.sn)
745
- ) {
746
- const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
747
- if (shouldLoadParts !== this.loadingParts) {
748
- this.log(
749
- `LL-Part loading ${
750
- shouldLoadParts ? 'ON' : 'OFF'
751
- } loading sn ${fragPrevious?.sn}->${frag.sn}`,
752
- );
753
- this.loadingParts = shouldLoadParts;
754
- }
755
- }
756
686
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
757
- if (this.loadingParts && frag.sn !== 'initSegment') {
687
+ if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
758
688
  const partList = details.partList;
759
689
  if (partList && progressCallback) {
760
690
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -769,7 +699,7 @@ export default class BaseStreamController
769
699
  } of playlist [${details.startSN}-${
770
700
  details.endSN
771
701
  }] parts [0-${partIndex}-${partList.length - 1}] ${
772
- this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
702
+ this.logPrefix === '[stream-controller]' ? 'level' : 'track'
773
703
  }: ${frag.level}, target: ${parseFloat(
774
704
  targetBufferTime.toFixed(3),
775
705
  )}`,
@@ -825,22 +755,10 @@ export default class BaseStreamController
825
755
  }
826
756
  }
827
757
 
828
- if (frag.sn !== 'initSegment' && this.loadingParts) {
829
- this.log(
830
- `LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
831
- 2,
832
- )}`,
833
- );
834
- this.loadingParts = false;
835
- } else if (!frag.url) {
836
- // Selected fragment hint for part but not loading parts
837
- return Promise.resolve(null);
838
- }
839
-
840
758
  this.log(
841
759
  `Loading fragment ${frag.sn} cc: ${frag.cc} ${
842
760
  details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
843
- }${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
761
+ }${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
844
762
  frag.level
845
763
  }, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
846
764
  );
@@ -907,7 +825,7 @@ export default class BaseStreamController
907
825
  const loadedPart = partLoadedData.part as Part;
908
826
  this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
909
827
  const nextPart =
910
- getPartWith(level.details, frag.sn as number, part.index + 1) ||
828
+ getPartWith(level, frag.sn as number, part.index + 1) ||
911
829
  findPart(initialPartList, frag.sn as number, part.index + 1);
912
830
  if (nextPart) {
913
831
  loadPart(nextPart);
@@ -964,50 +882,12 @@ export default class BaseStreamController
964
882
  if (part) {
965
883
  part.stats.parsing.end = now;
966
884
  }
967
- // See if part loading should be disabled/enabled based on buffer and playback position.
968
- const levelDetails = this.getLevelDetails();
969
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
970
- const shouldLoadParts =
971
- loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
972
- if (shouldLoadParts !== this.loadingParts) {
973
- this.log(
974
- `LL-Part loading ${
975
- shouldLoadParts ? 'ON' : 'OFF'
976
- } after parsing segment ending @${frag.end.toFixed(2)}`,
977
- );
978
- this.loadingParts = shouldLoadParts;
979
- }
980
885
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
981
886
  }
982
887
 
983
- private shouldLoadParts(
984
- details: LevelDetails | undefined,
985
- bufferEnd: number,
986
- ): boolean {
987
- if (this.config.lowLatencyMode) {
988
- if (!details) {
989
- return this.loadingParts;
990
- }
991
- if (details?.partList) {
992
- // Buffer must be ahead of first part + duration of parts after last segment
993
- // and playback must be at or past segment adjacent to part list
994
- const firstPart = details.partList[0];
995
- const safePartStart =
996
- firstPart.end + (details.fragmentHint?.duration || 0);
997
- if (
998
- bufferEnd >= safePartStart &&
999
- this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
1000
- ) {
1001
- return true;
1002
- }
1003
- }
1004
- }
1005
- return false;
1006
- }
1007
-
1008
888
  protected getCurrentContext(
1009
889
  chunkMeta: ChunkMetadata,
1010
- ): { frag: MediaFragment; part: Part | null; level: Level } | null {
890
+ ): { frag: Fragment; part: Part | null; level: Level } | null {
1011
891
  const { levels, fragCurrent } = this;
1012
892
  const { level: levelIndex, sn, part: partIndex } = chunkMeta;
1013
893
  if (!levels?.[levelIndex]) {
@@ -1017,13 +897,10 @@ export default class BaseStreamController
1017
897
  return null;
1018
898
  }
1019
899
  const level = levels[levelIndex];
1020
- const levelDetails = level.details;
1021
-
1022
- const part =
1023
- partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
900
+ const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
1024
901
  const frag = part
1025
902
  ? part.fragment
1026
- : getFragmentWithSN(levelDetails, sn, fragCurrent);
903
+ : getFragmentWithSN(level, sn, fragCurrent);
1027
904
  if (!frag) {
1028
905
  return null;
1029
906
  }
@@ -1124,10 +1001,7 @@ export default class BaseStreamController
1124
1001
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
1125
1002
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
1126
1003
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
1127
- if (
1128
- bufferedFragAtPos &&
1129
- (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
1130
- ) {
1004
+ if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
1131
1005
  return BufferHelper.bufferInfo(
1132
1006
  bufferable,
1133
1007
  pos,
@@ -1140,7 +1014,7 @@ export default class BaseStreamController
1140
1014
 
1141
1015
  protected getMaxBufferLength(levelBitrate?: number): number {
1142
1016
  const { config } = this;
1143
- let maxBufLen: number;
1017
+ let maxBufLen;
1144
1018
  if (levelBitrate) {
1145
1019
  maxBufLen = Math.max(
1146
1020
  (8 * config.maxBufferSize) / levelBitrate,
@@ -1152,10 +1026,16 @@ export default class BaseStreamController
1152
1026
  return Math.min(maxBufLen, config.maxMaxBufferLength);
1153
1027
  }
1154
1028
 
1155
- protected reduceMaxBufferLength(threshold: number) {
1029
+ protected reduceMaxBufferLength(threshold: number, fragDuration: number) {
1156
1030
  const config = this.config;
1157
- const minLength = threshold || config.maxBufferLength;
1158
- const reducedLength = config.maxMaxBufferLength / 2;
1031
+ const minLength = Math.max(
1032
+ Math.min(threshold, config.maxBufferLength),
1033
+ fragDuration,
1034
+ );
1035
+ const reducedLength = Math.max(
1036
+ threshold - fragDuration * 3,
1037
+ config.maxMaxBufferLength / 2,
1038
+ );
1159
1039
  if (reducedLength >= minLength) {
1160
1040
  // reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
1161
1041
  config.maxMaxBufferLength = reducedLength;
@@ -1169,9 +1049,9 @@ export default class BaseStreamController
1169
1049
  position: number,
1170
1050
  playlistType: PlaylistLevelType = PlaylistLevelType.MAIN,
1171
1051
  ): Fragment | null {
1172
- const fragOrPart = this.fragmentTracker?.getAppendedFrag(
1052
+ const fragOrPart = this.fragmentTracker.getAppendedFrag(
1173
1053
  position,
1174
- playlistType,
1054
+ PlaylistLevelType.MAIN,
1175
1055
  );
1176
1056
  if (fragOrPart && 'fragment' in fragOrPart) {
1177
1057
  return fragOrPart.fragment;
@@ -1193,8 +1073,7 @@ export default class BaseStreamController
1193
1073
  // find fragment index, contiguous with end of buffer position
1194
1074
  const { config } = this;
1195
1075
  const start = fragments[0].start;
1196
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
1197
- let frag: MediaFragment | null = null;
1076
+ let frag;
1198
1077
 
1199
1078
  if (levelDetails.live) {
1200
1079
  const initialLiveManifestSize = config.initialLiveManifestSize;
@@ -1214,10 +1093,6 @@ export default class BaseStreamController
1214
1093
  this.startPosition === -1) ||
1215
1094
  pos < start
1216
1095
  ) {
1217
- if (canLoadParts && !this.loadingParts) {
1218
- this.log(`LL-Part loading ON for initial live fragment`);
1219
- this.loadingParts = true;
1220
- }
1221
1096
  frag = this.getInitialLiveFragment(levelDetails, fragments);
1222
1097
  this.startPosition = this.nextLoadPosition = frag
1223
1098
  ? this.hls.liveSyncPosition || frag.start
@@ -1230,7 +1105,7 @@ export default class BaseStreamController
1230
1105
 
1231
1106
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
1232
1107
  if (!frag) {
1233
- const end = this.loadingParts
1108
+ const end = config.lowLatencyMode
1234
1109
  ? levelDetails.partEnd
1235
1110
  : levelDetails.fragmentEnd;
1236
1111
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
@@ -1255,34 +1130,34 @@ export default class BaseStreamController
1255
1130
  playlistType: PlaylistLevelType,
1256
1131
  maxBufLen: number,
1257
1132
  ): Fragment | null {
1258
- let nextFragment: Fragment | null = null;
1259
- if (frag.gap) {
1260
- nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
1261
- if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
1262
- // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
1263
- const nextbufferInfo = this.getFwdBufferInfoAtPos(
1264
- this.mediaBuffer ? this.mediaBuffer : this.media,
1265
- bufferInfo.nextStart,
1266
- playlistType,
1133
+ const gapStart = frag.gap;
1134
+ const nextFragment = this.getNextFragment(
1135
+ this.nextLoadPosition,
1136
+ levelDetails,
1137
+ );
1138
+ if (nextFragment === null) {
1139
+ return nextFragment;
1140
+ }
1141
+ frag = nextFragment;
1142
+ if (gapStart && frag && !frag.gap && bufferInfo.nextStart) {
1143
+ // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
1144
+ const nextbufferInfo = this.getFwdBufferInfoAtPos(
1145
+ this.mediaBuffer ? this.mediaBuffer : this.media,
1146
+ bufferInfo.nextStart,
1147
+ playlistType,
1148
+ );
1149
+ if (
1150
+ nextbufferInfo !== null &&
1151
+ bufferInfo.len + nextbufferInfo.len >= maxBufLen
1152
+ ) {
1153
+ // Returning here might result in not finding an audio and video candiate to skip to
1154
+ this.log(
1155
+ `buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`,
1267
1156
  );
1268
- if (
1269
- nextbufferInfo !== null &&
1270
- bufferInfo.len + nextbufferInfo.len >= maxBufLen
1271
- ) {
1272
- // Returning here might result in not finding an audio and video candiate to skip to
1273
- const sn = nextFragment.sn;
1274
- if (this.loopSn !== sn) {
1275
- this.log(
1276
- `buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`,
1277
- );
1278
- this.loopSn = sn;
1279
- }
1280
- return null;
1281
- }
1157
+ return null;
1282
1158
  }
1283
1159
  }
1284
- this.loopSn = undefined;
1285
- return nextFragment;
1160
+ return frag;
1286
1161
  }
1287
1162
 
1288
1163
  mapToInitFragWhenRequired(frag: Fragment | null): typeof frag {
@@ -1337,10 +1212,10 @@ export default class BaseStreamController
1337
1212
  */
1338
1213
  protected getInitialLiveFragment(
1339
1214
  levelDetails: LevelDetails,
1340
- fragments: MediaFragment[],
1341
- ): MediaFragment | null {
1215
+ fragments: Array<Fragment>,
1216
+ ): Fragment | null {
1342
1217
  const fragPrevious = this.fragPrevious;
1343
- let frag: MediaFragment | null = null;
1218
+ let frag: Fragment | null = null;
1344
1219
  if (fragPrevious) {
1345
1220
  if (levelDetails.hasProgramDateTime) {
1346
1221
  // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
@@ -1404,7 +1279,7 @@ export default class BaseStreamController
1404
1279
  bufferEnd: number,
1405
1280
  end: number,
1406
1281
  levelDetails: LevelDetails,
1407
- ): MediaFragment | null {
1282
+ ): Fragment | null {
1408
1283
  const { config } = this;
1409
1284
  let { fragPrevious } = this;
1410
1285
  let { fragments, endSN } = levelDetails;
@@ -1413,17 +1288,17 @@ export default class BaseStreamController
1413
1288
  const partList = levelDetails.partList;
1414
1289
 
1415
1290
  const loadingParts = !!(
1416
- this.loadingParts &&
1291
+ config.lowLatencyMode &&
1417
1292
  partList?.length &&
1418
1293
  fragmentHint
1419
1294
  );
1420
1295
  if (loadingParts && fragmentHint && !this.bitrateTest) {
1421
1296
  // Include incomplete fragment with parts at end
1422
1297
  fragments = fragments.concat(fragmentHint);
1423
- endSN = fragmentHint.sn;
1298
+ endSN = fragmentHint.sn as number;
1424
1299
  }
1425
1300
 
1426
- let frag: MediaFragment | null;
1301
+ let frag;
1427
1302
  if (bufferEnd < end) {
1428
1303
  const lookupTolerance =
1429
1304
  bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
@@ -1579,7 +1454,7 @@ export default class BaseStreamController
1579
1454
  if (startTimeOffset !== null && Number.isFinite(startTimeOffset)) {
1580
1455
  startPosition = sliding + startTimeOffset;
1581
1456
  if (startTimeOffset < 0) {
1582
- startPosition += details.edge;
1457
+ startPosition += details.totalduration;
1583
1458
  }
1584
1459
  startPosition = Math.min(
1585
1460
  Math.max(sliding, startPosition),
@@ -1660,7 +1535,7 @@ export default class BaseStreamController
1660
1535
  }
1661
1536
  const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
1662
1537
  if (gapTagEncountered) {
1663
- this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
1538
+ this.fragmentTracker.fragBuffered(frag, true);
1664
1539
  }
1665
1540
  // keep retrying until the limit will be reached
1666
1541
  const errorAction = data.errorAction;
@@ -1693,7 +1568,7 @@ export default class BaseStreamController
1693
1568
  errorAction.resolved = true;
1694
1569
  }
1695
1570
  } else {
1696
- this.warn(
1571
+ logger.warn(
1697
1572
  `${data.details} reached or exceeded max retry (${retryCount})`,
1698
1573
  );
1699
1574
  return;
@@ -1712,6 +1587,7 @@ export default class BaseStreamController
1712
1587
  protected reduceLengthAndFlushBuffer(data: ErrorData): boolean {
1713
1588
  // if in appending state
1714
1589
  if (this.state === State.PARSING || this.state === State.PARSED) {
1590
+ const frag = data.frag;
1715
1591
  const playlistType = data.parent as PlaylistLevelType;
1716
1592
  const bufferedInfo = this.getFwdBufferInfo(
1717
1593
  this.mediaBuffer,
@@ -1721,7 +1597,7 @@ export default class BaseStreamController
1721
1597
  // reduce max buf len if current position is buffered
1722
1598
  const buffered = bufferedInfo && bufferedInfo.len > 0.5;
1723
1599
  if (buffered) {
1724
- this.reduceMaxBufferLength(bufferedInfo.len);
1600
+ this.reduceMaxBufferLength(bufferedInfo.len, frag?.duration || 10);
1725
1601
  }
1726
1602
  const flushBuffer = !buffered;
1727
1603
  if (flushBuffer) {
@@ -1732,9 +1608,9 @@ export default class BaseStreamController
1732
1608
  `Buffer full error while media.currentTime is not buffered, flush ${playlistType} buffer`,
1733
1609
  );
1734
1610
  }
1735
- if (data.frag) {
1736
- this.fragmentTracker.removeFragment(data.frag);
1737
- this.nextLoadPosition = data.frag.start;
1611
+ if (frag) {
1612
+ this.fragmentTracker.removeFragment(frag);
1613
+ this.nextLoadPosition = frag.start;
1738
1614
  }
1739
1615
  this.resetLoadingState();
1740
1616
  return flushBuffer;
@@ -1782,9 +1658,7 @@ export default class BaseStreamController
1782
1658
  this.log('Reset loading state');
1783
1659
  this.fragCurrent = null;
1784
1660
  this.fragPrevious = null;
1785
- if (this.state !== State.STOPPED) {
1786
- this.state = State.IDLE;
1787
- }
1661
+ this.state = State.IDLE;
1788
1662
  }
1789
1663
 
1790
1664
  protected resetStartWhenNotLoaded(level: Level | null): void {
@@ -1824,7 +1698,7 @@ export default class BaseStreamController
1824
1698
  }
1825
1699
 
1826
1700
  private updateLevelTiming(
1827
- frag: MediaFragment,
1701
+ frag: Fragment,
1828
1702
  part: Part | null,
1829
1703
  level: Level,
1830
1704
  partial: boolean,