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.
@@ -2335,6 +2335,9 @@ function requiresMediaPipelineResetAfterAds() {
2335
2335
  var browser = detectBrowser();
2336
2336
  return browser.name === "Samsung Tizen" && ((_browser_tizenVersion = browser.tizenVersion) !== null && _browser_tizenVersion !== void 0 ? _browser_tizenVersion : 0) >= 5;
2337
2337
  }
2338
+ function prefersProgressiveAdMedia() {
2339
+ return detectBrowser().isSmartTV;
2340
+ }
2338
2341
  function getBrowserConfigOverrides() {
2339
2342
  var browser = detectBrowser();
2340
2343
  var overrides = {};
@@ -3178,6 +3181,7 @@ function createHlsAdPlayer(contentVideo, options) {
3178
3181
  var probe = adVideoElement !== null && adVideoElement !== void 0 ? adVideoElement : document.createElement("video");
3179
3182
  var pageIsHttps = typeof location !== "undefined" && location.protocol === "https:";
3180
3183
  var target = getMediaTargetQuality();
3184
+ var deprioritizeHls = prefersProgressiveAdMedia();
3181
3185
  var accepted = [];
3182
3186
  mediaFiles.forEach(function(file, index) {
3183
3187
  var isHls = isHlsMediaFile(file);
@@ -3208,19 +3212,24 @@ function createHlsAdPlayer(contentVideo, options) {
3208
3212
  var bitrateDiff = Math.abs((file.bitrate || 5e3) * 1e3 - target.bitrate);
3209
3213
  score = resolutionDiff * 2 + bitrateDiff / 1e3;
3210
3214
  }
3215
+ var deprioritizedHls = deprioritizeHls && isHls;
3211
3216
  accepted.push({
3212
3217
  file: file,
3213
3218
  score: score,
3214
- insecure: insecure
3219
+ insecure: insecure,
3220
+ deprioritizedHls: deprioritizedHls
3215
3221
  });
3216
3222
  logFunnel("media_candidate", _object_spread_props(_object_spread({}, details), {
3217
3223
  reason: "accepted",
3218
- why: insecure ? "insecure_http_deprioritized" : "playable",
3224
+ why: insecure ? "insecure_http_deprioritized" : deprioritizedHls ? "hls_deprioritized_on_tv" : "playable",
3219
3225
  score: score
3220
3226
  }));
3221
3227
  });
3222
3228
  accepted.sort(function(a, b) {
3223
3229
  if (a.insecure !== b.insecure) return a.insecure ? 1 : -1;
3230
+ if (a.deprioritizedHls !== b.deprioritizedHls) {
3231
+ return a.deprioritizedHls ? 1 : -1;
3232
+ }
3224
3233
  return a.score - b.score;
3225
3234
  });
3226
3235
  return accepted.map(function(entry) {
@@ -4893,8 +4902,8 @@ function createHlsAdPlayer(contentVideo, options) {
4893
4902
  console.warn("[HlsAdPlayer] Error preparing ad video for new source:", error);
4894
4903
  }
4895
4904
  }
4896
- function recreateAdVideoElement() {
4897
- console.log("[HlsAdPlayer] Recreating ad video element (single-decoder platform)");
4905
+ function recreateAdVideoElement(reason) {
4906
+ console.log("[HlsAdPlayer] Recreating ad video element (".concat(reason, ")"));
4898
4907
  teardownAdEventListeners();
4899
4908
  prepareVideoElementForNewSource();
4900
4909
  if (adVideoElement) {
@@ -4938,8 +4947,9 @@ function createHlsAdPlayer(contentVideo, options) {
4938
4947
  }
4939
4948
  function resetVideoElementForNextSource() {
4940
4949
  clearStallWatchdog();
4941
- if (requiresMediaPipelineResetAfterAds()) {
4942
- recreateAdVideoElement();
4950
+ var previousSourceUsedHlsJs = adHls !== void 0;
4951
+ if (requiresMediaPipelineResetAfterAds() || previousSourceUsedHlsJs) {
4952
+ recreateAdVideoElement(previousSourceUsedHlsJs ? "after hls.js source" : "single-decoder platform");
4943
4953
  setupAdEventListeners();
4944
4954
  } else {
4945
4955
  prepareVideoElementForNewSource();
@@ -12421,6 +12431,181 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
12421
12431
  ]);
12422
12432
  return AdBreakOrchestrator;
12423
12433
  }();
12434
+ // src/player/ContentHealthWatchdog.ts
12435
+ var POLL_INTERVAL_MS = 2e3;
12436
+ var STALL_THRESHOLD_MS = 12e3;
12437
+ var POST_BREAK_GRACE_MS = 5e4;
12438
+ var MAX_BREAK_SUPPRESSION_MS = 10 * 60 * 1e3;
12439
+ var SELF_INFLICTED_PAUSE_WINDOW_MS = 5e3;
12440
+ var HEALTHY_PLAYBACK_SECONDS = 5;
12441
+ var MAX_NORMAL_ADVANCE_PER_TICK_S = 4;
12442
+ var LIGHT_STEP_COOLDOWN_MS = 1e4;
12443
+ var RELOAD_BASE_COOLDOWN_MS = 15e3;
12444
+ var RELOAD_MAX_COOLDOWN_MS = 12e4;
12445
+ var ContentHealthWatchdog = /*#__PURE__*/ function() {
12446
+ function ContentHealthWatchdog(host) {
12447
+ var _this = this;
12448
+ _class_call_check(this, ContentHealthWatchdog);
12449
+ this.host = host;
12450
+ this.armed = false;
12451
+ this.userPaused = false;
12452
+ this.wasInBreak = false;
12453
+ this.breakStartedAt = 0;
12454
+ this.stuckBreakLogged = false;
12455
+ this.graceUntil = 0;
12456
+ this.lastPosition = 0;
12457
+ this.lastProgressAt = 0;
12458
+ this.lastRealPlaybackAt = 0;
12459
+ this.healthySeconds = 0;
12460
+ this.step = 0;
12461
+ this.nextActionAt = 0;
12462
+ this.lastActionAt = 0;
12463
+ this.onPlaying = function() {
12464
+ if (_this.host.isAdPlaying()) return;
12465
+ _this.armed = true;
12466
+ _this.userPaused = false;
12467
+ };
12468
+ this.onPause = function() {
12469
+ if (_this.host.isBreakActive() || _this.host.isAdPlaying() || Date.now() - _this.lastActionAt < SELF_INFLICTED_PAUSE_WINDOW_MS) {
12470
+ return;
12471
+ }
12472
+ _this.userPaused = true;
12473
+ };
12474
+ this.onPlay = function() {
12475
+ _this.userPaused = false;
12476
+ };
12477
+ }
12478
+ _create_class(ContentHealthWatchdog, [
12479
+ {
12480
+ key: "start",
12481
+ value: function start() {
12482
+ var _this = this;
12483
+ if (this.intervalId !== void 0) return;
12484
+ var video = this.host.video;
12485
+ video.addEventListener("playing", this.onPlaying);
12486
+ video.addEventListener("pause", this.onPause);
12487
+ video.addEventListener("play", this.onPlay);
12488
+ this.resetProgress(Date.now());
12489
+ this.intervalId = window.setInterval(function() {
12490
+ return _this.tick();
12491
+ }, POLL_INTERVAL_MS);
12492
+ }
12493
+ },
12494
+ {
12495
+ key: "stop",
12496
+ value: function stop() {
12497
+ if (this.intervalId !== void 0) {
12498
+ clearInterval(this.intervalId);
12499
+ this.intervalId = void 0;
12500
+ }
12501
+ var video = this.host.video;
12502
+ video.removeEventListener("playing", this.onPlaying);
12503
+ video.removeEventListener("pause", this.onPause);
12504
+ video.removeEventListener("play", this.onPlay);
12505
+ this.armed = false;
12506
+ this.step = 0;
12507
+ }
12508
+ },
12509
+ {
12510
+ key: "resetProgress",
12511
+ value: function resetProgress(now) {
12512
+ this.lastPosition = this.host.video.currentTime;
12513
+ this.lastProgressAt = now;
12514
+ }
12515
+ },
12516
+ {
12517
+ key: "tick",
12518
+ value: function tick() {
12519
+ var now = Date.now();
12520
+ var video = this.host.video;
12521
+ var hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
12522
+ if (!this.armed || this.userPaused || hidden || video.ended || !this.host.isLiveStream() || !this.host.hasHlsInstance()) {
12523
+ this.resetProgress(now);
12524
+ return;
12525
+ }
12526
+ var breakActive = this.host.isBreakActive() || this.host.isAdPlaying();
12527
+ if (breakActive && !this.wasInBreak) {
12528
+ this.wasInBreak = true;
12529
+ this.breakStartedAt = now;
12530
+ this.stuckBreakLogged = false;
12531
+ }
12532
+ if (breakActive && now - this.breakStartedAt < MAX_BREAK_SUPPRESSION_MS) {
12533
+ this.resetProgress(now);
12534
+ return;
12535
+ }
12536
+ if (breakActive && !this.stuckBreakLogged) {
12537
+ this.stuckBreakLogged = true;
12538
+ console.warn("[ContentWatchdog] Ad break state active for over ".concat(MAX_BREAK_SUPPRESSION_MS / 6e4, " min — monitoring content anyway"));
12539
+ }
12540
+ if (!breakActive && this.wasInBreak) {
12541
+ this.wasInBreak = false;
12542
+ this.graceUntil = now + POST_BREAK_GRACE_MS;
12543
+ this.resetProgress(now);
12544
+ return;
12545
+ }
12546
+ var currentTime = video.currentTime;
12547
+ var delta = currentTime - this.lastPosition;
12548
+ this.lastPosition = currentTime;
12549
+ if (delta > 0.25 && delta <= MAX_NORMAL_ADVANCE_PER_TICK_S && !video.paused) {
12550
+ this.lastProgressAt = now;
12551
+ this.lastRealPlaybackAt = now;
12552
+ this.healthySeconds += delta;
12553
+ if (this.step > 0 && this.healthySeconds >= HEALTHY_PLAYBACK_SECONDS) {
12554
+ console.log("[ContentWatchdog] Content playback recovered after ".concat(this.step, " recovery step(s)"));
12555
+ this.step = 0;
12556
+ this.nextActionAt = 0;
12557
+ }
12558
+ return;
12559
+ }
12560
+ if (now < this.graceUntil) {
12561
+ this.lastProgressAt = now;
12562
+ return;
12563
+ }
12564
+ if (now - this.lastProgressAt < STALL_THRESHOLD_MS || now < this.nextActionAt) {
12565
+ return;
12566
+ }
12567
+ this.recover(now);
12568
+ }
12569
+ },
12570
+ {
12571
+ key: "recover",
12572
+ value: function recover(now) {
12573
+ var video = this.host.video;
12574
+ 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");
12575
+ var stalledForS = ((now - (this.lastRealPlaybackAt || this.lastProgressAt)) / 1e3).toFixed(0);
12576
+ var fullReloadOnly = this.host.requiresMediaPipelineResetAfterAds();
12577
+ var action = fullReloadOnly || this.step >= 2 ? "reload" : this.step === 0 ? "recover_media_error" : "restart_load";
12578
+ console.warn("[ContentWatchdog] Content stalled for ".concat(stalledForS, "s — ").concat(action, " (step ").concat(this.step + 1, ") — ").concat(state));
12579
+ this.lastActionAt = now;
12580
+ this.healthySeconds = 0;
12581
+ if (action === "recover_media_error") {
12582
+ this.host.recoverMediaError();
12583
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12584
+ } else if (action === "restart_load") {
12585
+ var liveSyncPos = this.host.getLiveSyncPosition();
12586
+ var position = liveSyncPos != null && liveSyncPos > video.currentTime ? liveSyncPos : video.currentTime;
12587
+ if (position !== video.currentTime) {
12588
+ video.currentTime = position;
12589
+ }
12590
+ this.host.restartHlsLoad(position);
12591
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12592
+ } else {
12593
+ var reloadIndex = fullReloadOnly ? this.step : this.step - 2;
12594
+ var cooldown = Math.min(RELOAD_BASE_COOLDOWN_MS * Math.pow(2, reloadIndex), RELOAD_MAX_COOLDOWN_MS);
12595
+ this.host.reloadLiveStream();
12596
+ this.nextActionAt = now + cooldown;
12597
+ }
12598
+ this.step++;
12599
+ this.resetProgress(now);
12600
+ if (video.paused) {
12601
+ var _video_play;
12602
+ (_video_play = video.play()) === null || _video_play === void 0 ? void 0 : _video_play.catch(function() {});
12603
+ }
12604
+ }
12605
+ }
12606
+ ]);
12607
+ return ContentHealthWatchdog;
12608
+ }();
12424
12609
  // src/player/StormcloudVideoPlayer.ts
12425
12610
  var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12426
12611
  function StormcloudVideoPlayer(config) {
@@ -12551,6 +12736,36 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12551
12736
  return _this.palNonce.sendAdTouch(event);
12552
12737
  }
12553
12738
  }, this.timing, this.adConfig, this.cueManager, this.placeholder);
12739
+ this.contentWatchdog = new ContentHealthWatchdog({
12740
+ video: this.video,
12741
+ isLiveStream: function isLiveStream() {
12742
+ return _this.hlsEngine.isLiveStream;
12743
+ },
12744
+ hasHlsInstance: function hasHlsInstance() {
12745
+ return !!_this.hlsEngine.hls;
12746
+ },
12747
+ isBreakActive: function isBreakActive() {
12748
+ return _this.adBreak.inAdBreak || _this.adBreak.isBreakActive();
12749
+ },
12750
+ isAdPlaying: function isAdPlaying() {
12751
+ return _this.adPlayer.isAdPlaying();
12752
+ },
12753
+ requiresMediaPipelineResetAfterAds: function requiresMediaPipelineResetAfterAds1() {
12754
+ return requiresMediaPipelineResetAfterAds();
12755
+ },
12756
+ getLiveSyncPosition: function getLiveSyncPosition() {
12757
+ return _this.hlsEngine.getLiveSyncPosition();
12758
+ },
12759
+ recoverMediaError: function recoverMediaError() {
12760
+ return _this.hlsEngine.recoverMediaError();
12761
+ },
12762
+ restartHlsLoad: function restartHlsLoad(position) {
12763
+ return _this.hlsEngine.restartLoadAt(position);
12764
+ },
12765
+ reloadLiveStream: function reloadLiveStream() {
12766
+ return _this.hlsEngine.reloadLiveStream();
12767
+ }
12768
+ });
12554
12769
  }
12555
12770
  _create_class(StormcloudVideoPlayer, [
12556
12771
  {
@@ -12871,6 +13086,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12871
13086
  }
12872
13087
  };
12873
13088
  this.video.addEventListener("emptied", this.emptiedHandler);
13089
+ if (this.config.contentStallWatchdog !== false) {
13090
+ this.contentWatchdog.start();
13091
+ }
12874
13092
  }
12875
13093
  },
12876
13094
  {
@@ -13355,6 +13573,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
13355
13573
  key: "destroy",
13356
13574
  value: function destroy() {
13357
13575
  var _this_adPlayer;
13576
+ this.contentWatchdog.stop();
13358
13577
  this.adBreak.cancelScheduledRequest();
13359
13578
  this.timing.destroy();
13360
13579
  this.adBreak.clearPendingAdBreak();