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.cjs CHANGED
@@ -2465,6 +2465,9 @@ function requiresMediaPipelineResetAfterAds() {
2465
2465
  var browser = detectBrowser();
2466
2466
  return browser.name === "Samsung Tizen" && ((_browser_tizenVersion = browser.tizenVersion) !== null && _browser_tizenVersion !== void 0 ? _browser_tizenVersion : 0) >= 5;
2467
2467
  }
2468
+ function prefersProgressiveAdMedia() {
2469
+ return detectBrowser().isSmartTV;
2470
+ }
2468
2471
  function getBrowserConfigOverrides() {
2469
2472
  var browser = detectBrowser();
2470
2473
  var overrides = {};
@@ -3331,6 +3334,7 @@ function createHlsAdPlayer(contentVideo, options) {
3331
3334
  var probe = adVideoElement !== null && adVideoElement !== void 0 ? adVideoElement : document.createElement("video");
3332
3335
  var pageIsHttps = typeof location !== "undefined" && location.protocol === "https:";
3333
3336
  var target = getMediaTargetQuality();
3337
+ var deprioritizeHls = prefersProgressiveAdMedia();
3334
3338
  var accepted = [];
3335
3339
  mediaFiles.forEach(function(file, index) {
3336
3340
  var isHls = isHlsMediaFile(file);
@@ -3361,19 +3365,24 @@ function createHlsAdPlayer(contentVideo, options) {
3361
3365
  var bitrateDiff = Math.abs((file.bitrate || 5e3) * 1e3 - target.bitrate);
3362
3366
  score = resolutionDiff * 2 + bitrateDiff / 1e3;
3363
3367
  }
3368
+ var deprioritizedHls = deprioritizeHls && isHls;
3364
3369
  accepted.push({
3365
3370
  file: file,
3366
3371
  score: score,
3367
- insecure: insecure
3372
+ insecure: insecure,
3373
+ deprioritizedHls: deprioritizedHls
3368
3374
  });
3369
3375
  logFunnel("media_candidate", _object_spread_props(_object_spread({}, details), {
3370
3376
  reason: "accepted",
3371
- why: insecure ? "insecure_http_deprioritized" : "playable",
3377
+ why: insecure ? "insecure_http_deprioritized" : deprioritizedHls ? "hls_deprioritized_on_tv" : "playable",
3372
3378
  score: score
3373
3379
  }));
3374
3380
  });
3375
3381
  accepted.sort(function(a, b) {
3376
3382
  if (a.insecure !== b.insecure) return a.insecure ? 1 : -1;
3383
+ if (a.deprioritizedHls !== b.deprioritizedHls) {
3384
+ return a.deprioritizedHls ? 1 : -1;
3385
+ }
3377
3386
  return a.score - b.score;
3378
3387
  });
3379
3388
  return accepted.map(function(entry) {
@@ -5046,8 +5055,8 @@ function createHlsAdPlayer(contentVideo, options) {
5046
5055
  console.warn("[HlsAdPlayer] Error preparing ad video for new source:", error);
5047
5056
  }
5048
5057
  }
5049
- function recreateAdVideoElement() {
5050
- console.log("[HlsAdPlayer] Recreating ad video element (single-decoder platform)");
5058
+ function recreateAdVideoElement(reason) {
5059
+ console.log("[HlsAdPlayer] Recreating ad video element (".concat(reason, ")"));
5051
5060
  teardownAdEventListeners();
5052
5061
  prepareVideoElementForNewSource();
5053
5062
  if (adVideoElement) {
@@ -5091,8 +5100,9 @@ function createHlsAdPlayer(contentVideo, options) {
5091
5100
  }
5092
5101
  function resetVideoElementForNextSource() {
5093
5102
  clearStallWatchdog();
5094
- if (requiresMediaPipelineResetAfterAds()) {
5095
- recreateAdVideoElement();
5103
+ var previousSourceUsedHlsJs = adHls !== void 0;
5104
+ if (requiresMediaPipelineResetAfterAds() || previousSourceUsedHlsJs) {
5105
+ recreateAdVideoElement(previousSourceUsedHlsJs ? "after hls.js source" : "single-decoder platform");
5096
5106
  setupAdEventListeners();
5097
5107
  } else {
5098
5108
  prepareVideoElementForNewSource();
@@ -6314,14 +6324,16 @@ function ingestPlayerTracking(licenseKey, channel, body) {
6314
6324
  // src/utils/tracking.ts
6315
6325
  var cachedBrowserId = null;
6316
6326
  function buildAnalyticsContext(config) {
6317
- var _config_contentNetwork, _config_channelName, _config_programTitle;
6327
+ var _config_contentNetwork, _config_channelName, _config_programTitle, _config_src;
6318
6328
  var context = {};
6319
6329
  var contentNetwork = (_config_contentNetwork = config.contentNetwork) === null || _config_contentNetwork === void 0 ? void 0 : _config_contentNetwork.trim();
6320
6330
  var contentChannel = (_config_channelName = config.channelName) === null || _config_channelName === void 0 ? void 0 : _config_channelName.trim();
6321
6331
  var contentTitle = (_config_programTitle = config.programTitle) === null || _config_programTitle === void 0 ? void 0 : _config_programTitle.trim();
6332
+ var hlsUrl = (_config_src = config.src) === null || _config_src === void 0 ? void 0 : _config_src.trim();
6322
6333
  if (contentNetwork) context.contentNetwork = contentNetwork;
6323
6334
  if (contentChannel) context.contentChannel = contentChannel;
6324
6335
  if (contentTitle) context.contentTitle = contentTitle;
6336
+ if (hlsUrl) context.hlsUrl = hlsUrl;
6325
6337
  if (config.debugAdTiming) {
6326
6338
  context.debugAdTiming = true;
6327
6339
  }
@@ -6827,6 +6839,8 @@ function sendAdImpressionTracking(_0, _1) {
6827
6839
  content_channel: context.contentChannel
6828
6840
  } : {}, context.contentTitle ? {
6829
6841
  content_title: context.contentTitle
6842
+ } : {}, context.hlsUrl ? {
6843
+ hls_url: context.hlsUrl
6830
6844
  } : {})
6831
6845
  ]
6832
6846
  })
@@ -12580,6 +12594,181 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
12580
12594
  ]);
12581
12595
  return AdBreakOrchestrator;
12582
12596
  }();
12597
+ // src/player/ContentHealthWatchdog.ts
12598
+ var POLL_INTERVAL_MS = 2e3;
12599
+ var STALL_THRESHOLD_MS = 12e3;
12600
+ var POST_BREAK_GRACE_MS = 5e4;
12601
+ var MAX_BREAK_SUPPRESSION_MS = 10 * 60 * 1e3;
12602
+ var SELF_INFLICTED_PAUSE_WINDOW_MS = 5e3;
12603
+ var HEALTHY_PLAYBACK_SECONDS = 5;
12604
+ var MAX_NORMAL_ADVANCE_PER_TICK_S = 4;
12605
+ var LIGHT_STEP_COOLDOWN_MS = 1e4;
12606
+ var RELOAD_BASE_COOLDOWN_MS = 15e3;
12607
+ var RELOAD_MAX_COOLDOWN_MS = 12e4;
12608
+ var ContentHealthWatchdog = /*#__PURE__*/ function() {
12609
+ function ContentHealthWatchdog(host) {
12610
+ var _this = this;
12611
+ _class_call_check(this, ContentHealthWatchdog);
12612
+ this.host = host;
12613
+ this.armed = false;
12614
+ this.userPaused = false;
12615
+ this.wasInBreak = false;
12616
+ this.breakStartedAt = 0;
12617
+ this.stuckBreakLogged = false;
12618
+ this.graceUntil = 0;
12619
+ this.lastPosition = 0;
12620
+ this.lastProgressAt = 0;
12621
+ this.lastRealPlaybackAt = 0;
12622
+ this.healthySeconds = 0;
12623
+ this.step = 0;
12624
+ this.nextActionAt = 0;
12625
+ this.lastActionAt = 0;
12626
+ this.onPlaying = function() {
12627
+ if (_this.host.isAdPlaying()) return;
12628
+ _this.armed = true;
12629
+ _this.userPaused = false;
12630
+ };
12631
+ this.onPause = function() {
12632
+ if (_this.host.isBreakActive() || _this.host.isAdPlaying() || Date.now() - _this.lastActionAt < SELF_INFLICTED_PAUSE_WINDOW_MS) {
12633
+ return;
12634
+ }
12635
+ _this.userPaused = true;
12636
+ };
12637
+ this.onPlay = function() {
12638
+ _this.userPaused = false;
12639
+ };
12640
+ }
12641
+ _create_class(ContentHealthWatchdog, [
12642
+ {
12643
+ key: "start",
12644
+ value: function start() {
12645
+ var _this = this;
12646
+ if (this.intervalId !== void 0) return;
12647
+ var video = this.host.video;
12648
+ video.addEventListener("playing", this.onPlaying);
12649
+ video.addEventListener("pause", this.onPause);
12650
+ video.addEventListener("play", this.onPlay);
12651
+ this.resetProgress(Date.now());
12652
+ this.intervalId = window.setInterval(function() {
12653
+ return _this.tick();
12654
+ }, POLL_INTERVAL_MS);
12655
+ }
12656
+ },
12657
+ {
12658
+ key: "stop",
12659
+ value: function stop() {
12660
+ if (this.intervalId !== void 0) {
12661
+ clearInterval(this.intervalId);
12662
+ this.intervalId = void 0;
12663
+ }
12664
+ var video = this.host.video;
12665
+ video.removeEventListener("playing", this.onPlaying);
12666
+ video.removeEventListener("pause", this.onPause);
12667
+ video.removeEventListener("play", this.onPlay);
12668
+ this.armed = false;
12669
+ this.step = 0;
12670
+ }
12671
+ },
12672
+ {
12673
+ key: "resetProgress",
12674
+ value: function resetProgress(now) {
12675
+ this.lastPosition = this.host.video.currentTime;
12676
+ this.lastProgressAt = now;
12677
+ }
12678
+ },
12679
+ {
12680
+ key: "tick",
12681
+ value: function tick() {
12682
+ var now = Date.now();
12683
+ var video = this.host.video;
12684
+ var hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
12685
+ if (!this.armed || this.userPaused || hidden || video.ended || !this.host.isLiveStream() || !this.host.hasHlsInstance()) {
12686
+ this.resetProgress(now);
12687
+ return;
12688
+ }
12689
+ var breakActive = this.host.isBreakActive() || this.host.isAdPlaying();
12690
+ if (breakActive && !this.wasInBreak) {
12691
+ this.wasInBreak = true;
12692
+ this.breakStartedAt = now;
12693
+ this.stuckBreakLogged = false;
12694
+ }
12695
+ if (breakActive && now - this.breakStartedAt < MAX_BREAK_SUPPRESSION_MS) {
12696
+ this.resetProgress(now);
12697
+ return;
12698
+ }
12699
+ if (breakActive && !this.stuckBreakLogged) {
12700
+ this.stuckBreakLogged = true;
12701
+ console.warn("[ContentWatchdog] Ad break state active for over ".concat(MAX_BREAK_SUPPRESSION_MS / 6e4, " min — monitoring content anyway"));
12702
+ }
12703
+ if (!breakActive && this.wasInBreak) {
12704
+ this.wasInBreak = false;
12705
+ this.graceUntil = now + POST_BREAK_GRACE_MS;
12706
+ this.resetProgress(now);
12707
+ return;
12708
+ }
12709
+ var currentTime = video.currentTime;
12710
+ var delta = currentTime - this.lastPosition;
12711
+ this.lastPosition = currentTime;
12712
+ if (delta > 0.25 && delta <= MAX_NORMAL_ADVANCE_PER_TICK_S && !video.paused) {
12713
+ this.lastProgressAt = now;
12714
+ this.lastRealPlaybackAt = now;
12715
+ this.healthySeconds += delta;
12716
+ if (this.step > 0 && this.healthySeconds >= HEALTHY_PLAYBACK_SECONDS) {
12717
+ console.log("[ContentWatchdog] Content playback recovered after ".concat(this.step, " recovery step(s)"));
12718
+ this.step = 0;
12719
+ this.nextActionAt = 0;
12720
+ }
12721
+ return;
12722
+ }
12723
+ if (now < this.graceUntil) {
12724
+ this.lastProgressAt = now;
12725
+ return;
12726
+ }
12727
+ if (now - this.lastProgressAt < STALL_THRESHOLD_MS || now < this.nextActionAt) {
12728
+ return;
12729
+ }
12730
+ this.recover(now);
12731
+ }
12732
+ },
12733
+ {
12734
+ key: "recover",
12735
+ value: function recover(now) {
12736
+ var video = this.host.video;
12737
+ 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");
12738
+ var stalledForS = ((now - (this.lastRealPlaybackAt || this.lastProgressAt)) / 1e3).toFixed(0);
12739
+ var fullReloadOnly = this.host.requiresMediaPipelineResetAfterAds();
12740
+ var action = fullReloadOnly || this.step >= 2 ? "reload" : this.step === 0 ? "recover_media_error" : "restart_load";
12741
+ console.warn("[ContentWatchdog] Content stalled for ".concat(stalledForS, "s — ").concat(action, " (step ").concat(this.step + 1, ") — ").concat(state));
12742
+ this.lastActionAt = now;
12743
+ this.healthySeconds = 0;
12744
+ if (action === "recover_media_error") {
12745
+ this.host.recoverMediaError();
12746
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12747
+ } else if (action === "restart_load") {
12748
+ var liveSyncPos = this.host.getLiveSyncPosition();
12749
+ var position = liveSyncPos != null && liveSyncPos > video.currentTime ? liveSyncPos : video.currentTime;
12750
+ if (position !== video.currentTime) {
12751
+ video.currentTime = position;
12752
+ }
12753
+ this.host.restartHlsLoad(position);
12754
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12755
+ } else {
12756
+ var reloadIndex = fullReloadOnly ? this.step : this.step - 2;
12757
+ var cooldown = Math.min(RELOAD_BASE_COOLDOWN_MS * Math.pow(2, reloadIndex), RELOAD_MAX_COOLDOWN_MS);
12758
+ this.host.reloadLiveStream();
12759
+ this.nextActionAt = now + cooldown;
12760
+ }
12761
+ this.step++;
12762
+ this.resetProgress(now);
12763
+ if (video.paused) {
12764
+ var _video_play;
12765
+ (_video_play = video.play()) === null || _video_play === void 0 ? void 0 : _video_play.catch(function() {});
12766
+ }
12767
+ }
12768
+ }
12769
+ ]);
12770
+ return ContentHealthWatchdog;
12771
+ }();
12583
12772
  // src/player/StormcloudVideoPlayer.ts
12584
12773
  var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12585
12774
  function StormcloudVideoPlayer(config) {
@@ -12710,6 +12899,36 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12710
12899
  return _this.palNonce.sendAdTouch(event);
12711
12900
  }
12712
12901
  }, this.timing, this.adConfig, this.cueManager, this.placeholder);
12902
+ this.contentWatchdog = new ContentHealthWatchdog({
12903
+ video: this.video,
12904
+ isLiveStream: function isLiveStream() {
12905
+ return _this.hlsEngine.isLiveStream;
12906
+ },
12907
+ hasHlsInstance: function hasHlsInstance() {
12908
+ return !!_this.hlsEngine.hls;
12909
+ },
12910
+ isBreakActive: function isBreakActive() {
12911
+ return _this.adBreak.inAdBreak || _this.adBreak.isBreakActive();
12912
+ },
12913
+ isAdPlaying: function isAdPlaying() {
12914
+ return _this.adPlayer.isAdPlaying();
12915
+ },
12916
+ requiresMediaPipelineResetAfterAds: function requiresMediaPipelineResetAfterAds1() {
12917
+ return requiresMediaPipelineResetAfterAds();
12918
+ },
12919
+ getLiveSyncPosition: function getLiveSyncPosition() {
12920
+ return _this.hlsEngine.getLiveSyncPosition();
12921
+ },
12922
+ recoverMediaError: function recoverMediaError() {
12923
+ return _this.hlsEngine.recoverMediaError();
12924
+ },
12925
+ restartHlsLoad: function restartHlsLoad(position) {
12926
+ return _this.hlsEngine.restartLoadAt(position);
12927
+ },
12928
+ reloadLiveStream: function reloadLiveStream() {
12929
+ return _this.hlsEngine.reloadLiveStream();
12930
+ }
12931
+ });
12713
12932
  }
12714
12933
  _create_class(StormcloudVideoPlayer, [
12715
12934
  {
@@ -13030,6 +13249,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
13030
13249
  }
13031
13250
  };
13032
13251
  this.video.addEventListener("emptied", this.emptiedHandler);
13252
+ if (this.config.contentStallWatchdog !== false) {
13253
+ this.contentWatchdog.start();
13254
+ }
13033
13255
  }
13034
13256
  },
13035
13257
  {
@@ -13514,6 +13736,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
13514
13736
  key: "destroy",
13515
13737
  value: function destroy() {
13516
13738
  var _this_adPlayer;
13739
+ this.contentWatchdog.stop();
13517
13740
  this.adBreak.cancelScheduledRequest();
13518
13741
  this.timing.destroy();
13519
13742
  this.adBreak.clearPendingAdBreak();