stormcloud-video-player 0.2.31 → 0.2.32

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.
@@ -681,6 +681,15 @@ function createImaController(video, options) {
681
681
  "[IMA] Content video continues in background (Live mode)"
682
682
  );
683
683
  }
684
+ hideContentVideo();
685
+ if (adContainerEl) {
686
+ adContainerEl.style.pointerEvents = "auto";
687
+ adContainerEl.style.display = "flex";
688
+ adContainerEl.style.backgroundColor = "#000";
689
+ adContainerEl.offsetHeight;
690
+ adContainerEl.style.opacity = "1";
691
+ console.log("[IMA] Ad container shown on content pause");
692
+ }
684
693
  adPlaying = true;
685
694
  setAdPlayingFlag(true);
686
695
  emit("content_pause");
@@ -2215,6 +2224,9 @@ var StormcloudVideoPlayer = class {
2215
2224
  this.hasInitialBufferCompleted = false;
2216
2225
  this.adPodAllUrls = [];
2217
2226
  this.preloadingAdUrls = /* @__PURE__ */ new Set();
2227
+ this.vastToMediaUrlMap = /* @__PURE__ */ new Map();
2228
+ this.preloadedMediaUrls = /* @__PURE__ */ new Set();
2229
+ this.preloadingMediaUrls = /* @__PURE__ */ new Set();
2218
2230
  initializePolyfills();
2219
2231
  const browserOverrides = getBrowserConfigOverrides();
2220
2232
  this.config = { ...config, ...browserOverrides };
@@ -2487,27 +2499,44 @@ var StormcloudVideoPlayer = class {
2487
2499
  });
2488
2500
  this.ima.on("ad_error", () => {
2489
2501
  if (this.config.debugAdTiming) {
2490
- console.log("[StormcloudVideoPlayer] IMA ad_error event received");
2502
+ console.log("[StormcloudVideoPlayer] IMA ad_error event received", {
2503
+ showAds: this.showAds,
2504
+ inAdBreak: this.inAdBreak,
2505
+ remainingAds: this.adPodQueue.length
2506
+ });
2491
2507
  }
2492
- if (this.showAds) {
2493
- if (this.inAdBreak) {
2494
- const remaining = this.getRemainingAdMs();
2495
- if (remaining > 500 && this.adPodQueue.length > 0) {
2496
- const next = this.adPodQueue.shift();
2508
+ if (this.inAdBreak) {
2509
+ const remaining = this.getRemainingAdMs();
2510
+ if (remaining > 500 && this.adPodQueue.length > 0) {
2511
+ const nextPreloaded = this.findNextPreloadedAd();
2512
+ if (nextPreloaded) {
2497
2513
  this.currentAdIndex++;
2498
- this.playSingleAd(next).catch(() => {
2514
+ if (this.config.debugAdTiming) {
2515
+ console.log(
2516
+ `[StormcloudVideoPlayer] Skipping to next preloaded ad after error`
2517
+ );
2518
+ }
2519
+ this.playSingleAd(nextPreloaded).catch(() => {
2520
+ this.handleAdFailure();
2499
2521
  });
2500
2522
  } else {
2523
+ if (this.config.debugAdTiming) {
2524
+ console.log(
2525
+ "[StormcloudVideoPlayer] No preloaded ads available, ending ad break"
2526
+ );
2527
+ }
2501
2528
  this.handleAdFailure();
2502
2529
  }
2503
2530
  } else {
2504
- if (this.config.debugAdTiming) {
2505
- console.log(
2506
- "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2507
- );
2508
- }
2509
2531
  this.handleAdFailure();
2510
2532
  }
2533
+ } else {
2534
+ if (this.config.debugAdTiming) {
2535
+ console.log(
2536
+ "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2537
+ );
2538
+ }
2539
+ this.handleAdFailure();
2511
2540
  }
2512
2541
  });
2513
2542
  this.ima.on("content_pause", () => {
@@ -2538,22 +2567,31 @@ var StormcloudVideoPlayer = class {
2538
2567
  }
2539
2568
  const remaining = this.getRemainingAdMs();
2540
2569
  if (remaining > 500 && this.adPodQueue.length > 0) {
2541
- const next = this.adPodQueue.shift();
2542
- this.currentAdIndex++;
2543
- this.enforceAdHoldState();
2544
- if (this.config.debugAdTiming) {
2545
- console.log(
2546
- `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
2547
- );
2548
- }
2549
- this.playSingleAd(next).catch(() => {
2570
+ const nextPreloaded = this.findNextPreloadedAd();
2571
+ if (nextPreloaded) {
2572
+ this.currentAdIndex++;
2573
+ this.enforceAdHoldState();
2550
2574
  if (this.config.debugAdTiming) {
2551
- console.error(
2552
- "[StormcloudVideoPlayer] Failed to play next ad in pod"
2575
+ console.log(
2576
+ `[StormcloudVideoPlayer] Playing next preloaded ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
2577
+ );
2578
+ }
2579
+ this.playSingleAd(nextPreloaded).catch(() => {
2580
+ if (this.config.debugAdTiming) {
2581
+ console.error(
2582
+ "[StormcloudVideoPlayer] Failed to play next ad in pod"
2583
+ );
2584
+ }
2585
+ this.handleAdPodComplete();
2586
+ });
2587
+ } else {
2588
+ if (this.config.debugAdTiming) {
2589
+ console.log(
2590
+ "[StormcloudVideoPlayer] No preloaded ads available - completing ad break"
2553
2591
  );
2554
2592
  }
2555
2593
  this.handleAdPodComplete();
2556
- });
2594
+ }
2557
2595
  } else {
2558
2596
  if (this.config.debugAdTiming) {
2559
2597
  console.log(
@@ -2798,6 +2836,9 @@ var StormcloudVideoPlayer = class {
2798
2836
  const first = tags[0];
2799
2837
  const rest = tags.slice(1);
2800
2838
  this.adPodQueue = rest;
2839
+ if (!this.showAds) {
2840
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2841
+ }
2801
2842
  this.playSingleAd(first).catch(() => {
2802
2843
  });
2803
2844
  }
@@ -3154,19 +3195,38 @@ var StormcloudVideoPlayer = class {
3154
3195
  if (vastTagUrls.length > 0) {
3155
3196
  this.adPodAllUrls = [...vastTagUrls];
3156
3197
  this.preloadingAdUrls.clear();
3198
+ this.vastToMediaUrlMap.clear();
3199
+ this.preloadedMediaUrls.clear();
3200
+ this.preloadingMediaUrls.clear();
3157
3201
  this.logQueuedAdUrls(this.adPodAllUrls);
3202
+ if (this.config.debugAdTiming) {
3203
+ console.log(
3204
+ `[StormcloudVideoPlayer] Capturing original audio state before ad break:`,
3205
+ {
3206
+ videoMuted: this.video.muted,
3207
+ videoVolume: this.video.volume
3208
+ }
3209
+ );
3210
+ }
3211
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3158
3212
  this.inAdBreak = true;
3159
- this.showAds = true;
3160
3213
  this.currentAdIndex = 0;
3161
3214
  this.totalAdsInBreak = vastTagUrls.length;
3162
3215
  this.adPodQueue = [...vastTagUrls];
3163
3216
  this.enforceAdHoldState();
3164
- this.preloadUpcomingAds();
3165
3217
  if (this.config.debugAdTiming) {
3166
3218
  console.log(
3167
- `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
3219
+ `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - preloading all ads in parallel`
3168
3220
  );
3169
3221
  }
3222
+ this.preloadAllAdsInBackground().catch((error) => {
3223
+ if (this.config.debugAdTiming) {
3224
+ console.warn(
3225
+ "[StormcloudVideoPlayer] Error in background preloading:",
3226
+ error
3227
+ );
3228
+ }
3229
+ });
3170
3230
  try {
3171
3231
  await this.playAdPod();
3172
3232
  } catch (error) {
@@ -3192,14 +3252,28 @@ var StormcloudVideoPlayer = class {
3192
3252
  }
3193
3253
  return;
3194
3254
  }
3195
- const firstAd = this.adPodQueue.shift();
3255
+ await new Promise((resolve) => setTimeout(resolve, 500));
3256
+ const firstPreloaded = this.findNextPreloadedAd();
3257
+ if (!firstPreloaded) {
3258
+ if (this.config.debugAdTiming) {
3259
+ console.log(
3260
+ "[StormcloudVideoPlayer] No preloaded ads available after waiting, trying first ad anyway"
3261
+ );
3262
+ }
3263
+ const firstAd = this.adPodQueue.shift();
3264
+ if (firstAd) {
3265
+ this.currentAdIndex++;
3266
+ await this.playSingleAd(firstAd);
3267
+ }
3268
+ return;
3269
+ }
3196
3270
  this.currentAdIndex++;
3197
3271
  if (this.config.debugAdTiming) {
3198
3272
  console.log(
3199
- `[StormcloudVideoPlayer] Playing ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3273
+ `[StormcloudVideoPlayer] Playing first preloaded ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3200
3274
  );
3201
3275
  }
3202
- await this.playSingleAd(firstAd);
3276
+ await this.playSingleAd(firstPreloaded);
3203
3277
  }
3204
3278
  findCurrentOrNextBreak(nowMs) {
3205
3279
  var _a;
@@ -3232,6 +3306,7 @@ var StormcloudVideoPlayer = class {
3232
3306
  const first = tags[0];
3233
3307
  const rest = tags.slice(1);
3234
3308
  this.adPodQueue = rest;
3309
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3235
3310
  this.enforceAdHoldState();
3236
3311
  await this.playSingleAd(first);
3237
3312
  this.inAdBreak = true;
@@ -3352,27 +3427,9 @@ var StormcloudVideoPlayer = class {
3352
3427
  `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3353
3428
  );
3354
3429
  }
3355
- if (!this.showAds) {
3356
- if (this.config.debugAdTiming) {
3357
- console.log(
3358
- `[StormcloudVideoPlayer] Capturing original state before ad request:`,
3359
- {
3360
- videoMuted: this.video.muted,
3361
- videoVolume: this.video.volume,
3362
- showAds: this.showAds
3363
- }
3364
- );
3365
- }
3366
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3367
- } else if (this.config.debugAdTiming) {
3368
- console.log(
3369
- `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
3370
- );
3371
- }
3372
3430
  this.startAdFailsafeTimer();
3373
3431
  try {
3374
3432
  await this.ima.requestAds(vastTagUrl);
3375
- this.preloadUpcomingAds();
3376
3433
  try {
3377
3434
  if (this.config.debugAdTiming) {
3378
3435
  console.log(
@@ -3381,9 +3438,10 @@ var StormcloudVideoPlayer = class {
3381
3438
  }
3382
3439
  this.enforceAdHoldState();
3383
3440
  await this.ima.play();
3441
+ this.showAds = true;
3384
3442
  if (this.config.debugAdTiming) {
3385
3443
  console.log(
3386
- "[StormcloudVideoPlayer] Ad playback started successfully"
3444
+ "[StormcloudVideoPlayer] Ad playback started successfully, showAds = true"
3387
3445
  );
3388
3446
  }
3389
3447
  } catch (playError) {
@@ -3411,6 +3469,9 @@ var StormcloudVideoPlayer = class {
3411
3469
  }
3412
3470
  this.releaseAdHoldState();
3413
3471
  this.preloadingAdUrls.clear();
3472
+ this.vastToMediaUrlMap.clear();
3473
+ this.preloadedMediaUrls.clear();
3474
+ this.preloadingMediaUrls.clear();
3414
3475
  this.inAdBreak = false;
3415
3476
  this.expectedAdBreakDurationMs = void 0;
3416
3477
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3520,44 +3581,204 @@ var StormcloudVideoPlayer = class {
3520
3581
  this.ima.hidePlaceholder();
3521
3582
  }
3522
3583
  }
3523
- preloadUpcomingAds() {
3524
- if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3525
- return;
3584
+ async fetchAndParseVastXml(vastTagUrl) {
3585
+ try {
3586
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3587
+ if (!response.ok) {
3588
+ throw new Error(`Failed to fetch VAST: ${response.status}`);
3589
+ }
3590
+ const xmlText = await response.text();
3591
+ return this.extractMediaUrlsFromVast(xmlText);
3592
+ } catch (error) {
3593
+ if (this.config.debugAdTiming) {
3594
+ console.warn(
3595
+ `[StormcloudVideoPlayer] Failed to fetch/parse VAST XML: ${vastTagUrl}`,
3596
+ error
3597
+ );
3598
+ }
3599
+ return [];
3526
3600
  }
3527
- const upcoming = this.adPodQueue.slice(0, 2);
3528
- for (const url of upcoming) {
3529
- if (!url) continue;
3530
- if (this.ima.hasPreloadedAd(url)) {
3531
- this.preloadingAdUrls.delete(url);
3532
- continue;
3601
+ }
3602
+ extractMediaUrlsFromVast(xmlText) {
3603
+ var _a;
3604
+ const mediaUrls = [];
3605
+ try {
3606
+ const parser = new DOMParser();
3607
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3608
+ const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
3609
+ for (const mediaFile of Array.from(mediaFileElements)) {
3610
+ const url = (_a = mediaFile.textContent) == null ? void 0 : _a.trim();
3611
+ if (url) {
3612
+ const lowerUrl = url.toLowerCase();
3613
+ if (lowerUrl.endsWith(".mp4") || lowerUrl.endsWith(".webm") || lowerUrl.endsWith(".mov") || lowerUrl.endsWith(".avi") || lowerUrl.includes(".mp4?") || lowerUrl.includes(".webm?") || lowerUrl.includes("/mp4/") || lowerUrl.includes("type=video")) {
3614
+ mediaUrls.push(url);
3615
+ }
3616
+ }
3533
3617
  }
3534
- if (this.preloadingAdUrls.has(url)) {
3535
- continue;
3618
+ if (this.config.debugAdTiming && mediaUrls.length > 0) {
3619
+ console.log(
3620
+ `[StormcloudVideoPlayer] Extracted ${mediaUrls.length} media URLs from VAST:`,
3621
+ mediaUrls
3622
+ );
3623
+ }
3624
+ } catch (error) {
3625
+ if (this.config.debugAdTiming) {
3626
+ console.warn(
3627
+ "[StormcloudVideoPlayer] Failed to parse VAST XML:",
3628
+ error
3629
+ );
3536
3630
  }
3631
+ }
3632
+ return mediaUrls;
3633
+ }
3634
+ async preloadMediaFile(mediaUrl) {
3635
+ if (this.preloadedMediaUrls.has(mediaUrl)) {
3636
+ return;
3637
+ }
3638
+ if (this.preloadingMediaUrls.has(mediaUrl)) {
3639
+ return;
3640
+ }
3641
+ this.preloadingMediaUrls.add(mediaUrl);
3642
+ try {
3537
3643
  if (this.config.debugAdTiming) {
3538
3644
  console.log(
3539
- `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3645
+ `[StormcloudVideoPlayer] Preloading video file: ${mediaUrl}`
3540
3646
  );
3541
3647
  }
3542
- this.preloadingAdUrls.add(url);
3543
- this.ima.preloadAds(url).then(() => {
3648
+ const response = await fetch(mediaUrl, {
3649
+ mode: "cors",
3650
+ method: "GET",
3651
+ headers: {
3652
+ Range: "bytes=0-1048576"
3653
+ }
3654
+ });
3655
+ if (response.ok || response.status === 206) {
3656
+ this.preloadedMediaUrls.add(mediaUrl);
3544
3657
  if (this.config.debugAdTiming) {
3545
3658
  console.log(
3546
- `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3659
+ `[StormcloudVideoPlayer] Successfully preloaded video file: ${mediaUrl}`
3547
3660
  );
3548
3661
  }
3549
- }).catch((error) => {
3662
+ }
3663
+ } catch (error) {
3664
+ if (this.config.debugAdTiming) {
3665
+ console.warn(
3666
+ `[StormcloudVideoPlayer] Failed to preload video file: ${mediaUrl}`,
3667
+ error
3668
+ );
3669
+ }
3670
+ } finally {
3671
+ this.preloadingMediaUrls.delete(mediaUrl);
3672
+ }
3673
+ }
3674
+ async preloadAllAdsInBackground() {
3675
+ if (this.adPodAllUrls.length === 0) {
3676
+ return;
3677
+ }
3678
+ if (this.config.debugAdTiming) {
3679
+ console.log(
3680
+ `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3681
+ );
3682
+ }
3683
+ const preloadPromises = this.adPodAllUrls.map(
3684
+ (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3550
3685
  if (this.config.debugAdTiming) {
3551
3686
  console.warn(
3552
- `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3687
+ `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3553
3688
  error
3554
3689
  );
3555
3690
  }
3556
- }).finally(() => {
3557
- this.preloadingAdUrls.delete(url);
3558
- });
3691
+ })
3692
+ );
3693
+ await Promise.all(preloadPromises);
3694
+ if (this.config.debugAdTiming) {
3695
+ console.log(
3696
+ `[StormcloudVideoPlayer] Background preloading completed for all ads`
3697
+ );
3559
3698
  }
3560
3699
  }
3700
+ async preloadSingleAd(vastTagUrl) {
3701
+ if (!vastTagUrl) return;
3702
+ try {
3703
+ if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3704
+ if (!this.preloadingAdUrls.has(vastTagUrl)) {
3705
+ if (this.config.debugAdTiming) {
3706
+ console.log(
3707
+ `[StormcloudVideoPlayer] Preloading VAST: ${vastTagUrl}`
3708
+ );
3709
+ }
3710
+ this.preloadingAdUrls.add(vastTagUrl);
3711
+ await this.ima.preloadAds(vastTagUrl).then(() => {
3712
+ if (this.config.debugAdTiming) {
3713
+ console.log(
3714
+ `[StormcloudVideoPlayer] IMA VAST preload complete: ${vastTagUrl}`
3715
+ );
3716
+ }
3717
+ }).catch((error) => {
3718
+ if (this.config.debugAdTiming) {
3719
+ console.warn(
3720
+ `[StormcloudVideoPlayer] IMA VAST preload failed: ${vastTagUrl}`,
3721
+ error
3722
+ );
3723
+ }
3724
+ }).finally(() => {
3725
+ this.preloadingAdUrls.delete(vastTagUrl);
3726
+ });
3727
+ }
3728
+ }
3729
+ let mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3730
+ if (!mediaUrls) {
3731
+ if (this.config.debugAdTiming) {
3732
+ console.log(
3733
+ `[StormcloudVideoPlayer] Fetching and parsing VAST to extract media URLs: ${vastTagUrl}`
3734
+ );
3735
+ }
3736
+ mediaUrls = await this.fetchAndParseVastXml(vastTagUrl);
3737
+ if (mediaUrls.length > 0) {
3738
+ this.vastToMediaUrlMap.set(vastTagUrl, mediaUrls);
3739
+ }
3740
+ }
3741
+ if (mediaUrls && mediaUrls.length > 0) {
3742
+ const primaryMediaUrl = mediaUrls[0];
3743
+ if (primaryMediaUrl && !this.preloadedMediaUrls.has(primaryMediaUrl)) {
3744
+ await this.preloadMediaFile(primaryMediaUrl);
3745
+ }
3746
+ }
3747
+ } catch (error) {
3748
+ if (this.config.debugAdTiming) {
3749
+ console.warn(
3750
+ `[StormcloudVideoPlayer] Failed to preload ad: ${vastTagUrl}`,
3751
+ error
3752
+ );
3753
+ }
3754
+ }
3755
+ }
3756
+ findNextPreloadedAd() {
3757
+ var _a, _b, _c;
3758
+ for (let i = 0; i < this.adPodQueue.length; i++) {
3759
+ const vastTagUrl = this.adPodQueue[i];
3760
+ if (!vastTagUrl) continue;
3761
+ const hasImaPreload = (_c = (_b = (_a = this.ima).hasPreloadedAd) == null ? void 0 : _b.call(_a, vastTagUrl)) != null ? _c : false;
3762
+ const mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3763
+ const hasMediaPreload = mediaUrls && mediaUrls.length > 0 ? this.preloadedMediaUrls.has(mediaUrls[0]) : false;
3764
+ if (hasImaPreload || hasMediaPreload) {
3765
+ if (this.config.debugAdTiming) {
3766
+ console.log(
3767
+ `[StormcloudVideoPlayer] Found preloaded ad at index ${i}: ${vastTagUrl}`,
3768
+ { hasImaPreload, hasMediaPreload }
3769
+ );
3770
+ }
3771
+ this.adPodQueue.splice(0, i + 1);
3772
+ return vastTagUrl;
3773
+ }
3774
+ }
3775
+ if (this.config.debugAdTiming) {
3776
+ console.log(
3777
+ "[StormcloudVideoPlayer] No preloaded ads found in queue"
3778
+ );
3779
+ }
3780
+ return void 0;
3781
+ }
3561
3782
  getRemainingAdMs() {
3562
3783
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3563
3784
  return 0;
@@ -3726,6 +3947,9 @@ var StormcloudVideoPlayer = class {
3726
3947
  (_b = this.ima) == null ? void 0 : _b.destroy();
3727
3948
  this.releaseAdHoldState();
3728
3949
  this.preloadingAdUrls.clear();
3950
+ this.vastToMediaUrlMap.clear();
3951
+ this.preloadedMediaUrls.clear();
3952
+ this.preloadingMediaUrls.clear();
3729
3953
  this.adPodAllUrls = [];
3730
3954
  }
3731
3955
  };