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