stormcloud-video-player 0.2.35 → 0.3.0
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/README.md +118 -0
- package/dist/stormcloud-vp.min.js +2 -2
- package/lib/index.cjs +234 -60
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +8 -3
- package/lib/index.d.ts +8 -3
- package/lib/index.js +234 -60
- package/lib/index.js.map +1 -1
- package/lib/player/StormcloudVideoPlayer.cjs +234 -60
- package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/player/StormcloudVideoPlayer.d.cts +8 -3
- package/lib/players/HlsPlayer.cjs +234 -60
- package/lib/players/HlsPlayer.cjs.map +1 -1
- package/lib/players/index.cjs +234 -60
- package/lib/players/index.cjs.map +1 -1
- package/lib/sdk/ima.cjs +1 -10
- package/lib/sdk/ima.cjs.map +1 -1
- package/lib/ui/StormcloudVideoPlayer.cjs +234 -60
- package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ declare class StormcloudVideoPlayer {
|
|
|
15
15
|
private ptsDriftEmaMs;
|
|
16
16
|
private adPodQueue;
|
|
17
17
|
private apiVastTagUrl;
|
|
18
|
+
private apiNumberAds;
|
|
18
19
|
private lastHeartbeatTime;
|
|
19
20
|
private heartbeatInterval;
|
|
20
21
|
private currentAdIndex;
|
|
@@ -36,6 +37,9 @@ declare class StormcloudVideoPlayer {
|
|
|
36
37
|
private adRequestWatchdogId;
|
|
37
38
|
private adRequestWatchdogToken;
|
|
38
39
|
private adFailsafeToken;
|
|
40
|
+
private fetchedAdDurations;
|
|
41
|
+
private targetAdBreakDurationMs;
|
|
42
|
+
private isAdaptiveMode;
|
|
39
43
|
constructor(config: StormcloudVideoPlayerConfig);
|
|
40
44
|
private createAdPlayer;
|
|
41
45
|
load(): Promise<void>;
|
|
@@ -56,6 +60,7 @@ declare class StormcloudVideoPlayer {
|
|
|
56
60
|
private fetchAdConfiguration;
|
|
57
61
|
getCurrentAdIndex(): number;
|
|
58
62
|
getTotalAdsInBreak(): number;
|
|
63
|
+
private generateVastUrlsWithCorrelators;
|
|
59
64
|
isAdPlaying(): boolean;
|
|
60
65
|
isShowingAds(): boolean;
|
|
61
66
|
getStreamType(): "hls" | "other";
|
|
@@ -80,12 +85,12 @@ declare class StormcloudVideoPlayer {
|
|
|
80
85
|
private startAdFailsafeTimer;
|
|
81
86
|
private clearAdFailsafeTimer;
|
|
82
87
|
private selectVastTagsForBreak;
|
|
83
|
-
private logQueuedAdUrls;
|
|
84
88
|
private logAdState;
|
|
85
|
-
private enforceAdHoldState;
|
|
86
|
-
private releaseAdHoldState;
|
|
87
89
|
private fetchAndParseVastXml;
|
|
88
90
|
private extractMediaUrlsFromVast;
|
|
91
|
+
private fetchVastDuration;
|
|
92
|
+
private calculateAdditionalAdsNeeded;
|
|
93
|
+
private addAdaptiveAdsToQueue;
|
|
89
94
|
private preloadMediaFile;
|
|
90
95
|
private preloadAllAdsInBackground;
|
|
91
96
|
private preloadSingleAd;
|
|
@@ -631,19 +631,10 @@ function createImaController(video, options) {
|
|
|
631
631
|
adsManager.addEventListener(
|
|
632
632
|
AdEvent.CONTENT_PAUSE_REQUESTED,
|
|
633
633
|
() => {
|
|
634
|
-
console.log("[DEBUG-FLOW] \u{1F3AF} CONTENT_PAUSE_REQUESTED - Ad
|
|
634
|
+
console.log("[DEBUG-FLOW] \u{1F3AF} CONTENT_PAUSE_REQUESTED - Ad request accepted");
|
|
635
635
|
if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
|
|
636
636
|
video.pause();
|
|
637
637
|
}
|
|
638
|
-
hideContentVideo();
|
|
639
|
-
if (adContainerEl) {
|
|
640
|
-
adContainerEl.style.pointerEvents = "auto";
|
|
641
|
-
adContainerEl.style.display = "flex";
|
|
642
|
-
adContainerEl.style.backgroundColor = "#000";
|
|
643
|
-
adContainerEl.offsetHeight;
|
|
644
|
-
adContainerEl.style.opacity = "1";
|
|
645
|
-
console.log("[DEBUG-LAYER] \u{1F7E1} Ad container VISIBLE");
|
|
646
|
-
}
|
|
647
638
|
adPlaying = true;
|
|
648
639
|
setAdPlayingFlag(true);
|
|
649
640
|
emit("content_pause");
|
|
@@ -2171,6 +2162,9 @@ var StormcloudVideoPlayer = class {
|
|
|
2171
2162
|
this.activeAdRequestToken = null;
|
|
2172
2163
|
this.adRequestWatchdogToken = null;
|
|
2173
2164
|
this.adFailsafeToken = null;
|
|
2165
|
+
this.fetchedAdDurations = /* @__PURE__ */ new Map();
|
|
2166
|
+
this.targetAdBreakDurationMs = null;
|
|
2167
|
+
this.isAdaptiveMode = false;
|
|
2174
2168
|
initializePolyfills();
|
|
2175
2169
|
const browserOverrides = getBrowserConfigOverrides();
|
|
2176
2170
|
this.config = { ...config, ...browserOverrides };
|
|
@@ -2476,7 +2470,7 @@ var StormcloudVideoPlayer = class {
|
|
|
2476
2470
|
this.clearAdRequestWatchdog();
|
|
2477
2471
|
this.activeAdRequestToken = null;
|
|
2478
2472
|
this.showAds = true;
|
|
2479
|
-
|
|
2473
|
+
console.log("[DEBUG-LAYER] \u{1F3AC} Layers: Main=hidden, Ad=visible, Placeholder=no");
|
|
2480
2474
|
});
|
|
2481
2475
|
this.ima.on("content_resume", () => {
|
|
2482
2476
|
console.log(`[DEBUG-POD] \u23F8\uFE0F content_resume | ad ${this.currentAdIndex}/${this.totalAdsInBreak}, queue=${this.adPodQueue.length}, remaining=${this.getRemainingAdMs()}ms`);
|
|
@@ -2656,6 +2650,12 @@ var StormcloudVideoPlayer = class {
|
|
|
2656
2650
|
});
|
|
2657
2651
|
}
|
|
2658
2652
|
if (marker.type === "start") {
|
|
2653
|
+
if (this.inAdBreak) {
|
|
2654
|
+
console.log(
|
|
2655
|
+
`[DEBUG-POD] \u26A0\uFE0F SCTE-35 start marker ignored - already in ad break (currentTime: ${this.video.currentTime})`
|
|
2656
|
+
);
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
2659
|
this.inAdBreak = true;
|
|
2660
2660
|
const durationMs = marker.durationSeconds != null ? marker.durationSeconds * 1e3 : void 0;
|
|
2661
2661
|
this.expectedAdBreakDurationMs = durationMs;
|
|
@@ -2722,6 +2722,9 @@ var StormcloudVideoPlayer = class {
|
|
|
2722
2722
|
return;
|
|
2723
2723
|
}
|
|
2724
2724
|
if (marker.type === "progress" && this.inAdBreak) {
|
|
2725
|
+
console.log(
|
|
2726
|
+
`[DEBUG-POD] \u{1F4CA} SCTE-35 progress marker (currentTime: ${this.video.currentTime})`
|
|
2727
|
+
);
|
|
2725
2728
|
if (marker.durationSeconds != null) {
|
|
2726
2729
|
this.expectedAdBreakDurationMs = marker.durationSeconds * 1e3;
|
|
2727
2730
|
}
|
|
@@ -2733,7 +2736,8 @@ var StormcloudVideoPlayer = class {
|
|
|
2733
2736
|
);
|
|
2734
2737
|
this.scheduleAdStopCountdown(remainingMs);
|
|
2735
2738
|
}
|
|
2736
|
-
if (!this.ima.isAdPlaying()) {
|
|
2739
|
+
if (!this.ima.isAdPlaying() && this.activeAdRequestToken === null) {
|
|
2740
|
+
console.log("[DEBUG-POD] \u{1F4CA} Progress marker: no ad playing, attempting to start");
|
|
2737
2741
|
const scheduled = this.findCurrentOrNextBreak(
|
|
2738
2742
|
this.video.currentTime * 1e3
|
|
2739
2743
|
);
|
|
@@ -2742,25 +2746,31 @@ var StormcloudVideoPlayer = class {
|
|
|
2742
2746
|
const first = tags[0];
|
|
2743
2747
|
const rest = tags.slice(1);
|
|
2744
2748
|
this.adPodQueue = rest;
|
|
2745
|
-
if (!this.showAds) {
|
|
2746
|
-
this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
|
|
2747
|
-
}
|
|
2748
2749
|
this.playSingleAd(first).catch(() => {
|
|
2749
2750
|
});
|
|
2750
2751
|
}
|
|
2752
|
+
} else {
|
|
2753
|
+
console.log(
|
|
2754
|
+
`[DEBUG-POD] \u{1F4CA} Progress marker: ad playing or request active (playing=${this.ima.isAdPlaying()}, token=${this.activeAdRequestToken})`
|
|
2755
|
+
);
|
|
2751
2756
|
}
|
|
2752
2757
|
return;
|
|
2753
2758
|
}
|
|
2754
2759
|
if (marker.type === "end") {
|
|
2760
|
+
console.log(
|
|
2761
|
+
`[DEBUG-POD] \u{1F3C1} SCTE-35 end marker received (currentTime: ${this.video.currentTime})`
|
|
2762
|
+
);
|
|
2755
2763
|
this.inAdBreak = false;
|
|
2756
2764
|
this.expectedAdBreakDurationMs = void 0;
|
|
2757
2765
|
this.currentAdBreakStartWallClockMs = void 0;
|
|
2758
2766
|
this.clearAdStartTimer();
|
|
2759
2767
|
this.clearAdStopTimer();
|
|
2760
2768
|
if (this.ima.isAdPlaying()) {
|
|
2769
|
+
console.log("[DEBUG-POD] \u{1F6D1} Stopping ad due to SCTE-35 end marker");
|
|
2761
2770
|
this.ima.stop().catch(() => {
|
|
2762
2771
|
});
|
|
2763
2772
|
}
|
|
2773
|
+
this.handleAdPodComplete();
|
|
2764
2774
|
return;
|
|
2765
2775
|
}
|
|
2766
2776
|
}
|
|
@@ -2961,7 +2971,7 @@ var StormcloudVideoPlayer = class {
|
|
|
2961
2971
|
}
|
|
2962
2972
|
}
|
|
2963
2973
|
async fetchAdConfiguration() {
|
|
2964
|
-
var _a, _b, _c;
|
|
2974
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
2965
2975
|
const vastMode = this.config.vastMode || "default";
|
|
2966
2976
|
if (this.config.debugAdTiming) {
|
|
2967
2977
|
console.log("[StormcloudVideoPlayer] VAST mode:", vastMode);
|
|
@@ -3032,6 +3042,16 @@ var StormcloudVideoPlayer = class {
|
|
|
3032
3042
|
);
|
|
3033
3043
|
}
|
|
3034
3044
|
}
|
|
3045
|
+
const numberAds = (_g = (_f = (_e = (_d = data.response) == null ? void 0 : _d.options) == null ? void 0 : _e.vast) == null ? void 0 : _f.cue_tones) == null ? void 0 : _g.number_ads;
|
|
3046
|
+
if (numberAds != null && numberAds > 0) {
|
|
3047
|
+
this.apiNumberAds = numberAds;
|
|
3048
|
+
if (this.config.debugAdTiming) {
|
|
3049
|
+
console.log(
|
|
3050
|
+
"[StormcloudVideoPlayer] Number of ads per break from API:",
|
|
3051
|
+
this.apiNumberAds
|
|
3052
|
+
);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3035
3055
|
}
|
|
3036
3056
|
getCurrentAdIndex() {
|
|
3037
3057
|
return this.currentAdIndex;
|
|
@@ -3039,6 +3059,27 @@ var StormcloudVideoPlayer = class {
|
|
|
3039
3059
|
getTotalAdsInBreak() {
|
|
3040
3060
|
return this.totalAdsInBreak;
|
|
3041
3061
|
}
|
|
3062
|
+
generateVastUrlsWithCorrelators(baseUrl, count) {
|
|
3063
|
+
const urls = [];
|
|
3064
|
+
for (let i = 0; i < count; i++) {
|
|
3065
|
+
try {
|
|
3066
|
+
const url = new URL(baseUrl);
|
|
3067
|
+
const timestamp = Date.now();
|
|
3068
|
+
const random = Math.floor(Math.random() * 1e6);
|
|
3069
|
+
const uniqueCorrelator = `${timestamp}${random}${i}`;
|
|
3070
|
+
url.searchParams.set("correlator", uniqueCorrelator);
|
|
3071
|
+
urls.push(url.toString());
|
|
3072
|
+
} catch (error) {
|
|
3073
|
+
console.warn(
|
|
3074
|
+
"[StormcloudVideoPlayer] Failed to parse VAST URL:",
|
|
3075
|
+
baseUrl,
|
|
3076
|
+
error
|
|
3077
|
+
);
|
|
3078
|
+
urls.push(`${baseUrl}${baseUrl.includes("?") ? "&" : "?"}correlator=${Date.now()}${i}`);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
return urls;
|
|
3082
|
+
}
|
|
3042
3083
|
isAdPlaying() {
|
|
3043
3084
|
return this.inAdBreak && this.ima.isAdPlaying();
|
|
3044
3085
|
}
|
|
@@ -3077,7 +3118,52 @@ var StormcloudVideoPlayer = class {
|
|
|
3077
3118
|
const tags = this.selectVastTagsForBreak(scheduled);
|
|
3078
3119
|
let vastTagUrls = [];
|
|
3079
3120
|
if (this.apiVastTagUrl) {
|
|
3080
|
-
|
|
3121
|
+
let numberOfAds = 1;
|
|
3122
|
+
if (this.isLiveStream) {
|
|
3123
|
+
const adBreakDurationMs = _marker.durationSeconds != null ? _marker.durationSeconds * 1e3 : scheduled == null ? void 0 : scheduled.durationMs;
|
|
3124
|
+
if (adBreakDurationMs != null && adBreakDurationMs > 0) {
|
|
3125
|
+
this.isAdaptiveMode = true;
|
|
3126
|
+
this.targetAdBreakDurationMs = adBreakDurationMs;
|
|
3127
|
+
this.fetchedAdDurations.clear();
|
|
3128
|
+
numberOfAds = 2;
|
|
3129
|
+
if (this.config.debugAdTiming) {
|
|
3130
|
+
console.log(
|
|
3131
|
+
`[ADAPTIVE-POD] \u{1F4FA} LIVE MODE (ADAPTIVE): Target duration=${adBreakDurationMs}ms | Starting with ${numberOfAds} ads, will fetch actual durations and add more dynamically`
|
|
3132
|
+
);
|
|
3133
|
+
}
|
|
3134
|
+
} else {
|
|
3135
|
+
if (this.config.debugAdTiming) {
|
|
3136
|
+
console.warn(
|
|
3137
|
+
"[DEBUG-POD] \u26A0\uFE0F LIVE MODE: No duration available, defaulting to 1 ad"
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
} else {
|
|
3142
|
+
this.isAdaptiveMode = false;
|
|
3143
|
+
this.targetAdBreakDurationMs = null;
|
|
3144
|
+
this.fetchedAdDurations.clear();
|
|
3145
|
+
if (this.apiNumberAds && this.apiNumberAds > 1) {
|
|
3146
|
+
numberOfAds = this.apiNumberAds;
|
|
3147
|
+
if (this.config.debugAdTiming) {
|
|
3148
|
+
console.log(
|
|
3149
|
+
`[DEBUG-POD] \u{1F3AC} VOD MODE (FIXED): Using number_ads=${numberOfAds} from API`
|
|
3150
|
+
);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
if (numberOfAds > 1) {
|
|
3155
|
+
vastTagUrls = this.generateVastUrlsWithCorrelators(
|
|
3156
|
+
this.apiVastTagUrl,
|
|
3157
|
+
numberOfAds
|
|
3158
|
+
);
|
|
3159
|
+
if (this.config.debugAdTiming) {
|
|
3160
|
+
console.log(
|
|
3161
|
+
`[DEBUG-POD] \u{1F504} Generated ${vastTagUrls.length} initial VAST URLs with unique correlators`
|
|
3162
|
+
);
|
|
3163
|
+
}
|
|
3164
|
+
} else {
|
|
3165
|
+
vastTagUrls = [this.apiVastTagUrl];
|
|
3166
|
+
}
|
|
3081
3167
|
} else if (tags && tags.length > 0) {
|
|
3082
3168
|
vastTagUrls = tags;
|
|
3083
3169
|
} else {
|
|
@@ -3091,10 +3177,12 @@ var StormcloudVideoPlayer = class {
|
|
|
3091
3177
|
this.vastToMediaUrlMap.clear();
|
|
3092
3178
|
this.preloadedMediaUrls.clear();
|
|
3093
3179
|
this.preloadingMediaUrls.clear();
|
|
3180
|
+
const currentMuted = this.video.muted;
|
|
3181
|
+
const currentVolume = this.video.volume;
|
|
3094
3182
|
console.log(
|
|
3095
|
-
`[DEBUG-AUDIO] \u{1F4BE} Capturing
|
|
3183
|
+
`[DEBUG-AUDIO] \u{1F4BE} Capturing ORIGINAL state (once) | muted=${currentMuted}, volume=${currentVolume}`
|
|
3096
3184
|
);
|
|
3097
|
-
this.ima.updateOriginalMutedState(
|
|
3185
|
+
this.ima.updateOriginalMutedState(currentMuted, currentVolume);
|
|
3098
3186
|
this.inAdBreak = true;
|
|
3099
3187
|
this.currentAdIndex = 0;
|
|
3100
3188
|
this.totalAdsInBreak = vastTagUrls.length;
|
|
@@ -3308,7 +3396,6 @@ var StormcloudVideoPlayer = class {
|
|
|
3308
3396
|
this.clearAdRequestWatchdog();
|
|
3309
3397
|
this.clearAdFailsafeTimer();
|
|
3310
3398
|
this.activeAdRequestToken = null;
|
|
3311
|
-
this.releaseAdHoldState();
|
|
3312
3399
|
this.preloadingAdUrls.clear();
|
|
3313
3400
|
this.vastToMediaUrlMap.clear();
|
|
3314
3401
|
this.preloadedMediaUrls.clear();
|
|
@@ -3325,13 +3412,18 @@ var StormcloudVideoPlayer = class {
|
|
|
3325
3412
|
this.totalAdsInBreak = 0;
|
|
3326
3413
|
this.ima.stop().catch(() => {
|
|
3327
3414
|
});
|
|
3328
|
-
const
|
|
3329
|
-
const
|
|
3330
|
-
this.video.muted = originalMutedState;
|
|
3331
|
-
this.video.volume = originalVolume;
|
|
3415
|
+
const restoredMuted = this.ima.getOriginalMutedState();
|
|
3416
|
+
const restoredVolume = this.ima.getOriginalVolume();
|
|
3332
3417
|
console.log(
|
|
3333
|
-
`[DEBUG-AUDIO] \u{1F50A}
|
|
3418
|
+
`[DEBUG-AUDIO] \u{1F50A} Audio restored by IMA | muted=${restoredMuted}, volume=${restoredVolume}`
|
|
3334
3419
|
);
|
|
3420
|
+
console.log("[DEBUG-LAYER] \u{1F3AC} Layers: Main=visible, Ad=hidden, Placeholder=no");
|
|
3421
|
+
if (this.video.muted !== restoredMuted) {
|
|
3422
|
+
this.video.muted = restoredMuted;
|
|
3423
|
+
}
|
|
3424
|
+
if (Math.abs(this.video.volume - restoredVolume) > 0.01) {
|
|
3425
|
+
this.video.volume = restoredVolume;
|
|
3426
|
+
}
|
|
3335
3427
|
if (!this.shouldContinueLiveStreamDuringAds() && this.video.paused) {
|
|
3336
3428
|
console.log("[DEBUG-FLOW] \u25B6\uFE0F Resuming main video playback");
|
|
3337
3429
|
(_a = this.video.play()) == null ? void 0 : _a.catch((error) => {
|
|
@@ -3340,20 +3432,20 @@ var StormcloudVideoPlayer = class {
|
|
|
3340
3432
|
}
|
|
3341
3433
|
}
|
|
3342
3434
|
handleAdFailure() {
|
|
3435
|
+
console.log("[DEBUG-POD] \u274C handleAdFailure - skipping to next ad or ending break");
|
|
3343
3436
|
const remaining = this.getRemainingAdMs();
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
return;
|
|
3437
|
+
if (remaining > 500 && this.adPodQueue.length > 0) {
|
|
3438
|
+
const nextPreloaded = this.findNextPreloadedAd();
|
|
3439
|
+
if (nextPreloaded) {
|
|
3440
|
+
this.currentAdIndex++;
|
|
3441
|
+
console.log(`[DEBUG-POD] \u27A1\uFE0F Trying next ad after failure (${this.currentAdIndex}/${this.totalAdsInBreak})`);
|
|
3442
|
+
this.playSingleAd(nextPreloaded).catch(() => {
|
|
3443
|
+
this.handleAdPodComplete();
|
|
3444
|
+
});
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3355
3447
|
}
|
|
3356
|
-
console.log("[DEBUG-POD] \u23F9\uFE0F
|
|
3448
|
+
console.log("[DEBUG-POD] \u23F9\uFE0F Ending ad break after failure");
|
|
3357
3449
|
this.handleAdPodComplete();
|
|
3358
3450
|
}
|
|
3359
3451
|
startAdRequestWatchdog(token) {
|
|
@@ -3426,12 +3518,6 @@ var StormcloudVideoPlayer = class {
|
|
|
3426
3518
|
}
|
|
3427
3519
|
return [b.vastTagUrl];
|
|
3428
3520
|
}
|
|
3429
|
-
logQueuedAdUrls(urls) {
|
|
3430
|
-
if (!this.config.debugAdTiming) {
|
|
3431
|
-
return;
|
|
3432
|
-
}
|
|
3433
|
-
console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
|
|
3434
|
-
}
|
|
3435
3521
|
logAdState(event, extra = {}) {
|
|
3436
3522
|
if (!this.config.debugAdTiming) {
|
|
3437
3523
|
return;
|
|
@@ -3446,22 +3532,6 @@ var StormcloudVideoPlayer = class {
|
|
|
3446
3532
|
...extra
|
|
3447
3533
|
});
|
|
3448
3534
|
}
|
|
3449
|
-
enforceAdHoldState() {
|
|
3450
|
-
this.video.dataset.stormcloudAdPlaying = "true";
|
|
3451
|
-
this.video.muted = true;
|
|
3452
|
-
this.video.volume = 0;
|
|
3453
|
-
console.log("[DEBUG-LAYER] \u{1F512} Enforced ad hold state (main video muted)");
|
|
3454
|
-
if (typeof this.ima.showPlaceholder === "function") {
|
|
3455
|
-
this.ima.showPlaceholder();
|
|
3456
|
-
}
|
|
3457
|
-
}
|
|
3458
|
-
releaseAdHoldState() {
|
|
3459
|
-
delete this.video.dataset.stormcloudAdPlaying;
|
|
3460
|
-
console.log("[DEBUG-LAYER] \u{1F513} Released ad hold state");
|
|
3461
|
-
if (typeof this.ima.hidePlaceholder === "function") {
|
|
3462
|
-
this.ima.hidePlaceholder();
|
|
3463
|
-
}
|
|
3464
|
-
}
|
|
3465
3535
|
async fetchAndParseVastXml(vastTagUrl) {
|
|
3466
3536
|
try {
|
|
3467
3537
|
const response = await fetch(vastTagUrl, { mode: "cors" });
|
|
@@ -3512,6 +3582,99 @@ var StormcloudVideoPlayer = class {
|
|
|
3512
3582
|
}
|
|
3513
3583
|
return mediaUrls;
|
|
3514
3584
|
}
|
|
3585
|
+
async fetchVastDuration(vastTagUrl) {
|
|
3586
|
+
var _a;
|
|
3587
|
+
try {
|
|
3588
|
+
const response = await fetch(vastTagUrl, { mode: "cors" });
|
|
3589
|
+
if (!response.ok) {
|
|
3590
|
+
if (this.config.debugAdTiming) {
|
|
3591
|
+
console.warn(
|
|
3592
|
+
`[ADAPTIVE-POD] Failed to fetch VAST: ${response.status}`
|
|
3593
|
+
);
|
|
3594
|
+
}
|
|
3595
|
+
return null;
|
|
3596
|
+
}
|
|
3597
|
+
const xmlText = await response.text();
|
|
3598
|
+
const parser = new DOMParser();
|
|
3599
|
+
const xmlDoc = parser.parseFromString(xmlText, "text/xml");
|
|
3600
|
+
const durationText = (_a = xmlDoc.querySelector("Duration")) == null ? void 0 : _a.textContent;
|
|
3601
|
+
if (!durationText) {
|
|
3602
|
+
if (this.config.debugAdTiming) {
|
|
3603
|
+
console.warn("[ADAPTIVE-POD] No Duration element found in VAST");
|
|
3604
|
+
}
|
|
3605
|
+
return null;
|
|
3606
|
+
}
|
|
3607
|
+
const durationParts = durationText.split(":");
|
|
3608
|
+
const durationSeconds = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
|
|
3609
|
+
return durationSeconds;
|
|
3610
|
+
} catch (error) {
|
|
3611
|
+
if (this.config.debugAdTiming) {
|
|
3612
|
+
console.warn(
|
|
3613
|
+
`[ADAPTIVE-POD] Error fetching VAST duration from ${vastTagUrl}:`,
|
|
3614
|
+
error
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
return null;
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
calculateAdditionalAdsNeeded() {
|
|
3621
|
+
if (!this.isAdaptiveMode || this.targetAdBreakDurationMs === null) {
|
|
3622
|
+
return 0;
|
|
3623
|
+
}
|
|
3624
|
+
let totalFetchedDurationMs = 0;
|
|
3625
|
+
for (const duration of this.fetchedAdDurations.values()) {
|
|
3626
|
+
totalFetchedDurationMs += duration * 1e3;
|
|
3627
|
+
}
|
|
3628
|
+
const remainingTimeMs = this.targetAdBreakDurationMs - totalFetchedDurationMs;
|
|
3629
|
+
if (remainingTimeMs <= 0) {
|
|
3630
|
+
if (this.config.debugAdTiming) {
|
|
3631
|
+
console.log(
|
|
3632
|
+
`[ADAPTIVE-POD] \u2705 Target duration reached: ${totalFetchedDurationMs}ms / ${this.targetAdBreakDurationMs}ms`
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
return 0;
|
|
3636
|
+
}
|
|
3637
|
+
const fetchedCount = this.fetchedAdDurations.size;
|
|
3638
|
+
const averageDurationMs = fetchedCount > 0 ? totalFetchedDurationMs / fetchedCount : 30 * 1e3;
|
|
3639
|
+
const additionalAds = Math.ceil(remainingTimeMs / averageDurationMs);
|
|
3640
|
+
if (this.config.debugAdTiming) {
|
|
3641
|
+
console.log(
|
|
3642
|
+
`[ADAPTIVE-POD] \u{1F4CA} Need ${additionalAds} more ads | Fetched: ${totalFetchedDurationMs}ms / Target: ${this.targetAdBreakDurationMs}ms | Remaining: ${remainingTimeMs}ms | Avg duration: ${averageDurationMs}ms`
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
return additionalAds;
|
|
3646
|
+
}
|
|
3647
|
+
async addAdaptiveAdsToQueue() {
|
|
3648
|
+
if (!this.isAdaptiveMode || !this.apiVastTagUrl) {
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
const additionalAds = this.calculateAdditionalAdsNeeded();
|
|
3652
|
+
if (additionalAds <= 0) {
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
const newUrls = this.generateVastUrlsWithCorrelators(
|
|
3656
|
+
this.apiVastTagUrl,
|
|
3657
|
+
additionalAds
|
|
3658
|
+
);
|
|
3659
|
+
if (this.config.debugAdTiming) {
|
|
3660
|
+
console.log(
|
|
3661
|
+
`[ADAPTIVE-POD] \u{1F504} Adding ${newUrls.length} additional VAST URLs to queue`
|
|
3662
|
+
);
|
|
3663
|
+
}
|
|
3664
|
+
this.adPodAllUrls.push(...newUrls);
|
|
3665
|
+
this.adPodQueue.push(...newUrls);
|
|
3666
|
+
this.totalAdsInBreak += newUrls.length;
|
|
3667
|
+
for (const url of newUrls) {
|
|
3668
|
+
this.preloadSingleAd(url).catch((error) => {
|
|
3669
|
+
if (this.config.debugAdTiming) {
|
|
3670
|
+
console.warn(
|
|
3671
|
+
`[ADAPTIVE-POD] Failed to preload adaptive ad:`,
|
|
3672
|
+
error
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3675
|
+
});
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3515
3678
|
async preloadMediaFile(mediaUrl) {
|
|
3516
3679
|
if (this.preloadedMediaUrls.has(mediaUrl)) {
|
|
3517
3680
|
return;
|
|
@@ -3581,6 +3744,18 @@ var StormcloudVideoPlayer = class {
|
|
|
3581
3744
|
async preloadSingleAd(vastTagUrl) {
|
|
3582
3745
|
if (!vastTagUrl) return;
|
|
3583
3746
|
try {
|
|
3747
|
+
if (this.isAdaptiveMode && !this.fetchedAdDurations.has(vastTagUrl)) {
|
|
3748
|
+
const duration = await this.fetchVastDuration(vastTagUrl);
|
|
3749
|
+
if (duration !== null) {
|
|
3750
|
+
this.fetchedAdDurations.set(vastTagUrl, duration);
|
|
3751
|
+
if (this.config.debugAdTiming) {
|
|
3752
|
+
console.log(
|
|
3753
|
+
`[ADAPTIVE-POD] \u2713 Fetched ad duration: ${duration}s (${this.fetchedAdDurations.size} ads fetched so far)`
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3756
|
+
await this.addAdaptiveAdsToQueue();
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3584
3759
|
if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
|
|
3585
3760
|
if (!this.preloadingAdUrls.has(vastTagUrl)) {
|
|
3586
3761
|
if (this.config.debugAdTiming) {
|
|
@@ -3826,7 +4001,6 @@ var StormcloudVideoPlayer = class {
|
|
|
3826
4001
|
}
|
|
3827
4002
|
(_a = this.hls) == null ? void 0 : _a.destroy();
|
|
3828
4003
|
(_b = this.ima) == null ? void 0 : _b.destroy();
|
|
3829
|
-
this.releaseAdHoldState();
|
|
3830
4004
|
this.preloadingAdUrls.clear();
|
|
3831
4005
|
this.vastToMediaUrlMap.clear();
|
|
3832
4006
|
this.preloadedMediaUrls.clear();
|