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,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-9_2sbHCg.cjs';
3
3
 
4
4
  interface HlsPlayerProps extends StormcloudVideoPlayerConfig {
5
5
  onMount?: (player: any) => void;
@@ -242,6 +242,8 @@ function createImaController(video, options) {
242
242
  let adPlaying = false;
243
243
  let originalMutedState = false;
244
244
  const listeners = /* @__PURE__ */ new Map();
245
+ const preloadedVast = /* @__PURE__ */ new Map();
246
+ const preloadingVast = /* @__PURE__ */ new Map();
245
247
  function setAdPlayingFlag(isPlaying) {
246
248
  if (isPlaying) {
247
249
  video.dataset.stormcloudAdPlaying = "true";
@@ -332,7 +334,15 @@ function createImaController(video, options) {
332
334
  let adsLoadedReject;
333
335
  function makeAdsRequest(google, vastTagUrl) {
334
336
  const adsRequest = new google.ima.AdsRequest();
335
- adsRequest.adTagUrl = vastTagUrl;
337
+ const preloadedResponse = preloadedVast.get(vastTagUrl);
338
+ if (preloadedResponse) {
339
+ adsRequest.adsResponse = preloadedResponse;
340
+ console.log(
341
+ "[IMA] Using preloaded VAST response for immediate ad request"
342
+ );
343
+ } else {
344
+ adsRequest.adTagUrl = vastTagUrl;
345
+ }
336
346
  const videoWidth = video.offsetWidth || video.clientWidth || 640;
337
347
  const videoHeight = video.offsetHeight || video.clientHeight || 360;
338
348
  adsRequest.linearAdSlotWidth = videoWidth;
@@ -342,6 +352,36 @@ function createImaController(video, options) {
342
352
  adsRequest.vastLoadTimeout = 5e3;
343
353
  console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
344
354
  adsLoader.requestAds(adsRequest);
355
+ if (preloadedResponse) {
356
+ preloadedVast.delete(vastTagUrl);
357
+ }
358
+ }
359
+ function ensurePlaceholderContainer() {
360
+ var _a;
361
+ if (adContainerEl) {
362
+ return;
363
+ }
364
+ const container = document.createElement("div");
365
+ container.style.position = "absolute";
366
+ container.style.left = "0";
367
+ container.style.top = "0";
368
+ container.style.right = "0";
369
+ container.style.bottom = "0";
370
+ container.style.display = "none";
371
+ container.style.alignItems = "center";
372
+ container.style.justifyContent = "center";
373
+ container.style.pointerEvents = "none";
374
+ container.style.zIndex = "10";
375
+ container.style.backgroundColor = "#000";
376
+ (_a = video.parentElement) == null ? void 0 : _a.appendChild(container);
377
+ adContainerEl = container;
378
+ }
379
+ async function fetchVastDocument(vastTagUrl) {
380
+ const response = await fetch(vastTagUrl, { mode: "cors" });
381
+ if (!response.ok) {
382
+ throw new Error(`Failed to preload VAST: ${response.status}`);
383
+ }
384
+ return response.text();
345
385
  }
346
386
  function destroyAdsManager() {
347
387
  if (adsManager) {
@@ -357,29 +397,16 @@ function createImaController(video, options) {
357
397
  return {
358
398
  initialize() {
359
399
  ensureImaLoaded().then(() => {
360
- var _a, _b;
400
+ var _a;
361
401
  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;
402
+ ensurePlaceholderContainer();
403
+ if (!adDisplayContainer && adContainerEl) {
377
404
  adDisplayContainer = new google.ima.AdDisplayContainer(
378
- container,
405
+ adContainerEl,
379
406
  video
380
407
  );
381
408
  try {
382
- (_b = adDisplayContainer.initialize) == null ? void 0 : _b.call(adDisplayContainer);
409
+ (_a = adDisplayContainer.initialize) == null ? void 0 : _a.call(adDisplayContainer);
383
410
  } catch {
384
411
  }
385
412
  }
@@ -683,6 +710,32 @@ function createImaController(video, options) {
683
710
  return Promise.reject(error);
684
711
  }
685
712
  },
713
+ async preloadAds(vastTagUrl) {
714
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
715
+ return Promise.resolve();
716
+ }
717
+ if (preloadedVast.has(vastTagUrl)) {
718
+ return Promise.resolve();
719
+ }
720
+ const inflight = preloadingVast.get(vastTagUrl);
721
+ if (inflight) {
722
+ return inflight;
723
+ }
724
+ const preloadPromise = fetchVastDocument(vastTagUrl).then((xml) => {
725
+ preloadedVast.set(vastTagUrl, xml);
726
+ console.log("[IMA] Cached VAST response for preloading:", vastTagUrl);
727
+ }).catch((error) => {
728
+ console.warn("[IMA] Failed to preload VAST response:", error);
729
+ preloadedVast.delete(vastTagUrl);
730
+ }).finally(() => {
731
+ preloadingVast.delete(vastTagUrl);
732
+ });
733
+ preloadingVast.set(vastTagUrl, preloadPromise);
734
+ return preloadPromise;
735
+ },
736
+ hasPreloadedAd(vastTagUrl) {
737
+ return preloadedVast.has(vastTagUrl);
738
+ },
686
739
  async play() {
687
740
  var _a, _b;
688
741
  if (!((_a = window.google) == null ? void 0 : _a.ima) || !adDisplayContainer) {
@@ -758,6 +811,8 @@ function createImaController(video, options) {
758
811
  adContainerEl = void 0;
759
812
  adDisplayContainer = void 0;
760
813
  adsLoader = void 0;
814
+ preloadedVast.clear();
815
+ preloadingVast.clear();
761
816
  },
762
817
  isAdPlaying() {
763
818
  return adPlaying;
@@ -813,6 +868,19 @@ function createImaController(video, options) {
813
868
  }
814
869
  }
815
870
  return 1;
871
+ },
872
+ showPlaceholder() {
873
+ ensurePlaceholderContainer();
874
+ if (adContainerEl) {
875
+ adContainerEl.style.display = "flex";
876
+ adContainerEl.style.pointerEvents = "auto";
877
+ }
878
+ },
879
+ hidePlaceholder() {
880
+ if (adContainerEl) {
881
+ adContainerEl.style.display = "none";
882
+ adContainerEl.style.pointerEvents = "none";
883
+ }
816
884
  }
817
885
  };
818
886
  }
@@ -830,6 +898,8 @@ function createHlsAdPlayer(contentVideo, options) {
830
898
  let adContainerEl;
831
899
  let currentAd;
832
900
  let sessionId;
901
+ const preloadedAds = /* @__PURE__ */ new Map();
902
+ const preloadingAds = /* @__PURE__ */ new Map();
833
903
  let trackingFired = {
834
904
  impression: false,
835
905
  start: false,
@@ -1059,6 +1129,19 @@ function createHlsAdPlayer(contentVideo, options) {
1059
1129
  return null;
1060
1130
  }
1061
1131
  }
1132
+ async function fetchAndParseVastAd(vastTagUrl) {
1133
+ const response = await fetch(vastTagUrl);
1134
+ if (!response.ok) {
1135
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1136
+ }
1137
+ const vastXml = await response.text();
1138
+ console.log("[HlsAdPlayer] VAST XML received");
1139
+ console.log(
1140
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1141
+ vastXml.substring(0, 2e3)
1142
+ );
1143
+ return parseVastXml(vastXml);
1144
+ }
1062
1145
  function createAdVideoElement() {
1063
1146
  const video = document.createElement("video");
1064
1147
  video.style.position = "absolute";
@@ -1210,17 +1293,17 @@ function createHlsAdPlayer(contentVideo, options) {
1210
1293
  }
1211
1294
  try {
1212
1295
  sessionId = generateSessionId();
1213
- const response = await fetch(vastTagUrl);
1214
- if (!response.ok) {
1215
- throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1296
+ let ad;
1297
+ if (preloadedAds.has(vastTagUrl)) {
1298
+ ad = preloadedAds.get(vastTagUrl);
1299
+ preloadedAds.delete(vastTagUrl);
1300
+ console.log(
1301
+ "[HlsAdPlayer] Using preloaded VAST response:",
1302
+ vastTagUrl
1303
+ );
1304
+ } else {
1305
+ ad = await fetchAndParseVastAd(vastTagUrl);
1216
1306
  }
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
1307
  if (!ad) {
1225
1308
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1226
1309
  emit("ad_error");
@@ -1239,6 +1322,37 @@ function createHlsAdPlayer(contentVideo, options) {
1239
1322
  return Promise.reject(error);
1240
1323
  }
1241
1324
  },
1325
+ async preloadAds(vastTagUrl) {
1326
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
1327
+ return Promise.resolve();
1328
+ }
1329
+ if (preloadedAds.has(vastTagUrl)) {
1330
+ return Promise.resolve();
1331
+ }
1332
+ const inflight = preloadingAds.get(vastTagUrl);
1333
+ if (inflight) {
1334
+ return inflight;
1335
+ }
1336
+ const preloadPromise = fetchAndParseVastAd(vastTagUrl).then((ad) => {
1337
+ if (ad) {
1338
+ preloadedAds.set(vastTagUrl, ad);
1339
+ console.log(
1340
+ "[HlsAdPlayer] Cached VAST response for preloading:",
1341
+ vastTagUrl
1342
+ );
1343
+ }
1344
+ }).catch((error) => {
1345
+ console.warn("[HlsAdPlayer] Failed to preload VAST response:", error);
1346
+ preloadedAds.delete(vastTagUrl);
1347
+ }).finally(() => {
1348
+ preloadingAds.delete(vastTagUrl);
1349
+ });
1350
+ preloadingAds.set(vastTagUrl, preloadPromise);
1351
+ return preloadPromise;
1352
+ },
1353
+ hasPreloadedAd(vastTagUrl) {
1354
+ return preloadedAds.has(vastTagUrl);
1355
+ },
1242
1356
  async play() {
1243
1357
  if (!currentAd) {
1244
1358
  console.warn(
@@ -1369,6 +1483,8 @@ function createHlsAdPlayer(contentVideo, options) {
1369
1483
  adContainerEl = void 0;
1370
1484
  currentAd = void 0;
1371
1485
  listeners.clear();
1486
+ preloadedAds.clear();
1487
+ preloadingAds.clear();
1372
1488
  },
1373
1489
  isAdPlaying() {
1374
1490
  return adPlaying;
@@ -1411,6 +1527,35 @@ function createHlsAdPlayer(contentVideo, options) {
1411
1527
  return adVideoElement.volume;
1412
1528
  }
1413
1529
  return 1;
1530
+ },
1531
+ showPlaceholder() {
1532
+ var _a;
1533
+ if (!adContainerEl) {
1534
+ const container = document.createElement("div");
1535
+ container.style.position = "absolute";
1536
+ container.style.left = "0";
1537
+ container.style.top = "0";
1538
+ container.style.right = "0";
1539
+ container.style.bottom = "0";
1540
+ container.style.display = "none";
1541
+ container.style.alignItems = "center";
1542
+ container.style.justifyContent = "center";
1543
+ container.style.pointerEvents = "none";
1544
+ container.style.zIndex = "10";
1545
+ container.style.backgroundColor = "#000";
1546
+ (_a = contentVideo.parentElement) == null ? void 0 : _a.appendChild(container);
1547
+ adContainerEl = container;
1548
+ }
1549
+ if (adContainerEl) {
1550
+ adContainerEl.style.display = "flex";
1551
+ adContainerEl.style.pointerEvents = "auto";
1552
+ }
1553
+ },
1554
+ hidePlaceholder() {
1555
+ if (adContainerEl) {
1556
+ adContainerEl.style.display = "none";
1557
+ adContainerEl.style.pointerEvents = "none";
1558
+ }
1414
1559
  }
1415
1560
  };
1416
1561
  }
@@ -1891,6 +2036,8 @@ var StormcloudVideoPlayer = class {
1891
2036
  this.bufferedSegmentsCount = 0;
1892
2037
  this.shouldAutoplayAfterBuffering = false;
1893
2038
  this.hasInitialBufferCompleted = false;
2039
+ this.adPodAllUrls = [];
2040
+ this.preloadingAdUrls = /* @__PURE__ */ new Set();
1894
2041
  initializePolyfills();
1895
2042
  const browserOverrides = getBrowserConfigOverrides();
1896
2043
  this.config = { ...config, ...browserOverrides };
@@ -2191,6 +2338,7 @@ var StormcloudVideoPlayer = class {
2191
2338
  console.log("[StormcloudVideoPlayer] IMA content_pause event received");
2192
2339
  }
2193
2340
  this.clearAdFailsafeTimer();
2341
+ this.enforceAdHoldState();
2194
2342
  });
2195
2343
  this.ima.on("content_resume", () => {
2196
2344
  if (this.config.debugAdTiming) {
@@ -2215,9 +2363,7 @@ var StormcloudVideoPlayer = class {
2215
2363
  if (remaining > 500 && this.adPodQueue.length > 0) {
2216
2364
  const next = this.adPodQueue.shift();
2217
2365
  this.currentAdIndex++;
2218
- this.video.dataset.stormcloudAdPlaying = "true";
2219
- this.video.muted = true;
2220
- this.video.volume = 0;
2366
+ this.enforceAdHoldState();
2221
2367
  if (this.config.debugAdTiming) {
2222
2368
  console.log(
2223
2369
  `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
@@ -2829,11 +2975,16 @@ var StormcloudVideoPlayer = class {
2829
2975
  return;
2830
2976
  }
2831
2977
  if (vastTagUrls.length > 0) {
2978
+ this.adPodAllUrls = [...vastTagUrls];
2979
+ this.preloadingAdUrls.clear();
2980
+ this.logQueuedAdUrls(this.adPodAllUrls);
2832
2981
  this.inAdBreak = true;
2833
2982
  this.showAds = true;
2834
2983
  this.currentAdIndex = 0;
2835
2984
  this.totalAdsInBreak = vastTagUrls.length;
2836
2985
  this.adPodQueue = [...vastTagUrls];
2986
+ this.enforceAdHoldState();
2987
+ this.preloadUpcomingAds();
2837
2988
  if (this.config.debugAdTiming) {
2838
2989
  console.log(
2839
2990
  `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
@@ -2904,6 +3055,7 @@ var StormcloudVideoPlayer = class {
2904
3055
  const first = tags[0];
2905
3056
  const rest = tags.slice(1);
2906
3057
  this.adPodQueue = rest;
3058
+ this.enforceAdHoldState();
2907
3059
  await this.playSingleAd(first);
2908
3060
  this.inAdBreak = true;
2909
3061
  this.expectedAdBreakDurationMs = remainingMs;
@@ -3017,6 +3169,12 @@ var StormcloudVideoPlayer = class {
3017
3169
  }
3018
3170
  return;
3019
3171
  }
3172
+ const wasPreloaded = this.ima.hasPreloadedAd(vastTagUrl);
3173
+ if (wasPreloaded && this.config.debugAdTiming) {
3174
+ console.log(
3175
+ `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3176
+ );
3177
+ }
3020
3178
  if (!this.showAds) {
3021
3179
  if (this.config.debugAdTiming) {
3022
3180
  console.log(
@@ -3037,12 +3195,14 @@ var StormcloudVideoPlayer = class {
3037
3195
  this.startAdFailsafeTimer();
3038
3196
  try {
3039
3197
  await this.ima.requestAds(vastTagUrl);
3198
+ this.preloadUpcomingAds();
3040
3199
  try {
3041
3200
  if (this.config.debugAdTiming) {
3042
3201
  console.log(
3043
3202
  "[StormcloudVideoPlayer] Ad request completed, attempting playback"
3044
3203
  );
3045
3204
  }
3205
+ this.enforceAdHoldState();
3046
3206
  await this.ima.play();
3047
3207
  if (this.config.debugAdTiming) {
3048
3208
  console.log(
@@ -3072,6 +3232,8 @@ var StormcloudVideoPlayer = class {
3072
3232
  "[StormcloudVideoPlayer] Handling ad pod completion - resuming content and hiding ad layer"
3073
3233
  );
3074
3234
  }
3235
+ this.releaseAdHoldState();
3236
+ this.preloadingAdUrls.clear();
3075
3237
  this.inAdBreak = false;
3076
3238
  this.expectedAdBreakDurationMs = void 0;
3077
3239
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3079,6 +3241,7 @@ var StormcloudVideoPlayer = class {
3079
3241
  this.clearAdStopTimer();
3080
3242
  this.clearAdFailsafeTimer();
3081
3243
  this.adPodQueue = [];
3244
+ this.adPodAllUrls = [];
3082
3245
  this.showAds = false;
3083
3246
  this.currentAdIndex = 0;
3084
3247
  this.totalAdsInBreak = 0;
@@ -3159,6 +3322,64 @@ var StormcloudVideoPlayer = class {
3159
3322
  }
3160
3323
  return [b.vastTagUrl];
3161
3324
  }
3325
+ logQueuedAdUrls(urls) {
3326
+ if (!this.config.debugAdTiming) {
3327
+ return;
3328
+ }
3329
+ console.log("[StormcloudVideoPlayer] ALL ad URLs queued:", urls);
3330
+ }
3331
+ enforceAdHoldState() {
3332
+ this.video.dataset.stormcloudAdPlaying = "true";
3333
+ this.video.muted = true;
3334
+ this.video.volume = 0;
3335
+ if (typeof this.ima.showPlaceholder === "function") {
3336
+ this.ima.showPlaceholder();
3337
+ }
3338
+ }
3339
+ releaseAdHoldState() {
3340
+ delete this.video.dataset.stormcloudAdPlaying;
3341
+ if (typeof this.ima.hidePlaceholder === "function") {
3342
+ this.ima.hidePlaceholder();
3343
+ }
3344
+ }
3345
+ preloadUpcomingAds() {
3346
+ if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3347
+ return;
3348
+ }
3349
+ const upcoming = this.adPodQueue.slice(0, 2);
3350
+ for (const url of upcoming) {
3351
+ if (!url) continue;
3352
+ if (this.ima.hasPreloadedAd(url)) {
3353
+ this.preloadingAdUrls.delete(url);
3354
+ continue;
3355
+ }
3356
+ if (this.preloadingAdUrls.has(url)) {
3357
+ continue;
3358
+ }
3359
+ if (this.config.debugAdTiming) {
3360
+ console.log(
3361
+ `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3362
+ );
3363
+ }
3364
+ this.preloadingAdUrls.add(url);
3365
+ this.ima.preloadAds(url).then(() => {
3366
+ if (this.config.debugAdTiming) {
3367
+ console.log(
3368
+ `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3369
+ );
3370
+ }
3371
+ }).catch((error) => {
3372
+ if (this.config.debugAdTiming) {
3373
+ console.warn(
3374
+ `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3375
+ error
3376
+ );
3377
+ }
3378
+ }).finally(() => {
3379
+ this.preloadingAdUrls.delete(url);
3380
+ });
3381
+ }
3382
+ }
3162
3383
  getRemainingAdMs() {
3163
3384
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3164
3385
  return 0;
@@ -3310,6 +3531,9 @@ var StormcloudVideoPlayer = class {
3310
3531
  }
3311
3532
  (_a = this.hls) == null ? void 0 : _a.destroy();
3312
3533
  (_b = this.ima) == null ? void 0 : _b.destroy();
3534
+ this.releaseAdHoldState();
3535
+ this.preloadingAdUrls.clear();
3536
+ this.adPodAllUrls = [];
3313
3537
  }
3314
3538
  };
3315
3539