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.js CHANGED
@@ -68,8 +68,6 @@ function createImaController(video, options) {
68
68
  let adsLoadedPromise;
69
69
  let adsLoadedResolve;
70
70
  let adsLoadedReject;
71
- let currentAdDuration = 0;
72
- let preloadedAds = [];
73
71
  function makeAdsRequest(google, vastTagUrl) {
74
72
  const adsRequest = new google.ima.AdsRequest();
75
73
  adsRequest.adTagUrl = vastTagUrl;
@@ -176,7 +174,6 @@ function createImaController(video, options) {
176
174
  } catch {
177
175
  }
178
176
  adPlaying = false;
179
- currentAdDuration = 0;
180
177
  video.muted = originalMutedState;
181
178
  if (adContainerEl)
182
179
  adContainerEl.style.pointerEvents = "none";
@@ -202,7 +199,7 @@ function createImaController(video, options) {
202
199
  );
203
200
  emit("ad_error");
204
201
  if (!options?.continueLiveStreamDuringAds) {
205
- video.play().catch(() => {
202
+ video.play()?.catch(() => {
206
203
  });
207
204
  }
208
205
  }
@@ -237,7 +234,7 @@ function createImaController(video, options) {
237
234
  if (adContainerEl)
238
235
  adContainerEl.style.pointerEvents = "none";
239
236
  if (!options?.continueLiveStreamDuringAds) {
240
- video.play().catch(() => {
237
+ video.play()?.catch(() => {
241
238
  });
242
239
  console.log("[IMA] Video resumed (VOD mode)");
243
240
  } else {
@@ -248,22 +245,9 @@ function createImaController(video, options) {
248
245
  emit("content_resume");
249
246
  }
250
247
  );
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
- });
263
248
  adsManager.addEventListener(AdEvent.ALL_ADS_COMPLETED, () => {
264
249
  console.log("[IMA] All ads completed");
265
250
  adPlaying = false;
266
- currentAdDuration = 0;
267
251
  video.muted = originalMutedState;
268
252
  if (adContainerEl) adContainerEl.style.pointerEvents = "none";
269
253
  if (!options?.continueLiveStreamDuringAds) {
@@ -363,7 +347,7 @@ function createImaController(video, options) {
363
347
  console.error("[IMA] Error starting ad playback:", error);
364
348
  adPlaying = false;
365
349
  if (!options?.continueLiveStreamDuringAds) {
366
- video.play().catch(() => {
350
+ video.play()?.catch(() => {
367
351
  });
368
352
  }
369
353
  return Promise.reject(error);
@@ -448,84 +432,6 @@ function createImaController(video, options) {
448
432
  }
449
433
  }
450
434
  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;
529
435
  }
530
436
  };
531
437
  }
@@ -674,13 +580,32 @@ function getClientInfo() {
674
580
  }
675
581
  async function getBrowserID(clientInfo) {
676
582
  const fingerprintString = JSON.stringify(clientInfo);
677
- const hashBuffer = await crypto.subtle.digest(
678
- "SHA-256",
679
- new TextEncoder().encode(fingerprintString)
680
- );
681
- const hashArray = Array.from(new Uint8Array(hashBuffer));
682
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
683
- return hashHex;
583
+ if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
584
+ try {
585
+ const hashBuffer = await crypto.subtle.digest(
586
+ "SHA-256",
587
+ new TextEncoder().encode(fingerprintString)
588
+ );
589
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
590
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
591
+ return hashHex;
592
+ } catch (error) {
593
+ console.warn(
594
+ "[StormcloudVideoPlayer] crypto.subtle.digest failed, using fallback hash:",
595
+ error
596
+ );
597
+ }
598
+ }
599
+ let hash = 0;
600
+ for (let i = 0; i < fingerprintString.length; i++) {
601
+ const char = fingerprintString.charCodeAt(i);
602
+ hash = (hash << 5) - hash + char;
603
+ hash = hash & hash;
604
+ }
605
+ const fallbackHash = Math.abs(hash).toString(16).padStart(8, "0");
606
+ const timestamp = Date.now().toString(16).padStart(12, "0");
607
+ const random = Math.random().toString(16).substring(2, 14).padStart(12, "0");
608
+ return (fallbackHash + timestamp + random).padEnd(64, "0");
684
609
  }
685
610
  async function sendInitialTracking(licenseKey) {
686
611
  try {
@@ -758,8 +683,6 @@ var StormcloudVideoPlayer = class {
758
683
  this.totalAdsInBreak = 0;
759
684
  this.showAds = false;
760
685
  this.isLiveStream = false;
761
- this.preloadedAdInfo = [];
762
- this.cumulativeAdDurationMs = 0;
763
686
  this.config = config;
764
687
  this.video = config.videoElement;
765
688
  this.ima = createImaController(this.video, {
@@ -800,7 +723,7 @@ var StormcloudVideoPlayer = class {
800
723
  });
801
724
  this.ima.initialize();
802
725
  if (this.config.autoplay) {
803
- await this.video.play().catch(() => {
726
+ await this.video.play()?.catch(() => {
804
727
  });
805
728
  }
806
729
  return;
@@ -834,7 +757,7 @@ var StormcloudVideoPlayer = class {
834
757
  });
835
758
  this.ima.initialize();
836
759
  if (this.config.autoplay) {
837
- await this.video.play().catch(() => {
760
+ await this.video.play()?.catch(() => {
838
761
  });
839
762
  }
840
763
  });
@@ -931,28 +854,16 @@ var StormcloudVideoPlayer = class {
931
854
  this.ima.initialize();
932
855
  this.ima.on("all_ads_completed", () => {
933
856
  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
- }
943
857
  const remaining = this.getRemainingAdMs();
944
- const shouldContinue = this.shouldContinueAdBreak(remaining);
945
- if (shouldContinue && this.adPodQueue.length > 0) {
858
+ if (remaining > 500 && this.adPodQueue.length > 0) {
946
859
  const next = this.adPodQueue.shift();
947
860
  this.currentAdIndex++;
948
861
  this.playSingleAd(next).catch(() => {
949
862
  });
950
- } else if (shouldContinue && this.canRequestMoreAds()) {
951
- this.requestAdditionalAds().catch(() => {
952
- this.endAdBreak();
953
- });
954
863
  } else {
955
- this.endAdBreak();
864
+ this.currentAdIndex = 0;
865
+ this.totalAdsInBreak = 0;
866
+ this.showAds = false;
956
867
  }
957
868
  });
958
869
  this.ima.on("ad_error", () => {
@@ -961,16 +872,11 @@ var StormcloudVideoPlayer = class {
961
872
  }
962
873
  if (!this.inAdBreak) return;
963
874
  const remaining = this.getRemainingAdMs();
964
- const shouldContinue = this.shouldContinueAdBreak(remaining);
965
- if (shouldContinue && this.adPodQueue.length > 0) {
875
+ if (remaining > 500 && this.adPodQueue.length > 0) {
966
876
  const next = this.adPodQueue.shift();
967
877
  this.currentAdIndex++;
968
878
  this.playSingleAd(next).catch(() => {
969
879
  });
970
- } else if (shouldContinue && this.canRequestMoreAds()) {
971
- this.requestAdditionalAds().catch(() => {
972
- this.handleAdFailure();
973
- });
974
880
  } else {
975
881
  this.handleAdFailure();
976
882
  }
@@ -1461,25 +1367,6 @@ var StormcloudVideoPlayer = class {
1461
1367
  isShowingAds() {
1462
1368
  return this.showAds;
1463
1369
  }
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
- }
1483
1370
  getStreamType() {
1484
1371
  const url = this.config.src.toLowerCase();
1485
1372
  if (url.includes(".m3u8") || url.includes("/hls/") || url.includes("application/vnd.apple.mpegurl")) {
@@ -1503,70 +1390,59 @@ var StormcloudVideoPlayer = class {
1503
1390
  }
1504
1391
  return true;
1505
1392
  }
1506
- async loadDefaultVastFromAdstorm(adstormApiUrl, params) {
1507
- const usp = new URLSearchParams(params || {});
1508
- const url = `${adstormApiUrl}?${usp.toString()}`;
1509
- const res = await fetch(url);
1510
- if (!res.ok) throw new Error(`Failed to fetch adstorm ads: ${res.status}`);
1511
- const data = await res.json();
1512
- const tag = data?.adTagUrl || data?.vastTagUrl || data?.tagUrl;
1513
- if (typeof tag === "string" && tag.length > 0) {
1514
- this.apiVastTagUrl = tag;
1515
- }
1516
- }
1517
- async handleAdStart(marker) {
1393
+ async handleAdStart(_marker) {
1518
1394
  const scheduled = this.findCurrentOrNextBreak(
1519
1395
  this.video.currentTime * 1e3
1520
1396
  );
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
1397
+ const tags = this.selectVastTagsForBreak(scheduled);
1398
+ let vastTagUrl;
1399
+ let adsNumber = 1;
1400
+ if (this.apiVastTagUrl) {
1401
+ vastTagUrl = this.apiVastTagUrl;
1402
+ if (this.vastConfig) {
1403
+ const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1404
+ if (isHls && this.vastConfig.cue_tones?.number_ads) {
1405
+ adsNumber = this.vastConfig.cue_tones.number_ads;
1406
+ } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1407
+ adsNumber = this.vastConfig.timer_vod.number_ads;
1537
1408
  }
1538
- );
1539
- }
1540
- const adQueue = await this.buildAdQueueForDuration(targetDurationMs);
1541
- if (adQueue.length === 0) {
1409
+ }
1410
+ this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
1411
+ this.currentAdIndex = 0;
1412
+ this.totalAdsInBreak = adsNumber;
1413
+ if (this.config.debugAdTiming) {
1414
+ console.log(
1415
+ `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
1416
+ vastTagUrl
1417
+ );
1418
+ }
1419
+ } else if (tags && tags.length > 0) {
1420
+ vastTagUrl = tags[0];
1421
+ const rest = tags.slice(1);
1422
+ this.adPodQueue = rest;
1423
+ this.currentAdIndex = 0;
1424
+ this.totalAdsInBreak = tags.length;
1425
+ if (this.config.debugAdTiming) {
1426
+ console.log(
1427
+ "[StormcloudVideoPlayer] Using scheduled VAST tag:",
1428
+ vastTagUrl
1429
+ );
1430
+ }
1431
+ } else {
1542
1432
  if (this.config.debugAdTiming) {
1543
- console.log("[StormcloudVideoPlayer] No ads available for ad break");
1433
+ console.log("[StormcloudVideoPlayer] No VAST tag available for ad");
1544
1434
  }
1545
1435
  return;
1546
1436
  }
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
- });
1437
+ if (vastTagUrl) {
1438
+ this.showAds = true;
1439
+ this.currentAdIndex++;
1440
+ await this.playSingleAd(vastTagUrl);
1563
1441
  }
1564
- this.currentAdIndex++;
1565
- await this.playSingleAd(adQueue[0]);
1566
- if (targetDurationMs != null) {
1567
- this.expectedAdBreakDurationMs = targetDurationMs;
1442
+ if (this.expectedAdBreakDurationMs == null && scheduled?.durationMs != null) {
1443
+ this.expectedAdBreakDurationMs = scheduled.durationMs;
1568
1444
  this.currentAdBreakStartWallClockMs = this.currentAdBreakStartWallClockMs ?? Date.now();
1569
- this.scheduleAdStopCountdown(targetDurationMs);
1445
+ this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
1570
1446
  }
1571
1447
  }
1572
1448
  findCurrentOrNextBreak(nowMs) {
@@ -1683,89 +1559,25 @@ var StormcloudVideoPlayer = class {
1683
1559
  "[StormcloudVideoPlayer] Handling ad failure - resuming content"
1684
1560
  );
1685
1561
  }
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
- }
1709
1562
  this.inAdBreak = false;
1710
1563
  this.expectedAdBreakDurationMs = void 0;
1711
1564
  this.currentAdBreakStartWallClockMs = void 0;
1712
- this.currentAdBreakTargetDurationMs = void 0;
1713
- this.cumulativeAdDurationMs = 0;
1714
1565
  this.clearAdStartTimer();
1715
1566
  this.clearAdStopTimer();
1716
1567
  this.clearAdFailsafeTimer();
1717
1568
  this.adPodQueue = [];
1718
- this.preloadedAdInfo = [];
1719
1569
  this.showAds = false;
1720
1570
  this.currentAdIndex = 0;
1721
1571
  this.totalAdsInBreak = 0;
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) {
1572
+ if (this.video.paused) {
1573
+ this.video.play()?.catch(() => {
1731
1574
  if (this.config.debugAdTiming) {
1732
- console.log(
1733
- `[StormcloudVideoPlayer] Target duration not filled, continuing. Remaining: ${targetRemainingMs}ms`
1575
+ console.error(
1576
+ "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1734
1577
  );
1735
1578
  }
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
- );
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
- );
1579
+ });
1763
1580
  }
1764
- this.adPodQueue.push(this.apiVastTagUrl);
1765
- this.totalAdsInBreak++;
1766
- const next = this.adPodQueue.shift();
1767
- this.currentAdIndex++;
1768
- await this.playSingleAd(next);
1769
1581
  }
1770
1582
  startAdFailsafeTimer() {
1771
1583
  this.clearAdFailsafeTimer();
@@ -1799,109 +1611,6 @@ var StormcloudVideoPlayer = class {
1799
1611
  }
1800
1612
  return [b.vastTagUrl];
1801
1613
  }
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
- }
1905
1614
  getRemainingAdMs() {
1906
1615
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
1907
1616
  return 0;