stormcloud-video-player 0.5.25 → 0.5.27

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
@@ -638,9 +638,9 @@ function createPrebidManager() {
638
638
  });
639
639
  })();
640
640
  }
641
- function requestBids() {
641
+ function requestBids(context) {
642
642
  return _async_to_generator(function() {
643
- var timeout, controller, timeoutId, _data_ext, _data_ext1, fetchOptions, response, body, data, bids, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, b, error;
643
+ var timeout, controller, timeoutId, _data_ext, _data_ext1, fetchOptions, body, response, body1, data, bids, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, b, error;
644
644
  return _ts_generator(this, function(_state) {
645
645
  switch(_state.label){
646
646
  case 0:
@@ -667,6 +667,22 @@ function createPrebidManager() {
667
667
  if (controller) {
668
668
  fetchOptions.signal = controller.signal;
669
669
  }
670
+ if ((context === null || context === void 0 ? void 0 : context.remainingBreakSec) != null) {
671
+ body = {
672
+ imp: [
673
+ {
674
+ video: {
675
+ maxduration: Math.floor(context.remainingBreakSec)
676
+ }
677
+ }
678
+ ]
679
+ };
680
+ fetchOptions.body = JSON.stringify(body);
681
+ fetchOptions.headers = {
682
+ "Content-Type": "application/json"
683
+ };
684
+ log("Sending context to auction: maxduration=", Math.floor(context.remainingBreakSec));
685
+ }
670
686
  return [
671
687
  4,
672
688
  fetch(AUCTION_URL, fetchOptions)
@@ -685,8 +701,8 @@ function createPrebidManager() {
685
701
  })
686
702
  ];
687
703
  case 3:
688
- body = _state.sent();
689
- throw new Error("Prebid Server returned HTTP ".concat(response.status, ": ").concat(body.slice(0, 200)));
704
+ body1 = _state.sent();
705
+ throw new Error("Prebid Server returned HTTP ".concat(response.status, ": ").concat(body1.slice(0, 200)));
690
706
  case 4:
691
707
  return [
692
708
  4,
@@ -749,7 +765,7 @@ function createPrebidManager() {
749
765
  }
750
766
  var REQUEST_BIDS_MAX_RETRIES = 3;
751
767
  var REQUEST_BIDS_BACKOFF_MS = 1500;
752
- function requestBidsUntilResponse() {
768
+ function requestBidsUntilResponse(context) {
753
769
  return _async_to_generator(function() {
754
770
  var _loop, lastError, attempt, _ret;
755
771
  return _ts_generator(this, function(_state) {
@@ -768,7 +784,7 @@ function createPrebidManager() {
768
784
  ]);
769
785
  return [
770
786
  4,
771
- requestBids()
787
+ requestBids(context)
772
788
  ];
773
789
  case 1:
774
790
  bids = _state.sent();
@@ -1200,6 +1216,7 @@ function createPrebidAdLayer(contentVideo, options) {
1200
1216
  var destroyed = false;
1201
1217
  var tornDown = false;
1202
1218
  var trackingFired = createEmptyTrackingState();
1219
+ var preloadSlots = /* @__PURE__ */ new Map();
1203
1220
  function emit(event, payload) {
1204
1221
  var set = listeners.get(event);
1205
1222
  if (!set) return;
@@ -1547,6 +1564,192 @@ function createPrebidAdLayer(contentVideo, options) {
1547
1564
  });
1548
1565
  })();
1549
1566
  }
1567
+ function ensureAdContainer() {
1568
+ if (!adContainerEl) {
1569
+ var _contentVideo_parentElement;
1570
+ var container = document.createElement("div");
1571
+ container.style.position = "absolute";
1572
+ container.style.left = "0";
1573
+ container.style.top = "0";
1574
+ container.style.right = "0";
1575
+ container.style.bottom = "0";
1576
+ container.style.display = "none";
1577
+ container.style.alignItems = "center";
1578
+ container.style.justifyContent = "center";
1579
+ container.style.pointerEvents = "none";
1580
+ container.style.zIndex = "10";
1581
+ container.style.backgroundColor = "#000";
1582
+ (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1583
+ adContainerEl = container;
1584
+ }
1585
+ return adContainerEl;
1586
+ }
1587
+ function preloadAd(bids, token) {
1588
+ return _async_to_generator(function() {
1589
+ var winner, ad, mediaFile, videoEl, container, hls, slot, slot1;
1590
+ return _ts_generator(this, function(_state) {
1591
+ switch(_state.label){
1592
+ case 0:
1593
+ if (destroyed) return [
1594
+ 2
1595
+ ];
1596
+ winner = bids[0];
1597
+ if (!winner) return [
1598
+ 2
1599
+ ];
1600
+ if (debug) console.log("".concat(LOG, " [preload] Resolving VAST for token=").concat(token));
1601
+ return [
1602
+ 4,
1603
+ resolveBidToVastAd(winner, LOG)
1604
+ ];
1605
+ case 1:
1606
+ ad = _state.sent();
1607
+ if (!ad || destroyed) return [
1608
+ 2
1609
+ ];
1610
+ mediaFile = selectBestMediaFile(ad.mediaFiles);
1611
+ if (!mediaFile) return [
1612
+ 2
1613
+ ];
1614
+ videoEl = createAdVideoElement();
1615
+ videoEl.style.visibility = "hidden";
1616
+ videoEl.style.pointerEvents = "none";
1617
+ videoEl.preload = "auto";
1618
+ container = ensureAdContainer();
1619
+ container.appendChild(videoEl);
1620
+ if (isHlsMediaFile(mediaFile) && import_hls.default.isSupported()) {
1621
+ hls = new import_hls.default({
1622
+ enableWorker: true,
1623
+ lowLatencyMode: false
1624
+ });
1625
+ hls.loadSource(mediaFile.url);
1626
+ hls.attachMedia(videoEl);
1627
+ slot = {
1628
+ bids: bids,
1629
+ ad: ad,
1630
+ mediaFile: mediaFile,
1631
+ videoEl: videoEl,
1632
+ hlsInstance: hls,
1633
+ ready: false
1634
+ };
1635
+ preloadSlots.set(token, slot);
1636
+ hls.on(import_hls.default.Events.MANIFEST_PARSED, function() {
1637
+ var s = preloadSlots.get(token);
1638
+ if (s) s.ready = true;
1639
+ if (debug) console.log("".concat(LOG, " [preload] HLS manifest parsed, token=").concat(token));
1640
+ });
1641
+ hls.on(import_hls.default.Events.ERROR, function(_evt, data) {
1642
+ if (data.fatal) {
1643
+ if (debug) console.warn("".concat(LOG, " [preload] HLS error for token=").concat(token));
1644
+ preloadSlots.delete(token);
1645
+ hls.destroy();
1646
+ videoEl.remove();
1647
+ }
1648
+ });
1649
+ } else {
1650
+ videoEl.src = mediaFile.url;
1651
+ videoEl.load();
1652
+ slot1 = {
1653
+ bids: bids,
1654
+ ad: ad,
1655
+ mediaFile: mediaFile,
1656
+ videoEl: videoEl,
1657
+ ready: false
1658
+ };
1659
+ preloadSlots.set(token, slot1);
1660
+ videoEl.addEventListener("canplay", function() {
1661
+ var s = preloadSlots.get(token);
1662
+ if (s) s.ready = true;
1663
+ if (debug) console.log("".concat(LOG, " [preload] canplay fired, token=").concat(token));
1664
+ }, {
1665
+ once: true
1666
+ });
1667
+ }
1668
+ if (debug) console.log("".concat(LOG, " [preload] Started buffering token=").concat(token, ", url=").concat(mediaFile.url));
1669
+ return [
1670
+ 2
1671
+ ];
1672
+ }
1673
+ });
1674
+ })();
1675
+ }
1676
+ function playPreloaded(token) {
1677
+ return _async_to_generator(function() {
1678
+ var slot, contentVolume, adVolume, container;
1679
+ return _ts_generator(this, function(_state) {
1680
+ if (destroyed) return [
1681
+ 2,
1682
+ Promise.reject(new Error("Layer has been destroyed"))
1683
+ ];
1684
+ slot = preloadSlots.get(token);
1685
+ if (!slot) {
1686
+ if (debug) console.warn("".concat(LOG, " [preload] No slot found for token=").concat(token, ", nothing to play"));
1687
+ return [
1688
+ 2
1689
+ ];
1690
+ }
1691
+ preloadSlots.delete(token);
1692
+ if (debug) console.log("".concat(LOG, " [preload] Playing preloaded ad, token=").concat(token, ", ready=").concat(slot.ready));
1693
+ teardownCurrentPlayback();
1694
+ if (adVideoElement && adVideoElement !== slot.videoEl) {
1695
+ adVideoElement.remove();
1696
+ }
1697
+ slot.videoEl.style.visibility = "visible";
1698
+ slot.videoEl.style.pointerEvents = "none";
1699
+ adVideoElement = slot.videoEl;
1700
+ adHls = slot.hlsInstance;
1701
+ setupAdEventListeners();
1702
+ sessionId = generateSessionId();
1703
+ currentAd = slot.ad;
1704
+ trackingFired = _object_spread({}, createEmptyTrackingState());
1705
+ fireTrackingPixels2(slot.ad.trackingUrls.impression);
1706
+ trackingFired.impression = true;
1707
+ contentVolume = contentVideo.volume;
1708
+ originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1709
+ if (!continueLiveStreamDuringAds) {
1710
+ contentVideo.pause();
1711
+ }
1712
+ contentVideo.muted = true;
1713
+ contentVideo.volume = 0;
1714
+ adPlaying = true;
1715
+ setAdPlayingFlag(true);
1716
+ adVolume = originalMutedState ? 1 : originalVolume;
1717
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1718
+ adVideoElement.muted = false;
1719
+ container = ensureAdContainer();
1720
+ container.style.display = "flex";
1721
+ container.style.pointerEvents = "auto";
1722
+ emit("content_pause");
1723
+ if (slot.hlsInstance) {
1724
+ adVideoElement.play().catch(function(error) {
1725
+ console.error("".concat(LOG, " [preload] Error playing preloaded HLS ad:"), error);
1726
+ handleAdError();
1727
+ });
1728
+ } else {
1729
+ adVideoElement.play().catch(function(error) {
1730
+ console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1731
+ handleAdError();
1732
+ });
1733
+ }
1734
+ return [
1735
+ 2
1736
+ ];
1737
+ });
1738
+ })();
1739
+ }
1740
+ function cancelPreload(token) {
1741
+ var slot = preloadSlots.get(token);
1742
+ if (!slot) return;
1743
+ preloadSlots.delete(token);
1744
+ if (slot.hlsInstance) {
1745
+ slot.hlsInstance.destroy();
1746
+ }
1747
+ slot.videoEl.pause();
1748
+ slot.videoEl.removeAttribute("src");
1749
+ slot.videoEl.load();
1750
+ slot.videoEl.remove();
1751
+ if (debug) console.log("".concat(LOG, " [preload] Cancelled and cleaned up token=").concat(token));
1752
+ }
1550
1753
  return {
1551
1754
  initialize: function initialize() {
1552
1755
  if (debug) console.log("".concat(LOG, " Initializing"));
@@ -1561,6 +1764,12 @@ function createPrebidAdLayer(contentVideo, options) {
1561
1764
  }
1562
1765
  },
1563
1766
  playAd: playAd,
1767
+ preloadAd: preloadAd,
1768
+ playPreloaded: playPreloaded,
1769
+ hasPreloaded: function hasPreloaded(token) {
1770
+ return preloadSlots.has(token);
1771
+ },
1772
+ cancelPreload: cancelPreload,
1564
1773
  pause: function pause() {
1565
1774
  if (!adPlaying || !adVideoElement) return;
1566
1775
  try {
@@ -1617,6 +1826,26 @@ function createPrebidAdLayer(contentVideo, options) {
1617
1826
  setAdPlayingFlag(false);
1618
1827
  contentVideo.muted = originalMutedState;
1619
1828
  contentVideo.volume = originalVolume;
1829
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1830
+ try {
1831
+ for(var _iterator = Array.from(preloadSlots.entries())[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1832
+ var _step_value = _sliced_to_array(_step.value, 1), token = _step_value[0];
1833
+ cancelPreload(token);
1834
+ }
1835
+ } catch (err) {
1836
+ _didIteratorError = true;
1837
+ _iteratorError = err;
1838
+ } finally{
1839
+ try {
1840
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1841
+ _iterator.return();
1842
+ }
1843
+ } finally{
1844
+ if (_didIteratorError) {
1845
+ throw _iteratorError;
1846
+ }
1847
+ }
1848
+ }
1620
1849
  teardownCurrentPlayback();
1621
1850
  if (adVideoElement) {
1622
1851
  adVideoElement.pause();
@@ -2799,6 +3028,7 @@ function supportsFeature(feature) {
2799
3028
  var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2800
3029
  function StormcloudVideoPlayer(config) {
2801
3030
  _class_call_check(this, StormcloudVideoPlayer);
3031
+ var _this_config_adTransitionGapMs;
2802
3032
  this.pendingNextAdBids = null;
2803
3033
  this.continuousFetchLoopPromise = null;
2804
3034
  this.attached = false;
@@ -2832,11 +3062,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2832
3062
  this.minAdRequestIntervalMs = 2500;
2833
3063
  this.backoffBaseMs = 1e3;
2834
3064
  this.maxBackoffMs = 15e3;
2835
- this.adTransitionGapMs = 1500;
3065
+ this.MIN_AD_REMAINING_MS = 15e3;
3066
+ this.preloadedTokens = [];
2836
3067
  initializePolyfills();
2837
3068
  var browserOverrides = getBrowserConfigOverrides();
2838
3069
  this.config = _object_spread({}, browserOverrides, config);
2839
3070
  this.video = config.videoElement;
3071
+ this.adTransitionGapMs = (_this_config_adTransitionGapMs = this.config.adTransitionGapMs) !== null && _this_config_adTransitionGapMs !== void 0 ? _this_config_adTransitionGapMs : 100;
2840
3072
  logBrowserInfo(config.debugAdTiming);
2841
3073
  this.prebidManager = createPrebidManager(config.debugAdTiming !== void 0 ? {
2842
3074
  debug: !!config.debugAdTiming
@@ -2849,7 +3081,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2849
3081
  _create_class(StormcloudVideoPlayer, [
2850
3082
  {
2851
3083
  key: "adRequest",
2852
- value: function adRequest() {
3084
+ value: function adRequest(context) {
2853
3085
  return _async_to_generator(function() {
2854
3086
  return _ts_generator(this, function(_state) {
2855
3087
  switch(_state.label){
@@ -2866,7 +3098,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2866
3098
  _state.sent();
2867
3099
  return [
2868
3100
  2,
2869
- this.prebidManager.requestBidsUntilResponse()
3101
+ this.prebidManager.requestBidsUntilResponse(context)
2870
3102
  ];
2871
3103
  }
2872
3104
  });
@@ -3362,7 +3594,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3362
3594
  this.adLayer.on("content_resume", function() {
3363
3595
  var remaining = _this.getRemainingAdMs();
3364
3596
  if (_this.config.debugAdTiming) {
3365
- console.log("[StormcloudVideoPlayer] content_resume received, inAdBreak=%s, remaining=%s, pendingNext=%s", _this.inAdBreak, remaining, !!_this.pendingNextAdBids);
3597
+ console.log("[StormcloudVideoPlayer] content_resume received, inAdBreak=%s, remaining=%s, preloadedTokens=%d, pendingNext=%s", _this.inAdBreak, remaining, _this.preloadedTokens.length, !!_this.pendingNextAdBids);
3366
3598
  }
3367
3599
  _this.clearAdFailsafeTimer();
3368
3600
  _this.clearAdRequestWatchdog();
@@ -3375,32 +3607,75 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3375
3607
  return;
3376
3608
  }
3377
3609
  _this.consecutiveFailures = 0;
3610
+ if (!_this.video.muted) {
3611
+ _this.video.muted = true;
3612
+ _this.video.volume = 0;
3613
+ }
3614
+ if (_this.preloadedTokens.length > 0) {
3615
+ var token = _this.preloadedTokens.shift();
3616
+ var remainingNow = _this.getRemainingAdMs();
3617
+ if (_this.expectedAdBreakDurationMs != null && remainingNow < _this.MIN_AD_REMAINING_MS) {
3618
+ if (_this.config.debugAdTiming) {
3619
+ console.log("[StormcloudVideoPlayer] content_resume: skip preloaded ad, only", remainingNow, "ms left");
3620
+ }
3621
+ _this.adLayer.cancelPreload(token);
3622
+ } else {
3623
+ _this.showPlaceholderLayer();
3624
+ _this.adLayer.showPlaceholder();
3625
+ _this.isInAdTransition = true;
3626
+ setTimeout(function() {
3627
+ _this.isInAdTransition = false;
3628
+ if (!_this.inAdBreak) return;
3629
+ _this.currentAdIndex++;
3630
+ _this.adLayer.playPreloaded(token).catch(function(err) {
3631
+ if (_this.config.debugAdTiming) console.warn("[StormcloudVideoPlayer] playPreloaded failed:", err);
3632
+ _this.handleAdFailure();
3633
+ });
3634
+ }, _this.adTransitionGapMs);
3635
+ return;
3636
+ }
3637
+ }
3378
3638
  if (_this.pendingNextAdBids && _this.pendingNextAdBids.length > 0) {
3379
3639
  var bids = _to_consumable_array(_this.pendingNextAdBids);
3380
3640
  _this.pendingNextAdBids = null;
3381
- if (!_this.video.muted) {
3382
- _this.video.muted = true;
3383
- _this.video.volume = 0;
3641
+ var remainingNow1 = _this.getRemainingAdMs();
3642
+ if (_this.expectedAdBreakDurationMs != null && remainingNow1 < _this.MIN_AD_REMAINING_MS) {
3643
+ if (_this.config.debugAdTiming) {
3644
+ console.log("[StormcloudVideoPlayer] content_resume: skip pending bids, only", remainingNow1, "ms left");
3645
+ }
3646
+ } else {
3647
+ _this.showPlaceholderLayer();
3648
+ _this.adLayer.showPlaceholder();
3649
+ _this.isInAdTransition = true;
3650
+ setTimeout(function() {
3651
+ var _this_pendingNextAdBids;
3652
+ _this.isInAdTransition = false;
3653
+ if (!_this.inAdBreak || bids.length === 0) return;
3654
+ var freshBids = (_this_pendingNextAdBids = _this.pendingNextAdBids) !== null && _this_pendingNextAdBids !== void 0 ? _this_pendingNextAdBids : bids;
3655
+ _this.pendingNextAdBids = null;
3656
+ _this.currentAdIndex++;
3657
+ _this.adLayer.playAd(freshBids).catch(function(err) {
3658
+ if (_this.config.debugAdTiming) console.warn("[StormcloudVideoPlayer] playAd(pending) failed:", err);
3659
+ _this.handleAdFailure();
3660
+ });
3661
+ }, _this.adTransitionGapMs);
3662
+ return;
3384
3663
  }
3385
- _this.showPlaceholderLayer();
3386
- _this.adLayer.showPlaceholder();
3387
- _this.isInAdTransition = true;
3388
- setTimeout(function() {
3389
- var _this_pendingNextAdBids;
3390
- _this.isInAdTransition = false;
3391
- if (!_this.inAdBreak || bids.length === 0) return;
3392
- var freshBids = (_this_pendingNextAdBids = _this.pendingNextAdBids) !== null && _this_pendingNextAdBids !== void 0 ? _this_pendingNextAdBids : bids;
3393
- _this.pendingNextAdBids = null;
3394
- _this.currentAdIndex++;
3395
- _this.adLayer.playAd(freshBids).catch(function(err) {
3396
- if (_this.config.debugAdTiming) console.warn("[StormcloudVideoPlayer] playAd(pending) failed:", err);
3397
- _this.handleAdFailure();
3398
- });
3399
- }, _this.adTransitionGapMs);
3664
+ }
3665
+ var remainingFinal = _this.getRemainingAdMs();
3666
+ if (_this.inAdBreak && remainingFinal > _this.MIN_AD_REMAINING_MS) {
3667
+ if (_this.config.debugAdTiming) {
3668
+ console.log("[StormcloudVideoPlayer] content_resume: no more ads, showing filler for", remainingFinal, "ms");
3669
+ }
3670
+ if (!_this.config.disableFiller) {
3671
+ _this.showPlaceholderLayer();
3672
+ _this.adLayer.showPlaceholder();
3673
+ }
3674
+ _this.stopContinuousFetching();
3400
3675
  return;
3401
3676
  }
3402
3677
  if (_this.config.debugAdTiming) {
3403
- console.log("[StormcloudVideoPlayer] content_resume: no more ads, ending ad pod and restoring main content sound");
3678
+ console.log("[StormcloudVideoPlayer] content_resume: no more ads, ending ad pod");
3404
3679
  }
3405
3680
  _this.handleAdPodComplete();
3406
3681
  });
@@ -4167,7 +4442,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4167
4442
  {
4168
4443
  key: "startAdPrefetch",
4169
4444
  value: function startAdPrefetch(marker, fragmentSn) {
4170
- var _this = this;
4171
4445
  if (this.config.disablePrebid) return;
4172
4446
  if (this.pendingAdBreak || this.inAdBreak) {
4173
4447
  return;
@@ -4180,16 +4454,91 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4180
4454
  isFetching: false,
4181
4455
  fetchStartTime: Date.now()
4182
4456
  });
4183
- void this.adRequest().then(function() {}).catch(function() {
4184
- if (_this.config.debugAdTiming) {
4185
- console.log("[PREFETCH] Prebid auction prefetch failed, will request at playback time");
4186
- }
4187
- });
4457
+ void this.runAdPrefetch(marker);
4188
4458
  if (this.config.debugAdTiming) {
4189
- console.log("[PREFETCH] Ad break marker registered, auction prefetch started");
4459
+ console.log("[PREFETCH] Ad break marker registered, multi-ad prefetch started");
4190
4460
  }
4191
4461
  }
4192
4462
  },
4463
+ {
4464
+ key: "runAdPrefetch",
4465
+ value: function runAdPrefetch(marker) {
4466
+ return _async_to_generator(function() {
4467
+ var _this, _marker_durationSeconds, durSec, estimatedCount, context, fetches, results, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, result, token;
4468
+ return _ts_generator(this, function(_state) {
4469
+ switch(_state.label){
4470
+ case 0:
4471
+ _this = this;
4472
+ durSec = (_marker_durationSeconds = marker.durationSeconds) !== null && _marker_durationSeconds !== void 0 ? _marker_durationSeconds : 60;
4473
+ estimatedCount = Math.min(4, Math.max(1, Math.ceil(durSec / 20)));
4474
+ if (this.config.debugAdTiming) {
4475
+ console.log("[PREFETCH] Firing ".concat(estimatedCount, " parallel bid request(s) for ").concat(durSec, "s break"));
4476
+ }
4477
+ context = {
4478
+ breakDurationSec: durSec,
4479
+ remainingBreakSec: durSec
4480
+ };
4481
+ fetches = Array.from({
4482
+ length: estimatedCount
4483
+ }, function(_, i) {
4484
+ return _this.adRequest(_object_spread_props(_object_spread({}, context), {
4485
+ adIndex: i + 1
4486
+ })).then(function(bids) {
4487
+ return {
4488
+ ok: true,
4489
+ value: bids
4490
+ };
4491
+ }).catch(function() {
4492
+ return {
4493
+ ok: false
4494
+ };
4495
+ });
4496
+ });
4497
+ return [
4498
+ 4,
4499
+ Promise.all(fetches)
4500
+ ];
4501
+ case 1:
4502
+ results = _state.sent();
4503
+ _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
4504
+ try {
4505
+ for(_iterator = results[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
4506
+ result = _step.value;
4507
+ if (this.inAdBreak) break;
4508
+ if (result.ok && result.value.length > 0) {
4509
+ token = "preload_".concat(Date.now(), "_").concat(Math.random().toString(36).slice(2, 7));
4510
+ this.preloadedTokens.push(token);
4511
+ void this.adLayer.preloadAd(result.value, token);
4512
+ if (this.config.debugAdTiming) {
4513
+ console.log("[PREFETCH] Bid received, preloading video token=".concat(token));
4514
+ }
4515
+ }
4516
+ }
4517
+ } catch (err) {
4518
+ _didIteratorError = true;
4519
+ _iteratorError = err;
4520
+ } finally{
4521
+ try {
4522
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
4523
+ _iterator.return();
4524
+ }
4525
+ } finally{
4526
+ if (_didIteratorError) {
4527
+ throw _iteratorError;
4528
+ }
4529
+ }
4530
+ }
4531
+ if (this.config.debugAdTiming) {
4532
+ console.log("[PREFETCH] Pre-fetch complete: ".concat(this.preloadedTokens.length, " ad(s) queued"));
4533
+ }
4534
+ return [
4535
+ 2
4536
+ ];
4537
+ }
4538
+ });
4539
+ }).call(this);
4540
+ }
4541
+ },
4193
4542
  {
4194
4543
  key: "clearPendingAdBreak",
4195
4544
  value: function clearPendingAdBreak() {
@@ -4200,6 +4549,32 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4200
4549
  this.pendingAdBreak = null;
4201
4550
  }
4202
4551
  },
4552
+ {
4553
+ key: "cancelAndClearPreloadedTokens",
4554
+ value: function cancelAndClearPreloadedTokens() {
4555
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
4556
+ try {
4557
+ for(var _iterator = this.preloadedTokens[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
4558
+ var token = _step.value;
4559
+ this.adLayer.cancelPreload(token);
4560
+ }
4561
+ } catch (err) {
4562
+ _didIteratorError = true;
4563
+ _iteratorError = err;
4564
+ } finally{
4565
+ try {
4566
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
4567
+ _iterator.return();
4568
+ }
4569
+ } finally{
4570
+ if (_didIteratorError) {
4571
+ throw _iteratorError;
4572
+ }
4573
+ }
4574
+ }
4575
+ this.preloadedTokens = [];
4576
+ }
4577
+ },
4203
4578
  {
4204
4579
  key: "startContinuousFetchLoop",
4205
4580
  value: function startContinuousFetchLoop() {
@@ -4216,7 +4591,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4216
4591
  switch(_state.label){
4217
4592
  case 0:
4218
4593
  _loop = function() {
4219
- var delay, elapsed, bids, err;
4594
+ var remaining, prefetchContext, bids, err, bids1, remainingNow, delay, elapsed, remaining1, context, bids2, remainingNow1, err1;
4220
4595
  return _ts_generator(this, function(_state) {
4221
4596
  switch(_state.label){
4222
4597
  case 0:
@@ -4228,11 +4603,135 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4228
4603
  2,
4229
4604
  "break"
4230
4605
  ];
4606
+ if (!(_this.adLayer.isAdPlaying() || _this.isInAdTransition)) return [
4607
+ 3,
4608
+ 8
4609
+ ];
4610
+ if (!(_this.pendingNextAdBids == null)) return [
4611
+ 3,
4612
+ 5
4613
+ ];
4614
+ _state.label = 1;
4615
+ case 1:
4616
+ _state.trys.push([
4617
+ 1,
4618
+ 3,
4619
+ ,
4620
+ 4
4621
+ ]);
4622
+ remaining = _this.getRemainingAdMs();
4623
+ prefetchContext = _this.expectedAdBreakDurationMs != null ? {
4624
+ breakDurationSec: _this.expectedAdBreakDurationMs / 1e3,
4625
+ remainingBreakSec: remaining / 1e3,
4626
+ adIndex: _this.currentAdIndex + 1
4627
+ } : void 0;
4628
+ if (_this.config.debugAdTiming) {
4629
+ console.log("[CONTINUOUS-FETCH] Pre-fetching next ad during playback, remaining:", remaining, "ms");
4630
+ }
4631
+ return [
4632
+ 4,
4633
+ _this.adRequest(prefetchContext)
4634
+ ];
4635
+ case 2:
4636
+ bids = _state.sent();
4637
+ _this.lastAdRequestTime = Date.now();
4638
+ if (!_this.inAdBreak) return [
4639
+ 2,
4640
+ "break"
4641
+ ];
4642
+ if (bids.length > 0) {
4643
+ _this.consecutiveFailures = 0;
4644
+ _this.pendingNextAdBids = bids;
4645
+ _this.totalAdsInBreak = Math.max(_this.totalAdsInBreak, _this.currentAdIndex + _this.preloadedTokens.length + 1);
4646
+ if (_this.config.debugAdTiming) {
4647
+ console.log("[CONTINUOUS-FETCH] Pre-fetched next ad stored as pending");
4648
+ }
4649
+ } else {
4650
+ _this.consecutiveFailures++;
4651
+ }
4652
+ return [
4653
+ 3,
4654
+ 4
4655
+ ];
4656
+ case 3:
4657
+ err = _state.sent();
4658
+ _this.consecutiveFailures++;
4659
+ if (_this.config.debugAdTiming) {
4660
+ console.warn("[CONTINUOUS-FETCH] Pre-fetch adRequest failed:", err);
4661
+ }
4662
+ return [
4663
+ 3,
4664
+ 4
4665
+ ];
4666
+ case 4:
4667
+ return [
4668
+ 3,
4669
+ 7
4670
+ ];
4671
+ case 5:
4672
+ return [
4673
+ 4,
4674
+ new Promise(function(r) {
4675
+ return setTimeout(r, 200);
4676
+ })
4677
+ ];
4678
+ case 6:
4679
+ _state.sent();
4680
+ _state.label = 7;
4681
+ case 7:
4682
+ return [
4683
+ 2,
4684
+ "continue"
4685
+ ];
4686
+ case 8:
4687
+ if (!(_this.pendingNextAdBids != null && _this.pendingNextAdBids.length > 0)) return [
4688
+ 3,
4689
+ 12
4690
+ ];
4691
+ bids1 = _this.pendingNextAdBids;
4692
+ _this.pendingNextAdBids = null;
4693
+ remainingNow = _this.getRemainingAdMs();
4694
+ if (!(_this.expectedAdBreakDurationMs == null || remainingNow >= _this.MIN_AD_REMAINING_MS)) return [
4695
+ 3,
4696
+ 10
4697
+ ];
4698
+ _this.currentAdIndex++;
4699
+ if (_this.config.licenseKey) {
4700
+ sendAdLoadedTracking(_this.config.licenseKey, {
4701
+ source: _this.getAdSource(),
4702
+ timestamp: /* @__PURE__ */ new Date().toISOString()
4703
+ });
4704
+ }
4705
+ return [
4706
+ 4,
4707
+ _this.adLayer.playAd(bids1)
4708
+ ];
4709
+ case 9:
4710
+ _state.sent();
4711
+ if (_this.expectedAdBreakDurationMs != null && _this.adStopTimerId == null) {
4712
+ _this.scheduleAdStopCountdown(_this.getRemainingAdMs());
4713
+ }
4714
+ _this.adLayer.setAdVolume(_this.adLayer.getOriginalMutedState() ? 1 : _this.adLayer.getOriginalVolume());
4715
+ return [
4716
+ 3,
4717
+ 11
4718
+ ];
4719
+ case 10:
4720
+ if (_this.config.debugAdTiming) {
4721
+ console.log("[CONTINUOUS-FETCH] Discarding pre-fetched bids: only", remainingNow, "ms left");
4722
+ }
4723
+ _state.label = 11;
4724
+ case 11:
4725
+ return [
4726
+ 2,
4727
+ "continue"
4728
+ ];
4729
+ case 12:
4231
4730
  delay = _this.lastAdRequestTime ? _this.minAdRequestIntervalMs + (_this.consecutiveFailures > 0 ? backoffMs() : 0) : 0;
4232
4731
  elapsed = Date.now() - _this.lastAdRequestTime;
4233
4732
  if (!(elapsed < delay && _this.lastAdRequestTime > 0)) return [
4234
4733
  3,
4235
- 2
4734
+ 14
4236
4735
  ];
4237
4736
  return [
4238
4737
  4,
@@ -4240,51 +4739,71 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4240
4739
  return setTimeout(r, delay - elapsed);
4241
4740
  })
4242
4741
  ];
4243
- case 1:
4742
+ case 13:
4244
4743
  _state.sent();
4245
- _state.label = 2;
4246
- case 2:
4744
+ _state.label = 14;
4745
+ case 14:
4247
4746
  if (!_this.inAdBreak || !_this.continuousFetchingActive) return [
4248
4747
  2,
4249
4748
  "break"
4250
4749
  ];
4251
- _state.label = 3;
4252
- case 3:
4750
+ _state.label = 15;
4751
+ case 15:
4253
4752
  _state.trys.push([
4254
- 3,
4255
- 10,
4753
+ 15,
4754
+ 23,
4256
4755
  ,
4257
- 11
4756
+ 24
4258
4757
  ]);
4758
+ remaining1 = _this.getRemainingAdMs();
4759
+ context = _this.expectedAdBreakDurationMs != null ? {
4760
+ breakDurationSec: _this.expectedAdBreakDurationMs / 1e3,
4761
+ remainingBreakSec: remaining1 / 1e3,
4762
+ adIndex: _this.currentAdIndex + 1
4763
+ } : void 0;
4259
4764
  return [
4260
4765
  4,
4261
- _this.adRequest()
4766
+ _this.adRequest(context)
4262
4767
  ];
4263
- case 4:
4264
- bids = _state.sent();
4768
+ case 16:
4769
+ bids2 = _state.sent();
4265
4770
  _this.lastAdRequestTime = Date.now();
4266
4771
  if (!_this.inAdBreak) return [
4267
4772
  2,
4268
4773
  "break"
4269
4774
  ];
4270
- if (!(bids.length > 0)) return [
4775
+ if (!(bids2.length > 0)) return [
4271
4776
  3,
4272
- 8
4777
+ 21
4273
4778
  ];
4274
4779
  _this.consecutiveFailures = 0;
4275
4780
  if (!(_this.adLayer.isAdPlaying() || _this.isInAdTransition)) return [
4276
4781
  3,
4277
- 5
4782
+ 17
4278
4783
  ];
4279
- _this.pendingNextAdBids = bids;
4784
+ _this.pendingNextAdBids = bids2;
4785
+ _this.totalAdsInBreak = Math.max(_this.totalAdsInBreak, _this.currentAdIndex + _this.preloadedTokens.length + 1);
4280
4786
  if (_this.config.debugAdTiming) {
4281
4787
  console.log("[CONTINUOUS-FETCH] Next ad response stored (ad currently playing or in transition)");
4282
4788
  }
4283
4789
  return [
4284
4790
  3,
4285
- 7
4791
+ 20
4286
4792
  ];
4287
- case 5:
4793
+ case 17:
4794
+ remainingNow1 = _this.getRemainingAdMs();
4795
+ if (!(_this.expectedAdBreakDurationMs != null && remainingNow1 < _this.MIN_AD_REMAINING_MS)) return [
4796
+ 3,
4797
+ 18
4798
+ ];
4799
+ if (_this.config.debugAdTiming) {
4800
+ console.log("[CONTINUOUS-FETCH] Skipping ad from loop: only", remainingNow1, "ms left");
4801
+ }
4802
+ return [
4803
+ 3,
4804
+ 20
4805
+ ];
4806
+ case 18:
4288
4807
  _this.currentAdIndex++;
4289
4808
  if (_this.config.licenseKey) {
4290
4809
  sendAdLoadedTracking(_this.config.licenseKey, {
@@ -4294,46 +4813,46 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4294
4813
  }
4295
4814
  return [
4296
4815
  4,
4297
- _this.adLayer.playAd(bids)
4816
+ _this.adLayer.playAd(bids2)
4298
4817
  ];
4299
- case 6:
4818
+ case 19:
4300
4819
  _state.sent();
4301
4820
  if (_this.expectedAdBreakDurationMs != null && _this.adStopTimerId == null) {
4302
4821
  _this.scheduleAdStopCountdown(_this.getRemainingAdMs());
4303
4822
  }
4304
4823
  _this.adLayer.setAdVolume(_this.adLayer.getOriginalMutedState() ? 1 : _this.adLayer.getOriginalVolume());
4305
- _state.label = 7;
4306
- case 7:
4824
+ _state.label = 20;
4825
+ case 20:
4307
4826
  return [
4308
4827
  3,
4309
- 9
4828
+ 22
4310
4829
  ];
4311
- case 8:
4830
+ case 21:
4312
4831
  _this.consecutiveFailures++;
4313
- _state.label = 9;
4314
- case 9:
4832
+ _state.label = 22;
4833
+ case 22:
4315
4834
  return [
4316
4835
  3,
4317
- 11
4836
+ 24
4318
4837
  ];
4319
- case 10:
4320
- err = _state.sent();
4838
+ case 23:
4839
+ err1 = _state.sent();
4321
4840
  _this.consecutiveFailures++;
4322
4841
  if (_this.config.debugAdTiming) {
4323
- console.warn("[CONTINUOUS-FETCH] adRequest failed:", err);
4842
+ console.warn("[CONTINUOUS-FETCH] adRequest failed:", err1);
4324
4843
  }
4325
4844
  return [
4326
4845
  3,
4327
- 11
4846
+ 24
4328
4847
  ];
4329
- case 11:
4848
+ case 24:
4330
4849
  return [
4331
4850
  4,
4332
4851
  new Promise(function(r) {
4333
4852
  return setTimeout(r, backoffMs());
4334
4853
  })
4335
4854
  ];
4336
- case 12:
4855
+ case 25:
4337
4856
  _state.sent();
4338
4857
  return [
4339
4858
  2
@@ -4381,7 +4900,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4381
4900
  key: "handleAdStart",
4382
4901
  value: function handleAdStart(_marker) {
4383
4902
  return _async_to_generator(function() {
4384
- var _this_savedMutedStateBeforeScte, adBreakDurationMs, mode, state, adBreakToken, bids, adVolume, error;
4903
+ var _this_savedMutedStateBeforeScte, adBreakDurationMs, mode, state, adBreakToken, adVolume, token, remaining, err;
4385
4904
  return _ts_generator(this, function(_state) {
4386
4905
  switch(_state.label){
4387
4906
  case 0:
@@ -4417,7 +4936,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4417
4936
  this.inAdBreak = true;
4418
4937
  this.currentAdBreakStartWallClockMs = Date.now();
4419
4938
  this.currentAdIndex = 0;
4420
- this.totalAdsInBreak = 1;
4939
+ this.totalAdsInBreak = Math.max(1, this.preloadedTokens.length);
4421
4940
  this.adPodQueue = [];
4422
4941
  if (!this.config.disableFiller) this.showAds = true;
4423
4942
  if (adBreakDurationMs != null) {
@@ -4434,85 +4953,81 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4434
4953
  this.activeAdRequestToken = adBreakToken;
4435
4954
  this.startAdFailsafeTimer(adBreakToken);
4436
4955
  this.startAdRequestWatchdog(adBreakToken);
4437
- _state.label = 1;
4438
- case 1:
4439
- _state.trys.push([
4440
- 1,
4441
- 7,
4442
- ,
4443
- 9
4444
- ]);
4445
- this.lastAdRequestTime = Date.now();
4446
- return [
4447
- 4,
4448
- this.adRequest()
4449
- ];
4450
- case 2:
4451
- bids = _state.sent();
4452
- if (!this.inAdBreak) return [
4453
- 2
4956
+ adVolume = state.muted ? 1 : state.volume;
4957
+ if (!(this.preloadedTokens.length > 0)) return [
4958
+ 3,
4959
+ 8
4454
4960
  ];
4455
- if (!(bids.length > 0)) return [
4961
+ token = this.preloadedTokens.shift();
4962
+ if (this.config.debugAdTiming) {
4963
+ console.log("[CONTINUOUS-FETCH] \u2705 Playing pre-buffered ad, token=", token);
4964
+ }
4965
+ remaining = this.getRemainingAdMs();
4966
+ if (!(this.expectedAdBreakDurationMs == null || remaining >= this.MIN_AD_REMAINING_MS)) return [
4456
4967
  3,
4457
- 4
4968
+ 6
4458
4969
  ];
4459
- this.consecutiveFailures = 0;
4970
+ this.currentAdIndex++;
4460
4971
  if (this.config.licenseKey) {
4461
4972
  sendAdLoadedTracking(this.config.licenseKey, {
4462
4973
  source: this.getAdSource(),
4463
4974
  timestamp: /* @__PURE__ */ new Date().toISOString()
4464
4975
  });
4465
4976
  }
4466
- if (this.config.debugAdTiming) {
4467
- console.log("[CONTINUOUS-FETCH] \u2705 First ad request successful, starting playback");
4468
- }
4469
- this.currentAdIndex++;
4977
+ _state.label = 1;
4978
+ case 1:
4979
+ _state.trys.push([
4980
+ 1,
4981
+ 3,
4982
+ ,
4983
+ 5
4984
+ ]);
4470
4985
  return [
4471
4986
  4,
4472
- this.adLayer.playAd(bids)
4987
+ this.adLayer.playPreloaded(token)
4473
4988
  ];
4474
- case 3:
4989
+ case 2:
4475
4990
  _state.sent();
4476
4991
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
4477
4992
  this.scheduleAdStopCountdown(this.getRemainingAdMs());
4478
4993
  }
4479
- adVolume = state.muted ? 1 : state.volume;
4480
4994
  this.adLayer.setAdVolume(adVolume);
4481
4995
  return [
4482
4996
  3,
4483
- 6
4997
+ 5
4484
4998
  ];
4485
- case 4:
4999
+ case 3:
5000
+ err = _state.sent();
5001
+ if (this.config.debugAdTiming) console.warn("[CONTINUOUS-FETCH] playPreloaded failed:", err);
4486
5002
  this.consecutiveFailures++;
4487
5003
  return [
4488
5004
  4,
4489
5005
  this.showPlaceholderAndWaitForAds()
4490
5006
  ];
4491
- case 5:
5007
+ case 4:
4492
5008
  _state.sent();
4493
- _state.label = 6;
4494
- case 6:
4495
5009
  return [
4496
5010
  3,
4497
- 9
5011
+ 5
4498
5012
  ];
4499
- case 7:
4500
- error = _state.sent();
5013
+ case 5:
5014
+ return [
5015
+ 3,
5016
+ 8
5017
+ ];
5018
+ case 6:
4501
5019
  if (this.config.debugAdTiming) {
4502
- console.warn("[CONTINUOUS-FETCH] \u26A0\uFE0F First ad request failed:", error);
5020
+ console.log("[CONTINUOUS-FETCH] Skipping ad: only", remaining, "ms left in break");
4503
5021
  }
4504
- this.consecutiveFailures++;
5022
+ this.adLayer.cancelPreload(token);
4505
5023
  return [
4506
5024
  4,
4507
5025
  this.showPlaceholderAndWaitForAds()
4508
5026
  ];
4509
- case 8:
5027
+ case 7:
4510
5028
  _state.sent();
4511
- return [
4512
- 3,
4513
- 9
4514
- ];
4515
- case 9:
5029
+ _state.label = 8;
5030
+ case 8:
4516
5031
  this.startContinuousFetchLoop();
4517
5032
  return [
4518
5033
  2
@@ -4646,6 +5161,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4646
5161
  3
4647
5162
  ];
4648
5163
  this.pendingNextAdBids = bids;
5164
+ this.totalAdsInBreak = Math.max(this.totalAdsInBreak, this.currentAdIndex + this.preloadedTokens.length);
4649
5165
  return [
4650
5166
  3,
4651
5167
  5
@@ -4984,6 +5500,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4984
5500
  this.stopFillerBreakTimer();
4985
5501
  this.hidePlaceholderLayer();
4986
5502
  this.clearPendingAdBreak();
5503
+ this.cancelAndClearPreloadedTokens();
4987
5504
  this.pendingNextAdBids = null;
4988
5505
  if (this.isShowingPlaceholder) {
4989
5506
  this.adLayer.hidePlaceholder();
@@ -5435,6 +5952,7 @@ var CRITICAL_PROPS = [
5435
5952
  "driftToleranceMs"
5436
5953
  ];
5437
5954
  var CONTROLS_HIDE_DELAY = 3e3;
5955
+ var DEFAULT_PLAYER_ASPECT_RATIO = 16 / 9;
5438
5956
  var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5439
5957
  var src = props.src, autoplay = props.autoplay, muted = props.muted, lowLatencyMode = props.lowLatencyMode, allowNativeHls = props.allowNativeHls, driftToleranceMs = props.driftToleranceMs, immediateManifestAds = props.immediateManifestAds, debugAdTiming = props.debugAdTiming, showCustomControls = props.showCustomControls, hideLoadingIndicator = props.hideLoadingIndicator, onVolumeToggle = props.onVolumeToggle, onFullscreenToggle = props.onFullscreenToggle, onControlClick = props.onControlClick, onReady = props.onReady, wrapperClassName = props.wrapperClassName, wrapperStyle = props.wrapperStyle, className = props.className, style = props.style, controls = props.controls, playsInline = props.playsInline, preload = props.preload, poster = props.poster, children = props.children, licenseKey = props.licenseKey, minSegmentsBeforePlay = props.minSegmentsBeforePlay, disablePrebid = props.disablePrebid, disableFiller = props.disableFiller, restVideoAttrs = _object_without_properties(props, [
5440
5958
  "src",
@@ -5492,6 +6010,7 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5492
6010
  var _import_react_default_useState15 = _sliced_to_array(import_react.default.useState(true), 2), controlsVisible = _import_react_default_useState15[0], setControlsVisible = _import_react_default_useState15[1];
5493
6011
  var _import_react_default_useState16 = _sliced_to_array(import_react.default.useState(typeof window !== "undefined" ? window.innerWidth : 1920), 2), viewportWidth = _import_react_default_useState16[0], setViewportWidth = _import_react_default_useState16[1];
5494
6012
  var _import_react_default_useState17 = _sliced_to_array(import_react.default.useState(typeof window !== "undefined" ? window.innerHeight > window.innerWidth : false), 2), isPortrait = _import_react_default_useState17[0], setIsPortrait = _import_react_default_useState17[1];
6013
+ var _import_react_default_useState18 = _sliced_to_array(import_react.default.useState(DEFAULT_PLAYER_ASPECT_RATIO), 2), playerAspectRatio = _import_react_default_useState18[0], setPlayerAspectRatio = _import_react_default_useState18[1];
5495
6014
  var getResponsiveScale = function getResponsiveScale() {
5496
6015
  if (viewportWidth < 480) return 0.7;
5497
6016
  if (viewportWidth < 768) return 0.8;
@@ -5733,6 +6252,13 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5733
6252
  }, []);
5734
6253
  (0, import_react.useEffect)(function() {
5735
6254
  if (!videoRef.current) return;
6255
+ var handleLoadedMetadata = function handleLoadedMetadata() {
6256
+ var video2 = videoRef.current;
6257
+ if (!video2) return;
6258
+ if (video2.videoWidth > 0 && video2.videoHeight > 0) {
6259
+ setPlayerAspectRatio(video2.videoWidth / video2.videoHeight);
6260
+ }
6261
+ };
5736
6262
  var handleCanPlay = function handleCanPlay() {
5737
6263
  setIsLoading(false);
5738
6264
  if (bufferingTimeoutRef.current) {
@@ -5779,6 +6305,8 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5779
6305
  setShowCenterPlay(true);
5780
6306
  };
5781
6307
  var video = videoRef.current;
6308
+ handleLoadedMetadata();
6309
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
5782
6310
  video.addEventListener("canplay", handleCanPlay);
5783
6311
  video.addEventListener("canplaythrough", handleCanPlayThrough);
5784
6312
  video.addEventListener("waiting", handleWaiting);
@@ -5793,6 +6321,7 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5793
6321
  clearTimeout(bufferingTimeoutRef.current);
5794
6322
  bufferingTimeoutRef.current = null;
5795
6323
  }
6324
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
5796
6325
  video.removeEventListener("canplay", handleCanPlay);
5797
6326
  video.removeEventListener("canplaythrough", handleCanPlayThrough);
5798
6327
  video.removeEventListener("waiting", handleWaiting);
@@ -5803,6 +6332,11 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5803
6332
  }, [
5804
6333
  debugAdTiming
5805
6334
  ]);
6335
+ (0, import_react.useEffect)(function() {
6336
+ setPlayerAspectRatio(DEFAULT_PLAYER_ASPECT_RATIO);
6337
+ }, [
6338
+ src
6339
+ ]);
5806
6340
  (0, import_react.useEffect)(function() {
5807
6341
  return function() {
5808
6342
  if (controlsTimerRef.current) {
@@ -5828,7 +6362,7 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5828
6362
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, {
5829
6363
  children: [
5830
6364
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", {
5831
- children: "\n @keyframes sc-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n @keyframes sc-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n }\n @keyframes sc-fade-in {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .sc-wrapper:fullscreen,\n .sc-wrapper:has(*:fullscreen) {\n border-radius: 0 !important;\n box-shadow: none !important;\n width: 100vw !important;\n height: 100vh !important;\n max-width: 100vw !important;\n max-height: 100vh !important;\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n z-index: 999999 !important;\n background: #000 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n }\n .sc-ctrl-btn {\n background: none;\n border: none;\n color: #fff;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n padding: 8px;\n transition: background 0.15s ease, opacity 0.15s ease;\n opacity: 0.9;\n }\n .sc-ctrl-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.1);\n }\n .sc-ctrl-btn:active {\n opacity: 0.7;\n }\n .sc-controls-bar {\n transition: opacity 0.35s ease, transform 0.35s ease;\n }\n .sc-progress-track:hover .sc-progress-thumb {\n transform: translate(-50%, -50%) scale(1) !important;\n }\n .sc-loading-hidden .sc-loading-indicator {\n display: none !important;\n }\n "
6365
+ children: "\n @keyframes sc-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n @keyframes sc-loading-glow {\n 0%, 100% { opacity: 0.85; transform: scale(1); }\n 50% { opacity: 1; transform: scale(1.05); }\n }\n @keyframes sc-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n }\n @keyframes sc-fade-in {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .sc-wrapper:fullscreen,\n .sc-wrapper:has(*:fullscreen) {\n border-radius: 0 !important;\n box-shadow: none !important;\n width: 100vw !important;\n height: 100vh !important;\n max-width: 100vw !important;\n max-height: 100vh !important;\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n z-index: 999999 !important;\n background: #000 !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n }\n .sc-ctrl-btn {\n background: none;\n border: none;\n color: #fff;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n padding: 8px;\n transition: background 0.15s ease, opacity 0.15s ease;\n opacity: 0.9;\n }\n .sc-ctrl-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.1);\n }\n .sc-ctrl-btn:active {\n opacity: 0.7;\n }\n .sc-controls-bar {\n transition: opacity 0.35s ease, transform 0.35s ease;\n }\n .sc-progress-track:hover .sc-progress-thumb {\n transform: translate(-50%, -50%) scale(1) !important;\n }\n .sc-loading-hidden .sc-loading-indicator {\n display: none !important;\n }\n "
5832
6366
  }),
5833
6367
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
5834
6368
  ref: wrapperRef,
@@ -5846,6 +6380,7 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5846
6380
  width: isFullscreen ? "100vw" : "100%",
5847
6381
  height: isFullscreen ? "100vh" : "auto",
5848
6382
  minHeight: isFullscreen ? "100vh" : "auto",
6383
+ aspectRatio: isFullscreen ? void 0 : playerAspectRatio,
5849
6384
  maxWidth: isFullscreen ? "100vw" : "100%",
5850
6385
  maxHeight: isFullscreen ? "100vh" : "none",
5851
6386
  zIndex: isFullscreen ? 999999 : void 0,
@@ -5860,7 +6395,7 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5860
6395
  style: _object_spread({
5861
6396
  display: "block",
5862
6397
  width: "100%",
5863
- height: isFullscreen ? "100%" : "auto",
6398
+ height: "100%",
5864
6399
  maxWidth: "100%",
5865
6400
  maxHeight: isFullscreen ? "100%" : "none",
5866
6401
  objectFit: isFullscreen ? "cover" : "contain",
@@ -5874,18 +6409,44 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
5874
6409
  }, restVideoAttrs), {
5875
6410
  children: children
5876
6411
  })),
5877
- (isLoading || isBuffering) && !hideLoadingIndicator && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_fa.FaSpinner, {
6412
+ (isLoading || isBuffering) && !hideLoadingIndicator && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
5878
6413
  className: "sc-loading-indicator",
5879
- size: 40,
5880
- color: "rgba(255, 255, 255, 0.85)",
5881
6414
  style: {
5882
6415
  position: "absolute",
5883
- top: "calc(50% - 20px)",
5884
- left: "calc(50% - 20px)",
6416
+ top: "50%",
6417
+ left: "50%",
6418
+ transform: "translate(-50%, -50%)",
5885
6419
  zIndex: 20,
5886
- animation: "sc-spin 0.9s linear infinite",
5887
- filter: "drop-shadow(0 2px 8px rgba(0, 0, 0, 0.6))"
5888
- }
6420
+ width: "".concat(Math.max(34, 38 * responsiveScale), "px"),
6421
+ height: "".concat(Math.max(34, 38 * responsiveScale), "px"),
6422
+ display: "flex",
6423
+ alignItems: "center",
6424
+ justifyContent: "center",
6425
+ animation: "sc-loading-glow 1.4s ease-in-out infinite",
6426
+ filter: "drop-shadow(0 6px 14px rgba(0, 0, 0, 0.55))"
6427
+ },
6428
+ children: [
6429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
6430
+ style: {
6431
+ position: "absolute",
6432
+ inset: 0,
6433
+ borderRadius: "50%",
6434
+ border: "3px solid rgba(255, 255, 255, 0.25)",
6435
+ borderTopColor: "#ff0000",
6436
+ borderRightColor: "rgba(255, 255, 255, 0.85)",
6437
+ animation: "sc-spin 0.8s linear infinite"
6438
+ }
6439
+ }),
6440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
6441
+ style: {
6442
+ width: "7px",
6443
+ height: "7px",
6444
+ borderRadius: "50%",
6445
+ background: "#ff0000",
6446
+ boxShadow: "0 0 10px rgba(255, 0, 0, 0.65)"
6447
+ }
6448
+ })
6449
+ ]
5889
6450
  }),
5890
6451
  showLicenseWarning && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
5891
6452
  style: {
@@ -6121,7 +6682,8 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
6121
6682
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
6122
6683
  style: {
6123
6684
  display: "flex",
6124
- alignItems: "center"
6685
+ alignItems: "center",
6686
+ paddingRight: "".concat(6 * responsiveScale, "px")
6125
6687
  },
6126
6688
  onMouseEnter: function onMouseEnter() {
6127
6689
  return setShowVolumeSlider(true);
@@ -6152,13 +6714,13 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
6152
6714
  }),
6153
6715
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
6154
6716
  style: {
6155
- width: showVolumeSlider ? "".concat(62 * responsiveScale, "px") : "0px",
6717
+ width: showVolumeSlider ? "".concat(68 * responsiveScale, "px") : "0px",
6156
6718
  overflow: "hidden",
6157
6719
  transition: "width 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
6158
6720
  display: "flex",
6159
6721
  alignItems: "center",
6160
- paddingLeft: showVolumeSlider ? "2px" : "0",
6161
- paddingRight: showVolumeSlider ? "4px" : "0"
6722
+ paddingLeft: showVolumeSlider ? "".concat(3 * responsiveScale, "px") : "0",
6723
+ paddingRight: showVolumeSlider ? "".concat(8 * responsiveScale, "px") : "0"
6162
6724
  },
6163
6725
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
6164
6726
  style: {
@@ -6405,7 +6967,8 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
6405
6967
  alignItems: "center",
6406
6968
  background: "rgba(0, 0, 0, 0.6)",
6407
6969
  borderRadius: "".concat(18 * responsiveScale, "px"),
6408
- padding: "2px"
6970
+ padding: "2px",
6971
+ paddingRight: "".concat(8 * responsiveScale, "px")
6409
6972
  },
6410
6973
  onMouseEnter: function onMouseEnter() {
6411
6974
  return setShowVolumeSlider(true);
@@ -6434,13 +6997,13 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(function(props) {
6434
6997
  }),
6435
6998
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
6436
6999
  style: {
6437
- width: showVolumeSlider ? "".concat(62 * responsiveScale, "px") : "0px",
7000
+ width: showVolumeSlider ? "".concat(68 * responsiveScale, "px") : "0px",
6438
7001
  overflow: "hidden",
6439
7002
  transition: "width 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
6440
7003
  display: "flex",
6441
7004
  alignItems: "center",
6442
- paddingLeft: showVolumeSlider ? "2px" : "0",
6443
- paddingRight: showVolumeSlider ? "6px" : "0"
7005
+ paddingLeft: showVolumeSlider ? "".concat(3 * responsiveScale, "px") : "0",
7006
+ paddingRight: showVolumeSlider ? "".concat(10 * responsiveScale, "px") : "0"
6444
7007
  },
6445
7008
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
6446
7009
  style: {