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
package/lib/index.d.cts CHANGED
@@ -21,6 +21,7 @@ interface StormcloudVideoPlayerConfig {
21
21
  allowNativeHls?: boolean;
22
22
  lowLatencyMode?: boolean;
23
23
  isLiveStream?: boolean;
24
+ pauseContentDuringAds?: boolean;
24
25
  driftToleranceMs?: number;
25
26
  immediateManifestAds?: boolean;
26
27
  debugAdTiming?: boolean;
@@ -4002,6 +4003,7 @@ interface BrowserInfo {
4002
4003
  declare function detectBrowser(): BrowserInfo;
4003
4004
  declare function getBrowserConfigOverrides(): {
4004
4005
  allowNativeHls?: boolean;
4006
+ pauseContentDuringAds?: boolean;
4005
4007
  };
4006
4008
  declare function supportsModernJS(): boolean;
4007
4009
  declare function logBrowserInfo(debug?: boolean): void;
package/lib/index.d.ts CHANGED
@@ -21,6 +21,7 @@ interface StormcloudVideoPlayerConfig {
21
21
  allowNativeHls?: boolean;
22
22
  lowLatencyMode?: boolean;
23
23
  isLiveStream?: boolean;
24
+ pauseContentDuringAds?: boolean;
24
25
  driftToleranceMs?: number;
25
26
  immediateManifestAds?: boolean;
26
27
  debugAdTiming?: boolean;
@@ -4002,6 +4003,7 @@ interface BrowserInfo {
4002
4003
  declare function detectBrowser(): BrowserInfo;
4003
4004
  declare function getBrowserConfigOverrides(): {
4004
4005
  allowNativeHls?: boolean;
4006
+ pauseContentDuringAds?: boolean;
4005
4007
  };
4006
4008
  declare function supportsModernJS(): boolean;
4007
4009
  declare function logBrowserInfo(debug?: boolean): void;
package/lib/index.js CHANGED
@@ -389,6 +389,11 @@ function createHlsAdPlayer(contentVideo, options) {
389
389
  var preloadingAds = /* @__PURE__ */ new Map();
390
390
  var destroyed = false;
391
391
  var pendingTimeouts = [];
392
+ var STALL_TIMEOUT_MS = 4e3;
393
+ var STALL_CHECK_INTERVAL_MS = 1e3;
394
+ var stallWatchdogId;
395
+ var lastAdProgressTime = 0;
396
+ var lastAdProgressPosition = 0;
392
397
  var trackingFired = {
393
398
  impression: false,
394
399
  start: false,
@@ -1021,6 +1026,7 @@ function createHlsAdPlayer(contentVideo, options) {
1021
1026
  if (!adVideoElement || !currentAd) return;
1022
1027
  adVideoElement.addEventListener("timeupdate", function() {
1023
1028
  if (!currentAd || !adVideoElement) return;
1029
+ noteAdProgress();
1024
1030
  var progress = adVideoElement.currentTime / currentAd.duration;
1025
1031
  if (progress >= 0.25 && !trackingFired.firstQuartile) {
1026
1032
  trackingFired.firstQuartile = true;
@@ -1036,6 +1042,7 @@ function createHlsAdPlayer(contentVideo, options) {
1036
1042
  }
1037
1043
  });
1038
1044
  adVideoElement.addEventListener("playing", function() {
1045
+ startStallWatchdog();
1039
1046
  if (!currentAd || trackingFired.start) return;
1040
1047
  trackingFired.start = true;
1041
1048
  fireTrackingPixels(currentAd.trackingUrls.start);
@@ -1176,8 +1183,52 @@ function createHlsAdPlayer(contentVideo, options) {
1176
1183
  return false;
1177
1184
  }
1178
1185
  }
1186
+ function clearStallWatchdog() {
1187
+ if (stallWatchdogId != null) {
1188
+ clearInterval(stallWatchdogId);
1189
+ stallWatchdogId = void 0;
1190
+ }
1191
+ }
1192
+ function noteAdProgress() {
1193
+ lastAdProgressTime = Date.now();
1194
+ if (adVideoElement) {
1195
+ lastAdProgressPosition = adVideoElement.currentTime;
1196
+ }
1197
+ }
1198
+ function handleAdStall() {
1199
+ console.warn("[HlsAdPlayer] Ad playback stalled (no progress) - recovering to avoid a blank frame");
1200
+ clearStallWatchdog();
1201
+ if (currentAd) {
1202
+ fireTrackingPixels(currentAd.trackingUrls.error);
1203
+ }
1204
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1205
+ return;
1206
+ }
1207
+ handleAdError();
1208
+ }
1209
+ function startStallWatchdog() {
1210
+ clearStallWatchdog();
1211
+ noteAdProgress();
1212
+ stallWatchdogId = window.setInterval(function() {
1213
+ if (!adPlaying || !adVideoElement) {
1214
+ return;
1215
+ }
1216
+ if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1217
+ noteAdProgress();
1218
+ return;
1219
+ }
1220
+ if (adVideoElement.paused) {
1221
+ noteAdProgress();
1222
+ return;
1223
+ }
1224
+ if (Date.now() - lastAdProgressTime >= STALL_TIMEOUT_MS) {
1225
+ handleAdStall();
1226
+ }
1227
+ }, STALL_CHECK_INTERVAL_MS);
1228
+ }
1179
1229
  function handleAdComplete() {
1180
1230
  console.log("[HlsAdPlayer] Handling ad completion");
1231
+ clearStallWatchdog();
1181
1232
  adPlaying = false;
1182
1233
  setAdPlayingFlag(false);
1183
1234
  contentVideo.muted = true;
@@ -1185,6 +1236,10 @@ function createHlsAdPlayer(contentVideo, options) {
1185
1236
  adContainerEl.style.display = "none";
1186
1237
  adContainerEl.style.pointerEvents = "none";
1187
1238
  }
1239
+ if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
1240
+ contentVideo.style.visibility = "visible";
1241
+ contentVideo.style.opacity = "1";
1242
+ }
1188
1243
  if (options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds) {
1189
1244
  if (contentVideo.paused) {
1190
1245
  console.log("[HlsAdPlayer] Content video paused in live mode, resuming playback");
@@ -1197,6 +1252,7 @@ function createHlsAdPlayer(contentVideo, options) {
1197
1252
  }
1198
1253
  function handleAdError() {
1199
1254
  console.log("[HlsAdPlayer] Handling ad error");
1255
+ clearStallWatchdog();
1200
1256
  adPlaying = false;
1201
1257
  setAdPlayingFlag(false);
1202
1258
  contentVideo.muted = true;
@@ -1204,6 +1260,10 @@ function createHlsAdPlayer(contentVideo, options) {
1204
1260
  adContainerEl.style.display = "none";
1205
1261
  adContainerEl.style.pointerEvents = "none";
1206
1262
  }
1263
+ if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
1264
+ contentVideo.style.visibility = "visible";
1265
+ contentVideo.style.opacity = "1";
1266
+ }
1207
1267
  emit("ad_error");
1208
1268
  }
1209
1269
  return {
@@ -1401,6 +1461,7 @@ function createHlsAdPlayer(contentVideo, options) {
1401
1461
  var previousMutedState;
1402
1462
  return _ts_generator(this, function(_state) {
1403
1463
  console.log("[HlsAdPlayer] Stopping ad");
1464
+ clearStallWatchdog();
1404
1465
  adPlaying = false;
1405
1466
  setAdPlayingFlag(false);
1406
1467
  previousMutedState = contentVideo.muted;
@@ -1439,6 +1500,7 @@ function createHlsAdPlayer(contentVideo, options) {
1439
1500
  destroy: function destroy() {
1440
1501
  console.log("[HlsAdPlayer] Destroying");
1441
1502
  destroyed = true;
1503
+ clearStallWatchdog();
1442
1504
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1443
1505
  try {
1444
1506
  for(var _iterator = pendingTimeouts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -1583,6 +1645,41 @@ function createHlsAdPlayer(contentVideo, options) {
1583
1645
  }
1584
1646
  };
1585
1647
  }
1648
+ // src/utils/devUrl.ts
1649
+ var PRIVATE_HOST_PATTERNS = [
1650
+ /^localhost$/i,
1651
+ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
1652
+ /^0\.0\.0\.0$/,
1653
+ /^::1$/,
1654
+ /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
1655
+ /^192\.168\.\d{1,3}\.\d{1,3}$/,
1656
+ /^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/
1657
+ ];
1658
+ function isLocalDevHost(hostname) {
1659
+ var host = hostname.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
1660
+ if (!host) return false;
1661
+ if (host.endsWith(".local") || host.endsWith(".localhost")) return true;
1662
+ return PRIVATE_HOST_PATTERNS.some(function(pattern) {
1663
+ return pattern.test(host);
1664
+ });
1665
+ }
1666
+ function toDevSafeUrl(rawUrl) {
1667
+ if (!rawUrl) return rawUrl !== null && rawUrl !== void 0 ? rawUrl : "";
1668
+ var url;
1669
+ try {
1670
+ url = new URL(rawUrl);
1671
+ } catch (unused) {
1672
+ return rawUrl;
1673
+ }
1674
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1675
+ return rawUrl;
1676
+ }
1677
+ if (!isLocalDevHost(url.hostname)) {
1678
+ return rawUrl;
1679
+ }
1680
+ var cleanPath = url.pathname.replace(/\/{2,}/g, "/") || "/index.html";
1681
+ return "file://".concat(cleanPath);
1682
+ }
1586
1683
  // src/sdk/pal.ts
1587
1684
  var PAL_SDK_URL = "https://imasdk.googleapis.com/pal/sdkloader/pal.js";
1588
1685
  var _nonceLoader;
@@ -1669,12 +1766,12 @@ function createPalNonceManager() {
1669
1766
  request.adWillPlayMuted = (_options_adWillPlayMuted = options.adWillPlayMuted) !== null && _options_adWillPlayMuted !== void 0 ? _options_adWillPlayMuted : false;
1670
1767
  request.playerVolume = (_options_playerVolume = options.playerVolume) !== null && _options_playerVolume !== void 0 ? _options_playerVolume : request.adWillPlayMuted ? 0 : 1;
1671
1768
  request.continuousPlayback = (_options_continuousPlayback = options.continuousPlayback) !== null && _options_continuousPlayback !== void 0 ? _options_continuousPlayback : true;
1672
- request.descriptionUrl = options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : "");
1769
+ request.descriptionUrl = toDevSafeUrl(options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : ""));
1673
1770
  request.iconsSupported = true;
1674
1771
  request.playerType = "StormcloudVideoPlayer";
1675
1772
  request.playerVersion = "1.0";
1676
1773
  request.supportedApiFrameworks = "2,7,9";
1677
- request.url = options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : "");
1774
+ request.url = toDevSafeUrl(options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : ""));
1678
1775
  request.videoWidth = options.videoWidth || 640;
1679
1776
  request.videoHeight = options.videoHeight || 480;
1680
1777
  if (options.ppid) request.ppid = options.ppid;
@@ -3821,6 +3918,7 @@ function getBrowserConfigOverrides() {
3821
3918
  var overrides = {};
3822
3919
  if (browser.isSmartTV) {
3823
3920
  overrides.allowNativeHls = true;
3921
+ overrides.pauseContentDuringAds = true;
3824
3922
  }
3825
3923
  return overrides;
3826
3924
  }
@@ -5345,7 +5443,7 @@ var AdConfigManager = /*#__PURE__*/ function() {
5345
5443
  podMaxDurationMs: podParams.maxDurationMs,
5346
5444
  isCtv: envSignals.isCtv,
5347
5445
  contentUrl: envSignals.contentUrl,
5348
- pageUrl: !envSignals.isCtv && typeof window !== "undefined" ? window.location.href : void 0,
5446
+ pageUrl: !envSignals.isCtv && typeof window !== "undefined" ? toDevSafeUrl(window.location.href) : void 0,
5349
5447
  adWillPlayMuted: adWillPlayMuted,
5350
5448
  adWillAutoPlay: !!this.config.autoplay,
5351
5449
  appId: envSignals.appId,
@@ -6733,12 +6831,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
6733
6831
  // ───────────────────────────────────────────────────────────────
6734
6832
  // IMA event listeners
6735
6833
  // ───────────────────────────────────────────────────────────────
6736
- /**
6737
- * Produces the next pod ad-request URL for the current break. Every request
6738
- * (initial and continuous-fetch top-up) carries pod macros (pmad/pmnd/pmxd,
6739
- * no ppos); pmxd tracks the remaining break time so top-up pods only ask for
6740
- * what still fits.
6741
- */ key: "nextAdRequestUrl",
6834
+ key: "nextAdRequestUrl",
6742
6835
  value: function nextAdRequestUrl(baseVastUrl) {
6743
6836
  var remaining = Math.min(this.timing.getWallClockRemainingAdMs(), this.timing.getDurationBudgetRemainingMs());
6744
6837
  var breakDurationMs = remaining > 0 ? remaining : void 0;
@@ -7867,7 +7960,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7867
7960
  if (Math.abs(this.host.video.volume - restoredVolume) > 0.01) {
7868
7961
  this.host.video.volume = restoredVolume;
7869
7962
  }
7870
- if (this.host.shouldContinueLiveStreamDuringAds()) {
7963
+ this.host.video.style.visibility = "visible";
7964
+ this.host.video.style.opacity = "1";
7965
+ if (this.host.isLiveStream()) {
7871
7966
  var liveSyncPos = this.host.getLiveSyncPosition();
7872
7967
  if (liveSyncPos != null && liveSyncPos > this.host.video.currentTime) {
7873
7968
  if (this.debug) {
@@ -7875,16 +7970,39 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7875
7970
  }
7876
7971
  this.host.video.currentTime = liveSyncPos;
7877
7972
  }
7878
- if (this.host.video.paused) {
7879
- var _this_host_video_play;
7880
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
7881
- (_this_host_video_play = this.host.video.play()) === null || _this_host_video_play === void 0 ? void 0 : _this_host_video_play.catch(function() {});
7882
- } else {
7883
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video already playing in live mode after ads");
7973
+ }
7974
+ this.resumeContentPlayback();
7975
+ }
7976
+ },
7977
+ {
7978
+ key: "resumeContentPlayback",
7979
+ value: function resumeContentPlayback() {
7980
+ var _this = this;
7981
+ var attempt = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0;
7982
+ var maxAttempts = 3;
7983
+ if (!this.host.video.paused) {
7984
+ if (this.debug && attempt === 0) {
7985
+ console.log("[StormcloudVideoPlayer] Content video already playing after ads");
7884
7986
  }
7885
- } else if (this.host.video.paused) {
7886
- var _this_host_video_play1;
7887
- (_this_host_video_play1 = this.host.video.play()) === null || _this_host_video_play1 === void 0 ? void 0 : _this_host_video_play1.catch(function() {});
7987
+ return;
7988
+ }
7989
+ if (this.debug) {
7990
+ console.log("[StormcloudVideoPlayer] Resuming content playback after ads (attempt ".concat(attempt + 1, "/").concat(maxAttempts, ")"));
7991
+ }
7992
+ var playResult = this.host.video.play();
7993
+ if (playResult && typeof playResult.catch === "function") {
7994
+ playResult.catch(function(error) {
7995
+ if (attempt + 1 >= maxAttempts) {
7996
+ if (_this.debug) {
7997
+ console.warn("[StormcloudVideoPlayer] Failed to resume content playback after ads:", error);
7998
+ }
7999
+ return;
8000
+ }
8001
+ var backoffMs = 250 * (attempt + 1);
8002
+ window.setTimeout(function() {
8003
+ _this.resumeContentPlayback(attempt + 1);
8004
+ }, backoffMs);
8005
+ });
7888
8006
  }
7889
8007
  }
7890
8008
  },
@@ -7999,6 +8117,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7999
8117
  shouldContinueLiveStreamDuringAds: function shouldContinueLiveStreamDuringAds() {
8000
8118
  return _this.shouldContinueLiveStreamDuringAds();
8001
8119
  },
8120
+ isLiveStream: function isLiveStream() {
8121
+ return _this.hlsEngine.isLiveStream;
8122
+ },
8002
8123
  getLiveSyncPosition: function getLiveSyncPosition() {
8003
8124
  return _this.hlsEngine.getLiveSyncPosition();
8004
8125
  },
@@ -8221,6 +8342,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8221
8342
  {
8222
8343
  key: "shouldContinueLiveStreamDuringAds",
8223
8344
  value: function shouldContinueLiveStreamDuringAds() {
8345
+ if (this.config.pauseContentDuringAds) {
8346
+ return false;
8347
+ }
8224
8348
  if (this.hlsEngine.isLiveStream) {
8225
8349
  return true;
8226
8350
  }