stormcloud-video-player 0.8.34 → 0.8.36

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 (39) hide show
  1. package/dist/stormcloud-vp.min.js +1 -1
  2. package/lib/index.cjs +120 -16
  3. package/lib/index.cjs.map +1 -1
  4. package/lib/index.d.cts +21 -19
  5. package/lib/index.d.ts +21 -19
  6. package/lib/index.js +120 -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.cjs +18 -0
  12. package/lib/player/AdConfigManager.cjs.map +1 -1
  13. package/lib/player/AdConfigManager.d.cts +2 -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 +123 -16
  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 +123 -16
  24. package/lib/players/HlsPlayer.cjs.map +1 -1
  25. package/lib/players/HlsPlayer.d.cts +1 -1
  26. package/lib/players/index.cjs +123 -16
  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/{types-CSHvCbhZ.d.cts → types-cTqIKw_D.d.cts} +1 -0
  32. package/lib/ui/StormcloudVideoPlayer.cjs +123 -16
  33. package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
  34. package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
  35. package/lib/utils/browserCompat.cjs +1 -0
  36. package/lib/utils/browserCompat.cjs.map +1 -1
  37. package/lib/utils/browserCompat.d.cts +1 -0
  38. package/lib/utils/tracking.d.cts +1 -1
  39. package/package.json +1 -1
package/lib/index.d.cts CHANGED
@@ -2,6 +2,25 @@ import React, { CSSProperties, lazy as lazy$1 } from 'react';
2
2
  import * as csstype from 'csstype';
3
3
  import Hls from 'hls.js';
4
4
 
5
+ declare const MQTT_CA_CERT_FILE = "src/certs/emqxsl-ca.crt";
6
+ type MQTTConfig = {
7
+ enabled: boolean;
8
+ brokerAddress: string;
9
+ brokerPort: number;
10
+ wsPort: number;
11
+ username: string;
12
+ password: string;
13
+ topicPrefix: string;
14
+ qos: 0 | 1 | 2;
15
+ brokerUrl?: string;
16
+ };
17
+ declare const DEFAULT_MQTT_CONFIG: MQTTConfig;
18
+ declare const mqttConfig: MQTTConfig;
19
+ declare function applyMQTTConfig(overrides: Partial<MQTTConfig>): void;
20
+ declare function isMQTTEnabled(): boolean;
21
+ declare function buildMQTTBrokerUrl(): string;
22
+ declare function buildPlayerTopic(licenseKey: string, channel: "metrics" | "impressions" | "heartbeat"): string;
23
+
5
24
  type LateJoinPolicy = "play_remaining" | "skip_to_content";
6
25
  interface AdBreak {
7
26
  id?: string;
@@ -21,6 +40,7 @@ interface StormcloudVideoPlayerConfig {
21
40
  allowNativeHls?: boolean;
22
41
  lowLatencyMode?: boolean;
23
42
  isLiveStream?: boolean;
43
+ pauseContentDuringAds?: boolean;
24
44
  driftToleranceMs?: number;
25
45
  immediateManifestAds?: boolean;
26
46
  debugAdTiming?: boolean;
@@ -3943,25 +3963,6 @@ declare function sendAdLoadedTracking(licenseKey: string | undefined, adLoadedIn
3943
3963
  declare function sendAdImpressionTracking(licenseKey: string | undefined, adImpressionInfo: AdImpressionInfo, context?: PlayerAnalyticsContext): Promise<void>;
3944
3964
  declare function sendHeartbeat(licenseKey?: string, context?: PlayerAnalyticsContext, flags?: PlayerMetricFlags): Promise<void>;
3945
3965
 
3946
- declare const MQTT_CA_CERT_FILE = "src/certs/emqxsl-ca.crt";
3947
- type MQTTConfig = {
3948
- enabled: boolean;
3949
- brokerAddress: string;
3950
- brokerPort: number;
3951
- wsPort: number;
3952
- username: string;
3953
- password: string;
3954
- topicPrefix: string;
3955
- qos: 0 | 1 | 2;
3956
- brokerUrl?: string;
3957
- };
3958
- declare const DEFAULT_MQTT_CONFIG: MQTTConfig;
3959
- declare const mqttConfig: MQTTConfig;
3960
- declare function applyMQTTConfig(overrides: Partial<MQTTConfig>): void;
3961
- declare function isMQTTEnabled(): boolean;
3962
- declare function buildMQTTBrokerUrl(): string;
3963
- declare function buildPlayerTopic(licenseKey: string, channel: "metrics" | "impressions" | "heartbeat"): string;
3964
-
3965
3966
  type MQTTStatus = "disconnected" | "connecting" | "connected" | "error";
3966
3967
  declare function getMQTTStatus(): MQTTStatus;
3967
3968
  declare function isMQTTConnected(): 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
@@ -2,6 +2,25 @@ import React, { CSSProperties, lazy as lazy$1 } from 'react';
2
2
  import * as csstype from 'csstype';
3
3
  import Hls from 'hls.js';
4
4
 
5
+ declare const MQTT_CA_CERT_FILE = "src/certs/emqxsl-ca.crt";
6
+ type MQTTConfig = {
7
+ enabled: boolean;
8
+ brokerAddress: string;
9
+ brokerPort: number;
10
+ wsPort: number;
11
+ username: string;
12
+ password: string;
13
+ topicPrefix: string;
14
+ qos: 0 | 1 | 2;
15
+ brokerUrl?: string;
16
+ };
17
+ declare const DEFAULT_MQTT_CONFIG: MQTTConfig;
18
+ declare const mqttConfig: MQTTConfig;
19
+ declare function applyMQTTConfig(overrides: Partial<MQTTConfig>): void;
20
+ declare function isMQTTEnabled(): boolean;
21
+ declare function buildMQTTBrokerUrl(): string;
22
+ declare function buildPlayerTopic(licenseKey: string, channel: "metrics" | "impressions" | "heartbeat"): string;
23
+
5
24
  type LateJoinPolicy = "play_remaining" | "skip_to_content";
6
25
  interface AdBreak {
7
26
  id?: string;
@@ -21,6 +40,7 @@ interface StormcloudVideoPlayerConfig {
21
40
  allowNativeHls?: boolean;
22
41
  lowLatencyMode?: boolean;
23
42
  isLiveStream?: boolean;
43
+ pauseContentDuringAds?: boolean;
24
44
  driftToleranceMs?: number;
25
45
  immediateManifestAds?: boolean;
26
46
  debugAdTiming?: boolean;
@@ -3943,25 +3963,6 @@ declare function sendAdLoadedTracking(licenseKey: string | undefined, adLoadedIn
3943
3963
  declare function sendAdImpressionTracking(licenseKey: string | undefined, adImpressionInfo: AdImpressionInfo, context?: PlayerAnalyticsContext): Promise<void>;
3944
3964
  declare function sendHeartbeat(licenseKey?: string, context?: PlayerAnalyticsContext, flags?: PlayerMetricFlags): Promise<void>;
3945
3965
 
3946
- declare const MQTT_CA_CERT_FILE = "src/certs/emqxsl-ca.crt";
3947
- type MQTTConfig = {
3948
- enabled: boolean;
3949
- brokerAddress: string;
3950
- brokerPort: number;
3951
- wsPort: number;
3952
- username: string;
3953
- password: string;
3954
- topicPrefix: string;
3955
- qos: 0 | 1 | 2;
3956
- brokerUrl?: string;
3957
- };
3958
- declare const DEFAULT_MQTT_CONFIG: MQTTConfig;
3959
- declare const mqttConfig: MQTTConfig;
3960
- declare function applyMQTTConfig(overrides: Partial<MQTTConfig>): void;
3961
- declare function isMQTTEnabled(): boolean;
3962
- declare function buildMQTTBrokerUrl(): string;
3963
- declare function buildPlayerTopic(licenseKey: string, channel: "metrics" | "impressions" | "heartbeat"): string;
3964
-
3965
3966
  type MQTTStatus = "disconnected" | "connecting" | "connected" | "error";
3966
3967
  declare function getMQTTStatus(): MQTTStatus;
3967
3968
  declare function isMQTTConnected(): 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
  }
@@ -5036,6 +5099,7 @@ var AdConfigManager = /*#__PURE__*/ function() {
5036
5099
  ];
5037
5100
  case 4:
5038
5101
  data = _state.sent();
5102
+ this.applyMqttConfigFromResponse(data);
5039
5103
  imaPayload = (_data_response = data.response) === null || _data_response === void 0 ? void 0 : (_data_response_ima = _data_response.ima) === null || _data_response_ima === void 0 ? void 0 : (_data_response_ima_publisherdeskima = _data_response_ima["publisherdesk.ima"]) === null || _data_response_ima_publisherdeskima === void 0 ? void 0 : _data_response_ima_publisherdeskima.payload;
5040
5104
  if (imaPayload) {
5041
5105
  decodedPayload = imaPayload;
@@ -5069,6 +5133,20 @@ var AdConfigManager = /*#__PURE__*/ function() {
5069
5133
  }).call(this);
5070
5134
  }
5071
5135
  },
5136
+ {
5137
+ key: "applyMqttConfigFromResponse",
5138
+ value: function applyMqttConfigFromResponse(data) {
5139
+ var _data_response;
5140
+ var mqttConfig2 = (_data_response = data.response) === null || _data_response === void 0 ? void 0 : _data_response.mqtt_config;
5141
+ if (!mqttConfig2 || (typeof mqttConfig2 === "undefined" ? "undefined" : _type_of(mqttConfig2)) !== "object") {
5142
+ return;
5143
+ }
5144
+ applyMQTTConfig(mqttConfig2);
5145
+ if (this.debug) {
5146
+ console.log("[StormcloudVideoPlayer] Applied MQTT config from /ads/web:", mqttConfig2);
5147
+ }
5148
+ }
5149
+ },
5072
5150
  {
5073
5151
  key: "fetchAndParseVmap",
5074
5152
  value: function fetchAndParseVmap(vmapUrl) {
@@ -6768,12 +6846,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
6768
6846
  // ───────────────────────────────────────────────────────────────
6769
6847
  // IMA event listeners
6770
6848
  // ───────────────────────────────────────────────────────────────
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",
6849
+ key: "nextAdRequestUrl",
6777
6850
  value: function nextAdRequestUrl(baseVastUrl) {
6778
6851
  var remaining = Math.min(this.timing.getWallClockRemainingAdMs(), this.timing.getDurationBudgetRemainingMs());
6779
6852
  var breakDurationMs = remaining > 0 ? remaining : void 0;
@@ -7902,7 +7975,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7902
7975
  if (Math.abs(this.host.video.volume - restoredVolume) > 0.01) {
7903
7976
  this.host.video.volume = restoredVolume;
7904
7977
  }
7905
- if (this.host.shouldContinueLiveStreamDuringAds()) {
7978
+ this.host.video.style.visibility = "visible";
7979
+ this.host.video.style.opacity = "1";
7980
+ if (this.host.isLiveStream()) {
7906
7981
  var liveSyncPos = this.host.getLiveSyncPosition();
7907
7982
  if (liveSyncPos != null && liveSyncPos > this.host.video.currentTime) {
7908
7983
  if (this.debug) {
@@ -7910,16 +7985,39 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7910
7985
  }
7911
7986
  this.host.video.currentTime = liveSyncPos;
7912
7987
  }
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");
7988
+ }
7989
+ this.resumeContentPlayback();
7990
+ }
7991
+ },
7992
+ {
7993
+ key: "resumeContentPlayback",
7994
+ value: function resumeContentPlayback() {
7995
+ var _this = this;
7996
+ var attempt = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0;
7997
+ var maxAttempts = 3;
7998
+ if (!this.host.video.paused) {
7999
+ if (this.debug && attempt === 0) {
8000
+ console.log("[StormcloudVideoPlayer] Content video already playing after ads");
7919
8001
  }
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() {});
8002
+ return;
8003
+ }
8004
+ if (this.debug) {
8005
+ console.log("[StormcloudVideoPlayer] Resuming content playback after ads (attempt ".concat(attempt + 1, "/").concat(maxAttempts, ")"));
8006
+ }
8007
+ var playResult = this.host.video.play();
8008
+ if (playResult && typeof playResult.catch === "function") {
8009
+ playResult.catch(function(error) {
8010
+ if (attempt + 1 >= maxAttempts) {
8011
+ if (_this.debug) {
8012
+ console.warn("[StormcloudVideoPlayer] Failed to resume content playback after ads:", error);
8013
+ }
8014
+ return;
8015
+ }
8016
+ var backoffMs = 250 * (attempt + 1);
8017
+ window.setTimeout(function() {
8018
+ _this.resumeContentPlayback(attempt + 1);
8019
+ }, backoffMs);
8020
+ });
7923
8021
  }
7924
8022
  }
7925
8023
  },
@@ -8034,6 +8132,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8034
8132
  shouldContinueLiveStreamDuringAds: function shouldContinueLiveStreamDuringAds() {
8035
8133
  return _this.shouldContinueLiveStreamDuringAds();
8036
8134
  },
8135
+ isLiveStream: function isLiveStream() {
8136
+ return _this.hlsEngine.isLiveStream;
8137
+ },
8037
8138
  getLiveSyncPosition: function getLiveSyncPosition() {
8038
8139
  return _this.hlsEngine.getLiveSyncPosition();
8039
8140
  },
@@ -8256,6 +8357,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8256
8357
  {
8257
8358
  key: "shouldContinueLiveStreamDuringAds",
8258
8359
  value: function shouldContinueLiveStreamDuringAds() {
8360
+ if (this.config.pauseContentDuringAds) {
8361
+ return false;
8362
+ }
8259
8363
  if (this.hlsEngine.isLiveStream) {
8260
8364
  return true;
8261
8365
  }