stormcloud-video-player 0.2.31 → 0.2.33

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.
@@ -351,15 +351,17 @@ function createImaController(video, options) {
351
351
  let adsLoadedResolve;
352
352
  let adsLoadedReject;
353
353
  function makeAdsRequest(google, vastTagUrl) {
354
+ console.log("[IMA] \u{1F4CB} === makeAdsRequest() - Building IMA request ===");
354
355
  const adsRequest = new google.ima.AdsRequest();
355
356
  const preloadedResponse = preloadedVast.get(vastTagUrl);
356
357
  if (preloadedResponse) {
357
358
  adsRequest.adsResponse = preloadedResponse;
358
359
  console.log(
359
- "[IMA] Using preloaded VAST response for immediate ad request"
360
+ "[IMA] \u26A1 Using preloaded VAST response for immediate ad request"
360
361
  );
361
362
  } else {
362
363
  adsRequest.adTagUrl = vastTagUrl;
364
+ console.log("[IMA] \u{1F310} Will fetch VAST from URL:", vastTagUrl);
363
365
  }
364
366
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
365
367
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
@@ -371,6 +373,7 @@ function createImaController(video, options) {
371
373
  try {
372
374
  const willAutoPlay = !video.paused || video.autoplay;
373
375
  adsRequest.setAdWillAutoPlay(willAutoPlay);
376
+ console.log(`[IMA] Ad will autoplay: ${willAutoPlay}`);
374
377
  } catch (error) {
375
378
  console.warn("[IMA] Failed to call setAdWillAutoPlay:", error);
376
379
  }
@@ -379,13 +382,17 @@ function createImaController(video, options) {
379
382
  try {
380
383
  const willPlayMuted = video.muted || video.volume === 0;
381
384
  adsRequest.setAdWillPlayMuted(willPlayMuted);
385
+ console.log(`[IMA] Ad will play muted: ${willPlayMuted}`);
382
386
  } catch (error) {
383
387
  console.warn("[IMA] Failed to call setAdWillPlayMuted:", error);
384
388
  }
385
389
  }
386
390
  adsRequest.vastLoadTimeout = 5e3;
387
- console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
391
+ console.log(`[IMA] \u{1F4D0} Ads request dimensions: ${videoWidth}x${videoHeight}`);
392
+ console.log("[IMA] \u23F1\uFE0F VAST load timeout: 5000ms");
393
+ console.log("[IMA] \u{1F680} Calling adsLoader.requestAds()...");
388
394
  adsLoader.requestAds(adsRequest);
395
+ console.log("[IMA] \u23F3 Waiting for ADS_MANAGER_LOADED or AD_ERROR event...");
389
396
  if (preloadedResponse) {
390
397
  preloadedVast.delete(vastTagUrl);
391
398
  }
@@ -463,22 +470,24 @@ function createImaController(video, options) {
463
470
  });
464
471
  },
465
472
  async requestAds(vastTagUrl) {
466
- console.log("[IMA] Requesting ads:", vastTagUrl);
473
+ console.log("[IMA] \u{1F4E1} === requestAds() called ===");
474
+ console.log("[IMA] VAST URL:", vastTagUrl);
475
+ console.log("[IMA] This will fetch the ad from the server - no visual change yet");
467
476
  if (!vastTagUrl || vastTagUrl.trim() === "") {
468
477
  const error = new Error("VAST tag URL is empty or undefined");
469
- console.warn("[IMA]", error.message);
478
+ console.warn("[IMA] \u274C", error.message);
470
479
  return Promise.reject(error);
471
480
  }
472
481
  try {
473
482
  new URL(vastTagUrl);
474
483
  } catch (e) {
475
484
  const error = new Error(`Invalid VAST tag URL format: ${vastTagUrl}`);
476
- console.warn("[IMA]", error.message);
485
+ console.warn("[IMA] \u274C", error.message);
477
486
  return Promise.reject(error);
478
487
  }
479
488
  if (adPlaying) {
480
489
  console.warn(
481
- "[IMA] Cannot request new ads while an ad is playing. Call stop() first."
490
+ "[IMA] \u26A0\uFE0F Cannot request new ads while an ad is playing. Call stop() first."
482
491
  );
483
492
  return Promise.reject(
484
493
  new Error("Ad already playing - cannot request new ads")
@@ -568,20 +577,85 @@ function createImaController(video, options) {
568
577
  adsLoader.addEventListener(
569
578
  google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
570
579
  (evt) => {
571
- console.log(
572
- "[IMA] Ads manager loaded - enabling preloading for continuous playback"
573
- );
580
+ console.log("[IMA] \u2705 ADS_MANAGER_LOADED - Ads fetched successfully!");
581
+ console.log("[IMA] Setting up ads manager with preloading enabled");
582
+ console.log("[IMA] ========================================");
583
+ console.log("[IMA] EXPECTED EVENT FLOW:");
584
+ console.log("[IMA] 1. requestAds() \u2192 fetch VAST");
585
+ console.log("[IMA] 2. ADS_MANAGER_LOADED \u2192 ads ready");
586
+ console.log("[IMA] 3. play() \u2192 start playback");
587
+ console.log("[IMA] 4. CONTENT_PAUSE_REQUESTED \u2192 show ad layer \u2728");
588
+ console.log("[IMA] 5. STARTED \u2192 ad is playing");
589
+ console.log("[IMA] 6. CONTENT_RESUME_REQUESTED \u2192 ad done");
590
+ console.log("[IMA] 7. ALL_ADS_COMPLETED \u2192 hide ad layer");
591
+ console.log("[IMA] ========================================");
574
592
  try {
575
593
  const adsRenderingSettings = new google.ima.AdsRenderingSettings();
576
594
  adsRenderingSettings.enablePreloading = true;
577
595
  adsManager = evt.getAdsManager(video, adsRenderingSettings);
578
596
  const AdEvent = google.ima.AdEvent.Type;
579
597
  const AdErrorEvent = google.ima.AdErrorEvent.Type;
598
+ console.log("[IMA] ========== IMA EVENT LOGGING ENABLED ==========");
599
+ console.log("[IMA] All IMA SDK events will be logged below");
600
+ const allAdEvents = [
601
+ "AD_BREAK_READY",
602
+ "AD_METADATA",
603
+ "ALL_ADS_COMPLETED",
604
+ "CLICK",
605
+ "COMPLETE",
606
+ "CONTENT_PAUSE_REQUESTED",
607
+ "CONTENT_RESUME_REQUESTED",
608
+ "DURATION_CHANGE",
609
+ "FIRST_QUARTILE",
610
+ "IMPRESSION",
611
+ "INTERACTION",
612
+ "LINEAR_CHANGED",
613
+ "LOADED",
614
+ "LOG",
615
+ "MIDPOINT",
616
+ "PAUSED",
617
+ "RESUMED",
618
+ "SKIPPABLE_STATE_CHANGED",
619
+ "SKIPPED",
620
+ "STARTED",
621
+ "THIRD_QUARTILE",
622
+ "USER_CLOSE",
623
+ "VOLUME_CHANGED",
624
+ "VOLUME_MUTED"
625
+ ];
626
+ allAdEvents.forEach((eventType) => {
627
+ if (AdEvent[eventType]) {
628
+ adsManager.addEventListener(AdEvent[eventType], (e) => {
629
+ var _a, _b, _c, _d, _e, _f;
630
+ const ad = (_a = e.getAd) == null ? void 0 : _a.call(e);
631
+ const adData = ad ? {
632
+ adId: (_b = ad.getAdId) == null ? void 0 : _b.call(ad),
633
+ title: (_c = ad.getTitle) == null ? void 0 : _c.call(ad),
634
+ duration: (_d = ad.getDuration) == null ? void 0 : _d.call(ad),
635
+ isLinear: (_e = ad.isLinear) == null ? void 0 : _e.call(ad),
636
+ contentType: (_f = ad.getContentType) == null ? void 0 : _f.call(ad)
637
+ } : null;
638
+ console.log(`[IMA EVENT] ${eventType}`, {
639
+ eventType,
640
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
641
+ adData
642
+ });
643
+ });
644
+ }
645
+ });
646
+ console.log("[IMA] ========== EVENT LISTENERS ATTACHED ==========");
580
647
  adsManager.addEventListener(
581
648
  AdErrorEvent.AD_ERROR,
582
649
  (errorEvent) => {
583
- var _a;
584
- console.error("[IMA] Ad error:", errorEvent.getError());
650
+ var _a, _b, _c, _d, _e, _f;
651
+ const error = errorEvent.getError();
652
+ console.error("[IMA] \u274C AD_ERROR Event:", {
653
+ message: (_a = error.getMessage) == null ? void 0 : _a.call(error),
654
+ errorCode: (_b = error.getErrorCode) == null ? void 0 : _b.call(error),
655
+ type: (_c = error.getType) == null ? void 0 : _c.call(error),
656
+ vastErrorCode: (_d = error.getVastErrorCode) == null ? void 0 : _d.call(error),
657
+ innerError: (_e = error.getInnerError) == null ? void 0 : _e.call(error)
658
+ });
585
659
  destroyAdsManager();
586
660
  adPlaying = false;
587
661
  setAdPlayingFlag(false);
@@ -623,7 +697,7 @@ function createImaController(video, options) {
623
697
  console.log(
624
698
  "[IMA] Resuming paused video after ad error"
625
699
  );
626
- (_a = video.play()) == null ? void 0 : _a.catch(() => {
700
+ (_f = video.play()) == null ? void 0 : _f.catch(() => {
627
701
  });
628
702
  }
629
703
  }
@@ -633,7 +707,8 @@ function createImaController(video, options) {
633
707
  adsManager.addEventListener(
634
708
  AdEvent.CONTENT_PAUSE_REQUESTED,
635
709
  () => {
636
- console.log("[IMA] Content pause requested");
710
+ console.log("[IMA] \u2705 CONTENT_PAUSE_REQUESTED - Ad is ready to play!");
711
+ console.log("[IMA] This is the event that triggers the ad layer to appear");
637
712
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
638
713
  video.pause();
639
714
  console.log("[IMA] Content video paused (VOD mode)");
@@ -642,13 +717,23 @@ function createImaController(video, options) {
642
717
  "[IMA] Content video continues in background (Live mode)"
643
718
  );
644
719
  }
720
+ hideContentVideo();
721
+ if (adContainerEl) {
722
+ adContainerEl.style.pointerEvents = "auto";
723
+ adContainerEl.style.display = "flex";
724
+ adContainerEl.style.backgroundColor = "#000";
725
+ adContainerEl.offsetHeight;
726
+ adContainerEl.style.opacity = "1";
727
+ console.log("[IMA] \u2728 Ad container NOW VISIBLE (after content pause)");
728
+ }
645
729
  adPlaying = true;
646
730
  setAdPlayingFlag(true);
731
+ console.log("[IMA] Emitting 'content_pause' event to player");
647
732
  emit("content_pause");
648
733
  }
649
734
  );
650
735
  adsManager.addEventListener(AdEvent.STARTED, () => {
651
- console.log("[IMA] Ad started - showing ad video");
736
+ console.log("[IMA] \u25B6\uFE0F STARTED - Ad playback has begun");
652
737
  setAdPlayingFlag(true);
653
738
  hideContentVideo();
654
739
  if (adVideoElement) {
@@ -664,20 +749,21 @@ function createImaController(video, options) {
664
749
  adContainerEl.style.backgroundColor = "#000";
665
750
  adContainerEl.offsetHeight;
666
751
  adContainerEl.style.opacity = "1";
667
- console.log("[IMA] Ad container now visible");
752
+ console.log("[IMA] Ad container now visible (STARTED event)");
668
753
  }
669
754
  });
670
755
  adsManager.addEventListener(
671
756
  AdEvent.CONTENT_RESUME_REQUESTED,
672
757
  () => {
673
- console.log("[IMA] Content resume requested");
758
+ console.log("[IMA] \u23F8\uFE0F CONTENT_RESUME_REQUESTED - Single ad completed");
674
759
  adPlaying = false;
675
760
  setAdPlayingFlag(false);
761
+ console.log("[IMA] Emitting 'content_resume' event to player");
676
762
  emit("content_resume");
677
763
  }
678
764
  );
679
765
  adsManager.addEventListener(AdEvent.ALL_ADS_COMPLETED, () => {
680
- console.log("[IMA] All ads completed - restoring content");
766
+ console.log("[IMA] \u{1F3C1} ALL_ADS_COMPLETED - All ads in break finished");
681
767
  adPlaying = false;
682
768
  setAdPlayingFlag(false);
683
769
  if (adContainerEl) {
@@ -687,7 +773,7 @@ function createImaController(video, options) {
687
773
  if (adContainerEl) {
688
774
  adContainerEl.style.pointerEvents = "none";
689
775
  adContainerEl.style.display = "none";
690
- console.log("[IMA] Ad container hidden");
776
+ console.log("[IMA] \u{1F648} Ad container hidden (ALL_ADS_COMPLETED)");
691
777
  }
692
778
  }, 300);
693
779
  }
@@ -698,6 +784,7 @@ function createImaController(video, options) {
698
784
  console.warn("[IMA] Failed to resume content video:", e);
699
785
  });
700
786
  }
787
+ console.log("[IMA] Emitting 'all_ads_completed' event to player");
701
788
  emit("all_ads_completed");
702
789
  });
703
790
  console.log("[IMA] Ads manager event listeners attached");
@@ -746,7 +833,17 @@ function createImaController(video, options) {
746
833
  adsLoader.addEventListener(
747
834
  google.ima.AdErrorEvent.Type.AD_ERROR,
748
835
  (adErrorEvent) => {
749
- console.error("[IMA] Ads loader error:", adErrorEvent.getError());
836
+ var _a, _b, _c, _d;
837
+ const error = adErrorEvent.getError();
838
+ console.error("[IMA] \u274C ADS_LOADER ERROR - Ad request failed!", {
839
+ message: (_a = error.getMessage) == null ? void 0 : _a.call(error),
840
+ errorCode: (_b = error.getErrorCode) == null ? void 0 : _b.call(error),
841
+ type: (_c = error.getType) == null ? void 0 : _c.call(error),
842
+ vastErrorCode: (_d = error.getVastErrorCode) == null ? void 0 : _d.call(error)
843
+ });
844
+ console.error("[IMA] This means the ad server didn't return valid ads");
845
+ console.error("[IMA] Ad layer will NOT appear (no flicker)");
846
+ console.error("[IMA] Full error object:", adErrorEvent.getError());
750
847
  adPlaying = false;
751
848
  setAdPlayingFlag(false);
752
849
  if (adContainerEl) {
@@ -778,8 +875,10 @@ function createImaController(video, options) {
778
875
  false
779
876
  );
780
877
  }
781
- console.log("[IMA] Making ads request");
878
+ console.log("[IMA] \u{1F680} Making ads request to IMA SDK");
879
+ console.log("[IMA] Waiting for IMA SDK response (LOADED or ERROR event)...");
782
880
  makeAdsRequest(google, vastTagUrl);
881
+ console.log("[IMA] \u23F3 Returning promise that will resolve when ads are loaded");
783
882
  return adsLoadedPromise;
784
883
  } catch (error) {
785
884
  console.error("[IMA] Failed to request ads:", error);
@@ -817,20 +916,23 @@ function createImaController(video, options) {
817
916
  },
818
917
  async play() {
819
918
  var _a, _b;
919
+ console.log("[IMA] \u25B6\uFE0F === play() called ===");
920
+ console.log("[IMA] This initializes and starts the ad");
921
+ console.log("[IMA] Ad layer will appear when CONTENT_PAUSE_REQUESTED fires");
820
922
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
821
923
  console.warn(
822
- "[IMA] Cannot play ad: IMA SDK or ad container not available"
924
+ "[IMA] \u274C Cannot play ad: IMA SDK or ad container not available"
823
925
  );
824
926
  return Promise.reject(new Error("IMA SDK not available"));
825
927
  }
826
928
  if (!adsManager) {
827
- console.warn("[IMA] Cannot play ad: No ads manager available");
929
+ console.warn("[IMA] \u274C Cannot play ad: No ads manager available");
828
930
  return Promise.reject(new Error("No ads manager"));
829
931
  }
830
932
  try {
831
933
  const width = video.clientWidth || 640;
832
934
  const height = video.clientHeight || 360;
833
- console.log(`[IMA] Initializing ads manager (${width}x${height})`);
935
+ console.log(`[IMA] \u{1F3AC} Initializing ads manager (${width}x${height})`);
834
936
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
835
937
  adPlaying = true;
836
938
  const adVolume = originalMutedState ? 0 : originalVolume;
@@ -838,7 +940,7 @@ function createImaController(video, options) {
838
940
  adVideoElement.volume = adVolume;
839
941
  adVideoElement.muted = originalMutedState;
840
942
  console.log(
841
- `[IMA] Set dedicated ad video volume to ${adVolume}, muted: ${originalMutedState}`
943
+ `[IMA] \u{1F50A} Set dedicated ad video volume to ${adVolume}, muted: ${originalMutedState}`
842
944
  );
843
945
  }
844
946
  try {
@@ -847,11 +949,13 @@ function createImaController(video, options) {
847
949
  } catch (error) {
848
950
  console.warn("[IMA] Failed to set IMA manager volume:", error);
849
951
  }
850
- console.log("[IMA] Starting ad playback");
952
+ console.log("[IMA] \u{1F3AF} Calling adsManager.start()");
953
+ console.log("[IMA] If successful, IMA will fire CONTENT_PAUSE_REQUESTED");
851
954
  adsManager.start();
955
+ console.log("[IMA] \u2705 play() completed successfully");
852
956
  return Promise.resolve();
853
957
  } catch (error) {
854
- console.error("[IMA] Error starting ad playback:", error);
958
+ console.error("[IMA] \u274C Error starting ad playback:", error);
855
959
  adPlaying = false;
856
960
  setAdPlayingFlag(false);
857
961
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
@@ -2176,6 +2280,9 @@ var StormcloudVideoPlayer = class {
2176
2280
  this.hasInitialBufferCompleted = false;
2177
2281
  this.adPodAllUrls = [];
2178
2282
  this.preloadingAdUrls = /* @__PURE__ */ new Set();
2283
+ this.vastToMediaUrlMap = /* @__PURE__ */ new Map();
2284
+ this.preloadedMediaUrls = /* @__PURE__ */ new Set();
2285
+ this.preloadingMediaUrls = /* @__PURE__ */ new Set();
2179
2286
  initializePolyfills();
2180
2287
  const browserOverrides = getBrowserConfigOverrides();
2181
2288
  this.config = { ...config, ...browserOverrides };
@@ -2448,27 +2555,44 @@ var StormcloudVideoPlayer = class {
2448
2555
  });
2449
2556
  this.ima.on("ad_error", () => {
2450
2557
  if (this.config.debugAdTiming) {
2451
- console.log("[StormcloudVideoPlayer] IMA ad_error event received");
2558
+ console.log("[StormcloudVideoPlayer] IMA ad_error event received", {
2559
+ showAds: this.showAds,
2560
+ inAdBreak: this.inAdBreak,
2561
+ remainingAds: this.adPodQueue.length
2562
+ });
2452
2563
  }
2453
- if (this.showAds) {
2454
- if (this.inAdBreak) {
2455
- const remaining = this.getRemainingAdMs();
2456
- if (remaining > 500 && this.adPodQueue.length > 0) {
2457
- const next = this.adPodQueue.shift();
2564
+ if (this.inAdBreak) {
2565
+ const remaining = this.getRemainingAdMs();
2566
+ if (remaining > 500 && this.adPodQueue.length > 0) {
2567
+ const nextPreloaded = this.findNextPreloadedAd();
2568
+ if (nextPreloaded) {
2458
2569
  this.currentAdIndex++;
2459
- this.playSingleAd(next).catch(() => {
2570
+ if (this.config.debugAdTiming) {
2571
+ console.log(
2572
+ `[StormcloudVideoPlayer] Skipping to next preloaded ad after error`
2573
+ );
2574
+ }
2575
+ this.playSingleAd(nextPreloaded).catch(() => {
2576
+ this.handleAdFailure();
2460
2577
  });
2461
2578
  } else {
2579
+ if (this.config.debugAdTiming) {
2580
+ console.log(
2581
+ "[StormcloudVideoPlayer] No preloaded ads available, ending ad break"
2582
+ );
2583
+ }
2462
2584
  this.handleAdFailure();
2463
2585
  }
2464
2586
  } else {
2465
- if (this.config.debugAdTiming) {
2466
- console.log(
2467
- "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2468
- );
2469
- }
2470
2587
  this.handleAdFailure();
2471
2588
  }
2589
+ } else {
2590
+ if (this.config.debugAdTiming) {
2591
+ console.log(
2592
+ "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2593
+ );
2594
+ }
2595
+ this.handleAdFailure();
2472
2596
  }
2473
2597
  });
2474
2598
  this.ima.on("content_pause", () => {
@@ -2499,22 +2623,30 @@ var StormcloudVideoPlayer = class {
2499
2623
  }
2500
2624
  const remaining = this.getRemainingAdMs();
2501
2625
  if (remaining > 500 && this.adPodQueue.length > 0) {
2502
- const next = this.adPodQueue.shift();
2503
- this.currentAdIndex++;
2504
- this.enforceAdHoldState();
2505
- if (this.config.debugAdTiming) {
2506
- console.log(
2507
- `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
2508
- );
2509
- }
2510
- this.playSingleAd(next).catch(() => {
2626
+ const nextPreloaded = this.findNextPreloadedAd();
2627
+ if (nextPreloaded) {
2628
+ this.currentAdIndex++;
2511
2629
  if (this.config.debugAdTiming) {
2512
- console.error(
2513
- "[StormcloudVideoPlayer] Failed to play next ad in pod"
2630
+ console.log(
2631
+ `[StormcloudVideoPlayer] Playing next preloaded ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - layer will stay visible if ad loads`
2632
+ );
2633
+ }
2634
+ this.playSingleAd(nextPreloaded).catch(() => {
2635
+ if (this.config.debugAdTiming) {
2636
+ console.error(
2637
+ "[StormcloudVideoPlayer] Failed to play next ad in pod"
2638
+ );
2639
+ }
2640
+ this.handleAdPodComplete();
2641
+ });
2642
+ } else {
2643
+ if (this.config.debugAdTiming) {
2644
+ console.log(
2645
+ "[StormcloudVideoPlayer] No preloaded ads available - completing ad break"
2514
2646
  );
2515
2647
  }
2516
2648
  this.handleAdPodComplete();
2517
- });
2649
+ }
2518
2650
  } else {
2519
2651
  if (this.config.debugAdTiming) {
2520
2652
  console.log(
@@ -2759,6 +2891,9 @@ var StormcloudVideoPlayer = class {
2759
2891
  const first = tags[0];
2760
2892
  const rest = tags.slice(1);
2761
2893
  this.adPodQueue = rest;
2894
+ if (!this.showAds) {
2895
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2896
+ }
2762
2897
  this.playSingleAd(first).catch(() => {
2763
2898
  });
2764
2899
  }
@@ -3115,19 +3250,37 @@ var StormcloudVideoPlayer = class {
3115
3250
  if (vastTagUrls.length > 0) {
3116
3251
  this.adPodAllUrls = [...vastTagUrls];
3117
3252
  this.preloadingAdUrls.clear();
3253
+ this.vastToMediaUrlMap.clear();
3254
+ this.preloadedMediaUrls.clear();
3255
+ this.preloadingMediaUrls.clear();
3118
3256
  this.logQueuedAdUrls(this.adPodAllUrls);
3257
+ if (this.config.debugAdTiming) {
3258
+ console.log(
3259
+ `[StormcloudVideoPlayer] Capturing original audio state before ad break:`,
3260
+ {
3261
+ videoMuted: this.video.muted,
3262
+ videoVolume: this.video.volume
3263
+ }
3264
+ );
3265
+ }
3266
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3119
3267
  this.inAdBreak = true;
3120
- this.showAds = true;
3121
3268
  this.currentAdIndex = 0;
3122
3269
  this.totalAdsInBreak = vastTagUrls.length;
3123
3270
  this.adPodQueue = [...vastTagUrls];
3124
- this.enforceAdHoldState();
3125
- this.preloadUpcomingAds();
3126
3271
  if (this.config.debugAdTiming) {
3127
3272
  console.log(
3128
- `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
3273
+ `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - preloading all ads in parallel (ad layer will show after ad loads)`
3129
3274
  );
3130
3275
  }
3276
+ this.preloadAllAdsInBackground().catch((error) => {
3277
+ if (this.config.debugAdTiming) {
3278
+ console.warn(
3279
+ "[StormcloudVideoPlayer] Error in background preloading:",
3280
+ error
3281
+ );
3282
+ }
3283
+ });
3131
3284
  try {
3132
3285
  await this.playAdPod();
3133
3286
  } catch (error) {
@@ -3153,14 +3306,28 @@ var StormcloudVideoPlayer = class {
3153
3306
  }
3154
3307
  return;
3155
3308
  }
3156
- const firstAd = this.adPodQueue.shift();
3309
+ await new Promise((resolve) => setTimeout(resolve, 500));
3310
+ const firstPreloaded = this.findNextPreloadedAd();
3311
+ if (!firstPreloaded) {
3312
+ if (this.config.debugAdTiming) {
3313
+ console.log(
3314
+ "[StormcloudVideoPlayer] No preloaded ads available after waiting, trying first ad anyway"
3315
+ );
3316
+ }
3317
+ const firstAd = this.adPodQueue.shift();
3318
+ if (firstAd) {
3319
+ this.currentAdIndex++;
3320
+ await this.playSingleAd(firstAd);
3321
+ }
3322
+ return;
3323
+ }
3157
3324
  this.currentAdIndex++;
3158
3325
  if (this.config.debugAdTiming) {
3159
3326
  console.log(
3160
- `[StormcloudVideoPlayer] Playing ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3327
+ `[StormcloudVideoPlayer] Playing first preloaded ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3161
3328
  );
3162
3329
  }
3163
- await this.playSingleAd(firstAd);
3330
+ await this.playSingleAd(firstPreloaded);
3164
3331
  }
3165
3332
  findCurrentOrNextBreak(nowMs) {
3166
3333
  var _a;
@@ -3193,7 +3360,7 @@ var StormcloudVideoPlayer = class {
3193
3360
  const first = tags[0];
3194
3361
  const rest = tags.slice(1);
3195
3362
  this.adPodQueue = rest;
3196
- this.enforceAdHoldState();
3363
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3197
3364
  await this.playSingleAd(first);
3198
3365
  this.inAdBreak = true;
3199
3366
  this.expectedAdBreakDurationMs = remainingMs;
@@ -3313,38 +3480,20 @@ var StormcloudVideoPlayer = class {
3313
3480
  `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3314
3481
  );
3315
3482
  }
3316
- if (!this.showAds) {
3317
- if (this.config.debugAdTiming) {
3318
- console.log(
3319
- `[StormcloudVideoPlayer] Capturing original state before ad request:`,
3320
- {
3321
- videoMuted: this.video.muted,
3322
- videoVolume: this.video.volume,
3323
- showAds: this.showAds
3324
- }
3325
- );
3326
- }
3327
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3328
- } else if (this.config.debugAdTiming) {
3329
- console.log(
3330
- `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
3331
- );
3332
- }
3333
3483
  this.startAdFailsafeTimer();
3334
3484
  try {
3335
3485
  await this.ima.requestAds(vastTagUrl);
3336
- this.preloadUpcomingAds();
3337
3486
  try {
3338
3487
  if (this.config.debugAdTiming) {
3339
3488
  console.log(
3340
- "[StormcloudVideoPlayer] Ad request completed, attempting playback"
3489
+ "[StormcloudVideoPlayer] Ad request completed, attempting playback (ad layer will show when IMA triggers content pause)"
3341
3490
  );
3342
3491
  }
3343
- this.enforceAdHoldState();
3344
3492
  await this.ima.play();
3493
+ this.showAds = true;
3345
3494
  if (this.config.debugAdTiming) {
3346
3495
  console.log(
3347
- "[StormcloudVideoPlayer] Ad playback started successfully"
3496
+ "[StormcloudVideoPlayer] Ad playback started successfully, showAds = true"
3348
3497
  );
3349
3498
  }
3350
3499
  } catch (playError) {
@@ -3372,6 +3521,9 @@ var StormcloudVideoPlayer = class {
3372
3521
  }
3373
3522
  this.releaseAdHoldState();
3374
3523
  this.preloadingAdUrls.clear();
3524
+ this.vastToMediaUrlMap.clear();
3525
+ this.preloadedMediaUrls.clear();
3526
+ this.preloadingMediaUrls.clear();
3375
3527
  this.inAdBreak = false;
3376
3528
  this.expectedAdBreakDurationMs = void 0;
3377
3529
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3481,44 +3633,204 @@ var StormcloudVideoPlayer = class {
3481
3633
  this.ima.hidePlaceholder();
3482
3634
  }
3483
3635
  }
3484
- preloadUpcomingAds() {
3485
- if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3486
- return;
3636
+ async fetchAndParseVastXml(vastTagUrl) {
3637
+ try {
3638
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3639
+ if (!response.ok) {
3640
+ throw new Error(`Failed to fetch VAST: ${response.status}`);
3641
+ }
3642
+ const xmlText = await response.text();
3643
+ return this.extractMediaUrlsFromVast(xmlText);
3644
+ } catch (error) {
3645
+ if (this.config.debugAdTiming) {
3646
+ console.warn(
3647
+ `[StormcloudVideoPlayer] Failed to fetch/parse VAST XML: ${vastTagUrl}`,
3648
+ error
3649
+ );
3650
+ }
3651
+ return [];
3487
3652
  }
3488
- const upcoming = this.adPodQueue.slice(0, 2);
3489
- for (const url of upcoming) {
3490
- if (!url) continue;
3491
- if (this.ima.hasPreloadedAd(url)) {
3492
- this.preloadingAdUrls.delete(url);
3493
- continue;
3653
+ }
3654
+ extractMediaUrlsFromVast(xmlText) {
3655
+ var _a;
3656
+ const mediaUrls = [];
3657
+ try {
3658
+ const parser = new DOMParser();
3659
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3660
+ const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
3661
+ for (const mediaFile of Array.from(mediaFileElements)) {
3662
+ const url = (_a = mediaFile.textContent) == null ? void 0 : _a.trim();
3663
+ if (url) {
3664
+ const lowerUrl = url.toLowerCase();
3665
+ 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")) {
3666
+ mediaUrls.push(url);
3667
+ }
3668
+ }
3494
3669
  }
3495
- if (this.preloadingAdUrls.has(url)) {
3496
- continue;
3670
+ if (this.config.debugAdTiming && mediaUrls.length > 0) {
3671
+ console.log(
3672
+ `[StormcloudVideoPlayer] Extracted ${mediaUrls.length} media URLs from VAST:`,
3673
+ mediaUrls
3674
+ );
3497
3675
  }
3676
+ } catch (error) {
3677
+ if (this.config.debugAdTiming) {
3678
+ console.warn(
3679
+ "[StormcloudVideoPlayer] Failed to parse VAST XML:",
3680
+ error
3681
+ );
3682
+ }
3683
+ }
3684
+ return mediaUrls;
3685
+ }
3686
+ async preloadMediaFile(mediaUrl) {
3687
+ if (this.preloadedMediaUrls.has(mediaUrl)) {
3688
+ return;
3689
+ }
3690
+ if (this.preloadingMediaUrls.has(mediaUrl)) {
3691
+ return;
3692
+ }
3693
+ this.preloadingMediaUrls.add(mediaUrl);
3694
+ try {
3498
3695
  if (this.config.debugAdTiming) {
3499
3696
  console.log(
3500
- `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3697
+ `[StormcloudVideoPlayer] Preloading video file: ${mediaUrl}`
3501
3698
  );
3502
3699
  }
3503
- this.preloadingAdUrls.add(url);
3504
- this.ima.preloadAds(url).then(() => {
3700
+ const response = await fetch(mediaUrl, {
3701
+ mode: "cors",
3702
+ method: "GET",
3703
+ headers: {
3704
+ Range: "bytes=0-1048576"
3705
+ }
3706
+ });
3707
+ if (response.ok || response.status === 206) {
3708
+ this.preloadedMediaUrls.add(mediaUrl);
3505
3709
  if (this.config.debugAdTiming) {
3506
3710
  console.log(
3507
- `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3711
+ `[StormcloudVideoPlayer] Successfully preloaded video file: ${mediaUrl}`
3508
3712
  );
3509
3713
  }
3510
- }).catch((error) => {
3714
+ }
3715
+ } catch (error) {
3716
+ if (this.config.debugAdTiming) {
3717
+ console.warn(
3718
+ `[StormcloudVideoPlayer] Failed to preload video file: ${mediaUrl}`,
3719
+ error
3720
+ );
3721
+ }
3722
+ } finally {
3723
+ this.preloadingMediaUrls.delete(mediaUrl);
3724
+ }
3725
+ }
3726
+ async preloadAllAdsInBackground() {
3727
+ if (this.adPodAllUrls.length === 0) {
3728
+ return;
3729
+ }
3730
+ if (this.config.debugAdTiming) {
3731
+ console.log(
3732
+ `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3733
+ );
3734
+ }
3735
+ const preloadPromises = this.adPodAllUrls.map(
3736
+ (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3511
3737
  if (this.config.debugAdTiming) {
3512
3738
  console.warn(
3513
- `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3739
+ `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3514
3740
  error
3515
3741
  );
3516
3742
  }
3517
- }).finally(() => {
3518
- this.preloadingAdUrls.delete(url);
3519
- });
3743
+ })
3744
+ );
3745
+ await Promise.all(preloadPromises);
3746
+ if (this.config.debugAdTiming) {
3747
+ console.log(
3748
+ `[StormcloudVideoPlayer] Background preloading completed for all ads`
3749
+ );
3520
3750
  }
3521
3751
  }
3752
+ async preloadSingleAd(vastTagUrl) {
3753
+ if (!vastTagUrl) return;
3754
+ try {
3755
+ if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3756
+ if (!this.preloadingAdUrls.has(vastTagUrl)) {
3757
+ if (this.config.debugAdTiming) {
3758
+ console.log(
3759
+ `[StormcloudVideoPlayer] Preloading VAST: ${vastTagUrl}`
3760
+ );
3761
+ }
3762
+ this.preloadingAdUrls.add(vastTagUrl);
3763
+ await this.ima.preloadAds(vastTagUrl).then(() => {
3764
+ if (this.config.debugAdTiming) {
3765
+ console.log(
3766
+ `[StormcloudVideoPlayer] IMA VAST preload complete: ${vastTagUrl}`
3767
+ );
3768
+ }
3769
+ }).catch((error) => {
3770
+ if (this.config.debugAdTiming) {
3771
+ console.warn(
3772
+ `[StormcloudVideoPlayer] IMA VAST preload failed: ${vastTagUrl}`,
3773
+ error
3774
+ );
3775
+ }
3776
+ }).finally(() => {
3777
+ this.preloadingAdUrls.delete(vastTagUrl);
3778
+ });
3779
+ }
3780
+ }
3781
+ let mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3782
+ if (!mediaUrls) {
3783
+ if (this.config.debugAdTiming) {
3784
+ console.log(
3785
+ `[StormcloudVideoPlayer] Fetching and parsing VAST to extract media URLs: ${vastTagUrl}`
3786
+ );
3787
+ }
3788
+ mediaUrls = await this.fetchAndParseVastXml(vastTagUrl);
3789
+ if (mediaUrls.length > 0) {
3790
+ this.vastToMediaUrlMap.set(vastTagUrl, mediaUrls);
3791
+ }
3792
+ }
3793
+ if (mediaUrls && mediaUrls.length > 0) {
3794
+ const primaryMediaUrl = mediaUrls[0];
3795
+ if (primaryMediaUrl && !this.preloadedMediaUrls.has(primaryMediaUrl)) {
3796
+ await this.preloadMediaFile(primaryMediaUrl);
3797
+ }
3798
+ }
3799
+ } catch (error) {
3800
+ if (this.config.debugAdTiming) {
3801
+ console.warn(
3802
+ `[StormcloudVideoPlayer] Failed to preload ad: ${vastTagUrl}`,
3803
+ error
3804
+ );
3805
+ }
3806
+ }
3807
+ }
3808
+ findNextPreloadedAd() {
3809
+ var _a, _b, _c;
3810
+ for (let i = 0; i < this.adPodQueue.length; i++) {
3811
+ const vastTagUrl = this.adPodQueue[i];
3812
+ if (!vastTagUrl) continue;
3813
+ const hasImaPreload = (_c = (_b = (_a = this.ima).hasPreloadedAd) == null ? void 0 : _b.call(_a, vastTagUrl)) != null ? _c : false;
3814
+ const mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3815
+ const hasMediaPreload = mediaUrls && mediaUrls.length > 0 ? this.preloadedMediaUrls.has(mediaUrls[0]) : false;
3816
+ if (hasImaPreload || hasMediaPreload) {
3817
+ if (this.config.debugAdTiming) {
3818
+ console.log(
3819
+ `[StormcloudVideoPlayer] Found preloaded ad at index ${i}: ${vastTagUrl}`,
3820
+ { hasImaPreload, hasMediaPreload }
3821
+ );
3822
+ }
3823
+ this.adPodQueue.splice(0, i + 1);
3824
+ return vastTagUrl;
3825
+ }
3826
+ }
3827
+ if (this.config.debugAdTiming) {
3828
+ console.log(
3829
+ "[StormcloudVideoPlayer] No preloaded ads found in queue"
3830
+ );
3831
+ }
3832
+ return void 0;
3833
+ }
3522
3834
  getRemainingAdMs() {
3523
3835
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3524
3836
  return 0;
@@ -3687,6 +3999,9 @@ var StormcloudVideoPlayer = class {
3687
3999
  (_b = this.ima) == null ? void 0 : _b.destroy();
3688
4000
  this.releaseAdHoldState();
3689
4001
  this.preloadingAdUrls.clear();
4002
+ this.vastToMediaUrlMap.clear();
4003
+ this.preloadedMediaUrls.clear();
4004
+ this.preloadingMediaUrls.clear();
3690
4005
  this.adPodAllUrls = [];
3691
4006
  }
3692
4007
  };