stormcloud-video-player 0.8.9 → 0.8.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1262,6 +1262,12 @@ function createHlsAdPlayer(contentVideo, options) {
1262
1262
  contentVideo.style.visibility = "visible";
1263
1263
  contentVideo.style.opacity = "1";
1264
1264
  }
1265
+ },
1266
+ getLoadedAdDuration: function getLoadedAdDuration() {
1267
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration;
1268
+ },
1269
+ getLoadedAdId: function getLoadedAdId() {
1270
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.id;
1265
1271
  }
1266
1272
  };
1267
1273
  }
@@ -2708,6 +2714,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2708
2714
  this.maxConsecutiveFailures = 5;
2709
2715
  this.lastAdRequestTime = 0;
2710
2716
  this.minAdRequestIntervalMs = 3e3;
2717
+ this.defaultMinRemainingMsToStartAd = 4e3;
2711
2718
  this.backoffBaseMs = 1e3;
2712
2719
  this.maxBackoffMs = 15e3;
2713
2720
  this.globalConsecutiveNoFills = 0;
@@ -2723,6 +2730,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2723
2730
  this.preloadPoolLoopRunning = false;
2724
2731
  this.adDetectSentForCurrentBreak = false;
2725
2732
  this.adBreakEpoch = 0;
2733
+ this.adBreakPlayedDurationMs = 0;
2726
2734
  this.palPlaybackStarted = false;
2727
2735
  this.streamCorrelator = generateCorrelator();
2728
2736
  this.consentSignals = {};
@@ -3219,6 +3227,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3219
3227
  adIndex: _this.currentAdIndex,
3220
3228
  timestamp: /* @__PURE__ */ new Date().toISOString()
3221
3229
  }).catch(function() {});
3230
+ _this.recordPlayedAdDuration();
3222
3231
  var remaining = _this.getRemainingAdMs();
3223
3232
  _this.consecutiveFailures = 0;
3224
3233
  if (_this.config.debugAdTiming) {
@@ -3227,7 +3236,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3227
3236
  } : {});
3228
3237
  }
3229
3238
  if (_this.inAdBreak) {
3230
- if (remaining > 500) {
3239
+ if (_this.hasTimeToStartAnotherAd()) {
3231
3240
  _this.tryNextAvailableAdWithRateLimit();
3232
3241
  } else {
3233
3242
  _this.handleAdPodComplete();
@@ -3294,13 +3303,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3294
3303
  return;
3295
3304
  }
3296
3305
  _this.consecutiveFailures = 0;
3306
+ _this.recordPlayedAdDuration();
3297
3307
  var remaining = _this.getRemainingAdMs();
3298
3308
  if (_this.config.debugAdTiming) {
3299
3309
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
3300
3310
  queuedUrls: _to_consumable_array(_this.adRequestQueue)
3301
3311
  } : {});
3302
3312
  }
3303
- if (remaining > 500) {
3313
+ if (_this.hasTimeToStartAnotherAd()) {
3304
3314
  _this.tryNextAvailableAdWithRateLimit();
3305
3315
  } else {
3306
3316
  _this.handleAdPodComplete();
@@ -4000,7 +4010,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4000
4010
  var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
4001
4011
  this.scheduleAdStopCountdown(remainingMs1);
4002
4012
  }
4003
- if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
4013
+ if (!this.adPlayer.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0 && this.hasTimeToStartAnotherAd()) {
4004
4014
  this.tryNextAvailableAdWithRateLimit();
4005
4015
  }
4006
4016
  return;
@@ -5299,7 +5309,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5299
5309
  epoch = this.adBreakEpoch;
5300
5310
  loadPromise = function() {
5301
5311
  return _async_to_generator(function() {
5302
- var _this, hasAdError, adErrorPayload, errorListenerCleanup, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, lateErrorListener, error;
5312
+ var _this, hasAdError, adErrorPayload, errorListenerCleanup, _preloadAd_getLoadedAdDuration, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, loadedDuration, lateErrorListener, _poolEntry_durationSeconds, error;
5303
5313
  return _ts_generator(this, function(_state) {
5304
5314
  switch(_state.label){
5305
5315
  case 0:
@@ -5395,6 +5405,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5395
5405
  isReady: true,
5396
5406
  loadPromise: Promise.resolve()
5397
5407
  };
5408
+ loadedDuration = (_preloadAd_getLoadedAdDuration = preloadAd.getLoadedAdDuration) === null || _preloadAd_getLoadedAdDuration === void 0 ? void 0 : _preloadAd_getLoadedAdDuration.call(preloadAd);
5409
+ if (loadedDuration != null && loadedDuration > 0) {
5410
+ poolEntry.durationSeconds = loadedDuration;
5411
+ }
5398
5412
  lateErrorListener = function lateErrorListener(payload) {
5399
5413
  var index = _this.preloadPool.findIndex(function(entry) {
5400
5414
  return entry.vastUrl === vastUrl;
@@ -5412,7 +5426,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5412
5426
  preloadAd.on("ad_error", lateErrorListener);
5413
5427
  this.preloadPool.push(poolEntry);
5414
5428
  if (this.config.debugAdTiming) {
5415
- console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ")"));
5429
+ ;
5430
+ 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)"));
5416
5431
  }
5417
5432
  if (errorListenerCleanup) {
5418
5433
  errorListenerCleanup();
@@ -5459,7 +5474,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5459
5474
  key: "preloadPoolLoop",
5460
5475
  value: function preloadPoolLoop(baseVastUrl) {
5461
5476
  return _async_to_generator(function() {
5462
- var epoch, remaining, newUrls, newUrl;
5477
+ var epoch, newUrls, newUrl;
5463
5478
  return _ts_generator(this, function(_state) {
5464
5479
  switch(_state.label){
5465
5480
  case 0:
@@ -5526,14 +5541,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5526
5541
  8
5527
5542
  ];
5528
5543
  }
5529
- if (this.inAdBreak) {
5530
- remaining = this.getRemainingAdMs();
5531
- if (remaining <= 0) {
5532
- return [
5533
- 3,
5534
- 8
5535
- ];
5544
+ if (this.inAdBreak && !this.hasTimeToStartAnotherAd()) {
5545
+ if (this.config.debugAdTiming) {
5546
+ console.log("[PRELOAD-POOL] Not enough ad break time left, stopping preload");
5536
5547
  }
5548
+ return [
5549
+ 3,
5550
+ 8
5551
+ ];
5537
5552
  }
5538
5553
  newUrls = this.generateVastUrlsWithCorrelators(baseVastUrl, 1);
5539
5554
  newUrl = newUrls[0];
@@ -5693,8 +5708,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5693
5708
  }
5694
5709
  this.inAdBreak = true;
5695
5710
  this.currentAdBreakStartWallClockMs = Date.now();
5711
+ this.adBreakPlayedDurationMs = 0;
5696
5712
  this.currentAdIndex = 0;
5697
- this.totalAdsInBreak = 1;
5713
+ this.totalAdsInBreak = 0;
5698
5714
  this.adPodQueue = [];
5699
5715
  this.showAds = true;
5700
5716
  this.showPlaceholderLayer();
@@ -5748,14 +5764,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5748
5764
  case 1:
5749
5765
  _state.trys.push([
5750
5766
  1,
5751
- 8,
5767
+ 12,
5752
5768
  ,
5753
- 14
5769
+ 20
5754
5770
  ]);
5755
5771
  if (!(usePreloadedAd && preloadedController)) return [
5772
+ 3,
5773
+ 5
5774
+ ];
5775
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5756
5776
  3,
5757
5777
  3
5758
5778
  ];
5779
+ this.rejectLoadedAdForDuration(preloadedController);
5780
+ this.startContinuousFetching(baseVastUrl);
5781
+ return [
5782
+ 4,
5783
+ this.tryNextAvailableAdWithRateLimit()
5784
+ ];
5785
+ case 2:
5786
+ _state.sent();
5787
+ return [
5788
+ 2
5789
+ ];
5790
+ case 3:
5759
5791
  preservedMuted = this.adPlayer.getOriginalMutedState();
5760
5792
  preservedVolume = this.adPlayer.getOriginalVolume();
5761
5793
  this.adPlayer.destroy();
@@ -5768,7 +5800,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5768
5800
  }
5769
5801
  this.adPlayer.resume();
5770
5802
  this.consecutiveFailures = 0;
5771
- this.currentAdIndex++;
5772
5803
  this.startContinuousFetching(baseVastUrl);
5773
5804
  if (!this.preloadPoolActive) {
5774
5805
  this.preloadPoolActive = true;
@@ -5778,8 +5809,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5778
5809
  4,
5779
5810
  this.adPlayer.play()
5780
5811
  ];
5781
- case 2:
5812
+ case 4:
5782
5813
  _state.sent();
5814
+ this.markAdStarted();
5783
5815
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5784
5816
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5785
5817
  }
@@ -5787,21 +5819,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5787
5819
  this.adPlayer.setAdVolume(adVolume);
5788
5820
  return [
5789
5821
  3,
5790
- 7
5822
+ 11
5791
5823
  ];
5792
- case 3:
5824
+ case 5:
5793
5825
  return [
5794
5826
  4,
5795
5827
  this.enforceGlobalRateLimit()
5796
5828
  ];
5797
- case 4:
5829
+ case 6:
5798
5830
  _state.sent();
5799
5831
  this.lastAdRequestTime = Date.now();
5800
5832
  return [
5801
5833
  4,
5802
5834
  this.adPlayer.requestAds(firstAdUrl)
5803
5835
  ];
5804
- case 5:
5836
+ case 7:
5805
5837
  _state.sent();
5806
5838
  sendAdLoadedTracking(this.config.licenseKey, {
5807
5839
  source: "hls",
@@ -5811,8 +5843,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5811
5843
  if (this.config.debugAdTiming) {
5812
5844
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
5813
5845
  }
5846
+ if (!!this.ensureLoadedAdFitsBudget()) return [
5847
+ 3,
5848
+ 9
5849
+ ];
5850
+ this.rejectLoadedAdForDuration();
5851
+ this.recreateAdController();
5852
+ this.startContinuousFetching(baseVastUrl);
5853
+ return [
5854
+ 4,
5855
+ this.tryNextAvailableAdWithRateLimit()
5856
+ ];
5857
+ case 8:
5858
+ _state.sent();
5859
+ return [
5860
+ 2
5861
+ ];
5862
+ case 9:
5814
5863
  this.consecutiveFailures = 0;
5815
- this.currentAdIndex++;
5816
5864
  this.startContinuousFetching(baseVastUrl);
5817
5865
  if (!this.preloadPoolActive) {
5818
5866
  this.startPreloadPool(baseVastUrl, []);
@@ -5821,44 +5869,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5821
5869
  4,
5822
5870
  this.adPlayer.play()
5823
5871
  ];
5824
- case 6:
5872
+ case 10:
5825
5873
  _state.sent();
5874
+ this.markAdStarted();
5826
5875
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5827
5876
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5828
5877
  }
5829
5878
  adVolume1 = currentMuted ? 0 : currentVolume;
5830
5879
  this.adPlayer.setAdVolume(adVolume1);
5831
- _state.label = 7;
5832
- case 7:
5880
+ _state.label = 11;
5881
+ case 11:
5833
5882
  return [
5834
5883
  3,
5835
- 14
5884
+ 20
5836
5885
  ];
5837
- case 8:
5886
+ case 12:
5838
5887
  error = _state.sent();
5839
5888
  if (this.config.debugAdTiming) {
5840
5889
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
5841
5890
  }
5842
5891
  if (!!usePreloadedAd) return [
5843
5892
  3,
5844
- 12
5893
+ 18
5845
5894
  ];
5846
5895
  fallbackPreloaded = this.getPreloadedAd();
5847
5896
  if (!fallbackPreloaded) return [
5848
5897
  3,
5849
- 12
5898
+ 18
5850
5899
  ];
5851
5900
  if (this.config.debugAdTiming) {
5852
5901
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
5853
5902
  }
5854
- _state.label = 9;
5855
- case 9:
5903
+ _state.label = 13;
5904
+ case 13:
5856
5905
  _state.trys.push([
5857
- 9,
5858
- 11,
5906
+ 13,
5907
+ 17,
5859
5908
  ,
5860
- 12
5909
+ 18
5861
5910
  ]);
5911
+ if (!!this.ensureLoadedAdFitsBudget(fallbackPreloaded.adController)) return [
5912
+ 3,
5913
+ 15
5914
+ ];
5915
+ this.rejectLoadedAdForDuration(fallbackPreloaded.adController);
5916
+ this.startContinuousFetching(baseVastUrl);
5917
+ return [
5918
+ 4,
5919
+ this.tryNextAvailableAdWithRateLimit()
5920
+ ];
5921
+ case 14:
5922
+ _state.sent();
5923
+ return [
5924
+ 2
5925
+ ];
5926
+ case 15:
5862
5927
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
5863
5928
  preservedVolume1 = this.adPlayer.getOriginalVolume();
5864
5929
  this.adPlayer.destroy();
@@ -5868,7 +5933,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5868
5933
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
5869
5934
  this.adPlayer.resume();
5870
5935
  this.consecutiveFailures = 0;
5871
- this.currentAdIndex++;
5872
5936
  this.startContinuousFetching(baseVastUrl);
5873
5937
  if (!this.preloadPoolActive) {
5874
5938
  this.preloadPoolActive = true;
@@ -5878,8 +5942,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5878
5942
  4,
5879
5943
  this.adPlayer.play()
5880
5944
  ];
5881
- case 10:
5945
+ case 16:
5882
5946
  _state.sent();
5947
+ this.markAdStarted();
5883
5948
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5884
5949
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5885
5950
  }
@@ -5888,16 +5953,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5888
5953
  return [
5889
5954
  2
5890
5955
  ];
5891
- case 11:
5956
+ case 17:
5892
5957
  fallbackError = _state.sent();
5893
5958
  if (this.config.debugAdTiming) {
5894
5959
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
5895
5960
  }
5896
5961
  return [
5897
5962
  3,
5898
- 12
5963
+ 18
5899
5964
  ];
5900
- case 12:
5965
+ case 18:
5901
5966
  if (this.isTemporaryAdError(error)) {
5902
5967
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
5903
5968
  if (this.config.debugAdTiming) {
@@ -5915,13 +5980,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5915
5980
  4,
5916
5981
  this.tryNextAvailableAdWithRateLimit()
5917
5982
  ];
5918
- case 13:
5983
+ case 19:
5919
5984
  _state.sent();
5920
5985
  return [
5921
5986
  3,
5922
- 14
5987
+ 20
5923
5988
  ];
5924
- case 14:
5989
+ case 20:
5925
5990
  return [
5926
5991
  2
5927
5992
  ];
@@ -5962,10 +6027,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5962
6027
  return _ts_generator(this, function(_state) {
5963
6028
  switch(_state.label){
5964
6029
  case 0:
5965
- remaining = _this.getRemainingAdMs();
5966
- if (remaining <= 0) {
6030
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
6031
+ if (remaining <= _this.getMinRemainingMsToStartAd()) {
5967
6032
  if (_this.config.debugAdTiming) {
5968
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Ad break time expired, stopping URL generation");
6033
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (wall=".concat(_this.getWallClockRemainingAdMs(), "ms, budget=").concat(_this.getDurationBudgetRemainingMs(), "ms), stopping URL generation"));
5969
6034
  }
5970
6035
  return [
5971
6036
  2,
@@ -6047,7 +6112,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6047
6112
  });
6048
6113
  }
6049
6114
  _this.adRequestQueue.push(newAdUrl);
6050
- _this.totalAdsInBreak++;
6051
6115
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
6052
6116
  return [
6053
6117
  4,
@@ -6185,9 +6249,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6185
6249
  ];
6186
6250
  }
6187
6251
  remaining = this.getRemainingAdMs();
6188
- if (remaining <= 500) {
6252
+ if (!this.hasTimeToStartAnotherAd()) {
6189
6253
  if (this.config.debugAdTiming) {
6190
- console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F No time remaining, ending ad break");
6254
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough time remaining (".concat(remaining, "ms) to start another ad, ending ad break"));
6191
6255
  }
6192
6256
  this.handleAdPodComplete();
6193
6257
  return [
@@ -6216,7 +6280,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6216
6280
  preloaded = this.getPreloadedAd();
6217
6281
  if (!preloaded) return [
6218
6282
  3,
6219
- 4
6283
+ 6
6220
6284
  ];
6221
6285
  if (this.config.debugAdTiming) {
6222
6286
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6230,10 +6294,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6230
6294
  case 1:
6231
6295
  _state.trys.push([
6232
6296
  1,
6233
- 3,
6297
+ 5,
6234
6298
  ,
6235
- 4
6299
+ 6
6236
6300
  ]);
6301
+ if (!!this.ensureLoadedAdFitsBudget(preloaded.adController)) return [
6302
+ 3,
6303
+ 3
6304
+ ];
6305
+ this.rejectLoadedAdForDuration(preloaded.adController);
6306
+ return [
6307
+ 4,
6308
+ this.tryNextAvailableAdWithRateLimit()
6309
+ ];
6310
+ case 2:
6311
+ _state.sent();
6312
+ return [
6313
+ 2
6314
+ ];
6315
+ case 3:
6237
6316
  preservedMuted = this.adPlayer.getOriginalMutedState();
6238
6317
  preservedVolume = this.adPlayer.getOriginalVolume();
6239
6318
  this.adPlayer.destroy();
@@ -6242,15 +6321,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6242
6321
  this.attachImaEventListeners();
6243
6322
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6244
6323
  this.adPlayer.resume();
6245
- this.currentAdIndex++;
6246
- this.totalAdRequestsInBreak++;
6247
- this.lastAdRequestTime = Date.now();
6248
6324
  return [
6249
6325
  4,
6250
6326
  this.adPlayer.play()
6251
6327
  ];
6252
- case 2:
6328
+ case 4:
6253
6329
  _state.sent();
6330
+ this.markAdStarted();
6254
6331
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6255
6332
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6256
6333
  }
@@ -6259,25 +6336,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6259
6336
  return [
6260
6337
  2
6261
6338
  ];
6262
- case 3:
6339
+ case 5:
6263
6340
  error = _state.sent();
6264
6341
  if (this.config.debugAdTiming) {
6265
6342
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6266
6343
  }
6267
6344
  return [
6268
6345
  3,
6269
- 4
6346
+ 6
6270
6347
  ];
6271
- case 4:
6348
+ case 6:
6272
6349
  if (!(this.adRequestQueue.length > 0)) return [
6273
6350
  3,
6274
- 10
6351
+ 14
6275
6352
  ];
6276
6353
  nextAdUrl = this.adRequestQueue.shift();
6277
6354
  if (!nextAdUrl) {
6278
6355
  return [
6279
6356
  3,
6280
- 4
6357
+ 6
6281
6358
  ];
6282
6359
  }
6283
6360
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6286,7 +6363,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6286
6363
  }
6287
6364
  return [
6288
6365
  3,
6289
- 4
6366
+ 6
6290
6367
  ];
6291
6368
  }
6292
6369
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6295,40 +6372,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6295
6372
  }
6296
6373
  return [
6297
6374
  3,
6298
- 4
6375
+ 6
6299
6376
  ];
6300
6377
  }
6301
6378
  if (this.config.debugAdTiming) {
6302
- 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)"));
6379
+ console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6303
6380
  }
6304
- this.currentAdIndex++;
6305
- this.totalAdRequestsInBreak++;
6306
- this.lastAdRequestTime = Date.now();
6307
- _state.label = 5;
6308
- case 5:
6381
+ _state.label = 7;
6382
+ case 7:
6309
6383
  _state.trys.push([
6310
- 5,
6311
6384
  7,
6385
+ 9,
6312
6386
  ,
6313
- 9
6387
+ 13
6314
6388
  ]);
6315
6389
  return [
6316
6390
  4,
6317
6391
  this.playSingleAd(nextAdUrl)
6318
6392
  ];
6319
- case 6:
6393
+ case 8:
6320
6394
  _state.sent();
6321
6395
  this.consecutiveFailures = 0;
6322
6396
  return [
6323
6397
  3,
6324
- 9
6398
+ 13
6325
6399
  ];
6326
- case 7:
6400
+ case 9:
6327
6401
  error1 = _state.sent();
6328
6402
  errorMessage = error1.message;
6329
6403
  if (this.config.debugAdTiming) {
6330
6404
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6331
6405
  }
6406
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6407
+ 3,
6408
+ 11
6409
+ ];
6410
+ return [
6411
+ 4,
6412
+ this.tryNextAvailableAdWithRateLimit()
6413
+ ];
6414
+ case 10:
6415
+ _state.sent();
6416
+ return [
6417
+ 2
6418
+ ];
6419
+ case 11:
6332
6420
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6333
6421
  this.consecutiveFailures++;
6334
6422
  }
@@ -6336,21 +6424,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6336
6424
  4,
6337
6425
  this.tryNextAvailableAdWithRateLimit()
6338
6426
  ];
6339
- case 8:
6427
+ case 12:
6340
6428
  _state.sent();
6341
6429
  return [
6342
6430
  3,
6343
- 9
6431
+ 13
6344
6432
  ];
6345
- case 9:
6433
+ case 13:
6346
6434
  return [
6347
6435
  2
6348
6436
  ];
6349
- case 10:
6437
+ case 14:
6350
6438
  maxRetries = 3;
6351
6439
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6352
6440
  3,
6353
- 13
6441
+ 17
6354
6442
  ];
6355
6443
  if (this.config.debugAdTiming) {
6356
6444
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6361,18 +6449,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6361
6449
  return setTimeout(resolve, 500);
6362
6450
  })
6363
6451
  ];
6364
- case 11:
6452
+ case 15:
6365
6453
  _state.sent();
6366
6454
  return [
6367
6455
  4,
6368
6456
  this.tryNextAvailableAd(retryCount + 1)
6369
6457
  ];
6370
- case 12:
6458
+ case 16:
6371
6459
  _state.sent();
6372
6460
  return [
6373
6461
  2
6374
6462
  ];
6375
- case 13:
6463
+ case 17:
6376
6464
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6377
6465
  this.showPlaceholderAndWaitForAds();
6378
6466
  } else {
@@ -6435,7 +6523,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6435
6523
  case 1:
6436
6524
  if (!(i < maxChecks)) return [
6437
6525
  3,
6438
- 10
6526
+ 12
6439
6527
  ];
6440
6528
  return [
6441
6529
  4,
@@ -6456,15 +6544,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6456
6544
  }
6457
6545
  return [
6458
6546
  3,
6459
- 10
6547
+ 12
6548
+ ];
6549
+ }
6550
+ if (!this.hasTimeToStartAnotherAd()) {
6551
+ if (this.config.debugAdTiming) {
6552
+ console.log("[CONTINUOUS-FETCH] \u23F9\uFE0F Not enough time remaining during placeholder wait, ending ad break");
6553
+ }
6554
+ return [
6555
+ 3,
6556
+ 12
6460
6557
  ];
6461
6558
  }
6462
6559
  _state.label = 3;
6463
6560
  case 3:
6464
6561
  if (!(this.adRequestQueue.length > 0)) return [
6465
6562
  3,
6466
- 9
6563
+ 11
6467
6564
  ];
6565
+ if (!this.hasTimeToStartAnotherAd()) {
6566
+ return [
6567
+ 3,
6568
+ 11
6569
+ ];
6570
+ }
6468
6571
  nextAdUrl = this.adRequestQueue.shift();
6469
6572
  if (!nextAdUrl) {
6470
6573
  return [
@@ -6489,16 +6592,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6489
6592
  }
6490
6593
  this.isShowingPlaceholder = false;
6491
6594
  this.adPlayer.hidePlaceholder();
6492
- this.currentAdIndex++;
6493
- this.totalAdRequestsInBreak++;
6494
- this.lastAdRequestTime = Date.now();
6495
6595
  _state.label = 4;
6496
6596
  case 4:
6497
6597
  _state.trys.push([
6498
6598
  4,
6499
6599
  6,
6500
6600
  ,
6501
- 8
6601
+ 10
6502
6602
  ]);
6503
6603
  return [
6504
6604
  4,
@@ -6509,11 +6609,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6509
6609
  this.consecutiveFailures = 0;
6510
6610
  return [
6511
6611
  3,
6512
- 8
6612
+ 10
6513
6613
  ];
6514
6614
  case 6:
6515
6615
  error = _state.sent();
6516
6616
  errorMessage = error.message;
6617
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6618
+ 3,
6619
+ 8
6620
+ ];
6621
+ return [
6622
+ 4,
6623
+ this.tryNextAvailableAdWithRateLimit()
6624
+ ];
6625
+ case 7:
6626
+ _state.sent();
6627
+ return [
6628
+ 2
6629
+ ];
6630
+ case 8:
6517
6631
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6518
6632
  this.consecutiveFailures++;
6519
6633
  }
@@ -6521,23 +6635,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6521
6635
  4,
6522
6636
  this.tryNextAvailableAdWithRateLimit()
6523
6637
  ];
6524
- case 7:
6638
+ case 9:
6525
6639
  _state.sent();
6526
6640
  return [
6527
6641
  3,
6528
- 8
6642
+ 10
6529
6643
  ];
6530
- case 8:
6644
+ case 10:
6531
6645
  return [
6532
6646
  2
6533
6647
  ];
6534
- case 9:
6648
+ case 11:
6535
6649
  i++;
6536
6650
  return [
6537
6651
  3,
6538
6652
  1
6539
6653
  ];
6540
- case 10:
6654
+ case 12:
6541
6655
  if (this.config.debugAdTiming) {
6542
6656
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6543
6657
  }
@@ -6747,21 +6861,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6747
6861
  if (!this.inAdBreak) return;
6748
6862
  this.adStopTimerId = void 0;
6749
6863
  var adPlaying = this.adPlayer.isAdPlaying();
6750
- var pendingAds = this.adPodQueue.length > 0;
6751
6864
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
6752
6865
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
6753
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
6866
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
6754
6867
  var elapsedSinceStartMs = 0;
6755
6868
  if (this.currentAdBreakStartWallClockMs != null) {
6756
6869
  elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
6757
6870
  }
6758
6871
  var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
6759
6872
  var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
6760
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
6873
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
6761
6874
  if (shouldExtendAdBreak) {
6762
6875
  this.scheduleAdStopCountdown(checkIntervalMs);
6763
6876
  return;
6764
6877
  }
6878
+ if (this.config.debugAdTiming) {
6879
+ console.log("[StormcloudVideoPlayer] ⏱️ Ad break boundary reached (overrun=".concat(overrunMs, "ms) - hard-stopping ads and resuming stream"));
6880
+ }
6765
6881
  if (adPlaying) {
6766
6882
  this.adPlayer.stop().catch(function() {});
6767
6883
  }
@@ -7007,6 +7123,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7007
7123
  2
7008
7124
  ];
7009
7125
  }
7126
+ if (!this.ensureLoadedAdFitsBudget()) {
7127
+ this.rejectLoadedAdForDuration();
7128
+ this.recreateAdController();
7129
+ throw new Error("ad_duration_exceeds_budget");
7130
+ }
7010
7131
  _state.label = 4;
7011
7132
  case 4:
7012
7133
  _state.trys.push([
@@ -7022,6 +7143,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7022
7143
  ];
7023
7144
  case 5:
7024
7145
  _state.sent();
7146
+ this.markAdStarted();
7025
7147
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7026
7148
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7027
7149
  }
@@ -7053,6 +7175,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7053
7175
  ,
7054
7176
  10
7055
7177
  ]);
7178
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7179
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7180
+ throw playError;
7181
+ }
7056
7182
  this.clearAdFailsafeTimer();
7057
7183
  preservedMuted = this.adPlayer.getOriginalMutedState();
7058
7184
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -7063,14 +7189,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7063
7189
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
7064
7190
  this.adPlayer.resume();
7065
7191
  this.consecutiveFailures = 0;
7066
- this.currentAdIndex++;
7067
- this.totalAdRequestsInBreak++;
7068
7192
  return [
7069
7193
  4,
7070
7194
  this.adPlayer.play()
7071
7195
  ];
7072
7196
  case 8:
7073
7197
  _state.sent();
7198
+ this.markAdStarted();
7074
7199
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7075
7200
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7076
7201
  }
@@ -7131,6 +7256,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7131
7256
  ,
7132
7257
  16
7133
7258
  ]);
7259
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7260
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7261
+ throw error;
7262
+ }
7134
7263
  this.clearAdRequestWatchdog();
7135
7264
  this.clearAdFailsafeTimer();
7136
7265
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7142,14 +7271,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7142
7271
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7143
7272
  this.adPlayer.resume();
7144
7273
  this.consecutiveFailures = 0;
7145
- this.currentAdIndex++;
7146
- this.totalAdRequestsInBreak++;
7147
7274
  return [
7148
7275
  4,
7149
7276
  this.adPlayer.play()
7150
7277
  ];
7151
7278
  case 14:
7152
7279
  _state.sent();
7280
+ this.markAdStarted();
7153
7281
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7154
7282
  return [
7155
7283
  2
@@ -7210,6 +7338,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7210
7338
  this.adRequestQueue = [];
7211
7339
  this.inAdBreak = false;
7212
7340
  this.adDetectSentForCurrentBreak = false;
7341
+ this.adBreakPlayedDurationMs = 0;
7213
7342
  this.activeScte35BreakKey = void 0;
7214
7343
  this.scheduledScte35BreakKey = void 0;
7215
7344
  this.expectedAdBreakDurationMs = void 0;
@@ -7267,11 +7396,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7267
7396
  this.handleAdPodComplete();
7268
7397
  return;
7269
7398
  }
7270
- if (remaining > 500) {
7399
+ if (this.hasTimeToStartAnotherAd()) {
7271
7400
  this.tryNextAvailableAdWithRateLimit();
7272
7401
  } else {
7273
7402
  if (this.config.debugAdTiming) {
7274
- console.log("[AD-ERROR] Ad failed and no time remaining. Failed URLs:", this.failedVastUrls.size);
7403
+ console.log("[AD-ERROR] Ad failed and not enough time remaining for another ad. Failed URLs:", this.failedVastUrls.size);
7275
7404
  }
7276
7405
  this.handleAdPodComplete();
7277
7406
  }
@@ -7399,13 +7528,134 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7399
7528
  }
7400
7529
  },
7401
7530
  {
7402
- key: "getRemainingAdMs",
7403
- value: function getRemainingAdMs() {
7404
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7531
+ key: "getWallClockRemainingAdMs",
7532
+ value: function getWallClockRemainingAdMs() {
7533
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {
7534
+ return 0;
7535
+ }
7405
7536
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7406
7537
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7407
7538
  }
7408
7539
  },
7540
+ {
7541
+ key: "getDurationBudgetRemainingMs",
7542
+ value: function getDurationBudgetRemainingMs() {
7543
+ if (this.expectedAdBreakDurationMs == null) {
7544
+ return 0;
7545
+ }
7546
+ return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);
7547
+ }
7548
+ },
7549
+ {
7550
+ key: "getLoadedAdDurationMs",
7551
+ value: function getLoadedAdDurationMs() {
7552
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7553
+ var _adController_getLoadedAdDuration;
7554
+ var durationSec = (_adController_getLoadedAdDuration = adController.getLoadedAdDuration) === null || _adController_getLoadedAdDuration === void 0 ? void 0 : _adController_getLoadedAdDuration.call(adController);
7555
+ if (durationSec == null || durationSec <= 0) {
7556
+ return 0;
7557
+ }
7558
+ return Math.round(durationSec * 1e3);
7559
+ }
7560
+ },
7561
+ {
7562
+ key: "adFitsRemainingBudget",
7563
+ value: function adFitsRemainingBudget(durationMs) {
7564
+ var adController = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.adPlayer;
7565
+ if (durationMs <= 0) {
7566
+ return true;
7567
+ }
7568
+ return durationMs <= this.getDurationBudgetRemainingMs();
7569
+ }
7570
+ },
7571
+ {
7572
+ key: "logAdDurationDecision",
7573
+ value: function logAdDurationDecision(accepted, durationMs) {
7574
+ var adController = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : this.adPlayer;
7575
+ var _ref;
7576
+ var _adController_getLoadedAdId;
7577
+ if (!this.config.debugAdTiming) {
7578
+ return;
7579
+ }
7580
+ var remainingBudget = this.getDurationBudgetRemainingMs();
7581
+ var adId = (_ref = (_adController_getLoadedAdId = adController.getLoadedAdId) === null || _adController_getLoadedAdId === void 0 ? void 0 : _adController_getLoadedAdId.call(adController)) !== null && _ref !== void 0 ? _ref : "unknown";
7582
+ if (accepted) {
7583
+ console.log("[AD-DURATION] Accepted ad id=".concat(adId, ", duration=").concat(durationMs, "ms, budget remaining after play=").concat(Math.max(0, remainingBudget - durationMs), "ms"));
7584
+ } else {
7585
+ console.log("[AD-DURATION] Rejected ad id=".concat(adId, ", duration=").concat(durationMs, "ms exceeds remaining budget=").concat(remainingBudget, "ms — requesting another ad"));
7586
+ }
7587
+ }
7588
+ },
7589
+ {
7590
+ key: "recordPlayedAdDuration",
7591
+ value: function recordPlayedAdDuration() {
7592
+ var durationMs = this.getLoadedAdDurationMs();
7593
+ if (durationMs <= 0) {
7594
+ return;
7595
+ }
7596
+ this.adBreakPlayedDurationMs += durationMs;
7597
+ if (this.config.debugAdTiming) {
7598
+ var _this_expectedAdBreakDurationMs;
7599
+ 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"));
7600
+ }
7601
+ }
7602
+ },
7603
+ {
7604
+ key: "ensureLoadedAdFitsBudget",
7605
+ value: function ensureLoadedAdFitsBudget() {
7606
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7607
+ var durationMs = this.getLoadedAdDurationMs(adController);
7608
+ var fits = this.adFitsRemainingBudget(durationMs, adController);
7609
+ this.logAdDurationDecision(fits, durationMs, adController);
7610
+ return fits;
7611
+ }
7612
+ },
7613
+ {
7614
+ key: "rejectLoadedAdForDuration",
7615
+ value: function rejectLoadedAdForDuration() {
7616
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7617
+ try {
7618
+ adController.destroy();
7619
+ } catch (unused) {}
7620
+ }
7621
+ },
7622
+ {
7623
+ key: "markAdStarted",
7624
+ value: function markAdStarted() {
7625
+ this.currentAdIndex++;
7626
+ this.totalAdsInBreak = this.currentAdIndex;
7627
+ this.totalAdRequestsInBreak++;
7628
+ this.lastAdRequestTime = Date.now();
7629
+ }
7630
+ },
7631
+ {
7632
+ key: "getRemainingAdMs",
7633
+ value: function getRemainingAdMs() {
7634
+ var wallClockRemaining = this.getWallClockRemainingAdMs();
7635
+ var budgetRemaining = this.getDurationBudgetRemainingMs();
7636
+ if (this.expectedAdBreakDurationMs == null) {
7637
+ return wallClockRemaining;
7638
+ }
7639
+ return Math.min(wallClockRemaining, budgetRemaining);
7640
+ }
7641
+ },
7642
+ {
7643
+ key: "getMinRemainingMsToStartAd",
7644
+ value: function getMinRemainingMsToStartAd() {
7645
+ var configured = this.config.minRemainingMsToStartAd;
7646
+ if (typeof configured === "number" && configured >= 0) {
7647
+ return configured;
7648
+ }
7649
+ return this.defaultMinRemainingMsToStartAd;
7650
+ }
7651
+ },
7652
+ {
7653
+ key: "hasTimeToStartAnotherAd",
7654
+ value: function hasTimeToStartAnotherAd() {
7655
+ var minRemaining = this.getMinRemainingMsToStartAd();
7656
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7657
+ }
7658
+ },
7409
7659
  {
7410
7660
  key: "findBreakForTime",
7411
7661
  value: function findBreakForTime(nowMs) {