stormcloud-video-player 0.2.4 → 0.2.5

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
@@ -127,6 +127,8 @@ function createImaController(video, options) {
127
127
  let adsLoadedPromise;
128
128
  let adsLoadedResolve;
129
129
  let adsLoadedReject;
130
+ let currentAdDuration = 0;
131
+ let preloadedAds = [];
130
132
  function makeAdsRequest(google, vastTagUrl) {
131
133
  const adsRequest = new google.ima.AdsRequest();
132
134
  adsRequest.adTagUrl = vastTagUrl;
@@ -233,6 +235,7 @@ function createImaController(video, options) {
233
235
  } catch {
234
236
  }
235
237
  adPlaying = false;
238
+ currentAdDuration = 0;
236
239
  video.muted = originalMutedState;
237
240
  if (adContainerEl)
238
241
  adContainerEl.style.pointerEvents = "none";
@@ -304,9 +307,22 @@ function createImaController(video, options) {
304
307
  emit("content_resume");
305
308
  }
306
309
  );
310
+ adsManager.addEventListener(AdEvent.STARTED, (adEvent) => {
311
+ console.log("[IMA] Ad started");
312
+ try {
313
+ const ad = adEvent.getAd();
314
+ if (ad && ad.getDuration) {
315
+ currentAdDuration = ad.getDuration();
316
+ console.log(`[IMA] Ad duration: ${currentAdDuration}s`);
317
+ }
318
+ } catch (error) {
319
+ console.warn("[IMA] Could not get ad duration:", error);
320
+ }
321
+ });
307
322
  adsManager.addEventListener(AdEvent.ALL_ADS_COMPLETED, () => {
308
323
  console.log("[IMA] All ads completed");
309
324
  adPlaying = false;
325
+ currentAdDuration = 0;
310
326
  video.muted = originalMutedState;
311
327
  if (adContainerEl) adContainerEl.style.pointerEvents = "none";
312
328
  if (!options?.continueLiveStreamDuringAds) {
@@ -491,6 +507,84 @@ function createImaController(video, options) {
491
507
  }
492
508
  }
493
509
  return 1;
510
+ },
511
+ getAdDuration() {
512
+ return currentAdDuration;
513
+ },
514
+ async preloadAds(vastTagUrls) {
515
+ console.log(`[IMA] Preloading ${vastTagUrls.length} ads`);
516
+ const adInfos = [];
517
+ for (const vastTagUrl of vastTagUrls) {
518
+ try {
519
+ await ensureImaLoaded();
520
+ const google = window.google;
521
+ const tempAdsLoader = new google.ima.AdsLoader(adDisplayContainer);
522
+ const adInfo = await new Promise((resolve, reject) => {
523
+ const timeout = setTimeout(() => {
524
+ reject(new Error("Preload timeout"));
525
+ }, 5e3);
526
+ tempAdsLoader.addEventListener(
527
+ google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
528
+ (evt) => {
529
+ clearTimeout(timeout);
530
+ try {
531
+ const tempAdsManager = evt.getAdsManager(video);
532
+ let duration = 30;
533
+ try {
534
+ const ads = tempAdsManager.getCuePoints?.() || [];
535
+ if (ads.length > 0) {
536
+ duration = 15;
537
+ }
538
+ } catch {
539
+ }
540
+ tempAdsManager.destroy();
541
+ resolve({
542
+ duration,
543
+ vastTagUrl,
544
+ isPreloaded: true
545
+ });
546
+ } catch (error) {
547
+ clearTimeout(timeout);
548
+ reject(error);
549
+ }
550
+ }
551
+ );
552
+ tempAdsLoader.addEventListener(
553
+ google.ima.AdErrorEvent.Type.AD_ERROR,
554
+ (errorEvent) => {
555
+ clearTimeout(timeout);
556
+ console.warn(
557
+ `[IMA] Preload error for ${vastTagUrl}:`,
558
+ errorEvent.getError()
559
+ );
560
+ resolve({
561
+ duration: 30,
562
+ vastTagUrl,
563
+ isPreloaded: false
564
+ });
565
+ }
566
+ );
567
+ const adsRequest = new google.ima.AdsRequest();
568
+ adsRequest.adTagUrl = vastTagUrl;
569
+ tempAdsLoader.requestAds(adsRequest);
570
+ });
571
+ adInfos.push(adInfo);
572
+ tempAdsLoader.destroy();
573
+ } catch (error) {
574
+ console.warn(`[IMA] Failed to preload ad ${vastTagUrl}:`, error);
575
+ adInfos.push({
576
+ duration: 30,
577
+ vastTagUrl,
578
+ isPreloaded: false
579
+ });
580
+ }
581
+ }
582
+ preloadedAds = adInfos;
583
+ console.log(
584
+ `[IMA] Preloaded ${adInfos.length} ads with total duration:`,
585
+ adInfos.reduce((sum, ad) => sum + ad.duration, 0)
586
+ );
587
+ return adInfos;
494
588
  }
495
589
  };
496
590
  }
@@ -723,6 +817,8 @@ var StormcloudVideoPlayer = class {
723
817
  this.totalAdsInBreak = 0;
724
818
  this.showAds = false;
725
819
  this.isLiveStream = false;
820
+ this.preloadedAdInfo = [];
821
+ this.cumulativeAdDurationMs = 0;
726
822
  this.config = config;
727
823
  this.video = config.videoElement;
728
824
  this.ima = createImaController(this.video, {
@@ -894,16 +990,28 @@ var StormcloudVideoPlayer = class {
894
990
  this.ima.initialize();
895
991
  this.ima.on("all_ads_completed", () => {
896
992
  if (!this.inAdBreak) return;
993
+ const actualAdDuration = this.ima.getAdDuration();
994
+ if (actualAdDuration > 0) {
995
+ this.cumulativeAdDurationMs += actualAdDuration * 1e3;
996
+ if (this.config.debugAdTiming) {
997
+ console.log(
998
+ `[StormcloudVideoPlayer] Ad completed. Duration: ${actualAdDuration}s, Cumulative: ${this.cumulativeAdDurationMs}ms`
999
+ );
1000
+ }
1001
+ }
897
1002
  const remaining = this.getRemainingAdMs();
898
- if (remaining > 500 && this.adPodQueue.length > 0) {
1003
+ const shouldContinue = this.shouldContinueAdBreak(remaining);
1004
+ if (shouldContinue && this.adPodQueue.length > 0) {
899
1005
  const next = this.adPodQueue.shift();
900
1006
  this.currentAdIndex++;
901
1007
  this.playSingleAd(next).catch(() => {
902
1008
  });
1009
+ } else if (shouldContinue && this.canRequestMoreAds()) {
1010
+ this.requestAdditionalAds().catch(() => {
1011
+ this.endAdBreak();
1012
+ });
903
1013
  } else {
904
- this.currentAdIndex = 0;
905
- this.totalAdsInBreak = 0;
906
- this.showAds = false;
1014
+ this.endAdBreak();
907
1015
  }
908
1016
  });
909
1017
  this.ima.on("ad_error", () => {
@@ -912,11 +1020,16 @@ var StormcloudVideoPlayer = class {
912
1020
  }
913
1021
  if (!this.inAdBreak) return;
914
1022
  const remaining = this.getRemainingAdMs();
915
- if (remaining > 500 && this.adPodQueue.length > 0) {
1023
+ const shouldContinue = this.shouldContinueAdBreak(remaining);
1024
+ if (shouldContinue && this.adPodQueue.length > 0) {
916
1025
  const next = this.adPodQueue.shift();
917
1026
  this.currentAdIndex++;
918
1027
  this.playSingleAd(next).catch(() => {
919
1028
  });
1029
+ } else if (shouldContinue && this.canRequestMoreAds()) {
1030
+ this.requestAdditionalAds().catch(() => {
1031
+ this.handleAdFailure();
1032
+ });
920
1033
  } else {
921
1034
  this.handleAdFailure();
922
1035
  }
@@ -1407,6 +1520,25 @@ var StormcloudVideoPlayer = class {
1407
1520
  isShowingAds() {
1408
1521
  return this.showAds;
1409
1522
  }
1523
+ getAdBreakStats() {
1524
+ const remainingDurationMs = this.currentAdBreakTargetDurationMs != null ? Math.max(
1525
+ 0,
1526
+ this.currentAdBreakTargetDurationMs - this.cumulativeAdDurationMs
1527
+ ) : void 0;
1528
+ const estimatedFillRate = this.currentAdBreakTargetDurationMs != null && this.currentAdBreakTargetDurationMs > 0 ? this.cumulativeAdDurationMs / this.currentAdBreakTargetDurationMs * 100 : void 0;
1529
+ return {
1530
+ isInAdBreak: this.inAdBreak,
1531
+ currentAdIndex: this.currentAdIndex,
1532
+ totalAdsInBreak: this.totalAdsInBreak,
1533
+ targetDurationMs: this.currentAdBreakTargetDurationMs,
1534
+ cumulativeDurationMs: this.cumulativeAdDurationMs,
1535
+ estimatedFillRate,
1536
+ remainingDurationMs
1537
+ };
1538
+ }
1539
+ getPreloadedAdInfo() {
1540
+ return [...this.preloadedAdInfo];
1541
+ }
1410
1542
  getStreamType() {
1411
1543
  const url = this.config.src.toLowerCase();
1412
1544
  if (url.includes(".m3u8") || url.includes("/hls/") || url.includes("application/vnd.apple.mpegurl")) {
@@ -1441,59 +1573,59 @@ var StormcloudVideoPlayer = class {
1441
1573
  this.apiVastTagUrl = tag;
1442
1574
  }
1443
1575
  }
1444
- async handleAdStart(_marker) {
1576
+ async handleAdStart(marker) {
1445
1577
  const scheduled = this.findCurrentOrNextBreak(
1446
1578
  this.video.currentTime * 1e3
1447
1579
  );
1448
- const tags = this.selectVastTagsForBreak(scheduled);
1449
- let vastTagUrl;
1450
- let adsNumber = 1;
1451
- if (this.apiVastTagUrl) {
1452
- vastTagUrl = this.apiVastTagUrl;
1453
- if (this.vastConfig) {
1454
- const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1455
- if (isHls && this.vastConfig.cue_tones?.number_ads) {
1456
- adsNumber = this.vastConfig.cue_tones.number_ads;
1457
- } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1458
- adsNumber = this.vastConfig.timer_vod.number_ads;
1580
+ let targetDurationMs = this.expectedAdBreakDurationMs;
1581
+ if (!targetDurationMs && scheduled?.durationMs != null) {
1582
+ targetDurationMs = scheduled.durationMs;
1583
+ }
1584
+ if (!targetDurationMs && marker.durationSeconds != null) {
1585
+ targetDurationMs = marker.durationSeconds * 1e3;
1586
+ }
1587
+ this.currentAdBreakTargetDurationMs = targetDurationMs;
1588
+ this.cumulativeAdDurationMs = 0;
1589
+ if (this.config.debugAdTiming) {
1590
+ console.log(
1591
+ "[StormcloudVideoPlayer] Starting ad break with target duration:",
1592
+ {
1593
+ targetDurationMs,
1594
+ scte35Duration: marker.durationSeconds,
1595
+ scheduledDuration: scheduled?.durationMs
1459
1596
  }
1460
- }
1461
- this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
1462
- this.currentAdIndex = 0;
1463
- this.totalAdsInBreak = adsNumber;
1464
- if (this.config.debugAdTiming) {
1465
- console.log(
1466
- `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
1467
- vastTagUrl
1468
- );
1469
- }
1470
- } else if (tags && tags.length > 0) {
1471
- vastTagUrl = tags[0];
1472
- const rest = tags.slice(1);
1473
- this.adPodQueue = rest;
1474
- this.currentAdIndex = 0;
1475
- this.totalAdsInBreak = tags.length;
1476
- if (this.config.debugAdTiming) {
1477
- console.log(
1478
- "[StormcloudVideoPlayer] Using scheduled VAST tag:",
1479
- vastTagUrl
1480
- );
1481
- }
1482
- } else {
1597
+ );
1598
+ }
1599
+ const adQueue = await this.buildAdQueueForDuration(targetDurationMs);
1600
+ if (adQueue.length === 0) {
1483
1601
  if (this.config.debugAdTiming) {
1484
- console.log("[StormcloudVideoPlayer] No VAST tag available for ad");
1602
+ console.log("[StormcloudVideoPlayer] No ads available for ad break");
1485
1603
  }
1486
1604
  return;
1487
1605
  }
1488
- if (vastTagUrl) {
1489
- this.showAds = true;
1490
- this.currentAdIndex++;
1491
- await this.playSingleAd(vastTagUrl);
1606
+ this.adPodQueue = adQueue.slice(1);
1607
+ this.preloadedAdInfo = await this.getAdInfoForQueue(adQueue);
1608
+ this.currentAdIndex = 0;
1609
+ this.totalAdsInBreak = adQueue.length;
1610
+ this.showAds = true;
1611
+ if (this.config.debugAdTiming) {
1612
+ const totalEstimatedDuration = this.preloadedAdInfo.reduce(
1613
+ (sum, ad) => sum + ad.duration,
1614
+ 0
1615
+ );
1616
+ console.log("[StormcloudVideoPlayer] Ad queue built:", {
1617
+ totalAds: adQueue.length,
1618
+ estimatedTotalDuration: totalEstimatedDuration,
1619
+ targetDuration: targetDurationMs ? targetDurationMs / 1e3 : "unknown",
1620
+ fillRate: targetDurationMs ? totalEstimatedDuration * 1e3 / targetDurationMs : "unknown"
1621
+ });
1492
1622
  }
1493
- if (this.expectedAdBreakDurationMs == null && scheduled?.durationMs != null) {
1494
- this.expectedAdBreakDurationMs = scheduled.durationMs;
1623
+ this.currentAdIndex++;
1624
+ await this.playSingleAd(adQueue[0]);
1625
+ if (targetDurationMs != null) {
1626
+ this.expectedAdBreakDurationMs = targetDurationMs;
1495
1627
  this.currentAdBreakStartWallClockMs = this.currentAdBreakStartWallClockMs ?? Date.now();
1496
- this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
1628
+ this.scheduleAdStopCountdown(targetDurationMs);
1497
1629
  }
1498
1630
  }
1499
1631
  findCurrentOrNextBreak(nowMs) {
@@ -1610,25 +1742,89 @@ var StormcloudVideoPlayer = class {
1610
1742
  "[StormcloudVideoPlayer] Handling ad failure - resuming content"
1611
1743
  );
1612
1744
  }
1745
+ this.endAdBreak();
1746
+ if (this.video.paused) {
1747
+ this.video.play().catch(() => {
1748
+ if (this.config.debugAdTiming) {
1749
+ console.error(
1750
+ "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1751
+ );
1752
+ }
1753
+ });
1754
+ }
1755
+ }
1756
+ endAdBreak() {
1757
+ if (this.config.debugAdTiming) {
1758
+ const targetDuration = this.currentAdBreakTargetDurationMs ? this.currentAdBreakTargetDurationMs / 1e3 : "unknown";
1759
+ const actualDuration = this.cumulativeAdDurationMs / 1e3;
1760
+ const fillRate = this.currentAdBreakTargetDurationMs ? (this.cumulativeAdDurationMs / this.currentAdBreakTargetDurationMs * 100).toFixed(1) : "unknown";
1761
+ console.log("[StormcloudVideoPlayer] Ad break ended:", {
1762
+ targetDurationSeconds: targetDuration,
1763
+ actualDurationSeconds: actualDuration,
1764
+ fillRate: `${fillRate}%`,
1765
+ totalAdsPlayed: this.currentAdIndex
1766
+ });
1767
+ }
1613
1768
  this.inAdBreak = false;
1614
1769
  this.expectedAdBreakDurationMs = void 0;
1615
1770
  this.currentAdBreakStartWallClockMs = void 0;
1771
+ this.currentAdBreakTargetDurationMs = void 0;
1772
+ this.cumulativeAdDurationMs = 0;
1616
1773
  this.clearAdStartTimer();
1617
1774
  this.clearAdStopTimer();
1618
1775
  this.clearAdFailsafeTimer();
1619
1776
  this.adPodQueue = [];
1777
+ this.preloadedAdInfo = [];
1620
1778
  this.showAds = false;
1621
1779
  this.currentAdIndex = 0;
1622
1780
  this.totalAdsInBreak = 0;
1623
- if (this.video.paused) {
1624
- this.video.play().catch(() => {
1781
+ }
1782
+ shouldContinueAdBreak(remainingMs) {
1783
+ if (remainingMs > 500) {
1784
+ return true;
1785
+ }
1786
+ if (this.currentAdBreakTargetDurationMs) {
1787
+ const targetRemainingMs = this.currentAdBreakTargetDurationMs - this.cumulativeAdDurationMs;
1788
+ const minAdDuration = this.config.minAdDurationMs ?? 5e3;
1789
+ if (targetRemainingMs > minAdDuration) {
1625
1790
  if (this.config.debugAdTiming) {
1626
- console.error(
1627
- "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1791
+ console.log(
1792
+ `[StormcloudVideoPlayer] Target duration not filled, continuing. Remaining: ${targetRemainingMs}ms`
1628
1793
  );
1629
1794
  }
1630
- });
1795
+ return true;
1796
+ }
1797
+ }
1798
+ return false;
1799
+ }
1800
+ canRequestMoreAds() {
1801
+ const maxAdsPerBreak = this.config.maxAdsPerBreak ?? 10;
1802
+ if (this.currentAdIndex >= maxAdsPerBreak) {
1803
+ return false;
1804
+ }
1805
+ return !!this.apiVastTagUrl;
1806
+ }
1807
+ async requestAdditionalAds() {
1808
+ if (!this.currentAdBreakTargetDurationMs || !this.apiVastTagUrl) {
1809
+ throw new Error(
1810
+ "Cannot request additional ads without target duration and VAST URL"
1811
+ );
1631
1812
  }
1813
+ const remainingDurationMs = this.currentAdBreakTargetDurationMs - this.cumulativeAdDurationMs;
1814
+ const estimatedAdDurationMs = this.getEstimatedAdDuration() * 1e3;
1815
+ if (remainingDurationMs < estimatedAdDurationMs * 0.5) {
1816
+ throw new Error("Not enough time remaining for additional ads");
1817
+ }
1818
+ if (this.config.debugAdTiming) {
1819
+ console.log(
1820
+ `[StormcloudVideoPlayer] Requesting additional ads for remaining ${remainingDurationMs}ms`
1821
+ );
1822
+ }
1823
+ this.adPodQueue.push(this.apiVastTagUrl);
1824
+ this.totalAdsInBreak++;
1825
+ const next = this.adPodQueue.shift();
1826
+ this.currentAdIndex++;
1827
+ await this.playSingleAd(next);
1632
1828
  }
1633
1829
  startAdFailsafeTimer() {
1634
1830
  this.clearAdFailsafeTimer();
@@ -1662,6 +1858,109 @@ var StormcloudVideoPlayer = class {
1662
1858
  }
1663
1859
  return [b.vastTagUrl];
1664
1860
  }
1861
+ async buildAdQueueForDuration(targetDurationMs) {
1862
+ const adQueue = [];
1863
+ let baseVastTagUrl = this.apiVastTagUrl;
1864
+ if (!baseVastTagUrl) {
1865
+ const scheduled = this.findCurrentOrNextBreak(
1866
+ this.video.currentTime * 1e3
1867
+ );
1868
+ const scheduledTags = this.selectVastTagsForBreak(scheduled);
1869
+ if (scheduledTags && scheduledTags.length > 0) {
1870
+ baseVastTagUrl = scheduledTags[0];
1871
+ }
1872
+ }
1873
+ if (!baseVastTagUrl) {
1874
+ return adQueue;
1875
+ }
1876
+ if (!targetDurationMs) {
1877
+ let adsNumber = 1;
1878
+ if (this.vastConfig) {
1879
+ const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1880
+ if (isHls && this.vastConfig.cue_tones?.number_ads) {
1881
+ adsNumber = this.vastConfig.cue_tones.number_ads;
1882
+ } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1883
+ adsNumber = this.vastConfig.timer_vod.number_ads;
1884
+ }
1885
+ }
1886
+ return new Array(adsNumber).fill(baseVastTagUrl);
1887
+ }
1888
+ const targetDurationSeconds = targetDurationMs / 1e3;
1889
+ let cumulativeDurationSeconds = 0;
1890
+ const maxAdsToTry = 10;
1891
+ let adsAdded = 0;
1892
+ if (this.config.debugAdTiming) {
1893
+ console.log(
1894
+ `[StormcloudVideoPlayer] Attempting to fill ${targetDurationSeconds}s with ads`
1895
+ );
1896
+ }
1897
+ while (cumulativeDurationSeconds < targetDurationSeconds && adsAdded < maxAdsToTry) {
1898
+ adQueue.push(baseVastTagUrl);
1899
+ adsAdded++;
1900
+ const estimatedAdDuration = this.getEstimatedAdDuration();
1901
+ cumulativeDurationSeconds += estimatedAdDuration;
1902
+ if (this.config.debugAdTiming) {
1903
+ console.log(
1904
+ `[StormcloudVideoPlayer] Added ad ${adsAdded}, cumulative duration: ${cumulativeDurationSeconds}s`
1905
+ );
1906
+ }
1907
+ const remainingDuration = targetDurationSeconds - cumulativeDurationSeconds;
1908
+ const toleranceSeconds = (this.config.adBreakGapToleranceMs ?? 2e3) / 1e3;
1909
+ if (remainingDuration < estimatedAdDuration && remainingDuration >= -toleranceSeconds) {
1910
+ if (this.config.debugAdTiming) {
1911
+ console.log(
1912
+ `[StormcloudVideoPlayer] Within tolerance, adding final ad. Overage: ${-remainingDuration}s`
1913
+ );
1914
+ }
1915
+ break;
1916
+ }
1917
+ if (remainingDuration < estimatedAdDuration && remainingDuration < -toleranceSeconds) {
1918
+ if (this.config.debugAdTiming) {
1919
+ console.log(
1920
+ `[StormcloudVideoPlayer] Would exceed duration by too much, stopping at ${adsAdded} ads`
1921
+ );
1922
+ }
1923
+ break;
1924
+ }
1925
+ }
1926
+ return adQueue;
1927
+ }
1928
+ getEstimatedAdDuration() {
1929
+ if (this.vastConfig) {
1930
+ return 15;
1931
+ }
1932
+ return 30;
1933
+ }
1934
+ async getAdInfoForQueue(adQueue) {
1935
+ if (this.config.enableAdPreloading === false) {
1936
+ if (this.config.debugAdTiming) {
1937
+ console.log(
1938
+ "[StormcloudVideoPlayer] Ad preloading disabled, using estimates"
1939
+ );
1940
+ }
1941
+ return adQueue.map((vastTagUrl) => ({
1942
+ duration: this.getEstimatedAdDuration(),
1943
+ vastTagUrl,
1944
+ isPreloaded: false
1945
+ }));
1946
+ }
1947
+ try {
1948
+ const adInfos = await this.ima.preloadAds(adQueue);
1949
+ return adInfos;
1950
+ } catch (error) {
1951
+ if (this.config.debugAdTiming) {
1952
+ console.warn(
1953
+ "[StormcloudVideoPlayer] Failed to preload ads, using estimates:",
1954
+ error
1955
+ );
1956
+ }
1957
+ return adQueue.map((vastTagUrl) => ({
1958
+ duration: this.getEstimatedAdDuration(),
1959
+ vastTagUrl,
1960
+ isPreloaded: false
1961
+ }));
1962
+ }
1963
+ }
1665
1964
  getRemainingAdMs() {
1666
1965
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
1667
1966
  return 0;
@@ -1832,6 +2131,7 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
1832
2131
  const [isLoading, setIsLoading] = import_react.default.useState(true);
1833
2132
  const [isBuffering, setIsBuffering] = import_react.default.useState(false);
1834
2133
  const [showCenterPlay, setShowCenterPlay] = import_react.default.useState(false);
2134
+ const [showLicenseWarning, setShowLicenseWarning] = import_react.default.useState(false);
1835
2135
  const formatTime = (seconds) => {
1836
2136
  if (!isFinite(seconds)) return "0:00:00";
1837
2137
  const hours = Math.floor(seconds / 3600);
@@ -1889,6 +2189,15 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
1889
2189
  if (typeof window === "undefined") return;
1890
2190
  const el = videoRef.current;
1891
2191
  if (!el || !src) return;
2192
+ if (!licenseKey) {
2193
+ setShowLicenseWarning(true);
2194
+ setIsLoading(false);
2195
+ console.warn(
2196
+ "StormcloudVideoPlayer: License key is required but not provided. Please set the licenseKey prop to use the player."
2197
+ );
2198
+ return;
2199
+ }
2200
+ setShowLicenseWarning(false);
1892
2201
  if (playerRef.current) {
1893
2202
  try {
1894
2203
  playerRef.current.destroy();
@@ -2212,7 +2521,60 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
2212
2521
  )
2213
2522
  }
2214
2523
  ),
2215
- showCenterPlay && !isLoading && !isBuffering && !adStatus.showAds && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2524
+ showLicenseWarning && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2525
+ "div",
2526
+ {
2527
+ style: {
2528
+ position: "absolute",
2529
+ top: "50%",
2530
+ left: "50%",
2531
+ transform: "translate(-50%, -50%)",
2532
+ zIndex: 25,
2533
+ background: "linear-gradient(135deg, rgba(220, 38, 38, 0.95) 0%, rgba(185, 28, 28, 0.9) 100%)",
2534
+ color: "white",
2535
+ padding: "24px 32px",
2536
+ borderRadius: "16px",
2537
+ backdropFilter: "blur(20px)",
2538
+ border: "2px solid rgba(255, 255, 255, 0.2)",
2539
+ boxShadow: "0 20px 60px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255, 255, 255, 0.2)",
2540
+ textAlign: "center",
2541
+ maxWidth: "400px",
2542
+ margin: "0 16px"
2543
+ },
2544
+ children: [
2545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2546
+ "div",
2547
+ {
2548
+ style: {
2549
+ fontSize: "20px",
2550
+ fontWeight: "bold",
2551
+ marginBottom: "12px",
2552
+ color: "#ffffff",
2553
+ textShadow: "0 2px 4px rgba(0, 0, 0, 0.5)"
2554
+ },
2555
+ children: "License Key Required"
2556
+ }
2557
+ ),
2558
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2559
+ "div",
2560
+ {
2561
+ style: {
2562
+ fontSize: "14px",
2563
+ lineHeight: "1.5",
2564
+ color: "rgba(255, 255, 255, 0.9)",
2565
+ textShadow: "0 1px 2px rgba(0, 0, 0, 0.3)"
2566
+ },
2567
+ children: [
2568
+ "Please provide a valid license key to use the Stormcloud Video Player.",
2569
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
2570
+ "Contact your administrator for licensing information."
2571
+ ]
2572
+ }
2573
+ )
2574
+ ]
2575
+ }
2576
+ ),
2577
+ showCenterPlay && !isLoading && !isBuffering && !showLicenseWarning && !adStatus.showAds && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2216
2578
  "div",
2217
2579
  {
2218
2580
  onClick: handleCenterPlayClick,
@@ -2263,7 +2625,7 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
2263
2625
  )
2264
2626
  }
2265
2627
  ),
2266
- shouldShowEnhancedControls ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2628
+ shouldShowEnhancedControls && !showLicenseWarning ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2267
2629
  "div",
2268
2630
  {
2269
2631
  style: {
@@ -2728,7 +3090,7 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
2728
3090
  )
2729
3091
  ]
2730
3092
  }
2731
- ) }) : showCustomControls && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
3093
+ ) }) : showCustomControls && !showLicenseWarning && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2732
3094
  "div",
2733
3095
  {
2734
3096
  style: {