hls.js 1.5.14-0.canary.10515 → 1.5.14

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 (107) 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 +2903 -4542
  5. package/dist/hls.js.d.ts +112 -186
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2284 -3295
  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 +1804 -2817
  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 +4652 -6293
  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 +136 -156
  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 +107 -263
  26. package/src/controller/buffer-controller.ts +98 -252
  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 +5 -5
  48. package/src/demux/audio/ac3-demuxer.ts +4 -5
  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/inject-worker.ts +4 -38
  55. package/src/demux/mp4demuxer.ts +7 -7
  56. package/src/demux/sample-aes.ts +0 -2
  57. package/src/demux/transmuxer-interface.ts +83 -106
  58. package/src/demux/transmuxer-worker.ts +77 -111
  59. package/src/demux/transmuxer.ts +22 -46
  60. package/src/demux/tsdemuxer.ts +62 -122
  61. package/src/demux/video/avc-video-parser.ts +121 -210
  62. package/src/demux/video/base-video-parser.ts +2 -135
  63. package/src/demux/video/exp-golomb.ts +208 -0
  64. package/src/errors.ts +0 -2
  65. package/src/events.ts +1 -8
  66. package/src/exports-named.ts +1 -1
  67. package/src/hls.ts +48 -97
  68. package/src/loader/date-range.ts +5 -71
  69. package/src/loader/fragment-loader.ts +21 -23
  70. package/src/loader/fragment.ts +4 -8
  71. package/src/loader/key-loader.ts +1 -3
  72. package/src/loader/level-details.ts +6 -6
  73. package/src/loader/level-key.ts +9 -10
  74. package/src/loader/m3u8-parser.ts +144 -138
  75. package/src/loader/playlist-loader.ts +7 -5
  76. package/src/remux/mp4-generator.ts +1 -196
  77. package/src/remux/mp4-remuxer.ts +84 -55
  78. package/src/remux/passthrough-remuxer.ts +8 -23
  79. package/src/task-loop.ts +2 -5
  80. package/src/types/component-api.ts +1 -3
  81. package/src/types/demuxer.ts +0 -3
  82. package/src/types/events.ts +6 -19
  83. package/src/types/fragment-tracker.ts +2 -2
  84. package/src/types/general.ts +6 -0
  85. package/src/types/media-playlist.ts +1 -9
  86. package/src/types/remuxer.ts +1 -1
  87. package/src/utils/attr-list.ts +9 -96
  88. package/src/utils/buffer-helper.ts +31 -12
  89. package/src/utils/cea-608-parser.ts +3 -1
  90. package/src/utils/codecs.ts +5 -34
  91. package/src/utils/discontinuities.ts +47 -21
  92. package/src/utils/fetch-loader.ts +1 -1
  93. package/src/utils/hdr.ts +7 -4
  94. package/src/utils/imsc1-ttml-parser.ts +1 -1
  95. package/src/utils/keysystem-util.ts +6 -1
  96. package/src/utils/level-helper.ts +44 -71
  97. package/src/utils/logger.ts +23 -58
  98. package/src/utils/mp4-tools.ts +3 -5
  99. package/src/utils/rendition-helper.ts +74 -100
  100. package/src/utils/variable-substitution.ts +19 -0
  101. package/src/utils/webvtt-parser.ts +12 -2
  102. package/src/crypt/decrypter-aes-mode.ts +0 -4
  103. package/src/demux/video/hevc-video-parser.ts +0 -749
  104. package/src/utils/encryption-methods-util.ts +0 -21
  105. package/src/utils/hash.ts +0 -10
  106. package/src/utils/utf8-utils.ts +0 -18
  107. package/src/version.ts +0 -1
@@ -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
- protected 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,34 +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
- if (this.transmuxer) {
369
- this.transmuxer.destroy();
370
- this.transmuxer = null;
371
- }
372
313
  super.onHandlerDestroying();
373
314
  // @ts-ignore
374
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
315
+ this.hls = null;
375
316
  }
376
317
 
377
318
  protected onHandlerDestroyed() {
@@ -402,7 +343,6 @@ export default class BaseStreamController
402
343
  level: Level,
403
344
  targetBufferTime: number,
404
345
  ) {
405
- this.startFragRequested = true;
406
346
  this._loadFragForPlayback(frag, level, targetBufferTime);
407
347
  }
408
348
 
@@ -446,9 +386,7 @@ export default class BaseStreamController
446
386
  }
447
387
 
448
388
  if ('payload' in data) {
449
- this.log(
450
- `Loaded ${frag.type} sn: ${frag.sn} of ${this.playlistLabel()} ${frag.level}`,
451
- );
389
+ this.log(`Loaded fragment ${frag.sn} of level ${frag.level}`);
452
390
  this.hls.trigger(Events.FRAG_LOADED, data);
453
391
  }
454
392
 
@@ -558,7 +496,7 @@ export default class BaseStreamController
558
496
  payload.byteLength > 0 &&
559
497
  decryptData?.key &&
560
498
  decryptData.iv &&
561
- isFullSegmentEncryption(decryptData.method)
499
+ decryptData.method === 'AES-128'
562
500
  ) {
563
501
  const startTime = self.performance.now();
564
502
  // decrypt init segment data
@@ -567,7 +505,6 @@ export default class BaseStreamController
567
505
  new Uint8Array(payload),
568
506
  decryptData.key.buffer,
569
507
  decryptData.iv.buffer,
570
- getAesModeFromFullSegmentMethod(decryptData.method),
571
508
  )
572
509
  .catch((err) => {
573
510
  hls.trigger(Events.ERROR, {
@@ -611,9 +548,7 @@ export default class BaseStreamController
611
548
  throw new Error('init load aborted, missing levels');
612
549
  }
613
550
  const stats = data.frag.stats;
614
- if (this.state !== State.STOPPED) {
615
- this.state = State.IDLE;
616
- }
551
+ this.state = State.IDLE;
617
552
  data.frag.data = new Uint8Array(data.payload);
618
553
  stats.parsing.start = stats.buffering.start = self.performance.now();
619
554
  stats.parsing.end = stats.buffering.end = self.performance.now();
@@ -635,7 +570,11 @@ export default class BaseStreamController
635
570
  this.log(
636
571
  `Buffered ${frag.type} sn: ${frag.sn}${
637
572
  part ? ' part: ' + part.index : ''
638
- } of ${this.fragInfo(frag)} > buffer:${
573
+ } of ${
574
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
575
+ } ${frag.level} (frag:[${(frag.startPTS ?? NaN).toFixed(3)}-${(
576
+ frag.endPTS ?? NaN
577
+ ).toFixed(3)}] > buffer:${
639
578
  media
640
579
  ? TimeRanges.toString(BufferHelper.getBuffered(media))
641
580
  : '(detached)'
@@ -709,7 +648,6 @@ export default class BaseStreamController
709
648
  targetBufferTime: number | null = null,
710
649
  progressCallback?: FragmentLoadProgressCallback,
711
650
  ): Promise<PartsLoadedData | FragLoadedData | null> {
712
- this.fragCurrent = frag;
713
651
  const details = level?.details;
714
652
  if (!this.levels || !details) {
715
653
  throw new Error(
@@ -720,7 +658,9 @@ export default class BaseStreamController
720
658
  let keyLoadingPromise: Promise<KeyLoadedData | void> | null = null;
721
659
  if (frag.encrypted && !frag.decryptdata?.key) {
722
660
  this.log(
723
- `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistLabel()} ${frag.level}`,
661
+ `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
662
+ this.logPrefix === '[stream-controller]' ? 'level' : 'track'
663
+ } ${frag.level}`,
724
664
  );
725
665
  this.state = State.KEY_LOADING;
726
666
  this.fragCurrent = frag;
@@ -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) {
@@ -773,7 +698,9 @@ export default class BaseStreamController
773
698
  frag.cc
774
699
  } of playlist [${details.startSN}-${
775
700
  details.endSN
776
- }] parts [0-${partIndex}-${partList.length - 1}] ${this.playlistLabel()}: ${frag.level}, target: ${parseFloat(
701
+ }] parts [0-${partIndex}-${partList.length - 1}] ${
702
+ this.logPrefix === '[stream-controller]' ? 'level' : 'track'
703
+ }: ${frag.level}, target: ${parseFloat(
777
704
  targetBufferTime.toFixed(3),
778
705
  )}`,
779
706
  );
@@ -828,21 +755,11 @@ export default class BaseStreamController
828
755
  }
829
756
  }
830
757
 
831
- if (frag.sn !== 'initSegment' && this.loadingParts) {
832
- this.log(
833
- `LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
834
- 2,
835
- )}`,
836
- );
837
- this.loadingParts = false;
838
- } else if (!frag.url) {
839
- // Selected fragment hint for part but not loading parts
840
- return Promise.resolve(null);
841
- }
842
-
843
758
  this.log(
844
- `Loading ${frag.type} sn: ${frag.sn} of ${this.fragInfo(frag, false)}) cc: ${frag.cc} ${
845
- details ? '[' + details.startSN + '-' + details.endSN + ']' : ''
759
+ `Loading fragment ${frag.sn} cc: ${frag.cc} ${
760
+ details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
761
+ }${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
762
+ frag.level
846
763
  }, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
847
764
  );
848
765
  // Don't update nextLoadPosition for fragments which are not buffered
@@ -908,7 +825,7 @@ export default class BaseStreamController
908
825
  const loadedPart = partLoadedData.part as Part;
909
826
  this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
910
827
  const nextPart =
911
- getPartWith(level.details, frag.sn as number, part.index + 1) ||
828
+ getPartWith(level, frag.sn as number, part.index + 1) ||
912
829
  findPart(initialPartList, frag.sn as number, part.index + 1);
913
830
  if (nextPart) {
914
831
  loadPart(nextPart);
@@ -965,50 +882,12 @@ export default class BaseStreamController
965
882
  if (part) {
966
883
  part.stats.parsing.end = now;
967
884
  }
968
- // See if part loading should be disabled/enabled based on buffer and playback position.
969
- const levelDetails = this.getLevelDetails();
970
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
971
- const shouldLoadParts =
972
- loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
973
- if (shouldLoadParts !== this.loadingParts) {
974
- this.log(
975
- `LL-Part loading ${
976
- shouldLoadParts ? 'ON' : 'OFF'
977
- } after parsing segment ending @${frag.end.toFixed(2)}`,
978
- );
979
- this.loadingParts = shouldLoadParts;
980
- }
981
885
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
982
886
  }
983
887
 
984
- private shouldLoadParts(
985
- details: LevelDetails | undefined,
986
- bufferEnd: number,
987
- ): boolean {
988
- if (this.config.lowLatencyMode) {
989
- if (!details) {
990
- return this.loadingParts;
991
- }
992
- if (details?.partList) {
993
- // Buffer must be ahead of first part + duration of parts after last segment
994
- // and playback must be at or past segment adjacent to part list
995
- const firstPart = details.partList[0];
996
- const safePartStart =
997
- firstPart.end + (details.fragmentHint?.duration || 0);
998
- if (
999
- bufferEnd >= safePartStart &&
1000
- this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
1001
- ) {
1002
- return true;
1003
- }
1004
- }
1005
- }
1006
- return false;
1007
- }
1008
-
1009
888
  protected getCurrentContext(
1010
889
  chunkMeta: ChunkMetadata,
1011
- ): { frag: MediaFragment; part: Part | null; level: Level } | null {
890
+ ): { frag: Fragment; part: Part | null; level: Level } | null {
1012
891
  const { levels, fragCurrent } = this;
1013
892
  const { level: levelIndex, sn, part: partIndex } = chunkMeta;
1014
893
  if (!levels?.[levelIndex]) {
@@ -1018,13 +897,10 @@ export default class BaseStreamController
1018
897
  return null;
1019
898
  }
1020
899
  const level = levels[levelIndex];
1021
- const levelDetails = level.details;
1022
-
1023
- const part =
1024
- partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
900
+ const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
1025
901
  const frag = part
1026
902
  ? part.fragment
1027
- : getFragmentWithSN(levelDetails, sn, fragCurrent);
903
+ : getFragmentWithSN(level, sn, fragCurrent);
1028
904
  if (!frag) {
1029
905
  return null;
1030
906
  }
@@ -1110,26 +986,22 @@ export default class BaseStreamController
1110
986
  if (!Number.isFinite(pos)) {
1111
987
  return null;
1112
988
  }
1113
- const backwardSeek = this.lastCurrentTime > pos;
1114
- const maxBufferHole =
1115
- backwardSeek || this.media?.paused ? 0 : this.config.maxBufferHole;
1116
- return this.getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole);
989
+ return this.getFwdBufferInfoAtPos(bufferable, pos, type);
1117
990
  }
1118
991
 
1119
- private getFwdBufferInfoAtPos(
992
+ protected getFwdBufferInfoAtPos(
1120
993
  bufferable: Bufferable | null,
1121
994
  pos: number,
1122
995
  type: PlaylistLevelType,
1123
- maxBufferHole: number,
1124
996
  ): BufferInfo | null {
997
+ const {
998
+ config: { maxBufferHole },
999
+ } = this;
1125
1000
  const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);
1126
1001
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
1127
1002
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
1128
1003
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
1129
- if (
1130
- bufferedFragAtPos &&
1131
- (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
1132
- ) {
1004
+ if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
1133
1005
  return BufferHelper.bufferInfo(
1134
1006
  bufferable,
1135
1007
  pos,
@@ -1142,7 +1014,7 @@ export default class BaseStreamController
1142
1014
 
1143
1015
  protected getMaxBufferLength(levelBitrate?: number): number {
1144
1016
  const { config } = this;
1145
- let maxBufLen: number;
1017
+ let maxBufLen;
1146
1018
  if (levelBitrate) {
1147
1019
  maxBufLen = Math.max(
1148
1020
  (8 * config.maxBufferSize) / levelBitrate,
@@ -1178,9 +1050,9 @@ export default class BaseStreamController
1178
1050
  position: number,
1179
1051
  playlistType: PlaylistLevelType = PlaylistLevelType.MAIN,
1180
1052
  ): Fragment | null {
1181
- const fragOrPart = this.fragmentTracker?.getAppendedFrag(
1053
+ const fragOrPart = this.fragmentTracker.getAppendedFrag(
1182
1054
  position,
1183
- playlistType,
1055
+ PlaylistLevelType.MAIN,
1184
1056
  );
1185
1057
  if (fragOrPart && 'fragment' in fragOrPart) {
1186
1058
  return fragOrPart.fragment;
@@ -1202,8 +1074,7 @@ export default class BaseStreamController
1202
1074
  // find fragment index, contiguous with end of buffer position
1203
1075
  const { config } = this;
1204
1076
  const start = fragments[0].start;
1205
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
1206
- let frag: MediaFragment | null = null;
1077
+ let frag;
1207
1078
 
1208
1079
  if (levelDetails.live) {
1209
1080
  const initialLiveManifestSize = config.initialLiveManifestSize;
@@ -1223,10 +1094,6 @@ export default class BaseStreamController
1223
1094
  this.startPosition === -1) ||
1224
1095
  pos < start
1225
1096
  ) {
1226
- if (canLoadParts && !this.loadingParts) {
1227
- this.log(`LL-Part loading ON for initial live fragment`);
1228
- this.loadingParts = true;
1229
- }
1230
1097
  frag = this.getInitialLiveFragment(levelDetails, fragments);
1231
1098
  this.startPosition = this.nextLoadPosition = frag
1232
1099
  ? this.hls.liveSyncPosition || frag.start
@@ -1239,7 +1106,7 @@ export default class BaseStreamController
1239
1106
 
1240
1107
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
1241
1108
  if (!frag) {
1242
- const end = this.loadingParts
1109
+ const end = config.lowLatencyMode
1243
1110
  ? levelDetails.partEnd
1244
1111
  : levelDetails.fragmentEnd;
1245
1112
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
@@ -1264,35 +1131,34 @@ export default class BaseStreamController
1264
1131
  playlistType: PlaylistLevelType,
1265
1132
  maxBufLen: number,
1266
1133
  ): Fragment | null {
1267
- let nextFragment: Fragment | null = null;
1268
- if (frag.gap) {
1269
- nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
1270
- if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
1271
- // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
1272
- const nextbufferInfo = this.getFwdBufferInfoAtPos(
1273
- this.mediaBuffer ? this.mediaBuffer : this.media,
1274
- bufferInfo.nextStart,
1275
- playlistType,
1276
- 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}`,
1277
1157
  );
1278
- if (
1279
- nextbufferInfo !== null &&
1280
- bufferInfo.len + nextbufferInfo.len >= maxBufLen
1281
- ) {
1282
- // Returning here might result in not finding an audio and video candiate to skip to
1283
- const sn = nextFragment.sn;
1284
- if (this.loopSn !== sn) {
1285
- this.log(
1286
- `buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`,
1287
- );
1288
- this.loopSn = sn;
1289
- }
1290
- return null;
1291
- }
1158
+ return null;
1292
1159
  }
1293
1160
  }
1294
- this.loopSn = undefined;
1295
- return nextFragment;
1161
+ return frag;
1296
1162
  }
1297
1163
 
1298
1164
  mapToInitFragWhenRequired(frag: Fragment | null): typeof frag {
@@ -1347,10 +1213,10 @@ export default class BaseStreamController
1347
1213
  */
1348
1214
  protected getInitialLiveFragment(
1349
1215
  levelDetails: LevelDetails,
1350
- fragments: MediaFragment[],
1351
- ): MediaFragment | null {
1216
+ fragments: Array<Fragment>,
1217
+ ): Fragment | null {
1352
1218
  const fragPrevious = this.fragPrevious;
1353
- let frag: MediaFragment | null = null;
1219
+ let frag: Fragment | null = null;
1354
1220
  if (fragPrevious) {
1355
1221
  if (levelDetails.hasProgramDateTime) {
1356
1222
  // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
@@ -1414,7 +1280,7 @@ export default class BaseStreamController
1414
1280
  bufferEnd: number,
1415
1281
  end: number,
1416
1282
  levelDetails: LevelDetails,
1417
- ): MediaFragment | null {
1283
+ ): Fragment | null {
1418
1284
  const { config } = this;
1419
1285
  let { fragPrevious } = this;
1420
1286
  let { fragments, endSN } = levelDetails;
@@ -1423,25 +1289,20 @@ export default class BaseStreamController
1423
1289
  const partList = levelDetails.partList;
1424
1290
 
1425
1291
  const loadingParts = !!(
1426
- this.loadingParts &&
1292
+ config.lowLatencyMode &&
1427
1293
  partList?.length &&
1428
1294
  fragmentHint
1429
1295
  );
1430
1296
  if (loadingParts && fragmentHint && !this.bitrateTest) {
1431
1297
  // Include incomplete fragment with parts at end
1432
1298
  fragments = fragments.concat(fragmentHint);
1433
- endSN = fragmentHint.sn;
1299
+ endSN = fragmentHint.sn as number;
1434
1300
  }
1435
1301
 
1436
- let frag: MediaFragment | null;
1302
+ let frag;
1437
1303
  if (bufferEnd < end) {
1438
- const backwardSeek = bufferEnd < this.lastCurrentTime;
1439
1304
  const lookupTolerance =
1440
- backwardSeek ||
1441
- bufferEnd > end - maxFragLookUpTolerance ||
1442
- this.media?.paused
1443
- ? 0
1444
- : maxFragLookUpTolerance;
1305
+ bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
1445
1306
  // Remove the tolerance if it would put the bufferEnd past the actual end of stream
1446
1307
  // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
1447
1308
  frag = findFragmentByPTS(
@@ -1594,7 +1455,7 @@ export default class BaseStreamController
1594
1455
  if (startTimeOffset !== null && Number.isFinite(startTimeOffset)) {
1595
1456
  startPosition = sliding + startTimeOffset;
1596
1457
  if (startTimeOffset < 0) {
1597
- startPosition += details.edge;
1458
+ startPosition += details.totalduration;
1598
1459
  }
1599
1460
  startPosition = Math.min(
1600
1461
  Math.max(sliding, startPosition),
@@ -1675,7 +1536,7 @@ export default class BaseStreamController
1675
1536
  }
1676
1537
  const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
1677
1538
  if (gapTagEncountered) {
1678
- this.fragmentTracker.fragBuffered(frag as MediaFragment, true);
1539
+ this.fragmentTracker.fragBuffered(frag, true);
1679
1540
  }
1680
1541
  // keep retrying until the limit will be reached
1681
1542
  const errorAction = data.errorAction;
@@ -1708,7 +1569,7 @@ export default class BaseStreamController
1708
1569
  errorAction.resolved = true;
1709
1570
  }
1710
1571
  } else {
1711
- this.warn(
1572
+ logger.warn(
1712
1573
  `${data.details} reached or exceeded max retry (${retryCount})`,
1713
1574
  );
1714
1575
  return;
@@ -1798,9 +1659,7 @@ export default class BaseStreamController
1798
1659
  this.log('Reset loading state');
1799
1660
  this.fragCurrent = null;
1800
1661
  this.fragPrevious = null;
1801
- if (this.state !== State.STOPPED) {
1802
- this.state = State.IDLE;
1803
- }
1662
+ this.state = State.IDLE;
1804
1663
  }
1805
1664
 
1806
1665
  protected resetStartWhenNotLoaded(level: Level | null): void {
@@ -1840,7 +1699,7 @@ export default class BaseStreamController
1840
1699
  }
1841
1700
 
1842
1701
  private updateLevelTiming(
1843
- frag: MediaFragment,
1702
+ frag: Fragment,
1844
1703
  part: Part | null,
1845
1704
  level: Level,
1846
1705
  partial: boolean,
@@ -1916,35 +1775,20 @@ export default class BaseStreamController
1916
1775
  // For this error fallthrough. Marking parsed will allow advancing to next fragment.
1917
1776
  }
1918
1777
  this.state = State.PARSED;
1919
- this.log(
1920
- `Parsed ${frag.type} sn: ${frag.sn}${
1921
- part ? ' part: ' + part.index : ''
1922
- } of ${this.fragInfo(frag)})`,
1923
- );
1924
1778
  this.hls.trigger(Events.FRAG_PARSED, { frag, part });
1925
1779
  }
1926
1780
 
1927
- private playlistLabel() {
1928
- return this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track';
1929
- }
1930
-
1931
- private fragInfo(frag: Fragment, pts: boolean = true): string {
1932
- return `${this.playlistLabel()} ${frag.level} (frag:[${((pts ? frag.startPTS : frag.start) ?? NaN).toFixed(3)}-${(
1933
- (pts ? frag.endPTS : frag.end) ?? NaN
1934
- ).toFixed(3)}]`;
1935
- }
1936
-
1937
1781
  protected resetTransmuxer() {
1938
- this.transmuxer?.reset();
1782
+ if (this.transmuxer) {
1783
+ this.transmuxer.destroy();
1784
+ this.transmuxer = null;
1785
+ }
1939
1786
  }
1940
1787
 
1941
1788
  protected recoverWorkerError(data: ErrorData) {
1942
1789
  if (data.event === 'demuxerWorker') {
1943
1790
  this.fragmentTracker.removeAllFragments();
1944
- if (this.transmuxer) {
1945
- this.transmuxer.destroy();
1946
- this.transmuxer = null;
1947
- }
1791
+ this.resetTransmuxer();
1948
1792
  this.resetStartWhenNotLoaded(this.levelLastLoaded);
1949
1793
  this.resetLoadingState();
1950
1794
  }