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,5 +1,5 @@
1
1
  import { Component } from 'react';
2
- import { S as StormcloudVideoPlayerConfig } from '../types-D1xfSdLP.cjs';
2
+ import { S as StormcloudVideoPlayerConfig } from '../types-t9jEJXZy.cjs';
3
3
 
4
4
  interface HlsPlayerProps extends StormcloudVideoPlayerConfig {
5
5
  onMount?: (player: any) => void;
@@ -241,7 +241,10 @@ function getBrowserConfigOverrides() {
241
241
  function createImaController(video, options) {
242
242
  let adPlaying = false;
243
243
  let originalMutedState = false;
244
+ let originalVolume = typeof video.volume === "number" && !Number.isNaN(video.volume) ? Math.max(0, Math.min(1, video.volume)) : 1;
244
245
  const listeners = /* @__PURE__ */ new Map();
246
+ const preloadedVast = /* @__PURE__ */ new Map();
247
+ const preloadingVast = /* @__PURE__ */ new Map();
245
248
  function setAdPlayingFlag(isPlaying) {
246
249
  if (isPlaying) {
247
250
  video.dataset.stormcloudAdPlaying = "true";
@@ -332,16 +335,70 @@ function createImaController(video, options) {
332
335
  let adsLoadedReject;
333
336
  function makeAdsRequest(google, vastTagUrl) {
334
337
  const adsRequest = new google.ima.AdsRequest();
335
- adsRequest.adTagUrl = vastTagUrl;
338
+ const preloadedResponse = preloadedVast.get(vastTagUrl);
339
+ if (preloadedResponse) {
340
+ adsRequest.adsResponse = preloadedResponse;
341
+ console.log(
342
+ "[IMA] Using preloaded VAST response for immediate ad request"
343
+ );
344
+ } else {
345
+ adsRequest.adTagUrl = vastTagUrl;
346
+ }
336
347
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
337
348
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
338
349
  adsRequest.linearAdSlotWidth = videoWidth;
339
350
  adsRequest.linearAdSlotHeight = videoHeight;
340
351
  adsRequest.nonLinearAdSlotWidth = videoWidth;
341
352
  adsRequest.nonLinearAdSlotHeight = videoHeight;
353
+ if (typeof adsRequest.setAdWillAutoPlay === "function") {
354
+ try {
355
+ const willAutoPlay = !video.paused || video.autoplay;
356
+ adsRequest.setAdWillAutoPlay(willAutoPlay);
357
+ } catch (error) {
358
+ console.warn("[IMA] Failed to call setAdWillAutoPlay:", error);
359
+ }
360
+ }
361
+ if (typeof adsRequest.setAdWillPlayMuted === "function") {
362
+ try {
363
+ const willPlayMuted = video.muted || video.volume === 0;
364
+ adsRequest.setAdWillPlayMuted(willPlayMuted);
365
+ } catch (error) {
366
+ console.warn("[IMA] Failed to call setAdWillPlayMuted:", error);
367
+ }
368
+ }
342
369
  adsRequest.vastLoadTimeout = 5e3;
343
370
  console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
344
371
  adsLoader.requestAds(adsRequest);
372
+ if (preloadedResponse) {
373
+ preloadedVast.delete(vastTagUrl);
374
+ }
375
+ }
376
+ function ensurePlaceholderContainer() {
377
+ var _a;
378
+ if (adContainerEl) {
379
+ return;
380
+ }
381
+ const container = document.createElement("div");
382
+ container.style.position = "absolute";
383
+ container.style.left = "0";
384
+ container.style.top = "0";
385
+ container.style.right = "0";
386
+ container.style.bottom = "0";
387
+ container.style.display = "none";
388
+ container.style.alignItems = "center";
389
+ container.style.justifyContent = "center";
390
+ container.style.pointerEvents = "none";
391
+ container.style.zIndex = "10";
392
+ container.style.backgroundColor = "#000";
393
+ (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
394
+ adContainerEl = container;
395
+ }
396
+ async function fetchVastDocument(vastTagUrl) {
397
+ const response = await fetch(vastTagUrl, { mode: "cors" });
398
+ if (!response.ok) {
399
+ throw new Error(`Failed to preload VAST: ${response.status}`);
400
+ }
401
+ return response.text();
345
402
  }
346
403
  function destroyAdsManager() {
347
404
  if (adsManager) {
@@ -357,29 +414,16 @@ function createImaController(video, options) {
357
414
  return {
358
415
  initialize() {
359
416
  ensureImaLoaded().then(() => {
360
- var _a, _b;
417
+ var _a;
361
418
  const google = window.google;
362
- if (!adDisplayContainer) {
363
- const container = document.createElement("div");
364
- container.style.position = "absolute";
365
- container.style.left = "0";
366
- container.style.top = "0";
367
- container.style.right = "0";
368
- container.style.bottom = "0";
369
- container.style.display = "none";
370
- container.style.alignItems = "center";
371
- container.style.justifyContent = "center";
372
- container.style.pointerEvents = "none";
373
- container.style.zIndex = "10";
374
- container.style.backgroundColor = "#000";
375
- (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
376
- adContainerEl = container;
419
+ ensurePlaceholderContainer();
420
+ if (!adDisplayContainer && adContainerEl) {
377
421
  adDisplayContainer = new google.ima.AdDisplayContainer(
378
- container,
422
+ adContainerEl,
379
423
  video
380
424
  );
381
425
  try {
382
- (_b = adDisplayContainer.initialize) == null ? void 0 : _b.call(adDisplayContainer);
426
+ (_a = adDisplayContainer.initialize) == null ? void 0 : _a.call(adDisplayContainer);
383
427
  } catch {
384
428
  }
385
429
  }
@@ -683,6 +727,32 @@ function createImaController(video, options) {
683
727
  return Promise.reject(error);
684
728
  }
685
729
  },
730
+ async preloadAds(vastTagUrl) {
731
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
732
+ return Promise.resolve();
733
+ }
734
+ if (preloadedVast.has(vastTagUrl)) {
735
+ return Promise.resolve();
736
+ }
737
+ const inflight = preloadingVast.get(vastTagUrl);
738
+ if (inflight) {
739
+ return inflight;
740
+ }
741
+ const preloadPromise = fetchVastDocument(vastTagUrl).then((xml) => {
742
+ preloadedVast.set(vastTagUrl, xml);
743
+ console.log("[IMA] Cached VAST response for preloading:", vastTagUrl);
744
+ }).catch((error) => {
745
+ console.warn("[IMA] Failed to preload VAST response:", error);
746
+ preloadedVast.delete(vastTagUrl);
747
+ }).finally(() => {
748
+ preloadingVast.delete(vastTagUrl);
749
+ });
750
+ preloadingVast.set(vastTagUrl, preloadPromise);
751
+ return preloadPromise;
752
+ },
753
+ hasPreloadedAd(vastTagUrl) {
754
+ return preloadedVast.has(vastTagUrl);
755
+ },
686
756
  async play() {
687
757
  var _a, _b;
688
758
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
@@ -701,7 +771,7 @@ function createImaController(video, options) {
701
771
  console.log(`[IMA] Initializing ads manager (${width}x${height})`);
702
772
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
703
773
  adPlaying = true;
704
- const adVolume = originalMutedState ? 0 : video.volume;
774
+ const adVolume = originalMutedState ? 0 : originalVolume;
705
775
  try {
706
776
  adsManager.setVolume(adVolume);
707
777
  console.log(`[IMA] Set ad volume to ${adVolume}`);
@@ -743,6 +813,7 @@ function createImaController(video, options) {
743
813
  destroyAdsManager();
744
814
  adPlaying = false;
745
815
  video.muted = originalMutedState;
816
+ video.volume = originalVolume;
746
817
  setAdPlayingFlag(false);
747
818
  if (adContainerEl) {
748
819
  adContainerEl.style.pointerEvents = "none";
@@ -758,6 +829,8 @@ function createImaController(video, options) {
758
829
  adContainerEl = void 0;
759
830
  adDisplayContainer = void 0;
760
831
  adsLoader = void 0;
832
+ preloadedVast.clear();
833
+ preloadingVast.clear();
761
834
  },
762
835
  isAdPlaying() {
763
836
  return adPlaying;
@@ -785,15 +858,20 @@ function createImaController(video, options) {
785
858
  var _a;
786
859
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
787
860
  },
788
- updateOriginalMutedState(muted) {
861
+ updateOriginalMutedState(muted, volume) {
862
+ const nextVolume = typeof volume === "number" && !Number.isNaN(volume) ? Math.max(0, Math.min(1, volume)) : originalVolume;
789
863
  console.log(
790
- `[IMA] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
864
+ `[IMA] updateOriginalMutedState called: { muted: ${originalMutedState} -> ${muted}, volume: ${originalVolume} -> ${nextVolume} }`
791
865
  );
792
866
  originalMutedState = muted;
867
+ originalVolume = nextVolume;
793
868
  },
794
869
  getOriginalMutedState() {
795
870
  return originalMutedState;
796
871
  },
872
+ getOriginalVolume() {
873
+ return originalVolume;
874
+ },
797
875
  setAdVolume(volume) {
798
876
  if (adsManager && adPlaying) {
799
877
  try {
@@ -813,6 +891,19 @@ function createImaController(video, options) {
813
891
  }
814
892
  }
815
893
  return 1;
894
+ },
895
+ showPlaceholder() {
896
+ ensurePlaceholderContainer();
897
+ if (adContainerEl) {
898
+ adContainerEl.style.display = "flex";
899
+ adContainerEl.style.pointerEvents = "auto";
900
+ }
901
+ },
902
+ hidePlaceholder() {
903
+ if (adContainerEl) {
904
+ adContainerEl.style.display = "none";
905
+ adContainerEl.style.pointerEvents = "none";
906
+ }
816
907
  }
817
908
  };
818
909
  }
@@ -822,6 +913,7 @@ var import_hls = __toESM(require("hls.js"), 1);
822
913
  function createHlsAdPlayer(contentVideo, options) {
823
914
  let adPlaying = false;
824
915
  let originalMutedState = false;
916
+ let originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
825
917
  const listeners = /* @__PURE__ */ new Map();
826
918
  const licenseKey = options == null ? void 0 : options.licenseKey;
827
919
  const mainHlsInstance = options == null ? void 0 : options.mainHlsInstance;
@@ -830,6 +922,8 @@ function createHlsAdPlayer(contentVideo, options) {
830
922
  let adContainerEl;
831
923
  let currentAd;
832
924
  let sessionId;
925
+ const preloadedAds = /* @__PURE__ */ new Map();
926
+ const preloadingAds = /* @__PURE__ */ new Map();
833
927
  let trackingFired = {
834
928
  impression: false,
835
929
  start: false,
@@ -1059,6 +1153,19 @@ function createHlsAdPlayer(contentVideo, options) {
1059
1153
  return null;
1060
1154
  }
1061
1155
  }
1156
+ async function fetchAndParseVastAd(vastTagUrl) {
1157
+ const response = await fetch(vastTagUrl);
1158
+ if (!response.ok) {
1159
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1160
+ }
1161
+ const vastXml = await response.text();
1162
+ console.log("[HlsAdPlayer] VAST XML received");
1163
+ console.log(
1164
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1165
+ vastXml.substring(0, 2e3)
1166
+ );
1167
+ return parseVastXml(vastXml);
1168
+ }
1062
1169
  function createAdVideoElement() {
1063
1170
  const video = document.createElement("video");
1064
1171
  video.style.position = "absolute";
@@ -1164,6 +1271,7 @@ function createHlsAdPlayer(contentVideo, options) {
1164
1271
  setAdPlayingFlag(false);
1165
1272
  const previousMutedState = contentVideo.muted;
1166
1273
  contentVideo.muted = originalMutedState;
1274
+ contentVideo.volume = originalMutedState ? 0 : originalVolume;
1167
1275
  console.log(
1168
1276
  `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1169
1277
  );
@@ -1210,17 +1318,17 @@ function createHlsAdPlayer(contentVideo, options) {
1210
1318
  }
1211
1319
  try {
1212
1320
  sessionId = generateSessionId();
1213
- const response = await fetch(vastTagUrl);
1214
- if (!response.ok) {
1215
- throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1321
+ let ad;
1322
+ if (preloadedAds.has(vastTagUrl)) {
1323
+ ad = preloadedAds.get(vastTagUrl);
1324
+ preloadedAds.delete(vastTagUrl);
1325
+ console.log(
1326
+ "[HlsAdPlayer] Using preloaded VAST response:",
1327
+ vastTagUrl
1328
+ );
1329
+ } else {
1330
+ ad = await fetchAndParseVastAd(vastTagUrl);
1216
1331
  }
1217
- const vastXml = await response.text();
1218
- console.log("[HlsAdPlayer] VAST XML received");
1219
- console.log(
1220
- "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1221
- vastXml.substring(0, 2e3)
1222
- );
1223
- const ad = parseVastXml(vastXml);
1224
1332
  if (!ad) {
1225
1333
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1226
1334
  emit("ad_error");
@@ -1239,6 +1347,37 @@ function createHlsAdPlayer(contentVideo, options) {
1239
1347
  return Promise.reject(error);
1240
1348
  }
1241
1349
  },
1350
+ async preloadAds(vastTagUrl) {
1351
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
1352
+ return Promise.resolve();
1353
+ }
1354
+ if (preloadedAds.has(vastTagUrl)) {
1355
+ return Promise.resolve();
1356
+ }
1357
+ const inflight = preloadingAds.get(vastTagUrl);
1358
+ if (inflight) {
1359
+ return inflight;
1360
+ }
1361
+ const preloadPromise = fetchAndParseVastAd(vastTagUrl).then((ad) => {
1362
+ if (ad) {
1363
+ preloadedAds.set(vastTagUrl, ad);
1364
+ console.log(
1365
+ "[HlsAdPlayer] Cached VAST response for preloading:",
1366
+ vastTagUrl
1367
+ );
1368
+ }
1369
+ }).catch((error) => {
1370
+ console.warn("[HlsAdPlayer] Failed to preload VAST response:", error);
1371
+ preloadedAds.delete(vastTagUrl);
1372
+ }).finally(() => {
1373
+ preloadingAds.delete(vastTagUrl);
1374
+ });
1375
+ preloadingAds.set(vastTagUrl, preloadPromise);
1376
+ return preloadPromise;
1377
+ },
1378
+ hasPreloadedAd(vastTagUrl) {
1379
+ return preloadedAds.has(vastTagUrl);
1380
+ },
1242
1381
  async play() {
1243
1382
  if (!currentAd) {
1244
1383
  console.warn(
@@ -1262,6 +1401,10 @@ function createHlsAdPlayer(contentVideo, options) {
1262
1401
  complete: false
1263
1402
  };
1264
1403
  const contentVolume = contentVideo.volume;
1404
+ originalVolume = Math.max(
1405
+ 0,
1406
+ Math.min(1, contentVolume || originalVolume)
1407
+ );
1265
1408
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
1266
1409
  contentVideo.pause();
1267
1410
  console.log("[HlsAdPlayer] Content paused (VOD mode)");
@@ -1274,7 +1417,7 @@ function createHlsAdPlayer(contentVideo, options) {
1274
1417
  adPlaying = true;
1275
1418
  setAdPlayingFlag(true);
1276
1419
  if (adVideoElement) {
1277
- const adVolume = originalMutedState ? 0 : contentVolume;
1420
+ const adVolume = originalMutedState ? 0 : originalVolume;
1278
1421
  adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1279
1422
  adVideoElement.muted = false;
1280
1423
  console.log(
@@ -1353,6 +1496,7 @@ function createHlsAdPlayer(contentVideo, options) {
1353
1496
  adPlaying = false;
1354
1497
  setAdPlayingFlag(false);
1355
1498
  contentVideo.muted = originalMutedState;
1499
+ contentVideo.volume = originalMutedState ? 0 : originalVolume;
1356
1500
  if (adHls) {
1357
1501
  adHls.destroy();
1358
1502
  adHls = void 0;
@@ -1369,6 +1513,8 @@ function createHlsAdPlayer(contentVideo, options) {
1369
1513
  adContainerEl = void 0;
1370
1514
  currentAd = void 0;
1371
1515
  listeners.clear();
1516
+ preloadedAds.clear();
1517
+ preloadingAds.clear();
1372
1518
  },
1373
1519
  isAdPlaying() {
1374
1520
  return adPlaying;
@@ -1392,15 +1538,20 @@ function createHlsAdPlayer(contentVideo, options) {
1392
1538
  var _a;
1393
1539
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
1394
1540
  },
1395
- updateOriginalMutedState(muted) {
1541
+ updateOriginalMutedState(muted, volume) {
1542
+ const nextVolume = typeof volume === "number" && !Number.isNaN(volume) ? Math.max(0, Math.min(1, volume)) : originalVolume;
1396
1543
  console.log(
1397
- `[HlsAdPlayer] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
1544
+ `[HlsAdPlayer] updateOriginalMutedState called: { muted: ${originalMutedState} -> ${muted}, volume: ${originalVolume} -> ${nextVolume} }`
1398
1545
  );
1399
1546
  originalMutedState = muted;
1547
+ originalVolume = nextVolume;
1400
1548
  },
1401
1549
  getOriginalMutedState() {
1402
1550
  return originalMutedState;
1403
1551
  },
1552
+ getOriginalVolume() {
1553
+ return originalVolume;
1554
+ },
1404
1555
  setAdVolume(volume) {
1405
1556
  if (adVideoElement && adPlaying) {
1406
1557
  adVideoElement.volume = Math.max(0, Math.min(1, volume));
@@ -1411,6 +1562,35 @@ function createHlsAdPlayer(contentVideo, options) {
1411
1562
  return adVideoElement.volume;
1412
1563
  }
1413
1564
  return 1;
1565
+ },
1566
+ showPlaceholder() {
1567
+ var _a;
1568
+ if (!adContainerEl) {
1569
+ const container = document.createElement("div");
1570
+ container.style.position = "absolute";
1571
+ container.style.left = "0";
1572
+ container.style.top = "0";
1573
+ container.style.right = "0";
1574
+ container.style.bottom = "0";
1575
+ container.style.display = "none";
1576
+ container.style.alignItems = "center";
1577
+ container.style.justifyContent = "center";
1578
+ container.style.pointerEvents = "none";
1579
+ container.style.zIndex = "10";
1580
+ container.style.backgroundColor = "#000";
1581
+ (_a = contentVideo.parentElement) == null ? void 0 : _a.appendChild(container);
1582
+ adContainerEl = container;
1583
+ }
1584
+ if (adContainerEl) {
1585
+ adContainerEl.style.display = "flex";
1586
+ adContainerEl.style.pointerEvents = "auto";
1587
+ }
1588
+ },
1589
+ hidePlaceholder() {
1590
+ if (adContainerEl) {
1591
+ adContainerEl.style.display = "none";
1592
+ adContainerEl.style.pointerEvents = "none";
1593
+ }
1414
1594
  }
1415
1595
  };
1416
1596
  }
@@ -1891,6 +2071,8 @@ var StormcloudVideoPlayer = class {
1891
2071
  this.bufferedSegmentsCount = 0;
1892
2072
  this.shouldAutoplayAfterBuffering = false;
1893
2073
  this.hasInitialBufferCompleted = false;
2074
+ this.adPodAllUrls = [];
2075
+ this.preloadingAdUrls = /* @__PURE__ */ new Set();
1894
2076
  initializePolyfills();
1895
2077
  const browserOverrides = getBrowserConfigOverrides();
1896
2078
  this.config = { ...config, ...browserOverrides };
@@ -2150,7 +2332,7 @@ var StormcloudVideoPlayer = class {
2150
2332
  this.video.autoplay = !!this.config.autoplay;
2151
2333
  this.video.muted = !!this.config.muted;
2152
2334
  this.ima.initialize();
2153
- this.ima.updateOriginalMutedState(this.video.muted);
2335
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2154
2336
  this.ima.on("all_ads_completed", () => {
2155
2337
  if (this.config.debugAdTiming) {
2156
2338
  console.log(
@@ -2191,6 +2373,7 @@ var StormcloudVideoPlayer = class {
2191
2373
  console.log("[StormcloudVideoPlayer] IMA content_pause event received");
2192
2374
  }
2193
2375
  this.clearAdFailsafeTimer();
2376
+ this.enforceAdHoldState();
2194
2377
  });
2195
2378
  this.ima.on("content_resume", () => {
2196
2379
  if (this.config.debugAdTiming) {
@@ -2215,9 +2398,7 @@ var StormcloudVideoPlayer = class {
2215
2398
  if (remaining > 500 && this.adPodQueue.length > 0) {
2216
2399
  const next = this.adPodQueue.shift();
2217
2400
  this.currentAdIndex++;
2218
- this.video.dataset.stormcloudAdPlaying = "true";
2219
- this.video.muted = true;
2220
- this.video.volume = 0;
2401
+ this.enforceAdHoldState();
2221
2402
  if (this.config.debugAdTiming) {
2222
2403
  console.log(
2223
2404
  `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
@@ -2829,11 +3010,16 @@ var StormcloudVideoPlayer = class {
2829
3010
  return;
2830
3011
  }
2831
3012
  if (vastTagUrls.length > 0) {
3013
+ this.adPodAllUrls = [...vastTagUrls];
3014
+ this.preloadingAdUrls.clear();
3015
+ this.logQueuedAdUrls(this.adPodAllUrls);
2832
3016
  this.inAdBreak = true;
2833
3017
  this.showAds = true;
2834
3018
  this.currentAdIndex = 0;
2835
3019
  this.totalAdsInBreak = vastTagUrls.length;
2836
3020
  this.adPodQueue = [...vastTagUrls];
3021
+ this.enforceAdHoldState();
3022
+ this.preloadUpcomingAds();
2837
3023
  if (this.config.debugAdTiming) {
2838
3024
  console.log(
2839
3025
  `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
@@ -2904,6 +3090,7 @@ var StormcloudVideoPlayer = class {
2904
3090
  const first = tags[0];
2905
3091
  const rest = tags.slice(1);
2906
3092
  this.adPodQueue = rest;
3093
+ this.enforceAdHoldState();
2907
3094
  await this.playSingleAd(first);
2908
3095
  this.inAdBreak = true;
2909
3096
  this.expectedAdBreakDurationMs = remainingMs;
@@ -3017,6 +3204,12 @@ var StormcloudVideoPlayer = class {
3017
3204
  }
3018
3205
  return;
3019
3206
  }
3207
+ const wasPreloaded = this.ima.hasPreloadedAd(vastTagUrl);
3208
+ if (wasPreloaded && this.config.debugAdTiming) {
3209
+ console.log(
3210
+ `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3211
+ );
3212
+ }
3020
3213
  if (!this.showAds) {
3021
3214
  if (this.config.debugAdTiming) {
3022
3215
  console.log(
@@ -3028,7 +3221,7 @@ var StormcloudVideoPlayer = class {
3028
3221
  }
3029
3222
  );
3030
3223
  }
3031
- this.ima.updateOriginalMutedState(this.video.muted);
3224
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3032
3225
  } else if (this.config.debugAdTiming) {
3033
3226
  console.log(
3034
3227
  `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
@@ -3037,12 +3230,14 @@ var StormcloudVideoPlayer = class {
3037
3230
  this.startAdFailsafeTimer();
3038
3231
  try {
3039
3232
  await this.ima.requestAds(vastTagUrl);
3233
+ this.preloadUpcomingAds();
3040
3234
  try {
3041
3235
  if (this.config.debugAdTiming) {
3042
3236
  console.log(
3043
3237
  "[StormcloudVideoPlayer] Ad request completed, attempting playback"
3044
3238
  );
3045
3239
  }
3240
+ this.enforceAdHoldState();
3046
3241
  await this.ima.play();
3047
3242
  if (this.config.debugAdTiming) {
3048
3243
  console.log(
@@ -3072,6 +3267,8 @@ var StormcloudVideoPlayer = class {
3072
3267
  "[StormcloudVideoPlayer] Handling ad pod completion - resuming content and hiding ad layer"
3073
3268
  );
3074
3269
  }
3270
+ this.releaseAdHoldState();
3271
+ this.preloadingAdUrls.clear();
3075
3272
  this.inAdBreak = false;
3076
3273
  this.expectedAdBreakDurationMs = void 0;
3077
3274
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3079,14 +3276,16 @@ var StormcloudVideoPlayer = class {
3079
3276
  this.clearAdStopTimer();
3080
3277
  this.clearAdFailsafeTimer();
3081
3278
  this.adPodQueue = [];
3279
+ this.adPodAllUrls = [];
3082
3280
  this.showAds = false;
3083
3281
  this.currentAdIndex = 0;
3084
3282
  this.totalAdsInBreak = 0;
3085
3283
  this.ima.stop().catch(() => {
3086
3284
  });
3087
3285
  const originalMutedState = this.ima.getOriginalMutedState();
3286
+ const originalVolume = typeof this.ima.getOriginalVolume === "function" ? this.ima.getOriginalVolume() : this.video.volume;
3088
3287
  this.video.muted = originalMutedState;
3089
- this.video.volume = originalMutedState ? 0 : 1;
3288
+ this.video.volume = originalVolume;
3090
3289
  if (this.config.debugAdTiming) {
3091
3290
  console.log(
3092
3291
  `[StormcloudVideoPlayer] Restored main video - muted: ${originalMutedState}, volume: ${this.video.volume}`
@@ -3159,6 +3358,64 @@ var StormcloudVideoPlayer = class {
3159
3358
  }
3160
3359
  return [b.vastTagUrl];
3161
3360
  }
3361
+ logQueuedAdUrls(urls) {
3362
+ if (!this.config.debugAdTiming) {
3363
+ return;
3364
+ }
3365
+ console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3366
+ }
3367
+ enforceAdHoldState() {
3368
+ this.video.dataset.stormcloudAdPlaying = "true";
3369
+ this.video.muted = true;
3370
+ this.video.volume = 0;
3371
+ if (typeof this.ima.showPlaceholder === "function") {
3372
+ this.ima.showPlaceholder();
3373
+ }
3374
+ }
3375
+ releaseAdHoldState() {
3376
+ delete this.video.dataset.stormcloudAdPlaying;
3377
+ if (typeof this.ima.hidePlaceholder === "function") {
3378
+ this.ima.hidePlaceholder();
3379
+ }
3380
+ }
3381
+ preloadUpcomingAds() {
3382
+ if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3383
+ return;
3384
+ }
3385
+ const upcoming = this.adPodQueue.slice(0, 2);
3386
+ for (const url of upcoming) {
3387
+ if (!url) continue;
3388
+ if (this.ima.hasPreloadedAd(url)) {
3389
+ this.preloadingAdUrls.delete(url);
3390
+ continue;
3391
+ }
3392
+ if (this.preloadingAdUrls.has(url)) {
3393
+ continue;
3394
+ }
3395
+ if (this.config.debugAdTiming) {
3396
+ console.log(
3397
+ `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3398
+ );
3399
+ }
3400
+ this.preloadingAdUrls.add(url);
3401
+ this.ima.preloadAds(url).then(() => {
3402
+ if (this.config.debugAdTiming) {
3403
+ console.log(
3404
+ `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3405
+ );
3406
+ }
3407
+ }).catch((error) => {
3408
+ if (this.config.debugAdTiming) {
3409
+ console.warn(
3410
+ `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3411
+ error
3412
+ );
3413
+ }
3414
+ }).finally(() => {
3415
+ this.preloadingAdUrls.delete(url);
3416
+ });
3417
+ }
3418
+ }
3162
3419
  getRemainingAdMs() {
3163
3420
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3164
3421
  return 0;
@@ -3179,7 +3436,7 @@ var StormcloudVideoPlayer = class {
3179
3436
  if (this.ima.isAdPlaying()) {
3180
3437
  const currentPerceptualState = this.isMuted();
3181
3438
  const newMutedState = !currentPerceptualState;
3182
- this.ima.updateOriginalMutedState(newMutedState);
3439
+ this.ima.updateOriginalMutedState(newMutedState, this.video.volume);
3183
3440
  this.ima.setAdVolume(newMutedState ? 0 : 1);
3184
3441
  if (this.config.debugAdTiming) {
3185
3442
  console.log(
@@ -3189,7 +3446,7 @@ var StormcloudVideoPlayer = class {
3189
3446
  }
3190
3447
  } else {
3191
3448
  this.video.muted = !this.video.muted;
3192
- this.ima.updateOriginalMutedState(this.video.muted);
3449
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3193
3450
  if (this.config.debugAdTiming) {
3194
3451
  console.log("[StormcloudVideoPlayer] Muted:", this.video.muted);
3195
3452
  }
@@ -3261,7 +3518,7 @@ var StormcloudVideoPlayer = class {
3261
3518
  }
3262
3519
  this.video.muted = muted;
3263
3520
  if (adPlaying) {
3264
- this.ima.updateOriginalMutedState(muted);
3521
+ this.ima.updateOriginalMutedState(muted, this.video.volume);
3265
3522
  this.ima.setAdVolume(muted ? 0 : 1);
3266
3523
  if (this.config.debugAdTiming) {
3267
3524
  console.log("[StormcloudVideoPlayer] setMuted applied during ad", {
@@ -3270,7 +3527,7 @@ var StormcloudVideoPlayer = class {
3270
3527
  }
3271
3528
  return;
3272
3529
  }
3273
- this.ima.updateOriginalMutedState(muted);
3530
+ this.ima.updateOriginalMutedState(muted, this.video.volume);
3274
3531
  if (this.config.debugAdTiming) {
3275
3532
  console.log("[StormcloudVideoPlayer] setMuted called:", muted);
3276
3533
  }
@@ -3310,6 +3567,9 @@ var StormcloudVideoPlayer = class {
3310
3567
  }
3311
3568
  (_a = this.hls) == null ? void 0 : _a.destroy();
3312
3569
  (_b = this.ima) == null ? void 0 : _b.destroy();
3570
+ this.releaseAdHoldState();
3571
+ this.preloadingAdUrls.clear();
3572
+ this.adPodAllUrls = [];
3313
3573
  }
3314
3574
  };
3315
3575