avbridge 2.1.2 → 2.2.1

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 (67) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +98 -71
  3. package/dist/{avi-GNTV5ZOH.cjs → avi-6SJLWIWW.cjs} +19 -4
  4. package/dist/avi-6SJLWIWW.cjs.map +1 -0
  5. package/dist/{avi-V6HYQVR2.js → avi-GCGM7OJI.js} +18 -3
  6. package/dist/avi-GCGM7OJI.js.map +1 -0
  7. package/dist/{chunk-EJH67FXG.js → chunk-5DMTJVIU.js} +99 -3
  8. package/dist/chunk-5DMTJVIU.js.map +1 -0
  9. package/dist/{chunk-3AUGRKPY.js → chunk-DMWARSEF.js} +160 -27
  10. package/dist/chunk-DMWARSEF.js.map +1 -0
  11. package/dist/{chunk-JQH6D4OE.cjs → chunk-G4APZMCP.cjs} +100 -3
  12. package/dist/chunk-G4APZMCP.cjs.map +1 -0
  13. package/dist/{chunk-Y5FYF5KG.cjs → chunk-HZLQNKFN.cjs} +5 -2
  14. package/dist/chunk-HZLQNKFN.cjs.map +1 -0
  15. package/dist/{chunk-PQTZS7OA.js → chunk-ILKDNBSE.js} +5 -2
  16. package/dist/chunk-ILKDNBSE.js.map +1 -0
  17. package/dist/{chunk-DPVIOYGC.cjs → chunk-UF2N5L63.cjs} +164 -31
  18. package/dist/chunk-UF2N5L63.cjs.map +1 -0
  19. package/dist/element-browser.js +276 -21
  20. package/dist/element-browser.js.map +1 -1
  21. package/dist/element.cjs +4 -4
  22. package/dist/element.d.cts +1 -1
  23. package/dist/element.d.ts +1 -1
  24. package/dist/element.js +3 -3
  25. package/dist/index.cjs +18 -18
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +5 -5
  29. package/dist/libav-loader-27RDIN2I.js +3 -0
  30. package/dist/{libav-loader-XKH2TKUW.js.map → libav-loader-27RDIN2I.js.map} +1 -1
  31. package/dist/libav-loader-IV4AJ2HW.cjs +12 -0
  32. package/dist/{libav-loader-6APXVNIV.cjs.map → libav-loader-IV4AJ2HW.cjs.map} +1 -1
  33. package/dist/{player-BdtUG4rh.d.cts → player-U2NPmFvA.d.cts} +4 -3
  34. package/dist/{player-BdtUG4rh.d.ts → player-U2NPmFvA.d.ts} +4 -3
  35. package/dist/source-CN43EI7Z.cjs +28 -0
  36. package/dist/{source-SC6ZEQYR.cjs.map → source-CN43EI7Z.cjs.map} +1 -1
  37. package/dist/source-FFZ7TW2B.js +3 -0
  38. package/dist/{source-ZFS4H7J3.js.map → source-FFZ7TW2B.js.map} +1 -1
  39. package/package.json +1 -1
  40. package/src/classify/rules.ts +9 -2
  41. package/src/player.ts +46 -17
  42. package/src/probe/avi.ts +8 -1
  43. package/src/strategies/fallback/audio-output.ts +25 -3
  44. package/src/strategies/fallback/decoder.ts +96 -8
  45. package/src/strategies/fallback/index.ts +98 -6
  46. package/src/strategies/fallback/libav-loader.ts +12 -0
  47. package/src/strategies/fallback/video-renderer.ts +5 -1
  48. package/src/strategies/hybrid/index.ts +9 -1
  49. package/src/strategies/remux/index.ts +13 -1
  50. package/src/strategies/remux/pipeline.ts +6 -0
  51. package/src/types.ts +10 -1
  52. package/src/util/debug.ts +131 -0
  53. package/src/util/source.ts +4 -0
  54. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  55. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  56. package/dist/avi-GNTV5ZOH.cjs.map +0 -1
  57. package/dist/avi-V6HYQVR2.js.map +0 -1
  58. package/dist/chunk-3AUGRKPY.js.map +0 -1
  59. package/dist/chunk-DPVIOYGC.cjs.map +0 -1
  60. package/dist/chunk-EJH67FXG.js.map +0 -1
  61. package/dist/chunk-JQH6D4OE.cjs.map +0 -1
  62. package/dist/chunk-PQTZS7OA.js.map +0 -1
  63. package/dist/chunk-Y5FYF5KG.cjs.map +0 -1
  64. package/dist/libav-loader-6APXVNIV.cjs +0 -12
  65. package/dist/libav-loader-XKH2TKUW.js +0 -3
  66. package/dist/source-SC6ZEQYR.cjs +0 -28
  67. package/dist/source-ZFS4H7J3.js +0 -3
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkY5FYF5KG_cjs = require('./chunk-Y5FYF5KG.cjs');
4
- var chunkJQH6D4OE_cjs = require('./chunk-JQH6D4OE.cjs');
3
+ var chunkHZLQNKFN_cjs = require('./chunk-HZLQNKFN.cjs');
4
+ var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
5
5
  var chunkNZU7W256_cjs = require('./chunk-NZU7W256.cjs');
6
6
 
7
7
  // src/probe/mediabunny.ts
@@ -181,8 +181,8 @@ var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
181
181
  "mpegts"
182
182
  ]);
183
183
  async function probe(source) {
184
- const normalized = await chunkY5FYF5KG_cjs.normalizeSource(source);
185
- const sniffed = await chunkY5FYF5KG_cjs.sniffNormalizedSource(normalized);
184
+ const normalized = await chunkHZLQNKFN_cjs.normalizeSource(source);
185
+ const sniffed = await chunkHZLQNKFN_cjs.sniffNormalizedSource(normalized);
186
186
  if (MEDIABUNNY_CONTAINERS.has(sniffed)) {
187
187
  try {
188
188
  return await probeWithMediabunny(normalized, sniffed);
@@ -192,7 +192,7 @@ async function probe(source) {
192
192
  mediabunnyErr.message
193
193
  );
194
194
  try {
195
- const { probeWithLibav } = await import('./avi-GNTV5ZOH.cjs');
195
+ const { probeWithLibav } = await import('./avi-6SJLWIWW.cjs');
196
196
  return await probeWithLibav(normalized, sniffed);
197
197
  } catch (libavErr) {
198
198
  const mbMsg = mediabunnyErr.message || String(mediabunnyErr);
@@ -204,7 +204,7 @@ async function probe(source) {
204
204
  }
205
205
  }
206
206
  try {
207
- const { probeWithLibav } = await import('./avi-GNTV5ZOH.cjs');
207
+ const { probeWithLibav } = await import('./avi-6SJLWIWW.cjs');
208
208
  return await probeWithLibav(normalized, sniffed);
209
209
  } catch (err) {
210
210
  const inner = err instanceof Error ? err.message : String(err);
@@ -290,8 +290,29 @@ var NATIVE_AUDIO_CODECS = /* @__PURE__ */ new Set([
290
290
  "vorbis",
291
291
  "flac"
292
292
  ]);
293
- var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set(["wmv3", "vc1", "mpeg4", "rv40", "mpeg2", "mpeg1", "theora"]);
294
- var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set(["wmav2", "wmapro", "ac3", "eac3"]);
293
+ var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set([
294
+ "wmv3",
295
+ "vc1",
296
+ "mpeg4",
297
+ "rv10",
298
+ "rv20",
299
+ "rv30",
300
+ "rv40",
301
+ "mpeg2",
302
+ "mpeg1",
303
+ "theora"
304
+ ]);
305
+ var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set([
306
+ "wmav2",
307
+ "wmapro",
308
+ "ac3",
309
+ "eac3",
310
+ "cook",
311
+ "ra_144",
312
+ "ra_288",
313
+ "sipr",
314
+ "atrac3"
315
+ ]);
295
316
  var NATIVE_CONTAINERS = /* @__PURE__ */ new Set([
296
317
  "mp4",
297
318
  "mov",
@@ -1051,6 +1072,10 @@ async function createRemuxPipeline(ctx, video) {
1051
1072
  console.error("[avbridge] remux pipeline reseek failed:", err);
1052
1073
  });
1053
1074
  },
1075
+ setAutoPlay(autoPlay) {
1076
+ pendingAutoPlay = autoPlay;
1077
+ if (sink) sink.setPlayOnSeek(autoPlay);
1078
+ },
1054
1079
  async destroy() {
1055
1080
  destroyed = true;
1056
1081
  pumpToken++;
@@ -1091,7 +1116,11 @@ async function createRemuxSession(context, video) {
1091
1116
  await pipeline.start(video.currentTime || 0, true);
1092
1117
  return;
1093
1118
  }
1094
- await video.play();
1119
+ pipeline.setAutoPlay(true);
1120
+ try {
1121
+ await video.play();
1122
+ } catch {
1123
+ }
1095
1124
  },
1096
1125
  pause() {
1097
1126
  wantPlay = false;
@@ -1139,7 +1168,7 @@ var VideoRenderer = class {
1139
1168
  this.resolveFirstFrame = resolve;
1140
1169
  });
1141
1170
  this.canvas = document.createElement("canvas");
1142
- this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;";
1171
+ this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;object-fit:contain;";
1143
1172
  const parent = target.parentElement ?? target.parentNode;
1144
1173
  if (parent && parent instanceof HTMLElement) {
1145
1174
  if (getComputedStyle(parent).position === "static") {
@@ -1381,9 +1410,13 @@ var AudioOutput = class {
1381
1410
  const node = this.ctx.createBufferSource();
1382
1411
  node.buffer = buffer;
1383
1412
  node.connect(this.gain);
1384
- const ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
1385
- const safeStart = Math.max(ctxStart, this.ctx.currentTime);
1386
- node.start(safeStart);
1413
+ let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
1414
+ if (ctxStart < this.ctx.currentTime) {
1415
+ this.ctxTimeAtAnchor = this.ctx.currentTime;
1416
+ this.mediaTimeOfAnchor = this.mediaTimeOfNext;
1417
+ ctxStart = this.ctx.currentTime;
1418
+ }
1419
+ node.start(ctxStart);
1387
1420
  this.mediaTimeOfNext += frameCount / sampleRate;
1388
1421
  this.framesScheduled++;
1389
1422
  }
@@ -1485,7 +1518,7 @@ var AudioOutput = class {
1485
1518
  // src/strategies/hybrid/decoder.ts
1486
1519
  async function startHybridDecoder(opts) {
1487
1520
  const variant = chunkNZU7W256_cjs.pickLibavVariant(opts.context);
1488
- const libav = await chunkJQH6D4OE_cjs.loadLibav(variant);
1521
+ const libav = await chunkG4APZMCP_cjs.loadLibav(variant);
1489
1522
  const bridge = await loadBridge();
1490
1523
  const { prepareLibavInput } = await import('./libav-http-reader-FPYDBMYK.cjs');
1491
1524
  const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source);
@@ -1925,7 +1958,7 @@ async function loadBridge() {
1925
1958
  var READY_AUDIO_BUFFER_SECONDS = 0.3;
1926
1959
  var READY_TIMEOUT_SECONDS = 10;
1927
1960
  async function createHybridSession(ctx, target) {
1928
- const { normalizeSource: normalizeSource2 } = await import('./source-SC6ZEQYR.cjs');
1961
+ const { normalizeSource: normalizeSource2 } = await import('./source-CN43EI7Z.cjs');
1929
1962
  const source = await normalizeSource2(ctx.source);
1930
1963
  const fps = ctx.videoTracks[0]?.fps ?? 30;
1931
1964
  const audio = new AudioOutput();
@@ -1951,6 +1984,10 @@ async function createHybridSession(ctx, target) {
1951
1984
  void doSeek(v);
1952
1985
  }
1953
1986
  });
1987
+ Object.defineProperty(target, "paused", {
1988
+ configurable: true,
1989
+ get: () => !audio.isPlaying()
1990
+ });
1954
1991
  if (ctx.duration && Number.isFinite(ctx.duration)) {
1955
1992
  Object.defineProperty(target, "duration", {
1956
1993
  configurable: true,
@@ -2015,6 +2052,7 @@ async function createHybridSession(ctx, target) {
2015
2052
  try {
2016
2053
  delete target.currentTime;
2017
2054
  delete target.duration;
2055
+ delete target.paused;
2018
2056
  } catch {
2019
2057
  }
2020
2058
  },
@@ -2027,7 +2065,7 @@ async function createHybridSession(ctx, target) {
2027
2065
  // src/strategies/fallback/decoder.ts
2028
2066
  async function startDecoder(opts) {
2029
2067
  const variant = chunkNZU7W256_cjs.pickLibavVariant(opts.context);
2030
- const libav = await chunkJQH6D4OE_cjs.loadLibav(variant);
2068
+ const libav = await chunkG4APZMCP_cjs.loadLibav(variant);
2031
2069
  const bridge = await loadBridge2();
2032
2070
  const { prepareLibavInput } = await import('./libav-http-reader-FPYDBMYK.cjs');
2033
2071
  const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source);
@@ -2092,6 +2130,10 @@ async function startDecoder(opts) {
2092
2130
  let packetsRead = 0;
2093
2131
  let videoFramesDecoded = 0;
2094
2132
  let audioFramesDecoded = 0;
2133
+ let watchdogFirstFrameMs = 0;
2134
+ let watchdogSlowSinceMs = 0;
2135
+ let watchdogSlowWarned = false;
2136
+ let watchdogOverflowWarned = false;
2095
2137
  let syntheticVideoUs = 0;
2096
2138
  let syntheticAudioUs = 0;
2097
2139
  const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
@@ -2112,14 +2154,47 @@ async function startDecoder(opts) {
2112
2154
  if (myToken !== pumpToken || destroyed) return;
2113
2155
  const videoPackets = videoStream ? packets[videoStream.index] : void 0;
2114
2156
  const audioPackets = audioStream ? packets[audioStream.index] : void 0;
2115
- if (videoDec && videoPackets && videoPackets.length > 0) {
2116
- await decodeVideoBatch(videoPackets, myToken);
2117
- }
2118
- if (myToken !== pumpToken || destroyed) return;
2119
2157
  if (audioDec && audioPackets && audioPackets.length > 0) {
2120
2158
  await decodeAudioBatch(audioPackets, myToken);
2121
2159
  }
2160
+ if (myToken !== pumpToken || destroyed) return;
2161
+ if (videoDec && videoPackets && videoPackets.length > 0) {
2162
+ await decodeVideoBatch(videoPackets, myToken);
2163
+ }
2122
2164
  packetsRead += (videoPackets?.length ?? 0) + (audioPackets?.length ?? 0);
2165
+ if (videoFramesDecoded > 0) {
2166
+ if (watchdogFirstFrameMs === 0) {
2167
+ watchdogFirstFrameMs = performance.now();
2168
+ }
2169
+ const elapsedSinceFirst = (performance.now() - watchdogFirstFrameMs) / 1e3;
2170
+ if (elapsedSinceFirst > 1 && !watchdogSlowWarned) {
2171
+ const expectedFrames = elapsedSinceFirst * videoFps;
2172
+ const ratio = videoFramesDecoded / expectedFrames;
2173
+ if (ratio < 0.6) {
2174
+ if (watchdogSlowSinceMs === 0) watchdogSlowSinceMs = performance.now();
2175
+ if ((performance.now() - watchdogSlowSinceMs) / 1e3 > 5) {
2176
+ watchdogSlowWarned = true;
2177
+ console.warn(
2178
+ "[avbridge:decode-rate]",
2179
+ `decoder is running slower than realtime: ${videoFramesDecoded} frames in ${elapsedSinceFirst.toFixed(1)}s (${(videoFramesDecoded / elapsedSinceFirst).toFixed(1)} fps vs ${videoFps} fps source \u2014 ${(ratio * 100).toFixed(0)}% of realtime). Playback will stutter. Typical causes: software decode of a codec with no WebCodecs support (rv40, mpeg4 @ 720p+, wmv3), or a resolution too large for single-threaded WASM to keep up with.`
2180
+ );
2181
+ }
2182
+ } else {
2183
+ watchdogSlowSinceMs = 0;
2184
+ }
2185
+ }
2186
+ if (!watchdogOverflowWarned && videoFramesDecoded > 100) {
2187
+ const rendererStats = opts.renderer.stats();
2188
+ const overflow = rendererStats.framesDroppedOverflow ?? 0;
2189
+ if (overflow / videoFramesDecoded > 0.1) {
2190
+ watchdogOverflowWarned = true;
2191
+ console.warn(
2192
+ "[avbridge:overflow-drop]",
2193
+ `renderer is dropping ${overflow}/${videoFramesDecoded} frames (${(overflow / videoFramesDecoded * 100).toFixed(0)}%) because the decoder is producing bursts faster than the canvas can drain. Symptom: choppy playback despite decoder keeping up on average. Fix would be smaller read batches in the pump loop or a lower queueHighWater cap \u2014 see src/strategies/fallback/decoder.ts.`
2194
+ );
2195
+ }
2196
+ }
2197
+ }
2123
2198
  while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
2124
2199
  await new Promise((r) => setTimeout(r, 50));
2125
2200
  }
@@ -2446,10 +2521,10 @@ async function loadBridge2() {
2446
2521
  }
2447
2522
 
2448
2523
  // src/strategies/fallback/index.ts
2449
- var READY_AUDIO_BUFFER_SECONDS2 = 0.3;
2450
- var READY_TIMEOUT_SECONDS2 = 10;
2524
+ var READY_AUDIO_BUFFER_SECONDS2 = 0.04;
2525
+ var READY_TIMEOUT_SECONDS2 = 3;
2451
2526
  async function createFallbackSession(ctx, target) {
2452
- const { normalizeSource: normalizeSource2 } = await import('./source-SC6ZEQYR.cjs');
2527
+ const { normalizeSource: normalizeSource2 } = await import('./source-CN43EI7Z.cjs');
2453
2528
  const source = await normalizeSource2(ctx.source);
2454
2529
  const fps = ctx.videoTracks[0]?.fps ?? 30;
2455
2530
  const audio = new AudioOutput();
@@ -2475,6 +2550,10 @@ async function createFallbackSession(ctx, target) {
2475
2550
  void doSeek(v);
2476
2551
  }
2477
2552
  });
2553
+ Object.defineProperty(target, "paused", {
2554
+ configurable: true,
2555
+ get: () => !audio.isPlaying()
2556
+ });
2478
2557
  if (ctx.duration && Number.isFinite(ctx.duration)) {
2479
2558
  Object.defineProperty(target, "duration", {
2480
2559
  configurable: true,
@@ -2483,12 +2562,36 @@ async function createFallbackSession(ctx, target) {
2483
2562
  }
2484
2563
  async function waitForBuffer() {
2485
2564
  const start = performance.now();
2565
+ let firstFrameAtMs = 0;
2566
+ chunkG4APZMCP_cjs.dbg.info(
2567
+ "cold-start",
2568
+ `gate entry: want audio \u2265 ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms + 1 frame`
2569
+ );
2486
2570
  while (true) {
2487
- const audioReady = audio.isNoAudio() || audio.bufferAhead() >= READY_AUDIO_BUFFER_SECONDS2;
2488
- if (audioReady && renderer.hasFrames()) {
2571
+ const audioAhead = audio.isNoAudio() ? Infinity : audio.bufferAhead();
2572
+ const audioReady = audio.isNoAudio() || audioAhead >= READY_AUDIO_BUFFER_SECONDS2;
2573
+ const hasFrames = renderer.hasFrames();
2574
+ const nowMs = performance.now();
2575
+ if (hasFrames && firstFrameAtMs === 0) firstFrameAtMs = nowMs;
2576
+ if (audioReady && hasFrames) {
2577
+ chunkG4APZMCP_cjs.dbg.info(
2578
+ "cold-start",
2579
+ `gate satisfied in ${(nowMs - start).toFixed(0)}ms (audio=${(audioAhead * 1e3).toFixed(0)}ms, frames=${renderer.queueDepth()})`
2580
+ );
2489
2581
  return;
2490
2582
  }
2491
- if ((performance.now() - start) / 1e3 > READY_TIMEOUT_SECONDS2) {
2583
+ if (hasFrames && firstFrameAtMs > 0 && nowMs - firstFrameAtMs >= 500) {
2584
+ chunkG4APZMCP_cjs.dbg.info(
2585
+ "cold-start",
2586
+ `gate released on video-only grace at ${(nowMs - start).toFixed(0)}ms (frames=${renderer.queueDepth()}, audio=${(audioAhead * 1e3).toFixed(0)}ms \u2014 demuxer hasn't delivered audio packets yet, starting anyway and letting the audio scheduler catch up at its media-time anchor)`
2587
+ );
2588
+ return;
2589
+ }
2590
+ if ((nowMs - start) / 1e3 > READY_TIMEOUT_SECONDS2) {
2591
+ chunkG4APZMCP_cjs.dbg.diag(
2592
+ "cold-start",
2593
+ `gate TIMEOUT after ${READY_TIMEOUT_SECONDS2}s \u2014 audio=${(audioAhead * 1e3).toFixed(0)}ms (needed ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms), frames=${renderer.queueDepth()} (needed \u22651). Decoder produced nothing in ${READY_TIMEOUT_SECONDS2}s \u2014 either a corrupt source, a missing codec, or WASM is catastrophically slow on this file. Check getDiagnostics().runtime for decode counters.`
2594
+ );
2492
2595
  return;
2493
2596
  }
2494
2597
  await new Promise((r) => setTimeout(r, 50));
@@ -2536,6 +2639,7 @@ async function createFallbackSession(ctx, target) {
2536
2639
  try {
2537
2640
  delete target.currentTime;
2538
2641
  delete target.duration;
2642
+ delete target.paused;
2539
2643
  } catch {
2540
2644
  }
2541
2645
  },
@@ -2678,6 +2782,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
2678
2782
  lastProgressTime = 0;
2679
2783
  lastProgressPosition = -1;
2680
2784
  errorListener = null;
2785
+ // Bound so we can removeEventListener in destroy(); without this the
2786
+ // listener outlives the player and accumulates on elements that swap
2787
+ // source (e.g. <avbridge-video>).
2788
+ endedListener = null;
2681
2789
  // Serializes escalation / setStrategy calls
2682
2790
  switchingPromise = Promise.resolve();
2683
2791
  // Owns blob URLs created during sidecar discovery + SRT->VTT conversion.
@@ -2703,8 +2811,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
2703
2811
  return player;
2704
2812
  }
2705
2813
  async bootstrap() {
2814
+ const bootstrapStart = performance.now();
2706
2815
  try {
2707
- const ctx = await probe(this.options.source);
2816
+ chunkG4APZMCP_cjs.dbg.info("bootstrap", "start");
2817
+ const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => probe(this.options.source));
2818
+ chunkG4APZMCP_cjs.dbg.info(
2819
+ "probe",
2820
+ `container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
2821
+ );
2708
2822
  this.diag.recordProbe(ctx);
2709
2823
  this.mediaContext = ctx;
2710
2824
  if (this.options.subtitles) {
@@ -2730,6 +2844,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
2730
2844
  }
2731
2845
  }
2732
2846
  const decision = this.options.initialStrategy ? buildInitialDecision(this.options.initialStrategy, ctx) : classifyContext(ctx);
2847
+ chunkG4APZMCP_cjs.dbg.info(
2848
+ "classify",
2849
+ `strategy=${decision.strategy} class=${decision.class} reason="${decision.reason}"` + (decision.fallbackChain ? ` fallback=${decision.fallbackChain.join("\u2192")}` : "")
2850
+ );
2733
2851
  this.classification = decision;
2734
2852
  this.diag.recordClassification(decision);
2735
2853
  this.emitter.emitSticky("strategy", {
@@ -2753,8 +2871,17 @@ var UnifiedPlayer = class _UnifiedPlayer {
2753
2871
  subtitle: ctx.subtitleTracks
2754
2872
  });
2755
2873
  this.startTimeupdateLoop();
2756
- this.options.target.addEventListener("ended", () => this.emitter.emit("ended", void 0));
2874
+ this.endedListener = () => this.emitter.emit("ended", void 0);
2875
+ this.options.target.addEventListener("ended", this.endedListener);
2757
2876
  this.emitter.emitSticky("ready", void 0);
2877
+ const bootstrapElapsed = performance.now() - bootstrapStart;
2878
+ chunkG4APZMCP_cjs.dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
2879
+ if (bootstrapElapsed > 5e3) {
2880
+ console.warn(
2881
+ "[avbridge:bootstrap]",
2882
+ `total bootstrap time ${bootstrapElapsed.toFixed(0)}ms \u2014 unusually slow. Enable globalThis.AVBRIDGE_DEBUG for a per-phase breakdown.`
2883
+ );
2884
+ }
2758
2885
  } catch (err) {
2759
2886
  const e = err instanceof Error ? err : new Error(String(err));
2760
2887
  this.diag.recordError(e);
@@ -3031,6 +3158,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
3031
3158
  this.timeupdateInterval = null;
3032
3159
  }
3033
3160
  this.clearSupervisor();
3161
+ if (this.endedListener) {
3162
+ this.options.target.removeEventListener("ended", this.endedListener);
3163
+ this.endedListener = null;
3164
+ }
3034
3165
  if (this.session) {
3035
3166
  await this.session.destroy();
3036
3167
  this.session = null;
@@ -3045,11 +3176,13 @@ async function createPlayer(options) {
3045
3176
  function buildInitialDecision(initial, ctx) {
3046
3177
  const natural = classifyContext(ctx);
3047
3178
  const cls = strategyToClass(initial, natural);
3179
+ const inherited = natural.fallbackChain ?? defaultFallbackChain(initial);
3180
+ const fallbackChain = inherited.filter((s) => s !== initial);
3048
3181
  return {
3049
3182
  class: cls,
3050
3183
  strategy: initial,
3051
3184
  reason: `initial strategy "${initial}" requested via options.initialStrategy`,
3052
- fallbackChain: natural.fallbackChain ?? defaultFallbackChain(initial)
3185
+ fallbackChain
3053
3186
  };
3054
3187
  }
3055
3188
  function strategyToClass(strategy, natural) {
@@ -3086,5 +3219,5 @@ exports.classifyContext = classifyContext;
3086
3219
  exports.createPlayer = createPlayer;
3087
3220
  exports.probe = probe;
3088
3221
  exports.srtToVtt = srtToVtt;
3089
- //# sourceMappingURL=chunk-DPVIOYGC.cjs.map
3090
- //# sourceMappingURL=chunk-DPVIOYGC.cjs.map
3222
+ //# sourceMappingURL=chunk-UF2N5L63.cjs.map
3223
+ //# sourceMappingURL=chunk-UF2N5L63.cjs.map