avbridge 2.8.2 → 2.8.4

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.
@@ -31705,10 +31705,12 @@ function classifyContext(ctx) {
31705
31705
  reason: `${ctx.container} container with ${video.codec}${audio ? "/" + audio.codec : ""}; MSE rejects the remux target mime and WebCodecs is unavailable \u2014 falling back to WASM decode`
31706
31706
  };
31707
31707
  }
31708
+ const fallbackChain = webCodecsAvailable() ? ["hybrid", "fallback"] : ["fallback"];
31708
31709
  return {
31709
31710
  class: "REMUX_CANDIDATE",
31710
31711
  strategy: "remux",
31711
- reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback`
31712
+ reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback`,
31713
+ fallbackChain
31712
31714
  };
31713
31715
  }
31714
31716
  if (webCodecsAvailable()) {
@@ -34465,6 +34467,29 @@ function registerBuiltins(registry) {
34465
34467
  init_subtitles2();
34466
34468
  init_debug();
34467
34469
  init_errors();
34470
+ function readDecodedFrameCount(target) {
34471
+ if (typeof HTMLVideoElement === "undefined" || !(target instanceof HTMLVideoElement)) return 0;
34472
+ const vq = target.getVideoPlaybackQuality;
34473
+ if (typeof vq === "function") {
34474
+ try {
34475
+ return vq.call(target).totalVideoFrames;
34476
+ } catch {
34477
+ }
34478
+ }
34479
+ const legacy = target.webkitDecodedFrameCount;
34480
+ return typeof legacy === "number" ? legacy : 0;
34481
+ }
34482
+ function evaluateDecodeHealth(input) {
34483
+ const timeThreshold = input.timeStallThresholdMs ?? 5e3;
34484
+ const frameThreshold = input.frameStallThresholdMs ?? 3e3;
34485
+ if (!input.timeAdvanced && input.now - input.lastProgressTime > timeThreshold) {
34486
+ return { escalate: true, kind: "time-stall" };
34487
+ }
34488
+ if (input.hasVideoTrack && input.timeAdvanced && !input.framesAdvanced && input.now - input.lastFrameProgressTime > frameThreshold) {
34489
+ return { escalate: true, kind: "silent-video" };
34490
+ }
34491
+ return { escalate: false };
34492
+ }
34468
34493
  var UnifiedPlayer = class _UnifiedPlayer {
34469
34494
  /**
34470
34495
  * @internal Use {@link createPlayer} or {@link UnifiedPlayer.create} instead.
@@ -34490,6 +34515,13 @@ var UnifiedPlayer = class _UnifiedPlayer {
34490
34515
  stallTimer = null;
34491
34516
  lastProgressTime = 0;
34492
34517
  lastProgressPosition = -1;
34518
+ /** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
34519
+ * (or `webkitDecodedFrameCount` fallback). Used by the silent-video
34520
+ * watchdog — catches cases where `currentTime` advances (audio plays)
34521
+ * but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
34522
+ * via MSE when the decoder actually can't decode HEVC. */
34523
+ lastVideoFrameCount = 0;
34524
+ lastVideoFrameProgressTime = 0;
34493
34525
  errorListener = null;
34494
34526
  // Bound so we can removeEventListener in destroy(); without this the
34495
34527
  // listener outlives the player and accumulates on elements that swap
@@ -34734,22 +34766,41 @@ var UnifiedPlayer = class _UnifiedPlayer {
34734
34766
  if (strategy === "native" || strategy === "remux") {
34735
34767
  this.lastProgressPosition = this.options.target.currentTime;
34736
34768
  this.lastProgressTime = performance.now();
34769
+ this.lastVideoFrameCount = readDecodedFrameCount(this.options.target);
34770
+ this.lastVideoFrameProgressTime = performance.now();
34771
+ const hasVideoTrack = (this.mediaContext?.videoTracks.length ?? 0) > 0;
34737
34772
  this.stallTimer = setInterval(() => {
34738
34773
  const t = this.options.target;
34774
+ const now = performance.now();
34739
34775
  if (t.paused || t.ended || t.readyState < 2) {
34740
34776
  this.lastProgressPosition = t.currentTime;
34741
- this.lastProgressTime = performance.now();
34777
+ this.lastProgressTime = now;
34778
+ this.lastVideoFrameCount = readDecodedFrameCount(t);
34779
+ this.lastVideoFrameProgressTime = now;
34742
34780
  return;
34743
34781
  }
34744
- if (t.currentTime !== this.lastProgressPosition) {
34782
+ const timeAdvanced = t.currentTime !== this.lastProgressPosition;
34783
+ const frames = readDecodedFrameCount(t);
34784
+ const framesAdvanced = frames > this.lastVideoFrameCount;
34785
+ const health = evaluateDecodeHealth({
34786
+ hasVideoTrack,
34787
+ timeAdvanced,
34788
+ framesAdvanced,
34789
+ now,
34790
+ lastProgressTime: this.lastProgressTime,
34791
+ lastFrameProgressTime: this.lastVideoFrameProgressTime
34792
+ });
34793
+ if (timeAdvanced) {
34745
34794
  this.lastProgressPosition = t.currentTime;
34746
- this.lastProgressTime = performance.now();
34747
- return;
34795
+ this.lastProgressTime = now;
34748
34796
  }
34749
- if (performance.now() - this.lastProgressTime > 5e3) {
34750
- void this.escalate(
34751
- `${strategy} strategy stalled for 5s at ${t.currentTime.toFixed(1)}s`
34752
- );
34797
+ if (framesAdvanced) {
34798
+ this.lastVideoFrameCount = frames;
34799
+ this.lastVideoFrameProgressTime = now;
34800
+ }
34801
+ if (health.escalate) {
34802
+ const reason = health.kind === "time-stall" ? `${strategy} strategy stalled for 5s at ${t.currentTime.toFixed(1)}s` : `${strategy} strategy: audio is advancing but the video decoder has produced no new frames for 3s \u2014 likely a silent codec failure`;
34803
+ void this.escalate(reason);
34753
34804
  }
34754
34805
  }, 1e3);
34755
34806
  const onError = () => {