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.js CHANGED
@@ -68,6 +68,8 @@ function createImaController(video, options) {
68
68
  let adsLoadedPromise;
69
69
  let adsLoadedResolve;
70
70
  let adsLoadedReject;
71
+ let currentAdDuration = 0;
72
+ let preloadedAds = [];
71
73
  function makeAdsRequest(google, vastTagUrl) {
72
74
  const adsRequest = new google.ima.AdsRequest();
73
75
  adsRequest.adTagUrl = vastTagUrl;
@@ -174,6 +176,7 @@ function createImaController(video, options) {
174
176
  } catch {
175
177
  }
176
178
  adPlaying = false;
179
+ currentAdDuration = 0;
177
180
  video.muted = originalMutedState;
178
181
  if (adContainerEl)
179
182
  adContainerEl.style.pointerEvents = "none";
@@ -245,9 +248,22 @@ function createImaController(video, options) {
245
248
  emit("content_resume");
246
249
  }
247
250
  );
251
+ adsManager.addEventListener(AdEvent.STARTED, (adEvent) => {
252
+ console.log("[IMA] Ad started");
253
+ try {
254
+ const ad = adEvent.getAd();
255
+ if (ad && ad.getDuration) {
256
+ currentAdDuration = ad.getDuration();
257
+ console.log(`[IMA] Ad duration: ${currentAdDuration}s`);
258
+ }
259
+ } catch (error) {
260
+ console.warn("[IMA] Could not get ad duration:", error);
261
+ }
262
+ });
248
263
  adsManager.addEventListener(AdEvent.ALL_ADS_COMPLETED, () => {
249
264
  console.log("[IMA] All ads completed");
250
265
  adPlaying = false;
266
+ currentAdDuration = 0;
251
267
  video.muted = originalMutedState;
252
268
  if (adContainerEl) adContainerEl.style.pointerEvents = "none";
253
269
  if (!options?.continueLiveStreamDuringAds) {
@@ -432,6 +448,84 @@ function createImaController(video, options) {
432
448
  }
433
449
  }
434
450
  return 1;
451
+ },
452
+ getAdDuration() {
453
+ return currentAdDuration;
454
+ },
455
+ async preloadAds(vastTagUrls) {
456
+ console.log(`[IMA] Preloading ${vastTagUrls.length} ads`);
457
+ const adInfos = [];
458
+ for (const vastTagUrl of vastTagUrls) {
459
+ try {
460
+ await ensureImaLoaded();
461
+ const google = window.google;
462
+ const tempAdsLoader = new google.ima.AdsLoader(adDisplayContainer);
463
+ const adInfo = await new Promise((resolve, reject) => {
464
+ const timeout = setTimeout(() => {
465
+ reject(new Error("Preload timeout"));
466
+ }, 5e3);
467
+ tempAdsLoader.addEventListener(
468
+ google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
469
+ (evt) => {
470
+ clearTimeout(timeout);
471
+ try {
472
+ const tempAdsManager = evt.getAdsManager(video);
473
+ let duration = 30;
474
+ try {
475
+ const ads = tempAdsManager.getCuePoints?.() || [];
476
+ if (ads.length > 0) {
477
+ duration = 15;
478
+ }
479
+ } catch {
480
+ }
481
+ tempAdsManager.destroy();
482
+ resolve({
483
+ duration,
484
+ vastTagUrl,
485
+ isPreloaded: true
486
+ });
487
+ } catch (error) {
488
+ clearTimeout(timeout);
489
+ reject(error);
490
+ }
491
+ }
492
+ );
493
+ tempAdsLoader.addEventListener(
494
+ google.ima.AdErrorEvent.Type.AD_ERROR,
495
+ (errorEvent) => {
496
+ clearTimeout(timeout);
497
+ console.warn(
498
+ `[IMA] Preload error for ${vastTagUrl}:`,
499
+ errorEvent.getError()
500
+ );
501
+ resolve({
502
+ duration: 30,
503
+ vastTagUrl,
504
+ isPreloaded: false
505
+ });
506
+ }
507
+ );
508
+ const adsRequest = new google.ima.AdsRequest();
509
+ adsRequest.adTagUrl = vastTagUrl;
510
+ tempAdsLoader.requestAds(adsRequest);
511
+ });
512
+ adInfos.push(adInfo);
513
+ tempAdsLoader.destroy();
514
+ } catch (error) {
515
+ console.warn(`[IMA] Failed to preload ad ${vastTagUrl}:`, error);
516
+ adInfos.push({
517
+ duration: 30,
518
+ vastTagUrl,
519
+ isPreloaded: false
520
+ });
521
+ }
522
+ }
523
+ preloadedAds = adInfos;
524
+ console.log(
525
+ `[IMA] Preloaded ${adInfos.length} ads with total duration:`,
526
+ adInfos.reduce((sum, ad) => sum + ad.duration, 0)
527
+ );
528
+ return adInfos;
435
529
  }
436
530
  };
437
531
  }
@@ -664,6 +758,8 @@ var StormcloudVideoPlayer = class {
664
758
  this.totalAdsInBreak = 0;
665
759
  this.showAds = false;
666
760
  this.isLiveStream = false;
761
+ this.preloadedAdInfo = [];
762
+ this.cumulativeAdDurationMs = 0;
667
763
  this.config = config;
668
764
  this.video = config.videoElement;
669
765
  this.ima = createImaController(this.video, {
@@ -835,16 +931,28 @@ var StormcloudVideoPlayer = class {
835
931
  this.ima.initialize();
836
932
  this.ima.on("all_ads_completed", () => {
837
933
  if (!this.inAdBreak) return;
934
+ const actualAdDuration = this.ima.getAdDuration();
935
+ if (actualAdDuration > 0) {
936
+ this.cumulativeAdDurationMs += actualAdDuration * 1e3;
937
+ if (this.config.debugAdTiming) {
938
+ console.log(
939
+ `[StormcloudVideoPlayer] Ad completed. Duration: ${actualAdDuration}s, Cumulative: ${this.cumulativeAdDurationMs}ms`
940
+ );
941
+ }
942
+ }
838
943
  const remaining = this.getRemainingAdMs();
839
- if (remaining > 500 && this.adPodQueue.length > 0) {
944
+ const shouldContinue = this.shouldContinueAdBreak(remaining);
945
+ if (shouldContinue && this.adPodQueue.length > 0) {
840
946
  const next = this.adPodQueue.shift();
841
947
  this.currentAdIndex++;
842
948
  this.playSingleAd(next).catch(() => {
843
949
  });
950
+ } else if (shouldContinue && this.canRequestMoreAds()) {
951
+ this.requestAdditionalAds().catch(() => {
952
+ this.endAdBreak();
953
+ });
844
954
  } else {
845
- this.currentAdIndex = 0;
846
- this.totalAdsInBreak = 0;
847
- this.showAds = false;
955
+ this.endAdBreak();
848
956
  }
849
957
  });
850
958
  this.ima.on("ad_error", () => {
@@ -853,11 +961,16 @@ var StormcloudVideoPlayer = class {
853
961
  }
854
962
  if (!this.inAdBreak) return;
855
963
  const remaining = this.getRemainingAdMs();
856
- if (remaining > 500 && this.adPodQueue.length > 0) {
964
+ const shouldContinue = this.shouldContinueAdBreak(remaining);
965
+ if (shouldContinue && this.adPodQueue.length > 0) {
857
966
  const next = this.adPodQueue.shift();
858
967
  this.currentAdIndex++;
859
968
  this.playSingleAd(next).catch(() => {
860
969
  });
970
+ } else if (shouldContinue && this.canRequestMoreAds()) {
971
+ this.requestAdditionalAds().catch(() => {
972
+ this.handleAdFailure();
973
+ });
861
974
  } else {
862
975
  this.handleAdFailure();
863
976
  }
@@ -1348,6 +1461,25 @@ var StormcloudVideoPlayer = class {
1348
1461
  isShowingAds() {
1349
1462
  return this.showAds;
1350
1463
  }
1464
+ getAdBreakStats() {
1465
+ const remainingDurationMs = this.currentAdBreakTargetDurationMs != null ? Math.max(
1466
+ 0,
1467
+ this.currentAdBreakTargetDurationMs - this.cumulativeAdDurationMs
1468
+ ) : void 0;
1469
+ const estimatedFillRate = this.currentAdBreakTargetDurationMs != null && this.currentAdBreakTargetDurationMs > 0 ? this.cumulativeAdDurationMs / this.currentAdBreakTargetDurationMs * 100 : void 0;
1470
+ return {
1471
+ isInAdBreak: this.inAdBreak,
1472
+ currentAdIndex: this.currentAdIndex,
1473
+ totalAdsInBreak: this.totalAdsInBreak,
1474
+ targetDurationMs: this.currentAdBreakTargetDurationMs,
1475
+ cumulativeDurationMs: this.cumulativeAdDurationMs,
1476
+ estimatedFillRate,
1477
+ remainingDurationMs
1478
+ };
1479
+ }
1480
+ getPreloadedAdInfo() {
1481
+ return [...this.preloadedAdInfo];
1482
+ }
1351
1483
  getStreamType() {
1352
1484
  const url = this.config.src.toLowerCase();
1353
1485
  if (url.includes(".m3u8") || url.includes("/hls/") || url.includes("application/vnd.apple.mpegurl")) {
@@ -1382,59 +1514,59 @@ var StormcloudVideoPlayer = class {
1382
1514
  this.apiVastTagUrl = tag;
1383
1515
  }
1384
1516
  }
1385
- async handleAdStart(_marker) {
1517
+ async handleAdStart(marker) {
1386
1518
  const scheduled = this.findCurrentOrNextBreak(
1387
1519
  this.video.currentTime * 1e3
1388
1520
  );
1389
- const tags = this.selectVastTagsForBreak(scheduled);
1390
- let vastTagUrl;
1391
- let adsNumber = 1;
1392
- if (this.apiVastTagUrl) {
1393
- vastTagUrl = this.apiVastTagUrl;
1394
- if (this.vastConfig) {
1395
- const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1396
- if (isHls && this.vastConfig.cue_tones?.number_ads) {
1397
- adsNumber = this.vastConfig.cue_tones.number_ads;
1398
- } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1399
- adsNumber = this.vastConfig.timer_vod.number_ads;
1521
+ let targetDurationMs = this.expectedAdBreakDurationMs;
1522
+ if (!targetDurationMs && scheduled?.durationMs != null) {
1523
+ targetDurationMs = scheduled.durationMs;
1524
+ }
1525
+ if (!targetDurationMs && marker.durationSeconds != null) {
1526
+ targetDurationMs = marker.durationSeconds * 1e3;
1527
+ }
1528
+ this.currentAdBreakTargetDurationMs = targetDurationMs;
1529
+ this.cumulativeAdDurationMs = 0;
1530
+ if (this.config.debugAdTiming) {
1531
+ console.log(
1532
+ "[StormcloudVideoPlayer] Starting ad break with target duration:",
1533
+ {
1534
+ targetDurationMs,
1535
+ scte35Duration: marker.durationSeconds,
1536
+ scheduledDuration: scheduled?.durationMs
1400
1537
  }
1401
- }
1402
- this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
1403
- this.currentAdIndex = 0;
1404
- this.totalAdsInBreak = adsNumber;
1405
- if (this.config.debugAdTiming) {
1406
- console.log(
1407
- `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
1408
- vastTagUrl
1409
- );
1410
- }
1411
- } else if (tags && tags.length > 0) {
1412
- vastTagUrl = tags[0];
1413
- const rest = tags.slice(1);
1414
- this.adPodQueue = rest;
1415
- this.currentAdIndex = 0;
1416
- this.totalAdsInBreak = tags.length;
1417
- if (this.config.debugAdTiming) {
1418
- console.log(
1419
- "[StormcloudVideoPlayer] Using scheduled VAST tag:",
1420
- vastTagUrl
1421
- );
1422
- }
1423
- } else {
1538
+ );
1539
+ }
1540
+ const adQueue = await this.buildAdQueueForDuration(targetDurationMs);
1541
+ if (adQueue.length === 0) {
1424
1542
  if (this.config.debugAdTiming) {
1425
- console.log("[StormcloudVideoPlayer] No VAST tag available for ad");
1543
+ console.log("[StormcloudVideoPlayer] No ads available for ad break");
1426
1544
  }
1427
1545
  return;
1428
1546
  }
1429
- if (vastTagUrl) {
1430
- this.showAds = true;
1431
- this.currentAdIndex++;
1432
- await this.playSingleAd(vastTagUrl);
1547
+ this.adPodQueue = adQueue.slice(1);
1548
+ this.preloadedAdInfo = await this.getAdInfoForQueue(adQueue);
1549
+ this.currentAdIndex = 0;
1550
+ this.totalAdsInBreak = adQueue.length;
1551
+ this.showAds = true;
1552
+ if (this.config.debugAdTiming) {
1553
+ const totalEstimatedDuration = this.preloadedAdInfo.reduce(
1554
+ (sum, ad) => sum + ad.duration,
1555
+ 0
1556
+ );
1557
+ console.log("[StormcloudVideoPlayer] Ad queue built:", {
1558
+ totalAds: adQueue.length,
1559
+ estimatedTotalDuration: totalEstimatedDuration,
1560
+ targetDuration: targetDurationMs ? targetDurationMs / 1e3 : "unknown",
1561
+ fillRate: targetDurationMs ? totalEstimatedDuration * 1e3 / targetDurationMs : "unknown"
1562
+ });
1433
1563
  }
1434
- if (this.expectedAdBreakDurationMs == null && scheduled?.durationMs != null) {
1435
- this.expectedAdBreakDurationMs = scheduled.durationMs;
1564
+ this.currentAdIndex++;
1565
+ await this.playSingleAd(adQueue[0]);
1566
+ if (targetDurationMs != null) {
1567
+ this.expectedAdBreakDurationMs = targetDurationMs;
1436
1568
  this.currentAdBreakStartWallClockMs = this.currentAdBreakStartWallClockMs ?? Date.now();
1437
- this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
1569
+ this.scheduleAdStopCountdown(targetDurationMs);
1438
1570
  }
1439
1571
  }
1440
1572
  findCurrentOrNextBreak(nowMs) {
@@ -1551,25 +1683,89 @@ var StormcloudVideoPlayer = class {
1551
1683
  "[StormcloudVideoPlayer] Handling ad failure - resuming content"
1552
1684
  );
1553
1685
  }
1686
+ this.endAdBreak();
1687
+ if (this.video.paused) {
1688
+ this.video.play().catch(() => {
1689
+ if (this.config.debugAdTiming) {
1690
+ console.error(
1691
+ "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1692
+ );
1693
+ }
1694
+ });
1695
+ }
1696
+ }
1697
+ endAdBreak() {
1698
+ if (this.config.debugAdTiming) {
1699
+ const targetDuration = this.currentAdBreakTargetDurationMs ? this.currentAdBreakTargetDurationMs / 1e3 : "unknown";
1700
+ const actualDuration = this.cumulativeAdDurationMs / 1e3;
1701
+ const fillRate = this.currentAdBreakTargetDurationMs ? (this.cumulativeAdDurationMs / this.currentAdBreakTargetDurationMs * 100).toFixed(1) : "unknown";
1702
+ console.log("[StormcloudVideoPlayer] Ad break ended:", {
1703
+ targetDurationSeconds: targetDuration,
1704
+ actualDurationSeconds: actualDuration,
1705
+ fillRate: `${fillRate}%`,
1706
+ totalAdsPlayed: this.currentAdIndex
1707
+ });
1708
+ }
1554
1709
  this.inAdBreak = false;
1555
1710
  this.expectedAdBreakDurationMs = void 0;
1556
1711
  this.currentAdBreakStartWallClockMs = void 0;
1712
+ this.currentAdBreakTargetDurationMs = void 0;
1713
+ this.cumulativeAdDurationMs = 0;
1557
1714
  this.clearAdStartTimer();
1558
1715
  this.clearAdStopTimer();
1559
1716
  this.clearAdFailsafeTimer();
1560
1717
  this.adPodQueue = [];
1718
+ this.preloadedAdInfo = [];
1561
1719
  this.showAds = false;
1562
1720
  this.currentAdIndex = 0;
1563
1721
  this.totalAdsInBreak = 0;
1564
- if (this.video.paused) {
1565
- this.video.play().catch(() => {
1722
+ }
1723
+ shouldContinueAdBreak(remainingMs) {
1724
+ if (remainingMs > 500) {
1725
+ return true;
1726
+ }
1727
+ if (this.currentAdBreakTargetDurationMs) {
1728
+ const targetRemainingMs = this.currentAdBreakTargetDurationMs - this.cumulativeAdDurationMs;
1729
+ const minAdDuration = this.config.minAdDurationMs ?? 5e3;
1730
+ if (targetRemainingMs > minAdDuration) {
1566
1731
  if (this.config.debugAdTiming) {
1567
- console.error(
1568
- "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1732
+ console.log(
1733
+ `[StormcloudVideoPlayer] Target duration not filled, continuing. Remaining: ${targetRemainingMs}ms`
1569
1734
  );
1570
1735
  }
1571
- });
1736
+ return true;
1737
+ }
1738
+ }
1739
+ return false;
1740
+ }
1741
+ canRequestMoreAds() {
1742
+ const maxAdsPerBreak = this.config.maxAdsPerBreak ?? 10;
1743
+ if (this.currentAdIndex >= maxAdsPerBreak) {
1744
+ return false;
1745
+ }
1746
+ return !!this.apiVastTagUrl;
1747
+ }
1748
+ async requestAdditionalAds() {
1749
+ if (!this.currentAdBreakTargetDurationMs || !this.apiVastTagUrl) {
1750
+ throw new Error(
1751
+ "Cannot request additional ads without target duration and VAST URL"
1752
+ );
1572
1753
  }
1754
+ const remainingDurationMs = this.currentAdBreakTargetDurationMs - this.cumulativeAdDurationMs;
1755
+ const estimatedAdDurationMs = this.getEstimatedAdDuration() * 1e3;
1756
+ if (remainingDurationMs < estimatedAdDurationMs * 0.5) {
1757
+ throw new Error("Not enough time remaining for additional ads");
1758
+ }
1759
+ if (this.config.debugAdTiming) {
1760
+ console.log(
1761
+ `[StormcloudVideoPlayer] Requesting additional ads for remaining ${remainingDurationMs}ms`
1762
+ );
1763
+ }
1764
+ this.adPodQueue.push(this.apiVastTagUrl);
1765
+ this.totalAdsInBreak++;
1766
+ const next = this.adPodQueue.shift();
1767
+ this.currentAdIndex++;
1768
+ await this.playSingleAd(next);
1573
1769
  }
1574
1770
  startAdFailsafeTimer() {
1575
1771
  this.clearAdFailsafeTimer();
@@ -1603,6 +1799,109 @@ var StormcloudVideoPlayer = class {
1603
1799
  }
1604
1800
  return [b.vastTagUrl];
1605
1801
  }
1802
+ async buildAdQueueForDuration(targetDurationMs) {
1803
+ const adQueue = [];
1804
+ let baseVastTagUrl = this.apiVastTagUrl;
1805
+ if (!baseVastTagUrl) {
1806
+ const scheduled = this.findCurrentOrNextBreak(
1807
+ this.video.currentTime * 1e3
1808
+ );
1809
+ const scheduledTags = this.selectVastTagsForBreak(scheduled);
1810
+ if (scheduledTags && scheduledTags.length > 0) {
1811
+ baseVastTagUrl = scheduledTags[0];
1812
+ }
1813
+ }
1814
+ if (!baseVastTagUrl) {
1815
+ return adQueue;
1816
+ }
1817
+ if (!targetDurationMs) {
1818
+ let adsNumber = 1;
1819
+ if (this.vastConfig) {
1820
+ const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1821
+ if (isHls && this.vastConfig.cue_tones?.number_ads) {
1822
+ adsNumber = this.vastConfig.cue_tones.number_ads;
1823
+ } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1824
+ adsNumber = this.vastConfig.timer_vod.number_ads;
1825
+ }
1826
+ }
1827
+ return new Array(adsNumber).fill(baseVastTagUrl);
1828
+ }
1829
+ const targetDurationSeconds = targetDurationMs / 1e3;
1830
+ let cumulativeDurationSeconds = 0;
1831
+ const maxAdsToTry = 10;
1832
+ let adsAdded = 0;
1833
+ if (this.config.debugAdTiming) {
1834
+ console.log(
1835
+ `[StormcloudVideoPlayer] Attempting to fill ${targetDurationSeconds}s with ads`
1836
+ );
1837
+ }
1838
+ while (cumulativeDurationSeconds < targetDurationSeconds && adsAdded < maxAdsToTry) {
1839
+ adQueue.push(baseVastTagUrl);
1840
+ adsAdded++;
1841
+ const estimatedAdDuration = this.getEstimatedAdDuration();
1842
+ cumulativeDurationSeconds += estimatedAdDuration;
1843
+ if (this.config.debugAdTiming) {
1844
+ console.log(
1845
+ `[StormcloudVideoPlayer] Added ad ${adsAdded}, cumulative duration: ${cumulativeDurationSeconds}s`
1846
+ );
1847
+ }
1848
+ const remainingDuration = targetDurationSeconds - cumulativeDurationSeconds;
1849
+ const toleranceSeconds = (this.config.adBreakGapToleranceMs ?? 2e3) / 1e3;
1850
+ if (remainingDuration < estimatedAdDuration && remainingDuration >= -toleranceSeconds) {
1851
+ if (this.config.debugAdTiming) {
1852
+ console.log(
1853
+ `[StormcloudVideoPlayer] Within tolerance, adding final ad. Overage: ${-remainingDuration}s`
1854
+ );
1855
+ }
1856
+ break;
1857
+ }
1858
+ if (remainingDuration < estimatedAdDuration && remainingDuration < -toleranceSeconds) {
1859
+ if (this.config.debugAdTiming) {
1860
+ console.log(
1861
+ `[StormcloudVideoPlayer] Would exceed duration by too much, stopping at ${adsAdded} ads`
1862
+ );
1863
+ }
1864
+ break;
1865
+ }
1866
+ }
1867
+ return adQueue;
1868
+ }
1869
+ getEstimatedAdDuration() {
1870
+ if (this.vastConfig) {
1871
+ return 15;
1872
+ }
1873
+ return 30;
1874
+ }
1875
+ async getAdInfoForQueue(adQueue) {
1876
+ if (this.config.enableAdPreloading === false) {
1877
+ if (this.config.debugAdTiming) {
1878
+ console.log(
1879
+ "[StormcloudVideoPlayer] Ad preloading disabled, using estimates"
1880
+ );
1881
+ }
1882
+ return adQueue.map((vastTagUrl) => ({
1883
+ duration: this.getEstimatedAdDuration(),
1884
+ vastTagUrl,
1885
+ isPreloaded: false
1886
+ }));
1887
+ }
1888
+ try {
1889
+ const adInfos = await this.ima.preloadAds(adQueue);
1890
+ return adInfos;
1891
+ } catch (error) {
1892
+ if (this.config.debugAdTiming) {
1893
+ console.warn(
1894
+ "[StormcloudVideoPlayer] Failed to preload ads, using estimates:",
1895
+ error
1896
+ );
1897
+ }
1898
+ return adQueue.map((vastTagUrl) => ({
1899
+ duration: this.getEstimatedAdDuration(),
1900
+ vastTagUrl,
1901
+ isPreloaded: false
1902
+ }));
1903
+ }
1904
+ }
1606
1905
  getRemainingAdMs() {
1607
1906
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
1608
1907
  return 0;
@@ -1782,6 +2081,7 @@ var StormcloudVideoPlayerComponent = React.memo(
1782
2081
  const [isLoading, setIsLoading] = React.useState(true);
1783
2082
  const [isBuffering, setIsBuffering] = React.useState(false);
1784
2083
  const [showCenterPlay, setShowCenterPlay] = React.useState(false);
2084
+ const [showLicenseWarning, setShowLicenseWarning] = React.useState(false);
1785
2085
  const formatTime = (seconds) => {
1786
2086
  if (!isFinite(seconds)) return "0:00:00";
1787
2087
  const hours = Math.floor(seconds / 3600);
@@ -1839,6 +2139,15 @@ var StormcloudVideoPlayerComponent = React.memo(
1839
2139
  if (typeof window === "undefined") return;
1840
2140
  const el = videoRef.current;
1841
2141
  if (!el || !src) return;
2142
+ if (!licenseKey) {
2143
+ setShowLicenseWarning(true);
2144
+ setIsLoading(false);
2145
+ console.warn(
2146
+ "StormcloudVideoPlayer: License key is required but not provided. Please set the licenseKey prop to use the player."
2147
+ );
2148
+ return;
2149
+ }
2150
+ setShowLicenseWarning(false);
1842
2151
  if (playerRef.current) {
1843
2152
  try {
1844
2153
  playerRef.current.destroy();
@@ -2162,7 +2471,60 @@ var StormcloudVideoPlayerComponent = React.memo(
2162
2471
  )
2163
2472
  }
2164
2473
  ),
2165
- showCenterPlay && !isLoading && !isBuffering && !adStatus.showAds && /* @__PURE__ */ jsx(
2474
+ showLicenseWarning && /* @__PURE__ */ jsxs(
2475
+ "div",
2476
+ {
2477
+ style: {
2478
+ position: "absolute",
2479
+ top: "50%",
2480
+ left: "50%",
2481
+ transform: "translate(-50%, -50%)",
2482
+ zIndex: 25,
2483
+ background: "linear-gradient(135deg, rgba(220, 38, 38, 0.95) 0%, rgba(185, 28, 28, 0.9) 100%)",
2484
+ color: "white",
2485
+ padding: "24px 32px",
2486
+ borderRadius: "16px",
2487
+ backdropFilter: "blur(20px)",
2488
+ border: "2px solid rgba(255, 255, 255, 0.2)",
2489
+ boxShadow: "0 20px 60px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255, 255, 255, 0.2)",
2490
+ textAlign: "center",
2491
+ maxWidth: "400px",
2492
+ margin: "0 16px"
2493
+ },
2494
+ children: [
2495
+ /* @__PURE__ */ jsx(
2496
+ "div",
2497
+ {
2498
+ style: {
2499
+ fontSize: "20px",
2500
+ fontWeight: "bold",
2501
+ marginBottom: "12px",
2502
+ color: "#ffffff",
2503
+ textShadow: "0 2px 4px rgba(0, 0, 0, 0.5)"
2504
+ },
2505
+ children: "License Key Required"
2506
+ }
2507
+ ),
2508
+ /* @__PURE__ */ jsxs(
2509
+ "div",
2510
+ {
2511
+ style: {
2512
+ fontSize: "14px",
2513
+ lineHeight: "1.5",
2514
+ color: "rgba(255, 255, 255, 0.9)",
2515
+ textShadow: "0 1px 2px rgba(0, 0, 0, 0.3)"
2516
+ },
2517
+ children: [
2518
+ "Please provide a valid license key to use the Stormcloud Video Player.",
2519
+ /* @__PURE__ */ jsx("br", {}),
2520
+ "Contact your administrator for licensing information."
2521
+ ]
2522
+ }
2523
+ )
2524
+ ]
2525
+ }
2526
+ ),
2527
+ showCenterPlay && !isLoading && !isBuffering && !showLicenseWarning && !adStatus.showAds && /* @__PURE__ */ jsx(
2166
2528
  "div",
2167
2529
  {
2168
2530
  onClick: handleCenterPlayClick,
@@ -2213,7 +2575,7 @@ var StormcloudVideoPlayerComponent = React.memo(
2213
2575
  )
2214
2576
  }
2215
2577
  ),
2216
- shouldShowEnhancedControls ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
2578
+ shouldShowEnhancedControls && !showLicenseWarning ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
2217
2579
  "div",
2218
2580
  {
2219
2581
  style: {
@@ -2678,7 +3040,7 @@ var StormcloudVideoPlayerComponent = React.memo(
2678
3040
  )
2679
3041
  ]
2680
3042
  }
2681
- ) }) : showCustomControls && /* @__PURE__ */ jsxs(
3043
+ ) }) : showCustomControls && !showLicenseWarning && /* @__PURE__ */ jsxs(
2682
3044
  "div",
2683
3045
  {
2684
3046
  style: {