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/lib/index.cjs CHANGED
@@ -695,19 +695,10 @@ function createImaController(video, options) {
695
695
  adsManager.addEventListener(
696
696
  AdEvent.CONTENT_PAUSE_REQUESTED,
697
697
  () => {
698
- console.log("[DEBUG-FLOW] \u{1F3AF} CONTENT_PAUSE_REQUESTED - Ad starting");
698
+ console.log("[DEBUG-FLOW] \u{1F3AF} CONTENT_PAUSE_REQUESTED - Ad request accepted");
699
699
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
700
700
  video.pause();
701
701
  }
702
- hideContentVideo();
703
- if (adContainerEl) {
704
- adContainerEl.style.pointerEvents = "auto";
705
- adContainerEl.style.display = "flex";
706
- adContainerEl.style.backgroundColor = "#000";
707
- adContainerEl.offsetHeight;
708
- adContainerEl.style.opacity = "1";
709
- console.log("[DEBUG-LAYER] \u{1F7E1} Ad container VISIBLE");
710
- }
711
702
  adPlaying = true;
712
703
  setAdPlayingFlag(true);
713
704
  emit("content_pause");
@@ -2235,6 +2226,9 @@ var StormcloudVideoPlayer = class {
2235
2226
  this.activeAdRequestToken = null;
2236
2227
  this.adRequestWatchdogToken = null;
2237
2228
  this.adFailsafeToken = null;
2229
+ this.fetchedAdDurations = /* @__PURE__ */ new Map();
2230
+ this.targetAdBreakDurationMs = null;
2231
+ this.isAdaptiveMode = false;
2238
2232
  initializePolyfills();
2239
2233
  const browserOverrides = getBrowserConfigOverrides();
2240
2234
  this.config = { ...config, ...browserOverrides };
@@ -2540,7 +2534,7 @@ var StormcloudVideoPlayer = class {
2540
2534
  this.clearAdRequestWatchdog();
2541
2535
  this.activeAdRequestToken = null;
2542
2536
  this.showAds = true;
2543
- this.enforceAdHoldState();
2537
+ console.log("[DEBUG-LAYER] \u{1F3AC} Layers: Main=hidden, Ad=visible, Placeholder=no");
2544
2538
  });
2545
2539
  this.ima.on("content_resume", () => {
2546
2540
  console.log(`[DEBUG-POD] \u23F8\uFE0F content_resume | ad ${this.currentAdIndex}/${this.totalAdsInBreak}, queue=${this.adPodQueue.length}, remaining=${this.getRemainingAdMs()}ms`);
@@ -2720,6 +2714,12 @@ var StormcloudVideoPlayer = class {
2720
2714
  });
2721
2715
  }
2722
2716
  if (marker.type === "start") {
2717
+ if (this.inAdBreak) {
2718
+ console.log(
2719
+ `[DEBUG-POD] \u26A0\uFE0F SCTE-35 start marker ignored - already in ad break (currentTime: ${this.video.currentTime})`
2720
+ );
2721
+ return;
2722
+ }
2723
2723
  this.inAdBreak = true;
2724
2724
  const durationMs = marker.durationSeconds != null ? marker.durationSeconds * 1e3 : void 0;
2725
2725
  this.expectedAdBreakDurationMs = durationMs;
@@ -2786,6 +2786,9 @@ var StormcloudVideoPlayer = class {
2786
2786
  return;
2787
2787
  }
2788
2788
  if (marker.type === "progress" && this.inAdBreak) {
2789
+ console.log(
2790
+ `[DEBUG-POD] \u{1F4CA} SCTE-35 progress marker (currentTime: ${this.video.currentTime})`
2791
+ );
2789
2792
  if (marker.durationSeconds != null) {
2790
2793
  this.expectedAdBreakDurationMs = marker.durationSeconds * 1e3;
2791
2794
  }
@@ -2797,7 +2800,8 @@ var StormcloudVideoPlayer = class {
2797
2800
  );
2798
2801
  this.scheduleAdStopCountdown(remainingMs);
2799
2802
  }
2800
- if (!this.ima.isAdPlaying()) {
2803
+ if (!this.ima.isAdPlaying() && this.activeAdRequestToken === null) {
2804
+ console.log("[DEBUG-POD] \u{1F4CA} Progress marker: no ad playing, attempting to start");
2801
2805
  const scheduled = this.findCurrentOrNextBreak(
2802
2806
  this.video.currentTime * 1e3
2803
2807
  );
@@ -2806,25 +2810,31 @@ var StormcloudVideoPlayer = class {
2806
2810
  const first = tags[0];
2807
2811
  const rest = tags.slice(1);
2808
2812
  this.adPodQueue = rest;
2809
- if (!this.showAds) {
2810
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2811
- }
2812
2813
  this.playSingleAd(first).catch(() => {
2813
2814
  });
2814
2815
  }
2816
+ } else {
2817
+ console.log(
2818
+ `[DEBUG-POD] \u{1F4CA} Progress marker: ad playing or request active (playing=${this.ima.isAdPlaying()}, token=${this.activeAdRequestToken})`
2819
+ );
2815
2820
  }
2816
2821
  return;
2817
2822
  }
2818
2823
  if (marker.type === "end") {
2824
+ console.log(
2825
+ `[DEBUG-POD] \u{1F3C1} SCTE-35 end marker received (currentTime: ${this.video.currentTime})`
2826
+ );
2819
2827
  this.inAdBreak = false;
2820
2828
  this.expectedAdBreakDurationMs = void 0;
2821
2829
  this.currentAdBreakStartWallClockMs = void 0;
2822
2830
  this.clearAdStartTimer();
2823
2831
  this.clearAdStopTimer();
2824
2832
  if (this.ima.isAdPlaying()) {
2833
+ console.log("[DEBUG-POD] \u{1F6D1} Stopping ad due to SCTE-35 end marker");
2825
2834
  this.ima.stop().catch(() => {
2826
2835
  });
2827
2836
  }
2837
+ this.handleAdPodComplete();
2828
2838
  return;
2829
2839
  }
2830
2840
  }
@@ -3025,7 +3035,7 @@ var StormcloudVideoPlayer = class {
3025
3035
  }
3026
3036
  }
3027
3037
  async fetchAdConfiguration() {
3028
- var _a, _b, _c;
3038
+ var _a, _b, _c, _d, _e, _f, _g;
3029
3039
  const vastMode = this.config.vastMode || "default";
3030
3040
  if (this.config.debugAdTiming) {
3031
3041
  console.log("[StormcloudVideoPlayer] VAST mode:", vastMode);
@@ -3096,6 +3106,16 @@ var StormcloudVideoPlayer = class {
3096
3106
  );
3097
3107
  }
3098
3108
  }
3109
+ 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;
3110
+ if (numberAds != null && numberAds > 0) {
3111
+ this.apiNumberAds = numberAds;
3112
+ if (this.config.debugAdTiming) {
3113
+ console.log(
3114
+ "[StormcloudVideoPlayer] Number of ads per break from API:",
3115
+ this.apiNumberAds
3116
+ );
3117
+ }
3118
+ }
3099
3119
  }
3100
3120
  getCurrentAdIndex() {
3101
3121
  return this.currentAdIndex;
@@ -3103,6 +3123,27 @@ var StormcloudVideoPlayer = class {
3103
3123
  getTotalAdsInBreak() {
3104
3124
  return this.totalAdsInBreak;
3105
3125
  }
3126
+ generateVastUrlsWithCorrelators(baseUrl, count) {
3127
+ const urls = [];
3128
+ for (let i = 0; i < count; i++) {
3129
+ try {
3130
+ const url = new URL(baseUrl);
3131
+ const timestamp = Date.now();
3132
+ const random = Math.floor(Math.random() * 1e6);
3133
+ const uniqueCorrelator = `${timestamp}${random}${i}`;
3134
+ url.searchParams.set("correlator", uniqueCorrelator);
3135
+ urls.push(url.toString());
3136
+ } catch (error) {
3137
+ console.warn(
3138
+ "[StormcloudVideoPlayer] Failed to parse VAST URL:",
3139
+ baseUrl,
3140
+ error
3141
+ );
3142
+ urls.push(`${baseUrl}${baseUrl.includes("?") ? "&" : "?"}correlator=${Date.now()}${i}`);
3143
+ }
3144
+ }
3145
+ return urls;
3146
+ }
3106
3147
  isAdPlaying() {
3107
3148
  return this.inAdBreak && this.ima.isAdPlaying();
3108
3149
  }
@@ -3141,7 +3182,52 @@ var StormcloudVideoPlayer = class {
3141
3182
  const tags = this.selectVastTagsForBreak(scheduled);
3142
3183
  let vastTagUrls = [];
3143
3184
  if (this.apiVastTagUrl) {
3144
- vastTagUrls = [this.apiVastTagUrl];
3185
+ let numberOfAds = 1;
3186
+ if (this.isLiveStream) {
3187
+ const adBreakDurationMs = _marker.durationSeconds != null ? _marker.durationSeconds * 1e3 : scheduled == null ? void 0 : scheduled.durationMs;
3188
+ if (adBreakDurationMs != null && adBreakDurationMs > 0) {
3189
+ this.isAdaptiveMode = true;
3190
+ this.targetAdBreakDurationMs = adBreakDurationMs;
3191
+ this.fetchedAdDurations.clear();
3192
+ numberOfAds = 2;
3193
+ if (this.config.debugAdTiming) {
3194
+ console.log(
3195
+ `[ADAPTIVE-POD] \u{1F4FA} LIVE MODE (ADAPTIVE): Target duration=${adBreakDurationMs}ms | Starting with ${numberOfAds} ads, will fetch actual durations and add more dynamically`
3196
+ );
3197
+ }
3198
+ } else {
3199
+ if (this.config.debugAdTiming) {
3200
+ console.warn(
3201
+ "[DEBUG-POD] \u26A0\uFE0F LIVE MODE: No duration available, defaulting to 1 ad"
3202
+ );
3203
+ }
3204
+ }
3205
+ } else {
3206
+ this.isAdaptiveMode = false;
3207
+ this.targetAdBreakDurationMs = null;
3208
+ this.fetchedAdDurations.clear();
3209
+ if (this.apiNumberAds && this.apiNumberAds > 1) {
3210
+ numberOfAds = this.apiNumberAds;
3211
+ if (this.config.debugAdTiming) {
3212
+ console.log(
3213
+ `[DEBUG-POD] \u{1F3AC} VOD MODE (FIXED): Using number_ads=${numberOfAds} from API`
3214
+ );
3215
+ }
3216
+ }
3217
+ }
3218
+ if (numberOfAds > 1) {
3219
+ vastTagUrls = this.generateVastUrlsWithCorrelators(
3220
+ this.apiVastTagUrl,
3221
+ numberOfAds
3222
+ );
3223
+ if (this.config.debugAdTiming) {
3224
+ console.log(
3225
+ `[DEBUG-POD] \u{1F504} Generated ${vastTagUrls.length} initial VAST URLs with unique correlators`
3226
+ );
3227
+ }
3228
+ } else {
3229
+ vastTagUrls = [this.apiVastTagUrl];
3230
+ }
3145
3231
  } else if (tags && tags.length > 0) {
3146
3232
  vastTagUrls = tags;
3147
3233
  } else {
@@ -3155,10 +3241,12 @@ var StormcloudVideoPlayer = class {
3155
3241
  this.vastToMediaUrlMap.clear();
3156
3242
  this.preloadedMediaUrls.clear();
3157
3243
  this.preloadingMediaUrls.clear();
3244
+ const currentMuted = this.video.muted;
3245
+ const currentVolume = this.video.volume;
3158
3246
  console.log(
3159
- `[DEBUG-AUDIO] \u{1F4BE} Capturing original state | muted=${this.video.muted}, volume=${this.video.volume}`
3247
+ `[DEBUG-AUDIO] \u{1F4BE} Capturing ORIGINAL state (once) | muted=${currentMuted}, volume=${currentVolume}`
3160
3248
  );
3161
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3249
+ this.ima.updateOriginalMutedState(currentMuted, currentVolume);
3162
3250
  this.inAdBreak = true;
3163
3251
  this.currentAdIndex = 0;
3164
3252
  this.totalAdsInBreak = vastTagUrls.length;
@@ -3372,7 +3460,6 @@ var StormcloudVideoPlayer = class {
3372
3460
  this.clearAdRequestWatchdog();
3373
3461
  this.clearAdFailsafeTimer();
3374
3462
  this.activeAdRequestToken = null;
3375
- this.releaseAdHoldState();
3376
3463
  this.preloadingAdUrls.clear();
3377
3464
  this.vastToMediaUrlMap.clear();
3378
3465
  this.preloadedMediaUrls.clear();
@@ -3389,13 +3476,18 @@ var StormcloudVideoPlayer = class {
3389
3476
  this.totalAdsInBreak = 0;
3390
3477
  this.ima.stop().catch(() => {
3391
3478
  });
3392
- const originalMutedState = this.ima.getOriginalMutedState();
3393
- const originalVolume = typeof this.ima.getOriginalVolume === "function" ? this.ima.getOriginalVolume() : this.video.volume;
3394
- this.video.muted = originalMutedState;
3395
- this.video.volume = originalVolume;
3479
+ const restoredMuted = this.ima.getOriginalMutedState();
3480
+ const restoredVolume = this.ima.getOriginalVolume();
3396
3481
  console.log(
3397
- `[DEBUG-AUDIO] \u{1F50A} Main video restored | muted=${originalMutedState}, volume=${originalVolume}`
3482
+ `[DEBUG-AUDIO] \u{1F50A} Audio restored by IMA | muted=${restoredMuted}, volume=${restoredVolume}`
3398
3483
  );
3484
+ console.log("[DEBUG-LAYER] \u{1F3AC} Layers: Main=visible, Ad=hidden, Placeholder=no");
3485
+ if (this.video.muted !== restoredMuted) {
3486
+ this.video.muted = restoredMuted;
3487
+ }
3488
+ if (Math.abs(this.video.volume - restoredVolume) > 0.01) {
3489
+ this.video.volume = restoredVolume;
3490
+ }
3399
3491
  if (!this.shouldContinueLiveStreamDuringAds() && this.video.paused) {
3400
3492
  console.log("[DEBUG-FLOW] \u25B6\uFE0F Resuming main video playback");
3401
3493
  (_a = this.video.play()) == null ? void 0 : _a.catch((error) => {
@@ -3404,20 +3496,20 @@ var StormcloudVideoPlayer = class {
3404
3496
  }
3405
3497
  }
3406
3498
  handleAdFailure() {
3499
+ console.log("[DEBUG-POD] \u274C handleAdFailure - skipping to next ad or ending break");
3407
3500
  const remaining = this.getRemainingAdMs();
3408
- console.log(
3409
- `[DEBUG-POD] \u274C handleAdFailure | inBreak=${this.inAdBreak}, showAds=${this.showAds}, remaining=${remaining}ms`
3410
- );
3411
- if (remaining > 500 && this.inAdBreak) {
3412
- console.log(
3413
- `[DEBUG-POD] \u23F3 Ad failed but ${remaining}ms remaining - showing placeholder until duration expires`
3414
- );
3415
- this.showAds = true;
3416
- this.ima.showPlaceholder();
3417
- this.enforceAdHoldState();
3418
- return;
3501
+ if (remaining > 500 && this.adPodQueue.length > 0) {
3502
+ const nextPreloaded = this.findNextPreloadedAd();
3503
+ if (nextPreloaded) {
3504
+ this.currentAdIndex++;
3505
+ console.log(`[DEBUG-POD] \u27A1\uFE0F Trying next ad after failure (${this.currentAdIndex}/${this.totalAdsInBreak})`);
3506
+ this.playSingleAd(nextPreloaded).catch(() => {
3507
+ this.handleAdPodComplete();
3508
+ });
3509
+ return;
3510
+ }
3419
3511
  }
3420
- console.log("[DEBUG-POD] \u23F9\uFE0F No remaining time - ending ad break now");
3512
+ console.log("[DEBUG-POD] \u23F9\uFE0F Ending ad break after failure");
3421
3513
  this.handleAdPodComplete();
3422
3514
  }
3423
3515
  startAdRequestWatchdog(token) {
@@ -3490,12 +3582,6 @@ var StormcloudVideoPlayer = class {
3490
3582
  }
3491
3583
  return [b.vastTagUrl];
3492
3584
  }
3493
- logQueuedAdUrls(urls) {
3494
- if (!this.config.debugAdTiming) {
3495
- return;
3496
- }
3497
- console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3498
- }
3499
3585
  logAdState(event, extra = {}) {
3500
3586
  if (!this.config.debugAdTiming) {
3501
3587
  return;
@@ -3510,22 +3596,6 @@ var StormcloudVideoPlayer = class {
3510
3596
  ...extra
3511
3597
  });
3512
3598
  }
3513
- enforceAdHoldState() {
3514
- this.video.dataset.stormcloudAdPlaying = "true";
3515
- this.video.muted = true;
3516
- this.video.volume = 0;
3517
- console.log("[DEBUG-LAYER] \u{1F512} Enforced ad hold state (main video muted)");
3518
- if (typeof this.ima.showPlaceholder === "function") {
3519
- this.ima.showPlaceholder();
3520
- }
3521
- }
3522
- releaseAdHoldState() {
3523
- delete this.video.dataset.stormcloudAdPlaying;
3524
- console.log("[DEBUG-LAYER] \u{1F513} Released ad hold state");
3525
- if (typeof this.ima.hidePlaceholder === "function") {
3526
- this.ima.hidePlaceholder();
3527
- }
3528
- }
3529
3599
  async fetchAndParseVastXml(vastTagUrl) {
3530
3600
  try {
3531
3601
  const response = await fetch(vastTagUrl, { mode: "cors" });
@@ -3576,6 +3646,99 @@ var StormcloudVideoPlayer = class {
3576
3646
  }
3577
3647
  return mediaUrls;
3578
3648
  }
3649
+ async fetchVastDuration(vastTagUrl) {
3650
+ var _a;
3651
+ try {
3652
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3653
+ if (!response.ok) {
3654
+ if (this.config.debugAdTiming) {
3655
+ console.warn(
3656
+ `[ADAPTIVE-POD] Failed to fetch VAST: ${response.status}`
3657
+ );
3658
+ }
3659
+ return null;
3660
+ }
3661
+ const xmlText = await response.text();
3662
+ const parser = new DOMParser();
3663
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3664
+ const durationText = (_a = xmlDoc.querySelector("Duration")) == null ? void 0 : _a.textContent;
3665
+ if (!durationText) {
3666
+ if (this.config.debugAdTiming) {
3667
+ console.warn("[ADAPTIVE-POD] No Duration element found in VAST");
3668
+ }
3669
+ return null;
3670
+ }
3671
+ const durationParts = durationText.split(":");
3672
+ const durationSeconds = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
3673
+ return durationSeconds;
3674
+ } catch (error) {
3675
+ if (this.config.debugAdTiming) {
3676
+ console.warn(
3677
+ `[ADAPTIVE-POD] Error fetching VAST duration from ${vastTagUrl}:`,
3678
+ error
3679
+ );
3680
+ }
3681
+ return null;
3682
+ }
3683
+ }
3684
+ calculateAdditionalAdsNeeded() {
3685
+ if (!this.isAdaptiveMode || this.targetAdBreakDurationMs === null) {
3686
+ return 0;
3687
+ }
3688
+ let totalFetchedDurationMs = 0;
3689
+ for (const duration of this.fetchedAdDurations.values()) {
3690
+ totalFetchedDurationMs += duration * 1e3;
3691
+ }
3692
+ const remainingTimeMs = this.targetAdBreakDurationMs - totalFetchedDurationMs;
3693
+ if (remainingTimeMs <= 0) {
3694
+ if (this.config.debugAdTiming) {
3695
+ console.log(
3696
+ `[ADAPTIVE-POD] \u2705 Target duration reached: ${totalFetchedDurationMs}ms / ${this.targetAdBreakDurationMs}ms`
3697
+ );
3698
+ }
3699
+ return 0;
3700
+ }
3701
+ const fetchedCount = this.fetchedAdDurations.size;
3702
+ const averageDurationMs = fetchedCount > 0 ? totalFetchedDurationMs / fetchedCount : 30 * 1e3;
3703
+ const additionalAds = Math.ceil(remainingTimeMs / averageDurationMs);
3704
+ if (this.config.debugAdTiming) {
3705
+ console.log(
3706
+ `[ADAPTIVE-POD] \u{1F4CA} Need ${additionalAds} more ads | Fetched: ${totalFetchedDurationMs}ms / Target: ${this.targetAdBreakDurationMs}ms | Remaining: ${remainingTimeMs}ms | Avg duration: ${averageDurationMs}ms`
3707
+ );
3708
+ }
3709
+ return additionalAds;
3710
+ }
3711
+ async addAdaptiveAdsToQueue() {
3712
+ if (!this.isAdaptiveMode || !this.apiVastTagUrl) {
3713
+ return;
3714
+ }
3715
+ const additionalAds = this.calculateAdditionalAdsNeeded();
3716
+ if (additionalAds <= 0) {
3717
+ return;
3718
+ }
3719
+ const newUrls = this.generateVastUrlsWithCorrelators(
3720
+ this.apiVastTagUrl,
3721
+ additionalAds
3722
+ );
3723
+ if (this.config.debugAdTiming) {
3724
+ console.log(
3725
+ `[ADAPTIVE-POD] \u{1F504} Adding ${newUrls.length} additional VAST URLs to queue`
3726
+ );
3727
+ }
3728
+ this.adPodAllUrls.push(...newUrls);
3729
+ this.adPodQueue.push(...newUrls);
3730
+ this.totalAdsInBreak += newUrls.length;
3731
+ for (const url of newUrls) {
3732
+ this.preloadSingleAd(url).catch((error) => {
3733
+ if (this.config.debugAdTiming) {
3734
+ console.warn(
3735
+ `[ADAPTIVE-POD] Failed to preload adaptive ad:`,
3736
+ error
3737
+ );
3738
+ }
3739
+ });
3740
+ }
3741
+ }
3579
3742
  async preloadMediaFile(mediaUrl) {
3580
3743
  if (this.preloadedMediaUrls.has(mediaUrl)) {
3581
3744
  return;
@@ -3645,6 +3808,18 @@ var StormcloudVideoPlayer = class {
3645
3808
  async preloadSingleAd(vastTagUrl) {
3646
3809
  if (!vastTagUrl) return;
3647
3810
  try {
3811
+ if (this.isAdaptiveMode && !this.fetchedAdDurations.has(vastTagUrl)) {
3812
+ const duration = await this.fetchVastDuration(vastTagUrl);
3813
+ if (duration !== null) {
3814
+ this.fetchedAdDurations.set(vastTagUrl, duration);
3815
+ if (this.config.debugAdTiming) {
3816
+ console.log(
3817
+ `[ADAPTIVE-POD] \u2713 Fetched ad duration: ${duration}s (${this.fetchedAdDurations.size} ads fetched so far)`
3818
+ );
3819
+ }
3820
+ await this.addAdaptiveAdsToQueue();
3821
+ }
3822
+ }
3648
3823
  if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3649
3824
  if (!this.preloadingAdUrls.has(vastTagUrl)) {
3650
3825
  if (this.config.debugAdTiming) {
@@ -3890,7 +4065,6 @@ var StormcloudVideoPlayer = class {
3890
4065
  }
3891
4066
  (_a = this.hls) == null ? void 0 : _a.destroy();
3892
4067
  (_b = this.ima) == null ? void 0 : _b.destroy();
3893
- this.releaseAdHoldState();
3894
4068
  this.preloadingAdUrls.clear();
3895
4069
  this.vastToMediaUrlMap.clear();
3896
4070
  this.preloadedMediaUrls.clear();