hls.js 1.5.7-0.canary.10015 → 1.5.7-0.canary.10016

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.
@@ -512,7 +512,7 @@ function enableLogs(debugConfig, context, id) {
512
512
  // Some browsers don't allow to use bind on console object anyway
513
513
  // fallback to default if needed
514
514
  try {
515
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10015"}`);
515
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10016"}`);
516
516
  } catch (e) {
517
517
  /* log fn threw an exception. All logger methods are no-ops. */
518
518
  return createLogger();
@@ -6595,6 +6595,9 @@ class AbrController extends Logger {
6595
6595
  partCurrent,
6596
6596
  hls
6597
6597
  } = this;
6598
+ if (hls.levels.length <= 1) {
6599
+ return hls.loadLevel;
6600
+ }
6598
6601
  const {
6599
6602
  maxAutoLevel,
6600
6603
  config,
@@ -6949,24 +6952,22 @@ class BufferOperationQueue {
6949
6952
  this.executeNext(type);
6950
6953
  }
6951
6954
  }
6952
- insertAbort(operation, type) {
6953
- const queue = this.queues[type];
6954
- queue.unshift(operation);
6955
- this.executeNext(type);
6956
- }
6957
6955
  appendBlocker(type) {
6958
- let execute;
6959
- const promise = new Promise(resolve => {
6960
- execute = resolve;
6956
+ return new Promise(resolve => {
6957
+ const operation = {
6958
+ execute: resolve,
6959
+ onStart: () => {},
6960
+ onComplete: () => {},
6961
+ onError: () => {}
6962
+ };
6963
+ this.append(operation, type);
6961
6964
  });
6962
- const operation = {
6963
- execute,
6964
- onStart: () => {},
6965
- onComplete: () => {},
6966
- onError: () => {}
6967
- };
6968
- this.append(operation, type);
6969
- return promise;
6965
+ }
6966
+ unblockAudio(op) {
6967
+ const queue = this.queues.audio;
6968
+ if (queue[0] === op) {
6969
+ this.shiftAndExecuteNext('audio');
6970
+ }
6970
6971
  }
6971
6972
  executeNext(type) {
6972
6973
  const queue = this.queues[type];
@@ -6999,7 +7000,7 @@ class BufferOperationQueue {
6999
7000
 
7000
7001
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
7001
7002
  class BufferController extends Logger {
7002
- constructor(hls) {
7003
+ constructor(hls, fragmentTracker) {
7003
7004
  super('buffer-controller', hls.logger);
7004
7005
  // The level details used to determine duration, target-duration and live
7005
7006
  this.details = null;
@@ -7010,6 +7011,7 @@ class BufferController extends Logger {
7010
7011
  // References to event listeners for each SourceBuffer, so that they can be referenced for event removal
7011
7012
  this.listeners = void 0;
7012
7013
  this.hls = void 0;
7014
+ this.fragmentTracker = void 0;
7013
7015
  // The number of BUFFER_CODEC events received before any sourceBuffers are created
7014
7016
  this.bufferCodecEventsExpected = 0;
7015
7017
  // The total number of BUFFER_CODEC events received
@@ -7020,6 +7022,10 @@ class BufferController extends Logger {
7020
7022
  this.mediaSource = null;
7021
7023
  // Last MP3 audio chunk appended
7022
7024
  this.lastMpegAudioChunk = null;
7025
+ // Audio fragment blocked from appending until corresponding video appends or context changes
7026
+ this.blockedAudioAppend = null;
7027
+ // Keep track of video append position for unblocking audio
7028
+ this.lastVideoAppendEnd = 0;
7023
7029
  this.appendSource = void 0;
7024
7030
  // counters
7025
7031
  this.appendErrors = {
@@ -7051,7 +7057,10 @@ class BufferController extends Logger {
7051
7057
  this.log('Media source opened');
7052
7058
  if (media) {
7053
7059
  media.removeEventListener('emptied', this._onMediaEmptied);
7054
- this.updateMediaElementDuration();
7060
+ const durationAndRange = this.getDurationAndRange();
7061
+ if (durationAndRange) {
7062
+ this.updateMediaSource(durationAndRange);
7063
+ }
7055
7064
  this.hls.trigger(Events.MEDIA_ATTACHED, {
7056
7065
  media,
7057
7066
  mediaSource: mediaSource
@@ -7079,6 +7088,7 @@ class BufferController extends Logger {
7079
7088
  }
7080
7089
  };
7081
7090
  this.hls = hls;
7091
+ this.fragmentTracker = fragmentTracker;
7082
7092
  this.appendSource = hls.config.preferManagedMediaSource;
7083
7093
  this._initSourceBuffer();
7084
7094
  this.registerListeners();
@@ -7091,7 +7101,7 @@ class BufferController extends Logger {
7091
7101
  this.details = null;
7092
7102
  this.lastMpegAudioChunk = null;
7093
7103
  // @ts-ignore
7094
- this.hls = null;
7104
+ this.hls = this.fragmentTracker = null;
7095
7105
  // @ts-ignore
7096
7106
  this._onMediaSourceOpen = this._onMediaSourceClose = null;
7097
7107
  // @ts-ignore
@@ -7147,6 +7157,8 @@ class BufferController extends Logger {
7147
7157
  audiovideo: 0
7148
7158
  };
7149
7159
  this.lastMpegAudioChunk = null;
7160
+ this.blockedAudioAppend = null;
7161
+ this.lastVideoAppendEnd = 0;
7150
7162
  }
7151
7163
  onManifestLoading() {
7152
7164
  this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
@@ -7284,9 +7296,10 @@ class BufferController extends Logger {
7284
7296
  const trackNames = Object.keys(data);
7285
7297
  trackNames.forEach(trackName => {
7286
7298
  if (sourceBufferCount) {
7299
+ var _track$buffer;
7287
7300
  // check if SourceBuffer codec needs to change
7288
7301
  const track = this.tracks[trackName];
7289
- if (track && typeof track.buffer.changeType === 'function') {
7302
+ if (track && typeof ((_track$buffer = track.buffer) == null ? void 0 : _track$buffer.changeType) === 'function') {
7290
7303
  var _trackCodec;
7291
7304
  const {
7292
7305
  id,
@@ -7356,20 +7369,54 @@ class BufferController extends Logger {
7356
7369
  };
7357
7370
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
7358
7371
  }
7372
+ blockAudio(partOrFrag) {
7373
+ var _this$fragmentTracker;
7374
+ const pStart = partOrFrag.start;
7375
+ const pTime = pStart + partOrFrag.duration * 0.05;
7376
+ const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
7377
+ if (atGap) {
7378
+ return;
7379
+ }
7380
+ const op = {
7381
+ execute: () => {
7382
+ var _this$fragmentTracker2;
7383
+ if (this.lastVideoAppendEnd > pTime || this.sourceBuffer.video && BufferHelper.isBuffered(this.sourceBuffer.video, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
7384
+ this.blockedAudioAppend = null;
7385
+ this.operationQueue.shiftAndExecuteNext('audio');
7386
+ }
7387
+ },
7388
+ onStart: () => {},
7389
+ onComplete: () => {},
7390
+ onError: () => {}
7391
+ };
7392
+ this.blockedAudioAppend = {
7393
+ op,
7394
+ frag: partOrFrag
7395
+ };
7396
+ this.operationQueue.append(op, 'audio', true);
7397
+ }
7398
+ unblockAudio() {
7399
+ const blockedAudioAppend = this.blockedAudioAppend;
7400
+ if (blockedAudioAppend) {
7401
+ this.blockedAudioAppend = null;
7402
+ this.operationQueue.unblockAudio(blockedAudioAppend.op);
7403
+ }
7404
+ }
7359
7405
  onBufferAppending(event, eventData) {
7360
7406
  const {
7361
- hls,
7362
7407
  operationQueue,
7363
7408
  tracks
7364
7409
  } = this;
7365
7410
  const {
7366
7411
  data,
7367
7412
  type,
7413
+ parent,
7368
7414
  frag,
7369
7415
  part,
7370
7416
  chunkMeta
7371
7417
  } = eventData;
7372
7418
  const chunkStats = chunkMeta.buffering[type];
7419
+ const sn = frag.sn;
7373
7420
  const bufferAppendingStart = self.performance.now();
7374
7421
  chunkStats.start = bufferAppendingStart;
7375
7422
  const fragBuffering = frag.stats.buffering;
@@ -7392,7 +7439,36 @@ class BufferController extends Logger {
7392
7439
  checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
7393
7440
  this.lastMpegAudioChunk = chunkMeta;
7394
7441
  }
7395
- const fragStart = frag.start;
7442
+
7443
+ // Block audio append until overlapping video append
7444
+ const videoSb = this.sourceBuffer.video;
7445
+ if (videoSb && sn !== 'initSegment') {
7446
+ const partOrFrag = part || frag;
7447
+ const blockedAudioAppend = this.blockedAudioAppend;
7448
+ if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
7449
+ const pStart = partOrFrag.start;
7450
+ const pTime = pStart + partOrFrag.duration * 0.05;
7451
+ const vbuffered = videoSb.buffered;
7452
+ const vappending = this.operationQueue.current('video');
7453
+ if (!vbuffered.length && !vappending) {
7454
+ // wait for video before appending audio
7455
+ this.blockAudio(partOrFrag);
7456
+ } else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
7457
+ // audio is ahead of video
7458
+ this.blockAudio(partOrFrag);
7459
+ }
7460
+ } else if (type === 'video') {
7461
+ const videoAppendEnd = partOrFrag.end;
7462
+ if (blockedAudioAppend) {
7463
+ const audioStart = blockedAudioAppend.frag.start;
7464
+ if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
7465
+ this.unblockAudio();
7466
+ }
7467
+ }
7468
+ this.lastVideoAppendEnd = videoAppendEnd;
7469
+ }
7470
+ }
7471
+ const fragStart = (part || frag).start;
7396
7472
  const operation = {
7397
7473
  execute: () => {
7398
7474
  chunkStats.executeStart = self.performance.now();
@@ -7401,7 +7477,7 @@ class BufferController extends Logger {
7401
7477
  if (sb) {
7402
7478
  const delta = fragStart - sb.timestampOffset;
7403
7479
  if (Math.abs(delta) >= 0.1) {
7404
- this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`);
7480
+ this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`);
7405
7481
  sb.timestampOffset = fragStart;
7406
7482
  }
7407
7483
  }
@@ -7468,22 +7544,21 @@ class BufferController extends Logger {
7468
7544
  /* with UHD content, we could get loop of quota exceeded error until
7469
7545
  browser is able to evict some data from sourcebuffer. Retrying can help recover.
7470
7546
  */
7471
- this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
7472
- if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
7547
+ this.warn(`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
7548
+ if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
7473
7549
  event.fatal = true;
7474
7550
  }
7475
7551
  }
7476
- hls.trigger(Events.ERROR, event);
7552
+ this.hls.trigger(Events.ERROR, event);
7477
7553
  }
7478
7554
  };
7479
7555
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
7480
7556
  }
7481
- onBufferFlushing(event, data) {
7482
- const {
7483
- operationQueue
7484
- } = this;
7485
- const flushOperation = type => ({
7486
- execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
7557
+ getFlushOp(type, start, end) {
7558
+ return {
7559
+ execute: () => {
7560
+ this.removeExecutor(type, start, end);
7561
+ },
7487
7562
  onStart: () => {
7488
7563
  // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
7489
7564
  },
@@ -7496,12 +7571,22 @@ class BufferController extends Logger {
7496
7571
  onError: error => {
7497
7572
  this.warn(`Failed to remove from ${type} SourceBuffer`, error);
7498
7573
  }
7499
- });
7500
- if (data.type) {
7501
- operationQueue.append(flushOperation(data.type), data.type);
7574
+ };
7575
+ }
7576
+ onBufferFlushing(event, data) {
7577
+ const {
7578
+ operationQueue
7579
+ } = this;
7580
+ const {
7581
+ type,
7582
+ startOffset,
7583
+ endOffset
7584
+ } = data;
7585
+ if (type) {
7586
+ operationQueue.append(this.getFlushOp(type, startOffset, endOffset), type);
7502
7587
  } else {
7503
- this.getSourceBufferTypes().forEach(type => {
7504
- operationQueue.append(flushOperation(type), type);
7588
+ this.getSourceBufferTypes().forEach(sbType => {
7589
+ operationQueue.append(this.getFlushOp(sbType, startOffset, endOffset), sbType);
7505
7590
  });
7506
7591
  }
7507
7592
  }
@@ -7548,6 +7633,9 @@ class BufferController extends Logger {
7548
7633
  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
7549
7634
  // an undefined data.type will mark all buffers as EOS.
7550
7635
  onBufferEos(event, data) {
7636
+ if (data.type === 'video') {
7637
+ this.unblockAudio();
7638
+ }
7551
7639
  const ended = this.getSourceBufferTypes().reduce((acc, type) => {
7552
7640
  const sb = this.sourceBuffer[type];
7553
7641
  if (sb && (!data.type || data.type === type)) {
@@ -7590,10 +7678,14 @@ class BufferController extends Logger {
7590
7678
  return;
7591
7679
  }
7592
7680
  this.details = details;
7681
+ const durationAndRange = this.getDurationAndRange();
7682
+ if (!durationAndRange) {
7683
+ return;
7684
+ }
7593
7685
  if (this.getSourceBufferTypes().length) {
7594
- this.blockBuffers(this.updateMediaElementDuration.bind(this));
7686
+ this.blockBuffers(() => this.updateMediaSource(durationAndRange));
7595
7687
  } else {
7596
- this.updateMediaElementDuration();
7688
+ this.updateMediaSource(durationAndRange);
7597
7689
  }
7598
7690
  }
7599
7691
  trimBuffers() {
@@ -7698,9 +7790,9 @@ class BufferController extends Logger {
7698
7790
  * 'liveDurationInfinity` is set to `true`
7699
7791
  * More details: https://github.com/video-dev/hls.js/issues/355
7700
7792
  */
7701
- updateMediaElementDuration() {
7793
+ getDurationAndRange() {
7702
7794
  if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
7703
- return;
7795
+ return null;
7704
7796
  }
7705
7797
  const {
7706
7798
  details,
@@ -7714,25 +7806,41 @@ class BufferController extends Logger {
7714
7806
  if (details.live && hls.config.liveDurationInfinity) {
7715
7807
  // Override duration to Infinity
7716
7808
  mediaSource.duration = Infinity;
7717
- this.updateSeekableRange(details);
7809
+ const len = details.fragments.length;
7810
+ if (len && details.live && !!mediaSource.setLiveSeekableRange) {
7811
+ const start = Math.max(0, details.fragments[0].start);
7812
+ const end = Math.max(start, start + details.totalduration);
7813
+ return {
7814
+ duration: Infinity,
7815
+ start,
7816
+ end
7817
+ };
7818
+ }
7819
+ return {
7820
+ duration: Infinity
7821
+ };
7718
7822
  } else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
7719
- // levelDuration was the last value we set.
7720
- // not using mediaSource.duration as the browser may tweak this value
7721
- // only update Media Source duration if its value increase, this is to avoid
7722
- // flushing already buffered portion when switching between quality level
7723
- this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
7724
- mediaSource.duration = levelDuration;
7823
+ return {
7824
+ duration: levelDuration
7825
+ };
7725
7826
  }
7827
+ return null;
7726
7828
  }
7727
- updateSeekableRange(levelDetails) {
7728
- const mediaSource = this.mediaSource;
7729
- const fragments = levelDetails.fragments;
7730
- const len = fragments.length;
7731
- if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) {
7732
- const start = Math.max(0, fragments[0].start);
7733
- const end = Math.max(start, start + levelDetails.totalduration);
7734
- this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
7735
- mediaSource.setLiveSeekableRange(start, end);
7829
+ updateMediaSource({
7830
+ duration,
7831
+ start,
7832
+ end
7833
+ }) {
7834
+ if (!this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
7835
+ return;
7836
+ }
7837
+ if (isFiniteNumber(duration)) {
7838
+ this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
7839
+ }
7840
+ this.mediaSource.duration = duration;
7841
+ if (start !== undefined && end !== undefined) {
7842
+ this.log(`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
7843
+ this.mediaSource.setLiveSeekableRange(start, end);
7736
7844
  }
7737
7845
  }
7738
7846
  checkPendingTracks() {
@@ -7915,6 +8023,7 @@ class BufferController extends Logger {
7915
8023
  }
7916
8024
  return;
7917
8025
  }
8026
+ sb.ending = false;
7918
8027
  sb.ended = false;
7919
8028
  sb.appendBuffer(data);
7920
8029
  }
@@ -7934,10 +8043,14 @@ class BufferController extends Logger {
7934
8043
 
7935
8044
  // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
7936
8045
  const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
7937
- Promise.all(blockingOperations).then(() => {
8046
+ const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
8047
+ if (audioBlocked) {
8048
+ this.unblockAudio();
8049
+ }
8050
+ Promise.all(blockingOperations).then(result => {
7938
8051
  // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
7939
8052
  onUnblocked();
7940
- buffers.forEach(type => {
8053
+ buffers.forEach((type, i) => {
7941
8054
  const sb = this.sourceBuffer[type];
7942
8055
  // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
7943
8056
  // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
@@ -10256,13 +10369,16 @@ class FragmentTracker {
10256
10369
  * If not found any Fragment, return null
10257
10370
  */
10258
10371
  getBufferedFrag(position, levelType) {
10372
+ return this.getFragAtPos(position, levelType, true);
10373
+ }
10374
+ getFragAtPos(position, levelType, buffered) {
10259
10375
  const {
10260
10376
  fragments
10261
10377
  } = this;
10262
10378
  const keys = Object.keys(fragments);
10263
10379
  for (let i = keys.length; i--;) {
10264
10380
  const fragmentEntity = fragments[keys[i]];
10265
- if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
10381
+ if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
10266
10382
  const frag = fragmentEntity.body;
10267
10383
  if (frag.start <= position && position <= frag.end) {
10268
10384
  return frag;
@@ -10517,7 +10633,8 @@ class FragmentTracker {
10517
10633
  const {
10518
10634
  frag,
10519
10635
  part,
10520
- timeRanges
10636
+ timeRanges,
10637
+ type
10521
10638
  } = data;
10522
10639
  if (frag.sn === 'initSegment') {
10523
10640
  return;
@@ -10532,10 +10649,8 @@ class FragmentTracker {
10532
10649
  }
10533
10650
  // Store the latest timeRanges loaded in the buffer
10534
10651
  this.timeRanges = timeRanges;
10535
- Object.keys(timeRanges).forEach(elementaryStream => {
10536
- const timeRange = timeRanges[elementaryStream];
10537
- this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
10538
- });
10652
+ const timeRange = timeRanges[type];
10653
+ this.detectEvictedFragments(type, timeRange, playlistType, part);
10539
10654
  }
10540
10655
  onFragBuffered(event, data) {
10541
10656
  this.detectPartialFragments(data);
@@ -12712,7 +12827,7 @@ class BaseStreamController extends TaskLoop {
12712
12827
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
12713
12828
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
12714
12829
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
12715
- if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
12830
+ if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
12716
12831
  return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
12717
12832
  }
12718
12833
  }
@@ -19892,6 +20007,17 @@ class StreamController extends BaseStreamController {
19892
20007
  getMainFwdBufferInfo() {
19893
20008
  return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
19894
20009
  }
20010
+ get maxBufferLength() {
20011
+ const {
20012
+ levels,
20013
+ level
20014
+ } = this;
20015
+ const levelInfo = levels == null ? void 0 : levels[level];
20016
+ if (!levelInfo) {
20017
+ return this.config.maxBufferLength;
20018
+ }
20019
+ return this.getMaxBufferLength(levelInfo.maxBitrate);
20020
+ }
19895
20021
  backtrack(frag) {
19896
20022
  this.couldBacktrack = true;
19897
20023
  // Causes findFragments to backtrack through fragments to find the keyframe
@@ -19997,7 +20123,7 @@ class Hls {
19997
20123
  * Get the video-dev/hls.js package version.
19998
20124
  */
19999
20125
  static get version() {
20000
- return "1.5.7-0.canary.10015";
20126
+ return "1.5.7-0.canary.10016";
20001
20127
  }
20002
20128
 
20003
20129
  /**
@@ -20099,7 +20225,9 @@ class Hls {
20099
20225
  } = config;
20100
20226
  const errorController = new ConfigErrorController(this);
20101
20227
  const abrController = this.abrController = new ConfigAbrController(this);
20102
- const bufferController = this.bufferController = new ConfigBufferController(this);
20228
+ // FragmentTracker must be defined before StreamController because the order of event handling is important
20229
+ const fragmentTracker = new FragmentTracker(this);
20230
+ const bufferController = this.bufferController = new ConfigBufferController(this, fragmentTracker);
20103
20231
  const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
20104
20232
  const fpsController = new ConfigFpsController(this);
20105
20233
  const playListLoader = new PlaylistLoader(this);
@@ -20108,8 +20236,6 @@ class Hls {
20108
20236
  // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
20109
20237
  const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
20110
20238
  const levelController = this.levelController = new LevelController(this, contentSteering);
20111
- // FragmentTracker must be defined before StreamController because the order of event handling is important
20112
- const fragmentTracker = new FragmentTracker(this);
20113
20239
  const keyLoader = new KeyLoader(this.config);
20114
20240
  const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
20115
20241
 
@@ -20637,6 +20763,9 @@ class Hls {
20637
20763
  get mainForwardBufferInfo() {
20638
20764
  return this.streamController.getMainFwdBufferInfo();
20639
20765
  }
20766
+ get maxBufferLength() {
20767
+ return this.streamController.maxBufferLength;
20768
+ }
20640
20769
 
20641
20770
  /**
20642
20771
  * Find and select the best matching audio track, making a level switch when a Group change is necessary.