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.
@@ -1,4 +1,4 @@
1
- import { S as StormcloudVideoPlayerConfig } from '../types-D1xfSdLP.cjs';
1
+ import { S as StormcloudVideoPlayerConfig } from '../types-t9jEJXZy.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>;
@@ -68,6 +70,10 @@ declare class StormcloudVideoPlayer {
68
70
  private startAdFailsafeTimer;
69
71
  private clearAdFailsafeTimer;
70
72
  private selectVastTagsForBreak;
73
+ private logQueuedAdUrls;
74
+ private enforceAdHoldState;
75
+ private releaseAdHoldState;
76
+ private preloadUpcomingAds;
71
77
  private getRemainingAdMs;
72
78
  private findBreakForTime;
73
79
  toggleMute(): void;
@@ -202,7 +202,10 @@ function getBrowserConfigOverrides() {
202
202
  function createImaController(video, options) {
203
203
  let adPlaying = false;
204
204
  let originalMutedState = false;
205
+ let originalVolume = typeof video.volume === "number" && !Number.isNaN(video.volume) ? Math.max(0, Math.min(1, video.volume)) : 1;
205
206
  const listeners = /* @__PURE__ */ new Map();
207
+ const preloadedVast = /* @__PURE__ */ new Map();
208
+ const preloadingVast = /* @__PURE__ */ new Map();
206
209
  function setAdPlayingFlag(isPlaying) {
207
210
  if (isPlaying) {
208
211
  video.dataset.stormcloudAdPlaying = "true";
@@ -293,16 +296,70 @@ function createImaController(video, options) {
293
296
  let adsLoadedReject;
294
297
  function makeAdsRequest(google, vastTagUrl) {
295
298
  const adsRequest = new google.ima.AdsRequest();
296
- adsRequest.adTagUrl = vastTagUrl;
299
+ const preloadedResponse = preloadedVast.get(vastTagUrl);
300
+ if (preloadedResponse) {
301
+ adsRequest.adsResponse = preloadedResponse;
302
+ console.log(
303
+ "[IMA] Using preloaded VAST response for immediate ad request"
304
+ );
305
+ } else {
306
+ adsRequest.adTagUrl = vastTagUrl;
307
+ }
297
308
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
298
309
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
299
310
  adsRequest.linearAdSlotWidth = videoWidth;
300
311
  adsRequest.linearAdSlotHeight = videoHeight;
301
312
  adsRequest.nonLinearAdSlotWidth = videoWidth;
302
313
  adsRequest.nonLinearAdSlotHeight = videoHeight;
314
+ if (typeof adsRequest.setAdWillAutoPlay === "function") {
315
+ try {
316
+ const willAutoPlay = !video.paused || video.autoplay;
317
+ adsRequest.setAdWillAutoPlay(willAutoPlay);
318
+ } catch (error) {
319
+ console.warn("[IMA] Failed to call setAdWillAutoPlay:", error);
320
+ }
321
+ }
322
+ if (typeof adsRequest.setAdWillPlayMuted === "function") {
323
+ try {
324
+ const willPlayMuted = video.muted || video.volume === 0;
325
+ adsRequest.setAdWillPlayMuted(willPlayMuted);
326
+ } catch (error) {
327
+ console.warn("[IMA] Failed to call setAdWillPlayMuted:", error);
328
+ }
329
+ }
303
330
  adsRequest.vastLoadTimeout = 5e3;
304
331
  console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
305
332
  adsLoader.requestAds(adsRequest);
333
+ if (preloadedResponse) {
334
+ preloadedVast.delete(vastTagUrl);
335
+ }
336
+ }
337
+ function ensurePlaceholderContainer() {
338
+ var _a;
339
+ if (adContainerEl) {
340
+ return;
341
+ }
342
+ const container = document.createElement("div");
343
+ container.style.position = "absolute";
344
+ container.style.left = "0";
345
+ container.style.top = "0";
346
+ container.style.right = "0";
347
+ container.style.bottom = "0";
348
+ container.style.display = "none";
349
+ container.style.alignItems = "center";
350
+ container.style.justifyContent = "center";
351
+ container.style.pointerEvents = "none";
352
+ container.style.zIndex = "10";
353
+ container.style.backgroundColor = "#000";
354
+ (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
355
+ adContainerEl = container;
356
+ }
357
+ async function fetchVastDocument(vastTagUrl) {
358
+ const response = await fetch(vastTagUrl, { mode: "cors" });
359
+ if (!response.ok) {
360
+ throw new Error(`Failed to preload VAST: ${response.status}`);
361
+ }
362
+ return response.text();
306
363
  }
307
364
  function destroyAdsManager() {
308
365
  if (adsManager) {
@@ -318,29 +375,16 @@ function createImaController(video, options) {
318
375
  return {
319
376
  initialize() {
320
377
  ensureImaLoaded().then(() => {
321
- var _a, _b;
378
+ var _a;
322
379
  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;
380
+ ensurePlaceholderContainer();
381
+ if (!adDisplayContainer && adContainerEl) {
338
382
  adDisplayContainer = new google.ima.AdDisplayContainer(
339
- container,
383
+ adContainerEl,
340
384
  video
341
385
  );
342
386
  try {
343
- (_b = adDisplayContainer.initialize) == null ? void 0 : _b.call(adDisplayContainer);
387
+ (_a = adDisplayContainer.initialize) == null ? void 0 : _a.call(adDisplayContainer);
344
388
  } catch {
345
389
  }
346
390
  }
@@ -644,6 +688,32 @@ function createImaController(video, options) {
644
688
  return Promise.reject(error);
645
689
  }
646
690
  },
691
+ async preloadAds(vastTagUrl) {
692
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
693
+ return Promise.resolve();
694
+ }
695
+ if (preloadedVast.has(vastTagUrl)) {
696
+ return Promise.resolve();
697
+ }
698
+ const inflight = preloadingVast.get(vastTagUrl);
699
+ if (inflight) {
700
+ return inflight;
701
+ }
702
+ const preloadPromise = fetchVastDocument(vastTagUrl).then((xml) => {
703
+ preloadedVast.set(vastTagUrl, xml);
704
+ console.log("[IMA] Cached VAST response for preloading:", vastTagUrl);
705
+ }).catch((error) => {
706
+ console.warn("[IMA] Failed to preload VAST response:", error);
707
+ preloadedVast.delete(vastTagUrl);
708
+ }).finally(() => {
709
+ preloadingVast.delete(vastTagUrl);
710
+ });
711
+ preloadingVast.set(vastTagUrl, preloadPromise);
712
+ return preloadPromise;
713
+ },
714
+ hasPreloadedAd(vastTagUrl) {
715
+ return preloadedVast.has(vastTagUrl);
716
+ },
647
717
  async play() {
648
718
  var _a, _b;
649
719
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
@@ -662,7 +732,7 @@ function createImaController(video, options) {
662
732
  console.log(`[IMA] Initializing ads manager (${width}x${height})`);
663
733
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
664
734
  adPlaying = true;
665
- const adVolume = originalMutedState ? 0 : video.volume;
735
+ const adVolume = originalMutedState ? 0 : originalVolume;
666
736
  try {
667
737
  adsManager.setVolume(adVolume);
668
738
  console.log(`[IMA] Set ad volume to ${adVolume}`);
@@ -704,6 +774,7 @@ function createImaController(video, options) {
704
774
  destroyAdsManager();
705
775
  adPlaying = false;
706
776
  video.muted = originalMutedState;
777
+ video.volume = originalVolume;
707
778
  setAdPlayingFlag(false);
708
779
  if (adContainerEl) {
709
780
  adContainerEl.style.pointerEvents = "none";
@@ -719,6 +790,8 @@ function createImaController(video, options) {
719
790
  adContainerEl = void 0;
720
791
  adDisplayContainer = void 0;
721
792
  adsLoader = void 0;
793
+ preloadedVast.clear();
794
+ preloadingVast.clear();
722
795
  },
723
796
  isAdPlaying() {
724
797
  return adPlaying;
@@ -746,15 +819,20 @@ function createImaController(video, options) {
746
819
  var _a;
747
820
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
748
821
  },
749
- updateOriginalMutedState(muted) {
822
+ updateOriginalMutedState(muted, volume) {
823
+ const nextVolume = typeof volume === "number" && !Number.isNaN(volume) ? Math.max(0, Math.min(1, volume)) : originalVolume;
750
824
  console.log(
751
- `[IMA] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
825
+ `[IMA] updateOriginalMutedState called: { muted: ${originalMutedState} -> ${muted}, volume: ${originalVolume} -> ${nextVolume} }`
752
826
  );
753
827
  originalMutedState = muted;
828
+ originalVolume = nextVolume;
754
829
  },
755
830
  getOriginalMutedState() {
756
831
  return originalMutedState;
757
832
  },
833
+ getOriginalVolume() {
834
+ return originalVolume;
835
+ },
758
836
  setAdVolume(volume) {
759
837
  if (adsManager && adPlaying) {
760
838
  try {
@@ -774,6 +852,19 @@ function createImaController(video, options) {
774
852
  }
775
853
  }
776
854
  return 1;
855
+ },
856
+ showPlaceholder() {
857
+ ensurePlaceholderContainer();
858
+ if (adContainerEl) {
859
+ adContainerEl.style.display = "flex";
860
+ adContainerEl.style.pointerEvents = "auto";
861
+ }
862
+ },
863
+ hidePlaceholder() {
864
+ if (adContainerEl) {
865
+ adContainerEl.style.display = "none";
866
+ adContainerEl.style.pointerEvents = "none";
867
+ }
777
868
  }
778
869
  };
779
870
  }
@@ -783,6 +874,7 @@ var import_hls = __toESM(require("hls.js"), 1);
783
874
  function createHlsAdPlayer(contentVideo, options) {
784
875
  let adPlaying = false;
785
876
  let originalMutedState = false;
877
+ let originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
786
878
  const listeners = /* @__PURE__ */ new Map();
787
879
  const licenseKey = options == null ? void 0 : options.licenseKey;
788
880
  const mainHlsInstance = options == null ? void 0 : options.mainHlsInstance;
@@ -791,6 +883,8 @@ function createHlsAdPlayer(contentVideo, options) {
791
883
  let adContainerEl;
792
884
  let currentAd;
793
885
  let sessionId;
886
+ const preloadedAds = /* @__PURE__ */ new Map();
887
+ const preloadingAds = /* @__PURE__ */ new Map();
794
888
  let trackingFired = {
795
889
  impression: false,
796
890
  start: false,
@@ -1020,6 +1114,19 @@ function createHlsAdPlayer(contentVideo, options) {
1020
1114
  return null;
1021
1115
  }
1022
1116
  }
1117
+ async function fetchAndParseVastAd(vastTagUrl) {
1118
+ const response = await fetch(vastTagUrl);
1119
+ if (!response.ok) {
1120
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1121
+ }
1122
+ const vastXml = await response.text();
1123
+ console.log("[HlsAdPlayer] VAST XML received");
1124
+ console.log(
1125
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1126
+ vastXml.substring(0, 2e3)
1127
+ );
1128
+ return parseVastXml(vastXml);
1129
+ }
1023
1130
  function createAdVideoElement() {
1024
1131
  const video = document.createElement("video");
1025
1132
  video.style.position = "absolute";
@@ -1125,6 +1232,7 @@ function createHlsAdPlayer(contentVideo, options) {
1125
1232
  setAdPlayingFlag(false);
1126
1233
  const previousMutedState = contentVideo.muted;
1127
1234
  contentVideo.muted = originalMutedState;
1235
+ contentVideo.volume = originalMutedState ? 0 : originalVolume;
1128
1236
  console.log(
1129
1237
  `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1130
1238
  );
@@ -1171,17 +1279,17 @@ function createHlsAdPlayer(contentVideo, options) {
1171
1279
  }
1172
1280
  try {
1173
1281
  sessionId = generateSessionId();
1174
- const response = await fetch(vastTagUrl);
1175
- if (!response.ok) {
1176
- throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1282
+ let ad;
1283
+ if (preloadedAds.has(vastTagUrl)) {
1284
+ ad = preloadedAds.get(vastTagUrl);
1285
+ preloadedAds.delete(vastTagUrl);
1286
+ console.log(
1287
+ "[HlsAdPlayer] Using preloaded VAST response:",
1288
+ vastTagUrl
1289
+ );
1290
+ } else {
1291
+ ad = await fetchAndParseVastAd(vastTagUrl);
1177
1292
  }
1178
- const vastXml = await response.text();
1179
- console.log("[HlsAdPlayer] VAST XML received");
1180
- console.log(
1181
- "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1182
- vastXml.substring(0, 2e3)
1183
- );
1184
- const ad = parseVastXml(vastXml);
1185
1293
  if (!ad) {
1186
1294
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1187
1295
  emit("ad_error");
@@ -1200,6 +1308,37 @@ function createHlsAdPlayer(contentVideo, options) {
1200
1308
  return Promise.reject(error);
1201
1309
  }
1202
1310
  },
1311
+ async preloadAds(vastTagUrl) {
1312
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
1313
+ return Promise.resolve();
1314
+ }
1315
+ if (preloadedAds.has(vastTagUrl)) {
1316
+ return Promise.resolve();
1317
+ }
1318
+ const inflight = preloadingAds.get(vastTagUrl);
1319
+ if (inflight) {
1320
+ return inflight;
1321
+ }
1322
+ const preloadPromise = fetchAndParseVastAd(vastTagUrl).then((ad) => {
1323
+ if (ad) {
1324
+ preloadedAds.set(vastTagUrl, ad);
1325
+ console.log(
1326
+ "[HlsAdPlayer] Cached VAST response for preloading:",
1327
+ vastTagUrl
1328
+ );
1329
+ }
1330
+ }).catch((error) => {
1331
+ console.warn("[HlsAdPlayer] Failed to preload VAST response:", error);
1332
+ preloadedAds.delete(vastTagUrl);
1333
+ }).finally(() => {
1334
+ preloadingAds.delete(vastTagUrl);
1335
+ });
1336
+ preloadingAds.set(vastTagUrl, preloadPromise);
1337
+ return preloadPromise;
1338
+ },
1339
+ hasPreloadedAd(vastTagUrl) {
1340
+ return preloadedAds.has(vastTagUrl);
1341
+ },
1203
1342
  async play() {
1204
1343
  if (!currentAd) {
1205
1344
  console.warn(
@@ -1223,6 +1362,10 @@ function createHlsAdPlayer(contentVideo, options) {
1223
1362
  complete: false
1224
1363
  };
1225
1364
  const contentVolume = contentVideo.volume;
1365
+ originalVolume = Math.max(
1366
+ 0,
1367
+ Math.min(1, contentVolume || originalVolume)
1368
+ );
1226
1369
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
1227
1370
  contentVideo.pause();
1228
1371
  console.log("[HlsAdPlayer] Content paused (VOD mode)");
@@ -1235,7 +1378,7 @@ function createHlsAdPlayer(contentVideo, options) {
1235
1378
  adPlaying = true;
1236
1379
  setAdPlayingFlag(true);
1237
1380
  if (adVideoElement) {
1238
- const adVolume = originalMutedState ? 0 : contentVolume;
1381
+ const adVolume = originalMutedState ? 0 : originalVolume;
1239
1382
  adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1240
1383
  adVideoElement.muted = false;
1241
1384
  console.log(
@@ -1314,6 +1457,7 @@ function createHlsAdPlayer(contentVideo, options) {
1314
1457
  adPlaying = false;
1315
1458
  setAdPlayingFlag(false);
1316
1459
  contentVideo.muted = originalMutedState;
1460
+ contentVideo.volume = originalMutedState ? 0 : originalVolume;
1317
1461
  if (adHls) {
1318
1462
  adHls.destroy();
1319
1463
  adHls = void 0;
@@ -1330,6 +1474,8 @@ function createHlsAdPlayer(contentVideo, options) {
1330
1474
  adContainerEl = void 0;
1331
1475
  currentAd = void 0;
1332
1476
  listeners.clear();
1477
+ preloadedAds.clear();
1478
+ preloadingAds.clear();
1333
1479
  },
1334
1480
  isAdPlaying() {
1335
1481
  return adPlaying;
@@ -1353,15 +1499,20 @@ function createHlsAdPlayer(contentVideo, options) {
1353
1499
  var _a;
1354
1500
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
1355
1501
  },
1356
- updateOriginalMutedState(muted) {
1502
+ updateOriginalMutedState(muted, volume) {
1503
+ const nextVolume = typeof volume === "number" && !Number.isNaN(volume) ? Math.max(0, Math.min(1, volume)) : originalVolume;
1357
1504
  console.log(
1358
- `[HlsAdPlayer] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
1505
+ `[HlsAdPlayer] updateOriginalMutedState called: { muted: ${originalMutedState} -> ${muted}, volume: ${originalVolume} -> ${nextVolume} }`
1359
1506
  );
1360
1507
  originalMutedState = muted;
1508
+ originalVolume = nextVolume;
1361
1509
  },
1362
1510
  getOriginalMutedState() {
1363
1511
  return originalMutedState;
1364
1512
  },
1513
+ getOriginalVolume() {
1514
+ return originalVolume;
1515
+ },
1365
1516
  setAdVolume(volume) {
1366
1517
  if (adVideoElement && adPlaying) {
1367
1518
  adVideoElement.volume = Math.max(0, Math.min(1, volume));
@@ -1372,6 +1523,35 @@ function createHlsAdPlayer(contentVideo, options) {
1372
1523
  return adVideoElement.volume;
1373
1524
  }
1374
1525
  return 1;
1526
+ },
1527
+ showPlaceholder() {
1528
+ var _a;
1529
+ if (!adContainerEl) {
1530
+ const container = document.createElement("div");
1531
+ container.style.position = "absolute";
1532
+ container.style.left = "0";
1533
+ container.style.top = "0";
1534
+ container.style.right = "0";
1535
+ container.style.bottom = "0";
1536
+ container.style.display = "none";
1537
+ container.style.alignItems = "center";
1538
+ container.style.justifyContent = "center";
1539
+ container.style.pointerEvents = "none";
1540
+ container.style.zIndex = "10";
1541
+ container.style.backgroundColor = "#000";
1542
+ (_a = contentVideo.parentElement) == null ? void 0 : _a.appendChild(container);
1543
+ adContainerEl = container;
1544
+ }
1545
+ if (adContainerEl) {
1546
+ adContainerEl.style.display = "flex";
1547
+ adContainerEl.style.pointerEvents = "auto";
1548
+ }
1549
+ },
1550
+ hidePlaceholder() {
1551
+ if (adContainerEl) {
1552
+ adContainerEl.style.display = "none";
1553
+ adContainerEl.style.pointerEvents = "none";
1554
+ }
1375
1555
  }
1376
1556
  };
1377
1557
  }
@@ -1852,6 +2032,8 @@ var StormcloudVideoPlayer = class {
1852
2032
  this.bufferedSegmentsCount = 0;
1853
2033
  this.shouldAutoplayAfterBuffering = false;
1854
2034
  this.hasInitialBufferCompleted = false;
2035
+ this.adPodAllUrls = [];
2036
+ this.preloadingAdUrls = /* @__PURE__ */ new Set();
1855
2037
  initializePolyfills();
1856
2038
  const browserOverrides = getBrowserConfigOverrides();
1857
2039
  this.config = { ...config, ...browserOverrides };
@@ -2111,7 +2293,7 @@ var StormcloudVideoPlayer = class {
2111
2293
  this.video.autoplay = !!this.config.autoplay;
2112
2294
  this.video.muted = !!this.config.muted;
2113
2295
  this.ima.initialize();
2114
- this.ima.updateOriginalMutedState(this.video.muted);
2296
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2115
2297
  this.ima.on("all_ads_completed", () => {
2116
2298
  if (this.config.debugAdTiming) {
2117
2299
  console.log(
@@ -2152,6 +2334,7 @@ var StormcloudVideoPlayer = class {
2152
2334
  console.log("[StormcloudVideoPlayer] IMA content_pause event received");
2153
2335
  }
2154
2336
  this.clearAdFailsafeTimer();
2337
+ this.enforceAdHoldState();
2155
2338
  });
2156
2339
  this.ima.on("content_resume", () => {
2157
2340
  if (this.config.debugAdTiming) {
@@ -2176,9 +2359,7 @@ var StormcloudVideoPlayer = class {
2176
2359
  if (remaining > 500 && this.adPodQueue.length > 0) {
2177
2360
  const next = this.adPodQueue.shift();
2178
2361
  this.currentAdIndex++;
2179
- this.video.dataset.stormcloudAdPlaying = "true";
2180
- this.video.muted = true;
2181
- this.video.volume = 0;
2362
+ this.enforceAdHoldState();
2182
2363
  if (this.config.debugAdTiming) {
2183
2364
  console.log(
2184
2365
  `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
@@ -2790,11 +2971,16 @@ var StormcloudVideoPlayer = class {
2790
2971
  return;
2791
2972
  }
2792
2973
  if (vastTagUrls.length > 0) {
2974
+ this.adPodAllUrls = [...vastTagUrls];
2975
+ this.preloadingAdUrls.clear();
2976
+ this.logQueuedAdUrls(this.adPodAllUrls);
2793
2977
  this.inAdBreak = true;
2794
2978
  this.showAds = true;
2795
2979
  this.currentAdIndex = 0;
2796
2980
  this.totalAdsInBreak = vastTagUrls.length;
2797
2981
  this.adPodQueue = [...vastTagUrls];
2982
+ this.enforceAdHoldState();
2983
+ this.preloadUpcomingAds();
2798
2984
  if (this.config.debugAdTiming) {
2799
2985
  console.log(
2800
2986
  `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
@@ -2865,6 +3051,7 @@ var StormcloudVideoPlayer = class {
2865
3051
  const first = tags[0];
2866
3052
  const rest = tags.slice(1);
2867
3053
  this.adPodQueue = rest;
3054
+ this.enforceAdHoldState();
2868
3055
  await this.playSingleAd(first);
2869
3056
  this.inAdBreak = true;
2870
3057
  this.expectedAdBreakDurationMs = remainingMs;
@@ -2978,6 +3165,12 @@ var StormcloudVideoPlayer = class {
2978
3165
  }
2979
3166
  return;
2980
3167
  }
3168
+ const wasPreloaded = this.ima.hasPreloadedAd(vastTagUrl);
3169
+ if (wasPreloaded && this.config.debugAdTiming) {
3170
+ console.log(
3171
+ `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3172
+ );
3173
+ }
2981
3174
  if (!this.showAds) {
2982
3175
  if (this.config.debugAdTiming) {
2983
3176
  console.log(
@@ -2989,7 +3182,7 @@ var StormcloudVideoPlayer = class {
2989
3182
  }
2990
3183
  );
2991
3184
  }
2992
- this.ima.updateOriginalMutedState(this.video.muted);
3185
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2993
3186
  } else if (this.config.debugAdTiming) {
2994
3187
  console.log(
2995
3188
  `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
@@ -2998,12 +3191,14 @@ var StormcloudVideoPlayer = class {
2998
3191
  this.startAdFailsafeTimer();
2999
3192
  try {
3000
3193
  await this.ima.requestAds(vastTagUrl);
3194
+ this.preloadUpcomingAds();
3001
3195
  try {
3002
3196
  if (this.config.debugAdTiming) {
3003
3197
  console.log(
3004
3198
  "[StormcloudVideoPlayer] Ad request completed, attempting playback"
3005
3199
  );
3006
3200
  }
3201
+ this.enforceAdHoldState();
3007
3202
  await this.ima.play();
3008
3203
  if (this.config.debugAdTiming) {
3009
3204
  console.log(
@@ -3033,6 +3228,8 @@ var StormcloudVideoPlayer = class {
3033
3228
  "[StormcloudVideoPlayer] Handling ad pod completion - resuming content and hiding ad layer"
3034
3229
  );
3035
3230
  }
3231
+ this.releaseAdHoldState();
3232
+ this.preloadingAdUrls.clear();
3036
3233
  this.inAdBreak = false;
3037
3234
  this.expectedAdBreakDurationMs = void 0;
3038
3235
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3040,14 +3237,16 @@ var StormcloudVideoPlayer = class {
3040
3237
  this.clearAdStopTimer();
3041
3238
  this.clearAdFailsafeTimer();
3042
3239
  this.adPodQueue = [];
3240
+ this.adPodAllUrls = [];
3043
3241
  this.showAds = false;
3044
3242
  this.currentAdIndex = 0;
3045
3243
  this.totalAdsInBreak = 0;
3046
3244
  this.ima.stop().catch(() => {
3047
3245
  });
3048
3246
  const originalMutedState = this.ima.getOriginalMutedState();
3247
+ const originalVolume = typeof this.ima.getOriginalVolume === "function" ? this.ima.getOriginalVolume() : this.video.volume;
3049
3248
  this.video.muted = originalMutedState;
3050
- this.video.volume = originalMutedState ? 0 : 1;
3249
+ this.video.volume = originalVolume;
3051
3250
  if (this.config.debugAdTiming) {
3052
3251
  console.log(
3053
3252
  `[StormcloudVideoPlayer] Restored main video - muted: ${originalMutedState}, volume: ${this.video.volume}`
@@ -3120,6 +3319,64 @@ var StormcloudVideoPlayer = class {
3120
3319
  }
3121
3320
  return [b.vastTagUrl];
3122
3321
  }
3322
+ logQueuedAdUrls(urls) {
3323
+ if (!this.config.debugAdTiming) {
3324
+ return;
3325
+ }
3326
+ console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3327
+ }
3328
+ enforceAdHoldState() {
3329
+ this.video.dataset.stormcloudAdPlaying = "true";
3330
+ this.video.muted = true;
3331
+ this.video.volume = 0;
3332
+ if (typeof this.ima.showPlaceholder === "function") {
3333
+ this.ima.showPlaceholder();
3334
+ }
3335
+ }
3336
+ releaseAdHoldState() {
3337
+ delete this.video.dataset.stormcloudAdPlaying;
3338
+ if (typeof this.ima.hidePlaceholder === "function") {
3339
+ this.ima.hidePlaceholder();
3340
+ }
3341
+ }
3342
+ preloadUpcomingAds() {
3343
+ if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3344
+ return;
3345
+ }
3346
+ const upcoming = this.adPodQueue.slice(0, 2);
3347
+ for (const url of upcoming) {
3348
+ if (!url) continue;
3349
+ if (this.ima.hasPreloadedAd(url)) {
3350
+ this.preloadingAdUrls.delete(url);
3351
+ continue;
3352
+ }
3353
+ if (this.preloadingAdUrls.has(url)) {
3354
+ continue;
3355
+ }
3356
+ if (this.config.debugAdTiming) {
3357
+ console.log(
3358
+ `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3359
+ );
3360
+ }
3361
+ this.preloadingAdUrls.add(url);
3362
+ this.ima.preloadAds(url).then(() => {
3363
+ if (this.config.debugAdTiming) {
3364
+ console.log(
3365
+ `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3366
+ );
3367
+ }
3368
+ }).catch((error) => {
3369
+ if (this.config.debugAdTiming) {
3370
+ console.warn(
3371
+ `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3372
+ error
3373
+ );
3374
+ }
3375
+ }).finally(() => {
3376
+ this.preloadingAdUrls.delete(url);
3377
+ });
3378
+ }
3379
+ }
3123
3380
  getRemainingAdMs() {
3124
3381
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3125
3382
  return 0;
@@ -3140,7 +3397,7 @@ var StormcloudVideoPlayer = class {
3140
3397
  if (this.ima.isAdPlaying()) {
3141
3398
  const currentPerceptualState = this.isMuted();
3142
3399
  const newMutedState = !currentPerceptualState;
3143
- this.ima.updateOriginalMutedState(newMutedState);
3400
+ this.ima.updateOriginalMutedState(newMutedState, this.video.volume);
3144
3401
  this.ima.setAdVolume(newMutedState ? 0 : 1);
3145
3402
  if (this.config.debugAdTiming) {
3146
3403
  console.log(
@@ -3150,7 +3407,7 @@ var StormcloudVideoPlayer = class {
3150
3407
  }
3151
3408
  } else {
3152
3409
  this.video.muted = !this.video.muted;
3153
- this.ima.updateOriginalMutedState(this.video.muted);
3410
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3154
3411
  if (this.config.debugAdTiming) {
3155
3412
  console.log("[StormcloudVideoPlayer] Muted:", this.video.muted);
3156
3413
  }
@@ -3222,7 +3479,7 @@ var StormcloudVideoPlayer = class {
3222
3479
  }
3223
3480
  this.video.muted = muted;
3224
3481
  if (adPlaying) {
3225
- this.ima.updateOriginalMutedState(muted);
3482
+ this.ima.updateOriginalMutedState(muted, this.video.volume);
3226
3483
  this.ima.setAdVolume(muted ? 0 : 1);
3227
3484
  if (this.config.debugAdTiming) {
3228
3485
  console.log("[StormcloudVideoPlayer] setMuted applied during ad", {
@@ -3231,7 +3488,7 @@ var StormcloudVideoPlayer = class {
3231
3488
  }
3232
3489
  return;
3233
3490
  }
3234
- this.ima.updateOriginalMutedState(muted);
3491
+ this.ima.updateOriginalMutedState(muted, this.video.volume);
3235
3492
  if (this.config.debugAdTiming) {
3236
3493
  console.log("[StormcloudVideoPlayer] setMuted called:", muted);
3237
3494
  }
@@ -3271,6 +3528,9 @@ var StormcloudVideoPlayer = class {
3271
3528
  }
3272
3529
  (_a = this.hls) == null ? void 0 : _a.destroy();
3273
3530
  (_b = this.ima) == null ? void 0 : _b.destroy();
3531
+ this.releaseAdHoldState();
3532
+ this.preloadingAdUrls.clear();
3533
+ this.adPodAllUrls = [];
3274
3534
  }
3275
3535
  };
3276
3536