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.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
  }
@@ -2724,6 +2730,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2724
2730
  this.preloadPoolLoopRunning = false;
2725
2731
  this.adDetectSentForCurrentBreak = false;
2726
2732
  this.adBreakEpoch = 0;
2733
+ this.adBreakPlayedDurationMs = 0;
2727
2734
  this.palPlaybackStarted = false;
2728
2735
  this.streamCorrelator = generateCorrelator();
2729
2736
  this.consentSignals = {};
@@ -3220,6 +3227,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3220
3227
  adIndex: _this.currentAdIndex,
3221
3228
  timestamp: /* @__PURE__ */ new Date().toISOString()
3222
3229
  }).catch(function() {});
3230
+ _this.recordPlayedAdDuration();
3223
3231
  var remaining = _this.getRemainingAdMs();
3224
3232
  _this.consecutiveFailures = 0;
3225
3233
  if (_this.config.debugAdTiming) {
@@ -3295,6 +3303,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3295
3303
  return;
3296
3304
  }
3297
3305
  _this.consecutiveFailures = 0;
3306
+ _this.recordPlayedAdDuration();
3298
3307
  var remaining = _this.getRemainingAdMs();
3299
3308
  if (_this.config.debugAdTiming) {
3300
3309
  console.log("[CONTINUOUS-FETCH] content_resume event: remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
@@ -5300,7 +5309,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5300
5309
  epoch = this.adBreakEpoch;
5301
5310
  loadPromise = function() {
5302
5311
  return _async_to_generator(function() {
5303
- 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;
5304
5313
  return _ts_generator(this, function(_state) {
5305
5314
  switch(_state.label){
5306
5315
  case 0:
@@ -5396,6 +5405,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5396
5405
  isReady: true,
5397
5406
  loadPromise: Promise.resolve()
5398
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
+ }
5399
5412
  lateErrorListener = function lateErrorListener(payload) {
5400
5413
  var index = _this.preloadPool.findIndex(function(entry) {
5401
5414
  return entry.vastUrl === vastUrl;
@@ -5413,7 +5426,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5413
5426
  preloadAd.on("ad_error", lateErrorListener);
5414
5427
  this.preloadPool.push(poolEntry);
5415
5428
  if (this.config.debugAdTiming) {
5416
- 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)"));
5417
5431
  }
5418
5432
  if (errorListenerCleanup) {
5419
5433
  errorListenerCleanup();
@@ -5694,8 +5708,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5694
5708
  }
5695
5709
  this.inAdBreak = true;
5696
5710
  this.currentAdBreakStartWallClockMs = Date.now();
5711
+ this.adBreakPlayedDurationMs = 0;
5697
5712
  this.currentAdIndex = 0;
5698
- this.totalAdsInBreak = 1;
5713
+ this.totalAdsInBreak = 0;
5699
5714
  this.adPodQueue = [];
5700
5715
  this.showAds = true;
5701
5716
  this.showPlaceholderLayer();
@@ -5749,14 +5764,30 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5749
5764
  case 1:
5750
5765
  _state.trys.push([
5751
5766
  1,
5752
- 8,
5767
+ 12,
5753
5768
  ,
5754
- 14
5769
+ 20
5755
5770
  ]);
5756
5771
  if (!(usePreloadedAd && preloadedController)) return [
5772
+ 3,
5773
+ 5
5774
+ ];
5775
+ if (!!this.ensureLoadedAdFitsBudget(preloadedController)) return [
5757
5776
  3,
5758
5777
  3
5759
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:
5760
5791
  preservedMuted = this.adPlayer.getOriginalMutedState();
5761
5792
  preservedVolume = this.adPlayer.getOriginalVolume();
5762
5793
  this.adPlayer.destroy();
@@ -5769,7 +5800,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5769
5800
  }
5770
5801
  this.adPlayer.resume();
5771
5802
  this.consecutiveFailures = 0;
5772
- this.currentAdIndex++;
5773
5803
  this.startContinuousFetching(baseVastUrl);
5774
5804
  if (!this.preloadPoolActive) {
5775
5805
  this.preloadPoolActive = true;
@@ -5779,8 +5809,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5779
5809
  4,
5780
5810
  this.adPlayer.play()
5781
5811
  ];
5782
- case 2:
5812
+ case 4:
5783
5813
  _state.sent();
5814
+ this.markAdStarted();
5784
5815
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5785
5816
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5786
5817
  }
@@ -5788,21 +5819,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5788
5819
  this.adPlayer.setAdVolume(adVolume);
5789
5820
  return [
5790
5821
  3,
5791
- 7
5822
+ 11
5792
5823
  ];
5793
- case 3:
5824
+ case 5:
5794
5825
  return [
5795
5826
  4,
5796
5827
  this.enforceGlobalRateLimit()
5797
5828
  ];
5798
- case 4:
5829
+ case 6:
5799
5830
  _state.sent();
5800
5831
  this.lastAdRequestTime = Date.now();
5801
5832
  return [
5802
5833
  4,
5803
5834
  this.adPlayer.requestAds(firstAdUrl)
5804
5835
  ];
5805
- case 5:
5836
+ case 7:
5806
5837
  _state.sent();
5807
5838
  sendAdLoadedTracking(this.config.licenseKey, {
5808
5839
  source: "hls",
@@ -5812,8 +5843,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5812
5843
  if (this.config.debugAdTiming) {
5813
5844
  console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
5814
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:
5815
5863
  this.consecutiveFailures = 0;
5816
- this.currentAdIndex++;
5817
5864
  this.startContinuousFetching(baseVastUrl);
5818
5865
  if (!this.preloadPoolActive) {
5819
5866
  this.startPreloadPool(baseVastUrl, []);
@@ -5822,44 +5869,61 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5822
5869
  4,
5823
5870
  this.adPlayer.play()
5824
5871
  ];
5825
- case 6:
5872
+ case 10:
5826
5873
  _state.sent();
5874
+ this.markAdStarted();
5827
5875
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5828
5876
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5829
5877
  }
5830
5878
  adVolume1 = currentMuted ? 0 : currentVolume;
5831
5879
  this.adPlayer.setAdVolume(adVolume1);
5832
- _state.label = 7;
5833
- case 7:
5880
+ _state.label = 11;
5881
+ case 11:
5834
5882
  return [
5835
5883
  3,
5836
- 14
5884
+ 20
5837
5885
  ];
5838
- case 8:
5886
+ case 12:
5839
5887
  error = _state.sent();
5840
5888
  if (this.config.debugAdTiming) {
5841
5889
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
5842
5890
  }
5843
5891
  if (!!usePreloadedAd) return [
5844
5892
  3,
5845
- 12
5893
+ 18
5846
5894
  ];
5847
5895
  fallbackPreloaded = this.getPreloadedAd();
5848
5896
  if (!fallbackPreloaded) return [
5849
5897
  3,
5850
- 12
5898
+ 18
5851
5899
  ];
5852
5900
  if (this.config.debugAdTiming) {
5853
5901
  console.log("[CONTINUOUS-FETCH] \uD83D\uDD04 First ad failed, using preloaded fallback");
5854
5902
  }
5855
- _state.label = 9;
5856
- case 9:
5903
+ _state.label = 13;
5904
+ case 13:
5857
5905
  _state.trys.push([
5858
- 9,
5859
- 11,
5906
+ 13,
5907
+ 17,
5860
5908
  ,
5861
- 12
5909
+ 18
5862
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:
5863
5927
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
5864
5928
  preservedVolume1 = this.adPlayer.getOriginalVolume();
5865
5929
  this.adPlayer.destroy();
@@ -5869,7 +5933,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5869
5933
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
5870
5934
  this.adPlayer.resume();
5871
5935
  this.consecutiveFailures = 0;
5872
- this.currentAdIndex++;
5873
5936
  this.startContinuousFetching(baseVastUrl);
5874
5937
  if (!this.preloadPoolActive) {
5875
5938
  this.preloadPoolActive = true;
@@ -5879,8 +5942,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5879
5942
  4,
5880
5943
  this.adPlayer.play()
5881
5944
  ];
5882
- case 10:
5945
+ case 16:
5883
5946
  _state.sent();
5947
+ this.markAdStarted();
5884
5948
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
5885
5949
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
5886
5950
  }
@@ -5889,16 +5953,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5889
5953
  return [
5890
5954
  2
5891
5955
  ];
5892
- case 11:
5956
+ case 17:
5893
5957
  fallbackError = _state.sent();
5894
5958
  if (this.config.debugAdTiming) {
5895
5959
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded fallback also failed:", fallbackError);
5896
5960
  }
5897
5961
  return [
5898
5962
  3,
5899
- 12
5963
+ 18
5900
5964
  ];
5901
- case 12:
5965
+ case 18:
5902
5966
  if (this.isTemporaryAdError(error)) {
5903
5967
  this.temporaryFailureUrls.set(firstAdUrl, Date.now());
5904
5968
  if (this.config.debugAdTiming) {
@@ -5916,13 +5980,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5916
5980
  4,
5917
5981
  this.tryNextAvailableAdWithRateLimit()
5918
5982
  ];
5919
- case 13:
5983
+ case 19:
5920
5984
  _state.sent();
5921
5985
  return [
5922
5986
  3,
5923
- 14
5987
+ 20
5924
5988
  ];
5925
- case 14:
5989
+ case 20:
5926
5990
  return [
5927
5991
  2
5928
5992
  ];
@@ -5963,10 +6027,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5963
6027
  return _ts_generator(this, function(_state) {
5964
6028
  switch(_state.label){
5965
6029
  case 0:
5966
- remaining = _this.getRemainingAdMs();
6030
+ remaining = Math.min(_this.getWallClockRemainingAdMs(), _this.getDurationBudgetRemainingMs());
5967
6031
  if (remaining <= _this.getMinRemainingMsToStartAd()) {
5968
6032
  if (_this.config.debugAdTiming) {
5969
- console.log("[CONTINUOUS-FETCH] ⏹️ Not enough ad break time left (".concat(remaining, "ms), 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"));
5970
6034
  }
5971
6035
  return [
5972
6036
  2,
@@ -6048,7 +6112,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6048
6112
  });
6049
6113
  }
6050
6114
  _this.adRequestQueue.push(newAdUrl);
6051
- _this.totalAdsInBreak++;
6052
6115
  generationDelay = _this.consecutiveFailures > 0 ? Math.min(1e3 * Math.pow(2, _this.consecutiveFailures), 5e3) : 500;
6053
6116
  return [
6054
6117
  4,
@@ -6217,7 +6280,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6217
6280
  preloaded = this.getPreloadedAd();
6218
6281
  if (!preloaded) return [
6219
6282
  3,
6220
- 4
6283
+ 6
6221
6284
  ];
6222
6285
  if (this.config.debugAdTiming) {
6223
6286
  console.log("[CONTINUOUS-FETCH] \uD83C\uDFAF Using preloaded ad from pool (".concat(this.preloadPool.length, " remaining in pool)"));
@@ -6231,10 +6294,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6231
6294
  case 1:
6232
6295
  _state.trys.push([
6233
6296
  1,
6234
- 3,
6297
+ 5,
6235
6298
  ,
6236
- 4
6299
+ 6
6237
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:
6238
6316
  preservedMuted = this.adPlayer.getOriginalMutedState();
6239
6317
  preservedVolume = this.adPlayer.getOriginalVolume();
6240
6318
  this.adPlayer.destroy();
@@ -6243,15 +6321,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6243
6321
  this.attachImaEventListeners();
6244
6322
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
6245
6323
  this.adPlayer.resume();
6246
- this.currentAdIndex++;
6247
- this.totalAdRequestsInBreak++;
6248
- this.lastAdRequestTime = Date.now();
6249
6324
  return [
6250
6325
  4,
6251
6326
  this.adPlayer.play()
6252
6327
  ];
6253
- case 2:
6328
+ case 4:
6254
6329
  _state.sent();
6330
+ this.markAdStarted();
6255
6331
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6256
6332
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
6257
6333
  }
@@ -6260,25 +6336,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6260
6336
  return [
6261
6337
  2
6262
6338
  ];
6263
- case 3:
6339
+ case 5:
6264
6340
  error = _state.sent();
6265
6341
  if (this.config.debugAdTiming) {
6266
6342
  console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F Preloaded ad failed to play:", error);
6267
6343
  }
6268
6344
  return [
6269
6345
  3,
6270
- 4
6346
+ 6
6271
6347
  ];
6272
- case 4:
6348
+ case 6:
6273
6349
  if (!(this.adRequestQueue.length > 0)) return [
6274
6350
  3,
6275
- 10
6351
+ 14
6276
6352
  ];
6277
6353
  nextAdUrl = this.adRequestQueue.shift();
6278
6354
  if (!nextAdUrl) {
6279
6355
  return [
6280
6356
  3,
6281
- 4
6357
+ 6
6282
6358
  ];
6283
6359
  }
6284
6360
  if (this.failedVastUrls.has(nextAdUrl)) {
@@ -6287,7 +6363,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6287
6363
  }
6288
6364
  return [
6289
6365
  3,
6290
- 4
6366
+ 6
6291
6367
  ];
6292
6368
  }
6293
6369
  if (this.isUrlInCooldown(nextAdUrl)) {
@@ -6296,40 +6372,51 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6296
6372
  }
6297
6373
  return [
6298
6374
  3,
6299
- 4
6375
+ 6
6300
6376
  ];
6301
6377
  }
6302
6378
  if (this.config.debugAdTiming) {
6303
- 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)"));
6304
6380
  }
6305
- this.currentAdIndex++;
6306
- this.totalAdRequestsInBreak++;
6307
- this.lastAdRequestTime = Date.now();
6308
- _state.label = 5;
6309
- case 5:
6381
+ _state.label = 7;
6382
+ case 7:
6310
6383
  _state.trys.push([
6311
- 5,
6312
6384
  7,
6385
+ 9,
6313
6386
  ,
6314
- 9
6387
+ 13
6315
6388
  ]);
6316
6389
  return [
6317
6390
  4,
6318
6391
  this.playSingleAd(nextAdUrl)
6319
6392
  ];
6320
- case 6:
6393
+ case 8:
6321
6394
  _state.sent();
6322
6395
  this.consecutiveFailures = 0;
6323
6396
  return [
6324
6397
  3,
6325
- 9
6398
+ 13
6326
6399
  ];
6327
- case 7:
6400
+ case 9:
6328
6401
  error1 = _state.sent();
6329
6402
  errorMessage = error1.message;
6330
6403
  if (this.config.debugAdTiming) {
6331
6404
  console.log("[CONTINUOUS-FETCH] \u274C Ad request failed:", errorMessage);
6332
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:
6333
6420
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6334
6421
  this.consecutiveFailures++;
6335
6422
  }
@@ -6337,21 +6424,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6337
6424
  4,
6338
6425
  this.tryNextAvailableAdWithRateLimit()
6339
6426
  ];
6340
- case 8:
6427
+ case 12:
6341
6428
  _state.sent();
6342
6429
  return [
6343
6430
  3,
6344
- 9
6431
+ 13
6345
6432
  ];
6346
- case 9:
6433
+ case 13:
6347
6434
  return [
6348
6435
  2
6349
6436
  ];
6350
- case 10:
6437
+ case 14:
6351
6438
  maxRetries = 3;
6352
6439
  if (!(this.continuousFetchingActive && retryCount < maxRetries && remaining > 2e3)) return [
6353
6440
  3,
6354
- 13
6441
+ 17
6355
6442
  ];
6356
6443
  if (this.config.debugAdTiming) {
6357
6444
  console.log("[CONTINUOUS-FETCH] ⏳ Queue empty, waiting for URLs... (".concat(retryCount + 1, "/").concat(maxRetries, ")"));
@@ -6362,18 +6449,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6362
6449
  return setTimeout(resolve, 500);
6363
6450
  })
6364
6451
  ];
6365
- case 11:
6452
+ case 15:
6366
6453
  _state.sent();
6367
6454
  return [
6368
6455
  4,
6369
6456
  this.tryNextAvailableAd(retryCount + 1)
6370
6457
  ];
6371
- case 12:
6458
+ case 16:
6372
6459
  _state.sent();
6373
6460
  return [
6374
6461
  2
6375
6462
  ];
6376
- case 13:
6463
+ case 17:
6377
6464
  if (!this.isShowingPlaceholder && remaining > 1e3) {
6378
6465
  this.showPlaceholderAndWaitForAds();
6379
6466
  } else {
@@ -6436,7 +6523,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6436
6523
  case 1:
6437
6524
  if (!(i < maxChecks)) return [
6438
6525
  3,
6439
- 10
6526
+ 12
6440
6527
  ];
6441
6528
  return [
6442
6529
  4,
@@ -6457,7 +6544,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6457
6544
  }
6458
6545
  return [
6459
6546
  3,
6460
- 10
6547
+ 12
6461
6548
  ];
6462
6549
  }
6463
6550
  if (!this.hasTimeToStartAnotherAd()) {
@@ -6466,19 +6553,19 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6466
6553
  }
6467
6554
  return [
6468
6555
  3,
6469
- 10
6556
+ 12
6470
6557
  ];
6471
6558
  }
6472
6559
  _state.label = 3;
6473
6560
  case 3:
6474
6561
  if (!(this.adRequestQueue.length > 0)) return [
6475
6562
  3,
6476
- 9
6563
+ 11
6477
6564
  ];
6478
6565
  if (!this.hasTimeToStartAnotherAd()) {
6479
6566
  return [
6480
6567
  3,
6481
- 9
6568
+ 11
6482
6569
  ];
6483
6570
  }
6484
6571
  nextAdUrl = this.adRequestQueue.shift();
@@ -6505,16 +6592,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6505
6592
  }
6506
6593
  this.isShowingPlaceholder = false;
6507
6594
  this.adPlayer.hidePlaceholder();
6508
- this.currentAdIndex++;
6509
- this.totalAdRequestsInBreak++;
6510
- this.lastAdRequestTime = Date.now();
6511
6595
  _state.label = 4;
6512
6596
  case 4:
6513
6597
  _state.trys.push([
6514
6598
  4,
6515
6599
  6,
6516
6600
  ,
6517
- 8
6601
+ 10
6518
6602
  ]);
6519
6603
  return [
6520
6604
  4,
@@ -6525,11 +6609,25 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6525
6609
  this.consecutiveFailures = 0;
6526
6610
  return [
6527
6611
  3,
6528
- 8
6612
+ 10
6529
6613
  ];
6530
6614
  case 6:
6531
6615
  error = _state.sent();
6532
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:
6533
6631
  if (!errorMessage.includes("permanently failed") && !errorMessage.includes("in cooldown")) {
6534
6632
  this.consecutiveFailures++;
6535
6633
  }
@@ -6537,23 +6635,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6537
6635
  4,
6538
6636
  this.tryNextAvailableAdWithRateLimit()
6539
6637
  ];
6540
- case 7:
6638
+ case 9:
6541
6639
  _state.sent();
6542
6640
  return [
6543
6641
  3,
6544
- 8
6642
+ 10
6545
6643
  ];
6546
- case 8:
6644
+ case 10:
6547
6645
  return [
6548
6646
  2
6549
6647
  ];
6550
- case 9:
6648
+ case 11:
6551
6649
  i++;
6552
6650
  return [
6553
6651
  3,
6554
6652
  1
6555
6653
  ];
6556
- case 10:
6654
+ case 12:
6557
6655
  if (this.config.debugAdTiming) {
6558
6656
  console.log("[CONTINUOUS-FETCH] \u23F0 Placeholder timeout, ending ad break");
6559
6657
  }
@@ -7025,6 +7123,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7025
7123
  2
7026
7124
  ];
7027
7125
  }
7126
+ if (!this.ensureLoadedAdFitsBudget()) {
7127
+ this.rejectLoadedAdForDuration();
7128
+ this.recreateAdController();
7129
+ throw new Error("ad_duration_exceeds_budget");
7130
+ }
7028
7131
  _state.label = 4;
7029
7132
  case 4:
7030
7133
  _state.trys.push([
@@ -7040,6 +7143,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7040
7143
  ];
7041
7144
  case 5:
7042
7145
  _state.sent();
7146
+ this.markAdStarted();
7043
7147
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7044
7148
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7045
7149
  }
@@ -7071,6 +7175,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7071
7175
  ,
7072
7176
  10
7073
7177
  ]);
7178
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback.adController)) {
7179
+ this.rejectLoadedAdForDuration(preloadedFallback.adController);
7180
+ throw playError;
7181
+ }
7074
7182
  this.clearAdFailsafeTimer();
7075
7183
  preservedMuted = this.adPlayer.getOriginalMutedState();
7076
7184
  preservedVolume = this.adPlayer.getOriginalVolume();
@@ -7081,14 +7189,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7081
7189
  this.adPlayer.updateOriginalMutedState(preservedMuted, preservedVolume);
7082
7190
  this.adPlayer.resume();
7083
7191
  this.consecutiveFailures = 0;
7084
- this.currentAdIndex++;
7085
- this.totalAdRequestsInBreak++;
7086
7192
  return [
7087
7193
  4,
7088
7194
  this.adPlayer.play()
7089
7195
  ];
7090
7196
  case 8:
7091
7197
  _state.sent();
7198
+ this.markAdStarted();
7092
7199
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7093
7200
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
7094
7201
  }
@@ -7149,6 +7256,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7149
7256
  ,
7150
7257
  16
7151
7258
  ]);
7259
+ if (!this.ensureLoadedAdFitsBudget(preloadedFallback1.adController)) {
7260
+ this.rejectLoadedAdForDuration(preloadedFallback1.adController);
7261
+ throw error;
7262
+ }
7152
7263
  this.clearAdRequestWatchdog();
7153
7264
  this.clearAdFailsafeTimer();
7154
7265
  preservedMuted1 = this.adPlayer.getOriginalMutedState();
@@ -7160,14 +7271,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7160
7271
  this.adPlayer.updateOriginalMutedState(preservedMuted1, preservedVolume1);
7161
7272
  this.adPlayer.resume();
7162
7273
  this.consecutiveFailures = 0;
7163
- this.currentAdIndex++;
7164
- this.totalAdRequestsInBreak++;
7165
7274
  return [
7166
7275
  4,
7167
7276
  this.adPlayer.play()
7168
7277
  ];
7169
7278
  case 14:
7170
7279
  _state.sent();
7280
+ this.markAdStarted();
7171
7281
  this.adPlayer.setAdVolume(this.getAdAudioVolume());
7172
7282
  return [
7173
7283
  2
@@ -7228,6 +7338,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7228
7338
  this.adRequestQueue = [];
7229
7339
  this.inAdBreak = false;
7230
7340
  this.adDetectSentForCurrentBreak = false;
7341
+ this.adBreakPlayedDurationMs = 0;
7231
7342
  this.activeScte35BreakKey = void 0;
7232
7343
  this.scheduledScte35BreakKey = void 0;
7233
7344
  this.expectedAdBreakDurationMs = void 0;
@@ -7417,13 +7528,117 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7417
7528
  }
7418
7529
  },
7419
7530
  {
7420
- key: "getRemainingAdMs",
7421
- value: function getRemainingAdMs() {
7422
- 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
+ }
7423
7536
  var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7424
7537
  return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7425
7538
  }
7426
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
+ },
7427
7642
  {
7428
7643
  key: "getMinRemainingMsToStartAd",
7429
7644
  value: function getMinRemainingMsToStartAd() {
@@ -7437,7 +7652,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7437
7652
  {
7438
7653
  key: "hasTimeToStartAnotherAd",
7439
7654
  value: function hasTimeToStartAnotherAd() {
7440
- return this.getRemainingAdMs() > this.getMinRemainingMsToStartAd();
7655
+ var minRemaining = this.getMinRemainingMsToStartAd();
7656
+ return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;
7441
7657
  }
7442
7658
  },
7443
7659
  {