hls.js 1.5.12-0.canary.10399 → 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 +2671 -4220
  5. package/dist/hls.js.d.ts +108 -173
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1955 -2892
  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 +3402 -4354
  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 +4445 -6009
  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 +82 -215
  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
 
@@ -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,
@@ -1175,9 +1049,9 @@ export default class BaseStreamController
1175
1049
  position: number,
1176
1050
  playlistType: PlaylistLevelType = PlaylistLevelType.MAIN,
1177
1051
  ): Fragment | null {
1178
- const fragOrPart = this.fragmentTracker?.getAppendedFrag(
1052
+ const fragOrPart = this.fragmentTracker.getAppendedFrag(
1179
1053
  position,
1180
- playlistType,
1054
+ PlaylistLevelType.MAIN,
1181
1055
  );
1182
1056
  if (fragOrPart && 'fragment' in fragOrPart) {
1183
1057
  return fragOrPart.fragment;
@@ -1199,8 +1073,7 @@ export default class BaseStreamController
1199
1073
  // find fragment index, contiguous with end of buffer position
1200
1074
  const { config } = this;
1201
1075
  const start = fragments[0].start;
1202
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
1203
- let frag: MediaFragment | null = null;
1076
+ let frag;
1204
1077
 
1205
1078
  if (levelDetails.live) {
1206
1079
  const initialLiveManifestSize = config.initialLiveManifestSize;
@@ -1220,10 +1093,6 @@ export default class BaseStreamController
1220
1093
  this.startPosition === -1) ||
1221
1094
  pos < start
1222
1095
  ) {
1223
- if (canLoadParts && !this.loadingParts) {
1224
- this.log(`LL-Part loading ON for initial live fragment`);
1225
- this.loadingParts = true;
1226
- }
1227
1096
  frag = this.getInitialLiveFragment(levelDetails, fragments);
1228
1097
  this.startPosition = this.nextLoadPosition = frag
1229
1098
  ? this.hls.liveSyncPosition || frag.start
@@ -1236,7 +1105,7 @@ export default class BaseStreamController
1236
1105
 
1237
1106
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
1238
1107
  if (!frag) {
1239
- const end = this.loadingParts
1108
+ const end = config.lowLatencyMode
1240
1109
  ? levelDetails.partEnd
1241
1110
  : levelDetails.fragmentEnd;
1242
1111
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
@@ -1261,34 +1130,34 @@ export default class BaseStreamController
1261
1130
  playlistType: PlaylistLevelType,
1262
1131
  maxBufLen: number,
1263
1132
  ): Fragment | null {
1264
- let nextFragment: Fragment | null = null;
1265
- if (frag.gap) {
1266
- nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
1267
- if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
1268
- // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
1269
- const nextbufferInfo = this.getFwdBufferInfoAtPos(
1270
- this.mediaBuffer ? this.mediaBuffer : this.media,
1271
- bufferInfo.nextStart,
1272
- 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}`,
1273
1156
  );
1274
- if (
1275
- nextbufferInfo !== null &&
1276
- bufferInfo.len + nextbufferInfo.len >= maxBufLen
1277
- ) {
1278
- // Returning here might result in not finding an audio and video candiate to skip to
1279
- const sn = nextFragment.sn;
1280
- if (this.loopSn !== sn) {
1281
- this.log(
1282
- `buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`,
1283
- );
1284
- this.loopSn = sn;
1285
- }
1286
- return null;
1287
- }
1157
+ return null;
1288
1158
  }
1289
1159
  }
1290
- this.loopSn = undefined;
1291
- return nextFragment;
1160
+ return frag;
1292
1161
  }
1293
1162
 
1294
1163
  mapToInitFragWhenRequired(frag: Fragment | null): typeof frag {
@@ -1343,10 +1212,10 @@ export default class BaseStreamController
1343
1212
  */
1344
1213
  protected getInitialLiveFragment(
1345
1214
  levelDetails: LevelDetails,
1346
- fragments: MediaFragment[],
1347
- ): MediaFragment | null {
1215
+ fragments: Array<Fragment>,
1216
+ ): Fragment | null {
1348
1217
  const fragPrevious = this.fragPrevious;
1349
- let frag: MediaFragment | null = null;
1218
+ let frag: Fragment | null = null;
1350
1219
  if (fragPrevious) {
1351
1220
  if (levelDetails.hasProgramDateTime) {
1352
1221
  // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
@@ -1410,7 +1279,7 @@ export default class BaseStreamController
1410
1279
  bufferEnd: number,
1411
1280
  end: number,
1412
1281
  levelDetails: LevelDetails,
1413
- ): MediaFragment | null {
1282
+ ): Fragment | null {
1414
1283
  const { config } = this;
1415
1284
  let { fragPrevious } = this;
1416
1285
  let { fragments, endSN } = levelDetails;
@@ -1419,17 +1288,17 @@ export default class BaseStreamController
1419
1288
  const partList = levelDetails.partList;
1420
1289
 
1421
1290
  const loadingParts = !!(
1422
- this.loadingParts &&
1291
+ config.lowLatencyMode &&
1423
1292
  partList?.length &&
1424
1293
  fragmentHint
1425
1294
  );
1426
1295
  if (loadingParts && fragmentHint && !this.bitrateTest) {
1427
1296
  // Include incomplete fragment with parts at end
1428
1297
  fragments = fragments.concat(fragmentHint);
1429
- endSN = fragmentHint.sn;
1298
+ endSN = fragmentHint.sn as number;
1430
1299
  }
1431
1300
 
1432
- let frag: MediaFragment | null;
1301
+ let frag;
1433
1302
  if (bufferEnd < end) {
1434
1303
  const lookupTolerance =
1435
1304
  bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
@@ -1585,7 +1454,7 @@ export default class BaseStreamController
1585
1454
  if (startTimeOffset !== null && Number.isFinite(startTimeOffset)) {
1586
1455
  startPosition = sliding + startTimeOffset;
1587
1456
  if (startTimeOffset < 0) {
1588
- startPosition += details.edge;
1457
+ startPosition += details.totalduration;
1589
1458
  }
1590
1459
  startPosition = Math.min(
1591
1460
  Math.max(sliding, startPosition),
@@ -1666,7 +1535,7 @@ export default class BaseStreamController
1666
1535
  }
1667
1536
  const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
1668
1537
  if (gapTagEncountered) {
1669
- this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
1538
+ this.fragmentTracker.fragBuffered(frag, true);
1670
1539
  }
1671
1540
  // keep retrying until the limit will be reached
1672
1541
  const errorAction = data.errorAction;
@@ -1699,7 +1568,7 @@ export default class BaseStreamController
1699
1568
  errorAction.resolved = true;
1700
1569
  }
1701
1570
  } else {
1702
- this.warn(
1571
+ logger.warn(
1703
1572
  `${data.details} reached or exceeded max retry (${retryCount})`,
1704
1573
  );
1705
1574
  return;
@@ -1789,9 +1658,7 @@ export default class BaseStreamController
1789
1658
  this.log('Reset loading state');
1790
1659
  this.fragCurrent = null;
1791
1660
  this.fragPrevious = null;
1792
- if (this.state !== State.STOPPED) {
1793
- this.state = State.IDLE;
1794
- }
1661
+ this.state = State.IDLE;
1795
1662
  }
1796
1663
 
1797
1664
  protected resetStartWhenNotLoaded(level: Level | null): void {
@@ -1831,7 +1698,7 @@ export default class BaseStreamController
1831
1698
  }
1832
1699
 
1833
1700
  private updateLevelTiming(
1834
- frag: MediaFragment,
1701
+ frag: Fragment,
1835
1702
  part: Part | null,
1836
1703
  level: Level,
1837
1704
  partial: boolean,