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.
@@ -2201,6 +2201,9 @@ var StormcloudVideoPlayer = class {
2201
2201
  this.activeAdRequestToken = null;
2202
2202
  this.adRequestWatchdogToken = null;
2203
2203
  this.adFailsafeToken = null;
2204
+ this.fetchedAdDurations = /* @__PURE__ */ new Map();
2205
+ this.targetAdBreakDurationMs = null;
2206
+ this.isAdaptiveMode = false;
2204
2207
  initializePolyfills();
2205
2208
  const browserOverrides = getBrowserConfigOverrides();
2206
2209
  this.config = { ...config, ...browserOverrides };
@@ -3007,7 +3010,7 @@ var StormcloudVideoPlayer = class {
3007
3010
  }
3008
3011
  }
3009
3012
  async fetchAdConfiguration() {
3010
- var _a, _b, _c;
3013
+ var _a, _b, _c, _d, _e, _f, _g;
3011
3014
  const vastMode = this.config.vastMode || "default";
3012
3015
  if (this.config.debugAdTiming) {
3013
3016
  console.log("[StormcloudVideoPlayer] VAST mode:", vastMode);
@@ -3078,6 +3081,16 @@ var StormcloudVideoPlayer = class {
3078
3081
  );
3079
3082
  }
3080
3083
  }
3084
+ 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;
3085
+ if (numberAds != null && numberAds > 0) {
3086
+ this.apiNumberAds = numberAds;
3087
+ if (this.config.debugAdTiming) {
3088
+ console.log(
3089
+ "[StormcloudVideoPlayer] Number of ads per break from API:",
3090
+ this.apiNumberAds
3091
+ );
3092
+ }
3093
+ }
3081
3094
  }
3082
3095
  getCurrentAdIndex() {
3083
3096
  return this.currentAdIndex;
@@ -3085,6 +3098,27 @@ var StormcloudVideoPlayer = class {
3085
3098
  getTotalAdsInBreak() {
3086
3099
  return this.totalAdsInBreak;
3087
3100
  }
3101
+ generateVastUrlsWithCorrelators(baseUrl, count) {
3102
+ const urls = [];
3103
+ for (let i = 0; i < count; i++) {
3104
+ try {
3105
+ const url = new URL(baseUrl);
3106
+ const timestamp = Date.now();
3107
+ const random = Math.floor(Math.random() * 1e6);
3108
+ const uniqueCorrelator = `${timestamp}${random}${i}`;
3109
+ url.searchParams.set("correlator", uniqueCorrelator);
3110
+ urls.push(url.toString());
3111
+ } catch (error) {
3112
+ console.warn(
3113
+ "[StormcloudVideoPlayer] Failed to parse VAST URL:",
3114
+ baseUrl,
3115
+ error
3116
+ );
3117
+ urls.push(`${baseUrl}${baseUrl.includes("?") ? "&" : "?"}correlator=${Date.now()}${i}`);
3118
+ }
3119
+ }
3120
+ return urls;
3121
+ }
3088
3122
  isAdPlaying() {
3089
3123
  return this.inAdBreak && this.ima.isAdPlaying();
3090
3124
  }
@@ -3123,7 +3157,52 @@ var StormcloudVideoPlayer = class {
3123
3157
  const tags = this.selectVastTagsForBreak(scheduled);
3124
3158
  let vastTagUrls = [];
3125
3159
  if (this.apiVastTagUrl) {
3126
- vastTagUrls = [this.apiVastTagUrl];
3160
+ let numberOfAds = 1;
3161
+ if (this.isLiveStream) {
3162
+ const adBreakDurationMs = _marker.durationSeconds != null ? _marker.durationSeconds * 1e3 : scheduled == null ? void 0 : scheduled.durationMs;
3163
+ if (adBreakDurationMs != null && adBreakDurationMs > 0) {
3164
+ this.isAdaptiveMode = true;
3165
+ this.targetAdBreakDurationMs = adBreakDurationMs;
3166
+ this.fetchedAdDurations.clear();
3167
+ numberOfAds = 2;
3168
+ if (this.config.debugAdTiming) {
3169
+ console.log(
3170
+ `[ADAPTIVE-POD] \u{1F4FA} LIVE MODE (ADAPTIVE): Target duration=${adBreakDurationMs}ms | Starting with ${numberOfAds} ads, will fetch actual durations and add more dynamically`
3171
+ );
3172
+ }
3173
+ } else {
3174
+ if (this.config.debugAdTiming) {
3175
+ console.warn(
3176
+ "[DEBUG-POD] \u26A0\uFE0F LIVE MODE: No duration available, defaulting to 1 ad"
3177
+ );
3178
+ }
3179
+ }
3180
+ } else {
3181
+ this.isAdaptiveMode = false;
3182
+ this.targetAdBreakDurationMs = null;
3183
+ this.fetchedAdDurations.clear();
3184
+ if (this.apiNumberAds && this.apiNumberAds > 1) {
3185
+ numberOfAds = this.apiNumberAds;
3186
+ if (this.config.debugAdTiming) {
3187
+ console.log(
3188
+ `[DEBUG-POD] \u{1F3AC} VOD MODE (FIXED): Using number_ads=${numberOfAds} from API`
3189
+ );
3190
+ }
3191
+ }
3192
+ }
3193
+ if (numberOfAds > 1) {
3194
+ vastTagUrls = this.generateVastUrlsWithCorrelators(
3195
+ this.apiVastTagUrl,
3196
+ numberOfAds
3197
+ );
3198
+ if (this.config.debugAdTiming) {
3199
+ console.log(
3200
+ `[DEBUG-POD] \u{1F504} Generated ${vastTagUrls.length} initial VAST URLs with unique correlators`
3201
+ );
3202
+ }
3203
+ } else {
3204
+ vastTagUrls = [this.apiVastTagUrl];
3205
+ }
3127
3206
  } else if (tags && tags.length > 0) {
3128
3207
  vastTagUrls = tags;
3129
3208
  } else {
@@ -3173,7 +3252,8 @@ var StormcloudVideoPlayer = class {
3173
3252
  console.log("[DEBUG-POD] \u26A0\uFE0F No ads in pod");
3174
3253
  return;
3175
3254
  }
3176
- await new Promise((resolve) => setTimeout(resolve, 500));
3255
+ const waitTime = this.isAdaptiveMode ? 1500 : 500;
3256
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
3177
3257
  const firstPreloaded = this.findNextPreloadedAd();
3178
3258
  if (!firstPreloaded) {
3179
3259
  console.log("[DEBUG-POD] \u26A0\uFE0F No preloaded ads after wait, trying first ad");
@@ -3542,6 +3622,89 @@ var StormcloudVideoPlayer = class {
3542
3622
  }
3543
3623
  return mediaUrls;
3544
3624
  }
3625
+ async fetchVastDuration(vastTagUrl) {
3626
+ var _a;
3627
+ try {
3628
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3629
+ if (!response.ok) {
3630
+ if (this.config.debugAdTiming) {
3631
+ console.warn(
3632
+ `[ADAPTIVE-POD] Failed to fetch VAST: ${response.status}`
3633
+ );
3634
+ }
3635
+ return null;
3636
+ }
3637
+ const xmlText = await response.text();
3638
+ const parser = new DOMParser();
3639
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3640
+ const durationText = (_a = xmlDoc.querySelector("Duration")) == null ? void 0 : _a.textContent;
3641
+ if (!durationText) {
3642
+ if (this.config.debugAdTiming) {
3643
+ console.warn("[ADAPTIVE-POD] No Duration element found in VAST");
3644
+ }
3645
+ return null;
3646
+ }
3647
+ const durationParts = durationText.split(":");
3648
+ const durationSeconds = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
3649
+ return durationSeconds;
3650
+ } catch (error) {
3651
+ if (this.config.debugAdTiming) {
3652
+ console.warn(
3653
+ `[ADAPTIVE-POD] Error fetching VAST duration from ${vastTagUrl}:`,
3654
+ error
3655
+ );
3656
+ }
3657
+ return null;
3658
+ }
3659
+ }
3660
+ calculateAdditionalAdsNeeded() {
3661
+ if (!this.isAdaptiveMode || this.targetAdBreakDurationMs === null) {
3662
+ return 0;
3663
+ }
3664
+ let totalFetchedDurationMs = 0;
3665
+ for (const duration of this.fetchedAdDurations.values()) {
3666
+ totalFetchedDurationMs += duration * 1e3;
3667
+ }
3668
+ const remainingTimeMs = this.targetAdBreakDurationMs - totalFetchedDurationMs;
3669
+ if (remainingTimeMs <= 0) {
3670
+ if (this.config.debugAdTiming) {
3671
+ console.log(
3672
+ `[ADAPTIVE-POD] \u2705 Target duration reached: ${totalFetchedDurationMs}ms / ${this.targetAdBreakDurationMs}ms`
3673
+ );
3674
+ }
3675
+ return 0;
3676
+ }
3677
+ const fetchedCount = this.fetchedAdDurations.size;
3678
+ const averageDurationMs = fetchedCount > 0 ? totalFetchedDurationMs / fetchedCount : 30 * 1e3;
3679
+ const additionalAds = Math.ceil(remainingTimeMs / averageDurationMs);
3680
+ if (this.config.debugAdTiming) {
3681
+ console.log(
3682
+ `[ADAPTIVE-POD] \u{1F4CA} Need ${additionalAds} more ads | Fetched: ${totalFetchedDurationMs}ms / Target: ${this.targetAdBreakDurationMs}ms | Remaining: ${remainingTimeMs}ms | Avg duration: ${averageDurationMs}ms`
3683
+ );
3684
+ }
3685
+ return additionalAds;
3686
+ }
3687
+ async addAdaptiveAdsToQueue() {
3688
+ if (!this.isAdaptiveMode || !this.apiVastTagUrl) {
3689
+ return;
3690
+ }
3691
+ const additionalAds = this.calculateAdditionalAdsNeeded();
3692
+ if (additionalAds <= 0) {
3693
+ return;
3694
+ }
3695
+ const newUrls = this.generateVastUrlsWithCorrelators(
3696
+ this.apiVastTagUrl,
3697
+ additionalAds
3698
+ );
3699
+ if (this.config.debugAdTiming) {
3700
+ console.log(
3701
+ `[ADAPTIVE-POD] \u{1F504} Adding ${newUrls.length} additional VAST URLs to queue (will be preloaded sequentially)`
3702
+ );
3703
+ }
3704
+ this.adPodAllUrls.push(...newUrls);
3705
+ this.adPodQueue.push(...newUrls);
3706
+ this.totalAdsInBreak += newUrls.length;
3707
+ }
3545
3708
  async preloadMediaFile(mediaUrl) {
3546
3709
  if (this.preloadedMediaUrls.has(mediaUrl)) {
3547
3710
  return;
@@ -3586,31 +3749,82 @@ var StormcloudVideoPlayer = class {
3586
3749
  if (this.adPodAllUrls.length === 0) {
3587
3750
  return;
3588
3751
  }
3589
- if (this.config.debugAdTiming) {
3590
- console.log(
3591
- `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3592
- );
3593
- }
3594
- const preloadPromises = this.adPodAllUrls.map(
3595
- (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3596
- if (this.config.debugAdTiming) {
3597
- console.warn(
3598
- `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3599
- error
3600
- );
3752
+ if (this.isAdaptiveMode) {
3753
+ if (this.config.debugAdTiming) {
3754
+ console.log(
3755
+ `[ADAPTIVE-POD] Starting sequential preload of ${this.adPodAllUrls.length} initial ads`
3756
+ );
3757
+ }
3758
+ const processedUrls = /* @__PURE__ */ new Set();
3759
+ while (true) {
3760
+ const nextUrl = this.adPodAllUrls.find((url) => !processedUrls.has(url));
3761
+ if (!nextUrl) {
3762
+ break;
3601
3763
  }
3602
- })
3603
- );
3604
- await Promise.all(preloadPromises);
3605
- if (this.config.debugAdTiming) {
3606
- console.log(
3607
- `[StormcloudVideoPlayer] Background preloading completed for all ads`
3764
+ processedUrls.add(nextUrl);
3765
+ try {
3766
+ await this.preloadSingleAd(nextUrl);
3767
+ } catch (error) {
3768
+ if (this.config.debugAdTiming) {
3769
+ console.warn(
3770
+ `[ADAPTIVE-POD] Preload failed for ${nextUrl}:`,
3771
+ error
3772
+ );
3773
+ }
3774
+ }
3775
+ if (this.calculateAdditionalAdsNeeded() === 0) {
3776
+ if (this.config.debugAdTiming) {
3777
+ console.log(
3778
+ `[ADAPTIVE-POD] \u2705 Target duration reached, stopping preload`
3779
+ );
3780
+ }
3781
+ break;
3782
+ }
3783
+ }
3784
+ if (this.config.debugAdTiming) {
3785
+ console.log(
3786
+ `[ADAPTIVE-POD] Sequential preloading completed (${processedUrls.size} ads preloaded)`
3787
+ );
3788
+ }
3789
+ } else {
3790
+ if (this.config.debugAdTiming) {
3791
+ console.log(
3792
+ `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3793
+ );
3794
+ }
3795
+ const preloadPromises = this.adPodAllUrls.map(
3796
+ (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3797
+ if (this.config.debugAdTiming) {
3798
+ console.warn(
3799
+ `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3800
+ error
3801
+ );
3802
+ }
3803
+ })
3608
3804
  );
3805
+ await Promise.all(preloadPromises);
3806
+ if (this.config.debugAdTiming) {
3807
+ console.log(
3808
+ `[StormcloudVideoPlayer] Background preloading completed for all ads`
3809
+ );
3810
+ }
3609
3811
  }
3610
3812
  }
3611
3813
  async preloadSingleAd(vastTagUrl) {
3612
3814
  if (!vastTagUrl) return;
3613
3815
  try {
3816
+ if (this.isAdaptiveMode && !this.fetchedAdDurations.has(vastTagUrl)) {
3817
+ const duration = await this.fetchVastDuration(vastTagUrl);
3818
+ if (duration !== null) {
3819
+ this.fetchedAdDurations.set(vastTagUrl, duration);
3820
+ if (this.config.debugAdTiming) {
3821
+ console.log(
3822
+ `[ADAPTIVE-POD] \u2713 Fetched ad duration: ${duration}s (${this.fetchedAdDurations.size} ads fetched so far)`
3823
+ );
3824
+ }
3825
+ await this.addAdaptiveAdsToQueue();
3826
+ }
3827
+ }
3614
3828
  if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3615
3829
  if (!this.preloadingAdUrls.has(vastTagUrl)) {
3616
3830
  if (this.config.debugAdTiming) {