hls.js 1.6.0-beta.2.0.canary.10882 → 1.6.0-beta.2.0.canary.10883

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.
package/dist/hls.light.js CHANGED
@@ -756,6 +756,7 @@
756
756
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
757
757
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
758
758
  Events["MEDIA_ENDED"] = "hlsMediaEnded";
759
+ Events["STALL_RESOLVED"] = "hlsStallResolved";
759
760
  Events["BUFFER_RESET"] = "hlsBufferReset";
760
761
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
761
762
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -1029,7 +1030,7 @@
1029
1030
  // Some browsers don't allow to use bind on console object anyway
1030
1031
  // fallback to default if needed
1031
1032
  try {
1032
- newLogger.log("Debug logs enabled for \"" + context + "\" in hls.js version " + "1.6.0-beta.2.0.canary.10882");
1033
+ newLogger.log("Debug logs enabled for \"" + context + "\" in hls.js version " + "1.6.0-beta.2.0.canary.10883");
1033
1034
  } catch (e) {
1034
1035
  /* log fn threw an exception. All logger methods are no-ops. */
1035
1036
  return createLogger();
@@ -3621,8 +3622,7 @@
3621
3622
  return {
3622
3623
  len: 0,
3623
3624
  start: pos,
3624
- end: pos,
3625
- nextStart: undefined
3625
+ end: pos
3626
3626
  };
3627
3627
  };
3628
3628
  BufferHelper.bufferedInfo = function bufferedInfo(buffered, pos, maxHoleDuration) {
@@ -3687,7 +3687,8 @@
3687
3687
  len: bufferLen,
3688
3688
  start: bufferStart || 0,
3689
3689
  end: bufferEnd || 0,
3690
- nextStart: bufferStartNext
3690
+ nextStart: bufferStartNext,
3691
+ buffered: buffered
3691
3692
  };
3692
3693
  }
3693
3694
 
@@ -14972,6 +14973,7 @@
14972
14973
  progressive: false,
14973
14974
  lowLatencyMode: true,
14974
14975
  cmcd: undefined,
14976
+ detectStallWithCurrentTimeMs: 1250,
14975
14977
  enableDateRangeMetadataCues: true,
14976
14978
  enableEmsgMetadataCues: true,
14977
14979
  enableEmsgKLVMetadata: false,
@@ -19236,25 +19238,23 @@
19236
19238
  }]);
19237
19239
  }(TaskLoop);
19238
19240
 
19239
- var STALL_MINIMUM_DURATION_MS = 250;
19240
19241
  var MAX_START_GAP_JUMP = 2.0;
19241
19242
  var SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
19242
19243
  var SKIP_BUFFER_RANGE_START = 0.05;
19243
19244
  var GapController = /*#__PURE__*/function (_Logger) {
19244
- function GapController(config, media, fragmentTracker, hls) {
19245
+ function GapController(media, fragmentTracker, hls) {
19245
19246
  var _this;
19246
19247
  _this = _Logger.call(this, 'gap-controller', hls.logger) || this;
19247
- _this.config = undefined;
19248
19248
  _this.media = null;
19249
- _this.fragmentTracker = undefined;
19250
- _this.hls = undefined;
19249
+ _this.fragmentTracker = null;
19250
+ _this.hls = null;
19251
19251
  _this.nudgeRetry = 0;
19252
19252
  _this.stallReported = false;
19253
19253
  _this.stalled = null;
19254
19254
  _this.moved = false;
19255
19255
  _this.seeking = false;
19256
19256
  _this.ended = 0;
19257
- _this.config = config;
19257
+ _this.waiting = 0;
19258
19258
  _this.media = media;
19259
19259
  _this.fragmentTracker = fragmentTracker;
19260
19260
  _this.hls = hls;
@@ -19263,9 +19263,7 @@
19263
19263
  _inheritsLoose(GapController, _Logger);
19264
19264
  var _proto = GapController.prototype;
19265
19265
  _proto.destroy = function destroy() {
19266
- this.media = null;
19267
- // @ts-ignore
19268
- this.hls = this.fragmentTracker = null;
19266
+ this.media = this.hls = this.fragmentTracker = null;
19269
19267
  }
19270
19268
 
19271
19269
  /**
@@ -19275,10 +19273,10 @@
19275
19273
  * @param lastCurrentTime - Previously read playhead position
19276
19274
  */;
19277
19275
  _proto.poll = function poll(lastCurrentTime, activeFrag, levelDetails, state) {
19278
- var config = this.config,
19279
- media = this.media,
19276
+ var _this$hls;
19277
+ var media = this.media,
19280
19278
  stalled = this.stalled;
19281
- if (media === null) {
19279
+ if (!media) {
19282
19280
  return;
19283
19281
  }
19284
19282
  var currentTime = media.currentTime,
@@ -19296,43 +19294,45 @@
19296
19294
  if (!seeking) {
19297
19295
  this.nudgeRetry = 0;
19298
19296
  }
19299
- if (stalled !== null) {
19300
- // The playhead is now moving, but was previously stalled
19301
- if (this.stallReported) {
19302
- var _stalledDuration = self.performance.now() - stalled;
19303
- this.warn("playback not stuck anymore @" + currentTime + ", after " + Math.round(_stalledDuration) + "ms");
19304
- this.stallReported = false;
19305
- }
19306
- this.stalled = null;
19297
+ if (this.waiting === 0) {
19298
+ this.stallResolved(currentTime);
19307
19299
  }
19308
19300
  return;
19309
19301
  }
19310
19302
 
19311
19303
  // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
19312
19304
  if (beginSeek || seeked) {
19313
- this.stalled = null;
19305
+ if (seeked) {
19306
+ this.stallResolved(currentTime);
19307
+ }
19314
19308
  return;
19315
19309
  }
19316
19310
 
19317
19311
  // The playhead should not be moving
19318
- if (media.paused && !seeking || media.ended || media.playbackRate === 0 || !BufferHelper.getBuffered(media).length) {
19312
+ if (media.paused && !seeking || media.ended || media.playbackRate === 0) {
19313
+ this.nudgeRetry = 0;
19314
+ this.stallResolved(currentTime);
19319
19315
  // Fire MEDIA_ENDED to workaround event not being dispatched by browser
19320
- if (!this.ended && media.ended) {
19316
+ if (!this.ended && media.ended && this.hls) {
19321
19317
  this.ended = currentTime || 1;
19322
19318
  this.hls.trigger(Events.MEDIA_ENDED, {
19323
19319
  stalled: false
19324
19320
  });
19325
19321
  }
19322
+ return;
19323
+ }
19324
+ if (!BufferHelper.getBuffered(media).length) {
19326
19325
  this.nudgeRetry = 0;
19327
19326
  return;
19328
19327
  }
19329
19328
  var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
19330
19329
  var nextStart = bufferInfo.nextStart || 0;
19331
- if (seeking) {
19330
+ var fragmentTracker = this.fragmentTracker;
19331
+ if (seeking && fragmentTracker) {
19332
19332
  // Waiting for seeking in a buffered range to complete
19333
19333
  var hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
19334
19334
  // Next buffered range is too far ahead to jump to while still seeking
19335
- var noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime);
19335
+ var noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
19336
19336
  if (hasEnoughBuffer || noBufferGap) {
19337
19337
  return;
19338
19338
  }
@@ -19342,7 +19342,7 @@
19342
19342
 
19343
19343
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
19344
19344
  // The addition poll gives the browser a chance to jump the gap for us
19345
- if (!this.moved && this.stalled !== null) {
19345
+ if (!this.moved && this.stalled !== null && fragmentTracker) {
19346
19346
  // There is no playable buffer (seeked, waiting for buffer)
19347
19347
  var isBuffered = bufferInfo.len > 0;
19348
19348
  if (!isBuffered && !nextStart) {
@@ -19356,7 +19356,7 @@
19356
19356
  // that begins over 1 target duration after the video start position.
19357
19357
  var isLive = !!(levelDetails != null && levelDetails.live);
19358
19358
  var maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
19359
- var partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
19359
+ var partialOrGap = fragmentTracker.getPartialFragment(currentTime);
19360
19360
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
19361
19361
  if (!media.paused) {
19362
19362
  this._trySkipBufferHole(partialOrGap);
@@ -19366,16 +19366,27 @@
19366
19366
  }
19367
19367
 
19368
19368
  // Start tracking stall time
19369
+ var config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
19370
+ if (!config) {
19371
+ return;
19372
+ }
19373
+ var detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
19369
19374
  var tnow = self.performance.now();
19375
+ var tWaiting = this.waiting;
19370
19376
  if (stalled === null) {
19371
- this.stalled = tnow;
19377
+ // Use time of recent "waiting" event
19378
+ if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
19379
+ this.stalled = tWaiting;
19380
+ } else {
19381
+ this.stalled = tnow;
19382
+ }
19372
19383
  return;
19373
19384
  }
19374
19385
  var stalledDuration = tnow - stalled;
19375
- if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
19386
+ if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
19376
19387
  // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
19377
19388
  if (state === State.ENDED && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? undefined : levelDetails.edge) || 0)) < 1) {
19378
- if (stalledDuration < 1000 || this.ended) {
19389
+ if (this.ended) {
19379
19390
  return;
19380
19391
  }
19381
19392
  this.ended = currentTime || 1;
@@ -19386,12 +19397,26 @@
19386
19397
  }
19387
19398
  // Report stalling after trying to fix
19388
19399
  this._reportStall(bufferInfo);
19389
- if (!this.media) {
19400
+ if (!this.media || !this.hls) {
19390
19401
  return;
19391
19402
  }
19392
19403
  }
19393
19404
  var bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
19394
19405
  this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
19406
+ };
19407
+ _proto.stallResolved = function stallResolved(currentTime) {
19408
+ var stalled = this.stalled;
19409
+ if (stalled && this.hls) {
19410
+ this.stalled = null;
19411
+ // The playhead is now moving, but was previously stalled
19412
+ if (this.stallReported) {
19413
+ var stalledDuration = self.performance.now() - stalled;
19414
+ this.warn("playback not stuck anymore @" + currentTime + ", after " + Math.round(stalledDuration) + "ms");
19415
+ this.stallReported = false;
19416
+ this.waiting = 0;
19417
+ this.hls.trigger(Events.STALL_RESOLVED, {});
19418
+ }
19419
+ }
19395
19420
  }
19396
19421
 
19397
19422
  /**
@@ -19401,10 +19426,11 @@
19401
19426
  * @private
19402
19427
  */;
19403
19428
  _proto._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDurationMs) {
19404
- var config = this.config,
19405
- fragmentTracker = this.fragmentTracker,
19429
+ var _this$hls2;
19430
+ var fragmentTracker = this.fragmentTracker,
19406
19431
  media = this.media;
19407
- if (media === null) {
19432
+ var config = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.config;
19433
+ if (!media || !fragmentTracker || !config) {
19408
19434
  return;
19409
19435
  }
19410
19436
  var currentTime = media.currentTime;
@@ -19424,13 +19450,12 @@
19424
19450
  // we may just have to "nudge" the playlist as the browser decoding/rendering engine
19425
19451
  // needs to cross some sort of threshold covering all source-buffers content
19426
19452
  // to start playing properly.
19427
- if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
19453
+ var bufferedRanges = bufferInfo.buffered;
19454
+ if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
19428
19455
  this.warn('Trying to nudge playhead over buffer-hole');
19429
19456
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
19430
19457
  // We only try to jump the hole if it's under the configured size
19431
- // Reset stalled so to rearm watchdog timer
19432
- this.stalled = null;
19433
- this._tryNudgeBuffer();
19458
+ this._tryNudgeBuffer(bufferInfo);
19434
19459
  }
19435
19460
  }
19436
19461
 
@@ -19442,8 +19467,9 @@
19442
19467
  _proto._reportStall = function _reportStall(bufferInfo) {
19443
19468
  var hls = this.hls,
19444
19469
  media = this.media,
19445
- stallReported = this.stallReported;
19446
- if (!stallReported && media) {
19470
+ stallReported = this.stallReported,
19471
+ stalled = this.stalled;
19472
+ if (!stallReported && stalled !== null && media && hls) {
19447
19473
  // Report stalled error once
19448
19474
  this.stallReported = true;
19449
19475
  var error = new Error("Playback stalling at @" + media.currentTime + " due to low buffer (" + JSON.stringify(bufferInfo) + ")");
@@ -19453,7 +19479,11 @@
19453
19479
  details: ErrorDetails.BUFFER_STALLED_ERROR,
19454
19480
  fatal: false,
19455
19481
  error: error,
19456
- buffer: bufferInfo.len
19482
+ buffer: bufferInfo.len,
19483
+ bufferInfo: bufferInfo,
19484
+ stalled: {
19485
+ start: stalled
19486
+ }
19457
19487
  });
19458
19488
  }
19459
19489
  }
@@ -19464,10 +19494,11 @@
19464
19494
  * @private
19465
19495
  */;
19466
19496
  _proto._trySkipBufferHole = function _trySkipBufferHole(partial) {
19467
- var config = this.config,
19468
- hls = this.hls,
19497
+ var _this$hls3;
19498
+ var fragmentTracker = this.fragmentTracker,
19469
19499
  media = this.media;
19470
- if (media === null) {
19500
+ var config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
19501
+ if (!media || !fragmentTracker || !config) {
19471
19502
  return 0;
19472
19503
  }
19473
19504
 
@@ -19482,7 +19513,6 @@
19482
19513
  if (gapLength > 0 && (bufferStarved || waiting)) {
19483
19514
  // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
19484
19515
  if (gapLength > config.maxBufferHole) {
19485
- var fragmentTracker = this.fragmentTracker;
19486
19516
  var startGap = false;
19487
19517
  if (currentTime === 0) {
19488
19518
  var startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
@@ -19513,17 +19543,18 @@
19513
19543
  var targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
19514
19544
  this.warn("skipping hole, adjusting currentTime from " + currentTime + " to " + targetTime);
19515
19545
  this.moved = true;
19516
- this.stalled = null;
19517
19546
  media.currentTime = targetTime;
19518
- if (partial && !partial.gap) {
19547
+ if (partial && !partial.gap && this.hls) {
19519
19548
  var error = new Error("fragment loaded with buffer holes, seeking from " + currentTime + " to " + targetTime);
19520
- hls.trigger(Events.ERROR, {
19549
+ this.hls.trigger(Events.ERROR, {
19521
19550
  type: ErrorTypes.MEDIA_ERROR,
19522
19551
  details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
19523
19552
  fatal: false,
19524
19553
  error: error,
19525
19554
  reason: error.message,
19526
- frag: partial
19555
+ frag: partial,
19556
+ buffer: bufferInfo.len,
19557
+ bufferInfo: bufferInfo
19527
19558
  });
19528
19559
  }
19529
19560
  return targetTime;
@@ -19536,13 +19567,13 @@
19536
19567
  * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
19537
19568
  * @private
19538
19569
  */;
19539
- _proto._tryNudgeBuffer = function _tryNudgeBuffer() {
19540
- var config = this.config,
19541
- hls = this.hls,
19570
+ _proto._tryNudgeBuffer = function _tryNudgeBuffer(bufferInfo) {
19571
+ var hls = this.hls,
19542
19572
  media = this.media,
19543
19573
  nudgeRetry = this.nudgeRetry;
19544
- if (media === null) {
19545
- return;
19574
+ var config = hls == null ? undefined : hls.config;
19575
+ if (!media || !config) {
19576
+ return 0;
19546
19577
  }
19547
19578
  var currentTime = media.currentTime;
19548
19579
  this.nudgeRetry++;
@@ -19556,7 +19587,9 @@
19556
19587
  type: ErrorTypes.MEDIA_ERROR,
19557
19588
  details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
19558
19589
  error: error,
19559
- fatal: false
19590
+ fatal: false,
19591
+ buffer: bufferInfo.len,
19592
+ bufferInfo: bufferInfo
19560
19593
  });
19561
19594
  } else {
19562
19595
  var _error = new Error("Playhead still not moving while enough data buffered @" + currentTime + " after " + config.nudgeMaxRetry + " nudges");
@@ -19565,7 +19598,9 @@
19565
19598
  type: ErrorTypes.MEDIA_ERROR,
19566
19599
  details: ErrorDetails.BUFFER_STALLED_ERROR,
19567
19600
  error: _error,
19568
- fatal: true
19601
+ fatal: true,
19602
+ buffer: bufferInfo.len,
19603
+ bufferInfo: bufferInfo
19569
19604
  });
19570
19605
  }
19571
19606
  };
@@ -19735,7 +19770,7 @@
19735
19770
  return !remuxResult.audio && !remuxResult.video && !remuxResult.text && !remuxResult.id3 && !remuxResult.initSegment;
19736
19771
  }
19737
19772
 
19738
- var version = "1.6.0-beta.2.0.canary.10882";
19773
+ var version = "1.6.0-beta.2.0.canary.10883";
19739
19774
 
19740
19775
  // ensure the worker ends up in the bundle
19741
19776
  // If the worker should not be included this gets aliased to empty.js
@@ -20155,11 +20190,18 @@
20155
20190
  _this.backtrackFragment = null;
20156
20191
  _this.audioCodecSwitch = false;
20157
20192
  _this.videoBuffer = null;
20193
+ _this.onMediaWaiting = function () {
20194
+ var gapController = _this.gapController;
20195
+ if (gapController) {
20196
+ gapController.waiting = self.performance.now();
20197
+ }
20198
+ };
20158
20199
  _this.onMediaPlaying = function () {
20159
20200
  // tick to speed up FRAG_CHANGED triggering
20160
20201
  var gapController = _this.gapController;
20161
20202
  if (gapController) {
20162
20203
  gapController.ended = 0;
20204
+ gapController.waiting = 0;
20163
20205
  }
20164
20206
  _this.tick();
20165
20207
  };
@@ -20214,7 +20256,7 @@
20214
20256
  };
20215
20257
  _proto.onHandlerDestroying = function onHandlerDestroying() {
20216
20258
  // @ts-ignore
20217
- this.onMediaPlaying = this.onMediaSeeked = null;
20259
+ this.onMediaPlaying = this.onMediaSeeked = this.onMediaWaiting = null;
20218
20260
  this.unregisterListeners();
20219
20261
  _BaseStreamController.prototype.onHandlerDestroying.call(this);
20220
20262
  };
@@ -20535,15 +20577,18 @@
20535
20577
  var media = data.media;
20536
20578
  media.removeEventListener('playing', this.onMediaPlaying);
20537
20579
  media.removeEventListener('seeked', this.onMediaSeeked);
20580
+ media.removeEventListener('waiting', this.onMediaWaiting);
20538
20581
  media.addEventListener('playing', this.onMediaPlaying);
20539
20582
  media.addEventListener('seeked', this.onMediaSeeked);
20540
- this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
20583
+ media.addEventListener('waiting', this.onMediaWaiting);
20584
+ this.gapController = new GapController(media, this.fragmentTracker, this.hls);
20541
20585
  };
20542
20586
  _proto.onMediaDetaching = function onMediaDetaching(event, data) {
20543
20587
  var media = this.media;
20544
20588
  if (media) {
20545
20589
  media.removeEventListener('playing', this.onMediaPlaying);
20546
20590
  media.removeEventListener('seeked', this.onMediaSeeked);
20591
+ media.removeEventListener('waiting', this.onMediaWaiting);
20547
20592
  }
20548
20593
  this.videoBuffer = null;
20549
20594
  this.fragPlaying = null;
@@ -20953,7 +20998,7 @@
20953
20998
  var startPosition = this.startPosition;
20954
20999
  // only adjust currentTime if different from startPosition or if startPosition not buffered
20955
21000
  // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered
20956
- if (startPosition >= 0) {
21001
+ if (startPosition >= 0 && currentTime < startPosition) {
20957
21002
  if (media.seeking) {
20958
21003
  this.log("could not seek to " + startPosition + ", already seeking at " + currentTime);
20959
21004
  return;