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.
@@ -1328,6 +1328,12 @@ function createHlsAdPlayer(contentVideo, options) {
1328
1328
  contentVideo.style.visibility = "visible";
1329
1329
  contentVideo.style.opacity = "1";
1330
1330
  }
1331
+ },
1332
+ getLoadedAdDuration: function getLoadedAdDuration() {
1333
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration;
1334
+ },
1335
+ getLoadedAdId: function getLoadedAdId() {
1336
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.id;
1331
1337
  }
1332
1338
  };
1333
1339
  }
@@ -2724,6 +2730,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2724
2730
  this.maxConsecutiveFailures = 5;
2725
2731
  this.lastAdRequestTime = 0;
2726
2732
  this.minAdRequestIntervalMs = 3e3;
2733
+ this.defaultMinRemainingMsToStartAd = 4e3;
2727
2734
  this.backoffBaseMs = 1e3;
2728
2735
  this.maxBackoffMs = 15e3;
2729
2736
  this.globalConsecutiveNoFills = 0;
@@ -2739,6 +2746,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2739
2746
  this.preloadPoolLoopRunning = false;
2740
2747
  this.adDetectSentForCurrentBreak = false;
2741
2748
  this.adBreakEpoch = 0;
2749
+ this.adBreakPlayedDurationMs = 0;
2742
2750
  this.palPlaybackStarted = false;
2743
2751
  this.streamCorrelator = generateCorrelator();
2744
2752
  this.consentSignals = {};
@@ -3235,6 +3243,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3235
3243
  adIndex: _this.currentAdIndex,
3236
3244
  timestamp: /* @__PURE__ */ new Date().toISOString()
3237
3245
  }).catch(function() {});
3246
+ _this.recordPlayedAdDuration();
3238
3247
  var remaining = _this.getRemainingAdMs();
3239
3248
  _this.consecutiveFailures = 0;
3240
3249
  if (_this.config.debugAdTiming) {
@@ -3243,7 +3252,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3243
3252
  } : {});
3244
3253
  }
3245
3254
  if (_this.inAdBreak) {
3246
- if (remaining > 500) {
3255
+ if (_this.hasTimeToStartAnotherAd()) {
3247
3256
  _this.tryNextAvailableAdWithRateLimit();
3248
3257
  } else {
3249
3258
  _this.handleAdPodComplete();
@@ -3310,13 +3319,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3310
3319
  return;
3311
3320
  }
3312
3321
  _this.consecutiveFailures = 0;
3322
+ _this.recordPlayedAdDuration();
3313
3323
  var remaining = _this.getRemainingAdMs();
3314
3324
  if (_this.config.debugAdTiming) {
3315
3325
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
3316
3326
  queuedUrls: _to_consumable_array(_this.adRequestQueue)
3317
3327
  } : {});
3318
3328
  }
3319
- if (remaining > 500) {
3329
+ if (_this.hasTimeToStartAnotherAd()) {
3320
3330
  _this.tryNextAvailableAdWithRateLimit();
3321
3331
  } else {
3322
3332
  _this.handleAdPodComplete();
@@ -4016,7 +4026,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4016
4026
  var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
4017
4027
  this.scheduleAdStopCountdown(remainingMs1);
4018
4028
  }
4019
- if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
4029
+ if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0 && this.hasTimeToStartAnotherAd()) {
4020
4030
  this.tryNextAvailableAdWithRateLimit();
4021
4031
  }
4022
4032
  return;
@@ -5315,7 +5325,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5315
5325
  epoch = this.adBreakEpoch;
5316
5326
  loadPromise = function() {
5317
5327
  return _async_to_generator(function() {
5318
- var _this, hasAdError, adErrorPayload, errorListenerCleanup, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, lateErrorListener, error;
5328
+ var _this, hasAdError, adErrorPayload, errorListenerCleanup, _preloadAd_getLoadedAdDuration, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, loadedDuration, lateErrorListener, _poolEntry_durationSeconds, error;
5319
5329
  return _ts_generator(this, function(_state) {
5320
5330
  switch(_state.label){
5321
5331
  case 0:
@@ -5411,6 +5421,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5411
5421
  isReady: true,
5412
5422
  loadPromise: Promise.resolve()
5413
5423
  };
5424
+ loadedDuration = (_preloadAd_getLoadedAdDuration = preloadAd.getLoadedAdDuration) === null || _preloadAd_getLoadedAdDuration === void 0 ? void 0 : _preloadAd_getLoadedAdDuration.call(preloadAd);
5425
+ if (loadedDuration != null && loadedDuration > 0) {
5426
+ poolEntry.durationSeconds = loadedDuration;
5427
+ }
5414
5428
  lateErrorListener = function lateErrorListener(payload) {
5415
5429
  var index = _this.preloadPool.findIndex(function(entry) {
5416
5430
  return entry.vastUrl === vastUrl;
@@ -5428,7 +5442,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5428
5442
  preloadAd.on("ad_error", lateErrorListener);
5429
5443
  this.preloadPool.push(poolEntry);
5430
5444
  if (this.config.debugAdTiming) {
5431
- console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ")"));
5445
+ ;
5446
+ 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)"));
5432
5447
  }
5433
5448
  if (errorListenerCleanup) {
5434
5449
  errorListenerCleanup();
@@ -5475,7 +5490,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5475
5490
  key: "preloadPoolLoop",
5476
5491
  value: function preloadPoolLoop(baseVastUrl) {
5477
5492
  return _async_to_generator(function() {
5478
- var epoch, remaining, newUrls, newUrl;
5493
+ var epoch, newUrls, newUrl;
5479
5494
  return _ts_generator(this, function(_state) {
5480
5495
  switch(_state.label){
5481
5496
  case 0:
@@ -5542,14 +5557,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5542
5557
  8
5543
5558
  ];
5544
5559
  }
5545
- if (this.inAdBreak) {
5546
- remaining = this.getRemainingAdMs();
5547
- if (remaining <= 0) {
5548
- return [
5549
- 3,
5550
- 8
5551
- ];
5560
+ if (this.inAdBreak && !this.hasTimeToStartAnotherAd()) {
5561
+ if (this.config.debugAdTiming) {
5562
+ console.log("[PRELOAD-POOL] Not enough ad break time left, stopping preload");
5552
5563
  }
5564
+ return [
5565
+ 3,
5566
+ 8
5567
+ ];
5553
5568
  }
5554
5569
  newUrls = this.generateVastUrlsWithCorrelators(baseVastUrl, 1);
5555
5570
  newUrl = newUrls[0];
@@ -5709,8 +5724,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5709
5724
  }
5710
5725
  this.inAdBreak = true;
5711
5726
  this.currentAdBreakStartWallClockMs = Date.now();
5727
+ this.adBreakPlayedDurationMs = 0;
5712
5728
  this.currentAdIndex = 0;
5713
- this.totalAdsInBreak = 1;
5729
+ this.totalAdsInBreak = 0;
5714
5730
  this.adPodQueue = [];
5715
5731
  this.showAds = true;
5716
5732
  this.showPlaceholderLayer();
@@ -5764,14 +5780,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5764
5780
  case 1:
5765
5781
  _state.trys.push([
5766
5782
  1,
5767
- 8,
5783
+ 12,
5768
5784
  ,
5769
- 14
5785
+ 20
5770
5786
  ]);
5771
5787
  if (!(usePreloadedAd && preloadedController)) return [
5788
+ 3,
5789
+ 5
5790
+ ];
5791
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5772
5792
  3,
5773
5793
  3
5774
5794
  ];
5795
+ this.rejectLoadedAdForDuration(preloadedController);
5796
+ this.startContinuousFetching(baseVastUrl);
5797
+ return [
5798
+ 4,
5799
+ this.tryNextAvailableAdWithRateLimit()
5800
+ ];
5801
+ case 2:
5802
+ _state.sent();
5803
+ return [
5804
+ 2
5805
+ ];
5806
+ case 3:
5775
5807
  preservedMuted = this.adPlayer.getOriginalMutedState();
5776
5808
  preservedVolume = this.adPlayer.getOriginalVolume();
5777
5809
  this.adPlayer.destroy();
@@ -5784,7 +5816,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5784
5816
  }
5785
5817
  this.adPlayer.resume();
5786
5818
  this.consecutiveFailures = 0;
5787
- this.currentAdIndex++;
5788
5819
  this.startContinuousFetching(baseVastUrl);
5789
5820
  if (!this.preloadPoolActive) {
5790
5821
  this.preloadPoolActive = true;
@@ -5794,8 +5825,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5794
5825
  4,
5795
5826
  this.adPlayer.play()
5796
5827
  ];
5797
- case 2:
5828
+ case 4:
5798
5829
  _state.sent();
5830
+ this.markAdStarted();
5799
5831
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5800
5832
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5801
5833
  }
@@ -5803,21 +5835,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5803
5835
  this.adPlayer.setAdVolume(adVolume);
5804
5836
  return [
5805
5837
  3,
5806
- 7
5838
+ 11
5807
5839
  ];
5808
- case 3:
5840
+ case 5:
5809
5841
  return [
5810
5842
  4,
5811
5843
  this.enforceGlobalRateLimit()
5812
5844
  ];
5813
- case 4:
5845
+ case 6:
5814
5846
  _state.sent();
5815
5847
  this.lastAdRequestTime = Date.now();
5816
5848
  return [
5817
5849
  4,
5818
5850
  this.adPlayer.requestAds(firstAdUrl)
5819
5851
  ];
5820
- case 5:
5852
+ case 7:
5821
5853
  _state.sent();
5822
5854
  sendAdLoadedTracking(this.config.licenseKey, {
5823
5855
  source: "hls",
@@ -5827,8 +5859,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5827
5859
  if (this.config.debugAdTiming) {
5828
5860
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
5829
5861
  }
5862
+ if (!!this.ensureLoadedAdFitsBudget()) return [
5863
+ 3,
5864
+ 9
5865
+ ];
5866
+ this.rejectLoadedAdForDuration();
5867
+ this.recreateAdController();
5868
+ this.startContinuousFetching(baseVastUrl);
5869
+ return [
5870
+ 4,
5871
+ this.tryNextAvailableAdWithRateLimit()
5872
+ ];
5873
+ case 8:
5874
+ _state.sent();
5875
+ return [
5876
+ 2
5877
+ ];
5878
+ case 9:
5830
5879
  this.consecutiveFailures = 0;
5831
- this.currentAdIndex++;
5832
5880
  this.startContinuousFetching(baseVastUrl);
5833
5881
  if (!this.preloadPoolActive) {
5834
5882
  this.startPreloadPool(baseVastUrl, []);
@@ -5837,44 +5885,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5837
5885
  4,
5838
5886
  this.adPlayer.play()
5839
5887
  ];
5840
- case 6:
5888
+ case 10:
5841
5889
  _state.sent();
5890
+ this.markAdStarted();
5842
5891
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5843
5892
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5844
5893
  }
5845
5894
  adVolume1 = currentMuted ? 0 : currentVolume;
5846
5895
  this.adPlayer.setAdVolume(adVolume1);
5847
- _state.label = 7;
5848
- case 7:
5896
+ _state.label = 11;
5897
+ case 11:
5849
5898
  return [
5850
5899
  3,
5851
- 14
5900
+ 20
5852
5901
  ];
5853
- case 8:
5902
+ case 12:
5854
5903
  error = _state.sent();
5855
5904
  if (this.config.debugAdTiming) {
5856
5905
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
5857
5906
  }
5858
5907
  if (!!usePreloadedAd) return [
5859
5908
  3,
5860
- 12
5909
+ 18
5861
5910
  ];
5862
5911
  fallbackPreloaded = this.getPreloadedAd();
5863
5912
  if (!fallbackPreloaded) return [
5864
5913
  3,
5865
- 12
5914
+ 18
5866
5915
  ];
5867
5916
  if (this.config.debugAdTiming) {
5868
5917
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
5869
5918
  }
5870
- _state.label = 9;
5871
- case 9:
5919
+ _state.label = 13;
5920
+ case 13:
5872
5921
  _state.trys.push([
5873
- 9,
5874
- 11,
5922
+ 13,
5923
+ 17,
5875
5924
  ,
5876
- 12
5925
+ 18
5877
5926
  ]);
5927
+ if (!!this.ensureLoadedAdFitsBudget(fallbackPreloaded.adController)) return [
5928
+ 3,
5929
+ 15
5930
+ ];
5931
+ this.rejectLoadedAdForDuration(fallbackPreloaded.adController);
5932
+ this.startContinuousFetching(baseVastUrl);
5933
+ return [
5934
+ 4,
5935
+ this.tryNextAvailableAdWithRateLimit()
5936
+ ];
5937
+ case 14:
5938
+ _state.sent();
5939
+ return [
5940
+ 2
5941
+ ];
5942
+ case 15:
5878
5943
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
5879
5944
  preservedVolume1 = this.adPlayer.getOriginalVolume();
5880
5945
  this.adPlayer.destroy();
@@ -5884,7 +5949,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5884
5949
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
5885
5950
  this.adPlayer.resume();
5886
5951
  this.consecutiveFailures = 0;
5887
- this.currentAdIndex++;
5888
5952
  this.startContinuousFetching(baseVastUrl);
5889
5953
  if (!this.preloadPoolActive) {
5890
5954
  this.preloadPoolActive = true;
@@ -5894,8 +5958,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5894
5958
  4,
5895
5959
  this.adPlayer.play()
5896
5960
  ];
5897
- case 10:
5961
+ case 16:
5898
5962
  _state.sent();
5963
+ this.markAdStarted();
5899
5964
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5900
5965
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5901
5966
  }
@@ -5904,16 +5969,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5904
5969
  return [
5905
5970
  2
5906
5971
  ];
5907
- case 11:
5972
+ case 17:
5908
5973
  fallbackError = _state.sent();
5909
5974
  if (this.config.debugAdTiming) {
5910
5975
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
5911
5976
  }
5912
5977
  return [
5913
5978
  3,
5914
- 12
5979
+ 18
5915
5980
  ];
5916
- case 12:
5981
+ case 18:
5917
5982
  if (this.isTemporaryAdError(error)) {
5918
5983
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
5919
5984
  if (this.config.debugAdTiming) {
@@ -5931,13 +5996,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5931
5996
  4,
5932
5997
  this.tryNextAvailableAdWithRateLimit()
5933
5998
  ];
5934
- case 13:
5999
+ case 19:
5935
6000
  _state.sent();
5936
6001
  return [
5937
6002
  3,
5938
- 14
6003
+ 20
5939
6004
  ];
5940
- case 14:
6005
+ case 20:
5941
6006
  return [
5942
6007
  2
5943
6008
  ];
@@ -5978,10 +6043,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5978
6043
  return _ts_generator(this, function(_state) {
5979
6044
  switch(_state.label){
5980
6045
  case 0:
5981
- remaining = _this.getRemainingAdMs();
5982
- if (remaining <= 0) {
6046
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
6047
+ if (remaining <= _this.getMinRemainingMsToStartAd()) {
5983
6048
  if (_this.config.debugAdTiming) {
5984
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Ad break time expired, stopping URL generation");
6049
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (wall=".concat(_this.getWallClockRemainingAdMs(), "ms, budget=").concat(_this.getDurationBudgetRemainingMs(), "ms), stopping URL generation"));
5985
6050
  }
5986
6051
  return [
5987
6052
  2,
@@ -6063,7 +6128,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6063
6128
  });
6064
6129
  }
6065
6130
  _this.adRequestQueue.push(newAdUrl);
6066
- _this.totalAdsInBreak++;
6067
6131
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
6068
6132
  return [
6069
6133
  4,
@@ -6201,9 +6265,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6201
6265
  ];
6202
6266
  }
6203
6267
  remaining = this.getRemainingAdMs();
6204
- if (remaining <= 500) {
6268
+ if (!this.hasTimeToStartAnotherAd()) {
6205
6269
  if (this.config.debugAdTiming) {
6206
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F No time remaining, ending ad break");
6270
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough time remaining (".concat(remaining, "ms) to start another ad, ending ad break"));
6207
6271
  }
6208
6272
  this.handleAdPodComplete();
6209
6273
  return [
@@ -6232,7 +6296,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6232
6296
  preloaded = this.getPreloadedAd();
6233
6297
  if (!preloaded) return [
6234
6298
  3,
6235
- 4
6299
+ 6
6236
6300
  ];
6237
6301
  if (this.config.debugAdTiming) {
6238
6302
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6246,10 +6310,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6246
6310
  case 1:
6247
6311
  _state.trys.push([
6248
6312
  1,
6249
- 3,
6313
+ 5,
6250
6314
  ,
6251
- 4
6315
+ 6
6252
6316
  ]);
6317
+ if (!!this.ensureLoadedAdFitsBudget(preloaded.adController)) return [
6318
+ 3,
6319
+ 3
6320
+ ];
6321
+ this.rejectLoadedAdForDuration(preloaded.adController);
6322
+ return [
6323
+ 4,
6324
+ this.tryNextAvailableAdWithRateLimit()
6325
+ ];
6326
+ case 2:
6327
+ _state.sent();
6328
+ return [
6329
+ 2
6330
+ ];
6331
+ case 3:
6253
6332
  preservedMuted = this.adPlayer.getOriginalMutedState();
6254
6333
  preservedVolume = this.adPlayer.getOriginalVolume();
6255
6334
  this.adPlayer.destroy();
@@ -6258,15 +6337,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6258
6337
  this.attachImaEventListeners();
6259
6338
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6260
6339
  this.adPlayer.resume();
6261
- this.currentAdIndex++;
6262
- this.totalAdRequestsInBreak++;
6263
- this.lastAdRequestTime = Date.now();
6264
6340
  return [
6265
6341
  4,
6266
6342
  this.adPlayer.play()
6267
6343
  ];
6268
- case 2:
6344
+ case 4:
6269
6345
  _state.sent();
6346
+ this.markAdStarted();
6270
6347
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6271
6348
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6272
6349
  }
@@ -6275,25 +6352,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6275
6352
  return [
6276
6353
  2
6277
6354
  ];
6278
- case 3:
6355
+ case 5:
6279
6356
  error = _state.sent();
6280
6357
  if (this.config.debugAdTiming) {
6281
6358
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6282
6359
  }
6283
6360
  return [
6284
6361
  3,
6285
- 4
6362
+ 6
6286
6363
  ];
6287
- case 4:
6364
+ case 6:
6288
6365
  if (!(this.adRequestQueue.length > 0)) return [
6289
6366
  3,
6290
- 10
6367
+ 14
6291
6368
  ];
6292
6369
  nextAdUrl = this.adRequestQueue.shift();
6293
6370
  if (!nextAdUrl) {
6294
6371
  return [
6295
6372
  3,
6296
- 4
6373
+ 6
6297
6374
  ];
6298
6375
  }
6299
6376
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6302,7 +6379,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6302
6379
  }
6303
6380
  return [
6304
6381
  3,
6305
- 4
6382
+ 6
6306
6383
  ];
6307
6384
  }
6308
6385
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6311,40 +6388,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6311
6388
  }
6312
6389
  return [
6313
6390
  3,
6314
- 4
6391
+ 6
6315
6392
  ];
6316
6393
  }
6317
6394
  if (this.config.debugAdTiming) {
6318
- 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)"));
6395
+ console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6319
6396
  }
6320
- this.currentAdIndex++;
6321
- this.totalAdRequestsInBreak++;
6322
- this.lastAdRequestTime = Date.now();
6323
- _state.label = 5;
6324
- case 5:
6397
+ _state.label = 7;
6398
+ case 7:
6325
6399
  _state.trys.push([
6326
- 5,
6327
6400
  7,
6401
+ 9,
6328
6402
  ,
6329
- 9
6403
+ 13
6330
6404
  ]);
6331
6405
  return [
6332
6406
  4,
6333
6407
  this.playSingleAd(nextAdUrl)
6334
6408
  ];
6335
- case 6:
6409
+ case 8:
6336
6410
  _state.sent();
6337
6411
  this.consecutiveFailures = 0;
6338
6412
  return [
6339
6413
  3,
6340
- 9
6414
+ 13
6341
6415
  ];
6342
- case 7:
6416
+ case 9:
6343
6417
  error1 = _state.sent();
6344
6418
  errorMessage = error1.message;
6345
6419
  if (this.config.debugAdTiming) {
6346
6420
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6347
6421
  }
6422
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6423
+ 3,
6424
+ 11
6425
+ ];
6426
+ return [
6427
+ 4,
6428
+ this.tryNextAvailableAdWithRateLimit()
6429
+ ];
6430
+ case 10:
6431
+ _state.sent();
6432
+ return [
6433
+ 2
6434
+ ];
6435
+ case 11:
6348
6436
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6349
6437
  this.consecutiveFailures++;
6350
6438
  }
@@ -6352,21 +6440,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6352
6440
  4,
6353
6441
  this.tryNextAvailableAdWithRateLimit()
6354
6442
  ];
6355
- case 8:
6443
+ case 12:
6356
6444
  _state.sent();
6357
6445
  return [
6358
6446
  3,
6359
- 9
6447
+ 13
6360
6448
  ];
6361
- case 9:
6449
+ case 13:
6362
6450
  return [
6363
6451
  2
6364
6452
  ];
6365
- case 10:
6453
+ case 14:
6366
6454
  maxRetries = 3;
6367
6455
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6368
6456
  3,
6369
- 13
6457
+ 17
6370
6458
  ];
6371
6459
  if (this.config.debugAdTiming) {
6372
6460
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6377,18 +6465,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6377
6465
  return setTimeout(resolve, 500);
6378
6466
  })
6379
6467
  ];
6380
- case 11:
6468
+ case 15:
6381
6469
  _state.sent();
6382
6470
  return [
6383
6471
  4,
6384
6472
  this.tryNextAvailableAd(retryCount + 1)
6385
6473
  ];
6386
- case 12:
6474
+ case 16:
6387
6475
  _state.sent();
6388
6476
  return [
6389
6477
  2
6390
6478
  ];
6391
- case 13:
6479
+ case 17:
6392
6480
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6393
6481
  this.showPlaceholderAndWaitForAds();
6394
6482
  } else {
@@ -6451,7 +6539,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6451
6539
  case 1:
6452
6540
  if (!(i < maxChecks)) return [
6453
6541
  3,
6454
- 10
6542
+ 12
6455
6543
  ];
6456
6544
  return [
6457
6545
  4,
@@ -6472,15 +6560,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6472
6560
  }
6473
6561
  return [
6474
6562
  3,
6475
- 10
6563
+ 12
6564
+ ];
6565
+ }
6566
+ if (!this.hasTimeToStartAnotherAd()) {
6567
+ if (this.config.debugAdTiming) {
6568
+ console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Not enough time remaining during placeholder wait, ending ad break");
6569
+ }
6570
+ return [
6571
+ 3,
6572
+ 12
6476
6573
  ];
6477
6574
  }
6478
6575
  _state.label = 3;
6479
6576
  case 3:
6480
6577
  if (!(this.adRequestQueue.length > 0)) return [
6481
6578
  3,
6482
- 9
6579
+ 11
6483
6580
  ];
6581
+ if (!this.hasTimeToStartAnotherAd()) {
6582
+ return [
6583
+ 3,
6584
+ 11
6585
+ ];
6586
+ }
6484
6587
  nextAdUrl = this.adRequestQueue.shift();
6485
6588
  if (!nextAdUrl) {
6486
6589
  return [
@@ -6505,16 +6608,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6505
6608
  }
6506
6609
  this.isShowingPlaceholder = false;
6507
6610
  this.adPlayer.hidePlaceholder();
6508
- this.currentAdIndex++;
6509
- this.totalAdRequestsInBreak++;
6510
- this.lastAdRequestTime = Date.now();
6511
6611
  _state.label = 4;
6512
6612
  case 4:
6513
6613
  _state.trys.push([
6514
6614
  4,
6515
6615
  6,
6516
6616
  ,
6517
- 8
6617
+ 10
6518
6618
  ]);
6519
6619
  return [
6520
6620
  4,
@@ -6525,11 +6625,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6525
6625
  this.consecutiveFailures = 0;
6526
6626
  return [
6527
6627
  3,
6528
- 8
6628
+ 10
6529
6629
  ];
6530
6630
  case 6:
6531
6631
  error = _state.sent();
6532
6632
  errorMessage = error.message;
6633
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6634
+ 3,
6635
+ 8
6636
+ ];
6637
+ return [
6638
+ 4,
6639
+ this.tryNextAvailableAdWithRateLimit()
6640
+ ];
6641
+ case 7:
6642
+ _state.sent();
6643
+ return [
6644
+ 2
6645
+ ];
6646
+ case 8:
6533
6647
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6534
6648
  this.consecutiveFailures++;
6535
6649
  }
@@ -6537,23 +6651,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6537
6651
  4,
6538
6652
  this.tryNextAvailableAdWithRateLimit()
6539
6653
  ];
6540
- case 7:
6654
+ case 9:
6541
6655
  _state.sent();
6542
6656
  return [
6543
6657
  3,
6544
- 8
6658
+ 10
6545
6659
  ];
6546
- case 8:
6660
+ case 10:
6547
6661
  return [
6548
6662
  2
6549
6663
  ];
6550
- case 9:
6664
+ case 11:
6551
6665
  i++;
6552
6666
  return [
6553
6667
  3,
6554
6668
  1
6555
6669
  ];
6556
- case 10:
6670
+ case 12:
6557
6671
  if (this.config.debugAdTiming) {
6558
6672
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6559
6673
  }
@@ -6763,21 +6877,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6763
6877
  if (!this.inAdBreak) return;
6764
6878
  this.adStopTimerId = void 0;
6765
6879
  var adPlaying = this.adPlayer.isAdPlaying();
6766
- var pendingAds = this.adPodQueue.length > 0;
6767
6880
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
6768
6881
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
6769
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
6882
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
6770
6883
  var elapsedSinceStartMs = 0;
6771
6884
  if (this.currentAdBreakStartWallClockMs != null) {
6772
6885
  elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
6773
6886
  }
6774
6887
  var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
6775
6888
  var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
6776
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
6889
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
6777
6890
  if (shouldExtendAdBreak) {
6778
6891
  this.scheduleAdStopCountdown(checkIntervalMs);
6779
6892
  return;
6780
6893
  }
6894
+ if (this.config.debugAdTiming) {
6895
+ console.log("[StormcloudVideoPlayer] ⏱️ Ad break boundary reached (overrun=".concat(overrunMs, "ms) - hard-stopping ads and resuming stream"));
6896
+ }
6781
6897
  if (adPlaying) {
6782
6898
  this.adPlayer.stop().catch(function() {});
6783
6899
  }
@@ -7023,6 +7139,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7023
7139
  2
7024
7140
  ];
7025
7141
  }
7142
+ if (!this.ensureLoadedAdFitsBudget()) {
7143
+ this.rejectLoadedAdForDuration();
7144
+ this.recreateAdController();
7145
+ throw new Error("ad_duration_exceeds_budget");
7146
+ }
7026
7147
  _state.label = 4;
7027
7148
  case 4:
7028
7149
  _state.trys.push([
@@ -7038,6 +7159,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7038
7159
  ];
7039
7160
  case 5:
7040
7161
  _state.sent();
7162
+ this.markAdStarted();
7041
7163
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7042
7164
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7043
7165
  }
@@ -7069,6 +7191,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7069
7191
  ,
7070
7192
  10
7071
7193
  ]);
7194
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7195
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7196
+ throw playError;
7197
+ }
7072
7198
  this.clearAdFailsafeTimer();
7073
7199
  preservedMuted = this.adPlayer.getOriginalMutedState();
7074
7200
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -7079,14 +7205,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7079
7205
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
7080
7206
  this.adPlayer.resume();
7081
7207
  this.consecutiveFailures = 0;
7082
- this.currentAdIndex++;
7083
- this.totalAdRequestsInBreak++;
7084
7208
  return [
7085
7209
  4,
7086
7210
  this.adPlayer.play()
7087
7211
  ];
7088
7212
  case 8:
7089
7213
  _state.sent();
7214
+ this.markAdStarted();
7090
7215
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7091
7216
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7092
7217
  }
@@ -7147,6 +7272,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7147
7272
  ,
7148
7273
  16
7149
7274
  ]);
7275
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7276
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7277
+ throw error;
7278
+ }
7150
7279
  this.clearAdRequestWatchdog();
7151
7280
  this.clearAdFailsafeTimer();
7152
7281
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7158,14 +7287,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7158
7287
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7159
7288
  this.adPlayer.resume();
7160
7289
  this.consecutiveFailures = 0;
7161
- this.currentAdIndex++;
7162
- this.totalAdRequestsInBreak++;
7163
7290
  return [
7164
7291
  4,
7165
7292
  this.adPlayer.play()
7166
7293
  ];
7167
7294
  case 14:
7168
7295
  _state.sent();
7296
+ this.markAdStarted();
7169
7297
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7170
7298
  return [
7171
7299
  2
@@ -7226,6 +7354,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7226
7354
  this.adRequestQueue = [];
7227
7355
  this.inAdBreak = false;
7228
7356
  this.adDetectSentForCurrentBreak = false;
7357
+ this.adBreakPlayedDurationMs = 0;
7229
7358
  this.activeScte35BreakKey = void 0;
7230
7359
  this.scheduledScte35BreakKey = void 0;
7231
7360
  this.expectedAdBreakDurationMs = void 0;
@@ -7283,11 +7412,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7283
7412
  this.handleAdPodComplete();
7284
7413
  return;
7285
7414
  }
7286
- if (remaining > 500) {
7415
+ if (this.hasTimeToStartAnotherAd()) {
7287
7416
  this.tryNextAvailableAdWithRateLimit();
7288
7417
  } else {
7289
7418
  if (this.config.debugAdTiming) {
7290
- console.log("[AD-ERROR] Ad failed and no time remaining. Failed URLs:", this.failedVastUrls.size);
7419
+ console.log("[AD-ERROR] Ad failed and not enough time remaining for another ad. Failed URLs:", this.failedVastUrls.size);
7291
7420
  }
7292
7421
  this.handleAdPodComplete();
7293
7422
  }
@@ -7415,13 +7544,134 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7415
7544
  }
7416
7545
  },
7417
7546
  {
7418
- key: "getRemainingAdMs",
7419
- value: function getRemainingAdMs() {
7420
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7547
+ key: "getWallClockRemainingAdMs",
7548
+ value: function getWallClockRemainingAdMs() {
7549
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {
7550
+ return 0;
7551
+ }
7421
7552
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7422
7553
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7423
7554
  }
7424
7555
  },
7556
+ {
7557
+ key: "getDurationBudgetRemainingMs",
7558
+ value: function getDurationBudgetRemainingMs() {
7559
+ if (this.expectedAdBreakDurationMs == null) {
7560
+ return 0;
7561
+ }
7562
+ return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);
7563
+ }
7564
+ },
7565
+ {
7566
+ key: "getLoadedAdDurationMs",
7567
+ value: function getLoadedAdDurationMs() {
7568
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7569
+ var _adController_getLoadedAdDuration;
7570
+ var durationSec = (_adController_getLoadedAdDuration = adController.getLoadedAdDuration) === null || _adController_getLoadedAdDuration === void 0 ? void 0 : _adController_getLoadedAdDuration.call(adController);
7571
+ if (durationSec == null || durationSec <= 0) {
7572
+ return 0;
7573
+ }
7574
+ return Math.round(durationSec * 1e3);
7575
+ }
7576
+ },
7577
+ {
7578
+ key: "adFitsRemainingBudget",
7579
+ value: function adFitsRemainingBudget(durationMs) {
7580
+ var adController = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.adPlayer;
7581
+ if (durationMs <= 0) {
7582
+ return true;
7583
+ }
7584
+ return durationMs <= this.getDurationBudgetRemainingMs();
7585
+ }
7586
+ },
7587
+ {
7588
+ key: "logAdDurationDecision",
7589
+ value: function logAdDurationDecision(accepted, durationMs) {
7590
+ var adController = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : this.adPlayer;
7591
+ var _ref;
7592
+ var _adController_getLoadedAdId;
7593
+ if (!this.config.debugAdTiming) {
7594
+ return;
7595
+ }
7596
+ var remainingBudget = this.getDurationBudgetRemainingMs();
7597
+ var adId = (_ref = (_adController_getLoadedAdId = adController.getLoadedAdId) === null || _adController_getLoadedAdId === void 0 ? void 0 : _adController_getLoadedAdId.call(adController)) !== null && _ref !== void 0 ? _ref : "unknown";
7598
+ if (accepted) {
7599
+ console.log("[AD-DURATION] Accepted ad id=".concat(adId, ", duration=").concat(durationMs, "ms, budget remaining after play=").concat(Math.max(0, remainingBudget - durationMs), "ms"));
7600
+ } else {
7601
+ console.log("[AD-DURATION] Rejected ad id=".concat(adId, ", duration=").concat(durationMs, "ms exceeds remaining budget=").concat(remainingBudget, "ms — requesting another ad"));
7602
+ }
7603
+ }
7604
+ },
7605
+ {
7606
+ key: "recordPlayedAdDuration",
7607
+ value: function recordPlayedAdDuration() {
7608
+ var durationMs = this.getLoadedAdDurationMs();
7609
+ if (durationMs <= 0) {
7610
+ return;
7611
+ }
7612
+ this.adBreakPlayedDurationMs += durationMs;
7613
+ if (this.config.debugAdTiming) {
7614
+ var _this_expectedAdBreakDurationMs;
7615
+ 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"));
7616
+ }
7617
+ }
7618
+ },
7619
+ {
7620
+ key: "ensureLoadedAdFitsBudget",
7621
+ value: function ensureLoadedAdFitsBudget() {
7622
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7623
+ var durationMs = this.getLoadedAdDurationMs(adController);
7624
+ var fits = this.adFitsRemainingBudget(durationMs, adController);
7625
+ this.logAdDurationDecision(fits, durationMs, adController);
7626
+ return fits;
7627
+ }
7628
+ },
7629
+ {
7630
+ key: "rejectLoadedAdForDuration",
7631
+ value: function rejectLoadedAdForDuration() {
7632
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7633
+ try {
7634
+ adController.destroy();
7635
+ } catch (unused) {}
7636
+ }
7637
+ },
7638
+ {
7639
+ key: "markAdStarted",
7640
+ value: function markAdStarted() {
7641
+ this.currentAdIndex++;
7642
+ this.totalAdsInBreak = this.currentAdIndex;
7643
+ this.totalAdRequestsInBreak++;
7644
+ this.lastAdRequestTime = Date.now();
7645
+ }
7646
+ },
7647
+ {
7648
+ key: "getRemainingAdMs",
7649
+ value: function getRemainingAdMs() {
7650
+ var wallClockRemaining = this.getWallClockRemainingAdMs();
7651
+ var budgetRemaining = this.getDurationBudgetRemainingMs();
7652
+ if (this.expectedAdBreakDurationMs == null) {
7653
+ return wallClockRemaining;
7654
+ }
7655
+ return Math.min(wallClockRemaining, budgetRemaining);
7656
+ }
7657
+ },
7658
+ {
7659
+ key: "getMinRemainingMsToStartAd",
7660
+ value: function getMinRemainingMsToStartAd() {
7661
+ var configured = this.config.minRemainingMsToStartAd;
7662
+ if (typeof configured === "number" && configured >= 0) {
7663
+ return configured;
7664
+ }
7665
+ return this.defaultMinRemainingMsToStartAd;
7666
+ }
7667
+ },
7668
+ {
7669
+ key: "hasTimeToStartAnotherAd",
7670
+ value: function hasTimeToStartAnotherAd() {
7671
+ var minRemaining = this.getMinRemainingMsToStartAd();
7672
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7673
+ }
7674
+ },
7425
7675
  {
7426
7676
  key: "findBreakForTime",
7427
7677
  value: function findBreakForTime(nowMs) {