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/dist/stormcloud-vp.min.js +2 -2
- package/lib/index.cjs +418 -56
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +29 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.js +418 -56
- package/lib/index.js.map +1 -1
- package/lib/types-DOcCdwQI.d.cts +78 -0
- package/lib/ui/StormcloudVideoPlayer.cjs +66 -3
- package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
1576
|
+
async handleAdStart(marker) {
|
|
1445
1577
|
const scheduled = this.findCurrentOrNextBreak(
|
|
1446
1578
|
this.video.currentTime * 1e3
|
|
1447
1579
|
);
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
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
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
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
|
|
1602
|
+
console.log("[StormcloudVideoPlayer] No ads available for ad break");
|
|
1485
1603
|
}
|
|
1486
1604
|
return;
|
|
1487
1605
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
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
|
-
|
|
1494
|
-
|
|
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(
|
|
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
|
-
|
|
1624
|
-
|
|
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.
|
|
1627
|
-
|
|
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
|
-
|
|
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: {
|