stormcloud-video-player 0.2.24 → 0.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -200,6 +200,8 @@ function createImaController(video, options) {
200
200
  let adPlaying = false;
201
201
  let originalMutedState = false;
202
202
  const listeners = /* @__PURE__ */ new Map();
203
+ const preloadedVast = /* @__PURE__ */ new Map();
204
+ const preloadingVast = /* @__PURE__ */ new Map();
203
205
  function setAdPlayingFlag(isPlaying) {
204
206
  if (isPlaying) {
205
207
  video.dataset.stormcloudAdPlaying = "true";
@@ -290,7 +292,15 @@ function createImaController(video, options) {
290
292
  let adsLoadedReject;
291
293
  function makeAdsRequest(google, vastTagUrl) {
292
294
  const adsRequest = new google.ima.AdsRequest();
293
- adsRequest.adTagUrl = vastTagUrl;
295
+ const preloadedResponse = preloadedVast.get(vastTagUrl);
296
+ if (preloadedResponse) {
297
+ adsRequest.adsResponse = preloadedResponse;
298
+ console.log(
299
+ "[IMA] Using preloaded VAST response for immediate ad request"
300
+ );
301
+ } else {
302
+ adsRequest.adTagUrl = vastTagUrl;
303
+ }
294
304
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
295
305
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
296
306
  adsRequest.linearAdSlotWidth = videoWidth;
@@ -300,6 +310,36 @@ function createImaController(video, options) {
300
310
  adsRequest.vastLoadTimeout = 5e3;
301
311
  console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
302
312
  adsLoader.requestAds(adsRequest);
313
+ if (preloadedResponse) {
314
+ preloadedVast.delete(vastTagUrl);
315
+ }
316
+ }
317
+ function ensurePlaceholderContainer() {
318
+ var _a;
319
+ if (adContainerEl) {
320
+ return;
321
+ }
322
+ const container = document.createElement("div");
323
+ container.style.position = "absolute";
324
+ container.style.left = "0";
325
+ container.style.top = "0";
326
+ container.style.right = "0";
327
+ container.style.bottom = "0";
328
+ container.style.display = "none";
329
+ container.style.alignItems = "center";
330
+ container.style.justifyContent = "center";
331
+ container.style.pointerEvents = "none";
332
+ container.style.zIndex = "10";
333
+ container.style.backgroundColor = "#000";
334
+ (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
335
+ adContainerEl = container;
336
+ }
337
+ async function fetchVastDocument(vastTagUrl) {
338
+ const response = await fetch(vastTagUrl, { mode: "cors" });
339
+ if (!response.ok) {
340
+ throw new Error(`Failed to preload VAST: ${response.status}`);
341
+ }
342
+ return response.text();
303
343
  }
304
344
  function destroyAdsManager() {
305
345
  if (adsManager) {
@@ -315,29 +355,16 @@ function createImaController(video, options) {
315
355
  return {
316
356
  initialize() {
317
357
  ensureImaLoaded().then(() => {
318
- var _a, _b;
358
+ var _a;
319
359
  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;
360
+ ensurePlaceholderContainer();
361
+ if (!adDisplayContainer && adContainerEl) {
335
362
  adDisplayContainer = new google.ima.AdDisplayContainer(
336
- container,
363
+ adContainerEl,
337
364
  video
338
365
  );
339
366
  try {
340
- (_b = adDisplayContainer.initialize) == null ? void 0 : _b.call(adDisplayContainer);
367
+ (_a = adDisplayContainer.initialize) == null ? void 0 : _a.call(adDisplayContainer);
341
368
  } catch {
342
369
  }
343
370
  }
@@ -641,6 +668,32 @@ function createImaController(video, options) {
641
668
  return Promise.reject(error);
642
669
  }
643
670
  },
671
+ async preloadAds(vastTagUrl) {
672
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
673
+ return Promise.resolve();
674
+ }
675
+ if (preloadedVast.has(vastTagUrl)) {
676
+ return Promise.resolve();
677
+ }
678
+ const inflight = preloadingVast.get(vastTagUrl);
679
+ if (inflight) {
680
+ return inflight;
681
+ }
682
+ const preloadPromise = fetchVastDocument(vastTagUrl).then((xml) => {
683
+ preloadedVast.set(vastTagUrl, xml);
684
+ console.log("[IMA] Cached VAST response for preloading:", vastTagUrl);
685
+ }).catch((error) => {
686
+ console.warn("[IMA] Failed to preload VAST response:", error);
687
+ preloadedVast.delete(vastTagUrl);
688
+ }).finally(() => {
689
+ preloadingVast.delete(vastTagUrl);
690
+ });
691
+ preloadingVast.set(vastTagUrl, preloadPromise);
692
+ return preloadPromise;
693
+ },
694
+ hasPreloadedAd(vastTagUrl) {
695
+ return preloadedVast.has(vastTagUrl);
696
+ },
644
697
  async play() {
645
698
  var _a, _b;
646
699
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
@@ -716,6 +769,8 @@ function createImaController(video, options) {
716
769
  adContainerEl = void 0;
717
770
  adDisplayContainer = void 0;
718
771
  adsLoader = void 0;
772
+ preloadedVast.clear();
773
+ preloadingVast.clear();
719
774
  },
720
775
  isAdPlaying() {
721
776
  return adPlaying;
@@ -771,6 +826,19 @@ function createImaController(video, options) {
771
826
  }
772
827
  }
773
828
  return 1;
829
+ },
830
+ showPlaceholder() {
831
+ ensurePlaceholderContainer();
832
+ if (adContainerEl) {
833
+ adContainerEl.style.display = "flex";
834
+ adContainerEl.style.pointerEvents = "auto";
835
+ }
836
+ },
837
+ hidePlaceholder() {
838
+ if (adContainerEl) {
839
+ adContainerEl.style.display = "none";
840
+ adContainerEl.style.pointerEvents = "none";
841
+ }
774
842
  }
775
843
  };
776
844
  }
@@ -788,6 +856,8 @@ function createHlsAdPlayer(contentVideo, options) {
788
856
  let adContainerEl;
789
857
  let currentAd;
790
858
  let sessionId;
859
+ const preloadedAds = /* @__PURE__ */ new Map();
860
+ const preloadingAds = /* @__PURE__ */ new Map();
791
861
  let trackingFired = {
792
862
  impression: false,
793
863
  start: false,
@@ -1017,6 +1087,19 @@ function createHlsAdPlayer(contentVideo, options) {
1017
1087
  return null;
1018
1088
  }
1019
1089
  }
1090
+ async function fetchAndParseVastAd(vastTagUrl) {
1091
+ const response = await fetch(vastTagUrl);
1092
+ if (!response.ok) {
1093
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1094
+ }
1095
+ const vastXml = await response.text();
1096
+ console.log("[HlsAdPlayer] VAST XML received");
1097
+ console.log(
1098
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1099
+ vastXml.substring(0, 2e3)
1100
+ );
1101
+ return parseVastXml(vastXml);
1102
+ }
1020
1103
  function createAdVideoElement() {
1021
1104
  const video = document.createElement("video");
1022
1105
  video.style.position = "absolute";
@@ -1168,17 +1251,17 @@ function createHlsAdPlayer(contentVideo, options) {
1168
1251
  }
1169
1252
  try {
1170
1253
  sessionId = generateSessionId();
1171
- const response = await fetch(vastTagUrl);
1172
- if (!response.ok) {
1173
- throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1254
+ let ad;
1255
+ if (preloadedAds.has(vastTagUrl)) {
1256
+ ad = preloadedAds.get(vastTagUrl);
1257
+ preloadedAds.delete(vastTagUrl);
1258
+ console.log(
1259
+ "[HlsAdPlayer] Using preloaded VAST response:",
1260
+ vastTagUrl
1261
+ );
1262
+ } else {
1263
+ ad = await fetchAndParseVastAd(vastTagUrl);
1174
1264
  }
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
1265
  if (!ad) {
1183
1266
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1184
1267
  emit("ad_error");
@@ -1197,6 +1280,37 @@ function createHlsAdPlayer(contentVideo, options) {
1197
1280
  return Promise.reject(error);
1198
1281
  }
1199
1282
  },
1283
+ async preloadAds(vastTagUrl) {
1284
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
1285
+ return Promise.resolve();
1286
+ }
1287
+ if (preloadedAds.has(vastTagUrl)) {
1288
+ return Promise.resolve();
1289
+ }
1290
+ const inflight = preloadingAds.get(vastTagUrl);
1291
+ if (inflight) {
1292
+ return inflight;
1293
+ }
1294
+ const preloadPromise = fetchAndParseVastAd(vastTagUrl).then((ad) => {
1295
+ if (ad) {
1296
+ preloadedAds.set(vastTagUrl, ad);
1297
+ console.log(
1298
+ "[HlsAdPlayer] Cached VAST response for preloading:",
1299
+ vastTagUrl
1300
+ );
1301
+ }
1302
+ }).catch((error) => {
1303
+ console.warn("[HlsAdPlayer] Failed to preload VAST response:", error);
1304
+ preloadedAds.delete(vastTagUrl);
1305
+ }).finally(() => {
1306
+ preloadingAds.delete(vastTagUrl);
1307
+ });
1308
+ preloadingAds.set(vastTagUrl, preloadPromise);
1309
+ return preloadPromise;
1310
+ },
1311
+ hasPreloadedAd(vastTagUrl) {
1312
+ return preloadedAds.has(vastTagUrl);
1313
+ },
1200
1314
  async play() {
1201
1315
  if (!currentAd) {
1202
1316
  console.warn(
@@ -1327,6 +1441,8 @@ function createHlsAdPlayer(contentVideo, options) {
1327
1441
  adContainerEl = void 0;
1328
1442
  currentAd = void 0;
1329
1443
  listeners.clear();
1444
+ preloadedAds.clear();
1445
+ preloadingAds.clear();
1330
1446
  },
1331
1447
  isAdPlaying() {
1332
1448
  return adPlaying;
@@ -1369,6 +1485,35 @@ function createHlsAdPlayer(contentVideo, options) {
1369
1485
  return adVideoElement.volume;
1370
1486
  }
1371
1487
  return 1;
1488
+ },
1489
+ showPlaceholder() {
1490
+ var _a;
1491
+ if (!adContainerEl) {
1492
+ const container = document.createElement("div");
1493
+ container.style.position = "absolute";
1494
+ container.style.left = "0";
1495
+ container.style.top = "0";
1496
+ container.style.right = "0";
1497
+ container.style.bottom = "0";
1498
+ container.style.display = "none";
1499
+ container.style.alignItems = "center";
1500
+ container.style.justifyContent = "center";
1501
+ container.style.pointerEvents = "none";
1502
+ container.style.zIndex = "10";
1503
+ container.style.backgroundColor = "#000";
1504
+ (_a = contentVideo.parentElement) == null ? void 0 : _a.appendChild(container);
1505
+ adContainerEl = container;
1506
+ }
1507
+ if (adContainerEl) {
1508
+ adContainerEl.style.display = "flex";
1509
+ adContainerEl.style.pointerEvents = "auto";
1510
+ }
1511
+ },
1512
+ hidePlaceholder() {
1513
+ if (adContainerEl) {
1514
+ adContainerEl.style.display = "none";
1515
+ adContainerEl.style.pointerEvents = "none";
1516
+ }
1372
1517
  }
1373
1518
  };
1374
1519
  }
@@ -1849,6 +1994,8 @@ var StormcloudVideoPlayer = class {
1849
1994
  this.bufferedSegmentsCount = 0;
1850
1995
  this.shouldAutoplayAfterBuffering = false;
1851
1996
  this.hasInitialBufferCompleted = false;
1997
+ this.adPodAllUrls = [];
1998
+ this.preloadingAdUrls = /* @__PURE__ */ new Set();
1852
1999
  initializePolyfills();
1853
2000
  const browserOverrides = getBrowserConfigOverrides();
1854
2001
  this.config = { ...config, ...browserOverrides };
@@ -2149,6 +2296,7 @@ var StormcloudVideoPlayer = class {
2149
2296
  console.log("[StormcloudVideoPlayer] IMA content_pause event received");
2150
2297
  }
2151
2298
  this.clearAdFailsafeTimer();
2299
+ this.enforceAdHoldState();
2152
2300
  });
2153
2301
  this.ima.on("content_resume", () => {
2154
2302
  if (this.config.debugAdTiming) {
@@ -2173,9 +2321,7 @@ var StormcloudVideoPlayer = class {
2173
2321
  if (remaining > 500 && this.adPodQueue.length > 0) {
2174
2322
  const next = this.adPodQueue.shift();
2175
2323
  this.currentAdIndex++;
2176
- this.video.dataset.stormcloudAdPlaying = "true";
2177
- this.video.muted = true;
2178
- this.video.volume = 0;
2324
+ this.enforceAdHoldState();
2179
2325
  if (this.config.debugAdTiming) {
2180
2326
  console.log(
2181
2327
  `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
@@ -2787,11 +2933,16 @@ var StormcloudVideoPlayer = class {
2787
2933
  return;
2788
2934
  }
2789
2935
  if (vastTagUrls.length > 0) {
2936
+ this.adPodAllUrls = [...vastTagUrls];
2937
+ this.preloadingAdUrls.clear();
2938
+ this.logQueuedAdUrls(this.adPodAllUrls);
2790
2939
  this.inAdBreak = true;
2791
2940
  this.showAds = true;
2792
2941
  this.currentAdIndex = 0;
2793
2942
  this.totalAdsInBreak = vastTagUrls.length;
2794
2943
  this.adPodQueue = [...vastTagUrls];
2944
+ this.enforceAdHoldState();
2945
+ this.preloadUpcomingAds();
2795
2946
  if (this.config.debugAdTiming) {
2796
2947
  console.log(
2797
2948
  `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
@@ -2862,6 +3013,7 @@ var StormcloudVideoPlayer = class {
2862
3013
  const first = tags[0];
2863
3014
  const rest = tags.slice(1);
2864
3015
  this.adPodQueue = rest;
3016
+ this.enforceAdHoldState();
2865
3017
  await this.playSingleAd(first);
2866
3018
  this.inAdBreak = true;
2867
3019
  this.expectedAdBreakDurationMs = remainingMs;
@@ -2975,6 +3127,12 @@ var StormcloudVideoPlayer = class {
2975
3127
  }
2976
3128
  return;
2977
3129
  }
3130
+ const wasPreloaded = this.ima.hasPreloadedAd(vastTagUrl);
3131
+ if (wasPreloaded && this.config.debugAdTiming) {
3132
+ console.log(
3133
+ `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3134
+ );
3135
+ }
2978
3136
  if (!this.showAds) {
2979
3137
  if (this.config.debugAdTiming) {
2980
3138
  console.log(
@@ -2995,12 +3153,14 @@ var StormcloudVideoPlayer = class {
2995
3153
  this.startAdFailsafeTimer();
2996
3154
  try {
2997
3155
  await this.ima.requestAds(vastTagUrl);
3156
+ this.preloadUpcomingAds();
2998
3157
  try {
2999
3158
  if (this.config.debugAdTiming) {
3000
3159
  console.log(
3001
3160
  "[StormcloudVideoPlayer] Ad request completed, attempting playback"
3002
3161
  );
3003
3162
  }
3163
+ this.enforceAdHoldState();
3004
3164
  await this.ima.play();
3005
3165
  if (this.config.debugAdTiming) {
3006
3166
  console.log(
@@ -3030,6 +3190,8 @@ var StormcloudVideoPlayer = class {
3030
3190
  "[StormcloudVideoPlayer] Handling ad pod completion - resuming content and hiding ad layer"
3031
3191
  );
3032
3192
  }
3193
+ this.releaseAdHoldState();
3194
+ this.preloadingAdUrls.clear();
3033
3195
  this.inAdBreak = false;
3034
3196
  this.expectedAdBreakDurationMs = void 0;
3035
3197
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3037,6 +3199,7 @@ var StormcloudVideoPlayer = class {
3037
3199
  this.clearAdStopTimer();
3038
3200
  this.clearAdFailsafeTimer();
3039
3201
  this.adPodQueue = [];
3202
+ this.adPodAllUrls = [];
3040
3203
  this.showAds = false;
3041
3204
  this.currentAdIndex = 0;
3042
3205
  this.totalAdsInBreak = 0;
@@ -3117,6 +3280,64 @@ var StormcloudVideoPlayer = class {
3117
3280
  }
3118
3281
  return [b.vastTagUrl];
3119
3282
  }
3283
+ logQueuedAdUrls(urls) {
3284
+ if (!this.config.debugAdTiming) {
3285
+ return;
3286
+ }
3287
+ console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3288
+ }
3289
+ enforceAdHoldState() {
3290
+ this.video.dataset.stormcloudAdPlaying = "true";
3291
+ this.video.muted = true;
3292
+ this.video.volume = 0;
3293
+ if (typeof this.ima.showPlaceholder === "function") {
3294
+ this.ima.showPlaceholder();
3295
+ }
3296
+ }
3297
+ releaseAdHoldState() {
3298
+ delete this.video.dataset.stormcloudAdPlaying;
3299
+ if (typeof this.ima.hidePlaceholder === "function") {
3300
+ this.ima.hidePlaceholder();
3301
+ }
3302
+ }
3303
+ preloadUpcomingAds() {
3304
+ if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3305
+ return;
3306
+ }
3307
+ const upcoming = this.adPodQueue.slice(0, 2);
3308
+ for (const url of upcoming) {
3309
+ if (!url) continue;
3310
+ if (this.ima.hasPreloadedAd(url)) {
3311
+ this.preloadingAdUrls.delete(url);
3312
+ continue;
3313
+ }
3314
+ if (this.preloadingAdUrls.has(url)) {
3315
+ continue;
3316
+ }
3317
+ if (this.config.debugAdTiming) {
3318
+ console.log(
3319
+ `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3320
+ );
3321
+ }
3322
+ this.preloadingAdUrls.add(url);
3323
+ this.ima.preloadAds(url).then(() => {
3324
+ if (this.config.debugAdTiming) {
3325
+ console.log(
3326
+ `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3327
+ );
3328
+ }
3329
+ }).catch((error) => {
3330
+ if (this.config.debugAdTiming) {
3331
+ console.warn(
3332
+ `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3333
+ error
3334
+ );
3335
+ }
3336
+ }).finally(() => {
3337
+ this.preloadingAdUrls.delete(url);
3338
+ });
3339
+ }
3340
+ }
3120
3341
  getRemainingAdMs() {
3121
3342
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3122
3343
  return 0;
@@ -3268,6 +3489,9 @@ var StormcloudVideoPlayer = class {
3268
3489
  }
3269
3490
  (_a = this.hls) == null ? void 0 : _a.destroy();
3270
3491
  (_b = this.ima) == null ? void 0 : _b.destroy();
3492
+ this.releaseAdHoldState();
3493
+ this.preloadingAdUrls.clear();
3494
+ this.adPodAllUrls = [];
3271
3495
  }
3272
3496
  };
3273
3497
  // Annotate the CommonJS export names for ESM import in node: