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.
@@ -1240,6 +1240,12 @@ function createHlsAdPlayer(contentVideo, options) {
1240
1240
  contentVideo.style.visibility = "visible";
1241
1241
  contentVideo.style.opacity = "1";
1242
1242
  }
1243
+ },
1244
+ getLoadedAdDuration: function getLoadedAdDuration() {
1245
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration;
1246
+ },
1247
+ getLoadedAdId: function getLoadedAdId() {
1248
+ return currentAd === null || currentAd === void 0 ? void 0 : currentAd.id;
1243
1249
  }
1244
1250
  };
1245
1251
  }
@@ -2652,6 +2658,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2652
2658
  this.preloadPoolLoopRunning = false;
2653
2659
  this.adDetectSentForCurrentBreak = false;
2654
2660
  this.adBreakEpoch = 0;
2661
+ this.adBreakPlayedDurationMs = 0;
2655
2662
  this.palPlaybackStarted = false;
2656
2663
  this.streamCorrelator = generateCorrelator();
2657
2664
  this.consentSignals = {};
@@ -3148,6 +3155,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3148
3155
  adIndex: _this.currentAdIndex,
3149
3156
  timestamp: /* @__PURE__ */ new Date().toISOString()
3150
3157
  }).catch(function() {});
3158
+ _this.recordPlayedAdDuration();
3151
3159
  var remaining = _this.getRemainingAdMs();
3152
3160
  _this.consecutiveFailures = 0;
3153
3161
  if (_this.config.debugAdTiming) {
@@ -3223,6 +3231,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3223
3231
  return;
3224
3232
  }
3225
3233
  _this.consecutiveFailures = 0;
3234
+ _this.recordPlayedAdDuration();
3226
3235
  var remaining = _this.getRemainingAdMs();
3227
3236
  if (_this.config.debugAdTiming) {
3228
3237
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
@@ -5228,7 +5237,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5228
5237
  epoch = this.adBreakEpoch;
5229
5238
  loadPromise = function() {
5230
5239
  return _async_to_generator(function() {
5231
- var _this, hasAdError, adErrorPayload, errorListenerCleanup, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, lateErrorListener, error;
5240
+ var _this, hasAdError, adErrorPayload, errorListenerCleanup, _preloadAd_getLoadedAdDuration, continueLiveStreamDuringAds, preloadAd, errorListener, isNoFill, poolEntry, loadedDuration, lateErrorListener, _poolEntry_durationSeconds, error;
5232
5241
  return _ts_generator(this, function(_state) {
5233
5242
  switch(_state.label){
5234
5243
  case 0:
@@ -5324,6 +5333,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5324
5333
  isReady: true,
5325
5334
  loadPromise: Promise.resolve()
5326
5335
  };
5336
+ loadedDuration = (_preloadAd_getLoadedAdDuration = preloadAd.getLoadedAdDuration) === null || _preloadAd_getLoadedAdDuration === void 0 ? void 0 : _preloadAd_getLoadedAdDuration.call(preloadAd);
5337
+ if (loadedDuration != null && loadedDuration > 0) {
5338
+ poolEntry.durationSeconds = loadedDuration;
5339
+ }
5327
5340
  lateErrorListener = function lateErrorListener(payload) {
5328
5341
  var index = _this.preloadPool.findIndex(function(entry) {
5329
5342
  return entry.vastUrl === vastUrl;
@@ -5341,7 +5354,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5341
5354
  preloadAd.on("ad_error", lateErrorListener);
5342
5355
  this.preloadPool.push(poolEntry);
5343
5356
  if (this.config.debugAdTiming) {
5344
- console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ")"));
5357
+ ;
5358
+ console.log("[PRELOAD-POOL] ✅ Ad preloaded (no errors detected within timeout) (pool size: ".concat(this.preloadPool.length, "/").concat(this.maxPreloadPoolSize, ", duration=").concat((_poolEntry_durationSeconds = poolEntry.durationSeconds) !== null && _poolEntry_durationSeconds !== void 0 ? _poolEntry_durationSeconds : "?", "s)"));
5345
5359
  }
5346
5360
  if (errorListenerCleanup) {
5347
5361
  errorListenerCleanup();
@@ -5622,8 +5636,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5622
5636
  }
5623
5637
  this.inAdBreak = true;
5624
5638
  this.currentAdBreakStartWallClockMs = Date.now();
5639
+ this.adBreakPlayedDurationMs = 0;
5625
5640
  this.currentAdIndex = 0;
5626
- this.totalAdsInBreak = 1;
5641
+ this.totalAdsInBreak = 0;
5627
5642
  this.adPodQueue = [];
5628
5643
  this.showAds = true;
5629
5644
  this.showPlaceholderLayer();
@@ -5677,14 +5692,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5677
5692
  case 1:
5678
5693
  _state.trys.push([
5679
5694
  1,
5680
- 8,
5695
+ 12,
5681
5696
  ,
5682
- 14
5697
+ 20
5683
5698
  ]);
5684
5699
  if (!(usePreloadedAd && preloadedController)) return [
5700
+ 3,
5701
+ 5
5702
+ ];
5703
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5685
5704
  3,
5686
5705
  3
5687
5706
  ];
5707
+ this.rejectLoadedAdForDuration(preloadedController);
5708
+ this.startContinuousFetching(baseVastUrl);
5709
+ return [
5710
+ 4,
5711
+ this.tryNextAvailableAdWithRateLimit()
5712
+ ];
5713
+ case 2:
5714
+ _state.sent();
5715
+ return [
5716
+ 2
5717
+ ];
5718
+ case 3:
5688
5719
  preservedMuted = this.adPlayer.getOriginalMutedState();
5689
5720
  preservedVolume = this.adPlayer.getOriginalVolume();
5690
5721
  this.adPlayer.destroy();
@@ -5697,7 +5728,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5697
5728
  }
5698
5729
  this.adPlayer.resume();
5699
5730
  this.consecutiveFailures = 0;
5700
- this.currentAdIndex++;
5701
5731
  this.startContinuousFetching(baseVastUrl);
5702
5732
  if (!this.preloadPoolActive) {
5703
5733
  this.preloadPoolActive = true;
@@ -5707,8 +5737,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5707
5737
  4,
5708
5738
  this.adPlayer.play()
5709
5739
  ];
5710
- case 2:
5740
+ case 4:
5711
5741
  _state.sent();
5742
+ this.markAdStarted();
5712
5743
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5713
5744
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5714
5745
  }
@@ -5716,21 +5747,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5716
5747
  this.adPlayer.setAdVolume(adVolume);
5717
5748
  return [
5718
5749
  3,
5719
- 7
5750
+ 11
5720
5751
  ];
5721
- case 3:
5752
+ case 5:
5722
5753
  return [
5723
5754
  4,
5724
5755
  this.enforceGlobalRateLimit()
5725
5756
  ];
5726
- case 4:
5757
+ case 6:
5727
5758
  _state.sent();
5728
5759
  this.lastAdRequestTime = Date.now();
5729
5760
  return [
5730
5761
  4,
5731
5762
  this.adPlayer.requestAds(firstAdUrl)
5732
5763
  ];
5733
- case 5:
5764
+ case 7:
5734
5765
  _state.sent();
5735
5766
  sendAdLoadedTracking(this.config.licenseKey, {
5736
5767
  source: "hls",
@@ -5740,8 +5771,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5740
5771
  if (this.config.debugAdTiming) {
5741
5772
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
5742
5773
  }
5774
+ if (!!this.ensureLoadedAdFitsBudget()) return [
5775
+ 3,
5776
+ 9
5777
+ ];
5778
+ this.rejectLoadedAdForDuration();
5779
+ this.recreateAdController();
5780
+ this.startContinuousFetching(baseVastUrl);
5781
+ return [
5782
+ 4,
5783
+ this.tryNextAvailableAdWithRateLimit()
5784
+ ];
5785
+ case 8:
5786
+ _state.sent();
5787
+ return [
5788
+ 2
5789
+ ];
5790
+ case 9:
5743
5791
  this.consecutiveFailures = 0;
5744
- this.currentAdIndex++;
5745
5792
  this.startContinuousFetching(baseVastUrl);
5746
5793
  if (!this.preloadPoolActive) {
5747
5794
  this.startPreloadPool(baseVastUrl, []);
@@ -5750,44 +5797,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5750
5797
  4,
5751
5798
  this.adPlayer.play()
5752
5799
  ];
5753
- case 6:
5800
+ case 10:
5754
5801
  _state.sent();
5802
+ this.markAdStarted();
5755
5803
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5756
5804
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5757
5805
  }
5758
5806
  adVolume1 = currentMuted ? 0 : currentVolume;
5759
5807
  this.adPlayer.setAdVolume(adVolume1);
5760
- _state.label = 7;
5761
- case 7:
5808
+ _state.label = 11;
5809
+ case 11:
5762
5810
  return [
5763
5811
  3,
5764
- 14
5812
+ 20
5765
5813
  ];
5766
- case 8:
5814
+ case 12:
5767
5815
  error = _state.sent();
5768
5816
  if (this.config.debugAdTiming) {
5769
5817
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
5770
5818
  }
5771
5819
  if (!!usePreloadedAd) return [
5772
5820
  3,
5773
- 12
5821
+ 18
5774
5822
  ];
5775
5823
  fallbackPreloaded = this.getPreloadedAd();
5776
5824
  if (!fallbackPreloaded) return [
5777
5825
  3,
5778
- 12
5826
+ 18
5779
5827
  ];
5780
5828
  if (this.config.debugAdTiming) {
5781
5829
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
5782
5830
  }
5783
- _state.label = 9;
5784
- case 9:
5831
+ _state.label = 13;
5832
+ case 13:
5785
5833
  _state.trys.push([
5786
- 9,
5787
- 11,
5834
+ 13,
5835
+ 17,
5788
5836
  ,
5789
- 12
5837
+ 18
5790
5838
  ]);
5839
+ if (!!this.ensureLoadedAdFitsBudget(fallbackPreloaded.adController)) return [
5840
+ 3,
5841
+ 15
5842
+ ];
5843
+ this.rejectLoadedAdForDuration(fallbackPreloaded.adController);
5844
+ this.startContinuousFetching(baseVastUrl);
5845
+ return [
5846
+ 4,
5847
+ this.tryNextAvailableAdWithRateLimit()
5848
+ ];
5849
+ case 14:
5850
+ _state.sent();
5851
+ return [
5852
+ 2
5853
+ ];
5854
+ case 15:
5791
5855
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
5792
5856
  preservedVolume1 = this.adPlayer.getOriginalVolume();
5793
5857
  this.adPlayer.destroy();
@@ -5797,7 +5861,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5797
5861
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
5798
5862
  this.adPlayer.resume();
5799
5863
  this.consecutiveFailures = 0;
5800
- this.currentAdIndex++;
5801
5864
  this.startContinuousFetching(baseVastUrl);
5802
5865
  if (!this.preloadPoolActive) {
5803
5866
  this.preloadPoolActive = true;
@@ -5807,8 +5870,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5807
5870
  4,
5808
5871
  this.adPlayer.play()
5809
5872
  ];
5810
- case 10:
5873
+ case 16:
5811
5874
  _state.sent();
5875
+ this.markAdStarted();
5812
5876
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5813
5877
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5814
5878
  }
@@ -5817,16 +5881,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5817
5881
  return [
5818
5882
  2
5819
5883
  ];
5820
- case 11:
5884
+ case 17:
5821
5885
  fallbackError = _state.sent();
5822
5886
  if (this.config.debugAdTiming) {
5823
5887
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
5824
5888
  }
5825
5889
  return [
5826
5890
  3,
5827
- 12
5891
+ 18
5828
5892
  ];
5829
- case 12:
5893
+ case 18:
5830
5894
  if (this.isTemporaryAdError(error)) {
5831
5895
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
5832
5896
  if (this.config.debugAdTiming) {
@@ -5844,13 +5908,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5844
5908
  4,
5845
5909
  this.tryNextAvailableAdWithRateLimit()
5846
5910
  ];
5847
- case 13:
5911
+ case 19:
5848
5912
  _state.sent();
5849
5913
  return [
5850
5914
  3,
5851
- 14
5915
+ 20
5852
5916
  ];
5853
- case 14:
5917
+ case 20:
5854
5918
  return [
5855
5919
  2
5856
5920
  ];
@@ -5891,10 +5955,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5891
5955
  return _ts_generator(this, function(_state) {
5892
5956
  switch(_state.label){
5893
5957
  case 0:
5894
- remaining = _this.getRemainingAdMs();
5958
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
5895
5959
  if (remaining <= _this.getMinRemainingMsToStartAd()) {
5896
5960
  if (_this.config.debugAdTiming) {
5897
- console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (".concat(remaining, "ms), stopping URL generation"));
5961
+ console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (wall=".concat(_this.getWallClockRemainingAdMs(), "ms, budget=").concat(_this.getDurationBudgetRemainingMs(), "ms), stopping URL generation"));
5898
5962
  }
5899
5963
  return [
5900
5964
  2,
@@ -5976,7 +6040,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5976
6040
  });
5977
6041
  }
5978
6042
  _this.adRequestQueue.push(newAdUrl);
5979
- _this.totalAdsInBreak++;
5980
6043
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
5981
6044
  return [
5982
6045
  4,
@@ -6145,7 +6208,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6145
6208
  preloaded = this.getPreloadedAd();
6146
6209
  if (!preloaded) return [
6147
6210
  3,
6148
- 4
6211
+ 6
6149
6212
  ];
6150
6213
  if (this.config.debugAdTiming) {
6151
6214
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6159,10 +6222,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6159
6222
  case 1:
6160
6223
  _state.trys.push([
6161
6224
  1,
6162
- 3,
6225
+ 5,
6163
6226
  ,
6164
- 4
6227
+ 6
6165
6228
  ]);
6229
+ if (!!this.ensureLoadedAdFitsBudget(preloaded.adController)) return [
6230
+ 3,
6231
+ 3
6232
+ ];
6233
+ this.rejectLoadedAdForDuration(preloaded.adController);
6234
+ return [
6235
+ 4,
6236
+ this.tryNextAvailableAdWithRateLimit()
6237
+ ];
6238
+ case 2:
6239
+ _state.sent();
6240
+ return [
6241
+ 2
6242
+ ];
6243
+ case 3:
6166
6244
  preservedMuted = this.adPlayer.getOriginalMutedState();
6167
6245
  preservedVolume = this.adPlayer.getOriginalVolume();
6168
6246
  this.adPlayer.destroy();
@@ -6171,15 +6249,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6171
6249
  this.attachImaEventListeners();
6172
6250
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6173
6251
  this.adPlayer.resume();
6174
- this.currentAdIndex++;
6175
- this.totalAdRequestsInBreak++;
6176
- this.lastAdRequestTime = Date.now();
6177
6252
  return [
6178
6253
  4,
6179
6254
  this.adPlayer.play()
6180
6255
  ];
6181
- case 2:
6256
+ case 4:
6182
6257
  _state.sent();
6258
+ this.markAdStarted();
6183
6259
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6184
6260
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6185
6261
  }
@@ -6188,25 +6264,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6188
6264
  return [
6189
6265
  2
6190
6266
  ];
6191
- case 3:
6267
+ case 5:
6192
6268
  error = _state.sent();
6193
6269
  if (this.config.debugAdTiming) {
6194
6270
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6195
6271
  }
6196
6272
  return [
6197
6273
  3,
6198
- 4
6274
+ 6
6199
6275
  ];
6200
- case 4:
6276
+ case 6:
6201
6277
  if (!(this.adRequestQueue.length > 0)) return [
6202
6278
  3,
6203
- 10
6279
+ 14
6204
6280
  ];
6205
6281
  nextAdUrl = this.adRequestQueue.shift();
6206
6282
  if (!nextAdUrl) {
6207
6283
  return [
6208
6284
  3,
6209
- 4
6285
+ 6
6210
6286
  ];
6211
6287
  }
6212
6288
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6215,7 +6291,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6215
6291
  }
6216
6292
  return [
6217
6293
  3,
6218
- 4
6294
+ 6
6219
6295
  ];
6220
6296
  }
6221
6297
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6224,40 +6300,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6224
6300
  }
6225
6301
  return [
6226
6302
  3,
6227
- 4
6303
+ 6
6228
6304
  ];
6229
6305
  }
6230
6306
  if (this.config.debugAdTiming) {
6231
- console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, "/").concat(this.totalAdsInBreak, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6307
+ console.log("[CONTINUOUS-FETCH] \uD83C\uDFAC Requesting next ad via HLS ad player (".concat(this.currentAdIndex + 1, ", ").concat(this.adRequestQueue.length, " remaining in queue)"));
6232
6308
  }
6233
- this.currentAdIndex++;
6234
- this.totalAdRequestsInBreak++;
6235
- this.lastAdRequestTime = Date.now();
6236
- _state.label = 5;
6237
- case 5:
6309
+ _state.label = 7;
6310
+ case 7:
6238
6311
  _state.trys.push([
6239
- 5,
6240
6312
  7,
6313
+ 9,
6241
6314
  ,
6242
- 9
6315
+ 13
6243
6316
  ]);
6244
6317
  return [
6245
6318
  4,
6246
6319
  this.playSingleAd(nextAdUrl)
6247
6320
  ];
6248
- case 6:
6321
+ case 8:
6249
6322
  _state.sent();
6250
6323
  this.consecutiveFailures = 0;
6251
6324
  return [
6252
6325
  3,
6253
- 9
6326
+ 13
6254
6327
  ];
6255
- case 7:
6328
+ case 9:
6256
6329
  error1 = _state.sent();
6257
6330
  errorMessage = error1.message;
6258
6331
  if (this.config.debugAdTiming) {
6259
6332
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6260
6333
  }
6334
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6335
+ 3,
6336
+ 11
6337
+ ];
6338
+ return [
6339
+ 4,
6340
+ this.tryNextAvailableAdWithRateLimit()
6341
+ ];
6342
+ case 10:
6343
+ _state.sent();
6344
+ return [
6345
+ 2
6346
+ ];
6347
+ case 11:
6261
6348
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6262
6349
  this.consecutiveFailures++;
6263
6350
  }
@@ -6265,21 +6352,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6265
6352
  4,
6266
6353
  this.tryNextAvailableAdWithRateLimit()
6267
6354
  ];
6268
- case 8:
6355
+ case 12:
6269
6356
  _state.sent();
6270
6357
  return [
6271
6358
  3,
6272
- 9
6359
+ 13
6273
6360
  ];
6274
- case 9:
6361
+ case 13:
6275
6362
  return [
6276
6363
  2
6277
6364
  ];
6278
- case 10:
6365
+ case 14:
6279
6366
  maxRetries = 3;
6280
6367
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6281
6368
  3,
6282
- 13
6369
+ 17
6283
6370
  ];
6284
6371
  if (this.config.debugAdTiming) {
6285
6372
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6290,18 +6377,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6290
6377
  return setTimeout(resolve, 500);
6291
6378
  })
6292
6379
  ];
6293
- case 11:
6380
+ case 15:
6294
6381
  _state.sent();
6295
6382
  return [
6296
6383
  4,
6297
6384
  this.tryNextAvailableAd(retryCount + 1)
6298
6385
  ];
6299
- case 12:
6386
+ case 16:
6300
6387
  _state.sent();
6301
6388
  return [
6302
6389
  2
6303
6390
  ];
6304
- case 13:
6391
+ case 17:
6305
6392
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6306
6393
  this.showPlaceholderAndWaitForAds();
6307
6394
  } else {
@@ -6364,7 +6451,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6364
6451
  case 1:
6365
6452
  if (!(i < maxChecks)) return [
6366
6453
  3,
6367
- 10
6454
+ 12
6368
6455
  ];
6369
6456
  return [
6370
6457
  4,
@@ -6385,7 +6472,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6385
6472
  }
6386
6473
  return [
6387
6474
  3,
6388
- 10
6475
+ 12
6389
6476
  ];
6390
6477
  }
6391
6478
  if (!this.hasTimeToStartAnotherAd()) {
@@ -6394,19 +6481,19 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6394
6481
  }
6395
6482
  return [
6396
6483
  3,
6397
- 10
6484
+ 12
6398
6485
  ];
6399
6486
  }
6400
6487
  _state.label = 3;
6401
6488
  case 3:
6402
6489
  if (!(this.adRequestQueue.length > 0)) return [
6403
6490
  3,
6404
- 9
6491
+ 11
6405
6492
  ];
6406
6493
  if (!this.hasTimeToStartAnotherAd()) {
6407
6494
  return [
6408
6495
  3,
6409
- 9
6496
+ 11
6410
6497
  ];
6411
6498
  }
6412
6499
  nextAdUrl = this.adRequestQueue.shift();
@@ -6433,16 +6520,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6433
6520
  }
6434
6521
  this.isShowingPlaceholder = false;
6435
6522
  this.adPlayer.hidePlaceholder();
6436
- this.currentAdIndex++;
6437
- this.totalAdRequestsInBreak++;
6438
- this.lastAdRequestTime = Date.now();
6439
6523
  _state.label = 4;
6440
6524
  case 4:
6441
6525
  _state.trys.push([
6442
6526
  4,
6443
6527
  6,
6444
6528
  ,
6445
- 8
6529
+ 10
6446
6530
  ]);
6447
6531
  return [
6448
6532
  4,
@@ -6453,11 +6537,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6453
6537
  this.consecutiveFailures = 0;
6454
6538
  return [
6455
6539
  3,
6456
- 8
6540
+ 10
6457
6541
  ];
6458
6542
  case 6:
6459
6543
  error = _state.sent();
6460
6544
  errorMessage = error.message;
6545
+ if (!errorMessage.includes("ad_duration_exceeds_budget")) return [
6546
+ 3,
6547
+ 8
6548
+ ];
6549
+ return [
6550
+ 4,
6551
+ this.tryNextAvailableAdWithRateLimit()
6552
+ ];
6553
+ case 7:
6554
+ _state.sent();
6555
+ return [
6556
+ 2
6557
+ ];
6558
+ case 8:
6461
6559
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6462
6560
  this.consecutiveFailures++;
6463
6561
  }
@@ -6465,23 +6563,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6465
6563
  4,
6466
6564
  this.tryNextAvailableAdWithRateLimit()
6467
6565
  ];
6468
- case 7:
6566
+ case 9:
6469
6567
  _state.sent();
6470
6568
  return [
6471
6569
  3,
6472
- 8
6570
+ 10
6473
6571
  ];
6474
- case 8:
6572
+ case 10:
6475
6573
  return [
6476
6574
  2
6477
6575
  ];
6478
- case 9:
6576
+ case 11:
6479
6577
  i++;
6480
6578
  return [
6481
6579
  3,
6482
6580
  1
6483
6581
  ];
6484
- case 10:
6582
+ case 12:
6485
6583
  if (this.config.debugAdTiming) {
6486
6584
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6487
6585
  }
@@ -6953,6 +7051,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6953
7051
  2
6954
7052
  ];
6955
7053
  }
7054
+ if (!this.ensureLoadedAdFitsBudget()) {
7055
+ this.rejectLoadedAdForDuration();
7056
+ this.recreateAdController();
7057
+ throw new Error("ad_duration_exceeds_budget");
7058
+ }
6956
7059
  _state.label = 4;
6957
7060
  case 4:
6958
7061
  _state.trys.push([
@@ -6968,6 +7071,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6968
7071
  ];
6969
7072
  case 5:
6970
7073
  _state.sent();
7074
+ this.markAdStarted();
6971
7075
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6972
7076
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6973
7077
  }
@@ -6999,6 +7103,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6999
7103
  ,
7000
7104
  10
7001
7105
  ]);
7106
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7107
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7108
+ throw playError;
7109
+ }
7002
7110
  this.clearAdFailsafeTimer();
7003
7111
  preservedMuted = this.adPlayer.getOriginalMutedState();
7004
7112
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -7009,14 +7117,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7009
7117
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
7010
7118
  this.adPlayer.resume();
7011
7119
  this.consecutiveFailures = 0;
7012
- this.currentAdIndex++;
7013
- this.totalAdRequestsInBreak++;
7014
7120
  return [
7015
7121
  4,
7016
7122
  this.adPlayer.play()
7017
7123
  ];
7018
7124
  case 8:
7019
7125
  _state.sent();
7126
+ this.markAdStarted();
7020
7127
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7021
7128
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7022
7129
  }
@@ -7077,6 +7184,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7077
7184
  ,
7078
7185
  16
7079
7186
  ]);
7187
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7188
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7189
+ throw error;
7190
+ }
7080
7191
  this.clearAdRequestWatchdog();
7081
7192
  this.clearAdFailsafeTimer();
7082
7193
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7088,14 +7199,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7088
7199
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7089
7200
  this.adPlayer.resume();
7090
7201
  this.consecutiveFailures = 0;
7091
- this.currentAdIndex++;
7092
- this.totalAdRequestsInBreak++;
7093
7202
  return [
7094
7203
  4,
7095
7204
  this.adPlayer.play()
7096
7205
  ];
7097
7206
  case 14:
7098
7207
  _state.sent();
7208
+ this.markAdStarted();
7099
7209
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7100
7210
  return [
7101
7211
  2
@@ -7156,6 +7266,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7156
7266
  this.adRequestQueue = [];
7157
7267
  this.inAdBreak = false;
7158
7268
  this.adDetectSentForCurrentBreak = false;
7269
+ this.adBreakPlayedDurationMs = 0;
7159
7270
  this.activeScte35BreakKey = void 0;
7160
7271
  this.scheduledScte35BreakKey = void 0;
7161
7272
  this.expectedAdBreakDurationMs = void 0;
@@ -7345,13 +7456,117 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7345
7456
  }
7346
7457
  },
7347
7458
  {
7348
- key: "getRemainingAdMs",
7349
- value: function getRemainingAdMs() {
7350
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7459
+ key: "getWallClockRemainingAdMs",
7460
+ value: function getWallClockRemainingAdMs() {
7461
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {
7462
+ return 0;
7463
+ }
7351
7464
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7352
7465
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7353
7466
  }
7354
7467
  },
7468
+ {
7469
+ key: "getDurationBudgetRemainingMs",
7470
+ value: function getDurationBudgetRemainingMs() {
7471
+ if (this.expectedAdBreakDurationMs == null) {
7472
+ return 0;
7473
+ }
7474
+ return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);
7475
+ }
7476
+ },
7477
+ {
7478
+ key: "getLoadedAdDurationMs",
7479
+ value: function getLoadedAdDurationMs() {
7480
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7481
+ var _adController_getLoadedAdDuration;
7482
+ var durationSec = (_adController_getLoadedAdDuration = adController.getLoadedAdDuration) === null || _adController_getLoadedAdDuration === void 0 ? void 0 : _adController_getLoadedAdDuration.call(adController);
7483
+ if (durationSec == null || durationSec <= 0) {
7484
+ return 0;
7485
+ }
7486
+ return Math.round(durationSec * 1e3);
7487
+ }
7488
+ },
7489
+ {
7490
+ key: "adFitsRemainingBudget",
7491
+ value: function adFitsRemainingBudget(durationMs) {
7492
+ var adController = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.adPlayer;
7493
+ if (durationMs <= 0) {
7494
+ return true;
7495
+ }
7496
+ return durationMs <= this.getDurationBudgetRemainingMs();
7497
+ }
7498
+ },
7499
+ {
7500
+ key: "logAdDurationDecision",
7501
+ value: function logAdDurationDecision(accepted, durationMs) {
7502
+ var adController = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : this.adPlayer;
7503
+ var _ref;
7504
+ var _adController_getLoadedAdId;
7505
+ if (!this.config.debugAdTiming) {
7506
+ return;
7507
+ }
7508
+ var remainingBudget = this.getDurationBudgetRemainingMs();
7509
+ var adId = (_ref = (_adController_getLoadedAdId = adController.getLoadedAdId) === null || _adController_getLoadedAdId === void 0 ? void 0 : _adController_getLoadedAdId.call(adController)) !== null && _ref !== void 0 ? _ref : "unknown";
7510
+ if (accepted) {
7511
+ console.log("[AD-DURATION] Accepted ad id=".concat(adId, ", duration=").concat(durationMs, "ms, budget remaining after play=").concat(Math.max(0, remainingBudget - durationMs), "ms"));
7512
+ } else {
7513
+ console.log("[AD-DURATION] Rejected ad id=".concat(adId, ", duration=").concat(durationMs, "ms exceeds remaining budget=").concat(remainingBudget, "ms — requesting another ad"));
7514
+ }
7515
+ }
7516
+ },
7517
+ {
7518
+ key: "recordPlayedAdDuration",
7519
+ value: function recordPlayedAdDuration() {
7520
+ var durationMs = this.getLoadedAdDurationMs();
7521
+ if (durationMs <= 0) {
7522
+ return;
7523
+ }
7524
+ this.adBreakPlayedDurationMs += durationMs;
7525
+ if (this.config.debugAdTiming) {
7526
+ var _this_expectedAdBreakDurationMs;
7527
+ console.log("[AD-DURATION] Played ".concat(durationMs, "ms — total played=").concat(this.adBreakPlayedDurationMs, "ms / ").concat((_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : "?", "ms"));
7528
+ }
7529
+ }
7530
+ },
7531
+ {
7532
+ key: "ensureLoadedAdFitsBudget",
7533
+ value: function ensureLoadedAdFitsBudget() {
7534
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7535
+ var durationMs = this.getLoadedAdDurationMs(adController);
7536
+ var fits = this.adFitsRemainingBudget(durationMs, adController);
7537
+ this.logAdDurationDecision(fits, durationMs, adController);
7538
+ return fits;
7539
+ }
7540
+ },
7541
+ {
7542
+ key: "rejectLoadedAdForDuration",
7543
+ value: function rejectLoadedAdForDuration() {
7544
+ var adController = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.adPlayer;
7545
+ try {
7546
+ adController.destroy();
7547
+ } catch (unused) {}
7548
+ }
7549
+ },
7550
+ {
7551
+ key: "markAdStarted",
7552
+ value: function markAdStarted() {
7553
+ this.currentAdIndex++;
7554
+ this.totalAdsInBreak = this.currentAdIndex;
7555
+ this.totalAdRequestsInBreak++;
7556
+ this.lastAdRequestTime = Date.now();
7557
+ }
7558
+ },
7559
+ {
7560
+ key: "getRemainingAdMs",
7561
+ value: function getRemainingAdMs() {
7562
+ var wallClockRemaining = this.getWallClockRemainingAdMs();
7563
+ var budgetRemaining = this.getDurationBudgetRemainingMs();
7564
+ if (this.expectedAdBreakDurationMs == null) {
7565
+ return wallClockRemaining;
7566
+ }
7567
+ return Math.min(wallClockRemaining, budgetRemaining);
7568
+ }
7569
+ },
7355
7570
  {
7356
7571
  key: "getMinRemainingMsToStartAd",
7357
7572
  value: function getMinRemainingMsToStartAd() {
@@ -7365,7 +7580,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7365
7580
  {
7366
7581
  key: "hasTimeToStartAnotherAd",
7367
7582
  value: function hasTimeToStartAnotherAd() {
7368
- return this.getRemainingAdMs() > this.getMinRemainingMsToStartAd();
7583
+ var minRemaining = this.getMinRemainingMsToStartAd();
7584
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7369
7585
  }
7370
7586
  },
7371
7587
  {