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.
@@ -670,19 +670,10 @@ function createImaController(video, options) {
670
670
  adsManager.addEventListener(
671
671
  AdEvent.CONTENT_PAUSE_REQUESTED,
672
672
  () => {
673
- console.log("[DEBUG-FLOW] \u{1F3AF} CONTENT_PAUSE_REQUESTED - Ad starting");
673
+ console.log("[DEBUG-FLOW] \u{1F3AF} CONTENT_PAUSE_REQUESTED - Ad request accepted");
674
674
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
675
675
  video.pause();
676
676
  }
677
- hideContentVideo();
678
- if (adContainerEl) {
679
- adContainerEl.style.pointerEvents = "auto";
680
- adContainerEl.style.display = "flex";
681
- adContainerEl.style.backgroundColor = "#000";
682
- adContainerEl.offsetHeight;
683
- adContainerEl.style.opacity = "1";
684
- console.log("[DEBUG-LAYER] \u{1F7E1} Ad container VISIBLE");
685
- }
686
677
  adPlaying = true;
687
678
  setAdPlayingFlag(true);
688
679
  emit("content_pause");
@@ -2210,6 +2201,9 @@ var StormcloudVideoPlayer = class {
2210
2201
  this.activeAdRequestToken = null;
2211
2202
  this.adRequestWatchdogToken = null;
2212
2203
  this.adFailsafeToken = null;
2204
+ this.fetchedAdDurations = /* @__PURE__ */ new Map();
2205
+ this.targetAdBreakDurationMs = null;
2206
+ this.isAdaptiveMode = false;
2213
2207
  initializePolyfills();
2214
2208
  const browserOverrides = getBrowserConfigOverrides();
2215
2209
  this.config = { ...config, ...browserOverrides };
@@ -2515,7 +2509,7 @@ var StormcloudVideoPlayer = class {
2515
2509
  this.clearAdRequestWatchdog();
2516
2510
  this.activeAdRequestToken = null;
2517
2511
  this.showAds = true;
2518
- this.enforceAdHoldState();
2512
+ console.log("[DEBUG-LAYER] \u{1F3AC} Layers: Main=hidden, Ad=visible, Placeholder=no");
2519
2513
  });
2520
2514
  this.ima.on("content_resume", () => {
2521
2515
  console.log(`[DEBUG-POD] \u23F8\uFE0F content_resume | ad ${this.currentAdIndex}/${this.totalAdsInBreak}, queue=${this.adPodQueue.length}, remaining=${this.getRemainingAdMs()}ms`);
@@ -2695,6 +2689,12 @@ var StormcloudVideoPlayer = class {
2695
2689
  });
2696
2690
  }
2697
2691
  if (marker.type === "start") {
2692
+ if (this.inAdBreak) {
2693
+ console.log(
2694
+ `[DEBUG-POD] \u26A0\uFE0F SCTE-35 start marker ignored - already in ad break (currentTime: ${this.video.currentTime})`
2695
+ );
2696
+ return;
2697
+ }
2698
2698
  this.inAdBreak = true;
2699
2699
  const durationMs = marker.durationSeconds != null ? marker.durationSeconds * 1e3 : void 0;
2700
2700
  this.expectedAdBreakDurationMs = durationMs;
@@ -2761,6 +2761,9 @@ var StormcloudVideoPlayer = class {
2761
2761
  return;
2762
2762
  }
2763
2763
  if (marker.type === "progress" && this.inAdBreak) {
2764
+ console.log(
2765
+ `[DEBUG-POD] \u{1F4CA} SCTE-35 progress marker (currentTime: ${this.video.currentTime})`
2766
+ );
2764
2767
  if (marker.durationSeconds != null) {
2765
2768
  this.expectedAdBreakDurationMs = marker.durationSeconds * 1e3;
2766
2769
  }
@@ -2772,7 +2775,8 @@ var StormcloudVideoPlayer = class {
2772
2775
  );
2773
2776
  this.scheduleAdStopCountdown(remainingMs);
2774
2777
  }
2775
- if (!this.ima.isAdPlaying()) {
2778
+ if (!this.ima.isAdPlaying() && this.activeAdRequestToken === null) {
2779
+ console.log("[DEBUG-POD] \u{1F4CA} Progress marker: no ad playing, attempting to start");
2776
2780
  const scheduled = this.findCurrentOrNextBreak(
2777
2781
  this.video.currentTime * 1e3
2778
2782
  );
@@ -2781,25 +2785,31 @@ var StormcloudVideoPlayer = class {
2781
2785
  const first = tags[0];
2782
2786
  const rest = tags.slice(1);
2783
2787
  this.adPodQueue = rest;
2784
- if (!this.showAds) {
2785
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2786
- }
2787
2788
  this.playSingleAd(first).catch(() => {
2788
2789
  });
2789
2790
  }
2791
+ } else {
2792
+ console.log(
2793
+ `[DEBUG-POD] \u{1F4CA} Progress marker: ad playing or request active (playing=${this.ima.isAdPlaying()}, token=${this.activeAdRequestToken})`
2794
+ );
2790
2795
  }
2791
2796
  return;
2792
2797
  }
2793
2798
  if (marker.type === "end") {
2799
+ console.log(
2800
+ `[DEBUG-POD] \u{1F3C1} SCTE-35 end marker received (currentTime: ${this.video.currentTime})`
2801
+ );
2794
2802
  this.inAdBreak = false;
2795
2803
  this.expectedAdBreakDurationMs = void 0;
2796
2804
  this.currentAdBreakStartWallClockMs = void 0;
2797
2805
  this.clearAdStartTimer();
2798
2806
  this.clearAdStopTimer();
2799
2807
  if (this.ima.isAdPlaying()) {
2808
+ console.log("[DEBUG-POD] \u{1F6D1} Stopping ad due to SCTE-35 end marker");
2800
2809
  this.ima.stop().catch(() => {
2801
2810
  });
2802
2811
  }
2812
+ this.handleAdPodComplete();
2803
2813
  return;
2804
2814
  }
2805
2815
  }
@@ -3000,7 +3010,7 @@ var StormcloudVideoPlayer = class {
3000
3010
  }
3001
3011
  }
3002
3012
  async fetchAdConfiguration() {
3003
- var _a, _b, _c;
3013
+ var _a, _b, _c, _d, _e, _f, _g;
3004
3014
  const vastMode = this.config.vastMode || "default";
3005
3015
  if (this.config.debugAdTiming) {
3006
3016
  console.log("[StormcloudVideoPlayer] VAST mode:", vastMode);
@@ -3071,6 +3081,16 @@ var StormcloudVideoPlayer = class {
3071
3081
  );
3072
3082
  }
3073
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
+ }
3074
3094
  }
3075
3095
  getCurrentAdIndex() {
3076
3096
  return this.currentAdIndex;
@@ -3078,6 +3098,27 @@ var StormcloudVideoPlayer = class {
3078
3098
  getTotalAdsInBreak() {
3079
3099
  return this.totalAdsInBreak;
3080
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
+ }
3081
3122
  isAdPlaying() {
3082
3123
  return this.inAdBreak && this.ima.isAdPlaying();
3083
3124
  }
@@ -3116,7 +3157,52 @@ var StormcloudVideoPlayer = class {
3116
3157
  const tags = this.selectVastTagsForBreak(scheduled);
3117
3158
  let vastTagUrls = [];
3118
3159
  if (this.apiVastTagUrl) {
3119
- 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
+ }
3120
3206
  } else if (tags && tags.length > 0) {
3121
3207
  vastTagUrls = tags;
3122
3208
  } else {
@@ -3130,10 +3216,12 @@ var StormcloudVideoPlayer = class {
3130
3216
  this.vastToMediaUrlMap.clear();
3131
3217
  this.preloadedMediaUrls.clear();
3132
3218
  this.preloadingMediaUrls.clear();
3219
+ const currentMuted = this.video.muted;
3220
+ const currentVolume = this.video.volume;
3133
3221
  console.log(
3134
- `[DEBUG-AUDIO] \u{1F4BE} Capturing original state | muted=${this.video.muted}, volume=${this.video.volume}`
3222
+ `[DEBUG-AUDIO] \u{1F4BE} Capturing ORIGINAL state (once) | muted=${currentMuted}, volume=${currentVolume}`
3135
3223
  );
3136
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3224
+ this.ima.updateOriginalMutedState(currentMuted, currentVolume);
3137
3225
  this.inAdBreak = true;
3138
3226
  this.currentAdIndex = 0;
3139
3227
  this.totalAdsInBreak = vastTagUrls.length;
@@ -3347,7 +3435,6 @@ var StormcloudVideoPlayer = class {
3347
3435
  this.clearAdRequestWatchdog();
3348
3436
  this.clearAdFailsafeTimer();
3349
3437
  this.activeAdRequestToken = null;
3350
- this.releaseAdHoldState();
3351
3438
  this.preloadingAdUrls.clear();
3352
3439
  this.vastToMediaUrlMap.clear();
3353
3440
  this.preloadedMediaUrls.clear();
@@ -3364,13 +3451,18 @@ var StormcloudVideoPlayer = class {
3364
3451
  this.totalAdsInBreak = 0;
3365
3452
  this.ima.stop().catch(() => {
3366
3453
  });
3367
- const originalMutedState = this.ima.getOriginalMutedState();
3368
- const originalVolume = typeof this.ima.getOriginalVolume === "function" ? this.ima.getOriginalVolume() : this.video.volume;
3369
- this.video.muted = originalMutedState;
3370
- this.video.volume = originalVolume;
3454
+ const restoredMuted = this.ima.getOriginalMutedState();
3455
+ const restoredVolume = this.ima.getOriginalVolume();
3371
3456
  console.log(
3372
- `[DEBUG-AUDIO] \u{1F50A} Main video restored | muted=${originalMutedState}, volume=${originalVolume}`
3457
+ `[DEBUG-AUDIO] \u{1F50A} Audio restored by IMA | muted=${restoredMuted}, volume=${restoredVolume}`
3373
3458
  );
3459
+ console.log("[DEBUG-LAYER] \u{1F3AC} Layers: Main=visible, Ad=hidden, Placeholder=no");
3460
+ if (this.video.muted !== restoredMuted) {
3461
+ this.video.muted = restoredMuted;
3462
+ }
3463
+ if (Math.abs(this.video.volume - restoredVolume) > 0.01) {
3464
+ this.video.volume = restoredVolume;
3465
+ }
3374
3466
  if (!this.shouldContinueLiveStreamDuringAds() && this.video.paused) {
3375
3467
  console.log("[DEBUG-FLOW] \u25B6\uFE0F Resuming main video playback");
3376
3468
  (_a = this.video.play()) == null ? void 0 : _a.catch((error) => {
@@ -3379,20 +3471,20 @@ var StormcloudVideoPlayer = class {
3379
3471
  }
3380
3472
  }
3381
3473
  handleAdFailure() {
3474
+ console.log("[DEBUG-POD] \u274C handleAdFailure - skipping to next ad or ending break");
3382
3475
  const remaining = this.getRemainingAdMs();
3383
- console.log(
3384
- `[DEBUG-POD] \u274C handleAdFailure | inBreak=${this.inAdBreak}, showAds=${this.showAds}, remaining=${remaining}ms`
3385
- );
3386
- if (remaining > 500 && this.inAdBreak) {
3387
- console.log(
3388
- `[DEBUG-POD] \u23F3 Ad failed but ${remaining}ms remaining - showing placeholder until duration expires`
3389
- );
3390
- this.showAds = true;
3391
- this.ima.showPlaceholder();
3392
- this.enforceAdHoldState();
3393
- return;
3476
+ if (remaining > 500 && this.adPodQueue.length > 0) {
3477
+ const nextPreloaded = this.findNextPreloadedAd();
3478
+ if (nextPreloaded) {
3479
+ this.currentAdIndex++;
3480
+ console.log(`[DEBUG-POD] \u27A1\uFE0F Trying next ad after failure (${this.currentAdIndex}/${this.totalAdsInBreak})`);
3481
+ this.playSingleAd(nextPreloaded).catch(() => {
3482
+ this.handleAdPodComplete();
3483
+ });
3484
+ return;
3485
+ }
3394
3486
  }
3395
- console.log("[DEBUG-POD] \u23F9\uFE0F No remaining time - ending ad break now");
3487
+ console.log("[DEBUG-POD] \u23F9\uFE0F Ending ad break after failure");
3396
3488
  this.handleAdPodComplete();
3397
3489
  }
3398
3490
  startAdRequestWatchdog(token) {
@@ -3465,12 +3557,6 @@ var StormcloudVideoPlayer = class {
3465
3557
  }
3466
3558
  return [b.vastTagUrl];
3467
3559
  }
3468
- logQueuedAdUrls(urls) {
3469
- if (!this.config.debugAdTiming) {
3470
- return;
3471
- }
3472
- console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3473
- }
3474
3560
  logAdState(event, extra = {}) {
3475
3561
  if (!this.config.debugAdTiming) {
3476
3562
  return;
@@ -3485,22 +3571,6 @@ var StormcloudVideoPlayer = class {
3485
3571
  ...extra
3486
3572
  });
3487
3573
  }
3488
- enforceAdHoldState() {
3489
- this.video.dataset.stormcloudAdPlaying = "true";
3490
- this.video.muted = true;
3491
- this.video.volume = 0;
3492
- console.log("[DEBUG-LAYER] \u{1F512} Enforced ad hold state (main video muted)");
3493
- if (typeof this.ima.showPlaceholder === "function") {
3494
- this.ima.showPlaceholder();
3495
- }
3496
- }
3497
- releaseAdHoldState() {
3498
- delete this.video.dataset.stormcloudAdPlaying;
3499
- console.log("[DEBUG-LAYER] \u{1F513} Released ad hold state");
3500
- if (typeof this.ima.hidePlaceholder === "function") {
3501
- this.ima.hidePlaceholder();
3502
- }
3503
- }
3504
3574
  async fetchAndParseVastXml(vastTagUrl) {
3505
3575
  try {
3506
3576
  const response = await fetch(vastTagUrl, { mode: "cors" });
@@ -3551,6 +3621,99 @@ var StormcloudVideoPlayer = class {
3551
3621
  }
3552
3622
  return mediaUrls;
3553
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
+ }
3554
3717
  async preloadMediaFile(mediaUrl) {
3555
3718
  if (this.preloadedMediaUrls.has(mediaUrl)) {
3556
3719
  return;
@@ -3620,6 +3783,18 @@ var StormcloudVideoPlayer = class {
3620
3783
  async preloadSingleAd(vastTagUrl) {
3621
3784
  if (!vastTagUrl) return;
3622
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
+ }
3623
3798
  if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3624
3799
  if (!this.preloadingAdUrls.has(vastTagUrl)) {
3625
3800
  if (this.config.debugAdTiming) {
@@ -3865,7 +4040,6 @@ var StormcloudVideoPlayer = class {
3865
4040
  }
3866
4041
  (_a = this.hls) == null ? void 0 : _a.destroy();
3867
4042
  (_b = this.ima) == null ? void 0 : _b.destroy();
3868
- this.releaseAdHoldState();
3869
4043
  this.preloadingAdUrls.clear();
3870
4044
  this.vastToMediaUrlMap.clear();
3871
4045
  this.preloadedMediaUrls.clear();