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
@@ -19,6 +19,7 @@ declare class StormcloudVideoPlayer {
19
19
  private readonly hlsEngine;
20
20
  private readonly adBreak;
21
21
  private readonly floorLadder;
22
+ private readonly contentWatchdog;
22
23
  private edgePodBaseUrl;
23
24
  constructor(config: StormcloudVideoPlayerConfig);
24
25
  private get debug();
@@ -2299,6 +2299,9 @@ function requiresMediaPipelineResetAfterAds() {
2299
2299
  var browser = detectBrowser();
2300
2300
  return browser.name === "Samsung Tizen" && ((_browser_tizenVersion = browser.tizenVersion) !== null && _browser_tizenVersion !== void 0 ? _browser_tizenVersion : 0) >= 5;
2301
2301
  }
2302
+ function prefersProgressiveAdMedia() {
2303
+ return detectBrowser().isSmartTV;
2304
+ }
2302
2305
  function getBrowserConfigOverrides() {
2303
2306
  var browser = detectBrowser();
2304
2307
  var overrides = {};
@@ -3142,6 +3145,7 @@ function createHlsAdPlayer(contentVideo, options) {
3142
3145
  var probe = adVideoElement !== null && adVideoElement !== void 0 ? adVideoElement : document.createElement("video");
3143
3146
  var pageIsHttps = typeof location !== "undefined" && location.protocol === "https:";
3144
3147
  var target = getMediaTargetQuality();
3148
+ var deprioritizeHls = prefersProgressiveAdMedia();
3145
3149
  var accepted = [];
3146
3150
  mediaFiles.forEach(function(file, index) {
3147
3151
  var isHls = isHlsMediaFile(file);
@@ -3172,19 +3176,24 @@ function createHlsAdPlayer(contentVideo, options) {
3172
3176
  var bitrateDiff = Math.abs((file.bitrate || 5e3) * 1e3 - target.bitrate);
3173
3177
  score = resolutionDiff * 2 + bitrateDiff / 1e3;
3174
3178
  }
3179
+ var deprioritizedHls = deprioritizeHls && isHls;
3175
3180
  accepted.push({
3176
3181
  file: file,
3177
3182
  score: score,
3178
- insecure: insecure
3183
+ insecure: insecure,
3184
+ deprioritizedHls: deprioritizedHls
3179
3185
  });
3180
3186
  logFunnel("media_candidate", _object_spread_props(_object_spread({}, details), {
3181
3187
  reason: "accepted",
3182
- why: insecure ? "insecure_http_deprioritized" : "playable",
3188
+ why: insecure ? "insecure_http_deprioritized" : deprioritizedHls ? "hls_deprioritized_on_tv" : "playable",
3183
3189
  score: score
3184
3190
  }));
3185
3191
  });
3186
3192
  accepted.sort(function(a, b) {
3187
3193
  if (a.insecure !== b.insecure) return a.insecure ? 1 : -1;
3194
+ if (a.deprioritizedHls !== b.deprioritizedHls) {
3195
+ return a.deprioritizedHls ? 1 : -1;
3196
+ }
3188
3197
  return a.score - b.score;
3189
3198
  });
3190
3199
  return accepted.map(function(entry) {
@@ -4857,8 +4866,8 @@ function createHlsAdPlayer(contentVideo, options) {
4857
4866
  console.warn("[HlsAdPlayer] Error preparing ad video for new source:", error);
4858
4867
  }
4859
4868
  }
4860
- function recreateAdVideoElement() {
4861
- console.log("[HlsAdPlayer] Recreating ad video element (single-decoder platform)");
4869
+ function recreateAdVideoElement(reason) {
4870
+ console.log("[HlsAdPlayer] Recreating ad video element (".concat(reason, ")"));
4862
4871
  teardownAdEventListeners();
4863
4872
  prepareVideoElementForNewSource();
4864
4873
  if (adVideoElement) {
@@ -4902,8 +4911,9 @@ function createHlsAdPlayer(contentVideo, options) {
4902
4911
  }
4903
4912
  function resetVideoElementForNextSource() {
4904
4913
  clearStallWatchdog();
4905
- if (requiresMediaPipelineResetAfterAds()) {
4906
- recreateAdVideoElement();
4914
+ var previousSourceUsedHlsJs = adHls !== void 0;
4915
+ if (requiresMediaPipelineResetAfterAds() || previousSourceUsedHlsJs) {
4916
+ recreateAdVideoElement(previousSourceUsedHlsJs ? "after hls.js source" : "single-decoder platform");
4907
4917
  setupAdEventListeners();
4908
4918
  } else {
4909
4919
  prepareVideoElementForNewSource();
@@ -6119,14 +6129,16 @@ function ingestPlayerTracking(licenseKey, channel, body) {
6119
6129
  // src/utils/tracking.ts
6120
6130
  var cachedBrowserId = null;
6121
6131
  function buildAnalyticsContext(config) {
6122
- var _config_contentNetwork, _config_channelName, _config_programTitle;
6132
+ var _config_contentNetwork, _config_channelName, _config_programTitle, _config_src;
6123
6133
  var context = {};
6124
6134
  var contentNetwork = (_config_contentNetwork = config.contentNetwork) === null || _config_contentNetwork === void 0 ? void 0 : _config_contentNetwork.trim();
6125
6135
  var contentChannel = (_config_channelName = config.channelName) === null || _config_channelName === void 0 ? void 0 : _config_channelName.trim();
6126
6136
  var contentTitle = (_config_programTitle = config.programTitle) === null || _config_programTitle === void 0 ? void 0 : _config_programTitle.trim();
6137
+ var hlsUrl = (_config_src = config.src) === null || _config_src === void 0 ? void 0 : _config_src.trim();
6127
6138
  if (contentNetwork) context.contentNetwork = contentNetwork;
6128
6139
  if (contentChannel) context.contentChannel = contentChannel;
6129
6140
  if (contentTitle) context.contentTitle = contentTitle;
6141
+ if (hlsUrl) context.hlsUrl = hlsUrl;
6130
6142
  if (config.debugAdTiming) {
6131
6143
  context.debugAdTiming = true;
6132
6144
  }
@@ -6632,6 +6644,8 @@ function sendAdImpressionTracking(_0, _1) {
6632
6644
  content_channel: context.contentChannel
6633
6645
  } : {}, context.contentTitle ? {
6634
6646
  content_title: context.contentTitle
6647
+ } : {}, context.hlsUrl ? {
6648
+ hls_url: context.hlsUrl
6635
6649
  } : {})
6636
6650
  ]
6637
6651
  })
@@ -12385,6 +12399,181 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
12385
12399
  ]);
12386
12400
  return AdBreakOrchestrator;
12387
12401
  }();
12402
+ // src/player/ContentHealthWatchdog.ts
12403
+ var POLL_INTERVAL_MS = 2e3;
12404
+ var STALL_THRESHOLD_MS = 12e3;
12405
+ var POST_BREAK_GRACE_MS = 5e4;
12406
+ var MAX_BREAK_SUPPRESSION_MS = 10 * 60 * 1e3;
12407
+ var SELF_INFLICTED_PAUSE_WINDOW_MS = 5e3;
12408
+ var HEALTHY_PLAYBACK_SECONDS = 5;
12409
+ var MAX_NORMAL_ADVANCE_PER_TICK_S = 4;
12410
+ var LIGHT_STEP_COOLDOWN_MS = 1e4;
12411
+ var RELOAD_BASE_COOLDOWN_MS = 15e3;
12412
+ var RELOAD_MAX_COOLDOWN_MS = 12e4;
12413
+ var ContentHealthWatchdog = /*#__PURE__*/ function() {
12414
+ function ContentHealthWatchdog(host) {
12415
+ var _this = this;
12416
+ _class_call_check(this, ContentHealthWatchdog);
12417
+ this.host = host;
12418
+ this.armed = false;
12419
+ this.userPaused = false;
12420
+ this.wasInBreak = false;
12421
+ this.breakStartedAt = 0;
12422
+ this.stuckBreakLogged = false;
12423
+ this.graceUntil = 0;
12424
+ this.lastPosition = 0;
12425
+ this.lastProgressAt = 0;
12426
+ this.lastRealPlaybackAt = 0;
12427
+ this.healthySeconds = 0;
12428
+ this.step = 0;
12429
+ this.nextActionAt = 0;
12430
+ this.lastActionAt = 0;
12431
+ this.onPlaying = function() {
12432
+ if (_this.host.isAdPlaying()) return;
12433
+ _this.armed = true;
12434
+ _this.userPaused = false;
12435
+ };
12436
+ this.onPause = function() {
12437
+ if (_this.host.isBreakActive() || _this.host.isAdPlaying() || Date.now() - _this.lastActionAt < SELF_INFLICTED_PAUSE_WINDOW_MS) {
12438
+ return;
12439
+ }
12440
+ _this.userPaused = true;
12441
+ };
12442
+ this.onPlay = function() {
12443
+ _this.userPaused = false;
12444
+ };
12445
+ }
12446
+ _create_class(ContentHealthWatchdog, [
12447
+ {
12448
+ key: "start",
12449
+ value: function start() {
12450
+ var _this = this;
12451
+ if (this.intervalId !== void 0) return;
12452
+ var video = this.host.video;
12453
+ video.addEventListener("playing", this.onPlaying);
12454
+ video.addEventListener("pause", this.onPause);
12455
+ video.addEventListener("play", this.onPlay);
12456
+ this.resetProgress(Date.now());
12457
+ this.intervalId = window.setInterval(function() {
12458
+ return _this.tick();
12459
+ }, POLL_INTERVAL_MS);
12460
+ }
12461
+ },
12462
+ {
12463
+ key: "stop",
12464
+ value: function stop() {
12465
+ if (this.intervalId !== void 0) {
12466
+ clearInterval(this.intervalId);
12467
+ this.intervalId = void 0;
12468
+ }
12469
+ var video = this.host.video;
12470
+ video.removeEventListener("playing", this.onPlaying);
12471
+ video.removeEventListener("pause", this.onPause);
12472
+ video.removeEventListener("play", this.onPlay);
12473
+ this.armed = false;
12474
+ this.step = 0;
12475
+ }
12476
+ },
12477
+ {
12478
+ key: "resetProgress",
12479
+ value: function resetProgress(now) {
12480
+ this.lastPosition = this.host.video.currentTime;
12481
+ this.lastProgressAt = now;
12482
+ }
12483
+ },
12484
+ {
12485
+ key: "tick",
12486
+ value: function tick() {
12487
+ var now = Date.now();
12488
+ var video = this.host.video;
12489
+ var hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
12490
+ if (!this.armed || this.userPaused || hidden || video.ended || !this.host.isLiveStream() || !this.host.hasHlsInstance()) {
12491
+ this.resetProgress(now);
12492
+ return;
12493
+ }
12494
+ var breakActive = this.host.isBreakActive() || this.host.isAdPlaying();
12495
+ if (breakActive && !this.wasInBreak) {
12496
+ this.wasInBreak = true;
12497
+ this.breakStartedAt = now;
12498
+ this.stuckBreakLogged = false;
12499
+ }
12500
+ if (breakActive && now - this.breakStartedAt < MAX_BREAK_SUPPRESSION_MS) {
12501
+ this.resetProgress(now);
12502
+ return;
12503
+ }
12504
+ if (breakActive && !this.stuckBreakLogged) {
12505
+ this.stuckBreakLogged = true;
12506
+ console.warn("[ContentWatchdog] Ad break state active for over ".concat(MAX_BREAK_SUPPRESSION_MS / 6e4, " min — monitoring content anyway"));
12507
+ }
12508
+ if (!breakActive && this.wasInBreak) {
12509
+ this.wasInBreak = false;
12510
+ this.graceUntil = now + POST_BREAK_GRACE_MS;
12511
+ this.resetProgress(now);
12512
+ return;
12513
+ }
12514
+ var currentTime = video.currentTime;
12515
+ var delta = currentTime - this.lastPosition;
12516
+ this.lastPosition = currentTime;
12517
+ if (delta > 0.25 && delta <= MAX_NORMAL_ADVANCE_PER_TICK_S && !video.paused) {
12518
+ this.lastProgressAt = now;
12519
+ this.lastRealPlaybackAt = now;
12520
+ this.healthySeconds += delta;
12521
+ if (this.step > 0 && this.healthySeconds >= HEALTHY_PLAYBACK_SECONDS) {
12522
+ console.log("[ContentWatchdog] Content playback recovered after ".concat(this.step, " recovery step(s)"));
12523
+ this.step = 0;
12524
+ this.nextActionAt = 0;
12525
+ }
12526
+ return;
12527
+ }
12528
+ if (now < this.graceUntil) {
12529
+ this.lastProgressAt = now;
12530
+ return;
12531
+ }
12532
+ if (now - this.lastProgressAt < STALL_THRESHOLD_MS || now < this.nextActionAt) {
12533
+ return;
12534
+ }
12535
+ this.recover(now);
12536
+ }
12537
+ },
12538
+ {
12539
+ key: "recover",
12540
+ value: function recover(now) {
12541
+ var video = this.host.video;
12542
+ 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");
12543
+ var stalledForS = ((now - (this.lastRealPlaybackAt || this.lastProgressAt)) / 1e3).toFixed(0);
12544
+ var fullReloadOnly = this.host.requiresMediaPipelineResetAfterAds();
12545
+ var action = fullReloadOnly || this.step >= 2 ? "reload" : this.step === 0 ? "recover_media_error" : "restart_load";
12546
+ console.warn("[ContentWatchdog] Content stalled for ".concat(stalledForS, "s — ").concat(action, " (step ").concat(this.step + 1, ") — ").concat(state));
12547
+ this.lastActionAt = now;
12548
+ this.healthySeconds = 0;
12549
+ if (action === "recover_media_error") {
12550
+ this.host.recoverMediaError();
12551
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12552
+ } else if (action === "restart_load") {
12553
+ var liveSyncPos = this.host.getLiveSyncPosition();
12554
+ var position = liveSyncPos != null && liveSyncPos > video.currentTime ? liveSyncPos : video.currentTime;
12555
+ if (position !== video.currentTime) {
12556
+ video.currentTime = position;
12557
+ }
12558
+ this.host.restartHlsLoad(position);
12559
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12560
+ } else {
12561
+ var reloadIndex = fullReloadOnly ? this.step : this.step - 2;
12562
+ var cooldown = Math.min(RELOAD_BASE_COOLDOWN_MS * Math.pow(2, reloadIndex), RELOAD_MAX_COOLDOWN_MS);
12563
+ this.host.reloadLiveStream();
12564
+ this.nextActionAt = now + cooldown;
12565
+ }
12566
+ this.step++;
12567
+ this.resetProgress(now);
12568
+ if (video.paused) {
12569
+ var _video_play;
12570
+ (_video_play = video.play()) === null || _video_play === void 0 ? void 0 : _video_play.catch(function() {});
12571
+ }
12572
+ }
12573
+ }
12574
+ ]);
12575
+ return ContentHealthWatchdog;
12576
+ }();
12388
12577
  // src/player/StormcloudVideoPlayer.ts
12389
12578
  var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12390
12579
  function StormcloudVideoPlayer(config) {
@@ -12515,6 +12704,36 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12515
12704
  return _this.palNonce.sendAdTouch(event);
12516
12705
  }
12517
12706
  }, this.timing, this.adConfig, this.cueManager, this.placeholder);
12707
+ this.contentWatchdog = new ContentHealthWatchdog({
12708
+ video: this.video,
12709
+ isLiveStream: function isLiveStream() {
12710
+ return _this.hlsEngine.isLiveStream;
12711
+ },
12712
+ hasHlsInstance: function hasHlsInstance() {
12713
+ return !!_this.hlsEngine.hls;
12714
+ },
12715
+ isBreakActive: function isBreakActive() {
12716
+ return _this.adBreak.inAdBreak || _this.adBreak.isBreakActive();
12717
+ },
12718
+ isAdPlaying: function isAdPlaying() {
12719
+ return _this.adPlayer.isAdPlaying();
12720
+ },
12721
+ requiresMediaPipelineResetAfterAds: function requiresMediaPipelineResetAfterAds1() {
12722
+ return requiresMediaPipelineResetAfterAds();
12723
+ },
12724
+ getLiveSyncPosition: function getLiveSyncPosition() {
12725
+ return _this.hlsEngine.getLiveSyncPosition();
12726
+ },
12727
+ recoverMediaError: function recoverMediaError() {
12728
+ return _this.hlsEngine.recoverMediaError();
12729
+ },
12730
+ restartHlsLoad: function restartHlsLoad(position) {
12731
+ return _this.hlsEngine.restartLoadAt(position);
12732
+ },
12733
+ reloadLiveStream: function reloadLiveStream() {
12734
+ return _this.hlsEngine.reloadLiveStream();
12735
+ }
12736
+ });
12518
12737
  }
12519
12738
  _create_class(StormcloudVideoPlayer, [
12520
12739
  {
@@ -12835,6 +13054,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12835
13054
  }
12836
13055
  };
12837
13056
  this.video.addEventListener("emptied", this.emptiedHandler);
13057
+ if (this.config.contentStallWatchdog !== false) {
13058
+ this.contentWatchdog.start();
13059
+ }
12838
13060
  }
12839
13061
  },
12840
13062
  {
@@ -13319,6 +13541,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
13319
13541
  key: "destroy",
13320
13542
  value: function destroy() {
13321
13543
  var _this_adPlayer;
13544
+ this.contentWatchdog.stop();
13322
13545
  this.adBreak.cancelScheduledRequest();
13323
13546
  this.timing.destroy();
13324
13547
  this.adBreak.clearPendingAdBreak();