avbridge 2.8.4 → 2.10.0

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 (79) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/README.md +74 -1
  3. package/dist/{avi-F6WZJK5T.cjs → avi-2ILLBNPQ.cjs} +8 -2
  4. package/dist/avi-2ILLBNPQ.cjs.map +1 -0
  5. package/dist/{avi-W6L3BTWU.cjs → avi-B5CQYB7L.cjs} +8 -2
  6. package/dist/avi-B5CQYB7L.cjs.map +1 -0
  7. package/dist/{avi-2JPBSHGA.js → avi-JXU4GQL2.js} +8 -2
  8. package/dist/avi-JXU4GQL2.js.map +1 -0
  9. package/dist/{avi-NJXAXUXK.js → avi-RWWPN2PR.js} +8 -2
  10. package/dist/avi-RWWPN2PR.js.map +1 -0
  11. package/dist/{chunk-X2K3GIWE.js → chunk-2NSOOMXW.js} +14 -3
  12. package/dist/chunk-2NSOOMXW.js.map +1 -0
  13. package/dist/{chunk-KBWQRGHS.js → chunk-3GKM5DFM.js} +119 -8
  14. package/dist/chunk-3GKM5DFM.js.map +1 -0
  15. package/dist/{chunk-ZCUXHW55.cjs → chunk-BYGZN4Z5.cjs} +5 -5
  16. package/dist/{chunk-ZCUXHW55.cjs.map → chunk-BYGZN4Z5.cjs.map} +1 -1
  17. package/dist/{chunk-SMH6IOP2.js → chunk-CL6UEUQF.js} +4 -4
  18. package/dist/{chunk-SMH6IOP2.js.map → chunk-CL6UEUQF.js.map} +1 -1
  19. package/dist/{chunk-SR3MPV4D.js → chunk-GYIJU44C.js} +5 -5
  20. package/dist/{chunk-SR3MPV4D.js.map → chunk-GYIJU44C.js.map} +1 -1
  21. package/dist/{chunk-CPZ7PXAM.cjs → chunk-L7A3ECI2.cjs} +14 -2
  22. package/dist/chunk-L7A3ECI2.cjs.map +1 -0
  23. package/dist/{chunk-YX4AGLNF.cjs → chunk-NQULEIA3.cjs} +129 -18
  24. package/dist/chunk-NQULEIA3.cjs.map +1 -0
  25. package/dist/{chunk-Q2VUO52Z.cjs → chunk-OTFS7DC4.cjs} +12 -12
  26. package/dist/{chunk-Q2VUO52Z.cjs.map → chunk-OTFS7DC4.cjs.map} +1 -1
  27. package/dist/element-browser.js +144 -10
  28. package/dist/element-browser.js.map +1 -1
  29. package/dist/element.cjs +16 -10
  30. package/dist/element.cjs.map +1 -1
  31. package/dist/element.d.cts +11 -6
  32. package/dist/element.d.ts +11 -6
  33. package/dist/element.js +15 -9
  34. package/dist/element.js.map +1 -1
  35. package/dist/index.cjs +20 -20
  36. package/dist/index.d.cts +2 -2
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.js +8 -8
  39. package/dist/libav-demux-3N5Y3VQA.cjs +31 -0
  40. package/dist/{libav-demux-H2GS46GH.cjs.map → libav-demux-3N5Y3VQA.cjs.map} +1 -1
  41. package/dist/libav-demux-JXD4OTLM.js +6 -0
  42. package/dist/{libav-demux-OWZ4T2YW.js.map → libav-demux-JXD4OTLM.js.map} +1 -1
  43. package/dist/{player-BptSJPfn.d.cts → player-DDdNVFDv.d.cts} +24 -2
  44. package/dist/{player-BptSJPfn.d.ts → player-DDdNVFDv.d.ts} +24 -2
  45. package/dist/player.cjs +413 -117
  46. package/dist/player.cjs.map +1 -1
  47. package/dist/player.d.cts +44 -11
  48. package/dist/player.d.ts +44 -11
  49. package/dist/player.js +413 -117
  50. package/dist/player.js.map +1 -1
  51. package/dist/{remux-WBYIZBBX.js → remux-56V7LDAD.js} +5 -5
  52. package/dist/{remux-WBYIZBBX.js.map → remux-56V7LDAD.js.map} +1 -1
  53. package/dist/{remux-OBSMIENG.cjs → remux-KUS5GIL6.cjs} +10 -10
  54. package/dist/{remux-OBSMIENG.cjs.map → remux-KUS5GIL6.cjs.map} +1 -1
  55. package/package.json +1 -1
  56. package/src/classify/rules.ts +2 -0
  57. package/src/element/avbridge-player.ts +172 -86
  58. package/src/element/avbridge-video.ts +22 -6
  59. package/src/element/player-styles.ts +149 -34
  60. package/src/index.ts +1 -0
  61. package/src/probe/avi.ts +2 -0
  62. package/src/strategies/fallback/audio-output.ts +29 -4
  63. package/src/strategies/fallback/decoder.ts +30 -0
  64. package/src/strategies/fallback/index.ts +42 -0
  65. package/src/strategies/hybrid/decoder.ts +35 -0
  66. package/src/strategies/hybrid/index.ts +26 -0
  67. package/src/strategies/remux/index.ts +8 -0
  68. package/src/types.ts +31 -0
  69. package/src/util/libav-demux.ts +26 -0
  70. package/dist/avi-2JPBSHGA.js.map +0 -1
  71. package/dist/avi-F6WZJK5T.cjs.map +0 -1
  72. package/dist/avi-NJXAXUXK.js.map +0 -1
  73. package/dist/avi-W6L3BTWU.cjs.map +0 -1
  74. package/dist/chunk-CPZ7PXAM.cjs.map +0 -1
  75. package/dist/chunk-KBWQRGHS.js.map +0 -1
  76. package/dist/chunk-X2K3GIWE.js.map +0 -1
  77. package/dist/chunk-YX4AGLNF.cjs.map +0 -1
  78. package/dist/libav-demux-H2GS46GH.cjs +0 -27
  79. package/dist/libav-demux-OWZ4T2YW.js +0 -6
@@ -1,7 +1,7 @@
1
1
  import { SubtitleResourceBag, discoverSidecars, attachSubtitleTracks, SubtitleOverlay } from './chunk-5KVLE6YI.js';
2
- import { probe, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, avbridgeAudioToMediabunny } from './chunk-SR3MPV4D.js';
2
+ import { probe, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, avbridgeAudioToMediabunny } from './chunk-GYIJU44C.js';
3
3
  import { AvbridgeError, ERR_ALL_STRATEGIES_EXHAUSTED, ERR_PLAYER_NOT_READY, ERR_MSE_NOT_SUPPORTED, ERR_MSE_CODEC_NOT_SUPPORTED } from './chunk-CPJLFFCC.js';
4
- import { sanitizePacketTimestamp, sanitizeFrameTimestamp, libavFrameToInterleavedFloat32 } from './chunk-X2K3GIWE.js';
4
+ import { packetPtsSec, sanitizePacketTimestamp, sanitizeFrameTimestamp, libavFrameToInterleavedFloat32 } from './chunk-2NSOOMXW.js';
5
5
  import { dbg, loadLibav } from './chunk-5DMTJVIU.js';
6
6
  import { pickLibavVariant } from './chunk-5YAWWKA3.js';
7
7
 
@@ -90,7 +90,13 @@ var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set([
90
90
  "rv40",
91
91
  "mpeg2",
92
92
  "mpeg1",
93
- "theora"
93
+ "theora",
94
+ "dv",
95
+ "hq_hqa",
96
+ "rawvideo",
97
+ "qtrle",
98
+ "png",
99
+ "vp6f"
94
100
  ]);
95
101
  var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set([
96
102
  "wmav2",
@@ -974,6 +980,12 @@ async function createRemuxSession(context, video) {
974
980
  }
975
981
  const wasPlaying = !video.paused;
976
982
  await pipeline.seek(time, wasPlaying || wantPlay);
983
+ queueMicrotask(() => {
984
+ try {
985
+ video.dispatchEvent(new Event("seeked"));
986
+ } catch {
987
+ }
988
+ });
977
989
  },
978
990
  async setAudioTrack(id) {
979
991
  if (!context.audioTracks.some((t) => t.id === id)) {
@@ -1362,6 +1374,10 @@ var AudioOutput = class {
1362
1374
  _volume = 1;
1363
1375
  /** User-set muted flag. When true, gain is forced to 0. */
1364
1376
  _muted = false;
1377
+ /** Playback rate. Scales the media clock and each AudioBufferSourceNode's
1378
+ * playbackRate so audio pitches up/down accordingly (same as native
1379
+ * <video>.playbackRate). Default 1. */
1380
+ _rate = 1;
1365
1381
  constructor() {
1366
1382
  this.ctx = new AudioContext();
1367
1383
  this.gain = this.ctx.createGain();
@@ -1383,6 +1399,20 @@ var AudioOutput = class {
1383
1399
  getMuted() {
1384
1400
  return this._muted;
1385
1401
  }
1402
+ /** Set playback rate. Scales the media clock and pitches audio output
1403
+ * (same as native <video>.playbackRate — speed without pitch correction).
1404
+ * Rebases the anchor so the clock transition is seamless. */
1405
+ setPlaybackRate(rate) {
1406
+ if (rate === this._rate) return;
1407
+ const t = this.now();
1408
+ this.mediaTimeOfAnchor = t;
1409
+ this.ctxTimeAtAnchor = this.ctx.currentTime;
1410
+ this.wallAnchorMs = performance.now();
1411
+ this._rate = rate;
1412
+ }
1413
+ getPlaybackRate() {
1414
+ return this._rate;
1415
+ }
1386
1416
  applyGain() {
1387
1417
  const target = this._muted ? 0 : this._volume;
1388
1418
  try {
@@ -1403,12 +1433,12 @@ var AudioOutput = class {
1403
1433
  now() {
1404
1434
  if (this.noAudio) {
1405
1435
  if (this.state === "playing") {
1406
- return this.mediaTimeOfAnchor + (performance.now() - this.wallAnchorMs) / 1e3;
1436
+ return this.mediaTimeOfAnchor + (performance.now() - this.wallAnchorMs) / 1e3 * this._rate;
1407
1437
  }
1408
1438
  return this.mediaTimeOfAnchor;
1409
1439
  }
1410
1440
  if (this.state === "playing") {
1411
- return this.mediaTimeOfAnchor + (this.ctx.currentTime - this.ctxTimeAtAnchor);
1441
+ return this.mediaTimeOfAnchor + (this.ctx.currentTime - this.ctxTimeAtAnchor) * this._rate;
1412
1442
  }
1413
1443
  return this.mediaTimeOfAnchor;
1414
1444
  }
@@ -1460,7 +1490,8 @@ var AudioOutput = class {
1460
1490
  const node = this.ctx.createBufferSource();
1461
1491
  node.buffer = buffer;
1462
1492
  node.connect(this.gain);
1463
- let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
1493
+ if (this._rate !== 1) node.playbackRate.value = this._rate;
1494
+ let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor) / this._rate;
1464
1495
  if (ctxStart < this.ctx.currentTime) {
1465
1496
  this.ctxTimeAtAnchor = this.ctx.currentTime;
1466
1497
  this.mediaTimeOfAnchor = this.mediaTimeOfNext;
@@ -1701,6 +1732,7 @@ async function startHybridDecoder(opts) {
1701
1732
  let videoFramesDecoded = 0;
1702
1733
  let audioFramesDecoded = 0;
1703
1734
  let videoChunksFed = 0;
1735
+ let bufferedUntilSec = 0;
1704
1736
  let syntheticVideoUs = 0;
1705
1737
  let syntheticAudioUs = 0;
1706
1738
  const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
@@ -1721,6 +1753,18 @@ async function startHybridDecoder(opts) {
1721
1753
  if (myToken !== pumpToken || destroyed) return;
1722
1754
  const videoPackets = videoStream ? packets[videoStream.index] : void 0;
1723
1755
  const audioPackets = audioStream ? packets[audioStream.index] : void 0;
1756
+ if (videoPackets && videoTimeBase) {
1757
+ for (const pkt of videoPackets) {
1758
+ const sec = packetPtsSec(pkt, videoTimeBase);
1759
+ if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
1760
+ }
1761
+ }
1762
+ if (audioPackets && audioTimeBase) {
1763
+ for (const pkt of audioPackets) {
1764
+ const sec = packetPtsSec(pkt, audioTimeBase);
1765
+ if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
1766
+ }
1767
+ }
1724
1768
  if (audioDec && audioPackets && audioPackets.length > 0) {
1725
1769
  await decodeAudioBatch(audioPackets, myToken);
1726
1770
  }
@@ -1977,6 +2021,9 @@ async function startHybridDecoder(opts) {
1977
2021
  (err) => console.error("[avbridge] hybrid pump failed (post-seek):", err)
1978
2022
  );
1979
2023
  },
2024
+ bufferedUntilSec() {
2025
+ return bufferedUntilSec;
2026
+ },
1980
2027
  stats() {
1981
2028
  return {
1982
2029
  decoderType: "webcodecs-hybrid",
@@ -2092,6 +2139,14 @@ async function createHybridSession(ctx, target, transport) {
2092
2139
  get: () => ctx.duration ?? NaN
2093
2140
  });
2094
2141
  }
2142
+ Object.defineProperty(target, "playbackRate", {
2143
+ configurable: true,
2144
+ get: () => audio.getPlaybackRate(),
2145
+ set: (v) => {
2146
+ audio.setPlaybackRate(v);
2147
+ target.dispatchEvent(new Event("ratechange"));
2148
+ }
2149
+ });
2095
2150
  Object.defineProperty(target, "readyState", {
2096
2151
  configurable: true,
2097
2152
  get: () => {
@@ -2104,6 +2159,13 @@ async function createHybridSession(ctx, target, transport) {
2104
2159
  configurable: true,
2105
2160
  get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
2106
2161
  });
2162
+ Object.defineProperty(target, "buffered", {
2163
+ configurable: true,
2164
+ get: () => {
2165
+ const end = handles.bufferedUntilSec();
2166
+ return makeTimeRanges(end > 0 ? [[0, end]] : []);
2167
+ }
2168
+ });
2107
2169
  async function waitForBuffer() {
2108
2170
  const start = performance.now();
2109
2171
  while (true) {
@@ -2117,6 +2179,7 @@ async function createHybridSession(ctx, target, transport) {
2117
2179
  }
2118
2180
  async function doSeek(timeSec) {
2119
2181
  const wasPlaying = audio.isPlaying();
2182
+ target.dispatchEvent(new Event("seeking"));
2120
2183
  await audio.pause().catch(() => {
2121
2184
  });
2122
2185
  await handles.seek(timeSec).catch(
@@ -2128,7 +2191,14 @@ async function createHybridSession(ctx, target, transport) {
2128
2191
  await waitForBuffer();
2129
2192
  await audio.start();
2130
2193
  }
2194
+ target.dispatchEvent(new Event("seeked"));
2131
2195
  }
2196
+ queueMicrotask(() => {
2197
+ try {
2198
+ target.dispatchEvent(new Event("loadedmetadata"));
2199
+ } catch {
2200
+ }
2201
+ });
2132
2202
  let fatalErrorHandler = null;
2133
2203
  handles.onFatalError((reason) => fatalErrorHandler?.(reason));
2134
2204
  return {
@@ -2187,6 +2257,7 @@ async function createHybridSession(ctx, target, transport) {
2187
2257
  delete target.muted;
2188
2258
  delete target.readyState;
2189
2259
  delete target.seekable;
2260
+ delete target.playbackRate;
2190
2261
  } catch {
2191
2262
  }
2192
2263
  },
@@ -2313,6 +2384,7 @@ async function startDecoder(opts) {
2313
2384
  let pumpRunning = null;
2314
2385
  let packetsRead = 0;
2315
2386
  let videoFramesDecoded = 0;
2387
+ let bufferedUntilSec = 0;
2316
2388
  let audioFramesDecoded = 0;
2317
2389
  let watchdogFirstFrameMs = 0;
2318
2390
  let watchdogSlowSinceMs = 0;
@@ -2338,6 +2410,18 @@ async function startDecoder(opts) {
2338
2410
  if (myToken !== pumpToken || destroyed) return;
2339
2411
  const videoPackets = videoStream ? packets[videoStream.index] : void 0;
2340
2412
  const audioPackets = audioStream ? packets[audioStream.index] : void 0;
2413
+ if (videoPackets && videoTimeBase) {
2414
+ for (const pkt of videoPackets) {
2415
+ const sec = packetPtsSec(pkt, videoTimeBase);
2416
+ if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
2417
+ }
2418
+ }
2419
+ if (audioPackets && audioTimeBase) {
2420
+ for (const pkt of audioPackets) {
2421
+ const sec = packetPtsSec(pkt, audioTimeBase);
2422
+ if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
2423
+ }
2424
+ }
2341
2425
  if (audioDec && audioPackets && audioPackets.length > 0) {
2342
2426
  await decodeAudioBatch(audioPackets, myToken);
2343
2427
  }
@@ -2619,6 +2703,9 @@ async function startDecoder(opts) {
2619
2703
  (err) => console.error("[avbridge] decoder pump failed (post-seek):", err)
2620
2704
  );
2621
2705
  },
2706
+ bufferedUntilSec() {
2707
+ return bufferedUntilSec;
2708
+ },
2622
2709
  stats() {
2623
2710
  return {
2624
2711
  decoderType: "libav-wasm",
@@ -2706,6 +2793,14 @@ async function createFallbackSession(ctx, target, transport) {
2706
2793
  get: () => ctx.duration ?? NaN
2707
2794
  });
2708
2795
  }
2796
+ Object.defineProperty(target, "playbackRate", {
2797
+ configurable: true,
2798
+ get: () => audio.getPlaybackRate(),
2799
+ set: (v) => {
2800
+ audio.setPlaybackRate(v);
2801
+ target.dispatchEvent(new Event("ratechange"));
2802
+ }
2803
+ });
2709
2804
  Object.defineProperty(target, "readyState", {
2710
2805
  configurable: true,
2711
2806
  get: () => {
@@ -2718,6 +2813,13 @@ async function createFallbackSession(ctx, target, transport) {
2718
2813
  configurable: true,
2719
2814
  get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
2720
2815
  });
2816
+ Object.defineProperty(target, "buffered", {
2817
+ configurable: true,
2818
+ get: () => {
2819
+ const end = handles.bufferedUntilSec();
2820
+ return makeTimeRanges(end > 0 ? [[0, end]] : []);
2821
+ }
2822
+ });
2721
2823
  async function waitForBuffer() {
2722
2824
  const start = performance.now();
2723
2825
  let firstFrameAtMs = 0;
@@ -2757,6 +2859,7 @@ async function createFallbackSession(ctx, target, transport) {
2757
2859
  }
2758
2860
  async function doSeek(timeSec) {
2759
2861
  const wasPlaying = audio.isPlaying();
2862
+ target.dispatchEvent(new Event("seeking"));
2760
2863
  await audio.pause().catch(() => {
2761
2864
  });
2762
2865
  await handles.seek(timeSec).catch(
@@ -2768,7 +2871,14 @@ async function createFallbackSession(ctx, target, transport) {
2768
2871
  await waitForBuffer();
2769
2872
  await audio.start();
2770
2873
  }
2874
+ target.dispatchEvent(new Event("seeked"));
2771
2875
  }
2876
+ queueMicrotask(() => {
2877
+ try {
2878
+ target.dispatchEvent(new Event("loadedmetadata"));
2879
+ } catch {
2880
+ }
2881
+ });
2772
2882
  return {
2773
2883
  strategy: "fallback",
2774
2884
  async play() {
@@ -2822,6 +2932,7 @@ async function createFallbackSession(ctx, target, transport) {
2822
2932
  delete target.muted;
2823
2933
  delete target.readyState;
2824
2934
  delete target.seekable;
2935
+ delete target.playbackRate;
2825
2936
  } catch {
2826
2937
  }
2827
2938
  },
@@ -3424,5 +3535,5 @@ function defaultFallbackChain(strategy) {
3424
3535
  }
3425
3536
 
3426
3537
  export { FALLBACK_AUDIO_CODECS, FALLBACK_VIDEO_CODECS, NATIVE_AUDIO_CODECS, NATIVE_VIDEO_CODECS, UnifiedPlayer, classifyContext, createPlayer };
3427
- //# sourceMappingURL=chunk-KBWQRGHS.js.map
3428
- //# sourceMappingURL=chunk-KBWQRGHS.js.map
3538
+ //# sourceMappingURL=chunk-3GKM5DFM.js.map
3539
+ //# sourceMappingURL=chunk-3GKM5DFM.js.map