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