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.
@@ -1278,6 +1278,12 @@ function createHlsAdPlayer(contentVideo, options) {
1278
1278
  contentVideo.style.visibility = "visible";
1279
1279
  contentVideo.style.opacity = "1";
1280
1280
  }
1281
+ },
1282
+ getLoadedAdDuration: function getLoadedAdDuration() {
1283
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration;
1284
+ },
1285
+ getLoadedAdId: function getLoadedAdId() {
1286
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.id;
1281
1287
  }
1282
1288
  };
1283
1289
  }
@@ -2674,6 +2680,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2674
2680
  this.maxConsecutiveFailures = 5;
2675
2681
  this.lastAdRequestTime = 0;
2676
2682
  this.minAdRequestIntervalMs = 3e3;
2683
+ this.defaultMinRemainingMsToStartAd = 4e3;
2677
2684
  this.backoffBaseMs = 1e3;
2678
2685
  this.maxBackoffMs = 15e3;
2679
2686
  this.globalConsecutiveNoFills = 0;
@@ -2689,6 +2696,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2689
2696
  this.preloadPoolLoopRunning = false;
2690
2697
  this.adDetectSentForCurrentBreak = false;
2691
2698
  this.adBreakEpoch = 0;
2699
+ this.adBreakPlayedDurationMs = 0;
2692
2700
  this.palPlaybackStarted = false;
2693
2701
  this.streamCorrelator = generateCorrelator();
2694
2702
  this.consentSignals = {};
@@ -3185,6 +3193,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3185
3193
  adIndex: _this.currentAdIndex,
3186
3194
  timestamp: /* @__PURE__ */ new Date().toISOString()
3187
3195
  }).catch(function() {});
3196
+ _this.recordPlayedAdDuration();
3188
3197
  var remaining = _this.getRemainingAdMs();
3189
3198
  _this.consecutiveFailures = 0;
3190
3199
  if (_this.config.debugAdTiming) {
@@ -3193,7 +3202,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3193
3202
  } : {});
3194
3203
  }
3195
3204
  if (_this.inAdBreak) {
3196
- if (remaining > 500) {
3205
+ if (_this.hasTimeToStartAnotherAd()) {
3197
3206
  _this.tryNextAvailableAdWithRateLimit();
3198
3207
  } else {
3199
3208
  _this.handleAdPodComplete();
@@ -3260,13 +3269,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3260
3269
  return;
3261
3270
  }
3262
3271
  _this.consecutiveFailures = 0;
3272
+ _this.recordPlayedAdDuration();
3263
3273
  var remaining = _this.getRemainingAdMs();
3264
3274
  if (_this.config.debugAdTiming) {
3265
3275
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
3266
3276
  queuedUrls: _to_consumable_array(_this.adRequestQueue)
3267
3277
  } : {});
3268
3278
  }
3269
- if (remaining > 500) {
3279
+ if (_this.hasTimeToStartAnotherAd()) {
3270
3280
  _this.tryNextAvailableAdWithRateLimit();
3271
3281
  } else {
3272
3282
  _this.handleAdPodComplete();
@@ -3966,7 +3976,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3966
3976
  var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
3967
3977
  this.scheduleAdStopCountdown(remainingMs1);
3968
3978
  }
3969
- if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
3979
+ if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0 && this.hasTimeToStartAnotherAd()) {
3970
3980
  this.tryNextAvailableAdWithRateLimit();
3971
3981
  }
3972
3982
  return;
@@ -5265,7 +5275,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5265
5275
  epoch = this.adBreakEpoch;
5266
5276
  loadPromise = function() {
5267
5277
  return _async_to_generator(function() {
5268
- var _this, hasAdError, adErrorPayload, errorListenerCleanup, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, lateErrorListener, error;
5278
+ var _this, hasAdError, adErrorPayload, errorListenerCleanup, _preloadAd_getLoadedAdDuration, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, loadedDuration, lateErrorListener, _poolEntry_durationSeconds, error;
5269
5279
  return _ts_generator(this, function(_state) {
5270
5280
  switch(_state.label){
5271
5281
  case 0:
@@ -5361,6 +5371,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5361
5371
  isReady: true,
5362
5372
  loadPromise: Promise.resolve()
5363
5373
  };
5374
+ loadedDuration = (_preloadAd_getLoadedAdDuration = preloadAd.getLoadedAdDuration) === null || _preloadAd_getLoadedAdDuration === void 0 ? void 0 : _preloadAd_getLoadedAdDuration.call(preloadAd);
5375
+ if (loadedDuration != null && loadedDuration > 0) {
5376
+ poolEntry.durationSeconds = loadedDuration;
5377
+ }
5364
5378
  lateErrorListener = function lateErrorListener(payload) {
5365
5379
  var index = _this.preloadPool.findIndex(function(entry) {
5366
5380
  return entry.vastUrl === vastUrl;
@@ -5378,7 +5392,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5378
5392
  preloadAd.on("ad_error", lateErrorListener);
5379
5393
  this.preloadPool.push(poolEntry);
5380
5394
  if (this.config.debugAdTiming) {
5381
- console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ")"));
5395
+ ;
5396
+ 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)"));
5382
5397
  }
5383
5398
  if (errorListenerCleanup) {
5384
5399
  errorListenerCleanup();
@@ -5425,7 +5440,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5425
5440
  key: "preloadPoolLoop",
5426
5441
  value: function preloadPoolLoop(baseVastUrl) {
5427
5442
  return _async_to_generator(function() {
5428
- var epoch, remaining, newUrls, newUrl;
5443
+ var epoch, newUrls, newUrl;
5429
5444
  return _ts_generator(this, function(_state) {
5430
5445
  switch(_state.label){
5431
5446
  case 0:
@@ -5492,14 +5507,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5492
5507
  8
5493
5508
  ];
5494
5509
  }
5495
- if (this.inAdBreak) {
5496
- remaining = this.getRemainingAdMs();
5497
- if (remaining <= 0) {
5498
- return [
5499
- 3,
5500
- 8
5501
- ];
5510
+ if (this.inAdBreak && !this.hasTimeToStartAnotherAd()) {
5511
+ if (this.config.debugAdTiming) {
5512
+ console.log("[PRELOAD-POOL] Not enough ad break time left, stopping preload");
5502
5513
  }
5514
+ return [
5515
+ 3,
5516
+ 8
5517
+ ];
5503
5518
  }
5504
5519
  newUrls = this.generateVastUrlsWithCorrelators(baseVastUrl, 1);
5505
5520
  newUrl = newUrls[0];
@@ -5659,8 +5674,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5659
5674
  }
5660
5675
  this.inAdBreak = true;
5661
5676
  this.currentAdBreakStartWallClockMs = Date.now();
5677
+ this.adBreakPlayedDurationMs = 0;
5662
5678
  this.currentAdIndex = 0;
5663
- this.totalAdsInBreak = 1;
5679
+ this.totalAdsInBreak = 0;
5664
5680
  this.adPodQueue = [];
5665
5681
  this.showAds = true;
5666
5682
  this.showPlaceholderLayer();
@@ -5714,14 +5730,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5714
5730
  case 1:
5715
5731
  _state.trys.push([
5716
5732
  1,
5717
- 8,
5733
+ 12,
5718
5734
  ,
5719
- 14
5735
+ 20
5720
5736
  ]);
5721
5737
  if (!(usePreloadedAd && preloadedController)) return [
5738
+ 3,
5739
+ 5
5740
+ ];
5741
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5722
5742
  3,
5723
5743
  3
5724
5744
  ];
5745
+ this.rejectLoadedAdForDuration(preloadedController);
5746
+ this.startContinuousFetching(baseVastUrl);
5747
+ return [
5748
+ 4,
5749
+ this.tryNextAvailableAdWithRateLimit()
5750
+ ];
5751
+ case 2:
5752
+ _state.sent();
5753
+ return [
5754
+ 2
5755
+ ];
5756
+ case 3:
5725
5757
  preservedMuted = this.adPlayer.getOriginalMutedState();
5726
5758
  preservedVolume = this.adPlayer.getOriginalVolume();
5727
5759
  this.adPlayer.destroy();
@@ -5734,7 +5766,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5734
5766
  }
5735
5767
  this.adPlayer.resume();
5736
5768
  this.consecutiveFailures = 0;
5737
- this.currentAdIndex++;
5738
5769
  this.startContinuousFetching(baseVastUrl);
5739
5770
  if (!this.preloadPoolActive) {
5740
5771
  this.preloadPoolActive = true;
@@ -5744,8 +5775,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5744
5775
  4,
5745
5776
  this.adPlayer.play()
5746
5777
  ];
5747
- case 2:
5778
+ case 4:
5748
5779
  _state.sent();
5780
+ this.markAdStarted();
5749
5781
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5750
5782
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5751
5783
  }
@@ -5753,21 +5785,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5753
5785
  this.adPlayer.setAdVolume(adVolume);
5754
5786
  return [
5755
5787
  3,
5756
- 7
5788
+ 11
5757
5789
  ];
5758
- case 3:
5790
+ case 5:
5759
5791
  return [
5760
5792
  4,
5761
5793
  this.enforceGlobalRateLimit()
5762
5794
  ];
5763
- case 4:
5795
+ case 6:
5764
5796
  _state.sent();
5765
5797
  this.lastAdRequestTime = Date.now();
5766
5798
  return [
5767
5799
  4,
5768
5800
  this.adPlayer.requestAds(firstAdUrl)
5769
5801
  ];
5770
- case 5:
5802
+ case 7:
5771
5803
  _state.sent();
5772
5804
  sendAdLoadedTracking(this.config.licenseKey, {
5773
5805
  source: "hls",
@@ -5777,8 +5809,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5777
5809
  if (this.config.debugAdTiming) {
5778
5810
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
5779
5811
  }
5812
+ if (!!this.ensureLoadedAdFitsBudget()) return [
5813
+ 3,
5814
+ 9
5815
+ ];
5816
+ this.rejectLoadedAdForDuration();
5817
+ this.recreateAdController();
5818
+ this.startContinuousFetching(baseVastUrl);
5819
+ return [
5820
+ 4,
5821
+ this.tryNextAvailableAdWithRateLimit()
5822
+ ];
5823
+ case 8:
5824
+ _state.sent();
5825
+ return [
5826
+ 2
5827
+ ];
5828
+ case 9:
5780
5829
  this.consecutiveFailures = 0;
5781
- this.currentAdIndex++;
5782
5830
  this.startContinuousFetching(baseVastUrl);
5783
5831
  if (!this.preloadPoolActive) {
5784
5832
  this.startPreloadPool(baseVastUrl, []);
@@ -5787,44 +5835,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5787
5835
  4,
5788
5836
  this.adPlayer.play()
5789
5837
  ];
5790
- case 6:
5838
+ case 10:
5791
5839
  _state.sent();
5840
+ this.markAdStarted();
5792
5841
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5793
5842
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5794
5843
  }
5795
5844
  adVolume1 = currentMuted ? 0 : currentVolume;
5796
5845
  this.adPlayer.setAdVolume(adVolume1);
5797
- _state.label = 7;
5798
- case 7:
5846
+ _state.label = 11;
5847
+ case 11:
5799
5848
  return [
5800
5849
  3,
5801
- 14
5850
+ 20
5802
5851
  ];
5803
- case 8:
5852
+ case 12:
5804
5853
  error = _state.sent();
5805
5854
  if (this.config.debugAdTiming) {
5806
5855
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
5807
5856
  }
5808
5857
  if (!!usePreloadedAd) return [
5809
5858
  3,
5810
- 12
5859
+ 18
5811
5860
  ];
5812
5861
  fallbackPreloaded = this.getPreloadedAd();
5813
5862
  if (!fallbackPreloaded) return [
5814
5863
  3,
5815
- 12
5864
+ 18
5816
5865
  ];
5817
5866
  if (this.config.debugAdTiming) {
5818
5867
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
5819
5868
  }
5820
- _state.label = 9;
5821
- case 9:
5869
+ _state.label = 13;
5870
+ case 13:
5822
5871
  _state.trys.push([
5823
- 9,
5824
- 11,
5872
+ 13,
5873
+ 17,
5825
5874
  ,
5826
- 12
5875
+ 18
5827
5876
  ]);
5877
+ if (!!this.ensureLoadedAdFitsBudget(fallbackPreloaded.adController)) return [
5878
+ 3,
5879
+ 15
5880
+ ];
5881
+ this.rejectLoadedAdForDuration(fallbackPreloaded.adController);
5882
+ this.startContinuousFetching(baseVastUrl);
5883
+ return [
5884
+ 4,
5885
+ this.tryNextAvailableAdWithRateLimit()
5886
+ ];
5887
+ case 14:
5888
+ _state.sent();
5889
+ return [
5890
+ 2
5891
+ ];
5892
+ case 15:
5828
5893
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
5829
5894
  preservedVolume1 = this.adPlayer.getOriginalVolume();
5830
5895
  this.adPlayer.destroy();
@@ -5834,7 +5899,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5834
5899
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
5835
5900
  this.adPlayer.resume();
5836
5901
  this.consecutiveFailures = 0;
5837
- this.currentAdIndex++;
5838
5902
  this.startContinuousFetching(baseVastUrl);
5839
5903
  if (!this.preloadPoolActive) {
5840
5904
  this.preloadPoolActive = true;
@@ -5844,8 +5908,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5844
5908
  4,
5845
5909
  this.adPlayer.play()
5846
5910
  ];
5847
- case 10:
5911
+ case 16:
5848
5912
  _state.sent();
5913
+ this.markAdStarted();
5849
5914
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5850
5915
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5851
5916
  }
@@ -5854,16 +5919,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5854
5919
  return [
5855
5920
  2
5856
5921
  ];
5857
- case 11:
5922
+ case 17:
5858
5923
  fallbackError = _state.sent();
5859
5924
  if (this.config.debugAdTiming) {
5860
5925
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
5861
5926
  }
5862
5927
  return [
5863
5928
  3,
5864
- 12
5929
+ 18
5865
5930
  ];
5866
- case 12:
5931
+ case 18:
5867
5932
  if (this.isTemporaryAdError(error)) {
5868
5933
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
5869
5934
  if (this.config.debugAdTiming) {
@@ -5881,13 +5946,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5881
5946
  4,
5882
5947
  this.tryNextAvailableAdWithRateLimit()
5883
5948
  ];
5884
- case 13:
5949
+ case 19:
5885
5950
  _state.sent();
5886
5951
  return [
5887
5952
  3,
5888
- 14
5953
+ 20
5889
5954
  ];
5890
- case 14:
5955
+ case 20:
5891
5956
  return [
5892
5957
  2
5893
5958
  ];
@@ -5928,10 +5993,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5928
5993
  return _ts_generator(this, function(_state) {
5929
5994
  switch(_state.label){
5930
5995
  case 0:
5931
- remaining = _this.getRemainingAdMs();
5932
- if (remaining <= 0) {
5996
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
5997
+ if (remaining <= _this.getMinRemainingMsToStartAd()) {
5933
5998
  if (_this.config.debugAdTiming) {
5934
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Ad break time expired, stopping URL generation");
5999
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (wall=".concat(_this.getWallClockRemainingAdMs(), "ms, budget=").concat(_this.getDurationBudgetRemainingMs(), "ms), stopping URL generation"));
5935
6000
  }
5936
6001
  return [
5937
6002
  2,
@@ -6013,7 +6078,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6013
6078
  });
6014
6079
  }
6015
6080
  _this.adRequestQueue.push(newAdUrl);
6016
- _this.totalAdsInBreak++;
6017
6081
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
6018
6082
  return [
6019
6083
  4,
@@ -6151,9 +6215,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6151
6215
  ];
6152
6216
  }
6153
6217
  remaining = this.getRemainingAdMs();
6154
- if (remaining <= 500) {
6218
+ if (!this.hasTimeToStartAnotherAd()) {
6155
6219
  if (this.config.debugAdTiming) {
6156
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F No time remaining, ending ad break");
6220
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough time remaining (".concat(remaining, "ms) to start another ad, ending ad break"));
6157
6221
  }
6158
6222
  this.handleAdPodComplete();
6159
6223
  return [
@@ -6182,7 +6246,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6182
6246
  preloaded = this.getPreloadedAd();
6183
6247
  if (!preloaded) return [
6184
6248
  3,
6185
- 4
6249
+ 6
6186
6250
  ];
6187
6251
  if (this.config.debugAdTiming) {
6188
6252
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6196,10 +6260,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6196
6260
  case 1:
6197
6261
  _state.trys.push([
6198
6262
  1,
6199
- 3,
6263
+ 5,
6200
6264
  ,
6201
- 4
6265
+ 6
6202
6266
  ]);
6267
+ if (!!this.ensureLoadedAdFitsBudget(preloaded.adController)) return [
6268
+ 3,
6269
+ 3
6270
+ ];
6271
+ this.rejectLoadedAdForDuration(preloaded.adController);
6272
+ return [
6273
+ 4,
6274
+ this.tryNextAvailableAdWithRateLimit()
6275
+ ];
6276
+ case 2:
6277
+ _state.sent();
6278
+ return [
6279
+ 2
6280
+ ];
6281
+ case 3:
6203
6282
  preservedMuted = this.adPlayer.getOriginalMutedState();
6204
6283
  preservedVolume = this.adPlayer.getOriginalVolume();
6205
6284
  this.adPlayer.destroy();
@@ -6208,15 +6287,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6208
6287
  this.attachImaEventListeners();
6209
6288
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6210
6289
  this.adPlayer.resume();
6211
- this.currentAdIndex++;
6212
- this.totalAdRequestsInBreak++;
6213
- this.lastAdRequestTime = Date.now();
6214
6290
  return [
6215
6291
  4,
6216
6292
  this.adPlayer.play()
6217
6293
  ];
6218
- case 2:
6294
+ case 4:
6219
6295
  _state.sent();
6296
+ this.markAdStarted();
6220
6297
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6221
6298
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6222
6299
  }
@@ -6225,25 +6302,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6225
6302
  return [
6226
6303
  2
6227
6304
  ];
6228
- case 3:
6305
+ case 5:
6229
6306
  error = _state.sent();
6230
6307
  if (this.config.debugAdTiming) {
6231
6308
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6232
6309
  }
6233
6310
  return [
6234
6311
  3,
6235
- 4
6312
+ 6
6236
6313
  ];
6237
- case 4:
6314
+ case 6:
6238
6315
  if (!(this.adRequestQueue.length > 0)) return [
6239
6316
  3,
6240
- 10
6317
+ 14
6241
6318
  ];
6242
6319
  nextAdUrl = this.adRequestQueue.shift();
6243
6320
  if (!nextAdUrl) {
6244
6321
  return [
6245
6322
  3,
6246
- 4
6323
+ 6
6247
6324
  ];
6248
6325
  }
6249
6326
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6252,7 +6329,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6252
6329
  }
6253
6330
  return [
6254
6331
  3,
6255
- 4
6332
+ 6
6256
6333
  ];
6257
6334
  }
6258
6335
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6261,40 +6338,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6261
6338
  }
6262
6339
  return [
6263
6340
  3,
6264
- 4
6341
+ 6
6265
6342
  ];
6266
6343
  }
6267
6344
  if (this.config.debugAdTiming) {
6268
- 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)"));
6345
+ console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6269
6346
  }
6270
- this.currentAdIndex++;
6271
- this.totalAdRequestsInBreak++;
6272
- this.lastAdRequestTime = Date.now();
6273
- _state.label = 5;
6274
- case 5:
6347
+ _state.label = 7;
6348
+ case 7:
6275
6349
  _state.trys.push([
6276
- 5,
6277
6350
  7,
6351
+ 9,
6278
6352
  ,
6279
- 9
6353
+ 13
6280
6354
  ]);
6281
6355
  return [
6282
6356
  4,
6283
6357
  this.playSingleAd(nextAdUrl)
6284
6358
  ];
6285
- case 6:
6359
+ case 8:
6286
6360
  _state.sent();
6287
6361
  this.consecutiveFailures = 0;
6288
6362
  return [
6289
6363
  3,
6290
- 9
6364
+ 13
6291
6365
  ];
6292
- case 7:
6366
+ case 9:
6293
6367
  error1 = _state.sent();
6294
6368
  errorMessage = error1.message;
6295
6369
  if (this.config.debugAdTiming) {
6296
6370
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6297
6371
  }
6372
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6373
+ 3,
6374
+ 11
6375
+ ];
6376
+ return [
6377
+ 4,
6378
+ this.tryNextAvailableAdWithRateLimit()
6379
+ ];
6380
+ case 10:
6381
+ _state.sent();
6382
+ return [
6383
+ 2
6384
+ ];
6385
+ case 11:
6298
6386
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6299
6387
  this.consecutiveFailures++;
6300
6388
  }
@@ -6302,21 +6390,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6302
6390
  4,
6303
6391
  this.tryNextAvailableAdWithRateLimit()
6304
6392
  ];
6305
- case 8:
6393
+ case 12:
6306
6394
  _state.sent();
6307
6395
  return [
6308
6396
  3,
6309
- 9
6397
+ 13
6310
6398
  ];
6311
- case 9:
6399
+ case 13:
6312
6400
  return [
6313
6401
  2
6314
6402
  ];
6315
- case 10:
6403
+ case 14:
6316
6404
  maxRetries = 3;
6317
6405
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6318
6406
  3,
6319
- 13
6407
+ 17
6320
6408
  ];
6321
6409
  if (this.config.debugAdTiming) {
6322
6410
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6327,18 +6415,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6327
6415
  return setTimeout(resolve, 500);
6328
6416
  })
6329
6417
  ];
6330
- case 11:
6418
+ case 15:
6331
6419
  _state.sent();
6332
6420
  return [
6333
6421
  4,
6334
6422
  this.tryNextAvailableAd(retryCount + 1)
6335
6423
  ];
6336
- case 12:
6424
+ case 16:
6337
6425
  _state.sent();
6338
6426
  return [
6339
6427
  2
6340
6428
  ];
6341
- case 13:
6429
+ case 17:
6342
6430
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6343
6431
  this.showPlaceholderAndWaitForAds();
6344
6432
  } else {
@@ -6401,7 +6489,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6401
6489
  case 1:
6402
6490
  if (!(i < maxChecks)) return [
6403
6491
  3,
6404
- 10
6492
+ 12
6405
6493
  ];
6406
6494
  return [
6407
6495
  4,
@@ -6422,15 +6510,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6422
6510
  }
6423
6511
  return [
6424
6512
  3,
6425
- 10
6513
+ 12
6514
+ ];
6515
+ }
6516
+ if (!this.hasTimeToStartAnotherAd()) {
6517
+ if (this.config.debugAdTiming) {
6518
+ console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Not enough time remaining during placeholder wait, ending ad break");
6519
+ }
6520
+ return [
6521
+ 3,
6522
+ 12
6426
6523
  ];
6427
6524
  }
6428
6525
  _state.label = 3;
6429
6526
  case 3:
6430
6527
  if (!(this.adRequestQueue.length > 0)) return [
6431
6528
  3,
6432
- 9
6529
+ 11
6433
6530
  ];
6531
+ if (!this.hasTimeToStartAnotherAd()) {
6532
+ return [
6533
+ 3,
6534
+ 11
6535
+ ];
6536
+ }
6434
6537
  nextAdUrl = this.adRequestQueue.shift();
6435
6538
  if (!nextAdUrl) {
6436
6539
  return [
@@ -6455,16 +6558,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6455
6558
  }
6456
6559
  this.isShowingPlaceholder = false;
6457
6560
  this.adPlayer.hidePlaceholder();
6458
- this.currentAdIndex++;
6459
- this.totalAdRequestsInBreak++;
6460
- this.lastAdRequestTime = Date.now();
6461
6561
  _state.label = 4;
6462
6562
  case 4:
6463
6563
  _state.trys.push([
6464
6564
  4,
6465
6565
  6,
6466
6566
  ,
6467
- 8
6567
+ 10
6468
6568
  ]);
6469
6569
  return [
6470
6570
  4,
@@ -6475,11 +6575,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6475
6575
  this.consecutiveFailures = 0;
6476
6576
  return [
6477
6577
  3,
6478
- 8
6578
+ 10
6479
6579
  ];
6480
6580
  case 6:
6481
6581
  error = _state.sent();
6482
6582
  errorMessage = error.message;
6583
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6584
+ 3,
6585
+ 8
6586
+ ];
6587
+ return [
6588
+ 4,
6589
+ this.tryNextAvailableAdWithRateLimit()
6590
+ ];
6591
+ case 7:
6592
+ _state.sent();
6593
+ return [
6594
+ 2
6595
+ ];
6596
+ case 8:
6483
6597
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6484
6598
  this.consecutiveFailures++;
6485
6599
  }
@@ -6487,23 +6601,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6487
6601
  4,
6488
6602
  this.tryNextAvailableAdWithRateLimit()
6489
6603
  ];
6490
- case 7:
6604
+ case 9:
6491
6605
  _state.sent();
6492
6606
  return [
6493
6607
  3,
6494
- 8
6608
+ 10
6495
6609
  ];
6496
- case 8:
6610
+ case 10:
6497
6611
  return [
6498
6612
  2
6499
6613
  ];
6500
- case 9:
6614
+ case 11:
6501
6615
  i++;
6502
6616
  return [
6503
6617
  3,
6504
6618
  1
6505
6619
  ];
6506
- case 10:
6620
+ case 12:
6507
6621
  if (this.config.debugAdTiming) {
6508
6622
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6509
6623
  }
@@ -6713,21 +6827,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6713
6827
  if (!this.inAdBreak) return;
6714
6828
  this.adStopTimerId = void 0;
6715
6829
  var adPlaying = this.adPlayer.isAdPlaying();
6716
- var pendingAds = this.adPodQueue.length > 0;
6717
6830
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
6718
6831
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
6719
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
6832
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
6720
6833
  var elapsedSinceStartMs = 0;
6721
6834
  if (this.currentAdBreakStartWallClockMs != null) {
6722
6835
  elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
6723
6836
  }
6724
6837
  var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
6725
6838
  var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
6726
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
6839
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
6727
6840
  if (shouldExtendAdBreak) {
6728
6841
  this.scheduleAdStopCountdown(checkIntervalMs);
6729
6842
  return;
6730
6843
  }
6844
+ if (this.config.debugAdTiming) {
6845
+ console.log("[StormcloudVideoPlayer] ⏱️ Ad break boundary reached (overrun=".concat(overrunMs, "ms) - hard-stopping ads and resuming stream"));
6846
+ }
6731
6847
  if (adPlaying) {
6732
6848
  this.adPlayer.stop().catch(function() {});
6733
6849
  }
@@ -6973,6 +7089,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6973
7089
  2
6974
7090
  ];
6975
7091
  }
7092
+ if (!this.ensureLoadedAdFitsBudget()) {
7093
+ this.rejectLoadedAdForDuration();
7094
+ this.recreateAdController();
7095
+ throw new Error("ad_duration_exceeds_budget");
7096
+ }
6976
7097
  _state.label = 4;
6977
7098
  case 4:
6978
7099
  _state.trys.push([
@@ -6988,6 +7109,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6988
7109
  ];
6989
7110
  case 5:
6990
7111
  _state.sent();
7112
+ this.markAdStarted();
6991
7113
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6992
7114
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6993
7115
  }
@@ -7019,6 +7141,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7019
7141
  ,
7020
7142
  10
7021
7143
  ]);
7144
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7145
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7146
+ throw playError;
7147
+ }
7022
7148
  this.clearAdFailsafeTimer();
7023
7149
  preservedMuted = this.adPlayer.getOriginalMutedState();
7024
7150
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -7029,14 +7155,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7029
7155
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
7030
7156
  this.adPlayer.resume();
7031
7157
  this.consecutiveFailures = 0;
7032
- this.currentAdIndex++;
7033
- this.totalAdRequestsInBreak++;
7034
7158
  return [
7035
7159
  4,
7036
7160
  this.adPlayer.play()
7037
7161
  ];
7038
7162
  case 8:
7039
7163
  _state.sent();
7164
+ this.markAdStarted();
7040
7165
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7041
7166
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7042
7167
  }
@@ -7097,6 +7222,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7097
7222
  ,
7098
7223
  16
7099
7224
  ]);
7225
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7226
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7227
+ throw error;
7228
+ }
7100
7229
  this.clearAdRequestWatchdog();
7101
7230
  this.clearAdFailsafeTimer();
7102
7231
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7108,14 +7237,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7108
7237
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7109
7238
  this.adPlayer.resume();
7110
7239
  this.consecutiveFailures = 0;
7111
- this.currentAdIndex++;
7112
- this.totalAdRequestsInBreak++;
7113
7240
  return [
7114
7241
  4,
7115
7242
  this.adPlayer.play()
7116
7243
  ];
7117
7244
  case 14:
7118
7245
  _state.sent();
7246
+ this.markAdStarted();
7119
7247
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7120
7248
  return [
7121
7249
  2
@@ -7176,6 +7304,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7176
7304
  this.adRequestQueue = [];
7177
7305
  this.inAdBreak = false;
7178
7306
  this.adDetectSentForCurrentBreak = false;
7307
+ this.adBreakPlayedDurationMs = 0;
7179
7308
  this.activeScte35BreakKey = void 0;
7180
7309
  this.scheduledScte35BreakKey = void 0;
7181
7310
  this.expectedAdBreakDurationMs = void 0;
@@ -7233,11 +7362,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7233
7362
  this.handleAdPodComplete();
7234
7363
  return;
7235
7364
  }
7236
- if (remaining > 500) {
7365
+ if (this.hasTimeToStartAnotherAd()) {
7237
7366
  this.tryNextAvailableAdWithRateLimit();
7238
7367
  } else {
7239
7368
  if (this.config.debugAdTiming) {
7240
- console.log("[AD-ERROR] Ad failed and no time remaining. Failed URLs:", this.failedVastUrls.size);
7369
+ console.log("[AD-ERROR] Ad failed and not enough time remaining for another ad. Failed URLs:", this.failedVastUrls.size);
7241
7370
  }
7242
7371
  this.handleAdPodComplete();
7243
7372
  }
@@ -7365,13 +7494,134 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7365
7494
  }
7366
7495
  },
7367
7496
  {
7368
- key: "getRemainingAdMs",
7369
- value: function getRemainingAdMs() {
7370
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7497
+ key: "getWallClockRemainingAdMs",
7498
+ value: function getWallClockRemainingAdMs() {
7499
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {
7500
+ return 0;
7501
+ }
7371
7502
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7372
7503
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7373
7504
  }
7374
7505
  },
7506
+ {
7507
+ key: "getDurationBudgetRemainingMs",
7508
+ value: function getDurationBudgetRemainingMs() {
7509
+ if (this.expectedAdBreakDurationMs == null) {
7510
+ return 0;
7511
+ }
7512
+ return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);
7513
+ }
7514
+ },
7515
+ {
7516
+ key: "getLoadedAdDurationMs",
7517
+ value: function getLoadedAdDurationMs() {
7518
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7519
+ var _adController_getLoadedAdDuration;
7520
+ var durationSec = (_adController_getLoadedAdDuration = adController.getLoadedAdDuration) === null || _adController_getLoadedAdDuration === void 0 ? void 0 : _adController_getLoadedAdDuration.call(adController);
7521
+ if (durationSec == null || durationSec <= 0) {
7522
+ return 0;
7523
+ }
7524
+ return Math.round(durationSec * 1e3);
7525
+ }
7526
+ },
7527
+ {
7528
+ key: "adFitsRemainingBudget",
7529
+ value: function adFitsRemainingBudget(durationMs) {
7530
+ var adController = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.adPlayer;
7531
+ if (durationMs <= 0) {
7532
+ return true;
7533
+ }
7534
+ return durationMs <= this.getDurationBudgetRemainingMs();
7535
+ }
7536
+ },
7537
+ {
7538
+ key: "logAdDurationDecision",
7539
+ value: function logAdDurationDecision(accepted, durationMs) {
7540
+ var adController = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : this.adPlayer;
7541
+ var _ref;
7542
+ var _adController_getLoadedAdId;
7543
+ if (!this.config.debugAdTiming) {
7544
+ return;
7545
+ }
7546
+ var remainingBudget = this.getDurationBudgetRemainingMs();
7547
+ var adId = (_ref = (_adController_getLoadedAdId = adController.getLoadedAdId) === null || _adController_getLoadedAdId === void 0 ? void 0 : _adController_getLoadedAdId.call(adController)) !== null && _ref !== void 0 ? _ref : "unknown";
7548
+ if (accepted) {
7549
+ console.log("[AD-DURATION] Accepted ad id=".concat(adId, ", duration=").concat(durationMs, "ms, budget remaining after play=").concat(Math.max(0, remainingBudget - durationMs), "ms"));
7550
+ } else {
7551
+ console.log("[AD-DURATION] Rejected ad id=".concat(adId, ", duration=").concat(durationMs, "ms exceeds remaining budget=").concat(remainingBudget, "ms — requesting another ad"));
7552
+ }
7553
+ }
7554
+ },
7555
+ {
7556
+ key: "recordPlayedAdDuration",
7557
+ value: function recordPlayedAdDuration() {
7558
+ var durationMs = this.getLoadedAdDurationMs();
7559
+ if (durationMs <= 0) {
7560
+ return;
7561
+ }
7562
+ this.adBreakPlayedDurationMs += durationMs;
7563
+ if (this.config.debugAdTiming) {
7564
+ var _this_expectedAdBreakDurationMs;
7565
+ 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"));
7566
+ }
7567
+ }
7568
+ },
7569
+ {
7570
+ key: "ensureLoadedAdFitsBudget",
7571
+ value: function ensureLoadedAdFitsBudget() {
7572
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7573
+ var durationMs = this.getLoadedAdDurationMs(adController);
7574
+ var fits = this.adFitsRemainingBudget(durationMs, adController);
7575
+ this.logAdDurationDecision(fits, durationMs, adController);
7576
+ return fits;
7577
+ }
7578
+ },
7579
+ {
7580
+ key: "rejectLoadedAdForDuration",
7581
+ value: function rejectLoadedAdForDuration() {
7582
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7583
+ try {
7584
+ adController.destroy();
7585
+ } catch (unused) {}
7586
+ }
7587
+ },
7588
+ {
7589
+ key: "markAdStarted",
7590
+ value: function markAdStarted() {
7591
+ this.currentAdIndex++;
7592
+ this.totalAdsInBreak = this.currentAdIndex;
7593
+ this.totalAdRequestsInBreak++;
7594
+ this.lastAdRequestTime = Date.now();
7595
+ }
7596
+ },
7597
+ {
7598
+ key: "getRemainingAdMs",
7599
+ value: function getRemainingAdMs() {
7600
+ var wallClockRemaining = this.getWallClockRemainingAdMs();
7601
+ var budgetRemaining = this.getDurationBudgetRemainingMs();
7602
+ if (this.expectedAdBreakDurationMs == null) {
7603
+ return wallClockRemaining;
7604
+ }
7605
+ return Math.min(wallClockRemaining, budgetRemaining);
7606
+ }
7607
+ },
7608
+ {
7609
+ key: "getMinRemainingMsToStartAd",
7610
+ value: function getMinRemainingMsToStartAd() {
7611
+ var configured = this.config.minRemainingMsToStartAd;
7612
+ if (typeof configured === "number" && configured >= 0) {
7613
+ return configured;
7614
+ }
7615
+ return this.defaultMinRemainingMsToStartAd;
7616
+ }
7617
+ },
7618
+ {
7619
+ key: "hasTimeToStartAnotherAd",
7620
+ value: function hasTimeToStartAnotherAd() {
7621
+ var minRemaining = this.getMinRemainingMsToStartAd();
7622
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7623
+ }
7624
+ },
7375
7625
  {
7376
7626
  key: "findBreakForTime",
7377
7627
  value: function findBreakForTime(nowMs) {