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