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.
@@ -1292,6 +1292,12 @@ function createHlsAdPlayer(contentVideo, options) {
1292
1292
  contentVideo.style.visibility = "visible";
1293
1293
  contentVideo.style.opacity = "1";
1294
1294
  }
1295
+ },
1296
+ getLoadedAdDuration: function getLoadedAdDuration() {
1297
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration;
1298
+ },
1299
+ getLoadedAdId: function getLoadedAdId() {
1300
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.id;
1295
1301
  }
1296
1302
  };
1297
1303
  }
@@ -2688,6 +2694,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2688
2694
  this.maxConsecutiveFailures = 5;
2689
2695
  this.lastAdRequestTime = 0;
2690
2696
  this.minAdRequestIntervalMs = 3e3;
2697
+ this.defaultMinRemainingMsToStartAd = 4e3;
2691
2698
  this.backoffBaseMs = 1e3;
2692
2699
  this.maxBackoffMs = 15e3;
2693
2700
  this.globalConsecutiveNoFills = 0;
@@ -2703,6 +2710,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2703
2710
  this.preloadPoolLoopRunning = false;
2704
2711
  this.adDetectSentForCurrentBreak = false;
2705
2712
  this.adBreakEpoch = 0;
2713
+ this.adBreakPlayedDurationMs = 0;
2706
2714
  this.palPlaybackStarted = false;
2707
2715
  this.streamCorrelator = generateCorrelator();
2708
2716
  this.consentSignals = {};
@@ -3199,6 +3207,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3199
3207
  adIndex: _this.currentAdIndex,
3200
3208
  timestamp: /* @__PURE__ */ new Date().toISOString()
3201
3209
  }).catch(function() {});
3210
+ _this.recordPlayedAdDuration();
3202
3211
  var remaining = _this.getRemainingAdMs();
3203
3212
  _this.consecutiveFailures = 0;
3204
3213
  if (_this.config.debugAdTiming) {
@@ -3207,7 +3216,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3207
3216
  } : {});
3208
3217
  }
3209
3218
  if (_this.inAdBreak) {
3210
- if (remaining > 500) {
3219
+ if (_this.hasTimeToStartAnotherAd()) {
3211
3220
  _this.tryNextAvailableAdWithRateLimit();
3212
3221
  } else {
3213
3222
  _this.handleAdPodComplete();
@@ -3274,13 +3283,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3274
3283
  return;
3275
3284
  }
3276
3285
  _this.consecutiveFailures = 0;
3286
+ _this.recordPlayedAdDuration();
3277
3287
  var remaining = _this.getRemainingAdMs();
3278
3288
  if (_this.config.debugAdTiming) {
3279
3289
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
3280
3290
  queuedUrls: _to_consumable_array(_this.adRequestQueue)
3281
3291
  } : {});
3282
3292
  }
3283
- if (remaining > 500) {
3293
+ if (_this.hasTimeToStartAnotherAd()) {
3284
3294
  _this.tryNextAvailableAdWithRateLimit();
3285
3295
  } else {
3286
3296
  _this.handleAdPodComplete();
@@ -3980,7 +3990,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3980
3990
  var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
3981
3991
  this.scheduleAdStopCountdown(remainingMs1);
3982
3992
  }
3983
- if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
3993
+ if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0 && this.hasTimeToStartAnotherAd()) {
3984
3994
  this.tryNextAvailableAdWithRateLimit();
3985
3995
  }
3986
3996
  return;
@@ -5279,7 +5289,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5279
5289
  epoch = this.adBreakEpoch;
5280
5290
  loadPromise = function() {
5281
5291
  return _async_to_generator(function() {
5282
- var _this, hasAdError, adErrorPayload, errorListenerCleanup, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, lateErrorListener, error;
5292
+ var _this, hasAdError, adErrorPayload, errorListenerCleanup, _preloadAd_getLoadedAdDuration, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, loadedDuration, lateErrorListener, _poolEntry_durationSeconds, error;
5283
5293
  return _ts_generator(this, function(_state) {
5284
5294
  switch(_state.label){
5285
5295
  case 0:
@@ -5375,6 +5385,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5375
5385
  isReady: true,
5376
5386
  loadPromise: Promise.resolve()
5377
5387
  };
5388
+ loadedDuration = (_preloadAd_getLoadedAdDuration = preloadAd.getLoadedAdDuration) === null || _preloadAd_getLoadedAdDuration === void 0 ? void 0 : _preloadAd_getLoadedAdDuration.call(preloadAd);
5389
+ if (loadedDuration != null && loadedDuration > 0) {
5390
+ poolEntry.durationSeconds = loadedDuration;
5391
+ }
5378
5392
  lateErrorListener = function lateErrorListener(payload) {
5379
5393
  var index = _this.preloadPool.findIndex(function(entry) {
5380
5394
  return entry.vastUrl === vastUrl;
@@ -5392,7 +5406,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5392
5406
  preloadAd.on("ad_error", lateErrorListener);
5393
5407
  this.preloadPool.push(poolEntry);
5394
5408
  if (this.config.debugAdTiming) {
5395
- console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ")"));
5409
+ ;
5410
+ 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)"));
5396
5411
  }
5397
5412
  if (errorListenerCleanup) {
5398
5413
  errorListenerCleanup();
@@ -5439,7 +5454,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5439
5454
  key: "preloadPoolLoop",
5440
5455
  value: function preloadPoolLoop(baseVastUrl) {
5441
5456
  return _async_to_generator(function() {
5442
- var epoch, remaining, newUrls, newUrl;
5457
+ var epoch, newUrls, newUrl;
5443
5458
  return _ts_generator(this, function(_state) {
5444
5459
  switch(_state.label){
5445
5460
  case 0:
@@ -5506,14 +5521,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5506
5521
  8
5507
5522
  ];
5508
5523
  }
5509
- if (this.inAdBreak) {
5510
- remaining = this.getRemainingAdMs();
5511
- if (remaining <= 0) {
5512
- return [
5513
- 3,
5514
- 8
5515
- ];
5524
+ if (this.inAdBreak && !this.hasTimeToStartAnotherAd()) {
5525
+ if (this.config.debugAdTiming) {
5526
+ console.log("[PRELOAD-POOL] Not enough ad break time left, stopping preload");
5516
5527
  }
5528
+ return [
5529
+ 3,
5530
+ 8
5531
+ ];
5517
5532
  }
5518
5533
  newUrls = this.generateVastUrlsWithCorrelators(baseVastUrl, 1);
5519
5534
  newUrl = newUrls[0];
@@ -5673,8 +5688,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5673
5688
  }
5674
5689
  this.inAdBreak = true;
5675
5690
  this.currentAdBreakStartWallClockMs = Date.now();
5691
+ this.adBreakPlayedDurationMs = 0;
5676
5692
  this.currentAdIndex = 0;
5677
- this.totalAdsInBreak = 1;
5693
+ this.totalAdsInBreak = 0;
5678
5694
  this.adPodQueue = [];
5679
5695
  this.showAds = true;
5680
5696
  this.showPlaceholderLayer();
@@ -5728,14 +5744,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5728
5744
  case 1:
5729
5745
  _state.trys.push([
5730
5746
  1,
5731
- 8,
5747
+ 12,
5732
5748
  ,
5733
- 14
5749
+ 20
5734
5750
  ]);
5735
5751
  if (!(usePreloadedAd && preloadedController)) return [
5752
+ 3,
5753
+ 5
5754
+ ];
5755
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5736
5756
  3,
5737
5757
  3
5738
5758
  ];
5759
+ this.rejectLoadedAdForDuration(preloadedController);
5760
+ this.startContinuousFetching(baseVastUrl);
5761
+ return [
5762
+ 4,
5763
+ this.tryNextAvailableAdWithRateLimit()
5764
+ ];
5765
+ case 2:
5766
+ _state.sent();
5767
+ return [
5768
+ 2
5769
+ ];
5770
+ case 3:
5739
5771
  preservedMuted = this.adPlayer.getOriginalMutedState();
5740
5772
  preservedVolume = this.adPlayer.getOriginalVolume();
5741
5773
  this.adPlayer.destroy();
@@ -5748,7 +5780,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5748
5780
  }
5749
5781
  this.adPlayer.resume();
5750
5782
  this.consecutiveFailures = 0;
5751
- this.currentAdIndex++;
5752
5783
  this.startContinuousFetching(baseVastUrl);
5753
5784
  if (!this.preloadPoolActive) {
5754
5785
  this.preloadPoolActive = true;
@@ -5758,8 +5789,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5758
5789
  4,
5759
5790
  this.adPlayer.play()
5760
5791
  ];
5761
- case 2:
5792
+ case 4:
5762
5793
  _state.sent();
5794
+ this.markAdStarted();
5763
5795
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5764
5796
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5765
5797
  }
@@ -5767,21 +5799,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5767
5799
  this.adPlayer.setAdVolume(adVolume);
5768
5800
  return [
5769
5801
  3,
5770
- 7
5802
+ 11
5771
5803
  ];
5772
- case 3:
5804
+ case 5:
5773
5805
  return [
5774
5806
  4,
5775
5807
  this.enforceGlobalRateLimit()
5776
5808
  ];
5777
- case 4:
5809
+ case 6:
5778
5810
  _state.sent();
5779
5811
  this.lastAdRequestTime = Date.now();
5780
5812
  return [
5781
5813
  4,
5782
5814
  this.adPlayer.requestAds(firstAdUrl)
5783
5815
  ];
5784
- case 5:
5816
+ case 7:
5785
5817
  _state.sent();
5786
5818
  sendAdLoadedTracking(this.config.licenseKey, {
5787
5819
  source: "hls",
@@ -5791,8 +5823,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5791
5823
  if (this.config.debugAdTiming) {
5792
5824
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
5793
5825
  }
5826
+ if (!!this.ensureLoadedAdFitsBudget()) return [
5827
+ 3,
5828
+ 9
5829
+ ];
5830
+ this.rejectLoadedAdForDuration();
5831
+ this.recreateAdController();
5832
+ this.startContinuousFetching(baseVastUrl);
5833
+ return [
5834
+ 4,
5835
+ this.tryNextAvailableAdWithRateLimit()
5836
+ ];
5837
+ case 8:
5838
+ _state.sent();
5839
+ return [
5840
+ 2
5841
+ ];
5842
+ case 9:
5794
5843
  this.consecutiveFailures = 0;
5795
- this.currentAdIndex++;
5796
5844
  this.startContinuousFetching(baseVastUrl);
5797
5845
  if (!this.preloadPoolActive) {
5798
5846
  this.startPreloadPool(baseVastUrl, []);
@@ -5801,44 +5849,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5801
5849
  4,
5802
5850
  this.adPlayer.play()
5803
5851
  ];
5804
- case 6:
5852
+ case 10:
5805
5853
  _state.sent();
5854
+ this.markAdStarted();
5806
5855
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5807
5856
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5808
5857
  }
5809
5858
  adVolume1 = currentMuted ? 0 : currentVolume;
5810
5859
  this.adPlayer.setAdVolume(adVolume1);
5811
- _state.label = 7;
5812
- case 7:
5860
+ _state.label = 11;
5861
+ case 11:
5813
5862
  return [
5814
5863
  3,
5815
- 14
5864
+ 20
5816
5865
  ];
5817
- case 8:
5866
+ case 12:
5818
5867
  error = _state.sent();
5819
5868
  if (this.config.debugAdTiming) {
5820
5869
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
5821
5870
  }
5822
5871
  if (!!usePreloadedAd) return [
5823
5872
  3,
5824
- 12
5873
+ 18
5825
5874
  ];
5826
5875
  fallbackPreloaded = this.getPreloadedAd();
5827
5876
  if (!fallbackPreloaded) return [
5828
5877
  3,
5829
- 12
5878
+ 18
5830
5879
  ];
5831
5880
  if (this.config.debugAdTiming) {
5832
5881
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
5833
5882
  }
5834
- _state.label = 9;
5835
- case 9:
5883
+ _state.label = 13;
5884
+ case 13:
5836
5885
  _state.trys.push([
5837
- 9,
5838
- 11,
5886
+ 13,
5887
+ 17,
5839
5888
  ,
5840
- 12
5889
+ 18
5841
5890
  ]);
5891
+ if (!!this.ensureLoadedAdFitsBudget(fallbackPreloaded.adController)) return [
5892
+ 3,
5893
+ 15
5894
+ ];
5895
+ this.rejectLoadedAdForDuration(fallbackPreloaded.adController);
5896
+ this.startContinuousFetching(baseVastUrl);
5897
+ return [
5898
+ 4,
5899
+ this.tryNextAvailableAdWithRateLimit()
5900
+ ];
5901
+ case 14:
5902
+ _state.sent();
5903
+ return [
5904
+ 2
5905
+ ];
5906
+ case 15:
5842
5907
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
5843
5908
  preservedVolume1 = this.adPlayer.getOriginalVolume();
5844
5909
  this.adPlayer.destroy();
@@ -5848,7 +5913,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5848
5913
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
5849
5914
  this.adPlayer.resume();
5850
5915
  this.consecutiveFailures = 0;
5851
- this.currentAdIndex++;
5852
5916
  this.startContinuousFetching(baseVastUrl);
5853
5917
  if (!this.preloadPoolActive) {
5854
5918
  this.preloadPoolActive = true;
@@ -5858,8 +5922,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5858
5922
  4,
5859
5923
  this.adPlayer.play()
5860
5924
  ];
5861
- case 10:
5925
+ case 16:
5862
5926
  _state.sent();
5927
+ this.markAdStarted();
5863
5928
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5864
5929
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5865
5930
  }
@@ -5868,16 +5933,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5868
5933
  return [
5869
5934
  2
5870
5935
  ];
5871
- case 11:
5936
+ case 17:
5872
5937
  fallbackError = _state.sent();
5873
5938
  if (this.config.debugAdTiming) {
5874
5939
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
5875
5940
  }
5876
5941
  return [
5877
5942
  3,
5878
- 12
5943
+ 18
5879
5944
  ];
5880
- case 12:
5945
+ case 18:
5881
5946
  if (this.isTemporaryAdError(error)) {
5882
5947
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
5883
5948
  if (this.config.debugAdTiming) {
@@ -5895,13 +5960,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5895
5960
  4,
5896
5961
  this.tryNextAvailableAdWithRateLimit()
5897
5962
  ];
5898
- case 13:
5963
+ case 19:
5899
5964
  _state.sent();
5900
5965
  return [
5901
5966
  3,
5902
- 14
5967
+ 20
5903
5968
  ];
5904
- case 14:
5969
+ case 20:
5905
5970
  return [
5906
5971
  2
5907
5972
  ];
@@ -5942,10 +6007,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5942
6007
  return _ts_generator(this, function(_state) {
5943
6008
  switch(_state.label){
5944
6009
  case 0:
5945
- remaining = _this.getRemainingAdMs();
5946
- if (remaining <= 0) {
6010
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
6011
+ if (remaining <= _this.getMinRemainingMsToStartAd()) {
5947
6012
  if (_this.config.debugAdTiming) {
5948
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Ad break time expired, stopping URL generation");
6013
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (wall=".concat(_this.getWallClockRemainingAdMs(), "ms, budget=").concat(_this.getDurationBudgetRemainingMs(), "ms), stopping URL generation"));
5949
6014
  }
5950
6015
  return [
5951
6016
  2,
@@ -6027,7 +6092,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6027
6092
  });
6028
6093
  }
6029
6094
  _this.adRequestQueue.push(newAdUrl);
6030
- _this.totalAdsInBreak++;
6031
6095
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
6032
6096
  return [
6033
6097
  4,
@@ -6165,9 +6229,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6165
6229
  ];
6166
6230
  }
6167
6231
  remaining = this.getRemainingAdMs();
6168
- if (remaining <= 500) {
6232
+ if (!this.hasTimeToStartAnotherAd()) {
6169
6233
  if (this.config.debugAdTiming) {
6170
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F No time remaining, ending ad break");
6234
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough time remaining (".concat(remaining, "ms) to start another ad, ending ad break"));
6171
6235
  }
6172
6236
  this.handleAdPodComplete();
6173
6237
  return [
@@ -6196,7 +6260,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6196
6260
  preloaded = this.getPreloadedAd();
6197
6261
  if (!preloaded) return [
6198
6262
  3,
6199
- 4
6263
+ 6
6200
6264
  ];
6201
6265
  if (this.config.debugAdTiming) {
6202
6266
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6210,10 +6274,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6210
6274
  case 1:
6211
6275
  _state.trys.push([
6212
6276
  1,
6213
- 3,
6277
+ 5,
6214
6278
  ,
6215
- 4
6279
+ 6
6216
6280
  ]);
6281
+ if (!!this.ensureLoadedAdFitsBudget(preloaded.adController)) return [
6282
+ 3,
6283
+ 3
6284
+ ];
6285
+ this.rejectLoadedAdForDuration(preloaded.adController);
6286
+ return [
6287
+ 4,
6288
+ this.tryNextAvailableAdWithRateLimit()
6289
+ ];
6290
+ case 2:
6291
+ _state.sent();
6292
+ return [
6293
+ 2
6294
+ ];
6295
+ case 3:
6217
6296
  preservedMuted = this.adPlayer.getOriginalMutedState();
6218
6297
  preservedVolume = this.adPlayer.getOriginalVolume();
6219
6298
  this.adPlayer.destroy();
@@ -6222,15 +6301,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6222
6301
  this.attachImaEventListeners();
6223
6302
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6224
6303
  this.adPlayer.resume();
6225
- this.currentAdIndex++;
6226
- this.totalAdRequestsInBreak++;
6227
- this.lastAdRequestTime = Date.now();
6228
6304
  return [
6229
6305
  4,
6230
6306
  this.adPlayer.play()
6231
6307
  ];
6232
- case 2:
6308
+ case 4:
6233
6309
  _state.sent();
6310
+ this.markAdStarted();
6234
6311
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6235
6312
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6236
6313
  }
@@ -6239,25 +6316,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6239
6316
  return [
6240
6317
  2
6241
6318
  ];
6242
- case 3:
6319
+ case 5:
6243
6320
  error = _state.sent();
6244
6321
  if (this.config.debugAdTiming) {
6245
6322
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6246
6323
  }
6247
6324
  return [
6248
6325
  3,
6249
- 4
6326
+ 6
6250
6327
  ];
6251
- case 4:
6328
+ case 6:
6252
6329
  if (!(this.adRequestQueue.length > 0)) return [
6253
6330
  3,
6254
- 10
6331
+ 14
6255
6332
  ];
6256
6333
  nextAdUrl = this.adRequestQueue.shift();
6257
6334
  if (!nextAdUrl) {
6258
6335
  return [
6259
6336
  3,
6260
- 4
6337
+ 6
6261
6338
  ];
6262
6339
  }
6263
6340
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6266,7 +6343,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6266
6343
  }
6267
6344
  return [
6268
6345
  3,
6269
- 4
6346
+ 6
6270
6347
  ];
6271
6348
  }
6272
6349
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6275,40 +6352,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6275
6352
  }
6276
6353
  return [
6277
6354
  3,
6278
- 4
6355
+ 6
6279
6356
  ];
6280
6357
  }
6281
6358
  if (this.config.debugAdTiming) {
6282
- 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)"));
6359
+ console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6283
6360
  }
6284
- this.currentAdIndex++;
6285
- this.totalAdRequestsInBreak++;
6286
- this.lastAdRequestTime = Date.now();
6287
- _state.label = 5;
6288
- case 5:
6361
+ _state.label = 7;
6362
+ case 7:
6289
6363
  _state.trys.push([
6290
- 5,
6291
6364
  7,
6365
+ 9,
6292
6366
  ,
6293
- 9
6367
+ 13
6294
6368
  ]);
6295
6369
  return [
6296
6370
  4,
6297
6371
  this.playSingleAd(nextAdUrl)
6298
6372
  ];
6299
- case 6:
6373
+ case 8:
6300
6374
  _state.sent();
6301
6375
  this.consecutiveFailures = 0;
6302
6376
  return [
6303
6377
  3,
6304
- 9
6378
+ 13
6305
6379
  ];
6306
- case 7:
6380
+ case 9:
6307
6381
  error1 = _state.sent();
6308
6382
  errorMessage = error1.message;
6309
6383
  if (this.config.debugAdTiming) {
6310
6384
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6311
6385
  }
6386
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6387
+ 3,
6388
+ 11
6389
+ ];
6390
+ return [
6391
+ 4,
6392
+ this.tryNextAvailableAdWithRateLimit()
6393
+ ];
6394
+ case 10:
6395
+ _state.sent();
6396
+ return [
6397
+ 2
6398
+ ];
6399
+ case 11:
6312
6400
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6313
6401
  this.consecutiveFailures++;
6314
6402
  }
@@ -6316,21 +6404,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6316
6404
  4,
6317
6405
  this.tryNextAvailableAdWithRateLimit()
6318
6406
  ];
6319
- case 8:
6407
+ case 12:
6320
6408
  _state.sent();
6321
6409
  return [
6322
6410
  3,
6323
- 9
6411
+ 13
6324
6412
  ];
6325
- case 9:
6413
+ case 13:
6326
6414
  return [
6327
6415
  2
6328
6416
  ];
6329
- case 10:
6417
+ case 14:
6330
6418
  maxRetries = 3;
6331
6419
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6332
6420
  3,
6333
- 13
6421
+ 17
6334
6422
  ];
6335
6423
  if (this.config.debugAdTiming) {
6336
6424
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6341,18 +6429,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6341
6429
  return setTimeout(resolve, 500);
6342
6430
  })
6343
6431
  ];
6344
- case 11:
6432
+ case 15:
6345
6433
  _state.sent();
6346
6434
  return [
6347
6435
  4,
6348
6436
  this.tryNextAvailableAd(retryCount + 1)
6349
6437
  ];
6350
- case 12:
6438
+ case 16:
6351
6439
  _state.sent();
6352
6440
  return [
6353
6441
  2
6354
6442
  ];
6355
- case 13:
6443
+ case 17:
6356
6444
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6357
6445
  this.showPlaceholderAndWaitForAds();
6358
6446
  } else {
@@ -6415,7 +6503,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6415
6503
  case 1:
6416
6504
  if (!(i < maxChecks)) return [
6417
6505
  3,
6418
- 10
6506
+ 12
6419
6507
  ];
6420
6508
  return [
6421
6509
  4,
@@ -6436,15 +6524,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6436
6524
  }
6437
6525
  return [
6438
6526
  3,
6439
- 10
6527
+ 12
6528
+ ];
6529
+ }
6530
+ if (!this.hasTimeToStartAnotherAd()) {
6531
+ if (this.config.debugAdTiming) {
6532
+ console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Not enough time remaining during placeholder wait, ending ad break");
6533
+ }
6534
+ return [
6535
+ 3,
6536
+ 12
6440
6537
  ];
6441
6538
  }
6442
6539
  _state.label = 3;
6443
6540
  case 3:
6444
6541
  if (!(this.adRequestQueue.length > 0)) return [
6445
6542
  3,
6446
- 9
6543
+ 11
6447
6544
  ];
6545
+ if (!this.hasTimeToStartAnotherAd()) {
6546
+ return [
6547
+ 3,
6548
+ 11
6549
+ ];
6550
+ }
6448
6551
  nextAdUrl = this.adRequestQueue.shift();
6449
6552
  if (!nextAdUrl) {
6450
6553
  return [
@@ -6469,16 +6572,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6469
6572
  }
6470
6573
  this.isShowingPlaceholder = false;
6471
6574
  this.adPlayer.hidePlaceholder();
6472
- this.currentAdIndex++;
6473
- this.totalAdRequestsInBreak++;
6474
- this.lastAdRequestTime = Date.now();
6475
6575
  _state.label = 4;
6476
6576
  case 4:
6477
6577
  _state.trys.push([
6478
6578
  4,
6479
6579
  6,
6480
6580
  ,
6481
- 8
6581
+ 10
6482
6582
  ]);
6483
6583
  return [
6484
6584
  4,
@@ -6489,11 +6589,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6489
6589
  this.consecutiveFailures = 0;
6490
6590
  return [
6491
6591
  3,
6492
- 8
6592
+ 10
6493
6593
  ];
6494
6594
  case 6:
6495
6595
  error = _state.sent();
6496
6596
  errorMessage = error.message;
6597
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6598
+ 3,
6599
+ 8
6600
+ ];
6601
+ return [
6602
+ 4,
6603
+ this.tryNextAvailableAdWithRateLimit()
6604
+ ];
6605
+ case 7:
6606
+ _state.sent();
6607
+ return [
6608
+ 2
6609
+ ];
6610
+ case 8:
6497
6611
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6498
6612
  this.consecutiveFailures++;
6499
6613
  }
@@ -6501,23 +6615,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6501
6615
  4,
6502
6616
  this.tryNextAvailableAdWithRateLimit()
6503
6617
  ];
6504
- case 7:
6618
+ case 9:
6505
6619
  _state.sent();
6506
6620
  return [
6507
6621
  3,
6508
- 8
6622
+ 10
6509
6623
  ];
6510
- case 8:
6624
+ case 10:
6511
6625
  return [
6512
6626
  2
6513
6627
  ];
6514
- case 9:
6628
+ case 11:
6515
6629
  i++;
6516
6630
  return [
6517
6631
  3,
6518
6632
  1
6519
6633
  ];
6520
- case 10:
6634
+ case 12:
6521
6635
  if (this.config.debugAdTiming) {
6522
6636
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6523
6637
  }
@@ -6727,21 +6841,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6727
6841
  if (!this.inAdBreak) return;
6728
6842
  this.adStopTimerId = void 0;
6729
6843
  var adPlaying = this.adPlayer.isAdPlaying();
6730
- var pendingAds = this.adPodQueue.length > 0;
6731
6844
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
6732
6845
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
6733
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
6846
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
6734
6847
  var elapsedSinceStartMs = 0;
6735
6848
  if (this.currentAdBreakStartWallClockMs != null) {
6736
6849
  elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
6737
6850
  }
6738
6851
  var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
6739
6852
  var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
6740
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
6853
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
6741
6854
  if (shouldExtendAdBreak) {
6742
6855
  this.scheduleAdStopCountdown(checkIntervalMs);
6743
6856
  return;
6744
6857
  }
6858
+ if (this.config.debugAdTiming) {
6859
+ console.log("[StormcloudVideoPlayer] ⏱️ Ad break boundary reached (overrun=".concat(overrunMs, "ms) - hard-stopping ads and resuming stream"));
6860
+ }
6745
6861
  if (adPlaying) {
6746
6862
  this.adPlayer.stop().catch(function() {});
6747
6863
  }
@@ -6987,6 +7103,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6987
7103
  2
6988
7104
  ];
6989
7105
  }
7106
+ if (!this.ensureLoadedAdFitsBudget()) {
7107
+ this.rejectLoadedAdForDuration();
7108
+ this.recreateAdController();
7109
+ throw new Error("ad_duration_exceeds_budget");
7110
+ }
6990
7111
  _state.label = 4;
6991
7112
  case 4:
6992
7113
  _state.trys.push([
@@ -7002,6 +7123,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7002
7123
  ];
7003
7124
  case 5:
7004
7125
  _state.sent();
7126
+ this.markAdStarted();
7005
7127
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7006
7128
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7007
7129
  }
@@ -7033,6 +7155,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7033
7155
  ,
7034
7156
  10
7035
7157
  ]);
7158
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7159
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7160
+ throw playError;
7161
+ }
7036
7162
  this.clearAdFailsafeTimer();
7037
7163
  preservedMuted = this.adPlayer.getOriginalMutedState();
7038
7164
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -7043,14 +7169,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7043
7169
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
7044
7170
  this.adPlayer.resume();
7045
7171
  this.consecutiveFailures = 0;
7046
- this.currentAdIndex++;
7047
- this.totalAdRequestsInBreak++;
7048
7172
  return [
7049
7173
  4,
7050
7174
  this.adPlayer.play()
7051
7175
  ];
7052
7176
  case 8:
7053
7177
  _state.sent();
7178
+ this.markAdStarted();
7054
7179
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7055
7180
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7056
7181
  }
@@ -7111,6 +7236,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7111
7236
  ,
7112
7237
  16
7113
7238
  ]);
7239
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7240
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7241
+ throw error;
7242
+ }
7114
7243
  this.clearAdRequestWatchdog();
7115
7244
  this.clearAdFailsafeTimer();
7116
7245
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7122,14 +7251,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7122
7251
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7123
7252
  this.adPlayer.resume();
7124
7253
  this.consecutiveFailures = 0;
7125
- this.currentAdIndex++;
7126
- this.totalAdRequestsInBreak++;
7127
7254
  return [
7128
7255
  4,
7129
7256
  this.adPlayer.play()
7130
7257
  ];
7131
7258
  case 14:
7132
7259
  _state.sent();
7260
+ this.markAdStarted();
7133
7261
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7134
7262
  return [
7135
7263
  2
@@ -7190,6 +7318,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7190
7318
  this.adRequestQueue = [];
7191
7319
  this.inAdBreak = false;
7192
7320
  this.adDetectSentForCurrentBreak = false;
7321
+ this.adBreakPlayedDurationMs = 0;
7193
7322
  this.activeScte35BreakKey = void 0;
7194
7323
  this.scheduledScte35BreakKey = void 0;
7195
7324
  this.expectedAdBreakDurationMs = void 0;
@@ -7247,11 +7376,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7247
7376
  this.handleAdPodComplete();
7248
7377
  return;
7249
7378
  }
7250
- if (remaining > 500) {
7379
+ if (this.hasTimeToStartAnotherAd()) {
7251
7380
  this.tryNextAvailableAdWithRateLimit();
7252
7381
  } else {
7253
7382
  if (this.config.debugAdTiming) {
7254
- console.log("[AD-ERROR] Ad failed and no time remaining. Failed URLs:", this.failedVastUrls.size);
7383
+ console.log("[AD-ERROR] Ad failed and not enough time remaining for another ad. Failed URLs:", this.failedVastUrls.size);
7255
7384
  }
7256
7385
  this.handleAdPodComplete();
7257
7386
  }
@@ -7379,13 +7508,134 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7379
7508
  }
7380
7509
  },
7381
7510
  {
7382
- key: "getRemainingAdMs",
7383
- value: function getRemainingAdMs() {
7384
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7511
+ key: "getWallClockRemainingAdMs",
7512
+ value: function getWallClockRemainingAdMs() {
7513
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {
7514
+ return 0;
7515
+ }
7385
7516
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7386
7517
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7387
7518
  }
7388
7519
  },
7520
+ {
7521
+ key: "getDurationBudgetRemainingMs",
7522
+ value: function getDurationBudgetRemainingMs() {
7523
+ if (this.expectedAdBreakDurationMs == null) {
7524
+ return 0;
7525
+ }
7526
+ return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);
7527
+ }
7528
+ },
7529
+ {
7530
+ key: "getLoadedAdDurationMs",
7531
+ value: function getLoadedAdDurationMs() {
7532
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7533
+ var _adController_getLoadedAdDuration;
7534
+ var durationSec = (_adController_getLoadedAdDuration = adController.getLoadedAdDuration) === null || _adController_getLoadedAdDuration === void 0 ? void 0 : _adController_getLoadedAdDuration.call(adController);
7535
+ if (durationSec == null || durationSec <= 0) {
7536
+ return 0;
7537
+ }
7538
+ return Math.round(durationSec * 1e3);
7539
+ }
7540
+ },
7541
+ {
7542
+ key: "adFitsRemainingBudget",
7543
+ value: function adFitsRemainingBudget(durationMs) {
7544
+ var adController = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.adPlayer;
7545
+ if (durationMs <= 0) {
7546
+ return true;
7547
+ }
7548
+ return durationMs <= this.getDurationBudgetRemainingMs();
7549
+ }
7550
+ },
7551
+ {
7552
+ key: "logAdDurationDecision",
7553
+ value: function logAdDurationDecision(accepted, durationMs) {
7554
+ var adController = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : this.adPlayer;
7555
+ var _ref;
7556
+ var _adController_getLoadedAdId;
7557
+ if (!this.config.debugAdTiming) {
7558
+ return;
7559
+ }
7560
+ var remainingBudget = this.getDurationBudgetRemainingMs();
7561
+ var adId = (_ref = (_adController_getLoadedAdId = adController.getLoadedAdId) === null || _adController_getLoadedAdId === void 0 ? void 0 : _adController_getLoadedAdId.call(adController)) !== null && _ref !== void 0 ? _ref : "unknown";
7562
+ if (accepted) {
7563
+ console.log("[AD-DURATION] Accepted ad id=".concat(adId, ", duration=").concat(durationMs, "ms, budget remaining after play=").concat(Math.max(0, remainingBudget - durationMs), "ms"));
7564
+ } else {
7565
+ console.log("[AD-DURATION] Rejected ad id=".concat(adId, ", duration=").concat(durationMs, "ms exceeds remaining budget=").concat(remainingBudget, "ms — requesting another ad"));
7566
+ }
7567
+ }
7568
+ },
7569
+ {
7570
+ key: "recordPlayedAdDuration",
7571
+ value: function recordPlayedAdDuration() {
7572
+ var durationMs = this.getLoadedAdDurationMs();
7573
+ if (durationMs <= 0) {
7574
+ return;
7575
+ }
7576
+ this.adBreakPlayedDurationMs += durationMs;
7577
+ if (this.config.debugAdTiming) {
7578
+ var _this_expectedAdBreakDurationMs;
7579
+ 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"));
7580
+ }
7581
+ }
7582
+ },
7583
+ {
7584
+ key: "ensureLoadedAdFitsBudget",
7585
+ value: function ensureLoadedAdFitsBudget() {
7586
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7587
+ var durationMs = this.getLoadedAdDurationMs(adController);
7588
+ var fits = this.adFitsRemainingBudget(durationMs, adController);
7589
+ this.logAdDurationDecision(fits, durationMs, adController);
7590
+ return fits;
7591
+ }
7592
+ },
7593
+ {
7594
+ key: "rejectLoadedAdForDuration",
7595
+ value: function rejectLoadedAdForDuration() {
7596
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7597
+ try {
7598
+ adController.destroy();
7599
+ } catch (unused) {}
7600
+ }
7601
+ },
7602
+ {
7603
+ key: "markAdStarted",
7604
+ value: function markAdStarted() {
7605
+ this.currentAdIndex++;
7606
+ this.totalAdsInBreak = this.currentAdIndex;
7607
+ this.totalAdRequestsInBreak++;
7608
+ this.lastAdRequestTime = Date.now();
7609
+ }
7610
+ },
7611
+ {
7612
+ key: "getRemainingAdMs",
7613
+ value: function getRemainingAdMs() {
7614
+ var wallClockRemaining = this.getWallClockRemainingAdMs();
7615
+ var budgetRemaining = this.getDurationBudgetRemainingMs();
7616
+ if (this.expectedAdBreakDurationMs == null) {
7617
+ return wallClockRemaining;
7618
+ }
7619
+ return Math.min(wallClockRemaining, budgetRemaining);
7620
+ }
7621
+ },
7622
+ {
7623
+ key: "getMinRemainingMsToStartAd",
7624
+ value: function getMinRemainingMsToStartAd() {
7625
+ var configured = this.config.minRemainingMsToStartAd;
7626
+ if (typeof configured === "number" && configured >= 0) {
7627
+ return configured;
7628
+ }
7629
+ return this.defaultMinRemainingMsToStartAd;
7630
+ }
7631
+ },
7632
+ {
7633
+ key: "hasTimeToStartAnotherAd",
7634
+ value: function hasTimeToStartAnotherAd() {
7635
+ var minRemaining = this.getMinRemainingMsToStartAd();
7636
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7637
+ }
7638
+ },
7389
7639
  {
7390
7640
  key: "findBreakForTime",
7391
7641
  value: function findBreakForTime(nowMs) {