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.cjs CHANGED
@@ -606,6 +606,11 @@ function createHlsAdPlayer(contentVideo, options) {
606
606
  var preloadingAds = /* @__PURE__ */ new Map();
607
607
  var destroyed = false;
608
608
  var pendingTimeouts = [];
609
+ var STALL_TIMEOUT_MS = 4e3;
610
+ var STALL_CHECK_INTERVAL_MS = 1e3;
611
+ var stallWatchdogId;
612
+ var lastAdProgressTime = 0;
613
+ var lastAdProgressPosition = 0;
609
614
  var trackingFired = {
610
615
  impression: false,
611
616
  start: false,
@@ -1238,6 +1243,7 @@ function createHlsAdPlayer(contentVideo, options) {
1238
1243
  if (!adVideoElement || !currentAd) return;
1239
1244
  adVideoElement.addEventListener("timeupdate", function() {
1240
1245
  if (!currentAd || !adVideoElement) return;
1246
+ noteAdProgress();
1241
1247
  var progress = adVideoElement.currentTime / currentAd.duration;
1242
1248
  if (progress >= 0.25 && !trackingFired.firstQuartile) {
1243
1249
  trackingFired.firstQuartile = true;
@@ -1253,6 +1259,7 @@ function createHlsAdPlayer(contentVideo, options) {
1253
1259
  }
1254
1260
  });
1255
1261
  adVideoElement.addEventListener("playing", function() {
1262
+ startStallWatchdog();
1256
1263
  if (!currentAd || trackingFired.start) return;
1257
1264
  trackingFired.start = true;
1258
1265
  fireTrackingPixels(currentAd.trackingUrls.start);
@@ -1393,8 +1400,52 @@ function createHlsAdPlayer(contentVideo, options) {
1393
1400
  return false;
1394
1401
  }
1395
1402
  }
1403
+ function clearStallWatchdog() {
1404
+ if (stallWatchdogId != null) {
1405
+ clearInterval(stallWatchdogId);
1406
+ stallWatchdogId = void 0;
1407
+ }
1408
+ }
1409
+ function noteAdProgress() {
1410
+ lastAdProgressTime = Date.now();
1411
+ if (adVideoElement) {
1412
+ lastAdProgressPosition = adVideoElement.currentTime;
1413
+ }
1414
+ }
1415
+ function handleAdStall() {
1416
+ console.warn("[HlsAdPlayer] Ad playback stalled (no progress) - recovering to avoid a blank frame");
1417
+ clearStallWatchdog();
1418
+ if (currentAd) {
1419
+ fireTrackingPixels(currentAd.trackingUrls.error);
1420
+ }
1421
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1422
+ return;
1423
+ }
1424
+ handleAdError();
1425
+ }
1426
+ function startStallWatchdog() {
1427
+ clearStallWatchdog();
1428
+ noteAdProgress();
1429
+ stallWatchdogId = window.setInterval(function() {
1430
+ if (!adPlaying || !adVideoElement) {
1431
+ return;
1432
+ }
1433
+ if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1434
+ noteAdProgress();
1435
+ return;
1436
+ }
1437
+ if (adVideoElement.paused) {
1438
+ noteAdProgress();
1439
+ return;
1440
+ }
1441
+ if (Date.now() - lastAdProgressTime >= STALL_TIMEOUT_MS) {
1442
+ handleAdStall();
1443
+ }
1444
+ }, STALL_CHECK_INTERVAL_MS);
1445
+ }
1396
1446
  function handleAdComplete() {
1397
1447
  console.log("[HlsAdPlayer] Handling ad completion");
1448
+ clearStallWatchdog();
1398
1449
  adPlaying = false;
1399
1450
  setAdPlayingFlag(false);
1400
1451
  contentVideo.muted = true;
@@ -1402,6 +1453,10 @@ function createHlsAdPlayer(contentVideo, options) {
1402
1453
  adContainerEl.style.display = "none";
1403
1454
  adContainerEl.style.pointerEvents = "none";
1404
1455
  }
1456
+ if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
1457
+ contentVideo.style.visibility = "visible";
1458
+ contentVideo.style.opacity = "1";
1459
+ }
1405
1460
  if (options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds) {
1406
1461
  if (contentVideo.paused) {
1407
1462
  console.log("[HlsAdPlayer] Content video paused in live mode, resuming playback");
@@ -1414,6 +1469,7 @@ function createHlsAdPlayer(contentVideo, options) {
1414
1469
  }
1415
1470
  function handleAdError() {
1416
1471
  console.log("[HlsAdPlayer] Handling ad error");
1472
+ clearStallWatchdog();
1417
1473
  adPlaying = false;
1418
1474
  setAdPlayingFlag(false);
1419
1475
  contentVideo.muted = true;
@@ -1421,6 +1477,10 @@ function createHlsAdPlayer(contentVideo, options) {
1421
1477
  adContainerEl.style.display = "none";
1422
1478
  adContainerEl.style.pointerEvents = "none";
1423
1479
  }
1480
+ if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
1481
+ contentVideo.style.visibility = "visible";
1482
+ contentVideo.style.opacity = "1";
1483
+ }
1424
1484
  emit("ad_error");
1425
1485
  }
1426
1486
  return {
@@ -1618,6 +1678,7 @@ function createHlsAdPlayer(contentVideo, options) {
1618
1678
  var previousMutedState;
1619
1679
  return _ts_generator(this, function(_state) {
1620
1680
  console.log("[HlsAdPlayer] Stopping ad");
1681
+ clearStallWatchdog();
1621
1682
  adPlaying = false;
1622
1683
  setAdPlayingFlag(false);
1623
1684
  previousMutedState = contentVideo.muted;
@@ -1656,6 +1717,7 @@ function createHlsAdPlayer(contentVideo, options) {
1656
1717
  destroy: function destroy() {
1657
1718
  console.log("[HlsAdPlayer] Destroying");
1658
1719
  destroyed = true;
1720
+ clearStallWatchdog();
1659
1721
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1660
1722
  try {
1661
1723
  for(var _iterator = pendingTimeouts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -1800,6 +1862,41 @@ function createHlsAdPlayer(contentVideo, options) {
1800
1862
  }
1801
1863
  };
1802
1864
  }
1865
+ // src/utils/devUrl.ts
1866
+ var PRIVATE_HOST_PATTERNS = [
1867
+ /^localhost$/i,
1868
+ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
1869
+ /^0\.0\.0\.0$/,
1870
+ /^::1$/,
1871
+ /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
1872
+ /^192\.168\.\d{1,3}\.\d{1,3}$/,
1873
+ /^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/
1874
+ ];
1875
+ function isLocalDevHost(hostname) {
1876
+ var host = hostname.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
1877
+ if (!host) return false;
1878
+ if (host.endsWith(".local") || host.endsWith(".localhost")) return true;
1879
+ return PRIVATE_HOST_PATTERNS.some(function(pattern) {
1880
+ return pattern.test(host);
1881
+ });
1882
+ }
1883
+ function toDevSafeUrl(rawUrl) {
1884
+ if (!rawUrl) return rawUrl !== null && rawUrl !== void 0 ? rawUrl : "";
1885
+ var url;
1886
+ try {
1887
+ url = new URL(rawUrl);
1888
+ } catch (unused) {
1889
+ return rawUrl;
1890
+ }
1891
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1892
+ return rawUrl;
1893
+ }
1894
+ if (!isLocalDevHost(url.hostname)) {
1895
+ return rawUrl;
1896
+ }
1897
+ var cleanPath = url.pathname.replace(/\/{2,}/g, "/") || "/index.html";
1898
+ return "file://".concat(cleanPath);
1899
+ }
1803
1900
  // src/sdk/pal.ts
1804
1901
  var PAL_SDK_URL = "https://imasdk.googleapis.com/pal/sdkloader/pal.js";
1805
1902
  var _nonceLoader;
@@ -1886,12 +1983,12 @@ function createPalNonceManager() {
1886
1983
  request.adWillPlayMuted = (_options_adWillPlayMuted = options.adWillPlayMuted) !== null && _options_adWillPlayMuted !== void 0 ? _options_adWillPlayMuted : false;
1887
1984
  request.playerVolume = (_options_playerVolume = options.playerVolume) !== null && _options_playerVolume !== void 0 ? _options_playerVolume : request.adWillPlayMuted ? 0 : 1;
1888
1985
  request.continuousPlayback = (_options_continuousPlayback = options.continuousPlayback) !== null && _options_continuousPlayback !== void 0 ? _options_continuousPlayback : true;
1889
- request.descriptionUrl = options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : "");
1986
+ request.descriptionUrl = toDevSafeUrl(options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : ""));
1890
1987
  request.iconsSupported = true;
1891
1988
  request.playerType = "StormcloudVideoPlayer";
1892
1989
  request.playerVersion = "1.0";
1893
1990
  request.supportedApiFrameworks = "2,7,9";
1894
- request.url = options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : "");
1991
+ request.url = toDevSafeUrl(options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : ""));
1895
1992
  request.videoWidth = options.videoWidth || 640;
1896
1993
  request.videoHeight = options.videoHeight || 480;
1897
1994
  if (options.ppid) request.ppid = options.ppid;
@@ -4036,6 +4133,7 @@ function getBrowserConfigOverrides() {
4036
4133
  var overrides = {};
4037
4134
  if (browser.isSmartTV) {
4038
4135
  overrides.allowNativeHls = true;
4136
+ overrides.pauseContentDuringAds = true;
4039
4137
  }
4040
4138
  return overrides;
4041
4139
  }
@@ -5557,7 +5655,7 @@ var AdConfigManager = /*#__PURE__*/ function() {
5557
5655
  podMaxDurationMs: podParams.maxDurationMs,
5558
5656
  isCtv: envSignals.isCtv,
5559
5657
  contentUrl: envSignals.contentUrl,
5560
- pageUrl: !envSignals.isCtv && typeof window !== "undefined" ? window.location.href : void 0,
5658
+ pageUrl: !envSignals.isCtv && typeof window !== "undefined" ? toDevSafeUrl(window.location.href) : void 0,
5561
5659
  adWillPlayMuted: adWillPlayMuted,
5562
5660
  adWillAutoPlay: !!this.config.autoplay,
5563
5661
  appId: envSignals.appId,
@@ -6941,12 +7039,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
6941
7039
  // ───────────────────────────────────────────────────────────────
6942
7040
  // IMA event listeners
6943
7041
  // ───────────────────────────────────────────────────────────────
6944
- /**
6945
- * Produces the next pod ad-request URL for the current break. Every request
6946
- * (initial and continuous-fetch top-up) carries pod macros (pmad/pmnd/pmxd,
6947
- * no ppos); pmxd tracks the remaining break time so top-up pods only ask for
6948
- * what still fits.
6949
- */ key: "nextAdRequestUrl",
7042
+ key: "nextAdRequestUrl",
6950
7043
  value: function nextAdRequestUrl(baseVastUrl) {
6951
7044
  var remaining = Math.min(this.timing.getWallClockRemainingAdMs(), this.timing.getDurationBudgetRemainingMs());
6952
7045
  var breakDurationMs = remaining > 0 ? remaining : void 0;
@@ -8075,7 +8168,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
8075
8168
  if (Math.abs(this.host.video.volume - restoredVolume) > 0.01) {
8076
8169
  this.host.video.volume = restoredVolume;
8077
8170
  }
8078
- if (this.host.shouldContinueLiveStreamDuringAds()) {
8171
+ this.host.video.style.visibility = "visible";
8172
+ this.host.video.style.opacity = "1";
8173
+ if (this.host.isLiveStream()) {
8079
8174
  var liveSyncPos = this.host.getLiveSyncPosition();
8080
8175
  if (liveSyncPos != null && liveSyncPos > this.host.video.currentTime) {
8081
8176
  if (this.debug) {
@@ -8083,16 +8178,39 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
8083
8178
  }
8084
8179
  this.host.video.currentTime = liveSyncPos;
8085
8180
  }
8086
- if (this.host.video.paused) {
8087
- var _this_host_video_play;
8088
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
8089
- (_this_host_video_play = this.host.video.play()) === null || _this_host_video_play === void 0 ? void 0 : _this_host_video_play.catch(function() {});
8090
- } else {
8091
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video already playing in live mode after ads");
8181
+ }
8182
+ this.resumeContentPlayback();
8183
+ }
8184
+ },
8185
+ {
8186
+ key: "resumeContentPlayback",
8187
+ value: function resumeContentPlayback() {
8188
+ var _this = this;
8189
+ var attempt = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0;
8190
+ var maxAttempts = 3;
8191
+ if (!this.host.video.paused) {
8192
+ if (this.debug && attempt === 0) {
8193
+ console.log("[StormcloudVideoPlayer] Content video already playing after ads");
8092
8194
  }
8093
- } else if (this.host.video.paused) {
8094
- var _this_host_video_play1;
8095
- (_this_host_video_play1 = this.host.video.play()) === null || _this_host_video_play1 === void 0 ? void 0 : _this_host_video_play1.catch(function() {});
8195
+ return;
8196
+ }
8197
+ if (this.debug) {
8198
+ console.log("[StormcloudVideoPlayer] Resuming content playback after ads (attempt ".concat(attempt + 1, "/").concat(maxAttempts, ")"));
8199
+ }
8200
+ var playResult = this.host.video.play();
8201
+ if (playResult && typeof playResult.catch === "function") {
8202
+ playResult.catch(function(error) {
8203
+ if (attempt + 1 >= maxAttempts) {
8204
+ if (_this.debug) {
8205
+ console.warn("[StormcloudVideoPlayer] Failed to resume content playback after ads:", error);
8206
+ }
8207
+ return;
8208
+ }
8209
+ var backoffMs = 250 * (attempt + 1);
8210
+ window.setTimeout(function() {
8211
+ _this.resumeContentPlayback(attempt + 1);
8212
+ }, backoffMs);
8213
+ });
8096
8214
  }
8097
8215
  }
8098
8216
  },
@@ -8206,6 +8324,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8206
8324
  shouldContinueLiveStreamDuringAds: function shouldContinueLiveStreamDuringAds() {
8207
8325
  return _this.shouldContinueLiveStreamDuringAds();
8208
8326
  },
8327
+ isLiveStream: function isLiveStream() {
8328
+ return _this.hlsEngine.isLiveStream;
8329
+ },
8209
8330
  getLiveSyncPosition: function getLiveSyncPosition() {
8210
8331
  return _this.hlsEngine.getLiveSyncPosition();
8211
8332
  },
@@ -8428,6 +8549,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8428
8549
  {
8429
8550
  key: "shouldContinueLiveStreamDuringAds",
8430
8551
  value: function shouldContinueLiveStreamDuringAds() {
8552
+ if (this.config.pauseContentDuringAds) {
8553
+ return false;
8554
+ }
8431
8555
  if (this.hlsEngine.isLiveStream) {
8432
8556
  return true;
8433
8557
  }