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.
package/lib/index.cjs CHANGED
@@ -415,15 +415,17 @@ function createImaController(video, options) {
415
415
  let adsLoadedResolve;
416
416
  let adsLoadedReject;
417
417
  function makeAdsRequest(google, vastTagUrl) {
418
+ console.log("[IMA] \u{1F4CB} === makeAdsRequest() - Building IMA request ===");
418
419
  const adsRequest = new google.ima.AdsRequest();
419
420
  const preloadedResponse = preloadedVast.get(vastTagUrl);
420
421
  if (preloadedResponse) {
421
422
  adsRequest.adsResponse = preloadedResponse;
422
423
  console.log(
423
- "[IMA] Using preloaded VAST response for immediate ad request"
424
+ "[IMA] \u26A1 Using preloaded VAST response for immediate ad request"
424
425
  );
425
426
  } else {
426
427
  adsRequest.adTagUrl = vastTagUrl;
428
+ console.log("[IMA] \u{1F310} Will fetch VAST from URL:", vastTagUrl);
427
429
  }
428
430
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
429
431
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
@@ -435,6 +437,7 @@ function createImaController(video, options) {
435
437
  try {
436
438
  const willAutoPlay = !video.paused || video.autoplay;
437
439
  adsRequest.setAdWillAutoPlay(willAutoPlay);
440
+ console.log(`[IMA] Ad will autoplay: ${willAutoPlay}`);
438
441
  } catch (error) {
439
442
  console.warn("[IMA] Failed to call setAdWillAutoPlay:", error);
440
443
  }
@@ -443,13 +446,17 @@ function createImaController(video, options) {
443
446
  try {
444
447
  const willPlayMuted = video.muted || video.volume === 0;
445
448
  adsRequest.setAdWillPlayMuted(willPlayMuted);
449
+ console.log(`[IMA] Ad will play muted: ${willPlayMuted}`);
446
450
  } catch (error) {
447
451
  console.warn("[IMA] Failed to call setAdWillPlayMuted:", error);
448
452
  }
449
453
  }
450
454
  adsRequest.vastLoadTimeout = 5e3;
451
- console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
455
+ console.log(`[IMA] \u{1F4D0} Ads request dimensions: ${videoWidth}x${videoHeight}`);
456
+ console.log("[IMA] \u23F1\uFE0F VAST load timeout: 5000ms");
457
+ console.log("[IMA] \u{1F680} Calling adsLoader.requestAds()...");
452
458
  adsLoader.requestAds(adsRequest);
459
+ console.log("[IMA] \u23F3 Waiting for ADS_MANAGER_LOADED or AD_ERROR event...");
453
460
  if (preloadedResponse) {
454
461
  preloadedVast.delete(vastTagUrl);
455
462
  }
@@ -527,22 +534,24 @@ function createImaController(video, options) {
527
534
  });
528
535
  },
529
536
  async requestAds(vastTagUrl) {
530
- console.log("[IMA] Requesting ads:", vastTagUrl);
537
+ console.log("[IMA] \u{1F4E1} === requestAds() called ===");
538
+ console.log("[IMA] VAST URL:", vastTagUrl);
539
+ console.log("[IMA] This will fetch the ad from the server - no visual change yet");
531
540
  if (!vastTagUrl || vastTagUrl.trim() === "") {
532
541
  const error = new Error("VAST tag URL is empty or undefined");
533
- console.warn("[IMA]", error.message);
542
+ console.warn("[IMA] \u274C", error.message);
534
543
  return Promise.reject(error);
535
544
  }
536
545
  try {
537
546
  new URL(vastTagUrl);
538
547
  } catch (e) {
539
548
  const error = new Error(`Invalid VAST tag URL format: ${vastTagUrl}`);
540
- console.warn("[IMA]", error.message);
549
+ console.warn("[IMA] \u274C", error.message);
541
550
  return Promise.reject(error);
542
551
  }
543
552
  if (adPlaying) {
544
553
  console.warn(
545
- "[IMA] Cannot request new ads while an ad is playing. Call stop() first."
554
+ "[IMA] \u26A0\uFE0F Cannot request new ads while an ad is playing. Call stop() first."
546
555
  );
547
556
  return Promise.reject(
548
557
  new Error("Ad already playing - cannot request new ads")
@@ -632,20 +641,85 @@ function createImaController(video, options) {
632
641
  adsLoader.addEventListener(
633
642
  google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
634
643
  (evt) => {
635
- console.log(
636
- "[IMA] Ads manager loaded - enabling preloading for continuous playback"
637
- );
644
+ console.log("[IMA] \u2705 ADS_MANAGER_LOADED - Ads fetched successfully!");
645
+ console.log("[IMA] Setting up ads manager with preloading enabled");
646
+ console.log("[IMA] ========================================");
647
+ console.log("[IMA] EXPECTED EVENT FLOW:");
648
+ console.log("[IMA] 1. requestAds() \u2192 fetch VAST");
649
+ console.log("[IMA] 2. ADS_MANAGER_LOADED \u2192 ads ready");
650
+ console.log("[IMA] 3. play() \u2192 start playback");
651
+ console.log("[IMA] 4. CONTENT_PAUSE_REQUESTED \u2192 show ad layer \u2728");
652
+ console.log("[IMA] 5. STARTED \u2192 ad is playing");
653
+ console.log("[IMA] 6. CONTENT_RESUME_REQUESTED \u2192 ad done");
654
+ console.log("[IMA] 7. ALL_ADS_COMPLETED \u2192 hide ad layer");
655
+ console.log("[IMA] ========================================");
638
656
  try {
639
657
  const adsRenderingSettings = new google.ima.AdsRenderingSettings();
640
658
  adsRenderingSettings.enablePreloading = true;
641
659
  adsManager = evt.getAdsManager(video, adsRenderingSettings);
642
660
  const AdEvent = google.ima.AdEvent.Type;
643
661
  const AdErrorEvent = google.ima.AdErrorEvent.Type;
662
+ console.log("[IMA] ========== IMA EVENT LOGGING ENABLED ==========");
663
+ console.log("[IMA] All IMA SDK events will be logged below");
664
+ const allAdEvents = [
665
+ "AD_BREAK_READY",
666
+ "AD_METADATA",
667
+ "ALL_ADS_COMPLETED",
668
+ "CLICK",
669
+ "COMPLETE",
670
+ "CONTENT_PAUSE_REQUESTED",
671
+ "CONTENT_RESUME_REQUESTED",
672
+ "DURATION_CHANGE",
673
+ "FIRST_QUARTILE",
674
+ "IMPRESSION",
675
+ "INTERACTION",
676
+ "LINEAR_CHANGED",
677
+ "LOADED",
678
+ "LOG",
679
+ "MIDPOINT",
680
+ "PAUSED",
681
+ "RESUMED",
682
+ "SKIPPABLE_STATE_CHANGED",
683
+ "SKIPPED",
684
+ "STARTED",
685
+ "THIRD_QUARTILE",
686
+ "USER_CLOSE",
687
+ "VOLUME_CHANGED",
688
+ "VOLUME_MUTED"
689
+ ];
690
+ allAdEvents.forEach((eventType) => {
691
+ if (AdEvent[eventType]) {
692
+ adsManager.addEventListener(AdEvent[eventType], (e) => {
693
+ var _a, _b, _c, _d, _e, _f;
694
+ const ad = (_a = e.getAd) == null ? void 0 : _a.call(e);
695
+ const adData = ad ? {
696
+ adId: (_b = ad.getAdId) == null ? void 0 : _b.call(ad),
697
+ title: (_c = ad.getTitle) == null ? void 0 : _c.call(ad),
698
+ duration: (_d = ad.getDuration) == null ? void 0 : _d.call(ad),
699
+ isLinear: (_e = ad.isLinear) == null ? void 0 : _e.call(ad),
700
+ contentType: (_f = ad.getContentType) == null ? void 0 : _f.call(ad)
701
+ } : null;
702
+ console.log(`[IMA EVENT] ${eventType}`, {
703
+ eventType,
704
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
705
+ adData
706
+ });
707
+ });
708
+ }
709
+ });
710
+ console.log("[IMA] ========== EVENT LISTENERS ATTACHED ==========");
644
711
  adsManager.addEventListener(
645
712
  AdErrorEvent.AD_ERROR,
646
713
  (errorEvent) => {
647
- var _a;
648
- console.error("[IMA] Ad error:", errorEvent.getError());
714
+ var _a, _b, _c, _d, _e, _f;
715
+ const error = errorEvent.getError();
716
+ console.error("[IMA] \u274C AD_ERROR Event:", {
717
+ message: (_a = error.getMessage) == null ? void 0 : _a.call(error),
718
+ errorCode: (_b = error.getErrorCode) == null ? void 0 : _b.call(error),
719
+ type: (_c = error.getType) == null ? void 0 : _c.call(error),
720
+ vastErrorCode: (_d = error.getVastErrorCode) == null ? void 0 : _d.call(error),
721
+ innerError: (_e = error.getInnerError) == null ? void 0 : _e.call(error)
722
+ });
649
723
  destroyAdsManager();
650
724
  adPlaying = false;
651
725
  setAdPlayingFlag(false);
@@ -687,7 +761,7 @@ function createImaController(video, options) {
687
761
  console.log(
688
762
  "[IMA] Resuming paused video after ad error"
689
763
  );
690
- (_a = video.play()) == null ? void 0 : _a.catch(() => {
764
+ (_f = video.play()) == null ? void 0 : _f.catch(() => {
691
765
  });
692
766
  }
693
767
  }
@@ -697,7 +771,8 @@ function createImaController(video, options) {
697
771
  adsManager.addEventListener(
698
772
  AdEvent.CONTENT_PAUSE_REQUESTED,
699
773
  () => {
700
- console.log("[IMA] Content pause requested");
774
+ console.log("[IMA] \u2705 CONTENT_PAUSE_REQUESTED - Ad is ready to play!");
775
+ console.log("[IMA] This is the event that triggers the ad layer to appear");
701
776
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
702
777
  video.pause();
703
778
  console.log("[IMA] Content video paused (VOD mode)");
@@ -706,13 +781,23 @@ function createImaController(video, options) {
706
781
  "[IMA] Content video continues in background (Live mode)"
707
782
  );
708
783
  }
784
+ hideContentVideo();
785
+ if (adContainerEl) {
786
+ adContainerEl.style.pointerEvents = "auto";
787
+ adContainerEl.style.display = "flex";
788
+ adContainerEl.style.backgroundColor = "#000";
789
+ adContainerEl.offsetHeight;
790
+ adContainerEl.style.opacity = "1";
791
+ console.log("[IMA] \u2728 Ad container NOW VISIBLE (after content pause)");
792
+ }
709
793
  adPlaying = true;
710
794
  setAdPlayingFlag(true);
795
+ console.log("[IMA] Emitting 'content_pause' event to player");
711
796
  emit("content_pause");
712
797
  }
713
798
  );
714
799
  adsManager.addEventListener(AdEvent.STARTED, () => {
715
- console.log("[IMA] Ad started - showing ad video");
800
+ console.log("[IMA] \u25B6\uFE0F STARTED - Ad playback has begun");
716
801
  setAdPlayingFlag(true);
717
802
  hideContentVideo();
718
803
  if (adVideoElement) {
@@ -728,20 +813,21 @@ function createImaController(video, options) {
728
813
  adContainerEl.style.backgroundColor = "#000";
729
814
  adContainerEl.offsetHeight;
730
815
  adContainerEl.style.opacity = "1";
731
- console.log("[IMA] Ad container now visible");
816
+ console.log("[IMA] Ad container now visible (STARTED event)");
732
817
  }
733
818
  });
734
819
  adsManager.addEventListener(
735
820
  AdEvent.CONTENT_RESUME_REQUESTED,
736
821
  () => {
737
- console.log("[IMA] Content resume requested");
822
+ console.log("[IMA] \u23F8\uFE0F CONTENT_RESUME_REQUESTED - Single ad completed");
738
823
  adPlaying = false;
739
824
  setAdPlayingFlag(false);
825
+ console.log("[IMA] Emitting 'content_resume' event to player");
740
826
  emit("content_resume");
741
827
  }
742
828
  );
743
829
  adsManager.addEventListener(AdEvent.ALL_ADS_COMPLETED, () => {
744
- console.log("[IMA] All ads completed - restoring content");
830
+ console.log("[IMA] \u{1F3C1} ALL_ADS_COMPLETED - All ads in break finished");
745
831
  adPlaying = false;
746
832
  setAdPlayingFlag(false);
747
833
  if (adContainerEl) {
@@ -751,7 +837,7 @@ function createImaController(video, options) {
751
837
  if (adContainerEl) {
752
838
  adContainerEl.style.pointerEvents = "none";
753
839
  adContainerEl.style.display = "none";
754
- console.log("[IMA] Ad container hidden");
840
+ console.log("[IMA] \u{1F648} Ad container hidden (ALL_ADS_COMPLETED)");
755
841
  }
756
842
  }, 300);
757
843
  }
@@ -762,6 +848,7 @@ function createImaController(video, options) {
762
848
  console.warn("[IMA] Failed to resume content video:", e);
763
849
  });
764
850
  }
851
+ console.log("[IMA] Emitting 'all_ads_completed' event to player");
765
852
  emit("all_ads_completed");
766
853
  });
767
854
  console.log("[IMA] Ads manager event listeners attached");
@@ -810,7 +897,17 @@ function createImaController(video, options) {
810
897
  adsLoader.addEventListener(
811
898
  google.ima.AdErrorEvent.Type.AD_ERROR,
812
899
  (adErrorEvent) => {
813
- console.error("[IMA] Ads loader error:", adErrorEvent.getError());
900
+ var _a, _b, _c, _d;
901
+ const error = adErrorEvent.getError();
902
+ console.error("[IMA] \u274C ADS_LOADER ERROR - Ad request failed!", {
903
+ message: (_a = error.getMessage) == null ? void 0 : _a.call(error),
904
+ errorCode: (_b = error.getErrorCode) == null ? void 0 : _b.call(error),
905
+ type: (_c = error.getType) == null ? void 0 : _c.call(error),
906
+ vastErrorCode: (_d = error.getVastErrorCode) == null ? void 0 : _d.call(error)
907
+ });
908
+ console.error("[IMA] This means the ad server didn't return valid ads");
909
+ console.error("[IMA] Ad layer will NOT appear (no flicker)");
910
+ console.error("[IMA] Full error object:", adErrorEvent.getError());
814
911
  adPlaying = false;
815
912
  setAdPlayingFlag(false);
816
913
  if (adContainerEl) {
@@ -842,8 +939,10 @@ function createImaController(video, options) {
842
939
  false
843
940
  );
844
941
  }
845
- console.log("[IMA] Making ads request");
942
+ console.log("[IMA] \u{1F680} Making ads request to IMA SDK");
943
+ console.log("[IMA] Waiting for IMA SDK response (LOADED or ERROR event)...");
846
944
  makeAdsRequest(google, vastTagUrl);
945
+ console.log("[IMA] \u23F3 Returning promise that will resolve when ads are loaded");
847
946
  return adsLoadedPromise;
848
947
  } catch (error) {
849
948
  console.error("[IMA] Failed to request ads:", error);
@@ -881,20 +980,23 @@ function createImaController(video, options) {
881
980
  },
882
981
  async play() {
883
982
  var _a, _b;
983
+ console.log("[IMA] \u25B6\uFE0F === play() called ===");
984
+ console.log("[IMA] This initializes and starts the ad");
985
+ console.log("[IMA] Ad layer will appear when CONTENT_PAUSE_REQUESTED fires");
884
986
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
885
987
  console.warn(
886
- "[IMA] Cannot play ad: IMA SDK or ad container not available"
988
+ "[IMA] \u274C Cannot play ad: IMA SDK or ad container not available"
887
989
  );
888
990
  return Promise.reject(new Error("IMA SDK not available"));
889
991
  }
890
992
  if (!adsManager) {
891
- console.warn("[IMA] Cannot play ad: No ads manager available");
993
+ console.warn("[IMA] \u274C Cannot play ad: No ads manager available");
892
994
  return Promise.reject(new Error("No ads manager"));
893
995
  }
894
996
  try {
895
997
  const width = video.clientWidth || 640;
896
998
  const height = video.clientHeight || 360;
897
- console.log(`[IMA] Initializing ads manager (${width}x${height})`);
999
+ console.log(`[IMA] \u{1F3AC} Initializing ads manager (${width}x${height})`);
898
1000
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
899
1001
  adPlaying = true;
900
1002
  const adVolume = originalMutedState ? 0 : originalVolume;
@@ -902,7 +1004,7 @@ function createImaController(video, options) {
902
1004
  adVideoElement.volume = adVolume;
903
1005
  adVideoElement.muted = originalMutedState;
904
1006
  console.log(
905
- `[IMA] Set dedicated ad video volume to ${adVolume}, muted: ${originalMutedState}`
1007
+ `[IMA] \u{1F50A} Set dedicated ad video volume to ${adVolume}, muted: ${originalMutedState}`
906
1008
  );
907
1009
  }
908
1010
  try {
@@ -911,11 +1013,13 @@ function createImaController(video, options) {
911
1013
  } catch (error) {
912
1014
  console.warn("[IMA] Failed to set IMA manager volume:", error);
913
1015
  }
914
- console.log("[IMA] Starting ad playback");
1016
+ console.log("[IMA] \u{1F3AF} Calling adsManager.start()");
1017
+ console.log("[IMA] If successful, IMA will fire CONTENT_PAUSE_REQUESTED");
915
1018
  adsManager.start();
1019
+ console.log("[IMA] \u2705 play() completed successfully");
916
1020
  return Promise.resolve();
917
1021
  } catch (error) {
918
- console.error("[IMA] Error starting ad playback:", error);
1022
+ console.error("[IMA] \u274C Error starting ad playback:", error);
919
1023
  adPlaying = false;
920
1024
  setAdPlayingFlag(false);
921
1025
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
@@ -2240,6 +2344,9 @@ var StormcloudVideoPlayer = class {
2240
2344
  this.hasInitialBufferCompleted = false;
2241
2345
  this.adPodAllUrls = [];
2242
2346
  this.preloadingAdUrls = /* @__PURE__ */ new Set();
2347
+ this.vastToMediaUrlMap = /* @__PURE__ */ new Map();
2348
+ this.preloadedMediaUrls = /* @__PURE__ */ new Set();
2349
+ this.preloadingMediaUrls = /* @__PURE__ */ new Set();
2243
2350
  initializePolyfills();
2244
2351
  const browserOverrides = getBrowserConfigOverrides();
2245
2352
  this.config = { ...config, ...browserOverrides };
@@ -2512,27 +2619,44 @@ var StormcloudVideoPlayer = class {
2512
2619
  });
2513
2620
  this.ima.on("ad_error", () => {
2514
2621
  if (this.config.debugAdTiming) {
2515
- console.log("[StormcloudVideoPlayer] IMA ad_error event received");
2622
+ console.log("[StormcloudVideoPlayer] IMA ad_error event received", {
2623
+ showAds: this.showAds,
2624
+ inAdBreak: this.inAdBreak,
2625
+ remainingAds: this.adPodQueue.length
2626
+ });
2516
2627
  }
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();
2628
+ if (this.inAdBreak) {
2629
+ const remaining = this.getRemainingAdMs();
2630
+ if (remaining > 500 && this.adPodQueue.length > 0) {
2631
+ const nextPreloaded = this.findNextPreloadedAd();
2632
+ if (nextPreloaded) {
2522
2633
  this.currentAdIndex++;
2523
- this.playSingleAd(next).catch(() => {
2634
+ if (this.config.debugAdTiming) {
2635
+ console.log(
2636
+ `[StormcloudVideoPlayer] Skipping to next preloaded ad after error`
2637
+ );
2638
+ }
2639
+ this.playSingleAd(nextPreloaded).catch(() => {
2640
+ this.handleAdFailure();
2524
2641
  });
2525
2642
  } else {
2643
+ if (this.config.debugAdTiming) {
2644
+ console.log(
2645
+ "[StormcloudVideoPlayer] No preloaded ads available, ending ad break"
2646
+ );
2647
+ }
2526
2648
  this.handleAdFailure();
2527
2649
  }
2528
2650
  } else {
2529
- if (this.config.debugAdTiming) {
2530
- console.log(
2531
- "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2532
- );
2533
- }
2534
2651
  this.handleAdFailure();
2535
2652
  }
2653
+ } else {
2654
+ if (this.config.debugAdTiming) {
2655
+ console.log(
2656
+ "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2657
+ );
2658
+ }
2659
+ this.handleAdFailure();
2536
2660
  }
2537
2661
  });
2538
2662
  this.ima.on("content_pause", () => {
@@ -2563,22 +2687,30 @@ var StormcloudVideoPlayer = class {
2563
2687
  }
2564
2688
  const remaining = this.getRemainingAdMs();
2565
2689
  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(() => {
2690
+ const nextPreloaded = this.findNextPreloadedAd();
2691
+ if (nextPreloaded) {
2692
+ this.currentAdIndex++;
2575
2693
  if (this.config.debugAdTiming) {
2576
- console.error(
2577
- "[StormcloudVideoPlayer] Failed to play next ad in pod"
2694
+ console.log(
2695
+ `[StormcloudVideoPlayer] Playing next preloaded ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - layer will stay visible if ad loads`
2696
+ );
2697
+ }
2698
+ this.playSingleAd(nextPreloaded).catch(() => {
2699
+ if (this.config.debugAdTiming) {
2700
+ console.error(
2701
+ "[StormcloudVideoPlayer] Failed to play next ad in pod"
2702
+ );
2703
+ }
2704
+ this.handleAdPodComplete();
2705
+ });
2706
+ } else {
2707
+ if (this.config.debugAdTiming) {
2708
+ console.log(
2709
+ "[StormcloudVideoPlayer] No preloaded ads available - completing ad break"
2578
2710
  );
2579
2711
  }
2580
2712
  this.handleAdPodComplete();
2581
- });
2713
+ }
2582
2714
  } else {
2583
2715
  if (this.config.debugAdTiming) {
2584
2716
  console.log(
@@ -2823,6 +2955,9 @@ var StormcloudVideoPlayer = class {
2823
2955
  const first = tags[0];
2824
2956
  const rest = tags.slice(1);
2825
2957
  this.adPodQueue = rest;
2958
+ if (!this.showAds) {
2959
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2960
+ }
2826
2961
  this.playSingleAd(first).catch(() => {
2827
2962
  });
2828
2963
  }
@@ -3179,19 +3314,37 @@ var StormcloudVideoPlayer = class {
3179
3314
  if (vastTagUrls.length > 0) {
3180
3315
  this.adPodAllUrls = [...vastTagUrls];
3181
3316
  this.preloadingAdUrls.clear();
3317
+ this.vastToMediaUrlMap.clear();
3318
+ this.preloadedMediaUrls.clear();
3319
+ this.preloadingMediaUrls.clear();
3182
3320
  this.logQueuedAdUrls(this.adPodAllUrls);
3321
+ if (this.config.debugAdTiming) {
3322
+ console.log(
3323
+ `[StormcloudVideoPlayer] Capturing original audio state before ad break:`,
3324
+ {
3325
+ videoMuted: this.video.muted,
3326
+ videoVolume: this.video.volume
3327
+ }
3328
+ );
3329
+ }
3330
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3183
3331
  this.inAdBreak = true;
3184
- this.showAds = true;
3185
3332
  this.currentAdIndex = 0;
3186
3333
  this.totalAdsInBreak = vastTagUrls.length;
3187
3334
  this.adPodQueue = [...vastTagUrls];
3188
- this.enforceAdHoldState();
3189
- this.preloadUpcomingAds();
3190
3335
  if (this.config.debugAdTiming) {
3191
3336
  console.log(
3192
- `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
3337
+ `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - preloading all ads in parallel (ad layer will show after ad loads)`
3193
3338
  );
3194
3339
  }
3340
+ this.preloadAllAdsInBackground().catch((error) => {
3341
+ if (this.config.debugAdTiming) {
3342
+ console.warn(
3343
+ "[StormcloudVideoPlayer] Error in background preloading:",
3344
+ error
3345
+ );
3346
+ }
3347
+ });
3195
3348
  try {
3196
3349
  await this.playAdPod();
3197
3350
  } catch (error) {
@@ -3217,14 +3370,28 @@ var StormcloudVideoPlayer = class {
3217
3370
  }
3218
3371
  return;
3219
3372
  }
3220
- const firstAd = this.adPodQueue.shift();
3373
+ await new Promise((resolve) => setTimeout(resolve, 500));
3374
+ const firstPreloaded = this.findNextPreloadedAd();
3375
+ if (!firstPreloaded) {
3376
+ if (this.config.debugAdTiming) {
3377
+ console.log(
3378
+ "[StormcloudVideoPlayer] No preloaded ads available after waiting, trying first ad anyway"
3379
+ );
3380
+ }
3381
+ const firstAd = this.adPodQueue.shift();
3382
+ if (firstAd) {
3383
+ this.currentAdIndex++;
3384
+ await this.playSingleAd(firstAd);
3385
+ }
3386
+ return;
3387
+ }
3221
3388
  this.currentAdIndex++;
3222
3389
  if (this.config.debugAdTiming) {
3223
3390
  console.log(
3224
- `[StormcloudVideoPlayer] Playing ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3391
+ `[StormcloudVideoPlayer] Playing first preloaded ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3225
3392
  );
3226
3393
  }
3227
- await this.playSingleAd(firstAd);
3394
+ await this.playSingleAd(firstPreloaded);
3228
3395
  }
3229
3396
  findCurrentOrNextBreak(nowMs) {
3230
3397
  var _a;
@@ -3257,7 +3424,7 @@ var StormcloudVideoPlayer = class {
3257
3424
  const first = tags[0];
3258
3425
  const rest = tags.slice(1);
3259
3426
  this.adPodQueue = rest;
3260
- this.enforceAdHoldState();
3427
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3261
3428
  await this.playSingleAd(first);
3262
3429
  this.inAdBreak = true;
3263
3430
  this.expectedAdBreakDurationMs = remainingMs;
@@ -3377,38 +3544,20 @@ var StormcloudVideoPlayer = class {
3377
3544
  `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3378
3545
  );
3379
3546
  }
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
3547
  this.startAdFailsafeTimer();
3398
3548
  try {
3399
3549
  await this.ima.requestAds(vastTagUrl);
3400
- this.preloadUpcomingAds();
3401
3550
  try {
3402
3551
  if (this.config.debugAdTiming) {
3403
3552
  console.log(
3404
- "[StormcloudVideoPlayer] Ad request completed, attempting playback"
3553
+ "[StormcloudVideoPlayer] Ad request completed, attempting playback (ad layer will show when IMA triggers content pause)"
3405
3554
  );
3406
3555
  }
3407
- this.enforceAdHoldState();
3408
3556
  await this.ima.play();
3557
+ this.showAds = true;
3409
3558
  if (this.config.debugAdTiming) {
3410
3559
  console.log(
3411
- "[StormcloudVideoPlayer] Ad playback started successfully"
3560
+ "[StormcloudVideoPlayer] Ad playback started successfully, showAds = true"
3412
3561
  );
3413
3562
  }
3414
3563
  } catch (playError) {
@@ -3436,6 +3585,9 @@ var StormcloudVideoPlayer = class {
3436
3585
  }
3437
3586
  this.releaseAdHoldState();
3438
3587
  this.preloadingAdUrls.clear();
3588
+ this.vastToMediaUrlMap.clear();
3589
+ this.preloadedMediaUrls.clear();
3590
+ this.preloadingMediaUrls.clear();
3439
3591
  this.inAdBreak = false;
3440
3592
  this.expectedAdBreakDurationMs = void 0;
3441
3593
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3545,44 +3697,204 @@ var StormcloudVideoPlayer = class {
3545
3697
  this.ima.hidePlaceholder();
3546
3698
  }
3547
3699
  }
3548
- preloadUpcomingAds() {
3549
- if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3550
- return;
3700
+ async fetchAndParseVastXml(vastTagUrl) {
3701
+ try {
3702
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3703
+ if (!response.ok) {
3704
+ throw new Error(`Failed to fetch VAST: ${response.status}`);
3705
+ }
3706
+ const xmlText = await response.text();
3707
+ return this.extractMediaUrlsFromVast(xmlText);
3708
+ } catch (error) {
3709
+ if (this.config.debugAdTiming) {
3710
+ console.warn(
3711
+ `[StormcloudVideoPlayer] Failed to fetch/parse VAST XML: ${vastTagUrl}`,
3712
+ error
3713
+ );
3714
+ }
3715
+ return [];
3551
3716
  }
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;
3717
+ }
3718
+ extractMediaUrlsFromVast(xmlText) {
3719
+ var _a;
3720
+ const mediaUrls = [];
3721
+ try {
3722
+ const parser = new DOMParser();
3723
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3724
+ const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
3725
+ for (const mediaFile of Array.from(mediaFileElements)) {
3726
+ const url = (_a = mediaFile.textContent) == null ? void 0 : _a.trim();
3727
+ if (url) {
3728
+ const lowerUrl = url.toLowerCase();
3729
+ 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")) {
3730
+ mediaUrls.push(url);
3731
+ }
3732
+ }
3558
3733
  }
3559
- if (this.preloadingAdUrls.has(url)) {
3560
- continue;
3734
+ if (this.config.debugAdTiming && mediaUrls.length > 0) {
3735
+ console.log(
3736
+ `[StormcloudVideoPlayer] Extracted ${mediaUrls.length} media URLs from VAST:`,
3737
+ mediaUrls
3738
+ );
3561
3739
  }
3740
+ } catch (error) {
3741
+ if (this.config.debugAdTiming) {
3742
+ console.warn(
3743
+ "[StormcloudVideoPlayer] Failed to parse VAST XML:",
3744
+ error
3745
+ );
3746
+ }
3747
+ }
3748
+ return mediaUrls;
3749
+ }
3750
+ async preloadMediaFile(mediaUrl) {
3751
+ if (this.preloadedMediaUrls.has(mediaUrl)) {
3752
+ return;
3753
+ }
3754
+ if (this.preloadingMediaUrls.has(mediaUrl)) {
3755
+ return;
3756
+ }
3757
+ this.preloadingMediaUrls.add(mediaUrl);
3758
+ try {
3562
3759
  if (this.config.debugAdTiming) {
3563
3760
  console.log(
3564
- `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3761
+ `[StormcloudVideoPlayer] Preloading video file: ${mediaUrl}`
3565
3762
  );
3566
3763
  }
3567
- this.preloadingAdUrls.add(url);
3568
- this.ima.preloadAds(url).then(() => {
3764
+ const response = await fetch(mediaUrl, {
3765
+ mode: "cors",
3766
+ method: "GET",
3767
+ headers: {
3768
+ Range: "bytes=0-1048576"
3769
+ }
3770
+ });
3771
+ if (response.ok || response.status === 206) {
3772
+ this.preloadedMediaUrls.add(mediaUrl);
3569
3773
  if (this.config.debugAdTiming) {
3570
3774
  console.log(
3571
- `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3775
+ `[StormcloudVideoPlayer] Successfully preloaded video file: ${mediaUrl}`
3572
3776
  );
3573
3777
  }
3574
- }).catch((error) => {
3778
+ }
3779
+ } catch (error) {
3780
+ if (this.config.debugAdTiming) {
3781
+ console.warn(
3782
+ `[StormcloudVideoPlayer] Failed to preload video file: ${mediaUrl}`,
3783
+ error
3784
+ );
3785
+ }
3786
+ } finally {
3787
+ this.preloadingMediaUrls.delete(mediaUrl);
3788
+ }
3789
+ }
3790
+ async preloadAllAdsInBackground() {
3791
+ if (this.adPodAllUrls.length === 0) {
3792
+ return;
3793
+ }
3794
+ if (this.config.debugAdTiming) {
3795
+ console.log(
3796
+ `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3797
+ );
3798
+ }
3799
+ const preloadPromises = this.adPodAllUrls.map(
3800
+ (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3575
3801
  if (this.config.debugAdTiming) {
3576
3802
  console.warn(
3577
- `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3803
+ `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3578
3804
  error
3579
3805
  );
3580
3806
  }
3581
- }).finally(() => {
3582
- this.preloadingAdUrls.delete(url);
3583
- });
3807
+ })
3808
+ );
3809
+ await Promise.all(preloadPromises);
3810
+ if (this.config.debugAdTiming) {
3811
+ console.log(
3812
+ `[StormcloudVideoPlayer] Background preloading completed for all ads`
3813
+ );
3584
3814
  }
3585
3815
  }
3816
+ async preloadSingleAd(vastTagUrl) {
3817
+ if (!vastTagUrl) return;
3818
+ try {
3819
+ if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3820
+ if (!this.preloadingAdUrls.has(vastTagUrl)) {
3821
+ if (this.config.debugAdTiming) {
3822
+ console.log(
3823
+ `[StormcloudVideoPlayer] Preloading VAST: ${vastTagUrl}`
3824
+ );
3825
+ }
3826
+ this.preloadingAdUrls.add(vastTagUrl);
3827
+ await this.ima.preloadAds(vastTagUrl).then(() => {
3828
+ if (this.config.debugAdTiming) {
3829
+ console.log(
3830
+ `[StormcloudVideoPlayer] IMA VAST preload complete: ${vastTagUrl}`
3831
+ );
3832
+ }
3833
+ }).catch((error) => {
3834
+ if (this.config.debugAdTiming) {
3835
+ console.warn(
3836
+ `[StormcloudVideoPlayer] IMA VAST preload failed: ${vastTagUrl}`,
3837
+ error
3838
+ );
3839
+ }
3840
+ }).finally(() => {
3841
+ this.preloadingAdUrls.delete(vastTagUrl);
3842
+ });
3843
+ }
3844
+ }
3845
+ let mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3846
+ if (!mediaUrls) {
3847
+ if (this.config.debugAdTiming) {
3848
+ console.log(
3849
+ `[StormcloudVideoPlayer] Fetching and parsing VAST to extract media URLs: ${vastTagUrl}`
3850
+ );
3851
+ }
3852
+ mediaUrls = await this.fetchAndParseVastXml(vastTagUrl);
3853
+ if (mediaUrls.length > 0) {
3854
+ this.vastToMediaUrlMap.set(vastTagUrl, mediaUrls);
3855
+ }
3856
+ }
3857
+ if (mediaUrls && mediaUrls.length > 0) {
3858
+ const primaryMediaUrl = mediaUrls[0];
3859
+ if (primaryMediaUrl && !this.preloadedMediaUrls.has(primaryMediaUrl)) {
3860
+ await this.preloadMediaFile(primaryMediaUrl);
3861
+ }
3862
+ }
3863
+ } catch (error) {
3864
+ if (this.config.debugAdTiming) {
3865
+ console.warn(
3866
+ `[StormcloudVideoPlayer] Failed to preload ad: ${vastTagUrl}`,
3867
+ error
3868
+ );
3869
+ }
3870
+ }
3871
+ }
3872
+ findNextPreloadedAd() {
3873
+ var _a, _b, _c;
3874
+ for (let i = 0; i < this.adPodQueue.length; i++) {
3875
+ const vastTagUrl = this.adPodQueue[i];
3876
+ if (!vastTagUrl) continue;
3877
+ const hasImaPreload = (_c = (_b = (_a = this.ima).hasPreloadedAd) == null ? void 0 : _b.call(_a, vastTagUrl)) != null ? _c : false;
3878
+ const mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3879
+ const hasMediaPreload = mediaUrls && mediaUrls.length > 0 ? this.preloadedMediaUrls.has(mediaUrls[0]) : false;
3880
+ if (hasImaPreload || hasMediaPreload) {
3881
+ if (this.config.debugAdTiming) {
3882
+ console.log(
3883
+ `[StormcloudVideoPlayer] Found preloaded ad at index ${i}: ${vastTagUrl}`,
3884
+ { hasImaPreload, hasMediaPreload }
3885
+ );
3886
+ }
3887
+ this.adPodQueue.splice(0, i + 1);
3888
+ return vastTagUrl;
3889
+ }
3890
+ }
3891
+ if (this.config.debugAdTiming) {
3892
+ console.log(
3893
+ "[StormcloudVideoPlayer] No preloaded ads found in queue"
3894
+ );
3895
+ }
3896
+ return void 0;
3897
+ }
3586
3898
  getRemainingAdMs() {
3587
3899
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3588
3900
  return 0;
@@ -3751,6 +4063,9 @@ var StormcloudVideoPlayer = class {
3751
4063
  (_b = this.ima) == null ? void 0 : _b.destroy();
3752
4064
  this.releaseAdHoldState();
3753
4065
  this.preloadingAdUrls.clear();
4066
+ this.vastToMediaUrlMap.clear();
4067
+ this.preloadedMediaUrls.clear();
4068
+ this.preloadingMediaUrls.clear();
3754
4069
  this.adPodAllUrls = [];
3755
4070
  }
3756
4071
  };