stormcloud-video-player 0.8.77 → 0.8.79

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/Player.cjs +2 -0
  3. package/lib/Player.cjs.map +1 -1
  4. package/lib/Player.d.cts +1 -1
  5. package/lib/StormcloudPlayer.cjs +230 -7
  6. package/lib/StormcloudPlayer.cjs.map +1 -1
  7. package/lib/index.cjs +230 -7
  8. package/lib/index.cjs.map +1 -1
  9. package/lib/index.d.ts +4 -1
  10. package/lib/index.js +231 -7
  11. package/lib/index.js.map +1 -1
  12. package/lib/player/AdBreakOrchestrator.cjs +5 -1
  13. package/lib/player/AdBreakOrchestrator.cjs.map +1 -1
  14. package/lib/player/AdConfigManager.cjs +3 -1
  15. package/lib/player/AdConfigManager.cjs.map +1 -1
  16. package/lib/player/ContentHealthWatchdog.cjs +255 -0
  17. package/lib/player/ContentHealthWatchdog.cjs.map +1 -0
  18. package/lib/player/ContentHealthWatchdog.d.cts +40 -0
  19. package/lib/player/StormcloudVideoPlayer.cjs +230 -7
  20. package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
  21. package/lib/player/StormcloudVideoPlayer.d.cts +1 -0
  22. package/lib/players/HlsPlayer.cjs +230 -7
  23. package/lib/players/HlsPlayer.cjs.map +1 -1
  24. package/lib/players/index.cjs +230 -7
  25. package/lib/players/index.cjs.map +1 -1
  26. package/lib/props.cjs +2 -0
  27. package/lib/props.cjs.map +1 -1
  28. package/lib/sdk/hlsAdPlayer.cjs +16 -6
  29. package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
  30. package/lib/types.d.cts +2 -0
  31. package/lib/ui/StormcloudVideoPlayer.cjs +230 -7
  32. package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
  33. package/lib/utils/browserCompat.cjs +7 -0
  34. package/lib/utils/browserCompat.cjs.map +1 -1
  35. package/lib/utils/browserCompat.d.cts +2 -1
  36. package/lib/utils/tracking.cjs +5 -1
  37. package/lib/utils/tracking.cjs.map +1 -1
  38. package/lib/utils/tracking.d.cts +1 -1
  39. package/package.json +2 -2
package/lib/index.d.ts CHANGED
@@ -58,6 +58,7 @@ interface StormcloudVideoPlayerConfig {
58
58
  debugAdTiming?: boolean;
59
59
  adFailsafeTimeoutMs?: number;
60
60
  adMediaStartTimeoutMs?: number;
61
+ contentStallWatchdog?: boolean;
61
62
  adBreakCheckIntervalMs?: number;
62
63
  maxAdBreakExtensionMs?: number;
63
64
  minRemainingMsToStartAd?: number;
@@ -183,6 +184,7 @@ interface PlayerAnalyticsContext {
183
184
  contentNetwork?: string;
184
185
  contentChannel?: string;
185
186
  contentTitle?: string;
187
+ hlsUrl?: string;
186
188
  }
187
189
  interface HeartbeatData {
188
190
  browserId: string;
@@ -285,6 +287,7 @@ declare class StormcloudVideoPlayer {
285
287
  private readonly hlsEngine;
286
288
  private readonly adBreak;
287
289
  private readonly floorLadder;
290
+ private readonly contentWatchdog;
288
291
  private edgePodBaseUrl;
289
292
  constructor(config: StormcloudVideoPlayerConfig);
290
293
  private get debug();
@@ -4064,7 +4067,7 @@ declare const IS_SAFARI: boolean;
4064
4067
  declare const SUPPORTS_HLS: () => boolean;
4065
4068
  declare const SUPPORTS_DASH: () => boolean;
4066
4069
 
4067
- declare function buildAnalyticsContext(config: Pick<StormcloudVideoPlayerConfig, "debugAdTiming" | "isLiveStream" | "contentNetwork" | "channelName" | "programTitle">): PlayerAnalyticsContext;
4070
+ declare function buildAnalyticsContext(config: Pick<StormcloudVideoPlayerConfig, "debugAdTiming" | "isLiveStream" | "contentNetwork" | "channelName" | "programTitle" | "src">): PlayerAnalyticsContext;
4068
4071
  declare function getClientInfo(): ClientInfo;
4069
4072
  declare function getBrowserID(clientInfo: ClientInfo): Promise<string>;
4070
4073
  type PlayerMetricFlags = {
package/lib/index.js CHANGED
@@ -2243,6 +2243,9 @@ function requiresMediaPipelineResetAfterAds() {
2243
2243
  var browser = detectBrowser();
2244
2244
  return browser.name === "Samsung Tizen" && ((_browser_tizenVersion = browser.tizenVersion) !== null && _browser_tizenVersion !== void 0 ? _browser_tizenVersion : 0) >= 5;
2245
2245
  }
2246
+ function prefersProgressiveAdMedia() {
2247
+ return detectBrowser().isSmartTV;
2248
+ }
2246
2249
  function getBrowserConfigOverrides() {
2247
2250
  var browser = detectBrowser();
2248
2251
  var overrides = {};
@@ -3109,6 +3112,7 @@ function createHlsAdPlayer(contentVideo, options) {
3109
3112
  var probe = adVideoElement !== null && adVideoElement !== void 0 ? adVideoElement : document.createElement("video");
3110
3113
  var pageIsHttps = typeof location !== "undefined" && location.protocol === "https:";
3111
3114
  var target = getMediaTargetQuality();
3115
+ var deprioritizeHls = prefersProgressiveAdMedia();
3112
3116
  var accepted = [];
3113
3117
  mediaFiles.forEach(function(file, index) {
3114
3118
  var isHls = isHlsMediaFile(file);
@@ -3139,19 +3143,24 @@ function createHlsAdPlayer(contentVideo, options) {
3139
3143
  var bitrateDiff = Math.abs((file.bitrate || 5e3) * 1e3 - target.bitrate);
3140
3144
  score = resolutionDiff * 2 + bitrateDiff / 1e3;
3141
3145
  }
3146
+ var deprioritizedHls = deprioritizeHls && isHls;
3142
3147
  accepted.push({
3143
3148
  file: file,
3144
3149
  score: score,
3145
- insecure: insecure
3150
+ insecure: insecure,
3151
+ deprioritizedHls: deprioritizedHls
3146
3152
  });
3147
3153
  logFunnel("media_candidate", _object_spread_props(_object_spread({}, details), {
3148
3154
  reason: "accepted",
3149
- why: insecure ? "insecure_http_deprioritized" : "playable",
3155
+ why: insecure ? "insecure_http_deprioritized" : deprioritizedHls ? "hls_deprioritized_on_tv" : "playable",
3150
3156
  score: score
3151
3157
  }));
3152
3158
  });
3153
3159
  accepted.sort(function(a, b) {
3154
3160
  if (a.insecure !== b.insecure) return a.insecure ? 1 : -1;
3161
+ if (a.deprioritizedHls !== b.deprioritizedHls) {
3162
+ return a.deprioritizedHls ? 1 : -1;
3163
+ }
3155
3164
  return a.score - b.score;
3156
3165
  });
3157
3166
  return accepted.map(function(entry) {
@@ -4824,8 +4833,8 @@ function createHlsAdPlayer(contentVideo, options) {
4824
4833
  console.warn("[HlsAdPlayer] Error preparing ad video for new source:", error);
4825
4834
  }
4826
4835
  }
4827
- function recreateAdVideoElement() {
4828
- console.log("[HlsAdPlayer] Recreating ad video element (single-decoder platform)");
4836
+ function recreateAdVideoElement(reason) {
4837
+ console.log("[HlsAdPlayer] Recreating ad video element (".concat(reason, ")"));
4829
4838
  teardownAdEventListeners();
4830
4839
  prepareVideoElementForNewSource();
4831
4840
  if (adVideoElement) {
@@ -4869,8 +4878,9 @@ function createHlsAdPlayer(contentVideo, options) {
4869
4878
  }
4870
4879
  function resetVideoElementForNextSource() {
4871
4880
  clearStallWatchdog();
4872
- if (requiresMediaPipelineResetAfterAds()) {
4873
- recreateAdVideoElement();
4881
+ var previousSourceUsedHlsJs = adHls !== void 0;
4882
+ if (requiresMediaPipelineResetAfterAds() || previousSourceUsedHlsJs) {
4883
+ recreateAdVideoElement(previousSourceUsedHlsJs ? "after hls.js source" : "single-decoder platform");
4874
4884
  setupAdEventListeners();
4875
4885
  } else {
4876
4886
  prepareVideoElementForNewSource();
@@ -6092,14 +6102,16 @@ function ingestPlayerTracking(licenseKey, channel, body) {
6092
6102
  // src/utils/tracking.ts
6093
6103
  var cachedBrowserId = null;
6094
6104
  function buildAnalyticsContext(config) {
6095
- var _config_contentNetwork, _config_channelName, _config_programTitle;
6105
+ var _config_contentNetwork, _config_channelName, _config_programTitle, _config_src;
6096
6106
  var context = {};
6097
6107
  var contentNetwork = (_config_contentNetwork = config.contentNetwork) === null || _config_contentNetwork === void 0 ? void 0 : _config_contentNetwork.trim();
6098
6108
  var contentChannel = (_config_channelName = config.channelName) === null || _config_channelName === void 0 ? void 0 : _config_channelName.trim();
6099
6109
  var contentTitle = (_config_programTitle = config.programTitle) === null || _config_programTitle === void 0 ? void 0 : _config_programTitle.trim();
6110
+ var hlsUrl = (_config_src = config.src) === null || _config_src === void 0 ? void 0 : _config_src.trim();
6100
6111
  if (contentNetwork) context.contentNetwork = contentNetwork;
6101
6112
  if (contentChannel) context.contentChannel = contentChannel;
6102
6113
  if (contentTitle) context.contentTitle = contentTitle;
6114
+ if (hlsUrl) context.hlsUrl = hlsUrl;
6103
6115
  if (config.debugAdTiming) {
6104
6116
  context.debugAdTiming = true;
6105
6117
  }
@@ -6605,6 +6617,8 @@ function sendAdImpressionTracking(_0, _1) {
6605
6617
  content_channel: context.contentChannel
6606
6618
  } : {}, context.contentTitle ? {
6607
6619
  content_title: context.contentTitle
6620
+ } : {}, context.hlsUrl ? {
6621
+ hls_url: context.hlsUrl
6608
6622
  } : {})
6609
6623
  ]
6610
6624
  })
@@ -12367,6 +12381,182 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
12367
12381
  ]);
12368
12382
  return AdBreakOrchestrator;
12369
12383
  }();
12384
+ // src/player/ContentHealthWatchdog.ts
12385
+ var POLL_INTERVAL_MS = 2e3;
12386
+ var STALL_THRESHOLD_MS = 12e3;
12387
+ var POST_BREAK_GRACE_MS = 5e4;
12388
+ var MAX_BREAK_SUPPRESSION_MS = 10 * 60 * 1e3;
12389
+ var SELF_INFLICTED_PAUSE_WINDOW_MS = 5e3;
12390
+ var HEALTHY_PLAYBACK_SECONDS = 5;
12391
+ var MAX_NORMAL_ADVANCE_PER_TICK_S = 4;
12392
+ var LIGHT_STEP_COOLDOWN_MS = 1e4;
12393
+ var RELOAD_BASE_COOLDOWN_MS = 15e3;
12394
+ var RELOAD_MAX_COOLDOWN_MS = 12e4;
12395
+ var ContentHealthWatchdog = /*#__PURE__*/ function() {
12396
+ "use strict";
12397
+ function ContentHealthWatchdog(host) {
12398
+ var _this = this;
12399
+ _class_call_check(this, ContentHealthWatchdog);
12400
+ this.host = host;
12401
+ this.armed = false;
12402
+ this.userPaused = false;
12403
+ this.wasInBreak = false;
12404
+ this.breakStartedAt = 0;
12405
+ this.stuckBreakLogged = false;
12406
+ this.graceUntil = 0;
12407
+ this.lastPosition = 0;
12408
+ this.lastProgressAt = 0;
12409
+ this.lastRealPlaybackAt = 0;
12410
+ this.healthySeconds = 0;
12411
+ this.step = 0;
12412
+ this.nextActionAt = 0;
12413
+ this.lastActionAt = 0;
12414
+ this.onPlaying = function() {
12415
+ if (_this.host.isAdPlaying()) return;
12416
+ _this.armed = true;
12417
+ _this.userPaused = false;
12418
+ };
12419
+ this.onPause = function() {
12420
+ if (_this.host.isBreakActive() || _this.host.isAdPlaying() || Date.now() - _this.lastActionAt < SELF_INFLICTED_PAUSE_WINDOW_MS) {
12421
+ return;
12422
+ }
12423
+ _this.userPaused = true;
12424
+ };
12425
+ this.onPlay = function() {
12426
+ _this.userPaused = false;
12427
+ };
12428
+ }
12429
+ _create_class(ContentHealthWatchdog, [
12430
+ {
12431
+ key: "start",
12432
+ value: function start() {
12433
+ var _this = this;
12434
+ if (this.intervalId !== void 0) return;
12435
+ var video = this.host.video;
12436
+ video.addEventListener("playing", this.onPlaying);
12437
+ video.addEventListener("pause", this.onPause);
12438
+ video.addEventListener("play", this.onPlay);
12439
+ this.resetProgress(Date.now());
12440
+ this.intervalId = window.setInterval(function() {
12441
+ return _this.tick();
12442
+ }, POLL_INTERVAL_MS);
12443
+ }
12444
+ },
12445
+ {
12446
+ key: "stop",
12447
+ value: function stop() {
12448
+ if (this.intervalId !== void 0) {
12449
+ clearInterval(this.intervalId);
12450
+ this.intervalId = void 0;
12451
+ }
12452
+ var video = this.host.video;
12453
+ video.removeEventListener("playing", this.onPlaying);
12454
+ video.removeEventListener("pause", this.onPause);
12455
+ video.removeEventListener("play", this.onPlay);
12456
+ this.armed = false;
12457
+ this.step = 0;
12458
+ }
12459
+ },
12460
+ {
12461
+ key: "resetProgress",
12462
+ value: function resetProgress(now) {
12463
+ this.lastPosition = this.host.video.currentTime;
12464
+ this.lastProgressAt = now;
12465
+ }
12466
+ },
12467
+ {
12468
+ key: "tick",
12469
+ value: function tick() {
12470
+ var now = Date.now();
12471
+ var video = this.host.video;
12472
+ var hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
12473
+ if (!this.armed || this.userPaused || hidden || video.ended || !this.host.isLiveStream() || !this.host.hasHlsInstance()) {
12474
+ this.resetProgress(now);
12475
+ return;
12476
+ }
12477
+ var breakActive = this.host.isBreakActive() || this.host.isAdPlaying();
12478
+ if (breakActive && !this.wasInBreak) {
12479
+ this.wasInBreak = true;
12480
+ this.breakStartedAt = now;
12481
+ this.stuckBreakLogged = false;
12482
+ }
12483
+ if (breakActive && now - this.breakStartedAt < MAX_BREAK_SUPPRESSION_MS) {
12484
+ this.resetProgress(now);
12485
+ return;
12486
+ }
12487
+ if (breakActive && !this.stuckBreakLogged) {
12488
+ this.stuckBreakLogged = true;
12489
+ console.warn("[ContentWatchdog] Ad break state active for over ".concat(MAX_BREAK_SUPPRESSION_MS / 6e4, " min — monitoring content anyway"));
12490
+ }
12491
+ if (!breakActive && this.wasInBreak) {
12492
+ this.wasInBreak = false;
12493
+ this.graceUntil = now + POST_BREAK_GRACE_MS;
12494
+ this.resetProgress(now);
12495
+ return;
12496
+ }
12497
+ var currentTime = video.currentTime;
12498
+ var delta = currentTime - this.lastPosition;
12499
+ this.lastPosition = currentTime;
12500
+ if (delta > 0.25 && delta <= MAX_NORMAL_ADVANCE_PER_TICK_S && !video.paused) {
12501
+ this.lastProgressAt = now;
12502
+ this.lastRealPlaybackAt = now;
12503
+ this.healthySeconds += delta;
12504
+ if (this.step > 0 && this.healthySeconds >= HEALTHY_PLAYBACK_SECONDS) {
12505
+ console.log("[ContentWatchdog] Content playback recovered after ".concat(this.step, " recovery step(s)"));
12506
+ this.step = 0;
12507
+ this.nextActionAt = 0;
12508
+ }
12509
+ return;
12510
+ }
12511
+ if (now < this.graceUntil) {
12512
+ this.lastProgressAt = now;
12513
+ return;
12514
+ }
12515
+ if (now - this.lastProgressAt < STALL_THRESHOLD_MS || now < this.nextActionAt) {
12516
+ return;
12517
+ }
12518
+ this.recover(now);
12519
+ }
12520
+ },
12521
+ {
12522
+ key: "recover",
12523
+ value: function recover(now) {
12524
+ var video = this.host.video;
12525
+ var state = "t=".concat(video.currentTime.toFixed(2), " paused=").concat(video.paused, " readyState=").concat(video.readyState, " networkState=").concat(video.networkState, " error=").concat(video.error ? video.error.code : "none");
12526
+ var stalledForS = ((now - (this.lastRealPlaybackAt || this.lastProgressAt)) / 1e3).toFixed(0);
12527
+ var fullReloadOnly = this.host.requiresMediaPipelineResetAfterAds();
12528
+ var action = fullReloadOnly || this.step >= 2 ? "reload" : this.step === 0 ? "recover_media_error" : "restart_load";
12529
+ console.warn("[ContentWatchdog] Content stalled for ".concat(stalledForS, "s — ").concat(action, " (step ").concat(this.step + 1, ") — ").concat(state));
12530
+ this.lastActionAt = now;
12531
+ this.healthySeconds = 0;
12532
+ if (action === "recover_media_error") {
12533
+ this.host.recoverMediaError();
12534
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12535
+ } else if (action === "restart_load") {
12536
+ var liveSyncPos = this.host.getLiveSyncPosition();
12537
+ var position = liveSyncPos != null && liveSyncPos > video.currentTime ? liveSyncPos : video.currentTime;
12538
+ if (position !== video.currentTime) {
12539
+ video.currentTime = position;
12540
+ }
12541
+ this.host.restartHlsLoad(position);
12542
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12543
+ } else {
12544
+ var reloadIndex = fullReloadOnly ? this.step : this.step - 2;
12545
+ var cooldown = Math.min(RELOAD_BASE_COOLDOWN_MS * Math.pow(2, reloadIndex), RELOAD_MAX_COOLDOWN_MS);
12546
+ this.host.reloadLiveStream();
12547
+ this.nextActionAt = now + cooldown;
12548
+ }
12549
+ this.step++;
12550
+ this.resetProgress(now);
12551
+ if (video.paused) {
12552
+ var _video_play;
12553
+ (_video_play = video.play()) === null || _video_play === void 0 ? void 0 : _video_play.catch(function() {});
12554
+ }
12555
+ }
12556
+ }
12557
+ ]);
12558
+ return ContentHealthWatchdog;
12559
+ }();
12370
12560
  // src/player/StormcloudVideoPlayer.ts
12371
12561
  var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12372
12562
  "use strict";
@@ -12498,6 +12688,36 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12498
12688
  return _this.palNonce.sendAdTouch(event);
12499
12689
  }
12500
12690
  }, this.timing, this.adConfig, this.cueManager, this.placeholder);
12691
+ this.contentWatchdog = new ContentHealthWatchdog({
12692
+ video: this.video,
12693
+ isLiveStream: function isLiveStream() {
12694
+ return _this.hlsEngine.isLiveStream;
12695
+ },
12696
+ hasHlsInstance: function hasHlsInstance() {
12697
+ return !!_this.hlsEngine.hls;
12698
+ },
12699
+ isBreakActive: function isBreakActive() {
12700
+ return _this.adBreak.inAdBreak || _this.adBreak.isBreakActive();
12701
+ },
12702
+ isAdPlaying: function isAdPlaying() {
12703
+ return _this.adPlayer.isAdPlaying();
12704
+ },
12705
+ requiresMediaPipelineResetAfterAds: function requiresMediaPipelineResetAfterAds1() {
12706
+ return requiresMediaPipelineResetAfterAds();
12707
+ },
12708
+ getLiveSyncPosition: function getLiveSyncPosition() {
12709
+ return _this.hlsEngine.getLiveSyncPosition();
12710
+ },
12711
+ recoverMediaError: function recoverMediaError() {
12712
+ return _this.hlsEngine.recoverMediaError();
12713
+ },
12714
+ restartHlsLoad: function restartHlsLoad(position) {
12715
+ return _this.hlsEngine.restartLoadAt(position);
12716
+ },
12717
+ reloadLiveStream: function reloadLiveStream() {
12718
+ return _this.hlsEngine.reloadLiveStream();
12719
+ }
12720
+ });
12501
12721
  }
12502
12722
  _create_class(StormcloudVideoPlayer, [
12503
12723
  {
@@ -12818,6 +13038,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12818
13038
  }
12819
13039
  };
12820
13040
  this.video.addEventListener("emptied", this.emptiedHandler);
13041
+ if (this.config.contentStallWatchdog !== false) {
13042
+ this.contentWatchdog.start();
13043
+ }
12821
13044
  }
12822
13045
  },
12823
13046
  {
@@ -13302,6 +13525,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
13302
13525
  key: "destroy",
13303
13526
  value: function destroy() {
13304
13527
  var _this_adPlayer;
13528
+ this.contentWatchdog.stop();
13305
13529
  this.adBreak.cancelScheduledRequest();
13306
13530
  this.timing.destroy();
13307
13531
  this.adBreak.clearPendingAdBreak();