stormcloud-video-player 0.8.33 → 0.8.35

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.
Files changed (44) hide show
  1. package/dist/stormcloud-vp.min.js +1 -1
  2. package/lib/index.cjs +143 -19
  3. package/lib/index.cjs.map +1 -1
  4. package/lib/index.d.cts +2 -0
  5. package/lib/index.d.ts +2 -0
  6. package/lib/index.js +143 -19
  7. package/lib/index.js.map +1 -1
  8. package/lib/player/AdBreakOrchestrator.cjs +36 -16
  9. package/lib/player/AdBreakOrchestrator.cjs.map +1 -1
  10. package/lib/player/AdBreakOrchestrator.d.cts +3 -7
  11. package/lib/player/AdConfigManager.cjs +36 -1
  12. package/lib/player/AdConfigManager.cjs.map +1 -1
  13. package/lib/player/AdConfigManager.d.cts +1 -1
  14. package/lib/player/AdTimingService.d.cts +1 -1
  15. package/lib/player/HlsEngine.d.cts +1 -1
  16. package/lib/player/PlayerControls.d.cts +1 -1
  17. package/lib/player/Scte35CueManager.d.cts +1 -1
  18. package/lib/player/Scte35Parser.d.cts +1 -1
  19. package/lib/player/StormcloudVideoPlayer.cjs +143 -19
  20. package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
  21. package/lib/player/StormcloudVideoPlayer.d.cts +1 -1
  22. package/lib/player/playerTypes.d.cts +1 -1
  23. package/lib/players/HlsPlayer.cjs +143 -19
  24. package/lib/players/HlsPlayer.cjs.map +1 -1
  25. package/lib/players/HlsPlayer.d.cts +1 -1
  26. package/lib/players/index.cjs +143 -19
  27. package/lib/players/index.cjs.map +1 -1
  28. package/lib/sdk/hlsAdPlayer.cjs +62 -0
  29. package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
  30. package/lib/sdk/hlsAdPlayer.d.cts +1 -1
  31. package/lib/sdk/pal.cjs +38 -2
  32. package/lib/sdk/pal.cjs.map +1 -1
  33. package/lib/{types-CSHvCbhZ.d.cts → types-cTqIKw_D.d.cts} +1 -0
  34. package/lib/ui/StormcloudVideoPlayer.cjs +143 -19
  35. package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
  36. package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
  37. package/lib/utils/browserCompat.cjs +1 -0
  38. package/lib/utils/browserCompat.cjs.map +1 -1
  39. package/lib/utils/browserCompat.d.cts +1 -0
  40. package/lib/utils/devUrl.cjs +102 -0
  41. package/lib/utils/devUrl.cjs.map +1 -0
  42. package/lib/utils/devUrl.d.cts +4 -0
  43. package/lib/utils/tracking.d.cts +1 -1
  44. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { Component } from 'react';
2
- import { a as StormcloudVideoPlayerConfig } from '../types-CSHvCbhZ.cjs';
2
+ import { a as StormcloudVideoPlayerConfig } from '../types-cTqIKw_D.cjs';
3
3
 
4
4
  interface HlsPlayerProps extends StormcloudVideoPlayerConfig {
5
5
  onMount?: (player: any) => void;
@@ -455,6 +455,11 @@ function createHlsAdPlayer(contentVideo, options) {
455
455
  var preloadingAds = /* @__PURE__ */ new Map();
456
456
  var destroyed = false;
457
457
  var pendingTimeouts = [];
458
+ var STALL_TIMEOUT_MS = 4e3;
459
+ var STALL_CHECK_INTERVAL_MS = 1e3;
460
+ var stallWatchdogId;
461
+ var lastAdProgressTime = 0;
462
+ var lastAdProgressPosition = 0;
458
463
  var trackingFired = {
459
464
  impression: false,
460
465
  start: false,
@@ -1087,6 +1092,7 @@ function createHlsAdPlayer(contentVideo, options) {
1087
1092
  if (!adVideoElement || !currentAd) return;
1088
1093
  adVideoElement.addEventListener("timeupdate", function() {
1089
1094
  if (!currentAd || !adVideoElement) return;
1095
+ noteAdProgress();
1090
1096
  var progress = adVideoElement.currentTime / currentAd.duration;
1091
1097
  if (progress >= 0.25 && !trackingFired.firstQuartile) {
1092
1098
  trackingFired.firstQuartile = true;
@@ -1102,6 +1108,7 @@ function createHlsAdPlayer(contentVideo, options) {
1102
1108
  }
1103
1109
  });
1104
1110
  adVideoElement.addEventListener("playing", function() {
1111
+ startStallWatchdog();
1105
1112
  if (!currentAd || trackingFired.start) return;
1106
1113
  trackingFired.start = true;
1107
1114
  fireTrackingPixels(currentAd.trackingUrls.start);
@@ -1242,8 +1249,52 @@ function createHlsAdPlayer(contentVideo, options) {
1242
1249
  return false;
1243
1250
  }
1244
1251
  }
1252
+ function clearStallWatchdog() {
1253
+ if (stallWatchdogId != null) {
1254
+ clearInterval(stallWatchdogId);
1255
+ stallWatchdogId = void 0;
1256
+ }
1257
+ }
1258
+ function noteAdProgress() {
1259
+ lastAdProgressTime = Date.now();
1260
+ if (adVideoElement) {
1261
+ lastAdProgressPosition = adVideoElement.currentTime;
1262
+ }
1263
+ }
1264
+ function handleAdStall() {
1265
+ console.warn("[HlsAdPlayer] Ad playback stalled (no progress) - recovering to avoid a blank frame");
1266
+ clearStallWatchdog();
1267
+ if (currentAd) {
1268
+ fireTrackingPixels(currentAd.trackingUrls.error);
1269
+ }
1270
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1271
+ return;
1272
+ }
1273
+ handleAdError();
1274
+ }
1275
+ function startStallWatchdog() {
1276
+ clearStallWatchdog();
1277
+ noteAdProgress();
1278
+ stallWatchdogId = window.setInterval(function() {
1279
+ if (!adPlaying || !adVideoElement) {
1280
+ return;
1281
+ }
1282
+ if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1283
+ noteAdProgress();
1284
+ return;
1285
+ }
1286
+ if (adVideoElement.paused) {
1287
+ noteAdProgress();
1288
+ return;
1289
+ }
1290
+ if (Date.now() - lastAdProgressTime >= STALL_TIMEOUT_MS) {
1291
+ handleAdStall();
1292
+ }
1293
+ }, STALL_CHECK_INTERVAL_MS);
1294
+ }
1245
1295
  function handleAdComplete() {
1246
1296
  console.log("[HlsAdPlayer] Handling ad completion");
1297
+ clearStallWatchdog();
1247
1298
  adPlaying = false;
1248
1299
  setAdPlayingFlag(false);
1249
1300
  contentVideo.muted = true;
@@ -1251,6 +1302,10 @@ function createHlsAdPlayer(contentVideo, options) {
1251
1302
  adContainerEl.style.display = "none";
1252
1303
  adContainerEl.style.pointerEvents = "none";
1253
1304
  }
1305
+ if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
1306
+ contentVideo.style.visibility = "visible";
1307
+ contentVideo.style.opacity = "1";
1308
+ }
1254
1309
  if (options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds) {
1255
1310
  if (contentVideo.paused) {
1256
1311
  console.log("[HlsAdPlayer] Content video paused in live mode, resuming playback");
@@ -1263,6 +1318,7 @@ function createHlsAdPlayer(contentVideo, options) {
1263
1318
  }
1264
1319
  function handleAdError() {
1265
1320
  console.log("[HlsAdPlayer] Handling ad error");
1321
+ clearStallWatchdog();
1266
1322
  adPlaying = false;
1267
1323
  setAdPlayingFlag(false);
1268
1324
  contentVideo.muted = true;
@@ -1270,6 +1326,10 @@ function createHlsAdPlayer(contentVideo, options) {
1270
1326
  adContainerEl.style.display = "none";
1271
1327
  adContainerEl.style.pointerEvents = "none";
1272
1328
  }
1329
+ if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
1330
+ contentVideo.style.visibility = "visible";
1331
+ contentVideo.style.opacity = "1";
1332
+ }
1273
1333
  emit("ad_error");
1274
1334
  }
1275
1335
  return {
@@ -1467,6 +1527,7 @@ function createHlsAdPlayer(contentVideo, options) {
1467
1527
  var previousMutedState;
1468
1528
  return _ts_generator(this, function(_state) {
1469
1529
  console.log("[HlsAdPlayer] Stopping ad");
1530
+ clearStallWatchdog();
1470
1531
  adPlaying = false;
1471
1532
  setAdPlayingFlag(false);
1472
1533
  previousMutedState = contentVideo.muted;
@@ -1505,6 +1566,7 @@ function createHlsAdPlayer(contentVideo, options) {
1505
1566
  destroy: function destroy() {
1506
1567
  console.log("[HlsAdPlayer] Destroying");
1507
1568
  destroyed = true;
1569
+ clearStallWatchdog();
1508
1570
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1509
1571
  try {
1510
1572
  for(var _iterator = pendingTimeouts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -1649,6 +1711,41 @@ function createHlsAdPlayer(contentVideo, options) {
1649
1711
  }
1650
1712
  };
1651
1713
  }
1714
+ // src/utils/devUrl.ts
1715
+ var PRIVATE_HOST_PATTERNS = [
1716
+ /^localhost$/i,
1717
+ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
1718
+ /^0\.0\.0\.0$/,
1719
+ /^::1$/,
1720
+ /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
1721
+ /^192\.168\.\d{1,3}\.\d{1,3}$/,
1722
+ /^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/
1723
+ ];
1724
+ function isLocalDevHost(hostname) {
1725
+ var host = hostname.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
1726
+ if (!host) return false;
1727
+ if (host.endsWith(".local") || host.endsWith(".localhost")) return true;
1728
+ return PRIVATE_HOST_PATTERNS.some(function(pattern) {
1729
+ return pattern.test(host);
1730
+ });
1731
+ }
1732
+ function toDevSafeUrl(rawUrl) {
1733
+ if (!rawUrl) return rawUrl !== null && rawUrl !== void 0 ? rawUrl : "";
1734
+ var url;
1735
+ try {
1736
+ url = new URL(rawUrl);
1737
+ } catch (unused) {
1738
+ return rawUrl;
1739
+ }
1740
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1741
+ return rawUrl;
1742
+ }
1743
+ if (!isLocalDevHost(url.hostname)) {
1744
+ return rawUrl;
1745
+ }
1746
+ var cleanPath = url.pathname.replace(/\/{2,}/g, "/") || "/index.html";
1747
+ return "file://".concat(cleanPath);
1748
+ }
1652
1749
  // src/sdk/pal.ts
1653
1750
  var PAL_SDK_URL = "https://imasdk.googleapis.com/pal/sdkloader/pal.js";
1654
1751
  var _nonceLoader;
@@ -1735,12 +1832,12 @@ function createPalNonceManager() {
1735
1832
  request.adWillPlayMuted = (_options_adWillPlayMuted = options.adWillPlayMuted) !== null && _options_adWillPlayMuted !== void 0 ? _options_adWillPlayMuted : false;
1736
1833
  request.playerVolume = (_options_playerVolume = options.playerVolume) !== null && _options_playerVolume !== void 0 ? _options_playerVolume : request.adWillPlayMuted ? 0 : 1;
1737
1834
  request.continuousPlayback = (_options_continuousPlayback = options.continuousPlayback) !== null && _options_continuousPlayback !== void 0 ? _options_continuousPlayback : true;
1738
- request.descriptionUrl = options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : "");
1835
+ request.descriptionUrl = toDevSafeUrl(options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : ""));
1739
1836
  request.iconsSupported = true;
1740
1837
  request.playerType = "StormcloudVideoPlayer";
1741
1838
  request.playerVersion = "1.0";
1742
1839
  request.supportedApiFrameworks = "2,7,9";
1743
- request.url = options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : "");
1840
+ request.url = toDevSafeUrl(options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : ""));
1744
1841
  request.videoWidth = options.videoWidth || 640;
1745
1842
  request.videoHeight = options.videoHeight || 480;
1746
1843
  if (options.ppid) request.ppid = options.ppid;
@@ -3861,6 +3958,7 @@ function getBrowserConfigOverrides() {
3861
3958
  var overrides = {};
3862
3959
  if (browser.isSmartTV) {
3863
3960
  overrides.allowNativeHls = true;
3961
+ overrides.pauseContentDuringAds = true;
3864
3962
  }
3865
3963
  return overrides;
3866
3964
  }
@@ -5359,7 +5457,7 @@ var AdConfigManager = /*#__PURE__*/ function() {
5359
5457
  podMaxDurationMs: podParams.maxDurationMs,
5360
5458
  isCtv: envSignals.isCtv,
5361
5459
  contentUrl: envSignals.contentUrl,
5362
- pageUrl: !envSignals.isCtv && typeof window !== "undefined" ? window.location.href : void 0,
5460
+ pageUrl: !envSignals.isCtv && typeof window !== "undefined" ? toDevSafeUrl(window.location.href) : void 0,
5363
5461
  adWillPlayMuted: adWillPlayMuted,
5364
5462
  adWillAutoPlay: !!this.config.autoplay,
5365
5463
  appId: envSignals.appId,
@@ -6743,12 +6841,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
6743
6841
  // ───────────────────────────────────────────────────────────────
6744
6842
  // IMA event listeners
6745
6843
  // ───────────────────────────────────────────────────────────────
6746
- /**
6747
- * Produces the next pod ad-request URL for the current break. Every request
6748
- * (initial and continuous-fetch top-up) carries pod macros (pmad/pmnd/pmxd,
6749
- * no ppos); pmxd tracks the remaining break time so top-up pods only ask for
6750
- * what still fits.
6751
- */ key: "nextAdRequestUrl",
6844
+ key: "nextAdRequestUrl",
6752
6845
  value: function nextAdRequestUrl(baseVastUrl) {
6753
6846
  var remaining = Math.min(this.timing.getWallClockRemainingAdMs(), this.timing.getDurationBudgetRemainingMs());
6754
6847
  var breakDurationMs = remaining > 0 ? remaining : void 0;
@@ -7877,7 +7970,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7877
7970
  if (Math.abs(this.host.video.volume - restoredVolume) > 0.01) {
7878
7971
  this.host.video.volume = restoredVolume;
7879
7972
  }
7880
- if (this.host.shouldContinueLiveStreamDuringAds()) {
7973
+ this.host.video.style.visibility = "visible";
7974
+ this.host.video.style.opacity = "1";
7975
+ if (this.host.isLiveStream()) {
7881
7976
  var liveSyncPos = this.host.getLiveSyncPosition();
7882
7977
  if (liveSyncPos != null && liveSyncPos > this.host.video.currentTime) {
7883
7978
  if (this.debug) {
@@ -7885,16 +7980,39 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7885
7980
  }
7886
7981
  this.host.video.currentTime = liveSyncPos;
7887
7982
  }
7888
- if (this.host.video.paused) {
7889
- var _this_host_video_play;
7890
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
7891
- (_this_host_video_play = this.host.video.play()) === null || _this_host_video_play === void 0 ? void 0 : _this_host_video_play.catch(function() {});
7892
- } else {
7893
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video already playing in live mode after ads");
7983
+ }
7984
+ this.resumeContentPlayback();
7985
+ }
7986
+ },
7987
+ {
7988
+ key: "resumeContentPlayback",
7989
+ value: function resumeContentPlayback() {
7990
+ var _this = this;
7991
+ var attempt = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0;
7992
+ var maxAttempts = 3;
7993
+ if (!this.host.video.paused) {
7994
+ if (this.debug && attempt === 0) {
7995
+ console.log("[StormcloudVideoPlayer] Content video already playing after ads");
7894
7996
  }
7895
- } else if (this.host.video.paused) {
7896
- var _this_host_video_play1;
7897
- (_this_host_video_play1 = this.host.video.play()) === null || _this_host_video_play1 === void 0 ? void 0 : _this_host_video_play1.catch(function() {});
7997
+ return;
7998
+ }
7999
+ if (this.debug) {
8000
+ console.log("[StormcloudVideoPlayer] Resuming content playback after ads (attempt ".concat(attempt + 1, "/").concat(maxAttempts, ")"));
8001
+ }
8002
+ var playResult = this.host.video.play();
8003
+ if (playResult && typeof playResult.catch === "function") {
8004
+ playResult.catch(function(error) {
8005
+ if (attempt + 1 >= maxAttempts) {
8006
+ if (_this.debug) {
8007
+ console.warn("[StormcloudVideoPlayer] Failed to resume content playback after ads:", error);
8008
+ }
8009
+ return;
8010
+ }
8011
+ var backoffMs = 250 * (attempt + 1);
8012
+ window.setTimeout(function() {
8013
+ _this.resumeContentPlayback(attempt + 1);
8014
+ }, backoffMs);
8015
+ });
7898
8016
  }
7899
8017
  }
7900
8018
  },
@@ -8008,6 +8126,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8008
8126
  shouldContinueLiveStreamDuringAds: function shouldContinueLiveStreamDuringAds() {
8009
8127
  return _this.shouldContinueLiveStreamDuringAds();
8010
8128
  },
8129
+ isLiveStream: function isLiveStream() {
8130
+ return _this.hlsEngine.isLiveStream;
8131
+ },
8011
8132
  getLiveSyncPosition: function getLiveSyncPosition() {
8012
8133
  return _this.hlsEngine.getLiveSyncPosition();
8013
8134
  },
@@ -8230,6 +8351,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8230
8351
  {
8231
8352
  key: "shouldContinueLiveStreamDuringAds",
8232
8353
  value: function shouldContinueLiveStreamDuringAds() {
8354
+ if (this.config.pauseContentDuringAds) {
8355
+ return false;
8356
+ }
8233
8357
  if (this.hlsEngine.isLiveStream) {
8234
8358
  return true;
8235
8359
  }