stormcloud-video-player 0.8.34 → 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 (37) hide show
  1. package/dist/stormcloud-vp.min.js +1 -1
  2. package/lib/index.cjs +105 -16
  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 +105 -16
  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.d.cts +1 -1
  12. package/lib/player/AdTimingService.d.cts +1 -1
  13. package/lib/player/HlsEngine.d.cts +1 -1
  14. package/lib/player/PlayerControls.d.cts +1 -1
  15. package/lib/player/Scte35CueManager.d.cts +1 -1
  16. package/lib/player/Scte35Parser.d.cts +1 -1
  17. package/lib/player/StormcloudVideoPlayer.cjs +105 -16
  18. package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
  19. package/lib/player/StormcloudVideoPlayer.d.cts +1 -1
  20. package/lib/player/playerTypes.d.cts +1 -1
  21. package/lib/players/HlsPlayer.cjs +105 -16
  22. package/lib/players/HlsPlayer.cjs.map +1 -1
  23. package/lib/players/HlsPlayer.d.cts +1 -1
  24. package/lib/players/index.cjs +105 -16
  25. package/lib/players/index.cjs.map +1 -1
  26. package/lib/sdk/hlsAdPlayer.cjs +62 -0
  27. package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
  28. package/lib/sdk/hlsAdPlayer.d.cts +1 -1
  29. package/lib/{types-CSHvCbhZ.d.cts → types-cTqIKw_D.d.cts} +1 -0
  30. package/lib/ui/StormcloudVideoPlayer.cjs +105 -16
  31. package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
  32. package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
  33. package/lib/utils/browserCompat.cjs +1 -0
  34. package/lib/utils/browserCompat.cjs.map +1 -1
  35. package/lib/utils/browserCompat.d.cts +1 -0
  36. package/lib/utils/tracking.d.cts +1 -1
  37. 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){
@@ -3856,6 +3918,7 @@ function getBrowserConfigOverrides() {
3856
3918
  var overrides = {};
3857
3919
  if (browser.isSmartTV) {
3858
3920
  overrides.allowNativeHls = true;
3921
+ overrides.pauseContentDuringAds = true;
3859
3922
  }
3860
3923
  return overrides;
3861
3924
  }
@@ -6768,12 +6831,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
6768
6831
  // ───────────────────────────────────────────────────────────────
6769
6832
  // IMA event listeners
6770
6833
  // ───────────────────────────────────────────────────────────────
6771
- /**
6772
- * Produces the next pod ad-request URL for the current break. Every request
6773
- * (initial and continuous-fetch top-up) carries pod macros (pmad/pmnd/pmxd,
6774
- * no ppos); pmxd tracks the remaining break time so top-up pods only ask for
6775
- * what still fits.
6776
- */ key: "nextAdRequestUrl",
6834
+ key: "nextAdRequestUrl",
6777
6835
  value: function nextAdRequestUrl(baseVastUrl) {
6778
6836
  var remaining = Math.min(this.timing.getWallClockRemainingAdMs(), this.timing.getDurationBudgetRemainingMs());
6779
6837
  var breakDurationMs = remaining > 0 ? remaining : void 0;
@@ -7902,7 +7960,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7902
7960
  if (Math.abs(this.host.video.volume - restoredVolume) > 0.01) {
7903
7961
  this.host.video.volume = restoredVolume;
7904
7962
  }
7905
- if (this.host.shouldContinueLiveStreamDuringAds()) {
7963
+ this.host.video.style.visibility = "visible";
7964
+ this.host.video.style.opacity = "1";
7965
+ if (this.host.isLiveStream()) {
7906
7966
  var liveSyncPos = this.host.getLiveSyncPosition();
7907
7967
  if (liveSyncPos != null && liveSyncPos > this.host.video.currentTime) {
7908
7968
  if (this.debug) {
@@ -7910,16 +7970,39 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7910
7970
  }
7911
7971
  this.host.video.currentTime = liveSyncPos;
7912
7972
  }
7913
- if (this.host.video.paused) {
7914
- var _this_host_video_play;
7915
- if (this.debug) console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
7916
- (_this_host_video_play = this.host.video.play()) === null || _this_host_video_play === void 0 ? void 0 : _this_host_video_play.catch(function() {});
7917
- } else {
7918
- 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");
7919
7986
  }
7920
- } else if (this.host.video.paused) {
7921
- var _this_host_video_play1;
7922
- (_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
+ });
7923
8006
  }
7924
8007
  }
7925
8008
  },
@@ -8034,6 +8117,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8034
8117
  shouldContinueLiveStreamDuringAds: function shouldContinueLiveStreamDuringAds() {
8035
8118
  return _this.shouldContinueLiveStreamDuringAds();
8036
8119
  },
8120
+ isLiveStream: function isLiveStream() {
8121
+ return _this.hlsEngine.isLiveStream;
8122
+ },
8037
8123
  getLiveSyncPosition: function getLiveSyncPosition() {
8038
8124
  return _this.hlsEngine.getLiveSyncPosition();
8039
8125
  },
@@ -8256,6 +8342,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8256
8342
  {
8257
8343
  key: "shouldContinueLiveStreamDuringAds",
8258
8344
  value: function shouldContinueLiveStreamDuringAds() {
8345
+ if (this.config.pauseContentDuringAds) {
8346
+ return false;
8347
+ }
8259
8348
  if (this.hlsEngine.isLiveStream) {
8260
8349
  return true;
8261
8350
  }