stormcloud-video-player 0.8.9 → 0.8.11

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/lib/index.cjs CHANGED
@@ -1476,6 +1476,12 @@ function createHlsAdPlayer(contentVideo, options) {
1476
1476
  contentVideo.style.visibility = "visible";
1477
1477
  contentVideo.style.opacity = "1";
1478
1478
  }
1479
+ },
1480
+ getLoadedAdDuration: function getLoadedAdDuration() {
1481
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration;
1482
+ },
1483
+ getLoadedAdId: function getLoadedAdId() {
1484
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.id;
1479
1485
  }
1480
1486
  };
1481
1487
  }
@@ -2919,6 +2925,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2919
2925
  this.maxConsecutiveFailures = 5;
2920
2926
  this.lastAdRequestTime = 0;
2921
2927
  this.minAdRequestIntervalMs = 3e3;
2928
+ this.defaultMinRemainingMsToStartAd = 4e3;
2922
2929
  this.backoffBaseMs = 1e3;
2923
2930
  this.maxBackoffMs = 15e3;
2924
2931
  this.globalConsecutiveNoFills = 0;
@@ -2934,6 +2941,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2934
2941
  this.preloadPoolLoopRunning = false;
2935
2942
  this.adDetectSentForCurrentBreak = false;
2936
2943
  this.adBreakEpoch = 0;
2944
+ this.adBreakPlayedDurationMs = 0;
2937
2945
  this.palPlaybackStarted = false;
2938
2946
  this.streamCorrelator = generateCorrelator();
2939
2947
  this.consentSignals = {};
@@ -3430,6 +3438,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3430
3438
  adIndex: _this.currentAdIndex,
3431
3439
  timestamp: /* @__PURE__ */ new Date().toISOString()
3432
3440
  }).catch(function() {});
3441
+ _this.recordPlayedAdDuration();
3433
3442
  var remaining = _this.getRemainingAdMs();
3434
3443
  _this.consecutiveFailures = 0;
3435
3444
  if (_this.config.debugAdTiming) {
@@ -3438,7 +3447,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3438
3447
  } : {});
3439
3448
  }
3440
3449
  if (_this.inAdBreak) {
3441
- if (remaining > 500) {
3450
+ if (_this.hasTimeToStartAnotherAd()) {
3442
3451
  _this.tryNextAvailableAdWithRateLimit();
3443
3452
  } else {
3444
3453
  _this.handleAdPodComplete();
@@ -3505,13 +3514,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3505
3514
  return;
3506
3515
  }
3507
3516
  _this.consecutiveFailures = 0;
3517
+ _this.recordPlayedAdDuration();
3508
3518
  var remaining = _this.getRemainingAdMs();
3509
3519
  if (_this.config.debugAdTiming) {
3510
3520
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
3511
3521
  queuedUrls: _to_consumable_array(_this.adRequestQueue)
3512
3522
  } : {});
3513
3523
  }
3514
- if (remaining > 500) {
3524
+ if (_this.hasTimeToStartAnotherAd()) {
3515
3525
  _this.tryNextAvailableAdWithRateLimit();
3516
3526
  } else {
3517
3527
  _this.handleAdPodComplete();
@@ -4211,7 +4221,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4211
4221
  var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
4212
4222
  this.scheduleAdStopCountdown(remainingMs1);
4213
4223
  }
4214
- if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
4224
+ if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0 && this.hasTimeToStartAnotherAd()) {
4215
4225
  this.tryNextAvailableAdWithRateLimit();
4216
4226
  }
4217
4227
  return;
@@ -5510,7 +5520,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5510
5520
  epoch = this.adBreakEpoch;
5511
5521
  loadPromise = function() {
5512
5522
  return _async_to_generator(function() {
5513
- var _this, hasAdError, adErrorPayload, errorListenerCleanup, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, lateErrorListener, error;
5523
+ var _this, hasAdError, adErrorPayload, errorListenerCleanup, _preloadAd_getLoadedAdDuration, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, loadedDuration, lateErrorListener, _poolEntry_durationSeconds, error;
5514
5524
  return _ts_generator(this, function(_state) {
5515
5525
  switch(_state.label){
5516
5526
  case 0:
@@ -5606,6 +5616,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5606
5616
  isReady: true,
5607
5617
  loadPromise: Promise.resolve()
5608
5618
  };
5619
+ loadedDuration = (_preloadAd_getLoadedAdDuration = preloadAd.getLoadedAdDuration) === null || _preloadAd_getLoadedAdDuration === void 0 ? void 0 : _preloadAd_getLoadedAdDuration.call(preloadAd);
5620
+ if (loadedDuration != null && loadedDuration > 0) {
5621
+ poolEntry.durationSeconds = loadedDuration;
5622
+ }
5609
5623
  lateErrorListener = function lateErrorListener(payload) {
5610
5624
  var index = _this.preloadPool.findIndex(function(entry) {
5611
5625
  return entry.vastUrl === vastUrl;
@@ -5623,7 +5637,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5623
5637
  preloadAd.on("ad_error", lateErrorListener);
5624
5638
  this.preloadPool.push(poolEntry);
5625
5639
  if (this.config.debugAdTiming) {
5626
- console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ")"));
5640
+ ;
5641
+ console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ", duration=").concat((_poolEntry_durationSeconds = poolEntry.durationSeconds) !== null && _poolEntry_durationSeconds !== void 0 ? _poolEntry_durationSeconds : "?", "s)"));
5627
5642
  }
5628
5643
  if (errorListenerCleanup) {
5629
5644
  errorListenerCleanup();
@@ -5670,7 +5685,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5670
5685
  key: "preloadPoolLoop",
5671
5686
  value: function preloadPoolLoop(baseVastUrl) {
5672
5687
  return _async_to_generator(function() {
5673
- var epoch, remaining, newUrls, newUrl;
5688
+ var epoch, newUrls, newUrl;
5674
5689
  return _ts_generator(this, function(_state) {
5675
5690
  switch(_state.label){
5676
5691
  case 0:
@@ -5737,14 +5752,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5737
5752
  8
5738
5753
  ];
5739
5754
  }
5740
- if (this.inAdBreak) {
5741
- remaining = this.getRemainingAdMs();
5742
- if (remaining <= 0) {
5743
- return [
5744
- 3,
5745
- 8
5746
- ];
5755
+ if (this.inAdBreak && !this.hasTimeToStartAnotherAd()) {
5756
+ if (this.config.debugAdTiming) {
5757
+ console.log("[PRELOAD-POOL] Not enough ad break time left, stopping preload");
5747
5758
  }
5759
+ return [
5760
+ 3,
5761
+ 8
5762
+ ];
5748
5763
  }
5749
5764
  newUrls = this.generateVastUrlsWithCorrelators(baseVastUrl, 1);
5750
5765
  newUrl = newUrls[0];
@@ -5904,8 +5919,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5904
5919
  }
5905
5920
  this.inAdBreak = true;
5906
5921
  this.currentAdBreakStartWallClockMs = Date.now();
5922
+ this.adBreakPlayedDurationMs = 0;
5907
5923
  this.currentAdIndex = 0;
5908
- this.totalAdsInBreak = 1;
5924
+ this.totalAdsInBreak = 0;
5909
5925
  this.adPodQueue = [];
5910
5926
  this.showAds = true;
5911
5927
  this.showPlaceholderLayer();
@@ -5959,14 +5975,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5959
5975
  case 1:
5960
5976
  _state.trys.push([
5961
5977
  1,
5962
- 8,
5978
+ 12,
5963
5979
  ,
5964
- 14
5980
+ 20
5965
5981
  ]);
5966
5982
  if (!(usePreloadedAd && preloadedController)) return [
5983
+ 3,
5984
+ 5
5985
+ ];
5986
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5967
5987
  3,
5968
5988
  3
5969
5989
  ];
5990
+ this.rejectLoadedAdForDuration(preloadedController);
5991
+ this.startContinuousFetching(baseVastUrl);
5992
+ return [
5993
+ 4,
5994
+ this.tryNextAvailableAdWithRateLimit()
5995
+ ];
5996
+ case 2:
5997
+ _state.sent();
5998
+ return [
5999
+ 2
6000
+ ];
6001
+ case 3:
5970
6002
  preservedMuted = this.adPlayer.getOriginalMutedState();
5971
6003
  preservedVolume = this.adPlayer.getOriginalVolume();
5972
6004
  this.adPlayer.destroy();
@@ -5979,7 +6011,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5979
6011
  }
5980
6012
  this.adPlayer.resume();
5981
6013
  this.consecutiveFailures = 0;
5982
- this.currentAdIndex++;
5983
6014
  this.startContinuousFetching(baseVastUrl);
5984
6015
  if (!this.preloadPoolActive) {
5985
6016
  this.preloadPoolActive = true;
@@ -5989,8 +6020,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5989
6020
  4,
5990
6021
  this.adPlayer.play()
5991
6022
  ];
5992
- case 2:
6023
+ case 4:
5993
6024
  _state.sent();
6025
+ this.markAdStarted();
5994
6026
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5995
6027
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5996
6028
  }
@@ -5998,21 +6030,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5998
6030
  this.adPlayer.setAdVolume(adVolume);
5999
6031
  return [
6000
6032
  3,
6001
- 7
6033
+ 11
6002
6034
  ];
6003
- case 3:
6035
+ case 5:
6004
6036
  return [
6005
6037
  4,
6006
6038
  this.enforceGlobalRateLimit()
6007
6039
  ];
6008
- case 4:
6040
+ case 6:
6009
6041
  _state.sent();
6010
6042
  this.lastAdRequestTime = Date.now();
6011
6043
  return [
6012
6044
  4,
6013
6045
  this.adPlayer.requestAds(firstAdUrl)
6014
6046
  ];
6015
- case 5:
6047
+ case 7:
6016
6048
  _state.sent();
6017
6049
  sendAdLoadedTracking(this.config.licenseKey, {
6018
6050
  source: "hls",
@@ -6022,8 +6054,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6022
6054
  if (this.config.debugAdTiming) {
6023
6055
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
6024
6056
  }
6057
+ if (!!this.ensureLoadedAdFitsBudget()) return [
6058
+ 3,
6059
+ 9
6060
+ ];
6061
+ this.rejectLoadedAdForDuration();
6062
+ this.recreateAdController();
6063
+ this.startContinuousFetching(baseVastUrl);
6064
+ return [
6065
+ 4,
6066
+ this.tryNextAvailableAdWithRateLimit()
6067
+ ];
6068
+ case 8:
6069
+ _state.sent();
6070
+ return [
6071
+ 2
6072
+ ];
6073
+ case 9:
6025
6074
  this.consecutiveFailures = 0;
6026
- this.currentAdIndex++;
6027
6075
  this.startContinuousFetching(baseVastUrl);
6028
6076
  if (!this.preloadPoolActive) {
6029
6077
  this.startPreloadPool(baseVastUrl, []);
@@ -6032,44 +6080,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6032
6080
  4,
6033
6081
  this.adPlayer.play()
6034
6082
  ];
6035
- case 6:
6083
+ case 10:
6036
6084
  _state.sent();
6085
+ this.markAdStarted();
6037
6086
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6038
6087
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6039
6088
  }
6040
6089
  adVolume1 = currentMuted ? 0 : currentVolume;
6041
6090
  this.adPlayer.setAdVolume(adVolume1);
6042
- _state.label = 7;
6043
- case 7:
6091
+ _state.label = 11;
6092
+ case 11:
6044
6093
  return [
6045
6094
  3,
6046
- 14
6095
+ 20
6047
6096
  ];
6048
- case 8:
6097
+ case 12:
6049
6098
  error = _state.sent();
6050
6099
  if (this.config.debugAdTiming) {
6051
6100
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
6052
6101
  }
6053
6102
  if (!!usePreloadedAd) return [
6054
6103
  3,
6055
- 12
6104
+ 18
6056
6105
  ];
6057
6106
  fallbackPreloaded = this.getPreloadedAd();
6058
6107
  if (!fallbackPreloaded) return [
6059
6108
  3,
6060
- 12
6109
+ 18
6061
6110
  ];
6062
6111
  if (this.config.debugAdTiming) {
6063
6112
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
6064
6113
  }
6065
- _state.label = 9;
6066
- case 9:
6114
+ _state.label = 13;
6115
+ case 13:
6067
6116
  _state.trys.push([
6068
- 9,
6069
- 11,
6117
+ 13,
6118
+ 17,
6070
6119
  ,
6071
- 12
6120
+ 18
6072
6121
  ]);
6122
+ if (!!this.ensureLoadedAdFitsBudget(fallbackPreloaded.adController)) return [
6123
+ 3,
6124
+ 15
6125
+ ];
6126
+ this.rejectLoadedAdForDuration(fallbackPreloaded.adController);
6127
+ this.startContinuousFetching(baseVastUrl);
6128
+ return [
6129
+ 4,
6130
+ this.tryNextAvailableAdWithRateLimit()
6131
+ ];
6132
+ case 14:
6133
+ _state.sent();
6134
+ return [
6135
+ 2
6136
+ ];
6137
+ case 15:
6073
6138
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
6074
6139
  preservedVolume1 = this.adPlayer.getOriginalVolume();
6075
6140
  this.adPlayer.destroy();
@@ -6079,7 +6144,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6079
6144
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
6080
6145
  this.adPlayer.resume();
6081
6146
  this.consecutiveFailures = 0;
6082
- this.currentAdIndex++;
6083
6147
  this.startContinuousFetching(baseVastUrl);
6084
6148
  if (!this.preloadPoolActive) {
6085
6149
  this.preloadPoolActive = true;
@@ -6089,8 +6153,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6089
6153
  4,
6090
6154
  this.adPlayer.play()
6091
6155
  ];
6092
- case 10:
6156
+ case 16:
6093
6157
  _state.sent();
6158
+ this.markAdStarted();
6094
6159
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6095
6160
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6096
6161
  }
@@ -6099,16 +6164,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6099
6164
  return [
6100
6165
  2
6101
6166
  ];
6102
- case 11:
6167
+ case 17:
6103
6168
  fallbackError = _state.sent();
6104
6169
  if (this.config.debugAdTiming) {
6105
6170
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
6106
6171
  }
6107
6172
  return [
6108
6173
  3,
6109
- 12
6174
+ 18
6110
6175
  ];
6111
- case 12:
6176
+ case 18:
6112
6177
  if (this.isTemporaryAdError(error)) {
6113
6178
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
6114
6179
  if (this.config.debugAdTiming) {
@@ -6126,13 +6191,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6126
6191
  4,
6127
6192
  this.tryNextAvailableAdWithRateLimit()
6128
6193
  ];
6129
- case 13:
6194
+ case 19:
6130
6195
  _state.sent();
6131
6196
  return [
6132
6197
  3,
6133
- 14
6198
+ 20
6134
6199
  ];
6135
- case 14:
6200
+ case 20:
6136
6201
  return [
6137
6202
  2
6138
6203
  ];
@@ -6173,10 +6238,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6173
6238
  return _ts_generator(this, function(_state) {
6174
6239
  switch(_state.label){
6175
6240
  case 0:
6176
- remaining = _this.getRemainingAdMs();
6177
- if (remaining <= 0) {
6241
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
6242
+ if (remaining <= _this.getMinRemainingMsToStartAd()) {
6178
6243
  if (_this.config.debugAdTiming) {
6179
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Ad break time expired, stopping URL generation");
6244
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (wall=".concat(_this.getWallClockRemainingAdMs(), "ms, budget=").concat(_this.getDurationBudgetRemainingMs(), "ms), stopping URL generation"));
6180
6245
  }
6181
6246
  return [
6182
6247
  2,
@@ -6258,7 +6323,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6258
6323
  });
6259
6324
  }
6260
6325
  _this.adRequestQueue.push(newAdUrl);
6261
- _this.totalAdsInBreak++;
6262
6326
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
6263
6327
  return [
6264
6328
  4,
@@ -6396,9 +6460,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6396
6460
  ];
6397
6461
  }
6398
6462
  remaining = this.getRemainingAdMs();
6399
- if (remaining <= 500) {
6463
+ if (!this.hasTimeToStartAnotherAd()) {
6400
6464
  if (this.config.debugAdTiming) {
6401
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F No time remaining, ending ad break");
6465
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough time remaining (".concat(remaining, "ms) to start another ad, ending ad break"));
6402
6466
  }
6403
6467
  this.handleAdPodComplete();
6404
6468
  return [
@@ -6427,7 +6491,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6427
6491
  preloaded = this.getPreloadedAd();
6428
6492
  if (!preloaded) return [
6429
6493
  3,
6430
- 4
6494
+ 6
6431
6495
  ];
6432
6496
  if (this.config.debugAdTiming) {
6433
6497
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6441,10 +6505,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6441
6505
  case 1:
6442
6506
  _state.trys.push([
6443
6507
  1,
6444
- 3,
6508
+ 5,
6445
6509
  ,
6446
- 4
6510
+ 6
6447
6511
  ]);
6512
+ if (!!this.ensureLoadedAdFitsBudget(preloaded.adController)) return [
6513
+ 3,
6514
+ 3
6515
+ ];
6516
+ this.rejectLoadedAdForDuration(preloaded.adController);
6517
+ return [
6518
+ 4,
6519
+ this.tryNextAvailableAdWithRateLimit()
6520
+ ];
6521
+ case 2:
6522
+ _state.sent();
6523
+ return [
6524
+ 2
6525
+ ];
6526
+ case 3:
6448
6527
  preservedMuted = this.adPlayer.getOriginalMutedState();
6449
6528
  preservedVolume = this.adPlayer.getOriginalVolume();
6450
6529
  this.adPlayer.destroy();
@@ -6453,15 +6532,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6453
6532
  this.attachImaEventListeners();
6454
6533
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6455
6534
  this.adPlayer.resume();
6456
- this.currentAdIndex++;
6457
- this.totalAdRequestsInBreak++;
6458
- this.lastAdRequestTime = Date.now();
6459
6535
  return [
6460
6536
  4,
6461
6537
  this.adPlayer.play()
6462
6538
  ];
6463
- case 2:
6539
+ case 4:
6464
6540
  _state.sent();
6541
+ this.markAdStarted();
6465
6542
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6466
6543
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6467
6544
  }
@@ -6470,25 +6547,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6470
6547
  return [
6471
6548
  2
6472
6549
  ];
6473
- case 3:
6550
+ case 5:
6474
6551
  error = _state.sent();
6475
6552
  if (this.config.debugAdTiming) {
6476
6553
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6477
6554
  }
6478
6555
  return [
6479
6556
  3,
6480
- 4
6557
+ 6
6481
6558
  ];
6482
- case 4:
6559
+ case 6:
6483
6560
  if (!(this.adRequestQueue.length > 0)) return [
6484
6561
  3,
6485
- 10
6562
+ 14
6486
6563
  ];
6487
6564
  nextAdUrl = this.adRequestQueue.shift();
6488
6565
  if (!nextAdUrl) {
6489
6566
  return [
6490
6567
  3,
6491
- 4
6568
+ 6
6492
6569
  ];
6493
6570
  }
6494
6571
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6497,7 +6574,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6497
6574
  }
6498
6575
  return [
6499
6576
  3,
6500
- 4
6577
+ 6
6501
6578
  ];
6502
6579
  }
6503
6580
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6506,40 +6583,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6506
6583
  }
6507
6584
  return [
6508
6585
  3,
6509
- 4
6586
+ 6
6510
6587
  ];
6511
6588
  }
6512
6589
  if (this.config.debugAdTiming) {
6513
- console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, "/").concat(this.totalAdsInBreak, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6590
+ console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6514
6591
  }
6515
- this.currentAdIndex++;
6516
- this.totalAdRequestsInBreak++;
6517
- this.lastAdRequestTime = Date.now();
6518
- _state.label = 5;
6519
- case 5:
6592
+ _state.label = 7;
6593
+ case 7:
6520
6594
  _state.trys.push([
6521
- 5,
6522
6595
  7,
6596
+ 9,
6523
6597
  ,
6524
- 9
6598
+ 13
6525
6599
  ]);
6526
6600
  return [
6527
6601
  4,
6528
6602
  this.playSingleAd(nextAdUrl)
6529
6603
  ];
6530
- case 6:
6604
+ case 8:
6531
6605
  _state.sent();
6532
6606
  this.consecutiveFailures = 0;
6533
6607
  return [
6534
6608
  3,
6535
- 9
6609
+ 13
6536
6610
  ];
6537
- case 7:
6611
+ case 9:
6538
6612
  error1 = _state.sent();
6539
6613
  errorMessage = error1.message;
6540
6614
  if (this.config.debugAdTiming) {
6541
6615
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6542
6616
  }
6617
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6618
+ 3,
6619
+ 11
6620
+ ];
6621
+ return [
6622
+ 4,
6623
+ this.tryNextAvailableAdWithRateLimit()
6624
+ ];
6625
+ case 10:
6626
+ _state.sent();
6627
+ return [
6628
+ 2
6629
+ ];
6630
+ case 11:
6543
6631
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6544
6632
  this.consecutiveFailures++;
6545
6633
  }
@@ -6547,21 +6635,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6547
6635
  4,
6548
6636
  this.tryNextAvailableAdWithRateLimit()
6549
6637
  ];
6550
- case 8:
6638
+ case 12:
6551
6639
  _state.sent();
6552
6640
  return [
6553
6641
  3,
6554
- 9
6642
+ 13
6555
6643
  ];
6556
- case 9:
6644
+ case 13:
6557
6645
  return [
6558
6646
  2
6559
6647
  ];
6560
- case 10:
6648
+ case 14:
6561
6649
  maxRetries = 3;
6562
6650
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6563
6651
  3,
6564
- 13
6652
+ 17
6565
6653
  ];
6566
6654
  if (this.config.debugAdTiming) {
6567
6655
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6572,18 +6660,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6572
6660
  return setTimeout(resolve, 500);
6573
6661
  })
6574
6662
  ];
6575
- case 11:
6663
+ case 15:
6576
6664
  _state.sent();
6577
6665
  return [
6578
6666
  4,
6579
6667
  this.tryNextAvailableAd(retryCount + 1)
6580
6668
  ];
6581
- case 12:
6669
+ case 16:
6582
6670
  _state.sent();
6583
6671
  return [
6584
6672
  2
6585
6673
  ];
6586
- case 13:
6674
+ case 17:
6587
6675
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6588
6676
  this.showPlaceholderAndWaitForAds();
6589
6677
  } else {
@@ -6646,7 +6734,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6646
6734
  case 1:
6647
6735
  if (!(i < maxChecks)) return [
6648
6736
  3,
6649
- 10
6737
+ 12
6650
6738
  ];
6651
6739
  return [
6652
6740
  4,
@@ -6667,15 +6755,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6667
6755
  }
6668
6756
  return [
6669
6757
  3,
6670
- 10
6758
+ 12
6759
+ ];
6760
+ }
6761
+ if (!this.hasTimeToStartAnotherAd()) {
6762
+ if (this.config.debugAdTiming) {
6763
+ console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Not enough time remaining during placeholder wait, ending ad break");
6764
+ }
6765
+ return [
6766
+ 3,
6767
+ 12
6671
6768
  ];
6672
6769
  }
6673
6770
  _state.label = 3;
6674
6771
  case 3:
6675
6772
  if (!(this.adRequestQueue.length > 0)) return [
6676
6773
  3,
6677
- 9
6774
+ 11
6678
6775
  ];
6776
+ if (!this.hasTimeToStartAnotherAd()) {
6777
+ return [
6778
+ 3,
6779
+ 11
6780
+ ];
6781
+ }
6679
6782
  nextAdUrl = this.adRequestQueue.shift();
6680
6783
  if (!nextAdUrl) {
6681
6784
  return [
@@ -6700,16 +6803,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6700
6803
  }
6701
6804
  this.isShowingPlaceholder = false;
6702
6805
  this.adPlayer.hidePlaceholder();
6703
- this.currentAdIndex++;
6704
- this.totalAdRequestsInBreak++;
6705
- this.lastAdRequestTime = Date.now();
6706
6806
  _state.label = 4;
6707
6807
  case 4:
6708
6808
  _state.trys.push([
6709
6809
  4,
6710
6810
  6,
6711
6811
  ,
6712
- 8
6812
+ 10
6713
6813
  ]);
6714
6814
  return [
6715
6815
  4,
@@ -6720,11 +6820,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6720
6820
  this.consecutiveFailures = 0;
6721
6821
  return [
6722
6822
  3,
6723
- 8
6823
+ 10
6724
6824
  ];
6725
6825
  case 6:
6726
6826
  error = _state.sent();
6727
6827
  errorMessage = error.message;
6828
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6829
+ 3,
6830
+ 8
6831
+ ];
6832
+ return [
6833
+ 4,
6834
+ this.tryNextAvailableAdWithRateLimit()
6835
+ ];
6836
+ case 7:
6837
+ _state.sent();
6838
+ return [
6839
+ 2
6840
+ ];
6841
+ case 8:
6728
6842
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6729
6843
  this.consecutiveFailures++;
6730
6844
  }
@@ -6732,23 +6846,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6732
6846
  4,
6733
6847
  this.tryNextAvailableAdWithRateLimit()
6734
6848
  ];
6735
- case 7:
6849
+ case 9:
6736
6850
  _state.sent();
6737
6851
  return [
6738
6852
  3,
6739
- 8
6853
+ 10
6740
6854
  ];
6741
- case 8:
6855
+ case 10:
6742
6856
  return [
6743
6857
  2
6744
6858
  ];
6745
- case 9:
6859
+ case 11:
6746
6860
  i++;
6747
6861
  return [
6748
6862
  3,
6749
6863
  1
6750
6864
  ];
6751
- case 10:
6865
+ case 12:
6752
6866
  if (this.config.debugAdTiming) {
6753
6867
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6754
6868
  }
@@ -6958,21 +7072,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6958
7072
  if (!this.inAdBreak) return;
6959
7073
  this.adStopTimerId = void 0;
6960
7074
  var adPlaying = this.adPlayer.isAdPlaying();
6961
- var pendingAds = this.adPodQueue.length > 0;
6962
7075
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
6963
7076
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
6964
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
7077
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
6965
7078
  var elapsedSinceStartMs = 0;
6966
7079
  if (this.currentAdBreakStartWallClockMs != null) {
6967
7080
  elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
6968
7081
  }
6969
7082
  var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
6970
7083
  var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
6971
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
7084
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
6972
7085
  if (shouldExtendAdBreak) {
6973
7086
  this.scheduleAdStopCountdown(checkIntervalMs);
6974
7087
  return;
6975
7088
  }
7089
+ if (this.config.debugAdTiming) {
7090
+ console.log("[StormcloudVideoPlayer] ⏱️ Ad break boundary reached (overrun=".concat(overrunMs, "ms) - hard-stopping ads and resuming stream"));
7091
+ }
6976
7092
  if (adPlaying) {
6977
7093
  this.adPlayer.stop().catch(function() {});
6978
7094
  }
@@ -7218,6 +7334,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7218
7334
  2
7219
7335
  ];
7220
7336
  }
7337
+ if (!this.ensureLoadedAdFitsBudget()) {
7338
+ this.rejectLoadedAdForDuration();
7339
+ this.recreateAdController();
7340
+ throw new Error("ad_duration_exceeds_budget");
7341
+ }
7221
7342
  _state.label = 4;
7222
7343
  case 4:
7223
7344
  _state.trys.push([
@@ -7233,6 +7354,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7233
7354
  ];
7234
7355
  case 5:
7235
7356
  _state.sent();
7357
+ this.markAdStarted();
7236
7358
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7237
7359
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7238
7360
  }
@@ -7264,6 +7386,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7264
7386
  ,
7265
7387
  10
7266
7388
  ]);
7389
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7390
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7391
+ throw playError;
7392
+ }
7267
7393
  this.clearAdFailsafeTimer();
7268
7394
  preservedMuted = this.adPlayer.getOriginalMutedState();
7269
7395
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -7274,14 +7400,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7274
7400
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
7275
7401
  this.adPlayer.resume();
7276
7402
  this.consecutiveFailures = 0;
7277
- this.currentAdIndex++;
7278
- this.totalAdRequestsInBreak++;
7279
7403
  return [
7280
7404
  4,
7281
7405
  this.adPlayer.play()
7282
7406
  ];
7283
7407
  case 8:
7284
7408
  _state.sent();
7409
+ this.markAdStarted();
7285
7410
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7286
7411
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7287
7412
  }
@@ -7342,6 +7467,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7342
7467
  ,
7343
7468
  16
7344
7469
  ]);
7470
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7471
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7472
+ throw error;
7473
+ }
7345
7474
  this.clearAdRequestWatchdog();
7346
7475
  this.clearAdFailsafeTimer();
7347
7476
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7353,14 +7482,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7353
7482
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7354
7483
  this.adPlayer.resume();
7355
7484
  this.consecutiveFailures = 0;
7356
- this.currentAdIndex++;
7357
- this.totalAdRequestsInBreak++;
7358
7485
  return [
7359
7486
  4,
7360
7487
  this.adPlayer.play()
7361
7488
  ];
7362
7489
  case 14:
7363
7490
  _state.sent();
7491
+ this.markAdStarted();
7364
7492
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7365
7493
  return [
7366
7494
  2
@@ -7421,6 +7549,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7421
7549
  this.adRequestQueue = [];
7422
7550
  this.inAdBreak = false;
7423
7551
  this.adDetectSentForCurrentBreak = false;
7552
+ this.adBreakPlayedDurationMs = 0;
7424
7553
  this.activeScte35BreakKey = void 0;
7425
7554
  this.scheduledScte35BreakKey = void 0;
7426
7555
  this.expectedAdBreakDurationMs = void 0;
@@ -7478,11 +7607,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7478
7607
  this.handleAdPodComplete();
7479
7608
  return;
7480
7609
  }
7481
- if (remaining > 500) {
7610
+ if (this.hasTimeToStartAnotherAd()) {
7482
7611
  this.tryNextAvailableAdWithRateLimit();
7483
7612
  } else {
7484
7613
  if (this.config.debugAdTiming) {
7485
- console.log("[AD-ERROR] Ad failed and no time remaining. Failed URLs:", this.failedVastUrls.size);
7614
+ console.log("[AD-ERROR] Ad failed and not enough time remaining for another ad. Failed URLs:", this.failedVastUrls.size);
7486
7615
  }
7487
7616
  this.handleAdPodComplete();
7488
7617
  }
@@ -7610,13 +7739,134 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7610
7739
  }
7611
7740
  },
7612
7741
  {
7613
- key: "getRemainingAdMs",
7614
- value: function getRemainingAdMs() {
7615
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7742
+ key: "getWallClockRemainingAdMs",
7743
+ value: function getWallClockRemainingAdMs() {
7744
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {
7745
+ return 0;
7746
+ }
7616
7747
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7617
7748
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7618
7749
  }
7619
7750
  },
7751
+ {
7752
+ key: "getDurationBudgetRemainingMs",
7753
+ value: function getDurationBudgetRemainingMs() {
7754
+ if (this.expectedAdBreakDurationMs == null) {
7755
+ return 0;
7756
+ }
7757
+ return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);
7758
+ }
7759
+ },
7760
+ {
7761
+ key: "getLoadedAdDurationMs",
7762
+ value: function getLoadedAdDurationMs() {
7763
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7764
+ var _adController_getLoadedAdDuration;
7765
+ var durationSec = (_adController_getLoadedAdDuration = adController.getLoadedAdDuration) === null || _adController_getLoadedAdDuration === void 0 ? void 0 : _adController_getLoadedAdDuration.call(adController);
7766
+ if (durationSec == null || durationSec <= 0) {
7767
+ return 0;
7768
+ }
7769
+ return Math.round(durationSec * 1e3);
7770
+ }
7771
+ },
7772
+ {
7773
+ key: "adFitsRemainingBudget",
7774
+ value: function adFitsRemainingBudget(durationMs) {
7775
+ var adController = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.adPlayer;
7776
+ if (durationMs <= 0) {
7777
+ return true;
7778
+ }
7779
+ return durationMs <= this.getDurationBudgetRemainingMs();
7780
+ }
7781
+ },
7782
+ {
7783
+ key: "logAdDurationDecision",
7784
+ value: function logAdDurationDecision(accepted, durationMs) {
7785
+ var adController = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : this.adPlayer;
7786
+ var _ref;
7787
+ var _adController_getLoadedAdId;
7788
+ if (!this.config.debugAdTiming) {
7789
+ return;
7790
+ }
7791
+ var remainingBudget = this.getDurationBudgetRemainingMs();
7792
+ var adId = (_ref = (_adController_getLoadedAdId = adController.getLoadedAdId) === null || _adController_getLoadedAdId === void 0 ? void 0 : _adController_getLoadedAdId.call(adController)) !== null && _ref !== void 0 ? _ref : "unknown";
7793
+ if (accepted) {
7794
+ console.log("[AD-DURATION] Accepted ad id=".concat(adId, ", duration=").concat(durationMs, "ms, budget remaining after play=").concat(Math.max(0, remainingBudget - durationMs), "ms"));
7795
+ } else {
7796
+ console.log("[AD-DURATION] Rejected ad id=".concat(adId, ", duration=").concat(durationMs, "ms exceeds remaining budget=").concat(remainingBudget, "ms — requesting another ad"));
7797
+ }
7798
+ }
7799
+ },
7800
+ {
7801
+ key: "recordPlayedAdDuration",
7802
+ value: function recordPlayedAdDuration() {
7803
+ var durationMs = this.getLoadedAdDurationMs();
7804
+ if (durationMs <= 0) {
7805
+ return;
7806
+ }
7807
+ this.adBreakPlayedDurationMs += durationMs;
7808
+ if (this.config.debugAdTiming) {
7809
+ var _this_expectedAdBreakDurationMs;
7810
+ console.log("[AD-DURATION] Played ".concat(durationMs, "ms — total played=").concat(this.adBreakPlayedDurationMs, "ms / ").concat((_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : "?", "ms"));
7811
+ }
7812
+ }
7813
+ },
7814
+ {
7815
+ key: "ensureLoadedAdFitsBudget",
7816
+ value: function ensureLoadedAdFitsBudget() {
7817
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7818
+ var durationMs = this.getLoadedAdDurationMs(adController);
7819
+ var fits = this.adFitsRemainingBudget(durationMs, adController);
7820
+ this.logAdDurationDecision(fits, durationMs, adController);
7821
+ return fits;
7822
+ }
7823
+ },
7824
+ {
7825
+ key: "rejectLoadedAdForDuration",
7826
+ value: function rejectLoadedAdForDuration() {
7827
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7828
+ try {
7829
+ adController.destroy();
7830
+ } catch (unused) {}
7831
+ }
7832
+ },
7833
+ {
7834
+ key: "markAdStarted",
7835
+ value: function markAdStarted() {
7836
+ this.currentAdIndex++;
7837
+ this.totalAdsInBreak = this.currentAdIndex;
7838
+ this.totalAdRequestsInBreak++;
7839
+ this.lastAdRequestTime = Date.now();
7840
+ }
7841
+ },
7842
+ {
7843
+ key: "getRemainingAdMs",
7844
+ value: function getRemainingAdMs() {
7845
+ var wallClockRemaining = this.getWallClockRemainingAdMs();
7846
+ var budgetRemaining = this.getDurationBudgetRemainingMs();
7847
+ if (this.expectedAdBreakDurationMs == null) {
7848
+ return wallClockRemaining;
7849
+ }
7850
+ return Math.min(wallClockRemaining, budgetRemaining);
7851
+ }
7852
+ },
7853
+ {
7854
+ key: "getMinRemainingMsToStartAd",
7855
+ value: function getMinRemainingMsToStartAd() {
7856
+ var configured = this.config.minRemainingMsToStartAd;
7857
+ if (typeof configured === "number" && configured >= 0) {
7858
+ return configured;
7859
+ }
7860
+ return this.defaultMinRemainingMsToStartAd;
7861
+ }
7862
+ },
7863
+ {
7864
+ key: "hasTimeToStartAnotherAd",
7865
+ value: function hasTimeToStartAnotherAd() {
7866
+ var minRemaining = this.getMinRemainingMsToStartAd();
7867
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7868
+ }
7869
+ },
7620
7870
  {
7621
7871
  key: "findBreakForTime",
7622
7872
  value: function findBreakForTime(nowMs) {