stormcloud-video-player 0.2.5 → 0.2.7

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";
@@ -261,7 +258,7 @@ function createImaController(video, options) {
261
258
  );
262
259
  emit("ad_error");
263
260
  if (!options?.continueLiveStreamDuringAds) {
264
- video.play().catch(() => {
261
+ video.play()?.catch(() => {
265
262
  });
266
263
  }
267
264
  }
@@ -296,7 +293,7 @@ function createImaController(video, options) {
296
293
  if (adContainerEl)
297
294
  adContainerEl.style.pointerEvents = "none";
298
295
  if (!options?.continueLiveStreamDuringAds) {
299
- video.play().catch(() => {
296
+ video.play()?.catch(() => {
300
297
  });
301
298
  console.log("[IMA] Video resumed (VOD mode)");
302
299
  } else {
@@ -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) {
@@ -422,7 +406,7 @@ function createImaController(video, options) {
422
406
  console.error("[IMA] Error starting ad playback:", error);
423
407
  adPlaying = false;
424
408
  if (!options?.continueLiveStreamDuringAds) {
425
- video.play().catch(() => {
409
+ video.play()?.catch(() => {
426
410
  });
427
411
  }
428
412
  return Promise.reject(error);
@@ -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
  }
@@ -733,13 +639,32 @@ function getClientInfo() {
733
639
  }
734
640
  async function getBrowserID(clientInfo) {
735
641
  const fingerprintString = JSON.stringify(clientInfo);
736
- const hashBuffer = await crypto.subtle.digest(
737
- "SHA-256",
738
- new TextEncoder().encode(fingerprintString)
739
- );
740
- const hashArray = Array.from(new Uint8Array(hashBuffer));
741
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
742
- return hashHex;
642
+ if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
643
+ try {
644
+ const hashBuffer = await crypto.subtle.digest(
645
+ "SHA-256",
646
+ new TextEncoder().encode(fingerprintString)
647
+ );
648
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
649
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
650
+ return hashHex;
651
+ } catch (error) {
652
+ console.warn(
653
+ "[StormcloudVideoPlayer] crypto.subtle.digest failed, using fallback hash:",
654
+ error
655
+ );
656
+ }
657
+ }
658
+ let hash = 0;
659
+ for (let i = 0; i < fingerprintString.length; i++) {
660
+ const char = fingerprintString.charCodeAt(i);
661
+ hash = (hash << 5) - hash + char;
662
+ hash = hash & hash;
663
+ }
664
+ const fallbackHash = Math.abs(hash).toString(16).padStart(8, "0");
665
+ const timestamp = Date.now().toString(16).padStart(12, "0");
666
+ const random = Math.random().toString(16).substring(2, 14).padStart(12, "0");
667
+ return (fallbackHash + timestamp + random).padEnd(64, "0");
743
668
  }
744
669
  async function sendInitialTracking(licenseKey) {
745
670
  try {
@@ -817,8 +742,6 @@ var StormcloudVideoPlayer = class {
817
742
  this.totalAdsInBreak = 0;
818
743
  this.showAds = false;
819
744
  this.isLiveStream = false;
820
- this.preloadedAdInfo = [];
821
- this.cumulativeAdDurationMs = 0;
822
745
  this.config = config;
823
746
  this.video = config.videoElement;
824
747
  this.ima = createImaController(this.video, {
@@ -859,7 +782,7 @@ var StormcloudVideoPlayer = class {
859
782
  });
860
783
  this.ima.initialize();
861
784
  if (this.config.autoplay) {
862
- await this.video.play().catch(() => {
785
+ await this.video.play()?.catch(() => {
863
786
  });
864
787
  }
865
788
  return;
@@ -893,7 +816,7 @@ var StormcloudVideoPlayer = class {
893
816
  });
894
817
  this.ima.initialize();
895
818
  if (this.config.autoplay) {
896
- await this.video.play().catch(() => {
819
+ await this.video.play()?.catch(() => {
897
820
  });
898
821
  }
899
822
  });
@@ -990,28 +913,16 @@ var StormcloudVideoPlayer = class {
990
913
  this.ima.initialize();
991
914
  this.ima.on("all_ads_completed", () => {
992
915
  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
916
  const remaining = this.getRemainingAdMs();
1003
- const shouldContinue = this.shouldContinueAdBreak(remaining);
1004
- if (shouldContinue && this.adPodQueue.length > 0) {
917
+ if (remaining > 500 && this.adPodQueue.length > 0) {
1005
918
  const next = this.adPodQueue.shift();
1006
919
  this.currentAdIndex++;
1007
920
  this.playSingleAd(next).catch(() => {
1008
921
  });
1009
- } else if (shouldContinue && this.canRequestMoreAds()) {
1010
- this.requestAdditionalAds().catch(() => {
1011
- this.endAdBreak();
1012
- });
1013
922
  } else {
1014
- this.endAdBreak();
923
+ this.currentAdIndex = 0;
924
+ this.totalAdsInBreak = 0;
925
+ this.showAds = false;
1015
926
  }
1016
927
  });
1017
928
  this.ima.on("ad_error", () => {
@@ -1020,16 +931,11 @@ var StormcloudVideoPlayer = class {
1020
931
  }
1021
932
  if (!this.inAdBreak) return;
1022
933
  const remaining = this.getRemainingAdMs();
1023
- const shouldContinue = this.shouldContinueAdBreak(remaining);
1024
- if (shouldContinue && this.adPodQueue.length > 0) {
934
+ if (remaining > 500 && this.adPodQueue.length > 0) {
1025
935
  const next = this.adPodQueue.shift();
1026
936
  this.currentAdIndex++;
1027
937
  this.playSingleAd(next).catch(() => {
1028
938
  });
1029
- } else if (shouldContinue && this.canRequestMoreAds()) {
1030
- this.requestAdditionalAds().catch(() => {
1031
- this.handleAdFailure();
1032
- });
1033
939
  } else {
1034
940
  this.handleAdFailure();
1035
941
  }
@@ -1520,25 +1426,6 @@ var StormcloudVideoPlayer = class {
1520
1426
  isShowingAds() {
1521
1427
  return this.showAds;
1522
1428
  }
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
1429
  getStreamType() {
1543
1430
  const url = this.config.src.toLowerCase();
1544
1431
  if (url.includes(".m3u8") || url.includes("/hls/") || url.includes("application/vnd.apple.mpegurl")) {
@@ -1562,70 +1449,59 @@ var StormcloudVideoPlayer = class {
1562
1449
  }
1563
1450
  return true;
1564
1451
  }
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) {
1452
+ async handleAdStart(_marker) {
1577
1453
  const scheduled = this.findCurrentOrNextBreak(
1578
1454
  this.video.currentTime * 1e3
1579
1455
  );
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
1456
+ const tags = this.selectVastTagsForBreak(scheduled);
1457
+ let vastTagUrl;
1458
+ let adsNumber = 1;
1459
+ if (this.apiVastTagUrl) {
1460
+ vastTagUrl = this.apiVastTagUrl;
1461
+ if (this.vastConfig) {
1462
+ const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1463
+ if (isHls && this.vastConfig.cue_tones?.number_ads) {
1464
+ adsNumber = this.vastConfig.cue_tones.number_ads;
1465
+ } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1466
+ adsNumber = this.vastConfig.timer_vod.number_ads;
1596
1467
  }
1597
- );
1598
- }
1599
- const adQueue = await this.buildAdQueueForDuration(targetDurationMs);
1600
- if (adQueue.length === 0) {
1468
+ }
1469
+ this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
1470
+ this.currentAdIndex = 0;
1471
+ this.totalAdsInBreak = adsNumber;
1472
+ if (this.config.debugAdTiming) {
1473
+ console.log(
1474
+ `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
1475
+ vastTagUrl
1476
+ );
1477
+ }
1478
+ } else if (tags && tags.length > 0) {
1479
+ vastTagUrl = tags[0];
1480
+ const rest = tags.slice(1);
1481
+ this.adPodQueue = rest;
1482
+ this.currentAdIndex = 0;
1483
+ this.totalAdsInBreak = tags.length;
1484
+ if (this.config.debugAdTiming) {
1485
+ console.log(
1486
+ "[StormcloudVideoPlayer] Using scheduled VAST tag:",
1487
+ vastTagUrl
1488
+ );
1489
+ }
1490
+ } else {
1601
1491
  if (this.config.debugAdTiming) {
1602
- console.log("[StormcloudVideoPlayer] No ads available for ad break");
1492
+ console.log("[StormcloudVideoPlayer] No VAST tag available for ad");
1603
1493
  }
1604
1494
  return;
1605
1495
  }
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
- });
1496
+ if (vastTagUrl) {
1497
+ this.showAds = true;
1498
+ this.currentAdIndex++;
1499
+ await this.playSingleAd(vastTagUrl);
1622
1500
  }
1623
- this.currentAdIndex++;
1624
- await this.playSingleAd(adQueue[0]);
1625
- if (targetDurationMs != null) {
1626
- this.expectedAdBreakDurationMs = targetDurationMs;
1501
+ if (this.expectedAdBreakDurationMs == null && scheduled?.durationMs != null) {
1502
+ this.expectedAdBreakDurationMs = scheduled.durationMs;
1627
1503
  this.currentAdBreakStartWallClockMs = this.currentAdBreakStartWallClockMs ?? Date.now();
1628
- this.scheduleAdStopCountdown(targetDurationMs);
1504
+ this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
1629
1505
  }
1630
1506
  }
1631
1507
  findCurrentOrNextBreak(nowMs) {
@@ -1742,89 +1618,25 @@ var StormcloudVideoPlayer = class {
1742
1618
  "[StormcloudVideoPlayer] Handling ad failure - resuming content"
1743
1619
  );
1744
1620
  }
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
1621
  this.inAdBreak = false;
1769
1622
  this.expectedAdBreakDurationMs = void 0;
1770
1623
  this.currentAdBreakStartWallClockMs = void 0;
1771
- this.currentAdBreakTargetDurationMs = void 0;
1772
- this.cumulativeAdDurationMs = 0;
1773
1624
  this.clearAdStartTimer();
1774
1625
  this.clearAdStopTimer();
1775
1626
  this.clearAdFailsafeTimer();
1776
1627
  this.adPodQueue = [];
1777
- this.preloadedAdInfo = [];
1778
1628
  this.showAds = false;
1779
1629
  this.currentAdIndex = 0;
1780
1630
  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) {
1631
+ if (this.video.paused) {
1632
+ this.video.play()?.catch(() => {
1790
1633
  if (this.config.debugAdTiming) {
1791
- console.log(
1792
- `[StormcloudVideoPlayer] Target duration not filled, continuing. Remaining: ${targetRemainingMs}ms`
1634
+ console.error(
1635
+ "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1793
1636
  );
1794
1637
  }
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
- );
1638
+ });
1822
1639
  }
1823
- this.adPodQueue.push(this.apiVastTagUrl);
1824
- this.totalAdsInBreak++;
1825
- const next = this.adPodQueue.shift();
1826
- this.currentAdIndex++;
1827
- await this.playSingleAd(next);
1828
1640
  }
1829
1641
  startAdFailsafeTimer() {
1830
1642
  this.clearAdFailsafeTimer();
@@ -1858,109 +1670,6 @@ var StormcloudVideoPlayer = class {
1858
1670
  }
1859
1671
  return [b.vastTagUrl];
1860
1672
  }
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
1673
  getRemainingAdMs() {
1965
1674
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
1966
1675
  return 0;