hls.js 1.5.13-0.canary.10410 → 1.5.13

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 +2704 -4236
  5. package/dist/hls.js.d.ts +110 -179
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1994 -2914
  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 +3458 -4388
  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 +4506 -6048
  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 +91 -235
  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 +47 -84
  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 +62 -32
  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(
@@ -314,27 +282,7 @@ export default class BaseStreamController
314
282
  true,
315
283
  );
316
284
 
317
- // Don't set lastCurrentTime with backward seeks (allows for frag selection with strict tolerances)
318
- const lastCurrentTime = this.lastCurrentTime;
319
- if (currentTime > lastCurrentTime) {
320
- this.lastCurrentTime = currentTime;
321
- }
322
-
323
- if (!this.loadingParts) {
324
- const bufferEnd = Math.max(bufferInfo.end, currentTime);
325
- const shouldLoadParts = this.shouldLoadParts(
326
- this.getLevelDetails(),
327
- bufferEnd,
328
- );
329
- if (shouldLoadParts) {
330
- this.log(
331
- `LL-Part loading ON after seeking to ${currentTime.toFixed(
332
- 2,
333
- )} with buffer @${bufferEnd.toFixed(2)}`,
334
- );
335
- this.loadingParts = shouldLoadParts;
336
- }
337
- }
285
+ this.lastCurrentTime = currentTime;
338
286
  }
339
287
 
340
288
  // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
@@ -344,30 +292,27 @@ export default class BaseStreamController
344
292
 
345
293
  // Async tick to speed up processing
346
294
  this.tickImmediate();
347
- };
295
+ }
348
296
 
349
- protected onMediaEnded = () => {
297
+ protected onMediaEnded() {
350
298
  // reset startPosition and lastCurrentTime to restart playback @ stream beginning
351
299
  this.startPosition = this.lastCurrentTime = 0;
352
- if (this.playlistType === PlaylistLevelType.MAIN) {
353
- this.hls.trigger(Events.MEDIA_ENDED, {
354
- stalled: false,
355
- });
356
- }
357
- };
300
+ }
358
301
 
359
302
  protected onManifestLoaded(
360
303
  event: Events.MANIFEST_LOADED,
361
304
  data: ManifestLoadedData,
362
305
  ): void {
363
306
  this.startTimeOffset = data.startTimeOffset;
307
+ this.initPTS = [];
364
308
  }
365
309
 
366
310
  protected onHandlerDestroying() {
311
+ this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
367
312
  this.stopLoad();
368
313
  super.onHandlerDestroying();
369
314
  // @ts-ignore
370
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
315
+ this.hls = null;
371
316
  }
372
317
 
373
318
  protected onHandlerDestroyed() {
@@ -398,7 +343,6 @@ export default class BaseStreamController
398
343
  level: Level,
399
344
  targetBufferTime: number,
400
345
  ) {
401
- this.startFragRequested = true;
402
346
  this._loadFragForPlayback(frag, level, targetBufferTime);
403
347
  }
404
348
 
@@ -552,7 +496,7 @@ export default class BaseStreamController
552
496
  payload.byteLength > 0 &&
553
497
  decryptData?.key &&
554
498
  decryptData.iv &&
555
- isFullSegmentEncryption(decryptData.method)
499
+ decryptData.method === 'AES-128'
556
500
  ) {
557
501
  const startTime = self.performance.now();
558
502
  // decrypt init segment data
@@ -561,7 +505,6 @@ export default class BaseStreamController
561
505
  new Uint8Array(payload),
562
506
  decryptData.key.buffer,
563
507
  decryptData.iv.buffer,
564
- getAesModeFromFullSegmentMethod(decryptData.method),
565
508
  )
566
509
  .catch((err) => {
567
510
  hls.trigger(Events.ERROR, {
@@ -605,9 +548,7 @@ export default class BaseStreamController
605
548
  throw new Error('init load aborted, missing levels');
606
549
  }
607
550
  const stats = data.frag.stats;
608
- if (this.state !== State.STOPPED) {
609
- this.state = State.IDLE;
610
- }
551
+ this.state = State.IDLE;
611
552
  data.frag.data = new Uint8Array(data.payload);
612
553
  stats.parsing.start = stats.buffering.start = self.performance.now();
613
554
  stats.parsing.end = stats.buffering.end = self.performance.now();
@@ -707,7 +648,6 @@ export default class BaseStreamController
707
648
  targetBufferTime: number | null = null,
708
649
  progressCallback?: FragmentLoadProgressCallback,
709
650
  ): Promise<PartsLoadedData | FragLoadedData | null> {
710
- this.fragCurrent = frag;
711
651
  const details = level?.details;
712
652
  if (!this.levels || !details) {
713
653
  throw new Error(
@@ -719,7 +659,7 @@ export default class BaseStreamController
719
659
  if (frag.encrypted && !frag.decryptdata?.key) {
720
660
  this.log(
721
661
  `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
722
- this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
662
+ this.logPrefix === '[stream-controller]' ? 'level' : 'track'
723
663
  } ${frag.level}`,
724
664
  );
725
665
  this.state = State.KEY_LOADING;
@@ -743,23 +683,8 @@ export default class BaseStreamController
743
683
  this.keyLoader.loadClear(frag, details.encryptedFragments);
744
684
  }
745
685
 
746
- const fragPrevious = this.fragPrevious;
747
- if (
748
- frag.sn !== 'initSegment' &&
749
- (!fragPrevious || frag.sn !== fragPrevious.sn)
750
- ) {
751
- const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
752
- if (shouldLoadParts !== this.loadingParts) {
753
- this.log(
754
- `LL-Part loading ${
755
- shouldLoadParts ? 'ON' : 'OFF'
756
- } loading sn ${fragPrevious?.sn}->${frag.sn}`,
757
- );
758
- this.loadingParts = shouldLoadParts;
759
- }
760
- }
761
686
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
762
- if (this.loadingParts && frag.sn !== 'initSegment') {
687
+ if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
763
688
  const partList = details.partList;
764
689
  if (partList && progressCallback) {
765
690
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -774,7 +699,7 @@ export default class BaseStreamController
774
699
  } of playlist [${details.startSN}-${
775
700
  details.endSN
776
701
  }] parts [0-${partIndex}-${partList.length - 1}] ${
777
- this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
702
+ this.logPrefix === '[stream-controller]' ? 'level' : 'track'
778
703
  }: ${frag.level}, target: ${parseFloat(
779
704
  targetBufferTime.toFixed(3),
780
705
  )}`,
@@ -830,22 +755,10 @@ export default class BaseStreamController
830
755
  }
831
756
  }
832
757
 
833
- if (frag.sn !== 'initSegment' && this.loadingParts) {
834
- this.log(
835
- `LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
836
- 2,
837
- )}`,
838
- );
839
- this.loadingParts = false;
840
- } else if (!frag.url) {
841
- // Selected fragment hint for part but not loading parts
842
- return Promise.resolve(null);
843
- }
844
-
845
758
  this.log(
846
759
  `Loading fragment ${frag.sn} cc: ${frag.cc} ${
847
760
  details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
848
- }${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
761
+ }${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
849
762
  frag.level
850
763
  }, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
851
764
  );
@@ -912,7 +825,7 @@ export default class BaseStreamController
912
825
  const loadedPart = partLoadedData.part as Part;
913
826
  this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
914
827
  const nextPart =
915
- getPartWith(level.details, frag.sn as number, part.index + 1) ||
828
+ getPartWith(level, frag.sn as number, part.index + 1) ||
916
829
  findPart(initialPartList, frag.sn as number, part.index + 1);
917
830
  if (nextPart) {
918
831
  loadPart(nextPart);
@@ -969,50 +882,12 @@ export default class BaseStreamController
969
882
  if (part) {
970
883
  part.stats.parsing.end = now;
971
884
  }
972
- // See if part loading should be disabled/enabled based on buffer and playback position.
973
- const levelDetails = this.getLevelDetails();
974
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
975
- const shouldLoadParts =
976
- loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
977
- if (shouldLoadParts !== this.loadingParts) {
978
- this.log(
979
- `LL-Part loading ${
980
- shouldLoadParts ? 'ON' : 'OFF'
981
- } after parsing segment ending @${frag.end.toFixed(2)}`,
982
- );
983
- this.loadingParts = shouldLoadParts;
984
- }
985
885
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
986
886
  }
987
887
 
988
- private shouldLoadParts(
989
- details: LevelDetails | undefined,
990
- bufferEnd: number,
991
- ): boolean {
992
- if (this.config.lowLatencyMode) {
993
- if (!details) {
994
- return this.loadingParts;
995
- }
996
- if (details?.partList) {
997
- // Buffer must be ahead of first part + duration of parts after last segment
998
- // and playback must be at or past segment adjacent to part list
999
- const firstPart = details.partList[0];
1000
- const safePartStart =
1001
- firstPart.end + (details.fragmentHint?.duration || 0);
1002
- if (
1003
- bufferEnd >= safePartStart &&
1004
- this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
1005
- ) {
1006
- return true;
1007
- }
1008
- }
1009
- }
1010
- return false;
1011
- }
1012
-
1013
888
  protected getCurrentContext(
1014
889
  chunkMeta: ChunkMetadata,
1015
- ): { frag: MediaFragment; part: Part | null; level: Level } | null {
890
+ ): { frag: Fragment; part: Part | null; level: Level } | null {
1016
891
  const { levels, fragCurrent } = this;
1017
892
  const { level: levelIndex, sn, part: partIndex } = chunkMeta;
1018
893
  if (!levels?.[levelIndex]) {
@@ -1022,13 +897,10 @@ export default class BaseStreamController
1022
897
  return null;
1023
898
  }
1024
899
  const level = levels[levelIndex];
1025
- const levelDetails = level.details;
1026
-
1027
- const part =
1028
- partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
900
+ const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
1029
901
  const frag = part
1030
902
  ? part.fragment
1031
- : getFragmentWithSN(levelDetails, sn, fragCurrent);
903
+ : getFragmentWithSN(level, sn, fragCurrent);
1032
904
  if (!frag) {
1033
905
  return null;
1034
906
  }
@@ -1114,26 +986,22 @@ export default class BaseStreamController
1114
986
  if (!Number.isFinite(pos)) {
1115
987
  return null;
1116
988
  }
1117
- const backwardSeek = this.lastCurrentTime > pos;
1118
- const maxBufferHole =
1119
- backwardSeek || this.media?.paused ? 0 : this.config.maxBufferHole;
1120
- return this.getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole);
989
+ return this.getFwdBufferInfoAtPos(bufferable, pos, type);
1121
990
  }
1122
991
 
1123
- private getFwdBufferInfoAtPos(
992
+ protected getFwdBufferInfoAtPos(
1124
993
  bufferable: Bufferable | null,
1125
994
  pos: number,
1126
995
  type: PlaylistLevelType,
1127
- maxBufferHole: number,
1128
996
  ): BufferInfo | null {
997
+ const {
998
+ config: { maxBufferHole },
999
+ } = this;
1129
1000
  const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);
1130
1001
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
1131
1002
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
1132
1003
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
1133
- if (
1134
- bufferedFragAtPos &&
1135
- (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
1136
- ) {
1004
+ if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
1137
1005
  return BufferHelper.bufferInfo(
1138
1006
  bufferable,
1139
1007
  pos,
@@ -1146,7 +1014,7 @@ export default class BaseStreamController
1146
1014
 
1147
1015
  protected getMaxBufferLength(levelBitrate?: number): number {
1148
1016
  const { config } = this;
1149
- let maxBufLen: number;
1017
+ let maxBufLen;
1150
1018
  if (levelBitrate) {
1151
1019
  maxBufLen = Math.max(
1152
1020
  (8 * config.maxBufferSize) / levelBitrate,
@@ -1161,12 +1029,13 @@ export default class BaseStreamController
1161
1029
  protected reduceMaxBufferLength(threshold: number, fragDuration: number) {
1162
1030
  const config = this.config;
1163
1031
  const minLength = Math.max(
1164
- Math.min(threshold, config.maxBufferLength),
1032
+ Math.min(threshold - fragDuration, config.maxBufferLength),
1165
1033
  fragDuration,
1166
1034
  );
1167
1035
  const reducedLength = Math.max(
1168
1036
  threshold - fragDuration * 3,
1169
1037
  config.maxMaxBufferLength / 2,
1038
+ minLength,
1170
1039
  );
1171
1040
  if (reducedLength >= minLength) {
1172
1041
  // reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
@@ -1181,9 +1050,9 @@ export default class BaseStreamController
1181
1050
  position: number,
1182
1051
  playlistType: PlaylistLevelType = PlaylistLevelType.MAIN,
1183
1052
  ): Fragment | null {
1184
- const fragOrPart = this.fragmentTracker?.getAppendedFrag(
1053
+ const fragOrPart = this.fragmentTracker.getAppendedFrag(
1185
1054
  position,
1186
- playlistType,
1055
+ PlaylistLevelType.MAIN,
1187
1056
  );
1188
1057
  if (fragOrPart && 'fragment' in fragOrPart) {
1189
1058
  return fragOrPart.fragment;
@@ -1205,8 +1074,7 @@ export default class BaseStreamController
1205
1074
  // find fragment index, contiguous with end of buffer position
1206
1075
  const { config } = this;
1207
1076
  const start = fragments[0].start;
1208
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
1209
- let frag: MediaFragment | null = null;
1077
+ let frag;
1210
1078
 
1211
1079
  if (levelDetails.live) {
1212
1080
  const initialLiveManifestSize = config.initialLiveManifestSize;
@@ -1226,10 +1094,6 @@ export default class BaseStreamController
1226
1094
  this.startPosition === -1) ||
1227
1095
  pos < start
1228
1096
  ) {
1229
- if (canLoadParts && !this.loadingParts) {
1230
- this.log(`LL-Part loading ON for initial live fragment`);
1231
- this.loadingParts = true;
1232
- }
1233
1097
  frag = this.getInitialLiveFragment(levelDetails, fragments);
1234
1098
  this.startPosition = this.nextLoadPosition = frag
1235
1099
  ? this.hls.liveSyncPosition || frag.start
@@ -1242,7 +1106,7 @@ export default class BaseStreamController
1242
1106
 
1243
1107
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
1244
1108
  if (!frag) {
1245
- const end = this.loadingParts
1109
+ const end = config.lowLatencyMode
1246
1110
  ? levelDetails.partEnd
1247
1111
  : levelDetails.fragmentEnd;
1248
1112
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
@@ -1267,35 +1131,34 @@ export default class BaseStreamController
1267
1131
  playlistType: PlaylistLevelType,
1268
1132
  maxBufLen: number,
1269
1133
  ): Fragment | null {
1270
- let nextFragment: Fragment | null = null;
1271
- if (frag.gap) {
1272
- nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
1273
- if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
1274
- // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
1275
- const nextbufferInfo = this.getFwdBufferInfoAtPos(
1276
- this.mediaBuffer ? this.mediaBuffer : this.media,
1277
- bufferInfo.nextStart,
1278
- playlistType,
1279
- 0,
1134
+ const gapStart = frag.gap;
1135
+ const nextFragment = this.getNextFragment(
1136
+ this.nextLoadPosition,
1137
+ levelDetails,
1138
+ );
1139
+ if (nextFragment === null) {
1140
+ return nextFragment;
1141
+ }
1142
+ frag = nextFragment;
1143
+ if (gapStart && frag && !frag.gap && bufferInfo.nextStart) {
1144
+ // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
1145
+ const nextbufferInfo = this.getFwdBufferInfoAtPos(
1146
+ this.mediaBuffer ? this.mediaBuffer : this.media,
1147
+ bufferInfo.nextStart,
1148
+ playlistType,
1149
+ );
1150
+ if (
1151
+ nextbufferInfo !== null &&
1152
+ bufferInfo.len + nextbufferInfo.len >= maxBufLen
1153
+ ) {
1154
+ // Returning here might result in not finding an audio and video candiate to skip to
1155
+ this.log(
1156
+ `buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`,
1280
1157
  );
1281
- if (
1282
- nextbufferInfo !== null &&
1283
- bufferInfo.len + nextbufferInfo.len >= maxBufLen
1284
- ) {
1285
- // Returning here might result in not finding an audio and video candiate to skip to
1286
- const sn = nextFragment.sn;
1287
- if (this.loopSn !== sn) {
1288
- this.log(
1289
- `buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`,
1290
- );
1291
- this.loopSn = sn;
1292
- }
1293
- return null;
1294
- }
1158
+ return null;
1295
1159
  }
1296
1160
  }
1297
- this.loopSn = undefined;
1298
- return nextFragment;
1161
+ return frag;
1299
1162
  }
1300
1163
 
1301
1164
  mapToInitFragWhenRequired(frag: Fragment | null): typeof frag {
@@ -1350,10 +1213,10 @@ export default class BaseStreamController
1350
1213
  */
1351
1214
  protected getInitialLiveFragment(
1352
1215
  levelDetails: LevelDetails,
1353
- fragments: MediaFragment[],
1354
- ): MediaFragment | null {
1216
+ fragments: Array<Fragment>,
1217
+ ): Fragment | null {
1355
1218
  const fragPrevious = this.fragPrevious;
1356
- let frag: MediaFragment | null = null;
1219
+ let frag: Fragment | null = null;
1357
1220
  if (fragPrevious) {
1358
1221
  if (levelDetails.hasProgramDateTime) {
1359
1222
  // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
@@ -1417,7 +1280,7 @@ export default class BaseStreamController
1417
1280
  bufferEnd: number,
1418
1281
  end: number,
1419
1282
  levelDetails: LevelDetails,
1420
- ): MediaFragment | null {
1283
+ ): Fragment | null {
1421
1284
  const { config } = this;
1422
1285
  let { fragPrevious } = this;
1423
1286
  let { fragments, endSN } = levelDetails;
@@ -1426,25 +1289,20 @@ export default class BaseStreamController
1426
1289
  const partList = levelDetails.partList;
1427
1290
 
1428
1291
  const loadingParts = !!(
1429
- this.loadingParts &&
1292
+ config.lowLatencyMode &&
1430
1293
  partList?.length &&
1431
1294
  fragmentHint
1432
1295
  );
1433
1296
  if (loadingParts && fragmentHint && !this.bitrateTest) {
1434
1297
  // Include incomplete fragment with parts at end
1435
1298
  fragments = fragments.concat(fragmentHint);
1436
- endSN = fragmentHint.sn;
1299
+ endSN = fragmentHint.sn as number;
1437
1300
  }
1438
1301
 
1439
- let frag: MediaFragment | null;
1302
+ let frag;
1440
1303
  if (bufferEnd < end) {
1441
- const backwardSeek = bufferEnd < this.lastCurrentTime;
1442
1304
  const lookupTolerance =
1443
- backwardSeek ||
1444
- bufferEnd > end - maxFragLookUpTolerance ||
1445
- this.media?.paused
1446
- ? 0
1447
- : maxFragLookUpTolerance;
1305
+ bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
1448
1306
  // Remove the tolerance if it would put the bufferEnd past the actual end of stream
1449
1307
  // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
1450
1308
  frag = findFragmentByPTS(
@@ -1597,7 +1455,7 @@ export default class BaseStreamController
1597
1455
  if (startTimeOffset !== null && Number.isFinite(startTimeOffset)) {
1598
1456
  startPosition = sliding + startTimeOffset;
1599
1457
  if (startTimeOffset < 0) {
1600
- startPosition += details.edge;
1458
+ startPosition += details.totalduration;
1601
1459
  }
1602
1460
  startPosition = Math.min(
1603
1461
  Math.max(sliding, startPosition),
@@ -1678,7 +1536,7 @@ export default class BaseStreamController
1678
1536
  }
1679
1537
  const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
1680
1538
  if (gapTagEncountered) {
1681
- this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
1539
+ this.fragmentTracker.fragBuffered(frag, true);
1682
1540
  }
1683
1541
  // keep retrying until the limit will be reached
1684
1542
  const errorAction = data.errorAction;
@@ -1711,7 +1569,7 @@ export default class BaseStreamController
1711
1569
  errorAction.resolved = true;
1712
1570
  }
1713
1571
  } else {
1714
- this.warn(
1572
+ logger.warn(
1715
1573
  `${data.details} reached or exceeded max retry (${retryCount})`,
1716
1574
  );
1717
1575
  return;
@@ -1801,9 +1659,7 @@ export default class BaseStreamController
1801
1659
  this.log('Reset loading state');
1802
1660
  this.fragCurrent = null;
1803
1661
  this.fragPrevious = null;
1804
- if (this.state !== State.STOPPED) {
1805
- this.state = State.IDLE;
1806
- }
1662
+ this.state = State.IDLE;
1807
1663
  }
1808
1664
 
1809
1665
  protected resetStartWhenNotLoaded(level: Level | null): void {
@@ -1843,7 +1699,7 @@ export default class BaseStreamController
1843
1699
  }
1844
1700
 
1845
1701
  private updateLevelTiming(
1846
- frag: MediaFragment,
1702
+ frag: Fragment,
1847
1703
  part: Part | null,
1848
1704
  level: Level,
1849
1705
  partial: boolean,