stormcloud-video-player 0.2.36 → 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 +186 -2
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +8 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +186 -2
- package/lib/index.js.map +1 -1
- package/lib/player/StormcloudVideoPlayer.cjs +186 -2
- package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/player/StormcloudVideoPlayer.d.cts +8 -0
- package/lib/players/HlsPlayer.cjs +186 -2
- package/lib/players/HlsPlayer.cjs.map +1 -1
- package/lib/players/index.cjs +186 -2
- package/lib/players/index.cjs.map +1 -1
- package/lib/ui/StormcloudVideoPlayer.cjs +186 -2
- package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
- package/package.json +1 -1
package/lib/index.d.cts
CHANGED
|
@@ -116,6 +116,7 @@ declare class StormcloudVideoPlayer {
|
|
|
116
116
|
private ptsDriftEmaMs;
|
|
117
117
|
private adPodQueue;
|
|
118
118
|
private apiVastTagUrl;
|
|
119
|
+
private apiNumberAds;
|
|
119
120
|
private lastHeartbeatTime;
|
|
120
121
|
private heartbeatInterval;
|
|
121
122
|
private currentAdIndex;
|
|
@@ -137,6 +138,9 @@ declare class StormcloudVideoPlayer {
|
|
|
137
138
|
private adRequestWatchdogId;
|
|
138
139
|
private adRequestWatchdogToken;
|
|
139
140
|
private adFailsafeToken;
|
|
141
|
+
private fetchedAdDurations;
|
|
142
|
+
private targetAdBreakDurationMs;
|
|
143
|
+
private isAdaptiveMode;
|
|
140
144
|
constructor(config: StormcloudVideoPlayerConfig);
|
|
141
145
|
private createAdPlayer;
|
|
142
146
|
load(): Promise<void>;
|
|
@@ -157,6 +161,7 @@ declare class StormcloudVideoPlayer {
|
|
|
157
161
|
private fetchAdConfiguration;
|
|
158
162
|
getCurrentAdIndex(): number;
|
|
159
163
|
getTotalAdsInBreak(): number;
|
|
164
|
+
private generateVastUrlsWithCorrelators;
|
|
160
165
|
isAdPlaying(): boolean;
|
|
161
166
|
isShowingAds(): boolean;
|
|
162
167
|
getStreamType(): "hls" | "other";
|
|
@@ -184,6 +189,9 @@ declare class StormcloudVideoPlayer {
|
|
|
184
189
|
private logAdState;
|
|
185
190
|
private fetchAndParseVastXml;
|
|
186
191
|
private extractMediaUrlsFromVast;
|
|
192
|
+
private fetchVastDuration;
|
|
193
|
+
private calculateAdditionalAdsNeeded;
|
|
194
|
+
private addAdaptiveAdsToQueue;
|
|
187
195
|
private preloadMediaFile;
|
|
188
196
|
private preloadAllAdsInBackground;
|
|
189
197
|
private preloadSingleAd;
|
package/lib/index.d.ts
CHANGED
|
@@ -116,6 +116,7 @@ declare class StormcloudVideoPlayer {
|
|
|
116
116
|
private ptsDriftEmaMs;
|
|
117
117
|
private adPodQueue;
|
|
118
118
|
private apiVastTagUrl;
|
|
119
|
+
private apiNumberAds;
|
|
119
120
|
private lastHeartbeatTime;
|
|
120
121
|
private heartbeatInterval;
|
|
121
122
|
private currentAdIndex;
|
|
@@ -137,6 +138,9 @@ declare class StormcloudVideoPlayer {
|
|
|
137
138
|
private adRequestWatchdogId;
|
|
138
139
|
private adRequestWatchdogToken;
|
|
139
140
|
private adFailsafeToken;
|
|
141
|
+
private fetchedAdDurations;
|
|
142
|
+
private targetAdBreakDurationMs;
|
|
143
|
+
private isAdaptiveMode;
|
|
140
144
|
constructor(config: StormcloudVideoPlayerConfig);
|
|
141
145
|
private createAdPlayer;
|
|
142
146
|
load(): Promise<void>;
|
|
@@ -157,6 +161,7 @@ declare class StormcloudVideoPlayer {
|
|
|
157
161
|
private fetchAdConfiguration;
|
|
158
162
|
getCurrentAdIndex(): number;
|
|
159
163
|
getTotalAdsInBreak(): number;
|
|
164
|
+
private generateVastUrlsWithCorrelators;
|
|
160
165
|
isAdPlaying(): boolean;
|
|
161
166
|
isShowingAds(): boolean;
|
|
162
167
|
getStreamType(): "hls" | "other";
|
|
@@ -184,6 +189,9 @@ declare class StormcloudVideoPlayer {
|
|
|
184
189
|
private logAdState;
|
|
185
190
|
private fetchAndParseVastXml;
|
|
186
191
|
private extractMediaUrlsFromVast;
|
|
192
|
+
private fetchVastDuration;
|
|
193
|
+
private calculateAdditionalAdsNeeded;
|
|
194
|
+
private addAdaptiveAdsToQueue;
|
|
187
195
|
private preloadMediaFile;
|
|
188
196
|
private preloadAllAdsInBackground;
|
|
189
197
|
private preloadSingleAd;
|
package/lib/index.js
CHANGED
|
@@ -2157,6 +2157,9 @@ var StormcloudVideoPlayer = class {
|
|
|
2157
2157
|
this.activeAdRequestToken = null;
|
|
2158
2158
|
this.adRequestWatchdogToken = null;
|
|
2159
2159
|
this.adFailsafeToken = null;
|
|
2160
|
+
this.fetchedAdDurations = /* @__PURE__ */ new Map();
|
|
2161
|
+
this.targetAdBreakDurationMs = null;
|
|
2162
|
+
this.isAdaptiveMode = false;
|
|
2160
2163
|
initializePolyfills();
|
|
2161
2164
|
const browserOverrides = getBrowserConfigOverrides();
|
|
2162
2165
|
this.config = { ...config, ...browserOverrides };
|
|
@@ -2963,7 +2966,7 @@ var StormcloudVideoPlayer = class {
|
|
|
2963
2966
|
}
|
|
2964
2967
|
}
|
|
2965
2968
|
async fetchAdConfiguration() {
|
|
2966
|
-
var _a, _b, _c;
|
|
2969
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
2967
2970
|
const vastMode = this.config.vastMode || "default";
|
|
2968
2971
|
if (this.config.debugAdTiming) {
|
|
2969
2972
|
console.log("[StormcloudVideoPlayer] VAST mode:", vastMode);
|
|
@@ -3034,6 +3037,16 @@ var StormcloudVideoPlayer = class {
|
|
|
3034
3037
|
);
|
|
3035
3038
|
}
|
|
3036
3039
|
}
|
|
3040
|
+
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;
|
|
3041
|
+
if (numberAds != null && numberAds > 0) {
|
|
3042
|
+
this.apiNumberAds = numberAds;
|
|
3043
|
+
if (this.config.debugAdTiming) {
|
|
3044
|
+
console.log(
|
|
3045
|
+
"[StormcloudVideoPlayer] Number of ads per break from API:",
|
|
3046
|
+
this.apiNumberAds
|
|
3047
|
+
);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3037
3050
|
}
|
|
3038
3051
|
getCurrentAdIndex() {
|
|
3039
3052
|
return this.currentAdIndex;
|
|
@@ -3041,6 +3054,27 @@ var StormcloudVideoPlayer = class {
|
|
|
3041
3054
|
getTotalAdsInBreak() {
|
|
3042
3055
|
return this.totalAdsInBreak;
|
|
3043
3056
|
}
|
|
3057
|
+
generateVastUrlsWithCorrelators(baseUrl, count) {
|
|
3058
|
+
const urls = [];
|
|
3059
|
+
for (let i = 0; i < count; i++) {
|
|
3060
|
+
try {
|
|
3061
|
+
const url = new URL(baseUrl);
|
|
3062
|
+
const timestamp = Date.now();
|
|
3063
|
+
const random = Math.floor(Math.random() * 1e6);
|
|
3064
|
+
const uniqueCorrelator = `${timestamp}${random}${i}`;
|
|
3065
|
+
url.searchParams.set("correlator", uniqueCorrelator);
|
|
3066
|
+
urls.push(url.toString());
|
|
3067
|
+
} catch (error) {
|
|
3068
|
+
console.warn(
|
|
3069
|
+
"[StormcloudVideoPlayer] Failed to parse VAST URL:",
|
|
3070
|
+
baseUrl,
|
|
3071
|
+
error
|
|
3072
|
+
);
|
|
3073
|
+
urls.push(`${baseUrl}${baseUrl.includes("?") ? "&" : "?"}correlator=${Date.now()}${i}`);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
return urls;
|
|
3077
|
+
}
|
|
3044
3078
|
isAdPlaying() {
|
|
3045
3079
|
return this.inAdBreak && this.ima.isAdPlaying();
|
|
3046
3080
|
}
|
|
@@ -3079,7 +3113,52 @@ var StormcloudVideoPlayer = class {
|
|
|
3079
3113
|
const tags = this.selectVastTagsForBreak(scheduled);
|
|
3080
3114
|
let vastTagUrls = [];
|
|
3081
3115
|
if (this.apiVastTagUrl) {
|
|
3082
|
-
|
|
3116
|
+
let numberOfAds = 1;
|
|
3117
|
+
if (this.isLiveStream) {
|
|
3118
|
+
const adBreakDurationMs = _marker.durationSeconds != null ? _marker.durationSeconds * 1e3 : scheduled == null ? void 0 : scheduled.durationMs;
|
|
3119
|
+
if (adBreakDurationMs != null && adBreakDurationMs > 0) {
|
|
3120
|
+
this.isAdaptiveMode = true;
|
|
3121
|
+
this.targetAdBreakDurationMs = adBreakDurationMs;
|
|
3122
|
+
this.fetchedAdDurations.clear();
|
|
3123
|
+
numberOfAds = 2;
|
|
3124
|
+
if (this.config.debugAdTiming) {
|
|
3125
|
+
console.log(
|
|
3126
|
+
`[ADAPTIVE-POD] \u{1F4FA} LIVE MODE (ADAPTIVE): Target duration=${adBreakDurationMs}ms | Starting with ${numberOfAds} ads, will fetch actual durations and add more dynamically`
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
} else {
|
|
3130
|
+
if (this.config.debugAdTiming) {
|
|
3131
|
+
console.warn(
|
|
3132
|
+
"[DEBUG-POD] \u26A0\uFE0F LIVE MODE: No duration available, defaulting to 1 ad"
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
} else {
|
|
3137
|
+
this.isAdaptiveMode = false;
|
|
3138
|
+
this.targetAdBreakDurationMs = null;
|
|
3139
|
+
this.fetchedAdDurations.clear();
|
|
3140
|
+
if (this.apiNumberAds && this.apiNumberAds > 1) {
|
|
3141
|
+
numberOfAds = this.apiNumberAds;
|
|
3142
|
+
if (this.config.debugAdTiming) {
|
|
3143
|
+
console.log(
|
|
3144
|
+
`[DEBUG-POD] \u{1F3AC} VOD MODE (FIXED): Using number_ads=${numberOfAds} from API`
|
|
3145
|
+
);
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
if (numberOfAds > 1) {
|
|
3150
|
+
vastTagUrls = this.generateVastUrlsWithCorrelators(
|
|
3151
|
+
this.apiVastTagUrl,
|
|
3152
|
+
numberOfAds
|
|
3153
|
+
);
|
|
3154
|
+
if (this.config.debugAdTiming) {
|
|
3155
|
+
console.log(
|
|
3156
|
+
`[DEBUG-POD] \u{1F504} Generated ${vastTagUrls.length} initial VAST URLs with unique correlators`
|
|
3157
|
+
);
|
|
3158
|
+
}
|
|
3159
|
+
} else {
|
|
3160
|
+
vastTagUrls = [this.apiVastTagUrl];
|
|
3161
|
+
}
|
|
3083
3162
|
} else if (tags && tags.length > 0) {
|
|
3084
3163
|
vastTagUrls = tags;
|
|
3085
3164
|
} else {
|
|
@@ -3498,6 +3577,99 @@ var StormcloudVideoPlayer = class {
|
|
|
3498
3577
|
}
|
|
3499
3578
|
return mediaUrls;
|
|
3500
3579
|
}
|
|
3580
|
+
async fetchVastDuration(vastTagUrl) {
|
|
3581
|
+
var _a;
|
|
3582
|
+
try {
|
|
3583
|
+
const response = await fetch(vastTagUrl, { mode: "cors" });
|
|
3584
|
+
if (!response.ok) {
|
|
3585
|
+
if (this.config.debugAdTiming) {
|
|
3586
|
+
console.warn(
|
|
3587
|
+
`[ADAPTIVE-POD] Failed to fetch VAST: ${response.status}`
|
|
3588
|
+
);
|
|
3589
|
+
}
|
|
3590
|
+
return null;
|
|
3591
|
+
}
|
|
3592
|
+
const xmlText = await response.text();
|
|
3593
|
+
const parser = new DOMParser();
|
|
3594
|
+
const xmlDoc = parser.parseFromString(xmlText, "text/xml");
|
|
3595
|
+
const durationText = (_a = xmlDoc.querySelector("Duration")) == null ? void 0 : _a.textContent;
|
|
3596
|
+
if (!durationText) {
|
|
3597
|
+
if (this.config.debugAdTiming) {
|
|
3598
|
+
console.warn("[ADAPTIVE-POD] No Duration element found in VAST");
|
|
3599
|
+
}
|
|
3600
|
+
return null;
|
|
3601
|
+
}
|
|
3602
|
+
const durationParts = durationText.split(":");
|
|
3603
|
+
const durationSeconds = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
|
|
3604
|
+
return durationSeconds;
|
|
3605
|
+
} catch (error) {
|
|
3606
|
+
if (this.config.debugAdTiming) {
|
|
3607
|
+
console.warn(
|
|
3608
|
+
`[ADAPTIVE-POD] Error fetching VAST duration from ${vastTagUrl}:`,
|
|
3609
|
+
error
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3612
|
+
return null;
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
calculateAdditionalAdsNeeded() {
|
|
3616
|
+
if (!this.isAdaptiveMode || this.targetAdBreakDurationMs === null) {
|
|
3617
|
+
return 0;
|
|
3618
|
+
}
|
|
3619
|
+
let totalFetchedDurationMs = 0;
|
|
3620
|
+
for (const duration of this.fetchedAdDurations.values()) {
|
|
3621
|
+
totalFetchedDurationMs += duration * 1e3;
|
|
3622
|
+
}
|
|
3623
|
+
const remainingTimeMs = this.targetAdBreakDurationMs - totalFetchedDurationMs;
|
|
3624
|
+
if (remainingTimeMs <= 0) {
|
|
3625
|
+
if (this.config.debugAdTiming) {
|
|
3626
|
+
console.log(
|
|
3627
|
+
`[ADAPTIVE-POD] \u2705 Target duration reached: ${totalFetchedDurationMs}ms / ${this.targetAdBreakDurationMs}ms`
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
3630
|
+
return 0;
|
|
3631
|
+
}
|
|
3632
|
+
const fetchedCount = this.fetchedAdDurations.size;
|
|
3633
|
+
const averageDurationMs = fetchedCount > 0 ? totalFetchedDurationMs / fetchedCount : 30 * 1e3;
|
|
3634
|
+
const additionalAds = Math.ceil(remainingTimeMs / averageDurationMs);
|
|
3635
|
+
if (this.config.debugAdTiming) {
|
|
3636
|
+
console.log(
|
|
3637
|
+
`[ADAPTIVE-POD] \u{1F4CA} Need ${additionalAds} more ads | Fetched: ${totalFetchedDurationMs}ms / Target: ${this.targetAdBreakDurationMs}ms | Remaining: ${remainingTimeMs}ms | Avg duration: ${averageDurationMs}ms`
|
|
3638
|
+
);
|
|
3639
|
+
}
|
|
3640
|
+
return additionalAds;
|
|
3641
|
+
}
|
|
3642
|
+
async addAdaptiveAdsToQueue() {
|
|
3643
|
+
if (!this.isAdaptiveMode || !this.apiVastTagUrl) {
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
const additionalAds = this.calculateAdditionalAdsNeeded();
|
|
3647
|
+
if (additionalAds <= 0) {
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
const newUrls = this.generateVastUrlsWithCorrelators(
|
|
3651
|
+
this.apiVastTagUrl,
|
|
3652
|
+
additionalAds
|
|
3653
|
+
);
|
|
3654
|
+
if (this.config.debugAdTiming) {
|
|
3655
|
+
console.log(
|
|
3656
|
+
`[ADAPTIVE-POD] \u{1F504} Adding ${newUrls.length} additional VAST URLs to queue`
|
|
3657
|
+
);
|
|
3658
|
+
}
|
|
3659
|
+
this.adPodAllUrls.push(...newUrls);
|
|
3660
|
+
this.adPodQueue.push(...newUrls);
|
|
3661
|
+
this.totalAdsInBreak += newUrls.length;
|
|
3662
|
+
for (const url of newUrls) {
|
|
3663
|
+
this.preloadSingleAd(url).catch((error) => {
|
|
3664
|
+
if (this.config.debugAdTiming) {
|
|
3665
|
+
console.warn(
|
|
3666
|
+
`[ADAPTIVE-POD] Failed to preload adaptive ad:`,
|
|
3667
|
+
error
|
|
3668
|
+
);
|
|
3669
|
+
}
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3501
3673
|
async preloadMediaFile(mediaUrl) {
|
|
3502
3674
|
if (this.preloadedMediaUrls.has(mediaUrl)) {
|
|
3503
3675
|
return;
|
|
@@ -3567,6 +3739,18 @@ var StormcloudVideoPlayer = class {
|
|
|
3567
3739
|
async preloadSingleAd(vastTagUrl) {
|
|
3568
3740
|
if (!vastTagUrl) return;
|
|
3569
3741
|
try {
|
|
3742
|
+
if (this.isAdaptiveMode && !this.fetchedAdDurations.has(vastTagUrl)) {
|
|
3743
|
+
const duration = await this.fetchVastDuration(vastTagUrl);
|
|
3744
|
+
if (duration !== null) {
|
|
3745
|
+
this.fetchedAdDurations.set(vastTagUrl, duration);
|
|
3746
|
+
if (this.config.debugAdTiming) {
|
|
3747
|
+
console.log(
|
|
3748
|
+
`[ADAPTIVE-POD] \u2713 Fetched ad duration: ${duration}s (${this.fetchedAdDurations.size} ads fetched so far)`
|
|
3749
|
+
);
|
|
3750
|
+
}
|
|
3751
|
+
await this.addAdaptiveAdsToQueue();
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3570
3754
|
if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
|
|
3571
3755
|
if (!this.preloadingAdUrls.has(vastTagUrl)) {
|
|
3572
3756
|
if (this.config.debugAdTiming) {
|