avbridge 2.8.3 → 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.
package/dist/element.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkIUSFLVLJ_cjs = require('./chunk-IUSFLVLJ.cjs');
3
+ var chunkYX4AGLNF_cjs = require('./chunk-YX4AGLNF.cjs');
4
4
  require('./chunk-S4WAZC2T.cjs');
5
5
  require('./chunk-ZCUXHW55.cjs');
6
6
  require('./chunk-2IJ66NTD.cjs');
@@ -296,7 +296,7 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
296
296
  this._dispatch("loadstart", {});
297
297
  let player;
298
298
  try {
299
- player = await chunkIUSFLVLJ_cjs.createPlayer({
299
+ player = await chunkYX4AGLNF_cjs.createPlayer({
300
300
  source,
301
301
  target: this._videoEl,
302
302
  // Honor the consumer's preferred initial strategy. "auto" means
@@ -1,4 +1,4 @@
1
- import { a as MediaInput, n as StrategyName, S as StrategyClass, U as UnifiedPlayer, e as AudioTrackInfo, o as SubtitleTrackInfo, D as DiagnosticsSnapshot, s as AvbridgeVideoElementEventMap } from './player-DXEKOky8.cjs';
1
+ import { a as MediaInput, n as StrategyName, S as StrategyClass, U as UnifiedPlayer, e as AudioTrackInfo, o as SubtitleTrackInfo, D as DiagnosticsSnapshot, s as AvbridgeVideoElementEventMap } from './player-BptSJPfn.cjs';
2
2
 
3
3
  /**
4
4
  * `<avbridge-video>` — `HTMLMediaElement`-compatible primitive backed by the
package/dist/element.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as MediaInput, n as StrategyName, S as StrategyClass, U as UnifiedPlayer, e as AudioTrackInfo, o as SubtitleTrackInfo, D as DiagnosticsSnapshot, s as AvbridgeVideoElementEventMap } from './player-DXEKOky8.js';
1
+ import { a as MediaInput, n as StrategyName, S as StrategyClass, U as UnifiedPlayer, e as AudioTrackInfo, o as SubtitleTrackInfo, D as DiagnosticsSnapshot, s as AvbridgeVideoElementEventMap } from './player-BptSJPfn.js';
2
2
 
3
3
  /**
4
4
  * `<avbridge-video>` — `HTMLMediaElement`-compatible primitive backed by the
package/dist/element.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createPlayer } from './chunk-JSQOBUQB.js';
1
+ import { createPlayer } from './chunk-KBWQRGHS.js';
2
2
  import './chunk-5KVLE6YI.js';
3
3
  import './chunk-SR3MPV4D.js';
4
4
  import './chunk-CPJLFFCC.js';
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkQ2VUO52Z_cjs = require('./chunk-Q2VUO52Z.cjs');
4
- var chunkIUSFLVLJ_cjs = require('./chunk-IUSFLVLJ.cjs');
4
+ var chunkYX4AGLNF_cjs = require('./chunk-YX4AGLNF.cjs');
5
5
  var chunkS4WAZC2T_cjs = require('./chunk-S4WAZC2T.cjs');
6
6
  var chunkZCUXHW55_cjs = require('./chunk-ZCUXHW55.cjs');
7
7
  var chunk2IJ66NTD_cjs = require('./chunk-2IJ66NTD.cjs');
@@ -763,31 +763,31 @@ Object.defineProperty(exports, "remux", {
763
763
  });
764
764
  Object.defineProperty(exports, "FALLBACK_AUDIO_CODECS", {
765
765
  enumerable: true,
766
- get: function () { return chunkIUSFLVLJ_cjs.FALLBACK_AUDIO_CODECS; }
766
+ get: function () { return chunkYX4AGLNF_cjs.FALLBACK_AUDIO_CODECS; }
767
767
  });
768
768
  Object.defineProperty(exports, "FALLBACK_VIDEO_CODECS", {
769
769
  enumerable: true,
770
- get: function () { return chunkIUSFLVLJ_cjs.FALLBACK_VIDEO_CODECS; }
770
+ get: function () { return chunkYX4AGLNF_cjs.FALLBACK_VIDEO_CODECS; }
771
771
  });
772
772
  Object.defineProperty(exports, "NATIVE_AUDIO_CODECS", {
773
773
  enumerable: true,
774
- get: function () { return chunkIUSFLVLJ_cjs.NATIVE_AUDIO_CODECS; }
774
+ get: function () { return chunkYX4AGLNF_cjs.NATIVE_AUDIO_CODECS; }
775
775
  });
776
776
  Object.defineProperty(exports, "NATIVE_VIDEO_CODECS", {
777
777
  enumerable: true,
778
- get: function () { return chunkIUSFLVLJ_cjs.NATIVE_VIDEO_CODECS; }
778
+ get: function () { return chunkYX4AGLNF_cjs.NATIVE_VIDEO_CODECS; }
779
779
  });
780
780
  Object.defineProperty(exports, "UnifiedPlayer", {
781
781
  enumerable: true,
782
- get: function () { return chunkIUSFLVLJ_cjs.UnifiedPlayer; }
782
+ get: function () { return chunkYX4AGLNF_cjs.UnifiedPlayer; }
783
783
  });
784
784
  Object.defineProperty(exports, "classify", {
785
785
  enumerable: true,
786
- get: function () { return chunkIUSFLVLJ_cjs.classifyContext; }
786
+ get: function () { return chunkYX4AGLNF_cjs.classifyContext; }
787
787
  });
788
788
  Object.defineProperty(exports, "createPlayer", {
789
789
  enumerable: true,
790
- get: function () { return chunkIUSFLVLJ_cjs.createPlayer; }
790
+ get: function () { return chunkYX4AGLNF_cjs.createPlayer; }
791
791
  });
792
792
  Object.defineProperty(exports, "srtToVtt", {
793
793
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AudioCodec, V as VideoCodec, M as MediaContext, C as Classification, a as MediaInput, T as TransportConfig, b as ConvertOptions, c as ConvertResult, d as TranscodeOptions } from './player-DXEKOky8.cjs';
2
- export { e as AudioTrackInfo, f as ContainerKind, g as CreatePlayerOptions, D as DiagnosticsSnapshot, F as FetchFn, H as HardwareAccelerationHint, O as OutputAudioCodec, h as OutputFormat, i as OutputVideoCodec, P as PlaybackSession, j as PlayerEventMap, k as PlayerEventName, l as Plugin, m as ProgressInfo, S as StrategyClass, n as StrategyName, o as SubtitleTrackInfo, p as TranscodeQuality, U as UnifiedPlayer, q as VideoTrackInfo, r as createPlayer } from './player-DXEKOky8.cjs';
1
+ import { A as AudioCodec, V as VideoCodec, M as MediaContext, C as Classification, a as MediaInput, T as TransportConfig, b as ConvertOptions, c as ConvertResult, d as TranscodeOptions } from './player-BptSJPfn.cjs';
2
+ export { e as AudioTrackInfo, f as ContainerKind, g as CreatePlayerOptions, D as DiagnosticsSnapshot, F as FetchFn, H as HardwareAccelerationHint, O as OutputAudioCodec, h as OutputFormat, i as OutputVideoCodec, P as PlaybackSession, j as PlayerEventMap, k as PlayerEventName, l as Plugin, m as ProgressInfo, S as StrategyClass, n as StrategyName, o as SubtitleTrackInfo, p as TranscodeQuality, U as UnifiedPlayer, q as VideoTrackInfo, r as createPlayer } from './player-BptSJPfn.cjs';
3
3
 
4
4
  /**
5
5
  * Codecs we know `<video>` and MSE support across modern desktop + Android.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AudioCodec, V as VideoCodec, M as MediaContext, C as Classification, a as MediaInput, T as TransportConfig, b as ConvertOptions, c as ConvertResult, d as TranscodeOptions } from './player-DXEKOky8.js';
2
- export { e as AudioTrackInfo, f as ContainerKind, g as CreatePlayerOptions, D as DiagnosticsSnapshot, F as FetchFn, H as HardwareAccelerationHint, O as OutputAudioCodec, h as OutputFormat, i as OutputVideoCodec, P as PlaybackSession, j as PlayerEventMap, k as PlayerEventName, l as Plugin, m as ProgressInfo, S as StrategyClass, n as StrategyName, o as SubtitleTrackInfo, p as TranscodeQuality, U as UnifiedPlayer, q as VideoTrackInfo, r as createPlayer } from './player-DXEKOky8.js';
1
+ import { A as AudioCodec, V as VideoCodec, M as MediaContext, C as Classification, a as MediaInput, T as TransportConfig, b as ConvertOptions, c as ConvertResult, d as TranscodeOptions } from './player-BptSJPfn.js';
2
+ export { e as AudioTrackInfo, f as ContainerKind, g as CreatePlayerOptions, D as DiagnosticsSnapshot, F as FetchFn, H as HardwareAccelerationHint, O as OutputAudioCodec, h as OutputFormat, i as OutputVideoCodec, P as PlaybackSession, j as PlayerEventMap, k as PlayerEventName, l as Plugin, m as ProgressInfo, S as StrategyClass, n as StrategyName, o as SubtitleTrackInfo, p as TranscodeQuality, U as UnifiedPlayer, q as VideoTrackInfo, r as createPlayer } from './player-BptSJPfn.js';
3
3
 
4
4
  /**
5
5
  * Codecs we know `<video>` and MSE support across modern desktop + Android.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mimeForFormat, generateFilename, createOutputFormat } from './chunk-SMH6IOP2.js';
2
2
  export { remux } from './chunk-SMH6IOP2.js';
3
- export { FALLBACK_AUDIO_CODECS, FALLBACK_VIDEO_CODECS, NATIVE_AUDIO_CODECS, NATIVE_VIDEO_CODECS, UnifiedPlayer, classifyContext as classify, createPlayer } from './chunk-JSQOBUQB.js';
3
+ export { FALLBACK_AUDIO_CODECS, FALLBACK_VIDEO_CODECS, NATIVE_AUDIO_CODECS, NATIVE_VIDEO_CODECS, UnifiedPlayer, classifyContext as classify, createPlayer } from './chunk-KBWQRGHS.js';
4
4
  export { srtToVtt } from './chunk-5KVLE6YI.js';
5
5
  import { probe, buildMediabunnySourceFromInput } from './chunk-SR3MPV4D.js';
6
6
  export { probe } from './chunk-SR3MPV4D.js';
@@ -414,6 +414,13 @@ declare class UnifiedPlayer {
414
414
  private stallTimer;
415
415
  private lastProgressTime;
416
416
  private lastProgressPosition;
417
+ /** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
418
+ * (or `webkitDecodedFrameCount` fallback). Used by the silent-video
419
+ * watchdog — catches cases where `currentTime` advances (audio plays)
420
+ * but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
421
+ * via MSE when the decoder actually can't decode HEVC. */
422
+ private lastVideoFrameCount;
423
+ private lastVideoFrameProgressTime;
417
424
  private errorListener;
418
425
  private endedListener;
419
426
  private userIntent;
@@ -414,6 +414,13 @@ declare class UnifiedPlayer {
414
414
  private stallTimer;
415
415
  private lastProgressTime;
416
416
  private lastProgressPosition;
417
+ /** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
418
+ * (or `webkitDecodedFrameCount` fallback). Used by the silent-video
419
+ * watchdog — catches cases where `currentTime` advances (audio plays)
420
+ * but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
421
+ * via MSE when the decoder actually can't decode HEVC. */
422
+ private lastVideoFrameCount;
423
+ private lastVideoFrameProgressTime;
417
424
  private errorListener;
418
425
  private endedListener;
419
426
  private userIntent;
package/dist/player.cjs CHANGED
@@ -512,10 +512,12 @@ function classifyContext(ctx) {
512
512
  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`
513
513
  };
514
514
  }
515
+ const fallbackChain = webCodecsAvailable() ? ["hybrid", "fallback"] : ["fallback"];
515
516
  return {
516
517
  class: "REMUX_CANDIDATE",
517
518
  strategy: "remux",
518
- reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback`
519
+ reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback`,
520
+ fallbackChain
519
521
  };
520
522
  }
521
523
  if (webCodecsAvailable()) {
@@ -3260,6 +3262,29 @@ function registerBuiltins(registry) {
3260
3262
  }
3261
3263
 
3262
3264
  // src/player.ts
3265
+ function readDecodedFrameCount(target) {
3266
+ if (typeof HTMLVideoElement === "undefined" || !(target instanceof HTMLVideoElement)) return 0;
3267
+ const vq = target.getVideoPlaybackQuality;
3268
+ if (typeof vq === "function") {
3269
+ try {
3270
+ return vq.call(target).totalVideoFrames;
3271
+ } catch {
3272
+ }
3273
+ }
3274
+ const legacy = target.webkitDecodedFrameCount;
3275
+ return typeof legacy === "number" ? legacy : 0;
3276
+ }
3277
+ function evaluateDecodeHealth(input) {
3278
+ const timeThreshold = input.timeStallThresholdMs ?? 5e3;
3279
+ const frameThreshold = input.frameStallThresholdMs ?? 3e3;
3280
+ if (!input.timeAdvanced && input.now - input.lastProgressTime > timeThreshold) {
3281
+ return { escalate: true, kind: "time-stall" };
3282
+ }
3283
+ if (input.hasVideoTrack && input.timeAdvanced && !input.framesAdvanced && input.now - input.lastFrameProgressTime > frameThreshold) {
3284
+ return { escalate: true, kind: "silent-video" };
3285
+ }
3286
+ return { escalate: false };
3287
+ }
3263
3288
  var UnifiedPlayer = class _UnifiedPlayer {
3264
3289
  /**
3265
3290
  * @internal Use {@link createPlayer} or {@link UnifiedPlayer.create} instead.
@@ -3285,6 +3310,13 @@ var UnifiedPlayer = class _UnifiedPlayer {
3285
3310
  stallTimer = null;
3286
3311
  lastProgressTime = 0;
3287
3312
  lastProgressPosition = -1;
3313
+ /** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
3314
+ * (or `webkitDecodedFrameCount` fallback). Used by the silent-video
3315
+ * watchdog — catches cases where `currentTime` advances (audio plays)
3316
+ * but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
3317
+ * via MSE when the decoder actually can't decode HEVC. */
3318
+ lastVideoFrameCount = 0;
3319
+ lastVideoFrameProgressTime = 0;
3288
3320
  errorListener = null;
3289
3321
  // Bound so we can removeEventListener in destroy(); without this the
3290
3322
  // listener outlives the player and accumulates on elements that swap
@@ -3529,22 +3561,41 @@ var UnifiedPlayer = class _UnifiedPlayer {
3529
3561
  if (strategy === "native" || strategy === "remux") {
3530
3562
  this.lastProgressPosition = this.options.target.currentTime;
3531
3563
  this.lastProgressTime = performance.now();
3564
+ this.lastVideoFrameCount = readDecodedFrameCount(this.options.target);
3565
+ this.lastVideoFrameProgressTime = performance.now();
3566
+ const hasVideoTrack = (this.mediaContext?.videoTracks.length ?? 0) > 0;
3532
3567
  this.stallTimer = setInterval(() => {
3533
3568
  const t = this.options.target;
3569
+ const now = performance.now();
3534
3570
  if (t.paused || t.ended || t.readyState < 2) {
3535
3571
  this.lastProgressPosition = t.currentTime;
3536
- this.lastProgressTime = performance.now();
3572
+ this.lastProgressTime = now;
3573
+ this.lastVideoFrameCount = readDecodedFrameCount(t);
3574
+ this.lastVideoFrameProgressTime = now;
3537
3575
  return;
3538
3576
  }
3539
- if (t.currentTime !== this.lastProgressPosition) {
3577
+ const timeAdvanced = t.currentTime !== this.lastProgressPosition;
3578
+ const frames = readDecodedFrameCount(t);
3579
+ const framesAdvanced = frames > this.lastVideoFrameCount;
3580
+ const health = evaluateDecodeHealth({
3581
+ hasVideoTrack,
3582
+ timeAdvanced,
3583
+ framesAdvanced,
3584
+ now,
3585
+ lastProgressTime: this.lastProgressTime,
3586
+ lastFrameProgressTime: this.lastVideoFrameProgressTime
3587
+ });
3588
+ if (timeAdvanced) {
3540
3589
  this.lastProgressPosition = t.currentTime;
3541
- this.lastProgressTime = performance.now();
3542
- return;
3590
+ this.lastProgressTime = now;
3543
3591
  }
3544
- if (performance.now() - this.lastProgressTime > 5e3) {
3545
- void this.escalate(
3546
- `${strategy} strategy stalled for 5s at ${t.currentTime.toFixed(1)}s`
3547
- );
3592
+ if (framesAdvanced) {
3593
+ this.lastVideoFrameCount = frames;
3594
+ this.lastVideoFrameProgressTime = now;
3595
+ }
3596
+ if (health.escalate) {
3597
+ 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`;
3598
+ void this.escalate(reason);
3548
3599
  }
3549
3600
  }, 1e3);
3550
3601
  const onError = () => {
@@ -5648,12 +5699,27 @@ var AvbridgePlayerElement = class extends HTMLElement {
5648
5699
  this._fullscreenBtn.innerHTML = fs ? ICON_FULLSCREEN_EXIT : ICON_FULLSCREEN;
5649
5700
  }
5650
5701
  // ── Controls: auto-hide ────────────────────────────────────────────────
5651
- _showControls() {
5702
+ /**
5703
+ * Reveal the auto-hiding chrome (top toolbar + bottom controls) and
5704
+ * re-start the auto-hide timer. Call this from app-level code to
5705
+ * briefly surface the player UI — e.g. to confirm "you just swiped to
5706
+ * this video" in a carousel, or to flash the title on focus change.
5707
+ *
5708
+ * @param durationMs How long the chrome stays visible before fading.
5709
+ * Defaults to the player's normal 3 s auto-hide.
5710
+ * Pointer movement or any other interaction resets
5711
+ * the timer, so a user hovering during the flash
5712
+ * sees no flicker.
5713
+ */
5714
+ showControls(durationMs) {
5652
5715
  this.removeAttribute("data-controls-hidden");
5653
5716
  this._toolbarTop.setAttribute("data-visible", "true");
5654
- this._scheduleHide();
5717
+ this._scheduleHide(durationMs);
5718
+ }
5719
+ _showControls() {
5720
+ this.showControls();
5655
5721
  }
5656
- _scheduleHide() {
5722
+ _scheduleHide(durationMs = CONTROLS_HIDE_MS) {
5657
5723
  if (this._controlsTimer) clearTimeout(this._controlsTimer);
5658
5724
  if (this._state !== "playing" && this._state !== "buffering") return;
5659
5725
  if (this._settingsOpen) return;
@@ -5662,7 +5728,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
5662
5728
  this.setAttribute("data-controls-hidden", "");
5663
5729
  this._toolbarTop.setAttribute("data-visible", "false");
5664
5730
  }
5665
- }, CONTROLS_HIDE_MS);
5731
+ }, durationMs);
5666
5732
  }
5667
5733
  // Strategy is visible in Stats for Nerds, no badge in controls bar.
5668
5734
  // ── Click / tap handling (YouTube delayed-tap pattern) ──────────────────