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.
@@ -1,4 +1,4 @@
1
- import { S as StormcloudVideoPlayerConfig } from '../types-D1xfSdLP.cjs';
1
+ import { S as StormcloudVideoPlayerConfig } from '../types-9_2sbHCg.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;
@@ -203,6 +203,8 @@ function createImaController(video, options) {
203
203
  let adPlaying = false;
204
204
  let originalMutedState = false;
205
205
  const listeners = /* @__PURE__ */ new Map();
206
+ const preloadedVast = /* @__PURE__ */ new Map();
207
+ const preloadingVast = /* @__PURE__ */ new Map();
206
208
  function setAdPlayingFlag(isPlaying) {
207
209
  if (isPlaying) {
208
210
  video.dataset.stormcloudAdPlaying = "true";
@@ -293,7 +295,15 @@ function createImaController(video, options) {
293
295
  let adsLoadedReject;
294
296
  function makeAdsRequest(google, vastTagUrl) {
295
297
  const adsRequest = new google.ima.AdsRequest();
296
- adsRequest.adTagUrl = vastTagUrl;
298
+ const preloadedResponse = preloadedVast.get(vastTagUrl);
299
+ if (preloadedResponse) {
300
+ adsRequest.adsResponse = preloadedResponse;
301
+ console.log(
302
+ "[IMA] Using preloaded VAST response for immediate ad request"
303
+ );
304
+ } else {
305
+ adsRequest.adTagUrl = vastTagUrl;
306
+ }
297
307
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
298
308
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
299
309
  adsRequest.linearAdSlotWidth = videoWidth;
@@ -303,6 +313,36 @@ function createImaController(video, options) {
303
313
  adsRequest.vastLoadTimeout = 5e3;
304
314
  console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
305
315
  adsLoader.requestAds(adsRequest);
316
+ if (preloadedResponse) {
317
+ preloadedVast.delete(vastTagUrl);
318
+ }
319
+ }
320
+ function ensurePlaceholderContainer() {
321
+ var _a;
322
+ if (adContainerEl) {
323
+ return;
324
+ }
325
+ const container = document.createElement("div");
326
+ container.style.position = "absolute";
327
+ container.style.left = "0";
328
+ container.style.top = "0";
329
+ container.style.right = "0";
330
+ container.style.bottom = "0";
331
+ container.style.display = "none";
332
+ container.style.alignItems = "center";
333
+ container.style.justifyContent = "center";
334
+ container.style.pointerEvents = "none";
335
+ container.style.zIndex = "10";
336
+ container.style.backgroundColor = "#000";
337
+ (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
338
+ adContainerEl = container;
339
+ }
340
+ async function fetchVastDocument(vastTagUrl) {
341
+ const response = await fetch(vastTagUrl, { mode: "cors" });
342
+ if (!response.ok) {
343
+ throw new Error(`Failed to preload VAST: ${response.status}`);
344
+ }
345
+ return response.text();
306
346
  }
307
347
  function destroyAdsManager() {
308
348
  if (adsManager) {
@@ -318,29 +358,16 @@ function createImaController(video, options) {
318
358
  return {
319
359
  initialize() {
320
360
  ensureImaLoaded().then(() => {
321
- var _a, _b;
361
+ var _a;
322
362
  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;
363
+ ensurePlaceholderContainer();
364
+ if (!adDisplayContainer && adContainerEl) {
338
365
  adDisplayContainer = new google.ima.AdDisplayContainer(
339
- container,
366
+ adContainerEl,
340
367
  video
341
368
  );
342
369
  try {
343
- (_b = adDisplayContainer.initialize) == null ? void 0 : _b.call(adDisplayContainer);
370
+ (_a = adDisplayContainer.initialize) == null ? void 0 : _a.call(adDisplayContainer);
344
371
  } catch {
345
372
  }
346
373
  }
@@ -644,6 +671,32 @@ function createImaController(video, options) {
644
671
  return Promise.reject(error);
645
672
  }
646
673
  },
674
+ async preloadAds(vastTagUrl) {
675
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
676
+ return Promise.resolve();
677
+ }
678
+ if (preloadedVast.has(vastTagUrl)) {
679
+ return Promise.resolve();
680
+ }
681
+ const inflight = preloadingVast.get(vastTagUrl);
682
+ if (inflight) {
683
+ return inflight;
684
+ }
685
+ const preloadPromise = fetchVastDocument(vastTagUrl).then((xml) => {
686
+ preloadedVast.set(vastTagUrl, xml);
687
+ console.log("[IMA] Cached VAST response for preloading:", vastTagUrl);
688
+ }).catch((error) => {
689
+ console.warn("[IMA] Failed to preload VAST response:", error);
690
+ preloadedVast.delete(vastTagUrl);
691
+ }).finally(() => {
692
+ preloadingVast.delete(vastTagUrl);
693
+ });
694
+ preloadingVast.set(vastTagUrl, preloadPromise);
695
+ return preloadPromise;
696
+ },
697
+ hasPreloadedAd(vastTagUrl) {
698
+ return preloadedVast.has(vastTagUrl);
699
+ },
647
700
  async play() {
648
701
  var _a, _b;
649
702
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
@@ -719,6 +772,8 @@ function createImaController(video, options) {
719
772
  adContainerEl = void 0;
720
773
  adDisplayContainer = void 0;
721
774
  adsLoader = void 0;
775
+ preloadedVast.clear();
776
+ preloadingVast.clear();
722
777
  },
723
778
  isAdPlaying() {
724
779
  return adPlaying;
@@ -774,6 +829,19 @@ function createImaController(video, options) {
774
829
  }
775
830
  }
776
831
  return 1;
832
+ },
833
+ showPlaceholder() {
834
+ ensurePlaceholderContainer();
835
+ if (adContainerEl) {
836
+ adContainerEl.style.display = "flex";
837
+ adContainerEl.style.pointerEvents = "auto";
838
+ }
839
+ },
840
+ hidePlaceholder() {
841
+ if (adContainerEl) {
842
+ adContainerEl.style.display = "none";
843
+ adContainerEl.style.pointerEvents = "none";
844
+ }
777
845
  }
778
846
  };
779
847
  }
@@ -791,6 +859,8 @@ function createHlsAdPlayer(contentVideo, options) {
791
859
  let adContainerEl;
792
860
  let currentAd;
793
861
  let sessionId;
862
+ const preloadedAds = /* @__PURE__ */ new Map();
863
+ const preloadingAds = /* @__PURE__ */ new Map();
794
864
  let trackingFired = {
795
865
  impression: false,
796
866
  start: false,
@@ -1020,6 +1090,19 @@ function createHlsAdPlayer(contentVideo, options) {
1020
1090
  return null;
1021
1091
  }
1022
1092
  }
1093
+ async function fetchAndParseVastAd(vastTagUrl) {
1094
+ const response = await fetch(vastTagUrl);
1095
+ if (!response.ok) {
1096
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1097
+ }
1098
+ const vastXml = await response.text();
1099
+ console.log("[HlsAdPlayer] VAST XML received");
1100
+ console.log(
1101
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1102
+ vastXml.substring(0, 2e3)
1103
+ );
1104
+ return parseVastXml(vastXml);
1105
+ }
1023
1106
  function createAdVideoElement() {
1024
1107
  const video = document.createElement("video");
1025
1108
  video.style.position = "absolute";
@@ -1171,17 +1254,17 @@ function createHlsAdPlayer(contentVideo, options) {
1171
1254
  }
1172
1255
  try {
1173
1256
  sessionId = generateSessionId();
1174
- const response = await fetch(vastTagUrl);
1175
- if (!response.ok) {
1176
- throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1257
+ let ad;
1258
+ if (preloadedAds.has(vastTagUrl)) {
1259
+ ad = preloadedAds.get(vastTagUrl);
1260
+ preloadedAds.delete(vastTagUrl);
1261
+ console.log(
1262
+ "[HlsAdPlayer] Using preloaded VAST response:",
1263
+ vastTagUrl
1264
+ );
1265
+ } else {
1266
+ ad = await fetchAndParseVastAd(vastTagUrl);
1177
1267
  }
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
1268
  if (!ad) {
1186
1269
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1187
1270
  emit("ad_error");
@@ -1200,6 +1283,37 @@ function createHlsAdPlayer(contentVideo, options) {
1200
1283
  return Promise.reject(error);
1201
1284
  }
1202
1285
  },
1286
+ async preloadAds(vastTagUrl) {
1287
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
1288
+ return Promise.resolve();
1289
+ }
1290
+ if (preloadedAds.has(vastTagUrl)) {
1291
+ return Promise.resolve();
1292
+ }
1293
+ const inflight = preloadingAds.get(vastTagUrl);
1294
+ if (inflight) {
1295
+ return inflight;
1296
+ }
1297
+ const preloadPromise = fetchAndParseVastAd(vastTagUrl).then((ad) => {
1298
+ if (ad) {
1299
+ preloadedAds.set(vastTagUrl, ad);
1300
+ console.log(
1301
+ "[HlsAdPlayer] Cached VAST response for preloading:",
1302
+ vastTagUrl
1303
+ );
1304
+ }
1305
+ }).catch((error) => {
1306
+ console.warn("[HlsAdPlayer] Failed to preload VAST response:", error);
1307
+ preloadedAds.delete(vastTagUrl);
1308
+ }).finally(() => {
1309
+ preloadingAds.delete(vastTagUrl);
1310
+ });
1311
+ preloadingAds.set(vastTagUrl, preloadPromise);
1312
+ return preloadPromise;
1313
+ },
1314
+ hasPreloadedAd(vastTagUrl) {
1315
+ return preloadedAds.has(vastTagUrl);
1316
+ },
1203
1317
  async play() {
1204
1318
  if (!currentAd) {
1205
1319
  console.warn(
@@ -1330,6 +1444,8 @@ function createHlsAdPlayer(contentVideo, options) {
1330
1444
  adContainerEl = void 0;
1331
1445
  currentAd = void 0;
1332
1446
  listeners.clear();
1447
+ preloadedAds.clear();
1448
+ preloadingAds.clear();
1333
1449
  },
1334
1450
  isAdPlaying() {
1335
1451
  return adPlaying;
@@ -1372,6 +1488,35 @@ function createHlsAdPlayer(contentVideo, options) {
1372
1488
  return adVideoElement.volume;
1373
1489
  }
1374
1490
  return 1;
1491
+ },
1492
+ showPlaceholder() {
1493
+ var _a;
1494
+ if (!adContainerEl) {
1495
+ const container = document.createElement("div");
1496
+ container.style.position = "absolute";
1497
+ container.style.left = "0";
1498
+ container.style.top = "0";
1499
+ container.style.right = "0";
1500
+ container.style.bottom = "0";
1501
+ container.style.display = "none";
1502
+ container.style.alignItems = "center";
1503
+ container.style.justifyContent = "center";
1504
+ container.style.pointerEvents = "none";
1505
+ container.style.zIndex = "10";
1506
+ container.style.backgroundColor = "#000";
1507
+ (_a = contentVideo.parentElement) == null ? void 0 : _a.appendChild(container);
1508
+ adContainerEl = container;
1509
+ }
1510
+ if (adContainerEl) {
1511
+ adContainerEl.style.display = "flex";
1512
+ adContainerEl.style.pointerEvents = "auto";
1513
+ }
1514
+ },
1515
+ hidePlaceholder() {
1516
+ if (adContainerEl) {
1517
+ adContainerEl.style.display = "none";
1518
+ adContainerEl.style.pointerEvents = "none";
1519
+ }
1375
1520
  }
1376
1521
  };
1377
1522
  }
@@ -1852,6 +1997,8 @@ var StormcloudVideoPlayer = class {
1852
1997
  this.bufferedSegmentsCount = 0;
1853
1998
  this.shouldAutoplayAfterBuffering = false;
1854
1999
  this.hasInitialBufferCompleted = false;
2000
+ this.adPodAllUrls = [];
2001
+ this.preloadingAdUrls = /* @__PURE__ */ new Set();
1855
2002
  initializePolyfills();
1856
2003
  const browserOverrides = getBrowserConfigOverrides();
1857
2004
  this.config = { ...config, ...browserOverrides };
@@ -2152,6 +2299,7 @@ var StormcloudVideoPlayer = class {
2152
2299
  console.log("[StormcloudVideoPlayer] IMA content_pause event received");
2153
2300
  }
2154
2301
  this.clearAdFailsafeTimer();
2302
+ this.enforceAdHoldState();
2155
2303
  });
2156
2304
  this.ima.on("content_resume", () => {
2157
2305
  if (this.config.debugAdTiming) {
@@ -2176,9 +2324,7 @@ var StormcloudVideoPlayer = class {
2176
2324
  if (remaining > 500 && this.adPodQueue.length > 0) {
2177
2325
  const next = this.adPodQueue.shift();
2178
2326
  this.currentAdIndex++;
2179
- this.video.dataset.stormcloudAdPlaying = "true";
2180
- this.video.muted = true;
2181
- this.video.volume = 0;
2327
+ this.enforceAdHoldState();
2182
2328
  if (this.config.debugAdTiming) {
2183
2329
  console.log(
2184
2330
  `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
@@ -2790,11 +2936,16 @@ var StormcloudVideoPlayer = class {
2790
2936
  return;
2791
2937
  }
2792
2938
  if (vastTagUrls.length > 0) {
2939
+ this.adPodAllUrls = [...vastTagUrls];
2940
+ this.preloadingAdUrls.clear();
2941
+ this.logQueuedAdUrls(this.adPodAllUrls);
2793
2942
  this.inAdBreak = true;
2794
2943
  this.showAds = true;
2795
2944
  this.currentAdIndex = 0;
2796
2945
  this.totalAdsInBreak = vastTagUrls.length;
2797
2946
  this.adPodQueue = [...vastTagUrls];
2947
+ this.enforceAdHoldState();
2948
+ this.preloadUpcomingAds();
2798
2949
  if (this.config.debugAdTiming) {
2799
2950
  console.log(
2800
2951
  `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
@@ -2865,6 +3016,7 @@ var StormcloudVideoPlayer = class {
2865
3016
  const first = tags[0];
2866
3017
  const rest = tags.slice(1);
2867
3018
  this.adPodQueue = rest;
3019
+ this.enforceAdHoldState();
2868
3020
  await this.playSingleAd(first);
2869
3021
  this.inAdBreak = true;
2870
3022
  this.expectedAdBreakDurationMs = remainingMs;
@@ -2978,6 +3130,12 @@ var StormcloudVideoPlayer = class {
2978
3130
  }
2979
3131
  return;
2980
3132
  }
3133
+ const wasPreloaded = this.ima.hasPreloadedAd(vastTagUrl);
3134
+ if (wasPreloaded && this.config.debugAdTiming) {
3135
+ console.log(
3136
+ `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3137
+ );
3138
+ }
2981
3139
  if (!this.showAds) {
2982
3140
  if (this.config.debugAdTiming) {
2983
3141
  console.log(
@@ -2998,12 +3156,14 @@ var StormcloudVideoPlayer = class {
2998
3156
  this.startAdFailsafeTimer();
2999
3157
  try {
3000
3158
  await this.ima.requestAds(vastTagUrl);
3159
+ this.preloadUpcomingAds();
3001
3160
  try {
3002
3161
  if (this.config.debugAdTiming) {
3003
3162
  console.log(
3004
3163
  "[StormcloudVideoPlayer] Ad request completed, attempting playback"
3005
3164
  );
3006
3165
  }
3166
+ this.enforceAdHoldState();
3007
3167
  await this.ima.play();
3008
3168
  if (this.config.debugAdTiming) {
3009
3169
  console.log(
@@ -3033,6 +3193,8 @@ var StormcloudVideoPlayer = class {
3033
3193
  "[StormcloudVideoPlayer] Handling ad pod completion - resuming content and hiding ad layer"
3034
3194
  );
3035
3195
  }
3196
+ this.releaseAdHoldState();
3197
+ this.preloadingAdUrls.clear();
3036
3198
  this.inAdBreak = false;
3037
3199
  this.expectedAdBreakDurationMs = void 0;
3038
3200
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3040,6 +3202,7 @@ var StormcloudVideoPlayer = class {
3040
3202
  this.clearAdStopTimer();
3041
3203
  this.clearAdFailsafeTimer();
3042
3204
  this.adPodQueue = [];
3205
+ this.adPodAllUrls = [];
3043
3206
  this.showAds = false;
3044
3207
  this.currentAdIndex = 0;
3045
3208
  this.totalAdsInBreak = 0;
@@ -3120,6 +3283,64 @@ var StormcloudVideoPlayer = class {
3120
3283
  }
3121
3284
  return [b.vastTagUrl];
3122
3285
  }
3286
+ logQueuedAdUrls(urls) {
3287
+ if (!this.config.debugAdTiming) {
3288
+ return;
3289
+ }
3290
+ console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3291
+ }
3292
+ enforceAdHoldState() {
3293
+ this.video.dataset.stormcloudAdPlaying = "true";
3294
+ this.video.muted = true;
3295
+ this.video.volume = 0;
3296
+ if (typeof this.ima.showPlaceholder === "function") {
3297
+ this.ima.showPlaceholder();
3298
+ }
3299
+ }
3300
+ releaseAdHoldState() {
3301
+ delete this.video.dataset.stormcloudAdPlaying;
3302
+ if (typeof this.ima.hidePlaceholder === "function") {
3303
+ this.ima.hidePlaceholder();
3304
+ }
3305
+ }
3306
+ preloadUpcomingAds() {
3307
+ if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3308
+ return;
3309
+ }
3310
+ const upcoming = this.adPodQueue.slice(0, 2);
3311
+ for (const url of upcoming) {
3312
+ if (!url) continue;
3313
+ if (this.ima.hasPreloadedAd(url)) {
3314
+ this.preloadingAdUrls.delete(url);
3315
+ continue;
3316
+ }
3317
+ if (this.preloadingAdUrls.has(url)) {
3318
+ continue;
3319
+ }
3320
+ if (this.config.debugAdTiming) {
3321
+ console.log(
3322
+ `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3323
+ );
3324
+ }
3325
+ this.preloadingAdUrls.add(url);
3326
+ this.ima.preloadAds(url).then(() => {
3327
+ if (this.config.debugAdTiming) {
3328
+ console.log(
3329
+ `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3330
+ );
3331
+ }
3332
+ }).catch((error) => {
3333
+ if (this.config.debugAdTiming) {
3334
+ console.warn(
3335
+ `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3336
+ error
3337
+ );
3338
+ }
3339
+ }).finally(() => {
3340
+ this.preloadingAdUrls.delete(url);
3341
+ });
3342
+ }
3343
+ }
3123
3344
  getRemainingAdMs() {
3124
3345
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3125
3346
  return 0;
@@ -3271,6 +3492,9 @@ var StormcloudVideoPlayer = class {
3271
3492
  }
3272
3493
  (_a = this.hls) == null ? void 0 : _a.destroy();
3273
3494
  (_b = this.ima) == null ? void 0 : _b.destroy();
3495
+ this.releaseAdHoldState();
3496
+ this.preloadingAdUrls.clear();
3497
+ this.adPodAllUrls = [];
3274
3498
  }
3275
3499
  };
3276
3500