stormcloud-video-player 0.2.36 → 0.3.1

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.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
- vastTagUrls = [this.apiVastTagUrl];
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 {
@@ -3129,7 +3208,8 @@ var StormcloudVideoPlayer = class {
3129
3208
  console.log("[DEBUG-POD] \u26A0\uFE0F No ads in pod");
3130
3209
  return;
3131
3210
  }
3132
- await new Promise((resolve) => setTimeout(resolve, 500));
3211
+ const waitTime = this.isAdaptiveMode ? 1500 : 500;
3212
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
3133
3213
  const firstPreloaded = this.findNextPreloadedAd();
3134
3214
  if (!firstPreloaded) {
3135
3215
  console.log("[DEBUG-POD] \u26A0\uFE0F No preloaded ads after wait, trying first ad");
@@ -3498,6 +3578,89 @@ var StormcloudVideoPlayer = class {
3498
3578
  }
3499
3579
  return mediaUrls;
3500
3580
  }
3581
+ async fetchVastDuration(vastTagUrl) {
3582
+ var _a;
3583
+ try {
3584
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3585
+ if (!response.ok) {
3586
+ if (this.config.debugAdTiming) {
3587
+ console.warn(
3588
+ `[ADAPTIVE-POD] Failed to fetch VAST: ${response.status}`
3589
+ );
3590
+ }
3591
+ return null;
3592
+ }
3593
+ const xmlText = await response.text();
3594
+ const parser = new DOMParser();
3595
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3596
+ const durationText = (_a = xmlDoc.querySelector("Duration")) == null ? void 0 : _a.textContent;
3597
+ if (!durationText) {
3598
+ if (this.config.debugAdTiming) {
3599
+ console.warn("[ADAPTIVE-POD] No Duration element found in VAST");
3600
+ }
3601
+ return null;
3602
+ }
3603
+ const durationParts = durationText.split(":");
3604
+ const durationSeconds = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
3605
+ return durationSeconds;
3606
+ } catch (error) {
3607
+ if (this.config.debugAdTiming) {
3608
+ console.warn(
3609
+ `[ADAPTIVE-POD] Error fetching VAST duration from ${vastTagUrl}:`,
3610
+ error
3611
+ );
3612
+ }
3613
+ return null;
3614
+ }
3615
+ }
3616
+ calculateAdditionalAdsNeeded() {
3617
+ if (!this.isAdaptiveMode || this.targetAdBreakDurationMs === null) {
3618
+ return 0;
3619
+ }
3620
+ let totalFetchedDurationMs = 0;
3621
+ for (const duration of this.fetchedAdDurations.values()) {
3622
+ totalFetchedDurationMs += duration * 1e3;
3623
+ }
3624
+ const remainingTimeMs = this.targetAdBreakDurationMs - totalFetchedDurationMs;
3625
+ if (remainingTimeMs <= 0) {
3626
+ if (this.config.debugAdTiming) {
3627
+ console.log(
3628
+ `[ADAPTIVE-POD] \u2705 Target duration reached: ${totalFetchedDurationMs}ms / ${this.targetAdBreakDurationMs}ms`
3629
+ );
3630
+ }
3631
+ return 0;
3632
+ }
3633
+ const fetchedCount = this.fetchedAdDurations.size;
3634
+ const averageDurationMs = fetchedCount > 0 ? totalFetchedDurationMs / fetchedCount : 30 * 1e3;
3635
+ const additionalAds = Math.ceil(remainingTimeMs / averageDurationMs);
3636
+ if (this.config.debugAdTiming) {
3637
+ console.log(
3638
+ `[ADAPTIVE-POD] \u{1F4CA} Need ${additionalAds} more ads | Fetched: ${totalFetchedDurationMs}ms / Target: ${this.targetAdBreakDurationMs}ms | Remaining: ${remainingTimeMs}ms | Avg duration: ${averageDurationMs}ms`
3639
+ );
3640
+ }
3641
+ return additionalAds;
3642
+ }
3643
+ async addAdaptiveAdsToQueue() {
3644
+ if (!this.isAdaptiveMode || !this.apiVastTagUrl) {
3645
+ return;
3646
+ }
3647
+ const additionalAds = this.calculateAdditionalAdsNeeded();
3648
+ if (additionalAds <= 0) {
3649
+ return;
3650
+ }
3651
+ const newUrls = this.generateVastUrlsWithCorrelators(
3652
+ this.apiVastTagUrl,
3653
+ additionalAds
3654
+ );
3655
+ if (this.config.debugAdTiming) {
3656
+ console.log(
3657
+ `[ADAPTIVE-POD] \u{1F504} Adding ${newUrls.length} additional VAST URLs to queue (will be preloaded sequentially)`
3658
+ );
3659
+ }
3660
+ this.adPodAllUrls.push(...newUrls);
3661
+ this.adPodQueue.push(...newUrls);
3662
+ this.totalAdsInBreak += newUrls.length;
3663
+ }
3501
3664
  async preloadMediaFile(mediaUrl) {
3502
3665
  if (this.preloadedMediaUrls.has(mediaUrl)) {
3503
3666
  return;
@@ -3542,31 +3705,82 @@ var StormcloudVideoPlayer = class {
3542
3705
  if (this.adPodAllUrls.length === 0) {
3543
3706
  return;
3544
3707
  }
3545
- if (this.config.debugAdTiming) {
3546
- console.log(
3547
- `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3548
- );
3549
- }
3550
- const preloadPromises = this.adPodAllUrls.map(
3551
- (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3552
- if (this.config.debugAdTiming) {
3553
- console.warn(
3554
- `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3555
- error
3556
- );
3708
+ if (this.isAdaptiveMode) {
3709
+ if (this.config.debugAdTiming) {
3710
+ console.log(
3711
+ `[ADAPTIVE-POD] Starting sequential preload of ${this.adPodAllUrls.length} initial ads`
3712
+ );
3713
+ }
3714
+ const processedUrls = /* @__PURE__ */ new Set();
3715
+ while (true) {
3716
+ const nextUrl = this.adPodAllUrls.find((url) => !processedUrls.has(url));
3717
+ if (!nextUrl) {
3718
+ break;
3557
3719
  }
3558
- })
3559
- );
3560
- await Promise.all(preloadPromises);
3561
- if (this.config.debugAdTiming) {
3562
- console.log(
3563
- `[StormcloudVideoPlayer] Background preloading completed for all ads`
3720
+ processedUrls.add(nextUrl);
3721
+ try {
3722
+ await this.preloadSingleAd(nextUrl);
3723
+ } catch (error) {
3724
+ if (this.config.debugAdTiming) {
3725
+ console.warn(
3726
+ `[ADAPTIVE-POD] Preload failed for ${nextUrl}:`,
3727
+ error
3728
+ );
3729
+ }
3730
+ }
3731
+ if (this.calculateAdditionalAdsNeeded() === 0) {
3732
+ if (this.config.debugAdTiming) {
3733
+ console.log(
3734
+ `[ADAPTIVE-POD] \u2705 Target duration reached, stopping preload`
3735
+ );
3736
+ }
3737
+ break;
3738
+ }
3739
+ }
3740
+ if (this.config.debugAdTiming) {
3741
+ console.log(
3742
+ `[ADAPTIVE-POD] Sequential preloading completed (${processedUrls.size} ads preloaded)`
3743
+ );
3744
+ }
3745
+ } else {
3746
+ if (this.config.debugAdTiming) {
3747
+ console.log(
3748
+ `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3749
+ );
3750
+ }
3751
+ const preloadPromises = this.adPodAllUrls.map(
3752
+ (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3753
+ if (this.config.debugAdTiming) {
3754
+ console.warn(
3755
+ `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3756
+ error
3757
+ );
3758
+ }
3759
+ })
3564
3760
  );
3761
+ await Promise.all(preloadPromises);
3762
+ if (this.config.debugAdTiming) {
3763
+ console.log(
3764
+ `[StormcloudVideoPlayer] Background preloading completed for all ads`
3765
+ );
3766
+ }
3565
3767
  }
3566
3768
  }
3567
3769
  async preloadSingleAd(vastTagUrl) {
3568
3770
  if (!vastTagUrl) return;
3569
3771
  try {
3772
+ if (this.isAdaptiveMode && !this.fetchedAdDurations.has(vastTagUrl)) {
3773
+ const duration = await this.fetchVastDuration(vastTagUrl);
3774
+ if (duration !== null) {
3775
+ this.fetchedAdDurations.set(vastTagUrl, duration);
3776
+ if (this.config.debugAdTiming) {
3777
+ console.log(
3778
+ `[ADAPTIVE-POD] \u2713 Fetched ad duration: ${duration}s (${this.fetchedAdDurations.size} ads fetched so far)`
3779
+ );
3780
+ }
3781
+ await this.addAdaptiveAdsToQueue();
3782
+ }
3783
+ }
3570
3784
  if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3571
3785
  if (!this.preloadingAdUrls.has(vastTagUrl)) {
3572
3786
  if (this.config.debugAdTiming) {