stormcloud-video-player 0.2.5 → 0.2.6

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,8 +127,6 @@ function createImaController(video, options) {
127
127
  let adsLoadedPromise;
128
128
  let adsLoadedResolve;
129
129
  let adsLoadedReject;
130
- let currentAdDuration = 0;
131
- let preloadedAds = [];
132
130
  function makeAdsRequest(google, vastTagUrl) {
133
131
  const adsRequest = new google.ima.AdsRequest();
134
132
  adsRequest.adTagUrl = vastTagUrl;
@@ -235,7 +233,6 @@ function createImaController(video, options) {
235
233
  } catch {
236
234
  }
237
235
  adPlaying = false;
238
- currentAdDuration = 0;
239
236
  video.muted = originalMutedState;
240
237
  if (adContainerEl)
241
238
  adContainerEl.style.pointerEvents = "none";
@@ -307,22 +304,9 @@ function createImaController(video, options) {
307
304
  emit("content_resume");
308
305
  }
309
306
  );
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
- });
322
307
  adsManager.addEventListener(AdEvent.ALL_ADS_COMPLETED, () => {
323
308
  console.log("[IMA] All ads completed");
324
309
  adPlaying = false;
325
- currentAdDuration = 0;
326
310
  video.muted = originalMutedState;
327
311
  if (adContainerEl) adContainerEl.style.pointerEvents = "none";
328
312
  if (!options?.continueLiveStreamDuringAds) {
@@ -507,84 +491,6 @@ function createImaController(video, options) {
507
491
  }
508
492
  }
509
493
  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;
588
494
  }
589
495
  };
590
496
  }
@@ -817,8 +723,6 @@ var StormcloudVideoPlayer = class {
817
723
  this.totalAdsInBreak = 0;
818
724
  this.showAds = false;
819
725
  this.isLiveStream = false;
820
- this.preloadedAdInfo = [];
821
- this.cumulativeAdDurationMs = 0;
822
726
  this.config = config;
823
727
  this.video = config.videoElement;
824
728
  this.ima = createImaController(this.video, {
@@ -990,28 +894,16 @@ var StormcloudVideoPlayer = class {
990
894
  this.ima.initialize();
991
895
  this.ima.on("all_ads_completed", () => {
992
896
  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
- }
1002
897
  const remaining = this.getRemainingAdMs();
1003
- const shouldContinue = this.shouldContinueAdBreak(remaining);
1004
- if (shouldContinue && this.adPodQueue.length > 0) {
898
+ if (remaining > 500 && this.adPodQueue.length > 0) {
1005
899
  const next = this.adPodQueue.shift();
1006
900
  this.currentAdIndex++;
1007
901
  this.playSingleAd(next).catch(() => {
1008
902
  });
1009
- } else if (shouldContinue && this.canRequestMoreAds()) {
1010
- this.requestAdditionalAds().catch(() => {
1011
- this.endAdBreak();
1012
- });
1013
903
  } else {
1014
- this.endAdBreak();
904
+ this.currentAdIndex = 0;
905
+ this.totalAdsInBreak = 0;
906
+ this.showAds = false;
1015
907
  }
1016
908
  });
1017
909
  this.ima.on("ad_error", () => {
@@ -1020,16 +912,11 @@ var StormcloudVideoPlayer = class {
1020
912
  }
1021
913
  if (!this.inAdBreak) return;
1022
914
  const remaining = this.getRemainingAdMs();
1023
- const shouldContinue = this.shouldContinueAdBreak(remaining);
1024
- if (shouldContinue && this.adPodQueue.length > 0) {
915
+ if (remaining > 500 && this.adPodQueue.length > 0) {
1025
916
  const next = this.adPodQueue.shift();
1026
917
  this.currentAdIndex++;
1027
918
  this.playSingleAd(next).catch(() => {
1028
919
  });
1029
- } else if (shouldContinue && this.canRequestMoreAds()) {
1030
- this.requestAdditionalAds().catch(() => {
1031
- this.handleAdFailure();
1032
- });
1033
920
  } else {
1034
921
  this.handleAdFailure();
1035
922
  }
@@ -1520,25 +1407,6 @@ var StormcloudVideoPlayer = class {
1520
1407
  isShowingAds() {
1521
1408
  return this.showAds;
1522
1409
  }
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
- }
1542
1410
  getStreamType() {
1543
1411
  const url = this.config.src.toLowerCase();
1544
1412
  if (url.includes(".m3u8") || url.includes("/hls/") || url.includes("application/vnd.apple.mpegurl")) {
@@ -1562,70 +1430,59 @@ var StormcloudVideoPlayer = class {
1562
1430
  }
1563
1431
  return true;
1564
1432
  }
1565
- async loadDefaultVastFromAdstorm(adstormApiUrl, params) {
1566
- const usp = new URLSearchParams(params || {});
1567
- const url = `${adstormApiUrl}?${usp.toString()}`;
1568
- const res = await fetch(url);
1569
- if (!res.ok) throw new Error(`Failed to fetch adstorm ads: ${res.status}`);
1570
- const data = await res.json();
1571
- const tag = data?.adTagUrl || data?.vastTagUrl || data?.tagUrl;
1572
- if (typeof tag === "string" && tag.length > 0) {
1573
- this.apiVastTagUrl = tag;
1574
- }
1575
- }
1576
- async handleAdStart(marker) {
1433
+ async handleAdStart(_marker) {
1577
1434
  const scheduled = this.findCurrentOrNextBreak(
1578
1435
  this.video.currentTime * 1e3
1579
1436
  );
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
1437
+ const tags = this.selectVastTagsForBreak(scheduled);
1438
+ let vastTagUrl;
1439
+ let adsNumber = 1;
1440
+ if (this.apiVastTagUrl) {
1441
+ vastTagUrl = this.apiVastTagUrl;
1442
+ if (this.vastConfig) {
1443
+ const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1444
+ if (isHls && this.vastConfig.cue_tones?.number_ads) {
1445
+ adsNumber = this.vastConfig.cue_tones.number_ads;
1446
+ } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1447
+ adsNumber = this.vastConfig.timer_vod.number_ads;
1596
1448
  }
1597
- );
1598
- }
1599
- const adQueue = await this.buildAdQueueForDuration(targetDurationMs);
1600
- if (adQueue.length === 0) {
1449
+ }
1450
+ this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
1451
+ this.currentAdIndex = 0;
1452
+ this.totalAdsInBreak = adsNumber;
1601
1453
  if (this.config.debugAdTiming) {
1602
- console.log("[StormcloudVideoPlayer] No ads available for ad break");
1454
+ console.log(
1455
+ `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
1456
+ vastTagUrl
1457
+ );
1458
+ }
1459
+ } else if (tags && tags.length > 0) {
1460
+ vastTagUrl = tags[0];
1461
+ const rest = tags.slice(1);
1462
+ this.adPodQueue = rest;
1463
+ this.currentAdIndex = 0;
1464
+ this.totalAdsInBreak = tags.length;
1465
+ if (this.config.debugAdTiming) {
1466
+ console.log(
1467
+ "[StormcloudVideoPlayer] Using scheduled VAST tag:",
1468
+ vastTagUrl
1469
+ );
1470
+ }
1471
+ } else {
1472
+ if (this.config.debugAdTiming) {
1473
+ console.log("[StormcloudVideoPlayer] No VAST tag available for ad");
1603
1474
  }
1604
1475
  return;
1605
1476
  }
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
- });
1477
+ if (vastTagUrl) {
1478
+ this.showAds = true;
1479
+ this.currentAdIndex++;
1480
+ await this.playSingleAd(vastTagUrl);
1622
1481
  }
1623
- this.currentAdIndex++;
1624
- await this.playSingleAd(adQueue[0]);
1625
- if (targetDurationMs != null) {
1626
- this.expectedAdBreakDurationMs = targetDurationMs;
1482
+ if (this.expectedAdBreakDurationMs == null && scheduled?.durationMs != null) {
1483
+ this.expectedAdBreakDurationMs = scheduled.durationMs;
1627
1484
  this.currentAdBreakStartWallClockMs = this.currentAdBreakStartWallClockMs ?? Date.now();
1628
- this.scheduleAdStopCountdown(targetDurationMs);
1485
+ this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
1629
1486
  }
1630
1487
  }
1631
1488
  findCurrentOrNextBreak(nowMs) {
@@ -1742,89 +1599,25 @@ var StormcloudVideoPlayer = class {
1742
1599
  "[StormcloudVideoPlayer] Handling ad failure - resuming content"
1743
1600
  );
1744
1601
  }
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
- }
1768
1602
  this.inAdBreak = false;
1769
1603
  this.expectedAdBreakDurationMs = void 0;
1770
1604
  this.currentAdBreakStartWallClockMs = void 0;
1771
- this.currentAdBreakTargetDurationMs = void 0;
1772
- this.cumulativeAdDurationMs = 0;
1773
1605
  this.clearAdStartTimer();
1774
1606
  this.clearAdStopTimer();
1775
1607
  this.clearAdFailsafeTimer();
1776
1608
  this.adPodQueue = [];
1777
- this.preloadedAdInfo = [];
1778
1609
  this.showAds = false;
1779
1610
  this.currentAdIndex = 0;
1780
1611
  this.totalAdsInBreak = 0;
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) {
1612
+ if (this.video.paused) {
1613
+ this.video.play().catch(() => {
1790
1614
  if (this.config.debugAdTiming) {
1791
- console.log(
1792
- `[StormcloudVideoPlayer] Target duration not filled, continuing. Remaining: ${targetRemainingMs}ms`
1615
+ console.error(
1616
+ "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1793
1617
  );
1794
1618
  }
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
- );
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
- );
1619
+ });
1822
1620
  }
1823
- this.adPodQueue.push(this.apiVastTagUrl);
1824
- this.totalAdsInBreak++;
1825
- const next = this.adPodQueue.shift();
1826
- this.currentAdIndex++;
1827
- await this.playSingleAd(next);
1828
1621
  }
1829
1622
  startAdFailsafeTimer() {
1830
1623
  this.clearAdFailsafeTimer();
@@ -1858,109 +1651,6 @@ var StormcloudVideoPlayer = class {
1858
1651
  }
1859
1652
  return [b.vastTagUrl];
1860
1653
  }
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
- }
1964
1654
  getRemainingAdMs() {
1965
1655
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
1966
1656
  return 0;