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.
@@ -639,6 +639,15 @@ function createImaController(video, options) {
639
639
  "[IMA] Content video continues in background (Live mode)"
640
640
  );
641
641
  }
642
+ hideContentVideo();
643
+ if (adContainerEl) {
644
+ adContainerEl.style.pointerEvents = "auto";
645
+ adContainerEl.style.display = "flex";
646
+ adContainerEl.style.backgroundColor = "#000";
647
+ adContainerEl.offsetHeight;
648
+ adContainerEl.style.opacity = "1";
649
+ console.log("[IMA] Ad container shown on content pause");
650
+ }
642
651
  adPlaying = true;
643
652
  setAdPlayingFlag(true);
644
653
  emit("content_pause");
@@ -2173,6 +2182,9 @@ var StormcloudVideoPlayer = class {
2173
2182
  this.hasInitialBufferCompleted = false;
2174
2183
  this.adPodAllUrls = [];
2175
2184
  this.preloadingAdUrls = /* @__PURE__ */ new Set();
2185
+ this.vastToMediaUrlMap = /* @__PURE__ */ new Map();
2186
+ this.preloadedMediaUrls = /* @__PURE__ */ new Set();
2187
+ this.preloadingMediaUrls = /* @__PURE__ */ new Set();
2176
2188
  initializePolyfills();
2177
2189
  const browserOverrides = getBrowserConfigOverrides();
2178
2190
  this.config = { ...config, ...browserOverrides };
@@ -2445,27 +2457,44 @@ var StormcloudVideoPlayer = class {
2445
2457
  });
2446
2458
  this.ima.on("ad_error", () => {
2447
2459
  if (this.config.debugAdTiming) {
2448
- console.log("[StormcloudVideoPlayer] IMA ad_error event received");
2460
+ console.log("[StormcloudVideoPlayer] IMA ad_error event received", {
2461
+ showAds: this.showAds,
2462
+ inAdBreak: this.inAdBreak,
2463
+ remainingAds: this.adPodQueue.length
2464
+ });
2449
2465
  }
2450
- if (this.showAds) {
2451
- if (this.inAdBreak) {
2452
- const remaining = this.getRemainingAdMs();
2453
- if (remaining > 500 && this.adPodQueue.length > 0) {
2454
- const next = this.adPodQueue.shift();
2466
+ if (this.inAdBreak) {
2467
+ const remaining = this.getRemainingAdMs();
2468
+ if (remaining > 500 && this.adPodQueue.length > 0) {
2469
+ const nextPreloaded = this.findNextPreloadedAd();
2470
+ if (nextPreloaded) {
2455
2471
  this.currentAdIndex++;
2456
- this.playSingleAd(next).catch(() => {
2472
+ if (this.config.debugAdTiming) {
2473
+ console.log(
2474
+ `[StormcloudVideoPlayer] Skipping to next preloaded ad after error`
2475
+ );
2476
+ }
2477
+ this.playSingleAd(nextPreloaded).catch(() => {
2478
+ this.handleAdFailure();
2457
2479
  });
2458
2480
  } else {
2481
+ if (this.config.debugAdTiming) {
2482
+ console.log(
2483
+ "[StormcloudVideoPlayer] No preloaded ads available, ending ad break"
2484
+ );
2485
+ }
2459
2486
  this.handleAdFailure();
2460
2487
  }
2461
2488
  } else {
2462
- if (this.config.debugAdTiming) {
2463
- console.log(
2464
- "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2465
- );
2466
- }
2467
2489
  this.handleAdFailure();
2468
2490
  }
2491
+ } else {
2492
+ if (this.config.debugAdTiming) {
2493
+ console.log(
2494
+ "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2495
+ );
2496
+ }
2497
+ this.handleAdFailure();
2469
2498
  }
2470
2499
  });
2471
2500
  this.ima.on("content_pause", () => {
@@ -2496,22 +2525,31 @@ var StormcloudVideoPlayer = class {
2496
2525
  }
2497
2526
  const remaining = this.getRemainingAdMs();
2498
2527
  if (remaining > 500 && this.adPodQueue.length > 0) {
2499
- const next = this.adPodQueue.shift();
2500
- this.currentAdIndex++;
2501
- this.enforceAdHoldState();
2502
- if (this.config.debugAdTiming) {
2503
- console.log(
2504
- `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
2505
- );
2506
- }
2507
- this.playSingleAd(next).catch(() => {
2528
+ const nextPreloaded = this.findNextPreloadedAd();
2529
+ if (nextPreloaded) {
2530
+ this.currentAdIndex++;
2531
+ this.enforceAdHoldState();
2508
2532
  if (this.config.debugAdTiming) {
2509
- console.error(
2510
- "[StormcloudVideoPlayer] Failed to play next ad in pod"
2533
+ console.log(
2534
+ `[StormcloudVideoPlayer] Playing next preloaded ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
2535
+ );
2536
+ }
2537
+ this.playSingleAd(nextPreloaded).catch(() => {
2538
+ if (this.config.debugAdTiming) {
2539
+ console.error(
2540
+ "[StormcloudVideoPlayer] Failed to play next ad in pod"
2541
+ );
2542
+ }
2543
+ this.handleAdPodComplete();
2544
+ });
2545
+ } else {
2546
+ if (this.config.debugAdTiming) {
2547
+ console.log(
2548
+ "[StormcloudVideoPlayer] No preloaded ads available - completing ad break"
2511
2549
  );
2512
2550
  }
2513
2551
  this.handleAdPodComplete();
2514
- });
2552
+ }
2515
2553
  } else {
2516
2554
  if (this.config.debugAdTiming) {
2517
2555
  console.log(
@@ -2756,6 +2794,9 @@ var StormcloudVideoPlayer = class {
2756
2794
  const first = tags[0];
2757
2795
  const rest = tags.slice(1);
2758
2796
  this.adPodQueue = rest;
2797
+ if (!this.showAds) {
2798
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2799
+ }
2759
2800
  this.playSingleAd(first).catch(() => {
2760
2801
  });
2761
2802
  }
@@ -3112,19 +3153,38 @@ var StormcloudVideoPlayer = class {
3112
3153
  if (vastTagUrls.length > 0) {
3113
3154
  this.adPodAllUrls = [...vastTagUrls];
3114
3155
  this.preloadingAdUrls.clear();
3156
+ this.vastToMediaUrlMap.clear();
3157
+ this.preloadedMediaUrls.clear();
3158
+ this.preloadingMediaUrls.clear();
3115
3159
  this.logQueuedAdUrls(this.adPodAllUrls);
3160
+ if (this.config.debugAdTiming) {
3161
+ console.log(
3162
+ `[StormcloudVideoPlayer] Capturing original audio state before ad break:`,
3163
+ {
3164
+ videoMuted: this.video.muted,
3165
+ videoVolume: this.video.volume
3166
+ }
3167
+ );
3168
+ }
3169
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3116
3170
  this.inAdBreak = true;
3117
- this.showAds = true;
3118
3171
  this.currentAdIndex = 0;
3119
3172
  this.totalAdsInBreak = vastTagUrls.length;
3120
3173
  this.adPodQueue = [...vastTagUrls];
3121
3174
  this.enforceAdHoldState();
3122
- this.preloadUpcomingAds();
3123
3175
  if (this.config.debugAdTiming) {
3124
3176
  console.log(
3125
- `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
3177
+ `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - preloading all ads in parallel`
3126
3178
  );
3127
3179
  }
3180
+ this.preloadAllAdsInBackground().catch((error) => {
3181
+ if (this.config.debugAdTiming) {
3182
+ console.warn(
3183
+ "[StormcloudVideoPlayer] Error in background preloading:",
3184
+ error
3185
+ );
3186
+ }
3187
+ });
3128
3188
  try {
3129
3189
  await this.playAdPod();
3130
3190
  } catch (error) {
@@ -3150,14 +3210,28 @@ var StormcloudVideoPlayer = class {
3150
3210
  }
3151
3211
  return;
3152
3212
  }
3153
- const firstAd = this.adPodQueue.shift();
3213
+ await new Promise((resolve) => setTimeout(resolve, 500));
3214
+ const firstPreloaded = this.findNextPreloadedAd();
3215
+ if (!firstPreloaded) {
3216
+ if (this.config.debugAdTiming) {
3217
+ console.log(
3218
+ "[StormcloudVideoPlayer] No preloaded ads available after waiting, trying first ad anyway"
3219
+ );
3220
+ }
3221
+ const firstAd = this.adPodQueue.shift();
3222
+ if (firstAd) {
3223
+ this.currentAdIndex++;
3224
+ await this.playSingleAd(firstAd);
3225
+ }
3226
+ return;
3227
+ }
3154
3228
  this.currentAdIndex++;
3155
3229
  if (this.config.debugAdTiming) {
3156
3230
  console.log(
3157
- `[StormcloudVideoPlayer] Playing ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3231
+ `[StormcloudVideoPlayer] Playing first preloaded ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3158
3232
  );
3159
3233
  }
3160
- await this.playSingleAd(firstAd);
3234
+ await this.playSingleAd(firstPreloaded);
3161
3235
  }
3162
3236
  findCurrentOrNextBreak(nowMs) {
3163
3237
  var _a;
@@ -3190,6 +3264,7 @@ var StormcloudVideoPlayer = class {
3190
3264
  const first = tags[0];
3191
3265
  const rest = tags.slice(1);
3192
3266
  this.adPodQueue = rest;
3267
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3193
3268
  this.enforceAdHoldState();
3194
3269
  await this.playSingleAd(first);
3195
3270
  this.inAdBreak = true;
@@ -3310,27 +3385,9 @@ var StormcloudVideoPlayer = class {
3310
3385
  `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3311
3386
  );
3312
3387
  }
3313
- if (!this.showAds) {
3314
- if (this.config.debugAdTiming) {
3315
- console.log(
3316
- `[StormcloudVideoPlayer] Capturing original state before ad request:`,
3317
- {
3318
- videoMuted: this.video.muted,
3319
- videoVolume: this.video.volume,
3320
- showAds: this.showAds
3321
- }
3322
- );
3323
- }
3324
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3325
- } else if (this.config.debugAdTiming) {
3326
- console.log(
3327
- `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
3328
- );
3329
- }
3330
3388
  this.startAdFailsafeTimer();
3331
3389
  try {
3332
3390
  await this.ima.requestAds(vastTagUrl);
3333
- this.preloadUpcomingAds();
3334
3391
  try {
3335
3392
  if (this.config.debugAdTiming) {
3336
3393
  console.log(
@@ -3339,9 +3396,10 @@ var StormcloudVideoPlayer = class {
3339
3396
  }
3340
3397
  this.enforceAdHoldState();
3341
3398
  await this.ima.play();
3399
+ this.showAds = true;
3342
3400
  if (this.config.debugAdTiming) {
3343
3401
  console.log(
3344
- "[StormcloudVideoPlayer] Ad playback started successfully"
3402
+ "[StormcloudVideoPlayer] Ad playback started successfully, showAds = true"
3345
3403
  );
3346
3404
  }
3347
3405
  } catch (playError) {
@@ -3369,6 +3427,9 @@ var StormcloudVideoPlayer = class {
3369
3427
  }
3370
3428
  this.releaseAdHoldState();
3371
3429
  this.preloadingAdUrls.clear();
3430
+ this.vastToMediaUrlMap.clear();
3431
+ this.preloadedMediaUrls.clear();
3432
+ this.preloadingMediaUrls.clear();
3372
3433
  this.inAdBreak = false;
3373
3434
  this.expectedAdBreakDurationMs = void 0;
3374
3435
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3478,44 +3539,204 @@ var StormcloudVideoPlayer = class {
3478
3539
  this.ima.hidePlaceholder();
3479
3540
  }
3480
3541
  }
3481
- preloadUpcomingAds() {
3482
- if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3483
- return;
3542
+ async fetchAndParseVastXml(vastTagUrl) {
3543
+ try {
3544
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3545
+ if (!response.ok) {
3546
+ throw new Error(`Failed to fetch VAST: ${response.status}`);
3547
+ }
3548
+ const xmlText = await response.text();
3549
+ return this.extractMediaUrlsFromVast(xmlText);
3550
+ } catch (error) {
3551
+ if (this.config.debugAdTiming) {
3552
+ console.warn(
3553
+ `[StormcloudVideoPlayer] Failed to fetch/parse VAST XML: ${vastTagUrl}`,
3554
+ error
3555
+ );
3556
+ }
3557
+ return [];
3484
3558
  }
3485
- const upcoming = this.adPodQueue.slice(0, 2);
3486
- for (const url of upcoming) {
3487
- if (!url) continue;
3488
- if (this.ima.hasPreloadedAd(url)) {
3489
- this.preloadingAdUrls.delete(url);
3490
- continue;
3559
+ }
3560
+ extractMediaUrlsFromVast(xmlText) {
3561
+ var _a;
3562
+ const mediaUrls = [];
3563
+ try {
3564
+ const parser = new DOMParser();
3565
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3566
+ const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
3567
+ for (const mediaFile of Array.from(mediaFileElements)) {
3568
+ const url = (_a = mediaFile.textContent) == null ? void 0 : _a.trim();
3569
+ if (url) {
3570
+ const lowerUrl = url.toLowerCase();
3571
+ 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")) {
3572
+ mediaUrls.push(url);
3573
+ }
3574
+ }
3491
3575
  }
3492
- if (this.preloadingAdUrls.has(url)) {
3493
- continue;
3576
+ if (this.config.debugAdTiming && mediaUrls.length > 0) {
3577
+ console.log(
3578
+ `[StormcloudVideoPlayer] Extracted ${mediaUrls.length} media URLs from VAST:`,
3579
+ mediaUrls
3580
+ );
3581
+ }
3582
+ } catch (error) {
3583
+ if (this.config.debugAdTiming) {
3584
+ console.warn(
3585
+ "[StormcloudVideoPlayer] Failed to parse VAST XML:",
3586
+ error
3587
+ );
3494
3588
  }
3589
+ }
3590
+ return mediaUrls;
3591
+ }
3592
+ async preloadMediaFile(mediaUrl) {
3593
+ if (this.preloadedMediaUrls.has(mediaUrl)) {
3594
+ return;
3595
+ }
3596
+ if (this.preloadingMediaUrls.has(mediaUrl)) {
3597
+ return;
3598
+ }
3599
+ this.preloadingMediaUrls.add(mediaUrl);
3600
+ try {
3495
3601
  if (this.config.debugAdTiming) {
3496
3602
  console.log(
3497
- `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3603
+ `[StormcloudVideoPlayer] Preloading video file: ${mediaUrl}`
3498
3604
  );
3499
3605
  }
3500
- this.preloadingAdUrls.add(url);
3501
- this.ima.preloadAds(url).then(() => {
3606
+ const response = await fetch(mediaUrl, {
3607
+ mode: "cors",
3608
+ method: "GET",
3609
+ headers: {
3610
+ Range: "bytes=0-1048576"
3611
+ }
3612
+ });
3613
+ if (response.ok || response.status === 206) {
3614
+ this.preloadedMediaUrls.add(mediaUrl);
3502
3615
  if (this.config.debugAdTiming) {
3503
3616
  console.log(
3504
- `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3617
+ `[StormcloudVideoPlayer] Successfully preloaded video file: ${mediaUrl}`
3505
3618
  );
3506
3619
  }
3507
- }).catch((error) => {
3620
+ }
3621
+ } catch (error) {
3622
+ if (this.config.debugAdTiming) {
3623
+ console.warn(
3624
+ `[StormcloudVideoPlayer] Failed to preload video file: ${mediaUrl}`,
3625
+ error
3626
+ );
3627
+ }
3628
+ } finally {
3629
+ this.preloadingMediaUrls.delete(mediaUrl);
3630
+ }
3631
+ }
3632
+ async preloadAllAdsInBackground() {
3633
+ if (this.adPodAllUrls.length === 0) {
3634
+ return;
3635
+ }
3636
+ if (this.config.debugAdTiming) {
3637
+ console.log(
3638
+ `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3639
+ );
3640
+ }
3641
+ const preloadPromises = this.adPodAllUrls.map(
3642
+ (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3508
3643
  if (this.config.debugAdTiming) {
3509
3644
  console.warn(
3510
- `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3645
+ `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3511
3646
  error
3512
3647
  );
3513
3648
  }
3514
- }).finally(() => {
3515
- this.preloadingAdUrls.delete(url);
3516
- });
3649
+ })
3650
+ );
3651
+ await Promise.all(preloadPromises);
3652
+ if (this.config.debugAdTiming) {
3653
+ console.log(
3654
+ `[StormcloudVideoPlayer] Background preloading completed for all ads`
3655
+ );
3517
3656
  }
3518
3657
  }
3658
+ async preloadSingleAd(vastTagUrl) {
3659
+ if (!vastTagUrl) return;
3660
+ try {
3661
+ if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3662
+ if (!this.preloadingAdUrls.has(vastTagUrl)) {
3663
+ if (this.config.debugAdTiming) {
3664
+ console.log(
3665
+ `[StormcloudVideoPlayer] Preloading VAST: ${vastTagUrl}`
3666
+ );
3667
+ }
3668
+ this.preloadingAdUrls.add(vastTagUrl);
3669
+ await this.ima.preloadAds(vastTagUrl).then(() => {
3670
+ if (this.config.debugAdTiming) {
3671
+ console.log(
3672
+ `[StormcloudVideoPlayer] IMA VAST preload complete: ${vastTagUrl}`
3673
+ );
3674
+ }
3675
+ }).catch((error) => {
3676
+ if (this.config.debugAdTiming) {
3677
+ console.warn(
3678
+ `[StormcloudVideoPlayer] IMA VAST preload failed: ${vastTagUrl}`,
3679
+ error
3680
+ );
3681
+ }
3682
+ }).finally(() => {
3683
+ this.preloadingAdUrls.delete(vastTagUrl);
3684
+ });
3685
+ }
3686
+ }
3687
+ let mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3688
+ if (!mediaUrls) {
3689
+ if (this.config.debugAdTiming) {
3690
+ console.log(
3691
+ `[StormcloudVideoPlayer] Fetching and parsing VAST to extract media URLs: ${vastTagUrl}`
3692
+ );
3693
+ }
3694
+ mediaUrls = await this.fetchAndParseVastXml(vastTagUrl);
3695
+ if (mediaUrls.length > 0) {
3696
+ this.vastToMediaUrlMap.set(vastTagUrl, mediaUrls);
3697
+ }
3698
+ }
3699
+ if (mediaUrls && mediaUrls.length > 0) {
3700
+ const primaryMediaUrl = mediaUrls[0];
3701
+ if (primaryMediaUrl && !this.preloadedMediaUrls.has(primaryMediaUrl)) {
3702
+ await this.preloadMediaFile(primaryMediaUrl);
3703
+ }
3704
+ }
3705
+ } catch (error) {
3706
+ if (this.config.debugAdTiming) {
3707
+ console.warn(
3708
+ `[StormcloudVideoPlayer] Failed to preload ad: ${vastTagUrl}`,
3709
+ error
3710
+ );
3711
+ }
3712
+ }
3713
+ }
3714
+ findNextPreloadedAd() {
3715
+ var _a, _b, _c;
3716
+ for (let i = 0; i < this.adPodQueue.length; i++) {
3717
+ const vastTagUrl = this.adPodQueue[i];
3718
+ if (!vastTagUrl) continue;
3719
+ const hasImaPreload = (_c = (_b = (_a = this.ima).hasPreloadedAd) == null ? void 0 : _b.call(_a, vastTagUrl)) != null ? _c : false;
3720
+ const mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3721
+ const hasMediaPreload = mediaUrls && mediaUrls.length > 0 ? this.preloadedMediaUrls.has(mediaUrls[0]) : false;
3722
+ if (hasImaPreload || hasMediaPreload) {
3723
+ if (this.config.debugAdTiming) {
3724
+ console.log(
3725
+ `[StormcloudVideoPlayer] Found preloaded ad at index ${i}: ${vastTagUrl}`,
3726
+ { hasImaPreload, hasMediaPreload }
3727
+ );
3728
+ }
3729
+ this.adPodQueue.splice(0, i + 1);
3730
+ return vastTagUrl;
3731
+ }
3732
+ }
3733
+ if (this.config.debugAdTiming) {
3734
+ console.log(
3735
+ "[StormcloudVideoPlayer] No preloaded ads found in queue"
3736
+ );
3737
+ }
3738
+ return void 0;
3739
+ }
3519
3740
  getRemainingAdMs() {
3520
3741
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3521
3742
  return 0;
@@ -3684,6 +3905,9 @@ var StormcloudVideoPlayer = class {
3684
3905
  (_b = this.ima) == null ? void 0 : _b.destroy();
3685
3906
  this.releaseAdHoldState();
3686
3907
  this.preloadingAdUrls.clear();
3908
+ this.vastToMediaUrlMap.clear();
3909
+ this.preloadedMediaUrls.clear();
3910
+ this.preloadingMediaUrls.clear();
3687
3911
  this.adPodAllUrls = [];
3688
3912
  }
3689
3913
  };