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.
package/lib/index.cjs CHANGED
@@ -706,6 +706,15 @@ function createImaController(video, options) {
706
706
  "[IMA] Content video continues in background (Live mode)"
707
707
  );
708
708
  }
709
+ hideContentVideo();
710
+ if (adContainerEl) {
711
+ adContainerEl.style.pointerEvents = "auto";
712
+ adContainerEl.style.display = "flex";
713
+ adContainerEl.style.backgroundColor = "#000";
714
+ adContainerEl.offsetHeight;
715
+ adContainerEl.style.opacity = "1";
716
+ console.log("[IMA] Ad container shown on content pause");
717
+ }
709
718
  adPlaying = true;
710
719
  setAdPlayingFlag(true);
711
720
  emit("content_pause");
@@ -2240,6 +2249,9 @@ var StormcloudVideoPlayer = class {
2240
2249
  this.hasInitialBufferCompleted = false;
2241
2250
  this.adPodAllUrls = [];
2242
2251
  this.preloadingAdUrls = /* @__PURE__ */ new Set();
2252
+ this.vastToMediaUrlMap = /* @__PURE__ */ new Map();
2253
+ this.preloadedMediaUrls = /* @__PURE__ */ new Set();
2254
+ this.preloadingMediaUrls = /* @__PURE__ */ new Set();
2243
2255
  initializePolyfills();
2244
2256
  const browserOverrides = getBrowserConfigOverrides();
2245
2257
  this.config = { ...config, ...browserOverrides };
@@ -2512,27 +2524,44 @@ var StormcloudVideoPlayer = class {
2512
2524
  });
2513
2525
  this.ima.on("ad_error", () => {
2514
2526
  if (this.config.debugAdTiming) {
2515
- console.log("[StormcloudVideoPlayer] IMA ad_error event received");
2527
+ console.log("[StormcloudVideoPlayer] IMA ad_error event received", {
2528
+ showAds: this.showAds,
2529
+ inAdBreak: this.inAdBreak,
2530
+ remainingAds: this.adPodQueue.length
2531
+ });
2516
2532
  }
2517
- if (this.showAds) {
2518
- if (this.inAdBreak) {
2519
- const remaining = this.getRemainingAdMs();
2520
- if (remaining > 500 && this.adPodQueue.length > 0) {
2521
- const next = this.adPodQueue.shift();
2533
+ if (this.inAdBreak) {
2534
+ const remaining = this.getRemainingAdMs();
2535
+ if (remaining > 500 && this.adPodQueue.length > 0) {
2536
+ const nextPreloaded = this.findNextPreloadedAd();
2537
+ if (nextPreloaded) {
2522
2538
  this.currentAdIndex++;
2523
- this.playSingleAd(next).catch(() => {
2539
+ if (this.config.debugAdTiming) {
2540
+ console.log(
2541
+ `[StormcloudVideoPlayer] Skipping to next preloaded ad after error`
2542
+ );
2543
+ }
2544
+ this.playSingleAd(nextPreloaded).catch(() => {
2545
+ this.handleAdFailure();
2524
2546
  });
2525
2547
  } else {
2548
+ if (this.config.debugAdTiming) {
2549
+ console.log(
2550
+ "[StormcloudVideoPlayer] No preloaded ads available, ending ad break"
2551
+ );
2552
+ }
2526
2553
  this.handleAdFailure();
2527
2554
  }
2528
2555
  } else {
2529
- if (this.config.debugAdTiming) {
2530
- console.log(
2531
- "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2532
- );
2533
- }
2534
2556
  this.handleAdFailure();
2535
2557
  }
2558
+ } else {
2559
+ if (this.config.debugAdTiming) {
2560
+ console.log(
2561
+ "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2562
+ );
2563
+ }
2564
+ this.handleAdFailure();
2536
2565
  }
2537
2566
  });
2538
2567
  this.ima.on("content_pause", () => {
@@ -2563,22 +2592,31 @@ var StormcloudVideoPlayer = class {
2563
2592
  }
2564
2593
  const remaining = this.getRemainingAdMs();
2565
2594
  if (remaining > 500 && this.adPodQueue.length > 0) {
2566
- const next = this.adPodQueue.shift();
2567
- this.currentAdIndex++;
2568
- this.enforceAdHoldState();
2569
- if (this.config.debugAdTiming) {
2570
- console.log(
2571
- `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
2572
- );
2573
- }
2574
- this.playSingleAd(next).catch(() => {
2595
+ const nextPreloaded = this.findNextPreloadedAd();
2596
+ if (nextPreloaded) {
2597
+ this.currentAdIndex++;
2598
+ this.enforceAdHoldState();
2575
2599
  if (this.config.debugAdTiming) {
2576
- console.error(
2577
- "[StormcloudVideoPlayer] Failed to play next ad in pod"
2600
+ console.log(
2601
+ `[StormcloudVideoPlayer] Playing next preloaded ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
2602
+ );
2603
+ }
2604
+ this.playSingleAd(nextPreloaded).catch(() => {
2605
+ if (this.config.debugAdTiming) {
2606
+ console.error(
2607
+ "[StormcloudVideoPlayer] Failed to play next ad in pod"
2608
+ );
2609
+ }
2610
+ this.handleAdPodComplete();
2611
+ });
2612
+ } else {
2613
+ if (this.config.debugAdTiming) {
2614
+ console.log(
2615
+ "[StormcloudVideoPlayer] No preloaded ads available - completing ad break"
2578
2616
  );
2579
2617
  }
2580
2618
  this.handleAdPodComplete();
2581
- });
2619
+ }
2582
2620
  } else {
2583
2621
  if (this.config.debugAdTiming) {
2584
2622
  console.log(
@@ -2823,6 +2861,9 @@ var StormcloudVideoPlayer = class {
2823
2861
  const first = tags[0];
2824
2862
  const rest = tags.slice(1);
2825
2863
  this.adPodQueue = rest;
2864
+ if (!this.showAds) {
2865
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2866
+ }
2826
2867
  this.playSingleAd(first).catch(() => {
2827
2868
  });
2828
2869
  }
@@ -3179,19 +3220,38 @@ var StormcloudVideoPlayer = class {
3179
3220
  if (vastTagUrls.length > 0) {
3180
3221
  this.adPodAllUrls = [...vastTagUrls];
3181
3222
  this.preloadingAdUrls.clear();
3223
+ this.vastToMediaUrlMap.clear();
3224
+ this.preloadedMediaUrls.clear();
3225
+ this.preloadingMediaUrls.clear();
3182
3226
  this.logQueuedAdUrls(this.adPodAllUrls);
3227
+ if (this.config.debugAdTiming) {
3228
+ console.log(
3229
+ `[StormcloudVideoPlayer] Capturing original audio state before ad break:`,
3230
+ {
3231
+ videoMuted: this.video.muted,
3232
+ videoVolume: this.video.volume
3233
+ }
3234
+ );
3235
+ }
3236
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3183
3237
  this.inAdBreak = true;
3184
- this.showAds = true;
3185
3238
  this.currentAdIndex = 0;
3186
3239
  this.totalAdsInBreak = vastTagUrls.length;
3187
3240
  this.adPodQueue = [...vastTagUrls];
3188
3241
  this.enforceAdHoldState();
3189
- this.preloadUpcomingAds();
3190
3242
  if (this.config.debugAdTiming) {
3191
3243
  console.log(
3192
- `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
3244
+ `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - preloading all ads in parallel`
3193
3245
  );
3194
3246
  }
3247
+ this.preloadAllAdsInBackground().catch((error) => {
3248
+ if (this.config.debugAdTiming) {
3249
+ console.warn(
3250
+ "[StormcloudVideoPlayer] Error in background preloading:",
3251
+ error
3252
+ );
3253
+ }
3254
+ });
3195
3255
  try {
3196
3256
  await this.playAdPod();
3197
3257
  } catch (error) {
@@ -3217,14 +3277,28 @@ var StormcloudVideoPlayer = class {
3217
3277
  }
3218
3278
  return;
3219
3279
  }
3220
- const firstAd = this.adPodQueue.shift();
3280
+ await new Promise((resolve) => setTimeout(resolve, 500));
3281
+ const firstPreloaded = this.findNextPreloadedAd();
3282
+ if (!firstPreloaded) {
3283
+ if (this.config.debugAdTiming) {
3284
+ console.log(
3285
+ "[StormcloudVideoPlayer] No preloaded ads available after waiting, trying first ad anyway"
3286
+ );
3287
+ }
3288
+ const firstAd = this.adPodQueue.shift();
3289
+ if (firstAd) {
3290
+ this.currentAdIndex++;
3291
+ await this.playSingleAd(firstAd);
3292
+ }
3293
+ return;
3294
+ }
3221
3295
  this.currentAdIndex++;
3222
3296
  if (this.config.debugAdTiming) {
3223
3297
  console.log(
3224
- `[StormcloudVideoPlayer] Playing ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3298
+ `[StormcloudVideoPlayer] Playing first preloaded ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3225
3299
  );
3226
3300
  }
3227
- await this.playSingleAd(firstAd);
3301
+ await this.playSingleAd(firstPreloaded);
3228
3302
  }
3229
3303
  findCurrentOrNextBreak(nowMs) {
3230
3304
  var _a;
@@ -3257,6 +3331,7 @@ var StormcloudVideoPlayer = class {
3257
3331
  const first = tags[0];
3258
3332
  const rest = tags.slice(1);
3259
3333
  this.adPodQueue = rest;
3334
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3260
3335
  this.enforceAdHoldState();
3261
3336
  await this.playSingleAd(first);
3262
3337
  this.inAdBreak = true;
@@ -3377,27 +3452,9 @@ var StormcloudVideoPlayer = class {
3377
3452
  `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3378
3453
  );
3379
3454
  }
3380
- if (!this.showAds) {
3381
- if (this.config.debugAdTiming) {
3382
- console.log(
3383
- `[StormcloudVideoPlayer] Capturing original state before ad request:`,
3384
- {
3385
- videoMuted: this.video.muted,
3386
- videoVolume: this.video.volume,
3387
- showAds: this.showAds
3388
- }
3389
- );
3390
- }
3391
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3392
- } else if (this.config.debugAdTiming) {
3393
- console.log(
3394
- `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
3395
- );
3396
- }
3397
3455
  this.startAdFailsafeTimer();
3398
3456
  try {
3399
3457
  await this.ima.requestAds(vastTagUrl);
3400
- this.preloadUpcomingAds();
3401
3458
  try {
3402
3459
  if (this.config.debugAdTiming) {
3403
3460
  console.log(
@@ -3406,9 +3463,10 @@ var StormcloudVideoPlayer = class {
3406
3463
  }
3407
3464
  this.enforceAdHoldState();
3408
3465
  await this.ima.play();
3466
+ this.showAds = true;
3409
3467
  if (this.config.debugAdTiming) {
3410
3468
  console.log(
3411
- "[StormcloudVideoPlayer] Ad playback started successfully"
3469
+ "[StormcloudVideoPlayer] Ad playback started successfully, showAds = true"
3412
3470
  );
3413
3471
  }
3414
3472
  } catch (playError) {
@@ -3436,6 +3494,9 @@ var StormcloudVideoPlayer = class {
3436
3494
  }
3437
3495
  this.releaseAdHoldState();
3438
3496
  this.preloadingAdUrls.clear();
3497
+ this.vastToMediaUrlMap.clear();
3498
+ this.preloadedMediaUrls.clear();
3499
+ this.preloadingMediaUrls.clear();
3439
3500
  this.inAdBreak = false;
3440
3501
  this.expectedAdBreakDurationMs = void 0;
3441
3502
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3545,44 +3606,204 @@ var StormcloudVideoPlayer = class {
3545
3606
  this.ima.hidePlaceholder();
3546
3607
  }
3547
3608
  }
3548
- preloadUpcomingAds() {
3549
- if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3550
- return;
3609
+ async fetchAndParseVastXml(vastTagUrl) {
3610
+ try {
3611
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3612
+ if (!response.ok) {
3613
+ throw new Error(`Failed to fetch VAST: ${response.status}`);
3614
+ }
3615
+ const xmlText = await response.text();
3616
+ return this.extractMediaUrlsFromVast(xmlText);
3617
+ } catch (error) {
3618
+ if (this.config.debugAdTiming) {
3619
+ console.warn(
3620
+ `[StormcloudVideoPlayer] Failed to fetch/parse VAST XML: ${vastTagUrl}`,
3621
+ error
3622
+ );
3623
+ }
3624
+ return [];
3551
3625
  }
3552
- const upcoming = this.adPodQueue.slice(0, 2);
3553
- for (const url of upcoming) {
3554
- if (!url) continue;
3555
- if (this.ima.hasPreloadedAd(url)) {
3556
- this.preloadingAdUrls.delete(url);
3557
- continue;
3626
+ }
3627
+ extractMediaUrlsFromVast(xmlText) {
3628
+ var _a;
3629
+ const mediaUrls = [];
3630
+ try {
3631
+ const parser = new DOMParser();
3632
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3633
+ const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
3634
+ for (const mediaFile of Array.from(mediaFileElements)) {
3635
+ const url = (_a = mediaFile.textContent) == null ? void 0 : _a.trim();
3636
+ if (url) {
3637
+ const lowerUrl = url.toLowerCase();
3638
+ 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")) {
3639
+ mediaUrls.push(url);
3640
+ }
3641
+ }
3558
3642
  }
3559
- if (this.preloadingAdUrls.has(url)) {
3560
- continue;
3643
+ if (this.config.debugAdTiming && mediaUrls.length > 0) {
3644
+ console.log(
3645
+ `[StormcloudVideoPlayer] Extracted ${mediaUrls.length} media URLs from VAST:`,
3646
+ mediaUrls
3647
+ );
3648
+ }
3649
+ } catch (error) {
3650
+ if (this.config.debugAdTiming) {
3651
+ console.warn(
3652
+ "[StormcloudVideoPlayer] Failed to parse VAST XML:",
3653
+ error
3654
+ );
3561
3655
  }
3656
+ }
3657
+ return mediaUrls;
3658
+ }
3659
+ async preloadMediaFile(mediaUrl) {
3660
+ if (this.preloadedMediaUrls.has(mediaUrl)) {
3661
+ return;
3662
+ }
3663
+ if (this.preloadingMediaUrls.has(mediaUrl)) {
3664
+ return;
3665
+ }
3666
+ this.preloadingMediaUrls.add(mediaUrl);
3667
+ try {
3562
3668
  if (this.config.debugAdTiming) {
3563
3669
  console.log(
3564
- `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3670
+ `[StormcloudVideoPlayer] Preloading video file: ${mediaUrl}`
3565
3671
  );
3566
3672
  }
3567
- this.preloadingAdUrls.add(url);
3568
- this.ima.preloadAds(url).then(() => {
3673
+ const response = await fetch(mediaUrl, {
3674
+ mode: "cors",
3675
+ method: "GET",
3676
+ headers: {
3677
+ Range: "bytes=0-1048576"
3678
+ }
3679
+ });
3680
+ if (response.ok || response.status === 206) {
3681
+ this.preloadedMediaUrls.add(mediaUrl);
3569
3682
  if (this.config.debugAdTiming) {
3570
3683
  console.log(
3571
- `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3684
+ `[StormcloudVideoPlayer] Successfully preloaded video file: ${mediaUrl}`
3572
3685
  );
3573
3686
  }
3574
- }).catch((error) => {
3687
+ }
3688
+ } catch (error) {
3689
+ if (this.config.debugAdTiming) {
3690
+ console.warn(
3691
+ `[StormcloudVideoPlayer] Failed to preload video file: ${mediaUrl}`,
3692
+ error
3693
+ );
3694
+ }
3695
+ } finally {
3696
+ this.preloadingMediaUrls.delete(mediaUrl);
3697
+ }
3698
+ }
3699
+ async preloadAllAdsInBackground() {
3700
+ if (this.adPodAllUrls.length === 0) {
3701
+ return;
3702
+ }
3703
+ if (this.config.debugAdTiming) {
3704
+ console.log(
3705
+ `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3706
+ );
3707
+ }
3708
+ const preloadPromises = this.adPodAllUrls.map(
3709
+ (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3575
3710
  if (this.config.debugAdTiming) {
3576
3711
  console.warn(
3577
- `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3712
+ `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3578
3713
  error
3579
3714
  );
3580
3715
  }
3581
- }).finally(() => {
3582
- this.preloadingAdUrls.delete(url);
3583
- });
3716
+ })
3717
+ );
3718
+ await Promise.all(preloadPromises);
3719
+ if (this.config.debugAdTiming) {
3720
+ console.log(
3721
+ `[StormcloudVideoPlayer] Background preloading completed for all ads`
3722
+ );
3584
3723
  }
3585
3724
  }
3725
+ async preloadSingleAd(vastTagUrl) {
3726
+ if (!vastTagUrl) return;
3727
+ try {
3728
+ if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3729
+ if (!this.preloadingAdUrls.has(vastTagUrl)) {
3730
+ if (this.config.debugAdTiming) {
3731
+ console.log(
3732
+ `[StormcloudVideoPlayer] Preloading VAST: ${vastTagUrl}`
3733
+ );
3734
+ }
3735
+ this.preloadingAdUrls.add(vastTagUrl);
3736
+ await this.ima.preloadAds(vastTagUrl).then(() => {
3737
+ if (this.config.debugAdTiming) {
3738
+ console.log(
3739
+ `[StormcloudVideoPlayer] IMA VAST preload complete: ${vastTagUrl}`
3740
+ );
3741
+ }
3742
+ }).catch((error) => {
3743
+ if (this.config.debugAdTiming) {
3744
+ console.warn(
3745
+ `[StormcloudVideoPlayer] IMA VAST preload failed: ${vastTagUrl}`,
3746
+ error
3747
+ );
3748
+ }
3749
+ }).finally(() => {
3750
+ this.preloadingAdUrls.delete(vastTagUrl);
3751
+ });
3752
+ }
3753
+ }
3754
+ let mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3755
+ if (!mediaUrls) {
3756
+ if (this.config.debugAdTiming) {
3757
+ console.log(
3758
+ `[StormcloudVideoPlayer] Fetching and parsing VAST to extract media URLs: ${vastTagUrl}`
3759
+ );
3760
+ }
3761
+ mediaUrls = await this.fetchAndParseVastXml(vastTagUrl);
3762
+ if (mediaUrls.length > 0) {
3763
+ this.vastToMediaUrlMap.set(vastTagUrl, mediaUrls);
3764
+ }
3765
+ }
3766
+ if (mediaUrls && mediaUrls.length > 0) {
3767
+ const primaryMediaUrl = mediaUrls[0];
3768
+ if (primaryMediaUrl && !this.preloadedMediaUrls.has(primaryMediaUrl)) {
3769
+ await this.preloadMediaFile(primaryMediaUrl);
3770
+ }
3771
+ }
3772
+ } catch (error) {
3773
+ if (this.config.debugAdTiming) {
3774
+ console.warn(
3775
+ `[StormcloudVideoPlayer] Failed to preload ad: ${vastTagUrl}`,
3776
+ error
3777
+ );
3778
+ }
3779
+ }
3780
+ }
3781
+ findNextPreloadedAd() {
3782
+ var _a, _b, _c;
3783
+ for (let i = 0; i < this.adPodQueue.length; i++) {
3784
+ const vastTagUrl = this.adPodQueue[i];
3785
+ if (!vastTagUrl) continue;
3786
+ const hasImaPreload = (_c = (_b = (_a = this.ima).hasPreloadedAd) == null ? void 0 : _b.call(_a, vastTagUrl)) != null ? _c : false;
3787
+ const mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3788
+ const hasMediaPreload = mediaUrls && mediaUrls.length > 0 ? this.preloadedMediaUrls.has(mediaUrls[0]) : false;
3789
+ if (hasImaPreload || hasMediaPreload) {
3790
+ if (this.config.debugAdTiming) {
3791
+ console.log(
3792
+ `[StormcloudVideoPlayer] Found preloaded ad at index ${i}: ${vastTagUrl}`,
3793
+ { hasImaPreload, hasMediaPreload }
3794
+ );
3795
+ }
3796
+ this.adPodQueue.splice(0, i + 1);
3797
+ return vastTagUrl;
3798
+ }
3799
+ }
3800
+ if (this.config.debugAdTiming) {
3801
+ console.log(
3802
+ "[StormcloudVideoPlayer] No preloaded ads found in queue"
3803
+ );
3804
+ }
3805
+ return void 0;
3806
+ }
3586
3807
  getRemainingAdMs() {
3587
3808
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3588
3809
  return 0;
@@ -3751,6 +3972,9 @@ var StormcloudVideoPlayer = class {
3751
3972
  (_b = this.ima) == null ? void 0 : _b.destroy();
3752
3973
  this.releaseAdHoldState();
3753
3974
  this.preloadingAdUrls.clear();
3975
+ this.vastToMediaUrlMap.clear();
3976
+ this.preloadedMediaUrls.clear();
3977
+ this.preloadingMediaUrls.clear();
3754
3978
  this.adPodAllUrls = [];
3755
3979
  }
3756
3980
  };