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.
@@ -1240,6 +1240,12 @@ function createHlsAdPlayer(contentVideo, options) {
1240
1240
  contentVideo.style.visibility = "visible";
1241
1241
  contentVideo.style.opacity = "1";
1242
1242
  }
1243
+ },
1244
+ getLoadedAdDuration: function getLoadedAdDuration() {
1245
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration;
1246
+ },
1247
+ getLoadedAdId: function getLoadedAdId() {
1248
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.id;
1243
1249
  }
1244
1250
  };
1245
1251
  }
@@ -2636,6 +2642,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2636
2642
  this.maxConsecutiveFailures = 5;
2637
2643
  this.lastAdRequestTime = 0;
2638
2644
  this.minAdRequestIntervalMs = 3e3;
2645
+ this.defaultMinRemainingMsToStartAd = 4e3;
2639
2646
  this.backoffBaseMs = 1e3;
2640
2647
  this.maxBackoffMs = 15e3;
2641
2648
  this.globalConsecutiveNoFills = 0;
@@ -2651,6 +2658,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2651
2658
  this.preloadPoolLoopRunning = false;
2652
2659
  this.adDetectSentForCurrentBreak = false;
2653
2660
  this.adBreakEpoch = 0;
2661
+ this.adBreakPlayedDurationMs = 0;
2654
2662
  this.palPlaybackStarted = false;
2655
2663
  this.streamCorrelator = generateCorrelator();
2656
2664
  this.consentSignals = {};
@@ -3147,6 +3155,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3147
3155
  adIndex: _this.currentAdIndex,
3148
3156
  timestamp: /* @__PURE__ */ new Date().toISOString()
3149
3157
  }).catch(function() {});
3158
+ _this.recordPlayedAdDuration();
3150
3159
  var remaining = _this.getRemainingAdMs();
3151
3160
  _this.consecutiveFailures = 0;
3152
3161
  if (_this.config.debugAdTiming) {
@@ -3155,7 +3164,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3155
3164
  } : {});
3156
3165
  }
3157
3166
  if (_this.inAdBreak) {
3158
- if (remaining > 500) {
3167
+ if (_this.hasTimeToStartAnotherAd()) {
3159
3168
  _this.tryNextAvailableAdWithRateLimit();
3160
3169
  } else {
3161
3170
  _this.handleAdPodComplete();
@@ -3222,13 +3231,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3222
3231
  return;
3223
3232
  }
3224
3233
  _this.consecutiveFailures = 0;
3234
+ _this.recordPlayedAdDuration();
3225
3235
  var remaining = _this.getRemainingAdMs();
3226
3236
  if (_this.config.debugAdTiming) {
3227
3237
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
3228
3238
  queuedUrls: _to_consumable_array(_this.adRequestQueue)
3229
3239
  } : {});
3230
3240
  }
3231
- if (remaining > 500) {
3241
+ if (_this.hasTimeToStartAnotherAd()) {
3232
3242
  _this.tryNextAvailableAdWithRateLimit();
3233
3243
  } else {
3234
3244
  _this.handleAdPodComplete();
@@ -3928,7 +3938,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3928
3938
  var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
3929
3939
  this.scheduleAdStopCountdown(remainingMs1);
3930
3940
  }
3931
- if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
3941
+ if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0 && this.hasTimeToStartAnotherAd()) {
3932
3942
  this.tryNextAvailableAdWithRateLimit();
3933
3943
  }
3934
3944
  return;
@@ -5227,7 +5237,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5227
5237
  epoch = this.adBreakEpoch;
5228
5238
  loadPromise = function() {
5229
5239
  return _async_to_generator(function() {
5230
- var _this, hasAdError, adErrorPayload, errorListenerCleanup, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, lateErrorListener, error;
5240
+ var _this, hasAdError, adErrorPayload, errorListenerCleanup, _preloadAd_getLoadedAdDuration, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, loadedDuration, lateErrorListener, _poolEntry_durationSeconds, error;
5231
5241
  return _ts_generator(this, function(_state) {
5232
5242
  switch(_state.label){
5233
5243
  case 0:
@@ -5323,6 +5333,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5323
5333
  isReady: true,
5324
5334
  loadPromise: Promise.resolve()
5325
5335
  };
5336
+ loadedDuration = (_preloadAd_getLoadedAdDuration = preloadAd.getLoadedAdDuration) === null || _preloadAd_getLoadedAdDuration === void 0 ? void 0 : _preloadAd_getLoadedAdDuration.call(preloadAd);
5337
+ if (loadedDuration != null && loadedDuration > 0) {
5338
+ poolEntry.durationSeconds = loadedDuration;
5339
+ }
5326
5340
  lateErrorListener = function lateErrorListener(payload) {
5327
5341
  var index = _this.preloadPool.findIndex(function(entry) {
5328
5342
  return entry.vastUrl === vastUrl;
@@ -5340,7 +5354,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5340
5354
  preloadAd.on("ad_error", lateErrorListener);
5341
5355
  this.preloadPool.push(poolEntry);
5342
5356
  if (this.config.debugAdTiming) {
5343
- console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ")"));
5357
+ ;
5358
+ 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)"));
5344
5359
  }
5345
5360
  if (errorListenerCleanup) {
5346
5361
  errorListenerCleanup();
@@ -5387,7 +5402,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5387
5402
  key: "preloadPoolLoop",
5388
5403
  value: function preloadPoolLoop(baseVastUrl) {
5389
5404
  return _async_to_generator(function() {
5390
- var epoch, remaining, newUrls, newUrl;
5405
+ var epoch, newUrls, newUrl;
5391
5406
  return _ts_generator(this, function(_state) {
5392
5407
  switch(_state.label){
5393
5408
  case 0:
@@ -5454,14 +5469,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5454
5469
  8
5455
5470
  ];
5456
5471
  }
5457
- if (this.inAdBreak) {
5458
- remaining = this.getRemainingAdMs();
5459
- if (remaining <= 0) {
5460
- return [
5461
- 3,
5462
- 8
5463
- ];
5472
+ if (this.inAdBreak && !this.hasTimeToStartAnotherAd()) {
5473
+ if (this.config.debugAdTiming) {
5474
+ console.log("[PRELOAD-POOL] Not enough ad break time left, stopping preload");
5464
5475
  }
5476
+ return [
5477
+ 3,
5478
+ 8
5479
+ ];
5465
5480
  }
5466
5481
  newUrls = this.generateVastUrlsWithCorrelators(baseVastUrl, 1);
5467
5482
  newUrl = newUrls[0];
@@ -5621,8 +5636,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5621
5636
  }
5622
5637
  this.inAdBreak = true;
5623
5638
  this.currentAdBreakStartWallClockMs = Date.now();
5639
+ this.adBreakPlayedDurationMs = 0;
5624
5640
  this.currentAdIndex = 0;
5625
- this.totalAdsInBreak = 1;
5641
+ this.totalAdsInBreak = 0;
5626
5642
  this.adPodQueue = [];
5627
5643
  this.showAds = true;
5628
5644
  this.showPlaceholderLayer();
@@ -5676,14 +5692,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5676
5692
  case 1:
5677
5693
  _state.trys.push([
5678
5694
  1,
5679
- 8,
5695
+ 12,
5680
5696
  ,
5681
- 14
5697
+ 20
5682
5698
  ]);
5683
5699
  if (!(usePreloadedAd && preloadedController)) return [
5700
+ 3,
5701
+ 5
5702
+ ];
5703
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5684
5704
  3,
5685
5705
  3
5686
5706
  ];
5707
+ this.rejectLoadedAdForDuration(preloadedController);
5708
+ this.startContinuousFetching(baseVastUrl);
5709
+ return [
5710
+ 4,
5711
+ this.tryNextAvailableAdWithRateLimit()
5712
+ ];
5713
+ case 2:
5714
+ _state.sent();
5715
+ return [
5716
+ 2
5717
+ ];
5718
+ case 3:
5687
5719
  preservedMuted = this.adPlayer.getOriginalMutedState();
5688
5720
  preservedVolume = this.adPlayer.getOriginalVolume();
5689
5721
  this.adPlayer.destroy();
@@ -5696,7 +5728,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5696
5728
  }
5697
5729
  this.adPlayer.resume();
5698
5730
  this.consecutiveFailures = 0;
5699
- this.currentAdIndex++;
5700
5731
  this.startContinuousFetching(baseVastUrl);
5701
5732
  if (!this.preloadPoolActive) {
5702
5733
  this.preloadPoolActive = true;
@@ -5706,8 +5737,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5706
5737
  4,
5707
5738
  this.adPlayer.play()
5708
5739
  ];
5709
- case 2:
5740
+ case 4:
5710
5741
  _state.sent();
5742
+ this.markAdStarted();
5711
5743
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5712
5744
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5713
5745
  }
@@ -5715,21 +5747,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5715
5747
  this.adPlayer.setAdVolume(adVolume);
5716
5748
  return [
5717
5749
  3,
5718
- 7
5750
+ 11
5719
5751
  ];
5720
- case 3:
5752
+ case 5:
5721
5753
  return [
5722
5754
  4,
5723
5755
  this.enforceGlobalRateLimit()
5724
5756
  ];
5725
- case 4:
5757
+ case 6:
5726
5758
  _state.sent();
5727
5759
  this.lastAdRequestTime = Date.now();
5728
5760
  return [
5729
5761
  4,
5730
5762
  this.adPlayer.requestAds(firstAdUrl)
5731
5763
  ];
5732
- case 5:
5764
+ case 7:
5733
5765
  _state.sent();
5734
5766
  sendAdLoadedTracking(this.config.licenseKey, {
5735
5767
  source: "hls",
@@ -5739,8 +5771,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5739
5771
  if (this.config.debugAdTiming) {
5740
5772
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
5741
5773
  }
5774
+ if (!!this.ensureLoadedAdFitsBudget()) return [
5775
+ 3,
5776
+ 9
5777
+ ];
5778
+ this.rejectLoadedAdForDuration();
5779
+ this.recreateAdController();
5780
+ this.startContinuousFetching(baseVastUrl);
5781
+ return [
5782
+ 4,
5783
+ this.tryNextAvailableAdWithRateLimit()
5784
+ ];
5785
+ case 8:
5786
+ _state.sent();
5787
+ return [
5788
+ 2
5789
+ ];
5790
+ case 9:
5742
5791
  this.consecutiveFailures = 0;
5743
- this.currentAdIndex++;
5744
5792
  this.startContinuousFetching(baseVastUrl);
5745
5793
  if (!this.preloadPoolActive) {
5746
5794
  this.startPreloadPool(baseVastUrl, []);
@@ -5749,44 +5797,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5749
5797
  4,
5750
5798
  this.adPlayer.play()
5751
5799
  ];
5752
- case 6:
5800
+ case 10:
5753
5801
  _state.sent();
5802
+ this.markAdStarted();
5754
5803
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5755
5804
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5756
5805
  }
5757
5806
  adVolume1 = currentMuted ? 0 : currentVolume;
5758
5807
  this.adPlayer.setAdVolume(adVolume1);
5759
- _state.label = 7;
5760
- case 7:
5808
+ _state.label = 11;
5809
+ case 11:
5761
5810
  return [
5762
5811
  3,
5763
- 14
5812
+ 20
5764
5813
  ];
5765
- case 8:
5814
+ case 12:
5766
5815
  error = _state.sent();
5767
5816
  if (this.config.debugAdTiming) {
5768
5817
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
5769
5818
  }
5770
5819
  if (!!usePreloadedAd) return [
5771
5820
  3,
5772
- 12
5821
+ 18
5773
5822
  ];
5774
5823
  fallbackPreloaded = this.getPreloadedAd();
5775
5824
  if (!fallbackPreloaded) return [
5776
5825
  3,
5777
- 12
5826
+ 18
5778
5827
  ];
5779
5828
  if (this.config.debugAdTiming) {
5780
5829
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
5781
5830
  }
5782
- _state.label = 9;
5783
- case 9:
5831
+ _state.label = 13;
5832
+ case 13:
5784
5833
  _state.trys.push([
5785
- 9,
5786
- 11,
5834
+ 13,
5835
+ 17,
5787
5836
  ,
5788
- 12
5837
+ 18
5789
5838
  ]);
5839
+ if (!!this.ensureLoadedAdFitsBudget(fallbackPreloaded.adController)) return [
5840
+ 3,
5841
+ 15
5842
+ ];
5843
+ this.rejectLoadedAdForDuration(fallbackPreloaded.adController);
5844
+ this.startContinuousFetching(baseVastUrl);
5845
+ return [
5846
+ 4,
5847
+ this.tryNextAvailableAdWithRateLimit()
5848
+ ];
5849
+ case 14:
5850
+ _state.sent();
5851
+ return [
5852
+ 2
5853
+ ];
5854
+ case 15:
5790
5855
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
5791
5856
  preservedVolume1 = this.adPlayer.getOriginalVolume();
5792
5857
  this.adPlayer.destroy();
@@ -5796,7 +5861,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5796
5861
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
5797
5862
  this.adPlayer.resume();
5798
5863
  this.consecutiveFailures = 0;
5799
- this.currentAdIndex++;
5800
5864
  this.startContinuousFetching(baseVastUrl);
5801
5865
  if (!this.preloadPoolActive) {
5802
5866
  this.preloadPoolActive = true;
@@ -5806,8 +5870,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5806
5870
  4,
5807
5871
  this.adPlayer.play()
5808
5872
  ];
5809
- case 10:
5873
+ case 16:
5810
5874
  _state.sent();
5875
+ this.markAdStarted();
5811
5876
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5812
5877
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5813
5878
  }
@@ -5816,16 +5881,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5816
5881
  return [
5817
5882
  2
5818
5883
  ];
5819
- case 11:
5884
+ case 17:
5820
5885
  fallbackError = _state.sent();
5821
5886
  if (this.config.debugAdTiming) {
5822
5887
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
5823
5888
  }
5824
5889
  return [
5825
5890
  3,
5826
- 12
5891
+ 18
5827
5892
  ];
5828
- case 12:
5893
+ case 18:
5829
5894
  if (this.isTemporaryAdError(error)) {
5830
5895
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
5831
5896
  if (this.config.debugAdTiming) {
@@ -5843,13 +5908,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5843
5908
  4,
5844
5909
  this.tryNextAvailableAdWithRateLimit()
5845
5910
  ];
5846
- case 13:
5911
+ case 19:
5847
5912
  _state.sent();
5848
5913
  return [
5849
5914
  3,
5850
- 14
5915
+ 20
5851
5916
  ];
5852
- case 14:
5917
+ case 20:
5853
5918
  return [
5854
5919
  2
5855
5920
  ];
@@ -5890,10 +5955,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5890
5955
  return _ts_generator(this, function(_state) {
5891
5956
  switch(_state.label){
5892
5957
  case 0:
5893
- remaining = _this.getRemainingAdMs();
5894
- if (remaining <= 0) {
5958
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
5959
+ if (remaining <= _this.getMinRemainingMsToStartAd()) {
5895
5960
  if (_this.config.debugAdTiming) {
5896
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Ad break time expired, stopping URL generation");
5961
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (wall=".concat(_this.getWallClockRemainingAdMs(), "ms, budget=").concat(_this.getDurationBudgetRemainingMs(), "ms), stopping URL generation"));
5897
5962
  }
5898
5963
  return [
5899
5964
  2,
@@ -5975,7 +6040,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5975
6040
  });
5976
6041
  }
5977
6042
  _this.adRequestQueue.push(newAdUrl);
5978
- _this.totalAdsInBreak++;
5979
6043
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
5980
6044
  return [
5981
6045
  4,
@@ -6113,9 +6177,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6113
6177
  ];
6114
6178
  }
6115
6179
  remaining = this.getRemainingAdMs();
6116
- if (remaining <= 500) {
6180
+ if (!this.hasTimeToStartAnotherAd()) {
6117
6181
  if (this.config.debugAdTiming) {
6118
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F No time remaining, ending ad break");
6182
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough time remaining (".concat(remaining, "ms) to start another ad, ending ad break"));
6119
6183
  }
6120
6184
  this.handleAdPodComplete();
6121
6185
  return [
@@ -6144,7 +6208,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6144
6208
  preloaded = this.getPreloadedAd();
6145
6209
  if (!preloaded) return [
6146
6210
  3,
6147
- 4
6211
+ 6
6148
6212
  ];
6149
6213
  if (this.config.debugAdTiming) {
6150
6214
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6158,10 +6222,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6158
6222
  case 1:
6159
6223
  _state.trys.push([
6160
6224
  1,
6161
- 3,
6225
+ 5,
6162
6226
  ,
6163
- 4
6227
+ 6
6164
6228
  ]);
6229
+ if (!!this.ensureLoadedAdFitsBudget(preloaded.adController)) return [
6230
+ 3,
6231
+ 3
6232
+ ];
6233
+ this.rejectLoadedAdForDuration(preloaded.adController);
6234
+ return [
6235
+ 4,
6236
+ this.tryNextAvailableAdWithRateLimit()
6237
+ ];
6238
+ case 2:
6239
+ _state.sent();
6240
+ return [
6241
+ 2
6242
+ ];
6243
+ case 3:
6165
6244
  preservedMuted = this.adPlayer.getOriginalMutedState();
6166
6245
  preservedVolume = this.adPlayer.getOriginalVolume();
6167
6246
  this.adPlayer.destroy();
@@ -6170,15 +6249,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6170
6249
  this.attachImaEventListeners();
6171
6250
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6172
6251
  this.adPlayer.resume();
6173
- this.currentAdIndex++;
6174
- this.totalAdRequestsInBreak++;
6175
- this.lastAdRequestTime = Date.now();
6176
6252
  return [
6177
6253
  4,
6178
6254
  this.adPlayer.play()
6179
6255
  ];
6180
- case 2:
6256
+ case 4:
6181
6257
  _state.sent();
6258
+ this.markAdStarted();
6182
6259
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6183
6260
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6184
6261
  }
@@ -6187,25 +6264,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6187
6264
  return [
6188
6265
  2
6189
6266
  ];
6190
- case 3:
6267
+ case 5:
6191
6268
  error = _state.sent();
6192
6269
  if (this.config.debugAdTiming) {
6193
6270
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6194
6271
  }
6195
6272
  return [
6196
6273
  3,
6197
- 4
6274
+ 6
6198
6275
  ];
6199
- case 4:
6276
+ case 6:
6200
6277
  if (!(this.adRequestQueue.length > 0)) return [
6201
6278
  3,
6202
- 10
6279
+ 14
6203
6280
  ];
6204
6281
  nextAdUrl = this.adRequestQueue.shift();
6205
6282
  if (!nextAdUrl) {
6206
6283
  return [
6207
6284
  3,
6208
- 4
6285
+ 6
6209
6286
  ];
6210
6287
  }
6211
6288
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6214,7 +6291,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6214
6291
  }
6215
6292
  return [
6216
6293
  3,
6217
- 4
6294
+ 6
6218
6295
  ];
6219
6296
  }
6220
6297
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6223,40 +6300,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6223
6300
  }
6224
6301
  return [
6225
6302
  3,
6226
- 4
6303
+ 6
6227
6304
  ];
6228
6305
  }
6229
6306
  if (this.config.debugAdTiming) {
6230
- 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)"));
6307
+ console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6231
6308
  }
6232
- this.currentAdIndex++;
6233
- this.totalAdRequestsInBreak++;
6234
- this.lastAdRequestTime = Date.now();
6235
- _state.label = 5;
6236
- case 5:
6309
+ _state.label = 7;
6310
+ case 7:
6237
6311
  _state.trys.push([
6238
- 5,
6239
6312
  7,
6313
+ 9,
6240
6314
  ,
6241
- 9
6315
+ 13
6242
6316
  ]);
6243
6317
  return [
6244
6318
  4,
6245
6319
  this.playSingleAd(nextAdUrl)
6246
6320
  ];
6247
- case 6:
6321
+ case 8:
6248
6322
  _state.sent();
6249
6323
  this.consecutiveFailures = 0;
6250
6324
  return [
6251
6325
  3,
6252
- 9
6326
+ 13
6253
6327
  ];
6254
- case 7:
6328
+ case 9:
6255
6329
  error1 = _state.sent();
6256
6330
  errorMessage = error1.message;
6257
6331
  if (this.config.debugAdTiming) {
6258
6332
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6259
6333
  }
6334
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6335
+ 3,
6336
+ 11
6337
+ ];
6338
+ return [
6339
+ 4,
6340
+ this.tryNextAvailableAdWithRateLimit()
6341
+ ];
6342
+ case 10:
6343
+ _state.sent();
6344
+ return [
6345
+ 2
6346
+ ];
6347
+ case 11:
6260
6348
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6261
6349
  this.consecutiveFailures++;
6262
6350
  }
@@ -6264,21 +6352,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6264
6352
  4,
6265
6353
  this.tryNextAvailableAdWithRateLimit()
6266
6354
  ];
6267
- case 8:
6355
+ case 12:
6268
6356
  _state.sent();
6269
6357
  return [
6270
6358
  3,
6271
- 9
6359
+ 13
6272
6360
  ];
6273
- case 9:
6361
+ case 13:
6274
6362
  return [
6275
6363
  2
6276
6364
  ];
6277
- case 10:
6365
+ case 14:
6278
6366
  maxRetries = 3;
6279
6367
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6280
6368
  3,
6281
- 13
6369
+ 17
6282
6370
  ];
6283
6371
  if (this.config.debugAdTiming) {
6284
6372
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6289,18 +6377,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6289
6377
  return setTimeout(resolve, 500);
6290
6378
  })
6291
6379
  ];
6292
- case 11:
6380
+ case 15:
6293
6381
  _state.sent();
6294
6382
  return [
6295
6383
  4,
6296
6384
  this.tryNextAvailableAd(retryCount + 1)
6297
6385
  ];
6298
- case 12:
6386
+ case 16:
6299
6387
  _state.sent();
6300
6388
  return [
6301
6389
  2
6302
6390
  ];
6303
- case 13:
6391
+ case 17:
6304
6392
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6305
6393
  this.showPlaceholderAndWaitForAds();
6306
6394
  } else {
@@ -6363,7 +6451,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6363
6451
  case 1:
6364
6452
  if (!(i < maxChecks)) return [
6365
6453
  3,
6366
- 10
6454
+ 12
6367
6455
  ];
6368
6456
  return [
6369
6457
  4,
@@ -6384,15 +6472,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6384
6472
  }
6385
6473
  return [
6386
6474
  3,
6387
- 10
6475
+ 12
6476
+ ];
6477
+ }
6478
+ if (!this.hasTimeToStartAnotherAd()) {
6479
+ if (this.config.debugAdTiming) {
6480
+ console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Not enough time remaining during placeholder wait, ending ad break");
6481
+ }
6482
+ return [
6483
+ 3,
6484
+ 12
6388
6485
  ];
6389
6486
  }
6390
6487
  _state.label = 3;
6391
6488
  case 3:
6392
6489
  if (!(this.adRequestQueue.length > 0)) return [
6393
6490
  3,
6394
- 9
6491
+ 11
6395
6492
  ];
6493
+ if (!this.hasTimeToStartAnotherAd()) {
6494
+ return [
6495
+ 3,
6496
+ 11
6497
+ ];
6498
+ }
6396
6499
  nextAdUrl = this.adRequestQueue.shift();
6397
6500
  if (!nextAdUrl) {
6398
6501
  return [
@@ -6417,16 +6520,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6417
6520
  }
6418
6521
  this.isShowingPlaceholder = false;
6419
6522
  this.adPlayer.hidePlaceholder();
6420
- this.currentAdIndex++;
6421
- this.totalAdRequestsInBreak++;
6422
- this.lastAdRequestTime = Date.now();
6423
6523
  _state.label = 4;
6424
6524
  case 4:
6425
6525
  _state.trys.push([
6426
6526
  4,
6427
6527
  6,
6428
6528
  ,
6429
- 8
6529
+ 10
6430
6530
  ]);
6431
6531
  return [
6432
6532
  4,
@@ -6437,11 +6537,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6437
6537
  this.consecutiveFailures = 0;
6438
6538
  return [
6439
6539
  3,
6440
- 8
6540
+ 10
6441
6541
  ];
6442
6542
  case 6:
6443
6543
  error = _state.sent();
6444
6544
  errorMessage = error.message;
6545
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6546
+ 3,
6547
+ 8
6548
+ ];
6549
+ return [
6550
+ 4,
6551
+ this.tryNextAvailableAdWithRateLimit()
6552
+ ];
6553
+ case 7:
6554
+ _state.sent();
6555
+ return [
6556
+ 2
6557
+ ];
6558
+ case 8:
6445
6559
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6446
6560
  this.consecutiveFailures++;
6447
6561
  }
@@ -6449,23 +6563,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6449
6563
  4,
6450
6564
  this.tryNextAvailableAdWithRateLimit()
6451
6565
  ];
6452
- case 7:
6566
+ case 9:
6453
6567
  _state.sent();
6454
6568
  return [
6455
6569
  3,
6456
- 8
6570
+ 10
6457
6571
  ];
6458
- case 8:
6572
+ case 10:
6459
6573
  return [
6460
6574
  2
6461
6575
  ];
6462
- case 9:
6576
+ case 11:
6463
6577
  i++;
6464
6578
  return [
6465
6579
  3,
6466
6580
  1
6467
6581
  ];
6468
- case 10:
6582
+ case 12:
6469
6583
  if (this.config.debugAdTiming) {
6470
6584
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6471
6585
  }
@@ -6675,21 +6789,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6675
6789
  if (!this.inAdBreak) return;
6676
6790
  this.adStopTimerId = void 0;
6677
6791
  var adPlaying = this.adPlayer.isAdPlaying();
6678
- var pendingAds = this.adPodQueue.length > 0;
6679
6792
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
6680
6793
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
6681
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
6794
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
6682
6795
  var elapsedSinceStartMs = 0;
6683
6796
  if (this.currentAdBreakStartWallClockMs != null) {
6684
6797
  elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
6685
6798
  }
6686
6799
  var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
6687
6800
  var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
6688
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
6801
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
6689
6802
  if (shouldExtendAdBreak) {
6690
6803
  this.scheduleAdStopCountdown(checkIntervalMs);
6691
6804
  return;
6692
6805
  }
6806
+ if (this.config.debugAdTiming) {
6807
+ console.log("[StormcloudVideoPlayer] ⏱️ Ad break boundary reached (overrun=".concat(overrunMs, "ms) - hard-stopping ads and resuming stream"));
6808
+ }
6693
6809
  if (adPlaying) {
6694
6810
  this.adPlayer.stop().catch(function() {});
6695
6811
  }
@@ -6935,6 +7051,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6935
7051
  2
6936
7052
  ];
6937
7053
  }
7054
+ if (!this.ensureLoadedAdFitsBudget()) {
7055
+ this.rejectLoadedAdForDuration();
7056
+ this.recreateAdController();
7057
+ throw new Error("ad_duration_exceeds_budget");
7058
+ }
6938
7059
  _state.label = 4;
6939
7060
  case 4:
6940
7061
  _state.trys.push([
@@ -6950,6 +7071,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6950
7071
  ];
6951
7072
  case 5:
6952
7073
  _state.sent();
7074
+ this.markAdStarted();
6953
7075
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6954
7076
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6955
7077
  }
@@ -6981,6 +7103,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6981
7103
  ,
6982
7104
  10
6983
7105
  ]);
7106
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7107
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7108
+ throw playError;
7109
+ }
6984
7110
  this.clearAdFailsafeTimer();
6985
7111
  preservedMuted = this.adPlayer.getOriginalMutedState();
6986
7112
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -6991,14 +7117,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6991
7117
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6992
7118
  this.adPlayer.resume();
6993
7119
  this.consecutiveFailures = 0;
6994
- this.currentAdIndex++;
6995
- this.totalAdRequestsInBreak++;
6996
7120
  return [
6997
7121
  4,
6998
7122
  this.adPlayer.play()
6999
7123
  ];
7000
7124
  case 8:
7001
7125
  _state.sent();
7126
+ this.markAdStarted();
7002
7127
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7003
7128
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7004
7129
  }
@@ -7059,6 +7184,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7059
7184
  ,
7060
7185
  16
7061
7186
  ]);
7187
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7188
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7189
+ throw error;
7190
+ }
7062
7191
  this.clearAdRequestWatchdog();
7063
7192
  this.clearAdFailsafeTimer();
7064
7193
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7070,14 +7199,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7070
7199
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7071
7200
  this.adPlayer.resume();
7072
7201
  this.consecutiveFailures = 0;
7073
- this.currentAdIndex++;
7074
- this.totalAdRequestsInBreak++;
7075
7202
  return [
7076
7203
  4,
7077
7204
  this.adPlayer.play()
7078
7205
  ];
7079
7206
  case 14:
7080
7207
  _state.sent();
7208
+ this.markAdStarted();
7081
7209
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7082
7210
  return [
7083
7211
  2
@@ -7138,6 +7266,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7138
7266
  this.adRequestQueue = [];
7139
7267
  this.inAdBreak = false;
7140
7268
  this.adDetectSentForCurrentBreak = false;
7269
+ this.adBreakPlayedDurationMs = 0;
7141
7270
  this.activeScte35BreakKey = void 0;
7142
7271
  this.scheduledScte35BreakKey = void 0;
7143
7272
  this.expectedAdBreakDurationMs = void 0;
@@ -7195,11 +7324,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7195
7324
  this.handleAdPodComplete();
7196
7325
  return;
7197
7326
  }
7198
- if (remaining > 500) {
7327
+ if (this.hasTimeToStartAnotherAd()) {
7199
7328
  this.tryNextAvailableAdWithRateLimit();
7200
7329
  } else {
7201
7330
  if (this.config.debugAdTiming) {
7202
- console.log("[AD-ERROR] Ad failed and no time remaining. Failed URLs:", this.failedVastUrls.size);
7331
+ console.log("[AD-ERROR] Ad failed and not enough time remaining for another ad. Failed URLs:", this.failedVastUrls.size);
7203
7332
  }
7204
7333
  this.handleAdPodComplete();
7205
7334
  }
@@ -7327,13 +7456,134 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7327
7456
  }
7328
7457
  },
7329
7458
  {
7330
- key: "getRemainingAdMs",
7331
- value: function getRemainingAdMs() {
7332
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7459
+ key: "getWallClockRemainingAdMs",
7460
+ value: function getWallClockRemainingAdMs() {
7461
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {
7462
+ return 0;
7463
+ }
7333
7464
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7334
7465
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7335
7466
  }
7336
7467
  },
7468
+ {
7469
+ key: "getDurationBudgetRemainingMs",
7470
+ value: function getDurationBudgetRemainingMs() {
7471
+ if (this.expectedAdBreakDurationMs == null) {
7472
+ return 0;
7473
+ }
7474
+ return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);
7475
+ }
7476
+ },
7477
+ {
7478
+ key: "getLoadedAdDurationMs",
7479
+ value: function getLoadedAdDurationMs() {
7480
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7481
+ var _adController_getLoadedAdDuration;
7482
+ var durationSec = (_adController_getLoadedAdDuration = adController.getLoadedAdDuration) === null || _adController_getLoadedAdDuration === void 0 ? void 0 : _adController_getLoadedAdDuration.call(adController);
7483
+ if (durationSec == null || durationSec <= 0) {
7484
+ return 0;
7485
+ }
7486
+ return Math.round(durationSec * 1e3);
7487
+ }
7488
+ },
7489
+ {
7490
+ key: "adFitsRemainingBudget",
7491
+ value: function adFitsRemainingBudget(durationMs) {
7492
+ var adController = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.adPlayer;
7493
+ if (durationMs <= 0) {
7494
+ return true;
7495
+ }
7496
+ return durationMs <= this.getDurationBudgetRemainingMs();
7497
+ }
7498
+ },
7499
+ {
7500
+ key: "logAdDurationDecision",
7501
+ value: function logAdDurationDecision(accepted, durationMs) {
7502
+ var adController = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : this.adPlayer;
7503
+ var _ref;
7504
+ var _adController_getLoadedAdId;
7505
+ if (!this.config.debugAdTiming) {
7506
+ return;
7507
+ }
7508
+ var remainingBudget = this.getDurationBudgetRemainingMs();
7509
+ var adId = (_ref = (_adController_getLoadedAdId = adController.getLoadedAdId) === null || _adController_getLoadedAdId === void 0 ? void 0 : _adController_getLoadedAdId.call(adController)) !== null && _ref !== void 0 ? _ref : "unknown";
7510
+ if (accepted) {
7511
+ console.log("[AD-DURATION] Accepted ad id=".concat(adId, ", duration=").concat(durationMs, "ms, budget remaining after play=").concat(Math.max(0, remainingBudget - durationMs), "ms"));
7512
+ } else {
7513
+ console.log("[AD-DURATION] Rejected ad id=".concat(adId, ", duration=").concat(durationMs, "ms exceeds remaining budget=").concat(remainingBudget, "ms — requesting another ad"));
7514
+ }
7515
+ }
7516
+ },
7517
+ {
7518
+ key: "recordPlayedAdDuration",
7519
+ value: function recordPlayedAdDuration() {
7520
+ var durationMs = this.getLoadedAdDurationMs();
7521
+ if (durationMs <= 0) {
7522
+ return;
7523
+ }
7524
+ this.adBreakPlayedDurationMs += durationMs;
7525
+ if (this.config.debugAdTiming) {
7526
+ var _this_expectedAdBreakDurationMs;
7527
+ 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"));
7528
+ }
7529
+ }
7530
+ },
7531
+ {
7532
+ key: "ensureLoadedAdFitsBudget",
7533
+ value: function ensureLoadedAdFitsBudget() {
7534
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7535
+ var durationMs = this.getLoadedAdDurationMs(adController);
7536
+ var fits = this.adFitsRemainingBudget(durationMs, adController);
7537
+ this.logAdDurationDecision(fits, durationMs, adController);
7538
+ return fits;
7539
+ }
7540
+ },
7541
+ {
7542
+ key: "rejectLoadedAdForDuration",
7543
+ value: function rejectLoadedAdForDuration() {
7544
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7545
+ try {
7546
+ adController.destroy();
7547
+ } catch (unused) {}
7548
+ }
7549
+ },
7550
+ {
7551
+ key: "markAdStarted",
7552
+ value: function markAdStarted() {
7553
+ this.currentAdIndex++;
7554
+ this.totalAdsInBreak = this.currentAdIndex;
7555
+ this.totalAdRequestsInBreak++;
7556
+ this.lastAdRequestTime = Date.now();
7557
+ }
7558
+ },
7559
+ {
7560
+ key: "getRemainingAdMs",
7561
+ value: function getRemainingAdMs() {
7562
+ var wallClockRemaining = this.getWallClockRemainingAdMs();
7563
+ var budgetRemaining = this.getDurationBudgetRemainingMs();
7564
+ if (this.expectedAdBreakDurationMs == null) {
7565
+ return wallClockRemaining;
7566
+ }
7567
+ return Math.min(wallClockRemaining, budgetRemaining);
7568
+ }
7569
+ },
7570
+ {
7571
+ key: "getMinRemainingMsToStartAd",
7572
+ value: function getMinRemainingMsToStartAd() {
7573
+ var configured = this.config.minRemainingMsToStartAd;
7574
+ if (typeof configured === "number" && configured >= 0) {
7575
+ return configured;
7576
+ }
7577
+ return this.defaultMinRemainingMsToStartAd;
7578
+ }
7579
+ },
7580
+ {
7581
+ key: "hasTimeToStartAnotherAd",
7582
+ value: function hasTimeToStartAnotherAd() {
7583
+ var minRemaining = this.getMinRemainingMsToStartAd();
7584
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7585
+ }
7586
+ },
7337
7587
  {
7338
7588
  key: "findBreakForTime",
7339
7589
  value: function findBreakForTime(nowMs) {