stormcloud-video-player 0.8.10 → 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.cjs CHANGED
@@ -1476,6 +1476,12 @@ function createHlsAdPlayer(contentVideo, options) {
1476
1476
  contentVideo.style.visibility = "visible";
1477
1477
  contentVideo.style.opacity = "1";
1478
1478
  }
1479
+ },
1480
+ getLoadedAdDuration: function getLoadedAdDuration() {
1481
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration;
1482
+ },
1483
+ getLoadedAdId: function getLoadedAdId() {
1484
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.id;
1479
1485
  }
1480
1486
  };
1481
1487
  }
@@ -2935,6 +2941,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2935
2941
  this.preloadPoolLoopRunning = false;
2936
2942
  this.adDetectSentForCurrentBreak = false;
2937
2943
  this.adBreakEpoch = 0;
2944
+ this.adBreakPlayedDurationMs = 0;
2938
2945
  this.palPlaybackStarted = false;
2939
2946
  this.streamCorrelator = generateCorrelator();
2940
2947
  this.consentSignals = {};
@@ -3431,6 +3438,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3431
3438
  adIndex: _this.currentAdIndex,
3432
3439
  timestamp: /* @__PURE__ */ new Date().toISOString()
3433
3440
  }).catch(function() {});
3441
+ _this.recordPlayedAdDuration();
3434
3442
  var remaining = _this.getRemainingAdMs();
3435
3443
  _this.consecutiveFailures = 0;
3436
3444
  if (_this.config.debugAdTiming) {
@@ -3506,6 +3514,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3506
3514
  return;
3507
3515
  }
3508
3516
  _this.consecutiveFailures = 0;
3517
+ _this.recordPlayedAdDuration();
3509
3518
  var remaining = _this.getRemainingAdMs();
3510
3519
  if (_this.config.debugAdTiming) {
3511
3520
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
@@ -5511,7 +5520,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5511
5520
  epoch = this.adBreakEpoch;
5512
5521
  loadPromise = function() {
5513
5522
  return _async_to_generator(function() {
5514
- var _this, hasAdError, adErrorPayload, errorListenerCleanup, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, lateErrorListener, error;
5523
+ var _this, hasAdError, adErrorPayload, errorListenerCleanup, _preloadAd_getLoadedAdDuration, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, loadedDuration, lateErrorListener, _poolEntry_durationSeconds, error;
5515
5524
  return _ts_generator(this, function(_state) {
5516
5525
  switch(_state.label){
5517
5526
  case 0:
@@ -5607,6 +5616,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5607
5616
  isReady: true,
5608
5617
  loadPromise: Promise.resolve()
5609
5618
  };
5619
+ loadedDuration = (_preloadAd_getLoadedAdDuration = preloadAd.getLoadedAdDuration) === null || _preloadAd_getLoadedAdDuration === void 0 ? void 0 : _preloadAd_getLoadedAdDuration.call(preloadAd);
5620
+ if (loadedDuration != null && loadedDuration > 0) {
5621
+ poolEntry.durationSeconds = loadedDuration;
5622
+ }
5610
5623
  lateErrorListener = function lateErrorListener(payload) {
5611
5624
  var index = _this.preloadPool.findIndex(function(entry) {
5612
5625
  return entry.vastUrl === vastUrl;
@@ -5624,7 +5637,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5624
5637
  preloadAd.on("ad_error", lateErrorListener);
5625
5638
  this.preloadPool.push(poolEntry);
5626
5639
  if (this.config.debugAdTiming) {
5627
- console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ")"));
5640
+ ;
5641
+ 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)"));
5628
5642
  }
5629
5643
  if (errorListenerCleanup) {
5630
5644
  errorListenerCleanup();
@@ -5905,8 +5919,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5905
5919
  }
5906
5920
  this.inAdBreak = true;
5907
5921
  this.currentAdBreakStartWallClockMs = Date.now();
5922
+ this.adBreakPlayedDurationMs = 0;
5908
5923
  this.currentAdIndex = 0;
5909
- this.totalAdsInBreak = 1;
5924
+ this.totalAdsInBreak = 0;
5910
5925
  this.adPodQueue = [];
5911
5926
  this.showAds = true;
5912
5927
  this.showPlaceholderLayer();
@@ -5960,14 +5975,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5960
5975
  case 1:
5961
5976
  _state.trys.push([
5962
5977
  1,
5963
- 8,
5978
+ 12,
5964
5979
  ,
5965
- 14
5980
+ 20
5966
5981
  ]);
5967
5982
  if (!(usePreloadedAd && preloadedController)) return [
5983
+ 3,
5984
+ 5
5985
+ ];
5986
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5968
5987
  3,
5969
5988
  3
5970
5989
  ];
5990
+ this.rejectLoadedAdForDuration(preloadedController);
5991
+ this.startContinuousFetching(baseVastUrl);
5992
+ return [
5993
+ 4,
5994
+ this.tryNextAvailableAdWithRateLimit()
5995
+ ];
5996
+ case 2:
5997
+ _state.sent();
5998
+ return [
5999
+ 2
6000
+ ];
6001
+ case 3:
5971
6002
  preservedMuted = this.adPlayer.getOriginalMutedState();
5972
6003
  preservedVolume = this.adPlayer.getOriginalVolume();
5973
6004
  this.adPlayer.destroy();
@@ -5980,7 +6011,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5980
6011
  }
5981
6012
  this.adPlayer.resume();
5982
6013
  this.consecutiveFailures = 0;
5983
- this.currentAdIndex++;
5984
6014
  this.startContinuousFetching(baseVastUrl);
5985
6015
  if (!this.preloadPoolActive) {
5986
6016
  this.preloadPoolActive = true;
@@ -5990,8 +6020,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5990
6020
  4,
5991
6021
  this.adPlayer.play()
5992
6022
  ];
5993
- case 2:
6023
+ case 4:
5994
6024
  _state.sent();
6025
+ this.markAdStarted();
5995
6026
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5996
6027
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5997
6028
  }
@@ -5999,21 +6030,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5999
6030
  this.adPlayer.setAdVolume(adVolume);
6000
6031
  return [
6001
6032
  3,
6002
- 7
6033
+ 11
6003
6034
  ];
6004
- case 3:
6035
+ case 5:
6005
6036
  return [
6006
6037
  4,
6007
6038
  this.enforceGlobalRateLimit()
6008
6039
  ];
6009
- case 4:
6040
+ case 6:
6010
6041
  _state.sent();
6011
6042
  this.lastAdRequestTime = Date.now();
6012
6043
  return [
6013
6044
  4,
6014
6045
  this.adPlayer.requestAds(firstAdUrl)
6015
6046
  ];
6016
- case 5:
6047
+ case 7:
6017
6048
  _state.sent();
6018
6049
  sendAdLoadedTracking(this.config.licenseKey, {
6019
6050
  source: "hls",
@@ -6023,8 +6054,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6023
6054
  if (this.config.debugAdTiming) {
6024
6055
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
6025
6056
  }
6057
+ if (!!this.ensureLoadedAdFitsBudget()) return [
6058
+ 3,
6059
+ 9
6060
+ ];
6061
+ this.rejectLoadedAdForDuration();
6062
+ this.recreateAdController();
6063
+ this.startContinuousFetching(baseVastUrl);
6064
+ return [
6065
+ 4,
6066
+ this.tryNextAvailableAdWithRateLimit()
6067
+ ];
6068
+ case 8:
6069
+ _state.sent();
6070
+ return [
6071
+ 2
6072
+ ];
6073
+ case 9:
6026
6074
  this.consecutiveFailures = 0;
6027
- this.currentAdIndex++;
6028
6075
  this.startContinuousFetching(baseVastUrl);
6029
6076
  if (!this.preloadPoolActive) {
6030
6077
  this.startPreloadPool(baseVastUrl, []);
@@ -6033,44 +6080,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6033
6080
  4,
6034
6081
  this.adPlayer.play()
6035
6082
  ];
6036
- case 6:
6083
+ case 10:
6037
6084
  _state.sent();
6085
+ this.markAdStarted();
6038
6086
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6039
6087
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6040
6088
  }
6041
6089
  adVolume1 = currentMuted ? 0 : currentVolume;
6042
6090
  this.adPlayer.setAdVolume(adVolume1);
6043
- _state.label = 7;
6044
- case 7:
6091
+ _state.label = 11;
6092
+ case 11:
6045
6093
  return [
6046
6094
  3,
6047
- 14
6095
+ 20
6048
6096
  ];
6049
- case 8:
6097
+ case 12:
6050
6098
  error = _state.sent();
6051
6099
  if (this.config.debugAdTiming) {
6052
6100
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
6053
6101
  }
6054
6102
  if (!!usePreloadedAd) return [
6055
6103
  3,
6056
- 12
6104
+ 18
6057
6105
  ];
6058
6106
  fallbackPreloaded = this.getPreloadedAd();
6059
6107
  if (!fallbackPreloaded) return [
6060
6108
  3,
6061
- 12
6109
+ 18
6062
6110
  ];
6063
6111
  if (this.config.debugAdTiming) {
6064
6112
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
6065
6113
  }
6066
- _state.label = 9;
6067
- case 9:
6114
+ _state.label = 13;
6115
+ case 13:
6068
6116
  _state.trys.push([
6069
- 9,
6070
- 11,
6117
+ 13,
6118
+ 17,
6071
6119
  ,
6072
- 12
6120
+ 18
6073
6121
  ]);
6122
+ if (!!this.ensureLoadedAdFitsBudget(fallbackPreloaded.adController)) return [
6123
+ 3,
6124
+ 15
6125
+ ];
6126
+ this.rejectLoadedAdForDuration(fallbackPreloaded.adController);
6127
+ this.startContinuousFetching(baseVastUrl);
6128
+ return [
6129
+ 4,
6130
+ this.tryNextAvailableAdWithRateLimit()
6131
+ ];
6132
+ case 14:
6133
+ _state.sent();
6134
+ return [
6135
+ 2
6136
+ ];
6137
+ case 15:
6074
6138
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
6075
6139
  preservedVolume1 = this.adPlayer.getOriginalVolume();
6076
6140
  this.adPlayer.destroy();
@@ -6080,7 +6144,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6080
6144
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
6081
6145
  this.adPlayer.resume();
6082
6146
  this.consecutiveFailures = 0;
6083
- this.currentAdIndex++;
6084
6147
  this.startContinuousFetching(baseVastUrl);
6085
6148
  if (!this.preloadPoolActive) {
6086
6149
  this.preloadPoolActive = true;
@@ -6090,8 +6153,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6090
6153
  4,
6091
6154
  this.adPlayer.play()
6092
6155
  ];
6093
- case 10:
6156
+ case 16:
6094
6157
  _state.sent();
6158
+ this.markAdStarted();
6095
6159
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6096
6160
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6097
6161
  }
@@ -6100,16 +6164,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6100
6164
  return [
6101
6165
  2
6102
6166
  ];
6103
- case 11:
6167
+ case 17:
6104
6168
  fallbackError = _state.sent();
6105
6169
  if (this.config.debugAdTiming) {
6106
6170
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
6107
6171
  }
6108
6172
  return [
6109
6173
  3,
6110
- 12
6174
+ 18
6111
6175
  ];
6112
- case 12:
6176
+ case 18:
6113
6177
  if (this.isTemporaryAdError(error)) {
6114
6178
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
6115
6179
  if (this.config.debugAdTiming) {
@@ -6127,13 +6191,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6127
6191
  4,
6128
6192
  this.tryNextAvailableAdWithRateLimit()
6129
6193
  ];
6130
- case 13:
6194
+ case 19:
6131
6195
  _state.sent();
6132
6196
  return [
6133
6197
  3,
6134
- 14
6198
+ 20
6135
6199
  ];
6136
- case 14:
6200
+ case 20:
6137
6201
  return [
6138
6202
  2
6139
6203
  ];
@@ -6174,10 +6238,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6174
6238
  return _ts_generator(this, function(_state) {
6175
6239
  switch(_state.label){
6176
6240
  case 0:
6177
- remaining = _this.getRemainingAdMs();
6241
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
6178
6242
  if (remaining <= _this.getMinRemainingMsToStartAd()) {
6179
6243
  if (_this.config.debugAdTiming) {
6180
- console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (".concat(remaining, "ms), stopping URL generation"));
6244
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (wall=".concat(_this.getWallClockRemainingAdMs(), "ms, budget=").concat(_this.getDurationBudgetRemainingMs(), "ms), stopping URL generation"));
6181
6245
  }
6182
6246
  return [
6183
6247
  2,
@@ -6259,7 +6323,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6259
6323
  });
6260
6324
  }
6261
6325
  _this.adRequestQueue.push(newAdUrl);
6262
- _this.totalAdsInBreak++;
6263
6326
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
6264
6327
  return [
6265
6328
  4,
@@ -6428,7 +6491,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6428
6491
  preloaded = this.getPreloadedAd();
6429
6492
  if (!preloaded) return [
6430
6493
  3,
6431
- 4
6494
+ 6
6432
6495
  ];
6433
6496
  if (this.config.debugAdTiming) {
6434
6497
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6442,10 +6505,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6442
6505
  case 1:
6443
6506
  _state.trys.push([
6444
6507
  1,
6445
- 3,
6508
+ 5,
6446
6509
  ,
6447
- 4
6510
+ 6
6448
6511
  ]);
6512
+ if (!!this.ensureLoadedAdFitsBudget(preloaded.adController)) return [
6513
+ 3,
6514
+ 3
6515
+ ];
6516
+ this.rejectLoadedAdForDuration(preloaded.adController);
6517
+ return [
6518
+ 4,
6519
+ this.tryNextAvailableAdWithRateLimit()
6520
+ ];
6521
+ case 2:
6522
+ _state.sent();
6523
+ return [
6524
+ 2
6525
+ ];
6526
+ case 3:
6449
6527
  preservedMuted = this.adPlayer.getOriginalMutedState();
6450
6528
  preservedVolume = this.adPlayer.getOriginalVolume();
6451
6529
  this.adPlayer.destroy();
@@ -6454,15 +6532,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6454
6532
  this.attachImaEventListeners();
6455
6533
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6456
6534
  this.adPlayer.resume();
6457
- this.currentAdIndex++;
6458
- this.totalAdRequestsInBreak++;
6459
- this.lastAdRequestTime = Date.now();
6460
6535
  return [
6461
6536
  4,
6462
6537
  this.adPlayer.play()
6463
6538
  ];
6464
- case 2:
6539
+ case 4:
6465
6540
  _state.sent();
6541
+ this.markAdStarted();
6466
6542
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6467
6543
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6468
6544
  }
@@ -6471,25 +6547,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6471
6547
  return [
6472
6548
  2
6473
6549
  ];
6474
- case 3:
6550
+ case 5:
6475
6551
  error = _state.sent();
6476
6552
  if (this.config.debugAdTiming) {
6477
6553
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6478
6554
  }
6479
6555
  return [
6480
6556
  3,
6481
- 4
6557
+ 6
6482
6558
  ];
6483
- case 4:
6559
+ case 6:
6484
6560
  if (!(this.adRequestQueue.length > 0)) return [
6485
6561
  3,
6486
- 10
6562
+ 14
6487
6563
  ];
6488
6564
  nextAdUrl = this.adRequestQueue.shift();
6489
6565
  if (!nextAdUrl) {
6490
6566
  return [
6491
6567
  3,
6492
- 4
6568
+ 6
6493
6569
  ];
6494
6570
  }
6495
6571
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6498,7 +6574,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6498
6574
  }
6499
6575
  return [
6500
6576
  3,
6501
- 4
6577
+ 6
6502
6578
  ];
6503
6579
  }
6504
6580
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6507,40 +6583,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6507
6583
  }
6508
6584
  return [
6509
6585
  3,
6510
- 4
6586
+ 6
6511
6587
  ];
6512
6588
  }
6513
6589
  if (this.config.debugAdTiming) {
6514
- 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)"));
6590
+ console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6515
6591
  }
6516
- this.currentAdIndex++;
6517
- this.totalAdRequestsInBreak++;
6518
- this.lastAdRequestTime = Date.now();
6519
- _state.label = 5;
6520
- case 5:
6592
+ _state.label = 7;
6593
+ case 7:
6521
6594
  _state.trys.push([
6522
- 5,
6523
6595
  7,
6596
+ 9,
6524
6597
  ,
6525
- 9
6598
+ 13
6526
6599
  ]);
6527
6600
  return [
6528
6601
  4,
6529
6602
  this.playSingleAd(nextAdUrl)
6530
6603
  ];
6531
- case 6:
6604
+ case 8:
6532
6605
  _state.sent();
6533
6606
  this.consecutiveFailures = 0;
6534
6607
  return [
6535
6608
  3,
6536
- 9
6609
+ 13
6537
6610
  ];
6538
- case 7:
6611
+ case 9:
6539
6612
  error1 = _state.sent();
6540
6613
  errorMessage = error1.message;
6541
6614
  if (this.config.debugAdTiming) {
6542
6615
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6543
6616
  }
6617
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6618
+ 3,
6619
+ 11
6620
+ ];
6621
+ return [
6622
+ 4,
6623
+ this.tryNextAvailableAdWithRateLimit()
6624
+ ];
6625
+ case 10:
6626
+ _state.sent();
6627
+ return [
6628
+ 2
6629
+ ];
6630
+ case 11:
6544
6631
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6545
6632
  this.consecutiveFailures++;
6546
6633
  }
@@ -6548,21 +6635,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6548
6635
  4,
6549
6636
  this.tryNextAvailableAdWithRateLimit()
6550
6637
  ];
6551
- case 8:
6638
+ case 12:
6552
6639
  _state.sent();
6553
6640
  return [
6554
6641
  3,
6555
- 9
6642
+ 13
6556
6643
  ];
6557
- case 9:
6644
+ case 13:
6558
6645
  return [
6559
6646
  2
6560
6647
  ];
6561
- case 10:
6648
+ case 14:
6562
6649
  maxRetries = 3;
6563
6650
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6564
6651
  3,
6565
- 13
6652
+ 17
6566
6653
  ];
6567
6654
  if (this.config.debugAdTiming) {
6568
6655
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6573,18 +6660,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6573
6660
  return setTimeout(resolve, 500);
6574
6661
  })
6575
6662
  ];
6576
- case 11:
6663
+ case 15:
6577
6664
  _state.sent();
6578
6665
  return [
6579
6666
  4,
6580
6667
  this.tryNextAvailableAd(retryCount + 1)
6581
6668
  ];
6582
- case 12:
6669
+ case 16:
6583
6670
  _state.sent();
6584
6671
  return [
6585
6672
  2
6586
6673
  ];
6587
- case 13:
6674
+ case 17:
6588
6675
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6589
6676
  this.showPlaceholderAndWaitForAds();
6590
6677
  } else {
@@ -6647,7 +6734,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6647
6734
  case 1:
6648
6735
  if (!(i < maxChecks)) return [
6649
6736
  3,
6650
- 10
6737
+ 12
6651
6738
  ];
6652
6739
  return [
6653
6740
  4,
@@ -6668,7 +6755,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6668
6755
  }
6669
6756
  return [
6670
6757
  3,
6671
- 10
6758
+ 12
6672
6759
  ];
6673
6760
  }
6674
6761
  if (!this.hasTimeToStartAnotherAd()) {
@@ -6677,19 +6764,19 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6677
6764
  }
6678
6765
  return [
6679
6766
  3,
6680
- 10
6767
+ 12
6681
6768
  ];
6682
6769
  }
6683
6770
  _state.label = 3;
6684
6771
  case 3:
6685
6772
  if (!(this.adRequestQueue.length > 0)) return [
6686
6773
  3,
6687
- 9
6774
+ 11
6688
6775
  ];
6689
6776
  if (!this.hasTimeToStartAnotherAd()) {
6690
6777
  return [
6691
6778
  3,
6692
- 9
6779
+ 11
6693
6780
  ];
6694
6781
  }
6695
6782
  nextAdUrl = this.adRequestQueue.shift();
@@ -6716,16 +6803,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6716
6803
  }
6717
6804
  this.isShowingPlaceholder = false;
6718
6805
  this.adPlayer.hidePlaceholder();
6719
- this.currentAdIndex++;
6720
- this.totalAdRequestsInBreak++;
6721
- this.lastAdRequestTime = Date.now();
6722
6806
  _state.label = 4;
6723
6807
  case 4:
6724
6808
  _state.trys.push([
6725
6809
  4,
6726
6810
  6,
6727
6811
  ,
6728
- 8
6812
+ 10
6729
6813
  ]);
6730
6814
  return [
6731
6815
  4,
@@ -6736,11 +6820,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6736
6820
  this.consecutiveFailures = 0;
6737
6821
  return [
6738
6822
  3,
6739
- 8
6823
+ 10
6740
6824
  ];
6741
6825
  case 6:
6742
6826
  error = _state.sent();
6743
6827
  errorMessage = error.message;
6828
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6829
+ 3,
6830
+ 8
6831
+ ];
6832
+ return [
6833
+ 4,
6834
+ this.tryNextAvailableAdWithRateLimit()
6835
+ ];
6836
+ case 7:
6837
+ _state.sent();
6838
+ return [
6839
+ 2
6840
+ ];
6841
+ case 8:
6744
6842
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6745
6843
  this.consecutiveFailures++;
6746
6844
  }
@@ -6748,23 +6846,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6748
6846
  4,
6749
6847
  this.tryNextAvailableAdWithRateLimit()
6750
6848
  ];
6751
- case 7:
6849
+ case 9:
6752
6850
  _state.sent();
6753
6851
  return [
6754
6852
  3,
6755
- 8
6853
+ 10
6756
6854
  ];
6757
- case 8:
6855
+ case 10:
6758
6856
  return [
6759
6857
  2
6760
6858
  ];
6761
- case 9:
6859
+ case 11:
6762
6860
  i++;
6763
6861
  return [
6764
6862
  3,
6765
6863
  1
6766
6864
  ];
6767
- case 10:
6865
+ case 12:
6768
6866
  if (this.config.debugAdTiming) {
6769
6867
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6770
6868
  }
@@ -7236,6 +7334,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7236
7334
  2
7237
7335
  ];
7238
7336
  }
7337
+ if (!this.ensureLoadedAdFitsBudget()) {
7338
+ this.rejectLoadedAdForDuration();
7339
+ this.recreateAdController();
7340
+ throw new Error("ad_duration_exceeds_budget");
7341
+ }
7239
7342
  _state.label = 4;
7240
7343
  case 4:
7241
7344
  _state.trys.push([
@@ -7251,6 +7354,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7251
7354
  ];
7252
7355
  case 5:
7253
7356
  _state.sent();
7357
+ this.markAdStarted();
7254
7358
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7255
7359
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7256
7360
  }
@@ -7282,6 +7386,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7282
7386
  ,
7283
7387
  10
7284
7388
  ]);
7389
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7390
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7391
+ throw playError;
7392
+ }
7285
7393
  this.clearAdFailsafeTimer();
7286
7394
  preservedMuted = this.adPlayer.getOriginalMutedState();
7287
7395
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -7292,14 +7400,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7292
7400
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
7293
7401
  this.adPlayer.resume();
7294
7402
  this.consecutiveFailures = 0;
7295
- this.currentAdIndex++;
7296
- this.totalAdRequestsInBreak++;
7297
7403
  return [
7298
7404
  4,
7299
7405
  this.adPlayer.play()
7300
7406
  ];
7301
7407
  case 8:
7302
7408
  _state.sent();
7409
+ this.markAdStarted();
7303
7410
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7304
7411
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7305
7412
  }
@@ -7360,6 +7467,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7360
7467
  ,
7361
7468
  16
7362
7469
  ]);
7470
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7471
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7472
+ throw error;
7473
+ }
7363
7474
  this.clearAdRequestWatchdog();
7364
7475
  this.clearAdFailsafeTimer();
7365
7476
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7371,14 +7482,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7371
7482
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7372
7483
  this.adPlayer.resume();
7373
7484
  this.consecutiveFailures = 0;
7374
- this.currentAdIndex++;
7375
- this.totalAdRequestsInBreak++;
7376
7485
  return [
7377
7486
  4,
7378
7487
  this.adPlayer.play()
7379
7488
  ];
7380
7489
  case 14:
7381
7490
  _state.sent();
7491
+ this.markAdStarted();
7382
7492
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7383
7493
  return [
7384
7494
  2
@@ -7439,6 +7549,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7439
7549
  this.adRequestQueue = [];
7440
7550
  this.inAdBreak = false;
7441
7551
  this.adDetectSentForCurrentBreak = false;
7552
+ this.adBreakPlayedDurationMs = 0;
7442
7553
  this.activeScte35BreakKey = void 0;
7443
7554
  this.scheduledScte35BreakKey = void 0;
7444
7555
  this.expectedAdBreakDurationMs = void 0;
@@ -7628,13 +7739,117 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7628
7739
  }
7629
7740
  },
7630
7741
  {
7631
- key: "getRemainingAdMs",
7632
- value: function getRemainingAdMs() {
7633
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7742
+ key: "getWallClockRemainingAdMs",
7743
+ value: function getWallClockRemainingAdMs() {
7744
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {
7745
+ return 0;
7746
+ }
7634
7747
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7635
7748
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7636
7749
  }
7637
7750
  },
7751
+ {
7752
+ key: "getDurationBudgetRemainingMs",
7753
+ value: function getDurationBudgetRemainingMs() {
7754
+ if (this.expectedAdBreakDurationMs == null) {
7755
+ return 0;
7756
+ }
7757
+ return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);
7758
+ }
7759
+ },
7760
+ {
7761
+ key: "getLoadedAdDurationMs",
7762
+ value: function getLoadedAdDurationMs() {
7763
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7764
+ var _adController_getLoadedAdDuration;
7765
+ var durationSec = (_adController_getLoadedAdDuration = adController.getLoadedAdDuration) === null || _adController_getLoadedAdDuration === void 0 ? void 0 : _adController_getLoadedAdDuration.call(adController);
7766
+ if (durationSec == null || durationSec <= 0) {
7767
+ return 0;
7768
+ }
7769
+ return Math.round(durationSec * 1e3);
7770
+ }
7771
+ },
7772
+ {
7773
+ key: "adFitsRemainingBudget",
7774
+ value: function adFitsRemainingBudget(durationMs) {
7775
+ var adController = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.adPlayer;
7776
+ if (durationMs <= 0) {
7777
+ return true;
7778
+ }
7779
+ return durationMs <= this.getDurationBudgetRemainingMs();
7780
+ }
7781
+ },
7782
+ {
7783
+ key: "logAdDurationDecision",
7784
+ value: function logAdDurationDecision(accepted, durationMs) {
7785
+ var adController = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : this.adPlayer;
7786
+ var _ref;
7787
+ var _adController_getLoadedAdId;
7788
+ if (!this.config.debugAdTiming) {
7789
+ return;
7790
+ }
7791
+ var remainingBudget = this.getDurationBudgetRemainingMs();
7792
+ var adId = (_ref = (_adController_getLoadedAdId = adController.getLoadedAdId) === null || _adController_getLoadedAdId === void 0 ? void 0 : _adController_getLoadedAdId.call(adController)) !== null && _ref !== void 0 ? _ref : "unknown";
7793
+ if (accepted) {
7794
+ console.log("[AD-DURATION] Accepted ad id=".concat(adId, ", duration=").concat(durationMs, "ms, budget remaining after play=").concat(Math.max(0, remainingBudget - durationMs), "ms"));
7795
+ } else {
7796
+ console.log("[AD-DURATION] Rejected ad id=".concat(adId, ", duration=").concat(durationMs, "ms exceeds remaining budget=").concat(remainingBudget, "ms — requesting another ad"));
7797
+ }
7798
+ }
7799
+ },
7800
+ {
7801
+ key: "recordPlayedAdDuration",
7802
+ value: function recordPlayedAdDuration() {
7803
+ var durationMs = this.getLoadedAdDurationMs();
7804
+ if (durationMs <= 0) {
7805
+ return;
7806
+ }
7807
+ this.adBreakPlayedDurationMs += durationMs;
7808
+ if (this.config.debugAdTiming) {
7809
+ var _this_expectedAdBreakDurationMs;
7810
+ 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"));
7811
+ }
7812
+ }
7813
+ },
7814
+ {
7815
+ key: "ensureLoadedAdFitsBudget",
7816
+ value: function ensureLoadedAdFitsBudget() {
7817
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7818
+ var durationMs = this.getLoadedAdDurationMs(adController);
7819
+ var fits = this.adFitsRemainingBudget(durationMs, adController);
7820
+ this.logAdDurationDecision(fits, durationMs, adController);
7821
+ return fits;
7822
+ }
7823
+ },
7824
+ {
7825
+ key: "rejectLoadedAdForDuration",
7826
+ value: function rejectLoadedAdForDuration() {
7827
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7828
+ try {
7829
+ adController.destroy();
7830
+ } catch (unused) {}
7831
+ }
7832
+ },
7833
+ {
7834
+ key: "markAdStarted",
7835
+ value: function markAdStarted() {
7836
+ this.currentAdIndex++;
7837
+ this.totalAdsInBreak = this.currentAdIndex;
7838
+ this.totalAdRequestsInBreak++;
7839
+ this.lastAdRequestTime = Date.now();
7840
+ }
7841
+ },
7842
+ {
7843
+ key: "getRemainingAdMs",
7844
+ value: function getRemainingAdMs() {
7845
+ var wallClockRemaining = this.getWallClockRemainingAdMs();
7846
+ var budgetRemaining = this.getDurationBudgetRemainingMs();
7847
+ if (this.expectedAdBreakDurationMs == null) {
7848
+ return wallClockRemaining;
7849
+ }
7850
+ return Math.min(wallClockRemaining, budgetRemaining);
7851
+ }
7852
+ },
7638
7853
  {
7639
7854
  key: "getMinRemainingMsToStartAd",
7640
7855
  value: function getMinRemainingMsToStartAd() {
@@ -7648,7 +7863,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7648
7863
  {
7649
7864
  key: "hasTimeToStartAnotherAd",
7650
7865
  value: function hasTimeToStartAnotherAd() {
7651
- return this.getRemainingAdMs() > this.getMinRemainingMsToStartAd();
7866
+ var minRemaining = this.getMinRemainingMsToStartAd();
7867
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7652
7868
  }
7653
7869
  },
7654
7870
  {