stormcloud-video-player 0.2.23 → 0.2.25

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.
@@ -1,4 +1,4 @@
1
- import { S as StormcloudVideoPlayerConfig } from '../types-D1xfSdLP.cjs';
1
+ import { S as StormcloudVideoPlayerConfig } from '../types-9_2sbHCg.cjs';
2
2
 
3
3
  declare class StormcloudVideoPlayer {
4
4
  private readonly video;
@@ -26,6 +26,8 @@ declare class StormcloudVideoPlayer {
26
26
  private bufferedSegmentsCount;
27
27
  private shouldAutoplayAfterBuffering;
28
28
  private hasInitialBufferCompleted;
29
+ private adPodAllUrls;
30
+ private preloadingAdUrls;
29
31
  constructor(config: StormcloudVideoPlayerConfig);
30
32
  private createAdPlayer;
31
33
  load(): Promise<void>;
@@ -52,6 +54,7 @@ declare class StormcloudVideoPlayer {
52
54
  shouldShowNativeControls(): boolean;
53
55
  private shouldContinueLiveStreamDuringAds;
54
56
  private handleAdStart;
57
+ private playAdPod;
55
58
  private findCurrentOrNextBreak;
56
59
  private onTimeUpdate;
57
60
  private handleMidAdJoin;
@@ -67,6 +70,10 @@ declare class StormcloudVideoPlayer {
67
70
  private startAdFailsafeTimer;
68
71
  private clearAdFailsafeTimer;
69
72
  private selectVastTagsForBreak;
73
+ private logQueuedAdUrls;
74
+ private enforceAdHoldState;
75
+ private releaseAdHoldState;
76
+ private preloadUpcomingAds;
70
77
  private getRemainingAdMs;
71
78
  private findBreakForTime;
72
79
  toggleMute(): void;
@@ -203,6 +203,8 @@ function createImaController(video, options) {
203
203
  let adPlaying = false;
204
204
  let originalMutedState = false;
205
205
  const listeners = /* @__PURE__ */ new Map();
206
+ const preloadedVast = /* @__PURE__ */ new Map();
207
+ const preloadingVast = /* @__PURE__ */ new Map();
206
208
  function setAdPlayingFlag(isPlaying) {
207
209
  if (isPlaying) {
208
210
  video.dataset.stormcloudAdPlaying = "true";
@@ -293,7 +295,15 @@ function createImaController(video, options) {
293
295
  let adsLoadedReject;
294
296
  function makeAdsRequest(google, vastTagUrl) {
295
297
  const adsRequest = new google.ima.AdsRequest();
296
- adsRequest.adTagUrl = vastTagUrl;
298
+ const preloadedResponse = preloadedVast.get(vastTagUrl);
299
+ if (preloadedResponse) {
300
+ adsRequest.adsResponse = preloadedResponse;
301
+ console.log(
302
+ "[IMA] Using preloaded VAST response for immediate ad request"
303
+ );
304
+ } else {
305
+ adsRequest.adTagUrl = vastTagUrl;
306
+ }
297
307
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
298
308
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
299
309
  adsRequest.linearAdSlotWidth = videoWidth;
@@ -303,6 +313,36 @@ function createImaController(video, options) {
303
313
  adsRequest.vastLoadTimeout = 5e3;
304
314
  console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
305
315
  adsLoader.requestAds(adsRequest);
316
+ if (preloadedResponse) {
317
+ preloadedVast.delete(vastTagUrl);
318
+ }
319
+ }
320
+ function ensurePlaceholderContainer() {
321
+ var _a;
322
+ if (adContainerEl) {
323
+ return;
324
+ }
325
+ const container = document.createElement("div");
326
+ container.style.position = "absolute";
327
+ container.style.left = "0";
328
+ container.style.top = "0";
329
+ container.style.right = "0";
330
+ container.style.bottom = "0";
331
+ container.style.display = "none";
332
+ container.style.alignItems = "center";
333
+ container.style.justifyContent = "center";
334
+ container.style.pointerEvents = "none";
335
+ container.style.zIndex = "10";
336
+ container.style.backgroundColor = "#000";
337
+ (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
338
+ adContainerEl = container;
339
+ }
340
+ async function fetchVastDocument(vastTagUrl) {
341
+ const response = await fetch(vastTagUrl, { mode: "cors" });
342
+ if (!response.ok) {
343
+ throw new Error(`Failed to preload VAST: ${response.status}`);
344
+ }
345
+ return response.text();
306
346
  }
307
347
  function destroyAdsManager() {
308
348
  if (adsManager) {
@@ -318,29 +358,16 @@ function createImaController(video, options) {
318
358
  return {
319
359
  initialize() {
320
360
  ensureImaLoaded().then(() => {
321
- var _a, _b;
361
+ var _a;
322
362
  const google = window.google;
323
- if (!adDisplayContainer) {
324
- const container = document.createElement("div");
325
- container.style.position = "absolute";
326
- container.style.left = "0";
327
- container.style.top = "0";
328
- container.style.right = "0";
329
- container.style.bottom = "0";
330
- container.style.display = "none";
331
- container.style.alignItems = "center";
332
- container.style.justifyContent = "center";
333
- container.style.pointerEvents = "none";
334
- container.style.zIndex = "10";
335
- container.style.backgroundColor = "#000";
336
- (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
337
- adContainerEl = container;
363
+ ensurePlaceholderContainer();
364
+ if (!adDisplayContainer && adContainerEl) {
338
365
  adDisplayContainer = new google.ima.AdDisplayContainer(
339
- container,
366
+ adContainerEl,
340
367
  video
341
368
  );
342
369
  try {
343
- (_b = adDisplayContainer.initialize) == null ? void 0 : _b.call(adDisplayContainer);
370
+ (_a = adDisplayContainer.initialize) == null ? void 0 : _a.call(adDisplayContainer);
344
371
  } catch {
345
372
  }
346
373
  }
@@ -442,9 +469,13 @@ function createImaController(video, options) {
442
469
  adsLoader.addEventListener(
443
470
  google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
444
471
  (evt) => {
445
- console.log("[IMA] Ads manager loaded");
472
+ console.log(
473
+ "[IMA] Ads manager loaded - enabling preloading for continuous playback"
474
+ );
446
475
  try {
447
- adsManager = evt.getAdsManager(video);
476
+ const adsRenderingSettings = new google.ima.AdsRenderingSettings();
477
+ adsRenderingSettings.enablePreloading = true;
478
+ adsManager = evt.getAdsManager(video, adsRenderingSettings);
448
479
  const AdEvent = google.ima.AdEvent.Type;
449
480
  const AdErrorEvent = google.ima.AdErrorEvent.Type;
450
481
  adsManager.addEventListener(
@@ -640,6 +671,32 @@ function createImaController(video, options) {
640
671
  return Promise.reject(error);
641
672
  }
642
673
  },
674
+ async preloadAds(vastTagUrl) {
675
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
676
+ return Promise.resolve();
677
+ }
678
+ if (preloadedVast.has(vastTagUrl)) {
679
+ return Promise.resolve();
680
+ }
681
+ const inflight = preloadingVast.get(vastTagUrl);
682
+ if (inflight) {
683
+ return inflight;
684
+ }
685
+ const preloadPromise = fetchVastDocument(vastTagUrl).then((xml) => {
686
+ preloadedVast.set(vastTagUrl, xml);
687
+ console.log("[IMA] Cached VAST response for preloading:", vastTagUrl);
688
+ }).catch((error) => {
689
+ console.warn("[IMA] Failed to preload VAST response:", error);
690
+ preloadedVast.delete(vastTagUrl);
691
+ }).finally(() => {
692
+ preloadingVast.delete(vastTagUrl);
693
+ });
694
+ preloadingVast.set(vastTagUrl, preloadPromise);
695
+ return preloadPromise;
696
+ },
697
+ hasPreloadedAd(vastTagUrl) {
698
+ return preloadedVast.has(vastTagUrl);
699
+ },
643
700
  async play() {
644
701
  var _a, _b;
645
702
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
@@ -715,6 +772,8 @@ function createImaController(video, options) {
715
772
  adContainerEl = void 0;
716
773
  adDisplayContainer = void 0;
717
774
  adsLoader = void 0;
775
+ preloadedVast.clear();
776
+ preloadingVast.clear();
718
777
  },
719
778
  isAdPlaying() {
720
779
  return adPlaying;
@@ -770,6 +829,19 @@ function createImaController(video, options) {
770
829
  }
771
830
  }
772
831
  return 1;
832
+ },
833
+ showPlaceholder() {
834
+ ensurePlaceholderContainer();
835
+ if (adContainerEl) {
836
+ adContainerEl.style.display = "flex";
837
+ adContainerEl.style.pointerEvents = "auto";
838
+ }
839
+ },
840
+ hidePlaceholder() {
841
+ if (adContainerEl) {
842
+ adContainerEl.style.display = "none";
843
+ adContainerEl.style.pointerEvents = "none";
844
+ }
773
845
  }
774
846
  };
775
847
  }
@@ -787,6 +859,8 @@ function createHlsAdPlayer(contentVideo, options) {
787
859
  let adContainerEl;
788
860
  let currentAd;
789
861
  let sessionId;
862
+ const preloadedAds = /* @__PURE__ */ new Map();
863
+ const preloadingAds = /* @__PURE__ */ new Map();
790
864
  let trackingFired = {
791
865
  impression: false,
792
866
  start: false,
@@ -1016,6 +1090,19 @@ function createHlsAdPlayer(contentVideo, options) {
1016
1090
  return null;
1017
1091
  }
1018
1092
  }
1093
+ async function fetchAndParseVastAd(vastTagUrl) {
1094
+ const response = await fetch(vastTagUrl);
1095
+ if (!response.ok) {
1096
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1097
+ }
1098
+ const vastXml = await response.text();
1099
+ console.log("[HlsAdPlayer] VAST XML received");
1100
+ console.log(
1101
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1102
+ vastXml.substring(0, 2e3)
1103
+ );
1104
+ return parseVastXml(vastXml);
1105
+ }
1019
1106
  function createAdVideoElement() {
1020
1107
  const video = document.createElement("video");
1021
1108
  video.style.position = "absolute";
@@ -1167,17 +1254,17 @@ function createHlsAdPlayer(contentVideo, options) {
1167
1254
  }
1168
1255
  try {
1169
1256
  sessionId = generateSessionId();
1170
- const response = await fetch(vastTagUrl);
1171
- if (!response.ok) {
1172
- throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1257
+ let ad;
1258
+ if (preloadedAds.has(vastTagUrl)) {
1259
+ ad = preloadedAds.get(vastTagUrl);
1260
+ preloadedAds.delete(vastTagUrl);
1261
+ console.log(
1262
+ "[HlsAdPlayer] Using preloaded VAST response:",
1263
+ vastTagUrl
1264
+ );
1265
+ } else {
1266
+ ad = await fetchAndParseVastAd(vastTagUrl);
1173
1267
  }
1174
- const vastXml = await response.text();
1175
- console.log("[HlsAdPlayer] VAST XML received");
1176
- console.log(
1177
- "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1178
- vastXml.substring(0, 2e3)
1179
- );
1180
- const ad = parseVastXml(vastXml);
1181
1268
  if (!ad) {
1182
1269
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1183
1270
  emit("ad_error");
@@ -1196,6 +1283,37 @@ function createHlsAdPlayer(contentVideo, options) {
1196
1283
  return Promise.reject(error);
1197
1284
  }
1198
1285
  },
1286
+ async preloadAds(vastTagUrl) {
1287
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
1288
+ return Promise.resolve();
1289
+ }
1290
+ if (preloadedAds.has(vastTagUrl)) {
1291
+ return Promise.resolve();
1292
+ }
1293
+ const inflight = preloadingAds.get(vastTagUrl);
1294
+ if (inflight) {
1295
+ return inflight;
1296
+ }
1297
+ const preloadPromise = fetchAndParseVastAd(vastTagUrl).then((ad) => {
1298
+ if (ad) {
1299
+ preloadedAds.set(vastTagUrl, ad);
1300
+ console.log(
1301
+ "[HlsAdPlayer] Cached VAST response for preloading:",
1302
+ vastTagUrl
1303
+ );
1304
+ }
1305
+ }).catch((error) => {
1306
+ console.warn("[HlsAdPlayer] Failed to preload VAST response:", error);
1307
+ preloadedAds.delete(vastTagUrl);
1308
+ }).finally(() => {
1309
+ preloadingAds.delete(vastTagUrl);
1310
+ });
1311
+ preloadingAds.set(vastTagUrl, preloadPromise);
1312
+ return preloadPromise;
1313
+ },
1314
+ hasPreloadedAd(vastTagUrl) {
1315
+ return preloadedAds.has(vastTagUrl);
1316
+ },
1199
1317
  async play() {
1200
1318
  if (!currentAd) {
1201
1319
  console.warn(
@@ -1326,6 +1444,8 @@ function createHlsAdPlayer(contentVideo, options) {
1326
1444
  adContainerEl = void 0;
1327
1445
  currentAd = void 0;
1328
1446
  listeners.clear();
1447
+ preloadedAds.clear();
1448
+ preloadingAds.clear();
1329
1449
  },
1330
1450
  isAdPlaying() {
1331
1451
  return adPlaying;
@@ -1368,6 +1488,35 @@ function createHlsAdPlayer(contentVideo, options) {
1368
1488
  return adVideoElement.volume;
1369
1489
  }
1370
1490
  return 1;
1491
+ },
1492
+ showPlaceholder() {
1493
+ var _a;
1494
+ if (!adContainerEl) {
1495
+ const container = document.createElement("div");
1496
+ container.style.position = "absolute";
1497
+ container.style.left = "0";
1498
+ container.style.top = "0";
1499
+ container.style.right = "0";
1500
+ container.style.bottom = "0";
1501
+ container.style.display = "none";
1502
+ container.style.alignItems = "center";
1503
+ container.style.justifyContent = "center";
1504
+ container.style.pointerEvents = "none";
1505
+ container.style.zIndex = "10";
1506
+ container.style.backgroundColor = "#000";
1507
+ (_a = contentVideo.parentElement) == null ? void 0 : _a.appendChild(container);
1508
+ adContainerEl = container;
1509
+ }
1510
+ if (adContainerEl) {
1511
+ adContainerEl.style.display = "flex";
1512
+ adContainerEl.style.pointerEvents = "auto";
1513
+ }
1514
+ },
1515
+ hidePlaceholder() {
1516
+ if (adContainerEl) {
1517
+ adContainerEl.style.display = "none";
1518
+ adContainerEl.style.pointerEvents = "none";
1519
+ }
1371
1520
  }
1372
1521
  };
1373
1522
  }
@@ -1848,6 +1997,8 @@ var StormcloudVideoPlayer = class {
1848
1997
  this.bufferedSegmentsCount = 0;
1849
1998
  this.shouldAutoplayAfterBuffering = false;
1850
1999
  this.hasInitialBufferCompleted = false;
2000
+ this.adPodAllUrls = [];
2001
+ this.preloadingAdUrls = /* @__PURE__ */ new Set();
1851
2002
  initializePolyfills();
1852
2003
  const browserOverrides = getBrowserConfigOverrides();
1853
2004
  this.config = { ...config, ...browserOverrides };
@@ -2148,6 +2299,7 @@ var StormcloudVideoPlayer = class {
2148
2299
  console.log("[StormcloudVideoPlayer] IMA content_pause event received");
2149
2300
  }
2150
2301
  this.clearAdFailsafeTimer();
2302
+ this.enforceAdHoldState();
2151
2303
  });
2152
2304
  this.ima.on("content_resume", () => {
2153
2305
  if (this.config.debugAdTiming) {
@@ -2172,12 +2324,10 @@ var StormcloudVideoPlayer = class {
2172
2324
  if (remaining > 500 && this.adPodQueue.length > 0) {
2173
2325
  const next = this.adPodQueue.shift();
2174
2326
  this.currentAdIndex++;
2175
- this.video.dataset.stormcloudAdPlaying = "true";
2176
- this.video.muted = true;
2177
- this.video.volume = 0;
2327
+ this.enforceAdHoldState();
2178
2328
  if (this.config.debugAdTiming) {
2179
2329
  console.log(
2180
- `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - main video stays muted, ad layer stays visible`
2330
+ `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
2181
2331
  );
2182
2332
  }
2183
2333
  this.playSingleAd(next).catch(() => {
@@ -2762,25 +2912,21 @@ var StormcloudVideoPlayer = class {
2762
2912
  this.video.currentTime * 1e3
2763
2913
  );
2764
2914
  const tags = this.selectVastTagsForBreak(scheduled);
2765
- let vastTagUrl;
2915
+ let vastTagUrls = [];
2766
2916
  if (this.apiVastTagUrl) {
2767
- vastTagUrl = this.apiVastTagUrl;
2768
- this.adPodQueue = [];
2769
- this.currentAdIndex = 0;
2770
- this.totalAdsInBreak = 1;
2917
+ vastTagUrls = [this.apiVastTagUrl];
2771
2918
  if (this.config.debugAdTiming) {
2772
- console.log("[StormcloudVideoPlayer] Using VAST endpoint:", vastTagUrl);
2919
+ console.log(
2920
+ "[StormcloudVideoPlayer] Using VAST endpoint:",
2921
+ this.apiVastTagUrl
2922
+ );
2773
2923
  }
2774
2924
  } else if (tags && tags.length > 0) {
2775
- vastTagUrl = tags[0];
2776
- const rest = tags.slice(1);
2777
- this.adPodQueue = rest;
2778
- this.currentAdIndex = 0;
2779
- this.totalAdsInBreak = tags.length;
2925
+ vastTagUrls = tags;
2780
2926
  if (this.config.debugAdTiming) {
2781
2927
  console.log(
2782
- "[StormcloudVideoPlayer] Using scheduled VAST tag:",
2783
- vastTagUrl
2928
+ "[StormcloudVideoPlayer] Using scheduled VAST tags (count: " + tags.length + "):",
2929
+ tags
2784
2930
  );
2785
2931
  }
2786
2932
  } else {
@@ -2789,16 +2935,28 @@ var StormcloudVideoPlayer = class {
2789
2935
  }
2790
2936
  return;
2791
2937
  }
2792
- if (vastTagUrl) {
2938
+ if (vastTagUrls.length > 0) {
2939
+ this.adPodAllUrls = [...vastTagUrls];
2940
+ this.preloadingAdUrls.clear();
2941
+ this.logQueuedAdUrls(this.adPodAllUrls);
2793
2942
  this.inAdBreak = true;
2794
2943
  this.showAds = true;
2795
- this.currentAdIndex++;
2944
+ this.currentAdIndex = 0;
2945
+ this.totalAdsInBreak = vastTagUrls.length;
2946
+ this.adPodQueue = [...vastTagUrls];
2947
+ this.enforceAdHoldState();
2948
+ this.preloadUpcomingAds();
2949
+ if (this.config.debugAdTiming) {
2950
+ console.log(
2951
+ `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
2952
+ );
2953
+ }
2796
2954
  try {
2797
- await this.playSingleAd(vastTagUrl);
2955
+ await this.playAdPod();
2798
2956
  } catch (error) {
2799
2957
  if (this.config.debugAdTiming) {
2800
2958
  console.error(
2801
- "[StormcloudVideoPlayer] Ad playback failed in handleAdStart:",
2959
+ "[StormcloudVideoPlayer] Ad pod playback failed:",
2802
2960
  error
2803
2961
  );
2804
2962
  }
@@ -2811,6 +2969,22 @@ var StormcloudVideoPlayer = class {
2811
2969
  this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
2812
2970
  }
2813
2971
  }
2972
+ async playAdPod() {
2973
+ if (this.adPodQueue.length === 0) {
2974
+ if (this.config.debugAdTiming) {
2975
+ console.log("[StormcloudVideoPlayer] No ads in pod to play");
2976
+ }
2977
+ return;
2978
+ }
2979
+ const firstAd = this.adPodQueue.shift();
2980
+ this.currentAdIndex++;
2981
+ if (this.config.debugAdTiming) {
2982
+ console.log(
2983
+ `[StormcloudVideoPlayer] Playing ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
2984
+ );
2985
+ }
2986
+ await this.playSingleAd(firstAd);
2987
+ }
2814
2988
  findCurrentOrNextBreak(nowMs) {
2815
2989
  var _a;
2816
2990
  const schedule = [];
@@ -2842,6 +3016,7 @@ var StormcloudVideoPlayer = class {
2842
3016
  const first = tags[0];
2843
3017
  const rest = tags.slice(1);
2844
3018
  this.adPodQueue = rest;
3019
+ this.enforceAdHoldState();
2845
3020
  await this.playSingleAd(first);
2846
3021
  this.inAdBreak = true;
2847
3022
  this.expectedAdBreakDurationMs = remainingMs;
@@ -2955,6 +3130,12 @@ var StormcloudVideoPlayer = class {
2955
3130
  }
2956
3131
  return;
2957
3132
  }
3133
+ const wasPreloaded = this.ima.hasPreloadedAd(vastTagUrl);
3134
+ if (wasPreloaded && this.config.debugAdTiming) {
3135
+ console.log(
3136
+ `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3137
+ );
3138
+ }
2958
3139
  if (!this.showAds) {
2959
3140
  if (this.config.debugAdTiming) {
2960
3141
  console.log(
@@ -2975,12 +3156,14 @@ var StormcloudVideoPlayer = class {
2975
3156
  this.startAdFailsafeTimer();
2976
3157
  try {
2977
3158
  await this.ima.requestAds(vastTagUrl);
3159
+ this.preloadUpcomingAds();
2978
3160
  try {
2979
3161
  if (this.config.debugAdTiming) {
2980
3162
  console.log(
2981
3163
  "[StormcloudVideoPlayer] Ad request completed, attempting playback"
2982
3164
  );
2983
3165
  }
3166
+ this.enforceAdHoldState();
2984
3167
  await this.ima.play();
2985
3168
  if (this.config.debugAdTiming) {
2986
3169
  console.log(
@@ -3010,6 +3193,8 @@ var StormcloudVideoPlayer = class {
3010
3193
  "[StormcloudVideoPlayer] Handling ad pod completion - resuming content and hiding ad layer"
3011
3194
  );
3012
3195
  }
3196
+ this.releaseAdHoldState();
3197
+ this.preloadingAdUrls.clear();
3013
3198
  this.inAdBreak = false;
3014
3199
  this.expectedAdBreakDurationMs = void 0;
3015
3200
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3017,6 +3202,7 @@ var StormcloudVideoPlayer = class {
3017
3202
  this.clearAdStopTimer();
3018
3203
  this.clearAdFailsafeTimer();
3019
3204
  this.adPodQueue = [];
3205
+ this.adPodAllUrls = [];
3020
3206
  this.showAds = false;
3021
3207
  this.currentAdIndex = 0;
3022
3208
  this.totalAdsInBreak = 0;
@@ -3097,6 +3283,64 @@ var StormcloudVideoPlayer = class {
3097
3283
  }
3098
3284
  return [b.vastTagUrl];
3099
3285
  }
3286
+ logQueuedAdUrls(urls) {
3287
+ if (!this.config.debugAdTiming) {
3288
+ return;
3289
+ }
3290
+ console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3291
+ }
3292
+ enforceAdHoldState() {
3293
+ this.video.dataset.stormcloudAdPlaying = "true";
3294
+ this.video.muted = true;
3295
+ this.video.volume = 0;
3296
+ if (typeof this.ima.showPlaceholder === "function") {
3297
+ this.ima.showPlaceholder();
3298
+ }
3299
+ }
3300
+ releaseAdHoldState() {
3301
+ delete this.video.dataset.stormcloudAdPlaying;
3302
+ if (typeof this.ima.hidePlaceholder === "function") {
3303
+ this.ima.hidePlaceholder();
3304
+ }
3305
+ }
3306
+ preloadUpcomingAds() {
3307
+ if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3308
+ return;
3309
+ }
3310
+ const upcoming = this.adPodQueue.slice(0, 2);
3311
+ for (const url of upcoming) {
3312
+ if (!url) continue;
3313
+ if (this.ima.hasPreloadedAd(url)) {
3314
+ this.preloadingAdUrls.delete(url);
3315
+ continue;
3316
+ }
3317
+ if (this.preloadingAdUrls.has(url)) {
3318
+ continue;
3319
+ }
3320
+ if (this.config.debugAdTiming) {
3321
+ console.log(
3322
+ `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3323
+ );
3324
+ }
3325
+ this.preloadingAdUrls.add(url);
3326
+ this.ima.preloadAds(url).then(() => {
3327
+ if (this.config.debugAdTiming) {
3328
+ console.log(
3329
+ `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3330
+ );
3331
+ }
3332
+ }).catch((error) => {
3333
+ if (this.config.debugAdTiming) {
3334
+ console.warn(
3335
+ `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3336
+ error
3337
+ );
3338
+ }
3339
+ }).finally(() => {
3340
+ this.preloadingAdUrls.delete(url);
3341
+ });
3342
+ }
3343
+ }
3100
3344
  getRemainingAdMs() {
3101
3345
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3102
3346
  return 0;
@@ -3248,6 +3492,9 @@ var StormcloudVideoPlayer = class {
3248
3492
  }
3249
3493
  (_a = this.hls) == null ? void 0 : _a.destroy();
3250
3494
  (_b = this.ima) == null ? void 0 : _b.destroy();
3495
+ this.releaseAdHoldState();
3496
+ this.preloadingAdUrls.clear();
3497
+ this.adPodAllUrls = [];
3251
3498
  }
3252
3499
  };
3253
3500