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,4 +1,4 @@
1
- import { b as AdBreak, a as StormcloudVideoPlayerConfig, A as AdController } from '../types-CSHvCbhZ.cjs';
1
+ import { b as AdBreak, a as StormcloudVideoPlayerConfig, A as AdController } from '../types-cTqIKw_D.cjs';
2
2
  import { PalNonceManager } from '../sdk/pal.cjs';
3
3
  import { VastConsentSignals } from '../utils/vastMacros.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { a as StormcloudVideoPlayerConfig, S as Scte35Marker, A as AdController } from '../types-CSHvCbhZ.cjs';
1
+ import { a as StormcloudVideoPlayerConfig, S as Scte35Marker, A as AdController } from '../types-cTqIKw_D.cjs';
2
2
 
3
3
  interface AdTimingCallbacks {
4
4
  onAdStopTimerFired(): void;
@@ -1,5 +1,5 @@
1
1
  import Hls from 'hls.js';
2
- import { a as StormcloudVideoPlayerConfig, S as Scte35Marker } from '../types-CSHvCbhZ.cjs';
2
+ import { a as StormcloudVideoPlayerConfig, S as Scte35Marker } from '../types-cTqIKw_D.cjs';
3
3
  import { Scte35CueContext } from './playerTypes.cjs';
4
4
  import { Scte35CueManager } from './Scte35CueManager.cjs';
5
5
  import { AdTimingService } from './AdTimingService.cjs';
@@ -1,4 +1,4 @@
1
- import { A as AdController } from '../types-CSHvCbhZ.cjs';
1
+ import { A as AdController } from '../types-cTqIKw_D.cjs';
2
2
 
3
3
  declare function getAdAudioVolume(adPlayer: AdController): number;
4
4
  declare function toggleMute(video: HTMLVideoElement, adPlayer: AdController, debug: boolean): void;
@@ -1,4 +1,4 @@
1
- import { S as Scte35Marker } from '../types-CSHvCbhZ.cjs';
1
+ import { S as Scte35Marker } from '../types-cTqIKw_D.cjs';
2
2
  import { Scte35CueSource, Scte35CueReadiness, Scte35CueContext } from './playerTypes.cjs';
3
3
 
4
4
  interface Scte35CueManagerCallbacks {
@@ -1,4 +1,4 @@
1
- import { S as Scte35Marker, I as Id3TagInfo } from '../types-CSHvCbhZ.cjs';
1
+ import { S as Scte35Marker, I as Id3TagInfo } from '../types-cTqIKw_D.cjs';
2
2
 
3
3
  declare function parseCueOutDuration(value: string): number | undefined;
4
4
  declare function parseCueOutCont(value: string): {
@@ -368,6 +368,11 @@ function createHlsAdPlayer(contentVideo, options) {
368
368
  var preloadingAds = /* @__PURE__ */ new Map();
369
369
  var destroyed = false;
370
370
  var pendingTimeouts = [];
371
+ var STALL_TIMEOUT_MS = 4e3;
372
+ var STALL_CHECK_INTERVAL_MS = 1e3;
373
+ var stallWatchdogId;
374
+ var lastAdProgressTime = 0;
375
+ var lastAdProgressPosition = 0;
371
376
  var trackingFired = {
372
377
  impression: false,
373
378
  start: false,
@@ -1000,6 +1005,7 @@ function createHlsAdPlayer(contentVideo, options) {
1000
1005
  if (!adVideoElement || !currentAd) return;
1001
1006
  adVideoElement.addEventListener("timeupdate", function() {
1002
1007
  if (!currentAd || !adVideoElement) return;
1008
+ noteAdProgress();
1003
1009
  var progress = adVideoElement.currentTime / currentAd.duration;
1004
1010
  if (progress >= 0.25 && !trackingFired.firstQuartile) {
1005
1011
  trackingFired.firstQuartile = true;
@@ -1015,6 +1021,7 @@ function createHlsAdPlayer(contentVideo, options) {
1015
1021
  }
1016
1022
  });
1017
1023
  adVideoElement.addEventListener("playing", function() {
1024
+ startStallWatchdog();
1018
1025
  if (!currentAd || trackingFired.start) return;
1019
1026
  trackingFired.start = true;
1020
1027
  fireTrackingPixels(currentAd.trackingUrls.start);
@@ -1155,8 +1162,52 @@ function createHlsAdPlayer(contentVideo, options) {
1155
1162
  return false;
1156
1163
  }
1157
1164
  }
1165
+ function clearStallWatchdog() {
1166
+ if (stallWatchdogId != null) {
1167
+ clearInterval(stallWatchdogId);
1168
+ stallWatchdogId = void 0;
1169
+ }
1170
+ }
1171
+ function noteAdProgress() {
1172
+ lastAdProgressTime = Date.now();
1173
+ if (adVideoElement) {
1174
+ lastAdProgressPosition = adVideoElement.currentTime;
1175
+ }
1176
+ }
1177
+ function handleAdStall() {
1178
+ console.warn("[HlsAdPlayer] Ad playback stalled (no progress) - recovering to avoid a blank frame");
1179
+ clearStallWatchdog();
1180
+ if (currentAd) {
1181
+ fireTrackingPixels(currentAd.trackingUrls.error);
1182
+ }
1183
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1184
+ return;
1185
+ }
1186
+ handleAdError();
1187
+ }
1188
+ function startStallWatchdog() {
1189
+ clearStallWatchdog();
1190
+ noteAdProgress();
1191
+ stallWatchdogId = window.setInterval(function() {
1192
+ if (!adPlaying || !adVideoElement) {
1193
+ return;
1194
+ }
1195
+ if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1196
+ noteAdProgress();
1197
+ return;
1198
+ }
1199
+ if (adVideoElement.paused) {
1200
+ noteAdProgress();
1201
+ return;
1202
+ }
1203
+ if (Date.now() - lastAdProgressTime >= STALL_TIMEOUT_MS) {
1204
+ handleAdStall();
1205
+ }
1206
+ }, STALL_CHECK_INTERVAL_MS);
1207
+ }
1158
1208
  function handleAdComplete() {
1159
1209
  console.log("[HlsAdPlayer] Handling ad completion");
1210
+ clearStallWatchdog();
1160
1211
  adPlaying = false;
1161
1212
  setAdPlayingFlag(false);
1162
1213
  contentVideo.muted = true;
@@ -1164,6 +1215,10 @@ function createHlsAdPlayer(contentVideo, options) {
1164
1215
  adContainerEl.style.display = "none";
1165
1216
  adContainerEl.style.pointerEvents = "none";
1166
1217
  }
1218
+ if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
1219
+ contentVideo.style.visibility = "visible";
1220
+ contentVideo.style.opacity = "1";
1221
+ }
1167
1222
  if (options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds) {
1168
1223
  if (contentVideo.paused) {
1169
1224
  console.log("[HlsAdPlayer] Content video paused in live mode, resuming playback");
@@ -1176,6 +1231,7 @@ function createHlsAdPlayer(contentVideo, options) {
1176
1231
  }
1177
1232
  function handleAdError() {
1178
1233
  console.log("[HlsAdPlayer] Handling ad error");
1234
+ clearStallWatchdog();
1179
1235
  adPlaying = false;
1180
1236
  setAdPlayingFlag(false);
1181
1237
  contentVideo.muted = true;
@@ -1183,6 +1239,10 @@ function createHlsAdPlayer(contentVideo, options) {
1183
1239
  adContainerEl.style.display = "none";
1184
1240
  adContainerEl.style.pointerEvents = "none";
1185
1241
  }
1242
+ if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
1243
+ contentVideo.style.visibility = "visible";
1244
+ contentVideo.style.opacity = "1";
1245
+ }
1186
1246
  emit("ad_error");
1187
1247
  }
1188
1248
  return {
@@ -1380,6 +1440,7 @@ function createHlsAdPlayer(contentVideo, options) {
1380
1440
  var previousMutedState;
1381
1441
  return _ts_generator(this, function(_state) {
1382
1442
  console.log("[HlsAdPlayer] Stopping ad");
1443
+ clearStallWatchdog();
1383
1444
  adPlaying = false;
1384
1445
  setAdPlayingFlag(false);
1385
1446
  previousMutedState = contentVideo.muted;
@@ -1418,6 +1479,7 @@ function createHlsAdPlayer(contentVideo, options) {
1418
1479
  destroy: function destroy() {
1419
1480
  console.log("[HlsAdPlayer] Destroying");
1420
1481
  destroyed = true;
1482
+ clearStallWatchdog();
1421
1483
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1422
1484
  try {
1423
1485
  for(var _iterator = pendingTimeouts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -1562,6 +1624,41 @@ function createHlsAdPlayer(contentVideo, options) {
1562
1624
  }
1563
1625
  };
1564
1626
  }
1627
+ // src/utils/devUrl.ts
1628
+ var PRIVATE_HOST_PATTERNS = [
1629
+ /^localhost$/i,
1630
+ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
1631
+ /^0\.0\.0\.0$/,
1632
+ /^::1$/,
1633
+ /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
1634
+ /^192\.168\.\d{1,3}\.\d{1,3}$/,
1635
+ /^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/
1636
+ ];
1637
+ function isLocalDevHost(hostname) {
1638
+ var host = hostname.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
1639
+ if (!host) return false;
1640
+ if (host.endsWith(".local") || host.endsWith(".localhost")) return true;
1641
+ return PRIVATE_HOST_PATTERNS.some(function(pattern) {
1642
+ return pattern.test(host);
1643
+ });
1644
+ }
1645
+ function toDevSafeUrl(rawUrl) {
1646
+ if (!rawUrl) return rawUrl !== null && rawUrl !== void 0 ? rawUrl : "";
1647
+ var url;
1648
+ try {
1649
+ url = new URL(rawUrl);
1650
+ } catch (unused) {
1651
+ return rawUrl;
1652
+ }
1653
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1654
+ return rawUrl;
1655
+ }
1656
+ if (!isLocalDevHost(url.hostname)) {
1657
+ return rawUrl;
1658
+ }
1659
+ var cleanPath = url.pathname.replace(/\/{2,}/g, "/") || "/index.html";
1660
+ return "file://".concat(cleanPath);
1661
+ }
1565
1662
  // src/sdk/pal.ts
1566
1663
  var PAL_SDK_URL = "https://imasdk.googleapis.com/pal/sdkloader/pal.js";
1567
1664
  var _nonceLoader;
@@ -1648,12 +1745,12 @@ function createPalNonceManager() {
1648
1745
  request.adWillPlayMuted = (_options_adWillPlayMuted = options.adWillPlayMuted) !== null && _options_adWillPlayMuted !== void 0 ? _options_adWillPlayMuted : false;
1649
1746
  request.playerVolume = (_options_playerVolume = options.playerVolume) !== null && _options_playerVolume !== void 0 ? _options_playerVolume : request.adWillPlayMuted ? 0 : 1;
1650
1747
  request.continuousPlayback = (_options_continuousPlayback = options.continuousPlayback) !== null && _options_continuousPlayback !== void 0 ? _options_continuousPlayback : true;
1651
- request.descriptionUrl = options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : "");
1748
+ request.descriptionUrl = toDevSafeUrl(options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : ""));
1652
1749
  request.iconsSupported = true;
1653
1750
  request.playerType = "StormcloudVideoPlayer";
1654
1751
  request.playerVersion = "1.0";
1655
1752
  request.supportedApiFrameworks = "2,7,9";
1656
- request.url = options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : "");
1753
+ request.url = toDevSafeUrl(options.descriptionUrl || (typeof window !== "undefined" ? window.location.href : ""));
1657
1754
  request.videoWidth = options.videoWidth || 640;
1658
1755
  request.videoHeight = options.videoHeight || 480;
1659
1756
  if (options.ppid) request.ppid = options.ppid;
@@ -3774,6 +3871,7 @@ function getBrowserConfigOverrides() {
3774
3871
  var overrides = {};
3775
3872
  if (browser.isSmartTV) {
3776
3873
  overrides.allowNativeHls = true;
3874
+ overrides.pauseContentDuringAds = true;
3777
3875
  }
3778
3876
  return overrides;
3779
3877
  }
@@ -5272,7 +5370,7 @@ var AdConfigManager = /*#__PURE__*/ function() {
5272
5370
  podMaxDurationMs: podParams.maxDurationMs,
5273
5371
  isCtv: envSignals.isCtv,
5274
5372
  contentUrl: envSignals.contentUrl,
5275
- pageUrl: !envSignals.isCtv && typeof window !== "undefined" ? window.location.href : void 0,
5373
+ pageUrl: !envSignals.isCtv && typeof window !== "undefined" ? toDevSafeUrl(window.location.href) : void 0,
5276
5374
  adWillPlayMuted: adWillPlayMuted,
5277
5375
  adWillAutoPlay: !!this.config.autoplay,
5278
5376
  appId: envSignals.appId,
@@ -6656,12 +6754,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
6656
6754
  // ───────────────────────────────────────────────────────────────
6657
6755
  // IMA event listeners
6658
6756
  // ───────────────────────────────────────────────────────────────
6659
- /**
6660
- * Produces the next pod ad-request URL for the current break. Every request
6661
- * (initial and continuous-fetch top-up) carries pod macros (pmad/pmnd/pmxd,
6662
- * no ppos); pmxd tracks the remaining break time so top-up pods only ask for
6663
- * what still fits.
6664
- */ key: "nextAdRequestUrl",
6757
+ key: "nextAdRequestUrl",
6665
6758
  value: function nextAdRequestUrl(baseVastUrl) {
6666
6759
  var remaining = Math.min(this.timing.getWallClockRemainingAdMs(), this.timing.getDurationBudgetRemainingMs());
6667
6760
  var breakDurationMs = remaining > 0 ? remaining : void 0;
@@ -7790,7 +7883,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7790
7883
  if (Math.abs(this.host.video.volume - restoredVolume) > 0.01) {
7791
7884
  this.host.video.volume = restoredVolume;
7792
7885
  }
7793
- if (this.host.shouldContinueLiveStreamDuringAds()) {
7886
+ this.host.video.style.visibility = "visible";
7887
+ this.host.video.style.opacity = "1";
7888
+ if (this.host.isLiveStream()) {
7794
7889
  var liveSyncPos = this.host.getLiveSyncPosition();
7795
7890
  if (liveSyncPos != null && liveSyncPos > this.host.video.currentTime) {
7796
7891
  if (this.debug) {
@@ -7798,16 +7893,39 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7798
7893
  }
7799
7894
  this.host.video.currentTime = liveSyncPos;
7800
7895
  }
7801
- if (this.host.video.paused) {
7802
- var _this_host_video_play;
7803
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
7804
- (_this_host_video_play = this.host.video.play()) === null || _this_host_video_play === void 0 ? void 0 : _this_host_video_play.catch(function() {});
7805
- } else {
7806
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video already playing in live mode after ads");
7896
+ }
7897
+ this.resumeContentPlayback();
7898
+ }
7899
+ },
7900
+ {
7901
+ key: "resumeContentPlayback",
7902
+ value: function resumeContentPlayback() {
7903
+ var _this = this;
7904
+ var attempt = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0;
7905
+ var maxAttempts = 3;
7906
+ if (!this.host.video.paused) {
7907
+ if (this.debug && attempt === 0) {
7908
+ console.log("[StormcloudVideoPlayer] Content video already playing after ads");
7807
7909
  }
7808
- } else if (this.host.video.paused) {
7809
- var _this_host_video_play1;
7810
- (_this_host_video_play1 = this.host.video.play()) === null || _this_host_video_play1 === void 0 ? void 0 : _this_host_video_play1.catch(function() {});
7910
+ return;
7911
+ }
7912
+ if (this.debug) {
7913
+ console.log("[StormcloudVideoPlayer] Resuming content playback after ads (attempt ".concat(attempt + 1, "/").concat(maxAttempts, ")"));
7914
+ }
7915
+ var playResult = this.host.video.play();
7916
+ if (playResult && typeof playResult.catch === "function") {
7917
+ playResult.catch(function(error) {
7918
+ if (attempt + 1 >= maxAttempts) {
7919
+ if (_this.debug) {
7920
+ console.warn("[StormcloudVideoPlayer] Failed to resume content playback after ads:", error);
7921
+ }
7922
+ return;
7923
+ }
7924
+ var backoffMs = 250 * (attempt + 1);
7925
+ window.setTimeout(function() {
7926
+ _this.resumeContentPlayback(attempt + 1);
7927
+ }, backoffMs);
7928
+ });
7811
7929
  }
7812
7930
  }
7813
7931
  },
@@ -7921,6 +8039,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7921
8039
  shouldContinueLiveStreamDuringAds: function shouldContinueLiveStreamDuringAds() {
7922
8040
  return _this.shouldContinueLiveStreamDuringAds();
7923
8041
  },
8042
+ isLiveStream: function isLiveStream() {
8043
+ return _this.hlsEngine.isLiveStream;
8044
+ },
7924
8045
  getLiveSyncPosition: function getLiveSyncPosition() {
7925
8046
  return _this.hlsEngine.getLiveSyncPosition();
7926
8047
  },
@@ -8143,6 +8264,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8143
8264
  {
8144
8265
  key: "shouldContinueLiveStreamDuringAds",
8145
8266
  value: function shouldContinueLiveStreamDuringAds() {
8267
+ if (this.config.pauseContentDuringAds) {
8268
+ return false;
8269
+ }
8146
8270
  if (this.hlsEngine.isLiveStream) {
8147
8271
  return true;
8148
8272
  }