stormcloud-video-player 0.2.24 → 0.2.26

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.
@@ -199,7 +199,10 @@ function getBrowserConfigOverrides() {
199
199
  function createImaController(video, options) {
200
200
  let adPlaying = false;
201
201
  let originalMutedState = false;
202
+ let originalVolume = typeof video.volume === "number" && !Number.isNaN(video.volume) ? Math.max(0, Math.min(1, video.volume)) : 1;
202
203
  const listeners = /* @__PURE__ */ new Map();
204
+ const preloadedVast = /* @__PURE__ */ new Map();
205
+ const preloadingVast = /* @__PURE__ */ new Map();
203
206
  function setAdPlayingFlag(isPlaying) {
204
207
  if (isPlaying) {
205
208
  video.dataset.stormcloudAdPlaying = "true";
@@ -290,16 +293,70 @@ function createImaController(video, options) {
290
293
  let adsLoadedReject;
291
294
  function makeAdsRequest(google, vastTagUrl) {
292
295
  const adsRequest = new google.ima.AdsRequest();
293
- adsRequest.adTagUrl = vastTagUrl;
296
+ const preloadedResponse = preloadedVast.get(vastTagUrl);
297
+ if (preloadedResponse) {
298
+ adsRequest.adsResponse = preloadedResponse;
299
+ console.log(
300
+ "[IMA] Using preloaded VAST response for immediate ad request"
301
+ );
302
+ } else {
303
+ adsRequest.adTagUrl = vastTagUrl;
304
+ }
294
305
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
295
306
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
296
307
  adsRequest.linearAdSlotWidth = videoWidth;
297
308
  adsRequest.linearAdSlotHeight = videoHeight;
298
309
  adsRequest.nonLinearAdSlotWidth = videoWidth;
299
310
  adsRequest.nonLinearAdSlotHeight = videoHeight;
311
+ if (typeof adsRequest.setAdWillAutoPlay === "function") {
312
+ try {
313
+ const willAutoPlay = !video.paused || video.autoplay;
314
+ adsRequest.setAdWillAutoPlay(willAutoPlay);
315
+ } catch (error) {
316
+ console.warn("[IMA] Failed to call setAdWillAutoPlay:", error);
317
+ }
318
+ }
319
+ if (typeof adsRequest.setAdWillPlayMuted === "function") {
320
+ try {
321
+ const willPlayMuted = video.muted || video.volume === 0;
322
+ adsRequest.setAdWillPlayMuted(willPlayMuted);
323
+ } catch (error) {
324
+ console.warn("[IMA] Failed to call setAdWillPlayMuted:", error);
325
+ }
326
+ }
300
327
  adsRequest.vastLoadTimeout = 5e3;
301
328
  console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
302
329
  adsLoader.requestAds(adsRequest);
330
+ if (preloadedResponse) {
331
+ preloadedVast.delete(vastTagUrl);
332
+ }
333
+ }
334
+ function ensurePlaceholderContainer() {
335
+ var _a;
336
+ if (adContainerEl) {
337
+ return;
338
+ }
339
+ const container = document.createElement("div");
340
+ container.style.position = "absolute";
341
+ container.style.left = "0";
342
+ container.style.top = "0";
343
+ container.style.right = "0";
344
+ container.style.bottom = "0";
345
+ container.style.display = "none";
346
+ container.style.alignItems = "center";
347
+ container.style.justifyContent = "center";
348
+ container.style.pointerEvents = "none";
349
+ container.style.zIndex = "10";
350
+ container.style.backgroundColor = "#000";
351
+ (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
352
+ adContainerEl = container;
353
+ }
354
+ async function fetchVastDocument(vastTagUrl) {
355
+ const response = await fetch(vastTagUrl, { mode: "cors" });
356
+ if (!response.ok) {
357
+ throw new Error(`Failed to preload VAST: ${response.status}`);
358
+ }
359
+ return response.text();
303
360
  }
304
361
  function destroyAdsManager() {
305
362
  if (adsManager) {
@@ -315,29 +372,16 @@ function createImaController(video, options) {
315
372
  return {
316
373
  initialize() {
317
374
  ensureImaLoaded().then(() => {
318
- var _a, _b;
375
+ var _a;
319
376
  const google = window.google;
320
- if (!adDisplayContainer) {
321
- const container = document.createElement("div");
322
- container.style.position = "absolute";
323
- container.style.left = "0";
324
- container.style.top = "0";
325
- container.style.right = "0";
326
- container.style.bottom = "0";
327
- container.style.display = "none";
328
- container.style.alignItems = "center";
329
- container.style.justifyContent = "center";
330
- container.style.pointerEvents = "none";
331
- container.style.zIndex = "10";
332
- container.style.backgroundColor = "#000";
333
- (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
334
- adContainerEl = container;
377
+ ensurePlaceholderContainer();
378
+ if (!adDisplayContainer && adContainerEl) {
335
379
  adDisplayContainer = new google.ima.AdDisplayContainer(
336
- container,
380
+ adContainerEl,
337
381
  video
338
382
  );
339
383
  try {
340
- (_b = adDisplayContainer.initialize) == null ? void 0 : _b.call(adDisplayContainer);
384
+ (_a = adDisplayContainer.initialize) == null ? void 0 : _a.call(adDisplayContainer);
341
385
  } catch {
342
386
  }
343
387
  }
@@ -641,6 +685,32 @@ function createImaController(video, options) {
641
685
  return Promise.reject(error);
642
686
  }
643
687
  },
688
+ async preloadAds(vastTagUrl) {
689
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
690
+ return Promise.resolve();
691
+ }
692
+ if (preloadedVast.has(vastTagUrl)) {
693
+ return Promise.resolve();
694
+ }
695
+ const inflight = preloadingVast.get(vastTagUrl);
696
+ if (inflight) {
697
+ return inflight;
698
+ }
699
+ const preloadPromise = fetchVastDocument(vastTagUrl).then((xml) => {
700
+ preloadedVast.set(vastTagUrl, xml);
701
+ console.log("[IMA] Cached VAST response for preloading:", vastTagUrl);
702
+ }).catch((error) => {
703
+ console.warn("[IMA] Failed to preload VAST response:", error);
704
+ preloadedVast.delete(vastTagUrl);
705
+ }).finally(() => {
706
+ preloadingVast.delete(vastTagUrl);
707
+ });
708
+ preloadingVast.set(vastTagUrl, preloadPromise);
709
+ return preloadPromise;
710
+ },
711
+ hasPreloadedAd(vastTagUrl) {
712
+ return preloadedVast.has(vastTagUrl);
713
+ },
644
714
  async play() {
645
715
  var _a, _b;
646
716
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
@@ -659,7 +729,7 @@ function createImaController(video, options) {
659
729
  console.log(`[IMA] Initializing ads manager (${width}x${height})`);
660
730
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
661
731
  adPlaying = true;
662
- const adVolume = originalMutedState ? 0 : video.volume;
732
+ const adVolume = originalMutedState ? 0 : originalVolume;
663
733
  try {
664
734
  adsManager.setVolume(adVolume);
665
735
  console.log(`[IMA] Set ad volume to ${adVolume}`);
@@ -701,6 +771,7 @@ function createImaController(video, options) {
701
771
  destroyAdsManager();
702
772
  adPlaying = false;
703
773
  video.muted = originalMutedState;
774
+ video.volume = originalVolume;
704
775
  setAdPlayingFlag(false);
705
776
  if (adContainerEl) {
706
777
  adContainerEl.style.pointerEvents = "none";
@@ -716,6 +787,8 @@ function createImaController(video, options) {
716
787
  adContainerEl = void 0;
717
788
  adDisplayContainer = void 0;
718
789
  adsLoader = void 0;
790
+ preloadedVast.clear();
791
+ preloadingVast.clear();
719
792
  },
720
793
  isAdPlaying() {
721
794
  return adPlaying;
@@ -743,15 +816,20 @@ function createImaController(video, options) {
743
816
  var _a;
744
817
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
745
818
  },
746
- updateOriginalMutedState(muted) {
819
+ updateOriginalMutedState(muted, volume) {
820
+ const nextVolume = typeof volume === "number" && !Number.isNaN(volume) ? Math.max(0, Math.min(1, volume)) : originalVolume;
747
821
  console.log(
748
- `[IMA] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
822
+ `[IMA] updateOriginalMutedState called: { muted: ${originalMutedState} -> ${muted}, volume: ${originalVolume} -> ${nextVolume} }`
749
823
  );
750
824
  originalMutedState = muted;
825
+ originalVolume = nextVolume;
751
826
  },
752
827
  getOriginalMutedState() {
753
828
  return originalMutedState;
754
829
  },
830
+ getOriginalVolume() {
831
+ return originalVolume;
832
+ },
755
833
  setAdVolume(volume) {
756
834
  if (adsManager && adPlaying) {
757
835
  try {
@@ -771,6 +849,19 @@ function createImaController(video, options) {
771
849
  }
772
850
  }
773
851
  return 1;
852
+ },
853
+ showPlaceholder() {
854
+ ensurePlaceholderContainer();
855
+ if (adContainerEl) {
856
+ adContainerEl.style.display = "flex";
857
+ adContainerEl.style.pointerEvents = "auto";
858
+ }
859
+ },
860
+ hidePlaceholder() {
861
+ if (adContainerEl) {
862
+ adContainerEl.style.display = "none";
863
+ adContainerEl.style.pointerEvents = "none";
864
+ }
774
865
  }
775
866
  };
776
867
  }
@@ -780,6 +871,7 @@ var import_hls = __toESM(require("hls.js"), 1);
780
871
  function createHlsAdPlayer(contentVideo, options) {
781
872
  let adPlaying = false;
782
873
  let originalMutedState = false;
874
+ let originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
783
875
  const listeners = /* @__PURE__ */ new Map();
784
876
  const licenseKey = options == null ? void 0 : options.licenseKey;
785
877
  const mainHlsInstance = options == null ? void 0 : options.mainHlsInstance;
@@ -788,6 +880,8 @@ function createHlsAdPlayer(contentVideo, options) {
788
880
  let adContainerEl;
789
881
  let currentAd;
790
882
  let sessionId;
883
+ const preloadedAds = /* @__PURE__ */ new Map();
884
+ const preloadingAds = /* @__PURE__ */ new Map();
791
885
  let trackingFired = {
792
886
  impression: false,
793
887
  start: false,
@@ -1017,6 +1111,19 @@ function createHlsAdPlayer(contentVideo, options) {
1017
1111
  return null;
1018
1112
  }
1019
1113
  }
1114
+ async function fetchAndParseVastAd(vastTagUrl) {
1115
+ const response = await fetch(vastTagUrl);
1116
+ if (!response.ok) {
1117
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1118
+ }
1119
+ const vastXml = await response.text();
1120
+ console.log("[HlsAdPlayer] VAST XML received");
1121
+ console.log(
1122
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1123
+ vastXml.substring(0, 2e3)
1124
+ );
1125
+ return parseVastXml(vastXml);
1126
+ }
1020
1127
  function createAdVideoElement() {
1021
1128
  const video = document.createElement("video");
1022
1129
  video.style.position = "absolute";
@@ -1122,6 +1229,7 @@ function createHlsAdPlayer(contentVideo, options) {
1122
1229
  setAdPlayingFlag(false);
1123
1230
  const previousMutedState = contentVideo.muted;
1124
1231
  contentVideo.muted = originalMutedState;
1232
+ contentVideo.volume = originalMutedState ? 0 : originalVolume;
1125
1233
  console.log(
1126
1234
  `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1127
1235
  );
@@ -1168,17 +1276,17 @@ function createHlsAdPlayer(contentVideo, options) {
1168
1276
  }
1169
1277
  try {
1170
1278
  sessionId = generateSessionId();
1171
- const response = await fetch(vastTagUrl);
1172
- if (!response.ok) {
1173
- throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1279
+ let ad;
1280
+ if (preloadedAds.has(vastTagUrl)) {
1281
+ ad = preloadedAds.get(vastTagUrl);
1282
+ preloadedAds.delete(vastTagUrl);
1283
+ console.log(
1284
+ "[HlsAdPlayer] Using preloaded VAST response:",
1285
+ vastTagUrl
1286
+ );
1287
+ } else {
1288
+ ad = await fetchAndParseVastAd(vastTagUrl);
1174
1289
  }
1175
- const vastXml = await response.text();
1176
- console.log("[HlsAdPlayer] VAST XML received");
1177
- console.log(
1178
- "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1179
- vastXml.substring(0, 2e3)
1180
- );
1181
- const ad = parseVastXml(vastXml);
1182
1290
  if (!ad) {
1183
1291
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1184
1292
  emit("ad_error");
@@ -1197,6 +1305,37 @@ function createHlsAdPlayer(contentVideo, options) {
1197
1305
  return Promise.reject(error);
1198
1306
  }
1199
1307
  },
1308
+ async preloadAds(vastTagUrl) {
1309
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
1310
+ return Promise.resolve();
1311
+ }
1312
+ if (preloadedAds.has(vastTagUrl)) {
1313
+ return Promise.resolve();
1314
+ }
1315
+ const inflight = preloadingAds.get(vastTagUrl);
1316
+ if (inflight) {
1317
+ return inflight;
1318
+ }
1319
+ const preloadPromise = fetchAndParseVastAd(vastTagUrl).then((ad) => {
1320
+ if (ad) {
1321
+ preloadedAds.set(vastTagUrl, ad);
1322
+ console.log(
1323
+ "[HlsAdPlayer] Cached VAST response for preloading:",
1324
+ vastTagUrl
1325
+ );
1326
+ }
1327
+ }).catch((error) => {
1328
+ console.warn("[HlsAdPlayer] Failed to preload VAST response:", error);
1329
+ preloadedAds.delete(vastTagUrl);
1330
+ }).finally(() => {
1331
+ preloadingAds.delete(vastTagUrl);
1332
+ });
1333
+ preloadingAds.set(vastTagUrl, preloadPromise);
1334
+ return preloadPromise;
1335
+ },
1336
+ hasPreloadedAd(vastTagUrl) {
1337
+ return preloadedAds.has(vastTagUrl);
1338
+ },
1200
1339
  async play() {
1201
1340
  if (!currentAd) {
1202
1341
  console.warn(
@@ -1220,6 +1359,10 @@ function createHlsAdPlayer(contentVideo, options) {
1220
1359
  complete: false
1221
1360
  };
1222
1361
  const contentVolume = contentVideo.volume;
1362
+ originalVolume = Math.max(
1363
+ 0,
1364
+ Math.min(1, contentVolume || originalVolume)
1365
+ );
1223
1366
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
1224
1367
  contentVideo.pause();
1225
1368
  console.log("[HlsAdPlayer] Content paused (VOD mode)");
@@ -1232,7 +1375,7 @@ function createHlsAdPlayer(contentVideo, options) {
1232
1375
  adPlaying = true;
1233
1376
  setAdPlayingFlag(true);
1234
1377
  if (adVideoElement) {
1235
- const adVolume = originalMutedState ? 0 : contentVolume;
1378
+ const adVolume = originalMutedState ? 0 : originalVolume;
1236
1379
  adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1237
1380
  adVideoElement.muted = false;
1238
1381
  console.log(
@@ -1311,6 +1454,7 @@ function createHlsAdPlayer(contentVideo, options) {
1311
1454
  adPlaying = false;
1312
1455
  setAdPlayingFlag(false);
1313
1456
  contentVideo.muted = originalMutedState;
1457
+ contentVideo.volume = originalMutedState ? 0 : originalVolume;
1314
1458
  if (adHls) {
1315
1459
  adHls.destroy();
1316
1460
  adHls = void 0;
@@ -1327,6 +1471,8 @@ function createHlsAdPlayer(contentVideo, options) {
1327
1471
  adContainerEl = void 0;
1328
1472
  currentAd = void 0;
1329
1473
  listeners.clear();
1474
+ preloadedAds.clear();
1475
+ preloadingAds.clear();
1330
1476
  },
1331
1477
  isAdPlaying() {
1332
1478
  return adPlaying;
@@ -1350,15 +1496,20 @@ function createHlsAdPlayer(contentVideo, options) {
1350
1496
  var _a;
1351
1497
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
1352
1498
  },
1353
- updateOriginalMutedState(muted) {
1499
+ updateOriginalMutedState(muted, volume) {
1500
+ const nextVolume = typeof volume === "number" && !Number.isNaN(volume) ? Math.max(0, Math.min(1, volume)) : originalVolume;
1354
1501
  console.log(
1355
- `[HlsAdPlayer] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
1502
+ `[HlsAdPlayer] updateOriginalMutedState called: { muted: ${originalMutedState} -> ${muted}, volume: ${originalVolume} -> ${nextVolume} }`
1356
1503
  );
1357
1504
  originalMutedState = muted;
1505
+ originalVolume = nextVolume;
1358
1506
  },
1359
1507
  getOriginalMutedState() {
1360
1508
  return originalMutedState;
1361
1509
  },
1510
+ getOriginalVolume() {
1511
+ return originalVolume;
1512
+ },
1362
1513
  setAdVolume(volume) {
1363
1514
  if (adVideoElement && adPlaying) {
1364
1515
  adVideoElement.volume = Math.max(0, Math.min(1, volume));
@@ -1369,6 +1520,35 @@ function createHlsAdPlayer(contentVideo, options) {
1369
1520
  return adVideoElement.volume;
1370
1521
  }
1371
1522
  return 1;
1523
+ },
1524
+ showPlaceholder() {
1525
+ var _a;
1526
+ if (!adContainerEl) {
1527
+ const container = document.createElement("div");
1528
+ container.style.position = "absolute";
1529
+ container.style.left = "0";
1530
+ container.style.top = "0";
1531
+ container.style.right = "0";
1532
+ container.style.bottom = "0";
1533
+ container.style.display = "none";
1534
+ container.style.alignItems = "center";
1535
+ container.style.justifyContent = "center";
1536
+ container.style.pointerEvents = "none";
1537
+ container.style.zIndex = "10";
1538
+ container.style.backgroundColor = "#000";
1539
+ (_a = contentVideo.parentElement) == null ? void 0 : _a.appendChild(container);
1540
+ adContainerEl = container;
1541
+ }
1542
+ if (adContainerEl) {
1543
+ adContainerEl.style.display = "flex";
1544
+ adContainerEl.style.pointerEvents = "auto";
1545
+ }
1546
+ },
1547
+ hidePlaceholder() {
1548
+ if (adContainerEl) {
1549
+ adContainerEl.style.display = "none";
1550
+ adContainerEl.style.pointerEvents = "none";
1551
+ }
1372
1552
  }
1373
1553
  };
1374
1554
  }
@@ -1849,6 +2029,8 @@ var StormcloudVideoPlayer = class {
1849
2029
  this.bufferedSegmentsCount = 0;
1850
2030
  this.shouldAutoplayAfterBuffering = false;
1851
2031
  this.hasInitialBufferCompleted = false;
2032
+ this.adPodAllUrls = [];
2033
+ this.preloadingAdUrls = /* @__PURE__ */ new Set();
1852
2034
  initializePolyfills();
1853
2035
  const browserOverrides = getBrowserConfigOverrides();
1854
2036
  this.config = { ...config, ...browserOverrides };
@@ -2108,7 +2290,7 @@ var StormcloudVideoPlayer = class {
2108
2290
  this.video.autoplay = !!this.config.autoplay;
2109
2291
  this.video.muted = !!this.config.muted;
2110
2292
  this.ima.initialize();
2111
- this.ima.updateOriginalMutedState(this.video.muted);
2293
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2112
2294
  this.ima.on("all_ads_completed", () => {
2113
2295
  if (this.config.debugAdTiming) {
2114
2296
  console.log(
@@ -2149,6 +2331,7 @@ var StormcloudVideoPlayer = class {
2149
2331
  console.log("[StormcloudVideoPlayer] IMA content_pause event received");
2150
2332
  }
2151
2333
  this.clearAdFailsafeTimer();
2334
+ this.enforceAdHoldState();
2152
2335
  });
2153
2336
  this.ima.on("content_resume", () => {
2154
2337
  if (this.config.debugAdTiming) {
@@ -2173,9 +2356,7 @@ var StormcloudVideoPlayer = class {
2173
2356
  if (remaining > 500 && this.adPodQueue.length > 0) {
2174
2357
  const next = this.adPodQueue.shift();
2175
2358
  this.currentAdIndex++;
2176
- this.video.dataset.stormcloudAdPlaying = "true";
2177
- this.video.muted = true;
2178
- this.video.volume = 0;
2359
+ this.enforceAdHoldState();
2179
2360
  if (this.config.debugAdTiming) {
2180
2361
  console.log(
2181
2362
  `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
@@ -2787,11 +2968,16 @@ var StormcloudVideoPlayer = class {
2787
2968
  return;
2788
2969
  }
2789
2970
  if (vastTagUrls.length > 0) {
2971
+ this.adPodAllUrls = [...vastTagUrls];
2972
+ this.preloadingAdUrls.clear();
2973
+ this.logQueuedAdUrls(this.adPodAllUrls);
2790
2974
  this.inAdBreak = true;
2791
2975
  this.showAds = true;
2792
2976
  this.currentAdIndex = 0;
2793
2977
  this.totalAdsInBreak = vastTagUrls.length;
2794
2978
  this.adPodQueue = [...vastTagUrls];
2979
+ this.enforceAdHoldState();
2980
+ this.preloadUpcomingAds();
2795
2981
  if (this.config.debugAdTiming) {
2796
2982
  console.log(
2797
2983
  `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
@@ -2862,6 +3048,7 @@ var StormcloudVideoPlayer = class {
2862
3048
  const first = tags[0];
2863
3049
  const rest = tags.slice(1);
2864
3050
  this.adPodQueue = rest;
3051
+ this.enforceAdHoldState();
2865
3052
  await this.playSingleAd(first);
2866
3053
  this.inAdBreak = true;
2867
3054
  this.expectedAdBreakDurationMs = remainingMs;
@@ -2975,6 +3162,12 @@ var StormcloudVideoPlayer = class {
2975
3162
  }
2976
3163
  return;
2977
3164
  }
3165
+ const wasPreloaded = this.ima.hasPreloadedAd(vastTagUrl);
3166
+ if (wasPreloaded && this.config.debugAdTiming) {
3167
+ console.log(
3168
+ `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3169
+ );
3170
+ }
2978
3171
  if (!this.showAds) {
2979
3172
  if (this.config.debugAdTiming) {
2980
3173
  console.log(
@@ -2986,7 +3179,7 @@ var StormcloudVideoPlayer = class {
2986
3179
  }
2987
3180
  );
2988
3181
  }
2989
- this.ima.updateOriginalMutedState(this.video.muted);
3182
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2990
3183
  } else if (this.config.debugAdTiming) {
2991
3184
  console.log(
2992
3185
  `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
@@ -2995,12 +3188,14 @@ var StormcloudVideoPlayer = class {
2995
3188
  this.startAdFailsafeTimer();
2996
3189
  try {
2997
3190
  await this.ima.requestAds(vastTagUrl);
3191
+ this.preloadUpcomingAds();
2998
3192
  try {
2999
3193
  if (this.config.debugAdTiming) {
3000
3194
  console.log(
3001
3195
  "[StormcloudVideoPlayer] Ad request completed, attempting playback"
3002
3196
  );
3003
3197
  }
3198
+ this.enforceAdHoldState();
3004
3199
  await this.ima.play();
3005
3200
  if (this.config.debugAdTiming) {
3006
3201
  console.log(
@@ -3030,6 +3225,8 @@ var StormcloudVideoPlayer = class {
3030
3225
  "[StormcloudVideoPlayer] Handling ad pod completion - resuming content and hiding ad layer"
3031
3226
  );
3032
3227
  }
3228
+ this.releaseAdHoldState();
3229
+ this.preloadingAdUrls.clear();
3033
3230
  this.inAdBreak = false;
3034
3231
  this.expectedAdBreakDurationMs = void 0;
3035
3232
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3037,14 +3234,16 @@ var StormcloudVideoPlayer = class {
3037
3234
  this.clearAdStopTimer();
3038
3235
  this.clearAdFailsafeTimer();
3039
3236
  this.adPodQueue = [];
3237
+ this.adPodAllUrls = [];
3040
3238
  this.showAds = false;
3041
3239
  this.currentAdIndex = 0;
3042
3240
  this.totalAdsInBreak = 0;
3043
3241
  this.ima.stop().catch(() => {
3044
3242
  });
3045
3243
  const originalMutedState = this.ima.getOriginalMutedState();
3244
+ const originalVolume = typeof this.ima.getOriginalVolume === "function" ? this.ima.getOriginalVolume() : this.video.volume;
3046
3245
  this.video.muted = originalMutedState;
3047
- this.video.volume = originalMutedState ? 0 : 1;
3246
+ this.video.volume = originalVolume;
3048
3247
  if (this.config.debugAdTiming) {
3049
3248
  console.log(
3050
3249
  `[StormcloudVideoPlayer] Restored main video - muted: ${originalMutedState}, volume: ${this.video.volume}`
@@ -3117,6 +3316,64 @@ var StormcloudVideoPlayer = class {
3117
3316
  }
3118
3317
  return [b.vastTagUrl];
3119
3318
  }
3319
+ logQueuedAdUrls(urls) {
3320
+ if (!this.config.debugAdTiming) {
3321
+ return;
3322
+ }
3323
+ console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3324
+ }
3325
+ enforceAdHoldState() {
3326
+ this.video.dataset.stormcloudAdPlaying = "true";
3327
+ this.video.muted = true;
3328
+ this.video.volume = 0;
3329
+ if (typeof this.ima.showPlaceholder === "function") {
3330
+ this.ima.showPlaceholder();
3331
+ }
3332
+ }
3333
+ releaseAdHoldState() {
3334
+ delete this.video.dataset.stormcloudAdPlaying;
3335
+ if (typeof this.ima.hidePlaceholder === "function") {
3336
+ this.ima.hidePlaceholder();
3337
+ }
3338
+ }
3339
+ preloadUpcomingAds() {
3340
+ if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3341
+ return;
3342
+ }
3343
+ const upcoming = this.adPodQueue.slice(0, 2);
3344
+ for (const url of upcoming) {
3345
+ if (!url) continue;
3346
+ if (this.ima.hasPreloadedAd(url)) {
3347
+ this.preloadingAdUrls.delete(url);
3348
+ continue;
3349
+ }
3350
+ if (this.preloadingAdUrls.has(url)) {
3351
+ continue;
3352
+ }
3353
+ if (this.config.debugAdTiming) {
3354
+ console.log(
3355
+ `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3356
+ );
3357
+ }
3358
+ this.preloadingAdUrls.add(url);
3359
+ this.ima.preloadAds(url).then(() => {
3360
+ if (this.config.debugAdTiming) {
3361
+ console.log(
3362
+ `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3363
+ );
3364
+ }
3365
+ }).catch((error) => {
3366
+ if (this.config.debugAdTiming) {
3367
+ console.warn(
3368
+ `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3369
+ error
3370
+ );
3371
+ }
3372
+ }).finally(() => {
3373
+ this.preloadingAdUrls.delete(url);
3374
+ });
3375
+ }
3376
+ }
3120
3377
  getRemainingAdMs() {
3121
3378
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3122
3379
  return 0;
@@ -3137,7 +3394,7 @@ var StormcloudVideoPlayer = class {
3137
3394
  if (this.ima.isAdPlaying()) {
3138
3395
  const currentPerceptualState = this.isMuted();
3139
3396
  const newMutedState = !currentPerceptualState;
3140
- this.ima.updateOriginalMutedState(newMutedState);
3397
+ this.ima.updateOriginalMutedState(newMutedState, this.video.volume);
3141
3398
  this.ima.setAdVolume(newMutedState ? 0 : 1);
3142
3399
  if (this.config.debugAdTiming) {
3143
3400
  console.log(
@@ -3147,7 +3404,7 @@ var StormcloudVideoPlayer = class {
3147
3404
  }
3148
3405
  } else {
3149
3406
  this.video.muted = !this.video.muted;
3150
- this.ima.updateOriginalMutedState(this.video.muted);
3407
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3151
3408
  if (this.config.debugAdTiming) {
3152
3409
  console.log("[StormcloudVideoPlayer] Muted:", this.video.muted);
3153
3410
  }
@@ -3219,7 +3476,7 @@ var StormcloudVideoPlayer = class {
3219
3476
  }
3220
3477
  this.video.muted = muted;
3221
3478
  if (adPlaying) {
3222
- this.ima.updateOriginalMutedState(muted);
3479
+ this.ima.updateOriginalMutedState(muted, this.video.volume);
3223
3480
  this.ima.setAdVolume(muted ? 0 : 1);
3224
3481
  if (this.config.debugAdTiming) {
3225
3482
  console.log("[StormcloudVideoPlayer] setMuted applied during ad", {
@@ -3228,7 +3485,7 @@ var StormcloudVideoPlayer = class {
3228
3485
  }
3229
3486
  return;
3230
3487
  }
3231
- this.ima.updateOriginalMutedState(muted);
3488
+ this.ima.updateOriginalMutedState(muted, this.video.volume);
3232
3489
  if (this.config.debugAdTiming) {
3233
3490
  console.log("[StormcloudVideoPlayer] setMuted called:", muted);
3234
3491
  }
@@ -3268,6 +3525,9 @@ var StormcloudVideoPlayer = class {
3268
3525
  }
3269
3526
  (_a = this.hls) == null ? void 0 : _a.destroy();
3270
3527
  (_b = this.ima) == null ? void 0 : _b.destroy();
3528
+ this.releaseAdHoldState();
3529
+ this.preloadingAdUrls.clear();
3530
+ this.adPodAllUrls = [];
3271
3531
  }
3272
3532
  };
3273
3533
  // Annotate the CommonJS export names for ESM import in node: