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.
@@ -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 {
@@ -3542,6 +3621,99 @@ var StormcloudVideoPlayer = class {
3542
3621
  }
3543
3622
  return mediaUrls;
3544
3623
  }
3624
+ async fetchVastDuration(vastTagUrl) {
3625
+ var _a;
3626
+ try {
3627
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3628
+ if (!response.ok) {
3629
+ if (this.config.debugAdTiming) {
3630
+ console.warn(
3631
+ `[ADAPTIVE-POD] Failed to fetch VAST: ${response.status}`
3632
+ );
3633
+ }
3634
+ return null;
3635
+ }
3636
+ const xmlText = await response.text();
3637
+ const parser = new DOMParser();
3638
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3639
+ const durationText = (_a = xmlDoc.querySelector("Duration")) == null ? void 0 : _a.textContent;
3640
+ if (!durationText) {
3641
+ if (this.config.debugAdTiming) {
3642
+ console.warn("[ADAPTIVE-POD] No Duration element found in VAST");
3643
+ }
3644
+ return null;
3645
+ }
3646
+ const durationParts = durationText.split(":");
3647
+ const durationSeconds = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
3648
+ return durationSeconds;
3649
+ } catch (error) {
3650
+ if (this.config.debugAdTiming) {
3651
+ console.warn(
3652
+ `[ADAPTIVE-POD] Error fetching VAST duration from ${vastTagUrl}:`,
3653
+ error
3654
+ );
3655
+ }
3656
+ return null;
3657
+ }
3658
+ }
3659
+ calculateAdditionalAdsNeeded() {
3660
+ if (!this.isAdaptiveMode || this.targetAdBreakDurationMs === null) {
3661
+ return 0;
3662
+ }
3663
+ let totalFetchedDurationMs = 0;
3664
+ for (const duration of this.fetchedAdDurations.values()) {
3665
+ totalFetchedDurationMs += duration * 1e3;
3666
+ }
3667
+ const remainingTimeMs = this.targetAdBreakDurationMs - totalFetchedDurationMs;
3668
+ if (remainingTimeMs <= 0) {
3669
+ if (this.config.debugAdTiming) {
3670
+ console.log(
3671
+ `[ADAPTIVE-POD] \u2705 Target duration reached: ${totalFetchedDurationMs}ms / ${this.targetAdBreakDurationMs}ms`
3672
+ );
3673
+ }
3674
+ return 0;
3675
+ }
3676
+ const fetchedCount = this.fetchedAdDurations.size;
3677
+ const averageDurationMs = fetchedCount > 0 ? totalFetchedDurationMs / fetchedCount : 30 * 1e3;
3678
+ const additionalAds = Math.ceil(remainingTimeMs / averageDurationMs);
3679
+ if (this.config.debugAdTiming) {
3680
+ console.log(
3681
+ `[ADAPTIVE-POD] \u{1F4CA} Need ${additionalAds} more ads | Fetched: ${totalFetchedDurationMs}ms / Target: ${this.targetAdBreakDurationMs}ms | Remaining: ${remainingTimeMs}ms | Avg duration: ${averageDurationMs}ms`
3682
+ );
3683
+ }
3684
+ return additionalAds;
3685
+ }
3686
+ async addAdaptiveAdsToQueue() {
3687
+ if (!this.isAdaptiveMode || !this.apiVastTagUrl) {
3688
+ return;
3689
+ }
3690
+ const additionalAds = this.calculateAdditionalAdsNeeded();
3691
+ if (additionalAds <= 0) {
3692
+ return;
3693
+ }
3694
+ const newUrls = this.generateVastUrlsWithCorrelators(
3695
+ this.apiVastTagUrl,
3696
+ additionalAds
3697
+ );
3698
+ if (this.config.debugAdTiming) {
3699
+ console.log(
3700
+ `[ADAPTIVE-POD] \u{1F504} Adding ${newUrls.length} additional VAST URLs to queue`
3701
+ );
3702
+ }
3703
+ this.adPodAllUrls.push(...newUrls);
3704
+ this.adPodQueue.push(...newUrls);
3705
+ this.totalAdsInBreak += newUrls.length;
3706
+ for (const url of newUrls) {
3707
+ this.preloadSingleAd(url).catch((error) => {
3708
+ if (this.config.debugAdTiming) {
3709
+ console.warn(
3710
+ `[ADAPTIVE-POD] Failed to preload adaptive ad:`,
3711
+ error
3712
+ );
3713
+ }
3714
+ });
3715
+ }
3716
+ }
3545
3717
  async preloadMediaFile(mediaUrl) {
3546
3718
  if (this.preloadedMediaUrls.has(mediaUrl)) {
3547
3719
  return;
@@ -3611,6 +3783,18 @@ var StormcloudVideoPlayer = class {
3611
3783
  async preloadSingleAd(vastTagUrl) {
3612
3784
  if (!vastTagUrl) return;
3613
3785
  try {
3786
+ if (this.isAdaptiveMode && !this.fetchedAdDurations.has(vastTagUrl)) {
3787
+ const duration = await this.fetchVastDuration(vastTagUrl);
3788
+ if (duration !== null) {
3789
+ this.fetchedAdDurations.set(vastTagUrl, duration);
3790
+ if (this.config.debugAdTiming) {
3791
+ console.log(
3792
+ `[ADAPTIVE-POD] \u2713 Fetched ad duration: ${duration}s (${this.fetchedAdDurations.size} ads fetched so far)`
3793
+ );
3794
+ }
3795
+ await this.addAdaptiveAdsToQueue();
3796
+ }
3797
+ }
3614
3798
  if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3615
3799
  if (!this.preloadingAdUrls.has(vastTagUrl)) {
3616
3800
  if (this.config.debugAdTiming) {