stormcloud-video-player 0.8.77 → 0.8.78

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.
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();
@@ -12580,6 +12590,181 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
12580
12590
  ]);
12581
12591
  return AdBreakOrchestrator;
12582
12592
  }();
12593
+ // src/player/ContentHealthWatchdog.ts
12594
+ var POLL_INTERVAL_MS = 2e3;
12595
+ var STALL_THRESHOLD_MS = 12e3;
12596
+ var POST_BREAK_GRACE_MS = 5e4;
12597
+ var MAX_BREAK_SUPPRESSION_MS = 10 * 60 * 1e3;
12598
+ var SELF_INFLICTED_PAUSE_WINDOW_MS = 5e3;
12599
+ var HEALTHY_PLAYBACK_SECONDS = 5;
12600
+ var MAX_NORMAL_ADVANCE_PER_TICK_S = 4;
12601
+ var LIGHT_STEP_COOLDOWN_MS = 1e4;
12602
+ var RELOAD_BASE_COOLDOWN_MS = 15e3;
12603
+ var RELOAD_MAX_COOLDOWN_MS = 12e4;
12604
+ var ContentHealthWatchdog = /*#__PURE__*/ function() {
12605
+ function ContentHealthWatchdog(host) {
12606
+ var _this = this;
12607
+ _class_call_check(this, ContentHealthWatchdog);
12608
+ this.host = host;
12609
+ this.armed = false;
12610
+ this.userPaused = false;
12611
+ this.wasInBreak = false;
12612
+ this.breakStartedAt = 0;
12613
+ this.stuckBreakLogged = false;
12614
+ this.graceUntil = 0;
12615
+ this.lastPosition = 0;
12616
+ this.lastProgressAt = 0;
12617
+ this.lastRealPlaybackAt = 0;
12618
+ this.healthySeconds = 0;
12619
+ this.step = 0;
12620
+ this.nextActionAt = 0;
12621
+ this.lastActionAt = 0;
12622
+ this.onPlaying = function() {
12623
+ if (_this.host.isAdPlaying()) return;
12624
+ _this.armed = true;
12625
+ _this.userPaused = false;
12626
+ };
12627
+ this.onPause = function() {
12628
+ if (_this.host.isBreakActive() || _this.host.isAdPlaying() || Date.now() - _this.lastActionAt < SELF_INFLICTED_PAUSE_WINDOW_MS) {
12629
+ return;
12630
+ }
12631
+ _this.userPaused = true;
12632
+ };
12633
+ this.onPlay = function() {
12634
+ _this.userPaused = false;
12635
+ };
12636
+ }
12637
+ _create_class(ContentHealthWatchdog, [
12638
+ {
12639
+ key: "start",
12640
+ value: function start() {
12641
+ var _this = this;
12642
+ if (this.intervalId !== void 0) return;
12643
+ var video = this.host.video;
12644
+ video.addEventListener("playing", this.onPlaying);
12645
+ video.addEventListener("pause", this.onPause);
12646
+ video.addEventListener("play", this.onPlay);
12647
+ this.resetProgress(Date.now());
12648
+ this.intervalId = window.setInterval(function() {
12649
+ return _this.tick();
12650
+ }, POLL_INTERVAL_MS);
12651
+ }
12652
+ },
12653
+ {
12654
+ key: "stop",
12655
+ value: function stop() {
12656
+ if (this.intervalId !== void 0) {
12657
+ clearInterval(this.intervalId);
12658
+ this.intervalId = void 0;
12659
+ }
12660
+ var video = this.host.video;
12661
+ video.removeEventListener("playing", this.onPlaying);
12662
+ video.removeEventListener("pause", this.onPause);
12663
+ video.removeEventListener("play", this.onPlay);
12664
+ this.armed = false;
12665
+ this.step = 0;
12666
+ }
12667
+ },
12668
+ {
12669
+ key: "resetProgress",
12670
+ value: function resetProgress(now) {
12671
+ this.lastPosition = this.host.video.currentTime;
12672
+ this.lastProgressAt = now;
12673
+ }
12674
+ },
12675
+ {
12676
+ key: "tick",
12677
+ value: function tick() {
12678
+ var now = Date.now();
12679
+ var video = this.host.video;
12680
+ var hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
12681
+ if (!this.armed || this.userPaused || hidden || video.ended || !this.host.isLiveStream() || !this.host.hasHlsInstance()) {
12682
+ this.resetProgress(now);
12683
+ return;
12684
+ }
12685
+ var breakActive = this.host.isBreakActive() || this.host.isAdPlaying();
12686
+ if (breakActive && !this.wasInBreak) {
12687
+ this.wasInBreak = true;
12688
+ this.breakStartedAt = now;
12689
+ this.stuckBreakLogged = false;
12690
+ }
12691
+ if (breakActive && now - this.breakStartedAt < MAX_BREAK_SUPPRESSION_MS) {
12692
+ this.resetProgress(now);
12693
+ return;
12694
+ }
12695
+ if (breakActive && !this.stuckBreakLogged) {
12696
+ this.stuckBreakLogged = true;
12697
+ console.warn("[ContentWatchdog] Ad break state active for over ".concat(MAX_BREAK_SUPPRESSION_MS / 6e4, " min — monitoring content anyway"));
12698
+ }
12699
+ if (!breakActive && this.wasInBreak) {
12700
+ this.wasInBreak = false;
12701
+ this.graceUntil = now + POST_BREAK_GRACE_MS;
12702
+ this.resetProgress(now);
12703
+ return;
12704
+ }
12705
+ var currentTime = video.currentTime;
12706
+ var delta = currentTime - this.lastPosition;
12707
+ this.lastPosition = currentTime;
12708
+ if (delta > 0.25 && delta <= MAX_NORMAL_ADVANCE_PER_TICK_S && !video.paused) {
12709
+ this.lastProgressAt = now;
12710
+ this.lastRealPlaybackAt = now;
12711
+ this.healthySeconds += delta;
12712
+ if (this.step > 0 && this.healthySeconds >= HEALTHY_PLAYBACK_SECONDS) {
12713
+ console.log("[ContentWatchdog] Content playback recovered after ".concat(this.step, " recovery step(s)"));
12714
+ this.step = 0;
12715
+ this.nextActionAt = 0;
12716
+ }
12717
+ return;
12718
+ }
12719
+ if (now < this.graceUntil) {
12720
+ this.lastProgressAt = now;
12721
+ return;
12722
+ }
12723
+ if (now - this.lastProgressAt < STALL_THRESHOLD_MS || now < this.nextActionAt) {
12724
+ return;
12725
+ }
12726
+ this.recover(now);
12727
+ }
12728
+ },
12729
+ {
12730
+ key: "recover",
12731
+ value: function recover(now) {
12732
+ var video = this.host.video;
12733
+ 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");
12734
+ var stalledForS = ((now - (this.lastRealPlaybackAt || this.lastProgressAt)) / 1e3).toFixed(0);
12735
+ var fullReloadOnly = this.host.requiresMediaPipelineResetAfterAds();
12736
+ var action = fullReloadOnly || this.step >= 2 ? "reload" : this.step === 0 ? "recover_media_error" : "restart_load";
12737
+ console.warn("[ContentWatchdog] Content stalled for ".concat(stalledForS, "s — ").concat(action, " (step ").concat(this.step + 1, ") — ").concat(state));
12738
+ this.lastActionAt = now;
12739
+ this.healthySeconds = 0;
12740
+ if (action === "recover_media_error") {
12741
+ this.host.recoverMediaError();
12742
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12743
+ } else if (action === "restart_load") {
12744
+ var liveSyncPos = this.host.getLiveSyncPosition();
12745
+ var position = liveSyncPos != null && liveSyncPos > video.currentTime ? liveSyncPos : video.currentTime;
12746
+ if (position !== video.currentTime) {
12747
+ video.currentTime = position;
12748
+ }
12749
+ this.host.restartHlsLoad(position);
12750
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12751
+ } else {
12752
+ var reloadIndex = fullReloadOnly ? this.step : this.step - 2;
12753
+ var cooldown = Math.min(RELOAD_BASE_COOLDOWN_MS * Math.pow(2, reloadIndex), RELOAD_MAX_COOLDOWN_MS);
12754
+ this.host.reloadLiveStream();
12755
+ this.nextActionAt = now + cooldown;
12756
+ }
12757
+ this.step++;
12758
+ this.resetProgress(now);
12759
+ if (video.paused) {
12760
+ var _video_play;
12761
+ (_video_play = video.play()) === null || _video_play === void 0 ? void 0 : _video_play.catch(function() {});
12762
+ }
12763
+ }
12764
+ }
12765
+ ]);
12766
+ return ContentHealthWatchdog;
12767
+ }();
12583
12768
  // src/player/StormcloudVideoPlayer.ts
12584
12769
  var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12585
12770
  function StormcloudVideoPlayer(config) {
@@ -12710,6 +12895,36 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12710
12895
  return _this.palNonce.sendAdTouch(event);
12711
12896
  }
12712
12897
  }, this.timing, this.adConfig, this.cueManager, this.placeholder);
12898
+ this.contentWatchdog = new ContentHealthWatchdog({
12899
+ video: this.video,
12900
+ isLiveStream: function isLiveStream() {
12901
+ return _this.hlsEngine.isLiveStream;
12902
+ },
12903
+ hasHlsInstance: function hasHlsInstance() {
12904
+ return !!_this.hlsEngine.hls;
12905
+ },
12906
+ isBreakActive: function isBreakActive() {
12907
+ return _this.adBreak.inAdBreak || _this.adBreak.isBreakActive();
12908
+ },
12909
+ isAdPlaying: function isAdPlaying() {
12910
+ return _this.adPlayer.isAdPlaying();
12911
+ },
12912
+ requiresMediaPipelineResetAfterAds: function requiresMediaPipelineResetAfterAds1() {
12913
+ return requiresMediaPipelineResetAfterAds();
12914
+ },
12915
+ getLiveSyncPosition: function getLiveSyncPosition() {
12916
+ return _this.hlsEngine.getLiveSyncPosition();
12917
+ },
12918
+ recoverMediaError: function recoverMediaError() {
12919
+ return _this.hlsEngine.recoverMediaError();
12920
+ },
12921
+ restartHlsLoad: function restartHlsLoad(position) {
12922
+ return _this.hlsEngine.restartLoadAt(position);
12923
+ },
12924
+ reloadLiveStream: function reloadLiveStream() {
12925
+ return _this.hlsEngine.reloadLiveStream();
12926
+ }
12927
+ });
12713
12928
  }
12714
12929
  _create_class(StormcloudVideoPlayer, [
12715
12930
  {
@@ -13030,6 +13245,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
13030
13245
  }
13031
13246
  };
13032
13247
  this.video.addEventListener("emptied", this.emptiedHandler);
13248
+ if (this.config.contentStallWatchdog !== false) {
13249
+ this.contentWatchdog.start();
13250
+ }
13033
13251
  }
13034
13252
  },
13035
13253
  {
@@ -13514,6 +13732,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
13514
13732
  key: "destroy",
13515
13733
  value: function destroy() {
13516
13734
  var _this_adPlayer;
13735
+ this.contentWatchdog.stop();
13517
13736
  this.adBreak.cancelScheduledRequest();
13518
13737
  this.timing.destroy();
13519
13738
  this.adBreak.clearPendingAdBreak();