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
@@ -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();
@@ -6155,14 +6165,16 @@ function ingestPlayerTracking(licenseKey, channel, body) {
6155
6165
  // src/utils/tracking.ts
6156
6166
  var cachedBrowserId = null;
6157
6167
  function buildAnalyticsContext(config) {
6158
- var _config_contentNetwork, _config_channelName, _config_programTitle;
6168
+ var _config_contentNetwork, _config_channelName, _config_programTitle, _config_src;
6159
6169
  var context = {};
6160
6170
  var contentNetwork = (_config_contentNetwork = config.contentNetwork) === null || _config_contentNetwork === void 0 ? void 0 : _config_contentNetwork.trim();
6161
6171
  var contentChannel = (_config_channelName = config.channelName) === null || _config_channelName === void 0 ? void 0 : _config_channelName.trim();
6162
6172
  var contentTitle = (_config_programTitle = config.programTitle) === null || _config_programTitle === void 0 ? void 0 : _config_programTitle.trim();
6173
+ var hlsUrl = (_config_src = config.src) === null || _config_src === void 0 ? void 0 : _config_src.trim();
6163
6174
  if (contentNetwork) context.contentNetwork = contentNetwork;
6164
6175
  if (contentChannel) context.contentChannel = contentChannel;
6165
6176
  if (contentTitle) context.contentTitle = contentTitle;
6177
+ if (hlsUrl) context.hlsUrl = hlsUrl;
6166
6178
  if (config.debugAdTiming) {
6167
6179
  context.debugAdTiming = true;
6168
6180
  }
@@ -6668,6 +6680,8 @@ function sendAdImpressionTracking(_0, _1) {
6668
6680
  content_channel: context.contentChannel
6669
6681
  } : {}, context.contentTitle ? {
6670
6682
  content_title: context.contentTitle
6683
+ } : {}, context.hlsUrl ? {
6684
+ hls_url: context.hlsUrl
6671
6685
  } : {})
6672
6686
  ]
6673
6687
  })
@@ -12421,6 +12435,181 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
12421
12435
  ]);
12422
12436
  return AdBreakOrchestrator;
12423
12437
  }();
12438
+ // src/player/ContentHealthWatchdog.ts
12439
+ var POLL_INTERVAL_MS = 2e3;
12440
+ var STALL_THRESHOLD_MS = 12e3;
12441
+ var POST_BREAK_GRACE_MS = 5e4;
12442
+ var MAX_BREAK_SUPPRESSION_MS = 10 * 60 * 1e3;
12443
+ var SELF_INFLICTED_PAUSE_WINDOW_MS = 5e3;
12444
+ var HEALTHY_PLAYBACK_SECONDS = 5;
12445
+ var MAX_NORMAL_ADVANCE_PER_TICK_S = 4;
12446
+ var LIGHT_STEP_COOLDOWN_MS = 1e4;
12447
+ var RELOAD_BASE_COOLDOWN_MS = 15e3;
12448
+ var RELOAD_MAX_COOLDOWN_MS = 12e4;
12449
+ var ContentHealthWatchdog = /*#__PURE__*/ function() {
12450
+ function ContentHealthWatchdog(host) {
12451
+ var _this = this;
12452
+ _class_call_check(this, ContentHealthWatchdog);
12453
+ this.host = host;
12454
+ this.armed = false;
12455
+ this.userPaused = false;
12456
+ this.wasInBreak = false;
12457
+ this.breakStartedAt = 0;
12458
+ this.stuckBreakLogged = false;
12459
+ this.graceUntil = 0;
12460
+ this.lastPosition = 0;
12461
+ this.lastProgressAt = 0;
12462
+ this.lastRealPlaybackAt = 0;
12463
+ this.healthySeconds = 0;
12464
+ this.step = 0;
12465
+ this.nextActionAt = 0;
12466
+ this.lastActionAt = 0;
12467
+ this.onPlaying = function() {
12468
+ if (_this.host.isAdPlaying()) return;
12469
+ _this.armed = true;
12470
+ _this.userPaused = false;
12471
+ };
12472
+ this.onPause = function() {
12473
+ if (_this.host.isBreakActive() || _this.host.isAdPlaying() || Date.now() - _this.lastActionAt < SELF_INFLICTED_PAUSE_WINDOW_MS) {
12474
+ return;
12475
+ }
12476
+ _this.userPaused = true;
12477
+ };
12478
+ this.onPlay = function() {
12479
+ _this.userPaused = false;
12480
+ };
12481
+ }
12482
+ _create_class(ContentHealthWatchdog, [
12483
+ {
12484
+ key: "start",
12485
+ value: function start() {
12486
+ var _this = this;
12487
+ if (this.intervalId !== void 0) return;
12488
+ var video = this.host.video;
12489
+ video.addEventListener("playing", this.onPlaying);
12490
+ video.addEventListener("pause", this.onPause);
12491
+ video.addEventListener("play", this.onPlay);
12492
+ this.resetProgress(Date.now());
12493
+ this.intervalId = window.setInterval(function() {
12494
+ return _this.tick();
12495
+ }, POLL_INTERVAL_MS);
12496
+ }
12497
+ },
12498
+ {
12499
+ key: "stop",
12500
+ value: function stop() {
12501
+ if (this.intervalId !== void 0) {
12502
+ clearInterval(this.intervalId);
12503
+ this.intervalId = void 0;
12504
+ }
12505
+ var video = this.host.video;
12506
+ video.removeEventListener("playing", this.onPlaying);
12507
+ video.removeEventListener("pause", this.onPause);
12508
+ video.removeEventListener("play", this.onPlay);
12509
+ this.armed = false;
12510
+ this.step = 0;
12511
+ }
12512
+ },
12513
+ {
12514
+ key: "resetProgress",
12515
+ value: function resetProgress(now) {
12516
+ this.lastPosition = this.host.video.currentTime;
12517
+ this.lastProgressAt = now;
12518
+ }
12519
+ },
12520
+ {
12521
+ key: "tick",
12522
+ value: function tick() {
12523
+ var now = Date.now();
12524
+ var video = this.host.video;
12525
+ var hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
12526
+ if (!this.armed || this.userPaused || hidden || video.ended || !this.host.isLiveStream() || !this.host.hasHlsInstance()) {
12527
+ this.resetProgress(now);
12528
+ return;
12529
+ }
12530
+ var breakActive = this.host.isBreakActive() || this.host.isAdPlaying();
12531
+ if (breakActive && !this.wasInBreak) {
12532
+ this.wasInBreak = true;
12533
+ this.breakStartedAt = now;
12534
+ this.stuckBreakLogged = false;
12535
+ }
12536
+ if (breakActive && now - this.breakStartedAt < MAX_BREAK_SUPPRESSION_MS) {
12537
+ this.resetProgress(now);
12538
+ return;
12539
+ }
12540
+ if (breakActive && !this.stuckBreakLogged) {
12541
+ this.stuckBreakLogged = true;
12542
+ console.warn("[ContentWatchdog] Ad break state active for over ".concat(MAX_BREAK_SUPPRESSION_MS / 6e4, " min — monitoring content anyway"));
12543
+ }
12544
+ if (!breakActive && this.wasInBreak) {
12545
+ this.wasInBreak = false;
12546
+ this.graceUntil = now + POST_BREAK_GRACE_MS;
12547
+ this.resetProgress(now);
12548
+ return;
12549
+ }
12550
+ var currentTime = video.currentTime;
12551
+ var delta = currentTime - this.lastPosition;
12552
+ this.lastPosition = currentTime;
12553
+ if (delta > 0.25 && delta <= MAX_NORMAL_ADVANCE_PER_TICK_S && !video.paused) {
12554
+ this.lastProgressAt = now;
12555
+ this.lastRealPlaybackAt = now;
12556
+ this.healthySeconds += delta;
12557
+ if (this.step > 0 && this.healthySeconds >= HEALTHY_PLAYBACK_SECONDS) {
12558
+ console.log("[ContentWatchdog] Content playback recovered after ".concat(this.step, " recovery step(s)"));
12559
+ this.step = 0;
12560
+ this.nextActionAt = 0;
12561
+ }
12562
+ return;
12563
+ }
12564
+ if (now < this.graceUntil) {
12565
+ this.lastProgressAt = now;
12566
+ return;
12567
+ }
12568
+ if (now - this.lastProgressAt < STALL_THRESHOLD_MS || now < this.nextActionAt) {
12569
+ return;
12570
+ }
12571
+ this.recover(now);
12572
+ }
12573
+ },
12574
+ {
12575
+ key: "recover",
12576
+ value: function recover(now) {
12577
+ var video = this.host.video;
12578
+ 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");
12579
+ var stalledForS = ((now - (this.lastRealPlaybackAt || this.lastProgressAt)) / 1e3).toFixed(0);
12580
+ var fullReloadOnly = this.host.requiresMediaPipelineResetAfterAds();
12581
+ var action = fullReloadOnly || this.step >= 2 ? "reload" : this.step === 0 ? "recover_media_error" : "restart_load";
12582
+ console.warn("[ContentWatchdog] Content stalled for ".concat(stalledForS, "s — ").concat(action, " (step ").concat(this.step + 1, ") — ").concat(state));
12583
+ this.lastActionAt = now;
12584
+ this.healthySeconds = 0;
12585
+ if (action === "recover_media_error") {
12586
+ this.host.recoverMediaError();
12587
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12588
+ } else if (action === "restart_load") {
12589
+ var liveSyncPos = this.host.getLiveSyncPosition();
12590
+ var position = liveSyncPos != null && liveSyncPos > video.currentTime ? liveSyncPos : video.currentTime;
12591
+ if (position !== video.currentTime) {
12592
+ video.currentTime = position;
12593
+ }
12594
+ this.host.restartHlsLoad(position);
12595
+ this.nextActionAt = now + LIGHT_STEP_COOLDOWN_MS;
12596
+ } else {
12597
+ var reloadIndex = fullReloadOnly ? this.step : this.step - 2;
12598
+ var cooldown = Math.min(RELOAD_BASE_COOLDOWN_MS * Math.pow(2, reloadIndex), RELOAD_MAX_COOLDOWN_MS);
12599
+ this.host.reloadLiveStream();
12600
+ this.nextActionAt = now + cooldown;
12601
+ }
12602
+ this.step++;
12603
+ this.resetProgress(now);
12604
+ if (video.paused) {
12605
+ var _video_play;
12606
+ (_video_play = video.play()) === null || _video_play === void 0 ? void 0 : _video_play.catch(function() {});
12607
+ }
12608
+ }
12609
+ }
12610
+ ]);
12611
+ return ContentHealthWatchdog;
12612
+ }();
12424
12613
  // src/player/StormcloudVideoPlayer.ts
12425
12614
  var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12426
12615
  function StormcloudVideoPlayer(config) {
@@ -12551,6 +12740,36 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12551
12740
  return _this.palNonce.sendAdTouch(event);
12552
12741
  }
12553
12742
  }, this.timing, this.adConfig, this.cueManager, this.placeholder);
12743
+ this.contentWatchdog = new ContentHealthWatchdog({
12744
+ video: this.video,
12745
+ isLiveStream: function isLiveStream() {
12746
+ return _this.hlsEngine.isLiveStream;
12747
+ },
12748
+ hasHlsInstance: function hasHlsInstance() {
12749
+ return !!_this.hlsEngine.hls;
12750
+ },
12751
+ isBreakActive: function isBreakActive() {
12752
+ return _this.adBreak.inAdBreak || _this.adBreak.isBreakActive();
12753
+ },
12754
+ isAdPlaying: function isAdPlaying() {
12755
+ return _this.adPlayer.isAdPlaying();
12756
+ },
12757
+ requiresMediaPipelineResetAfterAds: function requiresMediaPipelineResetAfterAds1() {
12758
+ return requiresMediaPipelineResetAfterAds();
12759
+ },
12760
+ getLiveSyncPosition: function getLiveSyncPosition() {
12761
+ return _this.hlsEngine.getLiveSyncPosition();
12762
+ },
12763
+ recoverMediaError: function recoverMediaError() {
12764
+ return _this.hlsEngine.recoverMediaError();
12765
+ },
12766
+ restartHlsLoad: function restartHlsLoad(position) {
12767
+ return _this.hlsEngine.restartLoadAt(position);
12768
+ },
12769
+ reloadLiveStream: function reloadLiveStream() {
12770
+ return _this.hlsEngine.reloadLiveStream();
12771
+ }
12772
+ });
12554
12773
  }
12555
12774
  _create_class(StormcloudVideoPlayer, [
12556
12775
  {
@@ -12871,6 +13090,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12871
13090
  }
12872
13091
  };
12873
13092
  this.video.addEventListener("emptied", this.emptiedHandler);
13093
+ if (this.config.contentStallWatchdog !== false) {
13094
+ this.contentWatchdog.start();
13095
+ }
12874
13096
  }
12875
13097
  },
12876
13098
  {
@@ -13355,6 +13577,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
13355
13577
  key: "destroy",
13356
13578
  value: function destroy() {
13357
13579
  var _this_adPlayer;
13580
+ this.contentWatchdog.stop();
13358
13581
  this.adBreak.cancelScheduledRequest();
13359
13582
  this.timing.destroy();
13360
13583
  this.adBreak.clearPendingAdBreak();