avbridge 2.8.4 → 2.9.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 (77) hide show
  1. package/CHANGELOG.md +133 -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-ZCUXHW55.cjs → chunk-BYGZN4Z5.cjs} +5 -5
  14. package/dist/{chunk-ZCUXHW55.cjs.map → chunk-BYGZN4Z5.cjs.map} +1 -1
  15. package/dist/{chunk-SMH6IOP2.js → chunk-CL6UEUQF.js} +4 -4
  16. package/dist/{chunk-SMH6IOP2.js.map → chunk-CL6UEUQF.js.map} +1 -1
  17. package/dist/{chunk-YX4AGLNF.cjs → chunk-EY6DZEDT.cjs} +89 -15
  18. package/dist/chunk-EY6DZEDT.cjs.map +1 -0
  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-Q2VUO52Z.cjs → chunk-OTFS7DC4.cjs} +12 -12
  24. package/dist/{chunk-Q2VUO52Z.cjs.map → chunk-OTFS7DC4.cjs.map} +1 -1
  25. package/dist/{chunk-KBWQRGHS.js → chunk-SN4WZE24.js} +79 -5
  26. package/dist/chunk-SN4WZE24.js.map +1 -0
  27. package/dist/element-browser.js +104 -7
  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-DEcidWk6.d.cts} +1 -1
  44. package/dist/{player-BptSJPfn.d.ts → player-DEcidWk6.d.ts} +1 -1
  45. package/dist/player.cjs +187 -23
  46. package/dist/player.cjs.map +1 -1
  47. package/dist/player.d.cts +17 -11
  48. package/dist/player.d.ts +17 -11
  49. package/dist/player.js +187 -23
  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 +22 -11
  58. package/src/element/avbridge-video.ts +22 -6
  59. package/src/element/player-styles.ts +68 -3
  60. package/src/probe/avi.ts +2 -0
  61. package/src/strategies/fallback/decoder.ts +30 -0
  62. package/src/strategies/fallback/index.ts +30 -0
  63. package/src/strategies/hybrid/decoder.ts +35 -0
  64. package/src/strategies/hybrid/index.ts +17 -0
  65. package/src/strategies/remux/index.ts +8 -0
  66. package/src/types.ts +6 -0
  67. package/src/util/libav-demux.ts +26 -0
  68. package/dist/avi-2JPBSHGA.js.map +0 -1
  69. package/dist/avi-F6WZJK5T.cjs.map +0 -1
  70. package/dist/avi-NJXAXUXK.js.map +0 -1
  71. package/dist/avi-W6L3BTWU.cjs.map +0 -1
  72. package/dist/chunk-CPZ7PXAM.cjs.map +0 -1
  73. package/dist/chunk-KBWQRGHS.js.map +0 -1
  74. package/dist/chunk-X2K3GIWE.js.map +0 -1
  75. package/dist/chunk-YX4AGLNF.cjs.map +0 -1
  76. package/dist/libav-demux-H2GS46GH.cjs +0 -27
  77. package/dist/libav-demux-OWZ4T2YW.js +0 -6
@@ -30030,6 +30030,12 @@ function ffmpegToAvbridgeVideo(name) {
30030
30030
  return "rv30";
30031
30031
  case "rv40":
30032
30032
  return "rv40";
30033
+ case "dvvideo":
30034
+ return "dv";
30035
+ // DV / DVCPRO (camcorder, MiniDV)
30036
+ case "hq_hqa":
30037
+ return "hq_hqa";
30038
+ // Canopus HQ / HQA (Grass Valley)
30033
30039
  default:
30034
30040
  return name;
30035
30041
  }
@@ -31564,7 +31570,13 @@ var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set([
31564
31570
  "rv40",
31565
31571
  "mpeg2",
31566
31572
  "mpeg1",
31567
- "theora"
31573
+ "theora",
31574
+ "dv",
31575
+ "hq_hqa",
31576
+ "rawvideo",
31577
+ "qtrle",
31578
+ "png",
31579
+ "vp6f"
31568
31580
  ]);
31569
31581
  var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set([
31570
31582
  "wmav2",
@@ -32401,6 +32413,12 @@ async function createRemuxSession(context, video) {
32401
32413
  }
32402
32414
  const wasPlaying = !video.paused;
32403
32415
  await pipeline.seek(time, wasPlaying || wantPlay);
32416
+ queueMicrotask(() => {
32417
+ try {
32418
+ video.dispatchEvent(new Event("seeked"));
32419
+ } catch {
32420
+ }
32421
+ });
32404
32422
  },
32405
32423
  async setAudioTrack(id) {
32406
32424
  if (!context.audioTracks.some((t) => t.id === id)) {
@@ -33042,6 +33060,17 @@ function sanitizePacketTimestamp(pkt, nextUs, fallbackTimeBase) {
33042
33060
  pkt.time_base_num = 1;
33043
33061
  pkt.time_base_den = 1e6;
33044
33062
  }
33063
+ function packetPtsSec(pkt, timeBase) {
33064
+ const lo = pkt.pts ?? 0;
33065
+ const hi = pkt.ptshi ?? 0;
33066
+ const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
33067
+ if (isInvalid) return null;
33068
+ const tb = timeBase ?? [1, 1e6];
33069
+ if (!tb[0] || !tb[1]) return null;
33070
+ const pts64 = hi * 4294967296 + lo;
33071
+ const sec = pts64 * tb[0] / tb[1];
33072
+ return Number.isFinite(sec) ? sec : null;
33073
+ }
33045
33074
  var AV_SAMPLE_FMT_U8 = 0;
33046
33075
  var AV_SAMPLE_FMT_S16 = 1;
33047
33076
  var AV_SAMPLE_FMT_S32 = 2;
@@ -33302,6 +33331,7 @@ async function startHybridDecoder(opts) {
33302
33331
  let videoFramesDecoded = 0;
33303
33332
  let audioFramesDecoded = 0;
33304
33333
  let videoChunksFed = 0;
33334
+ let bufferedUntilSec = 0;
33305
33335
  let syntheticVideoUs = 0;
33306
33336
  let syntheticAudioUs = 0;
33307
33337
  const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
@@ -33322,6 +33352,18 @@ async function startHybridDecoder(opts) {
33322
33352
  if (myToken !== pumpToken || destroyed) return;
33323
33353
  const videoPackets = videoStream ? packets[videoStream.index] : void 0;
33324
33354
  const audioPackets = audioStream ? packets[audioStream.index] : void 0;
33355
+ if (videoPackets && videoTimeBase) {
33356
+ for (const pkt of videoPackets) {
33357
+ const sec = packetPtsSec(pkt, videoTimeBase);
33358
+ if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
33359
+ }
33360
+ }
33361
+ if (audioPackets && audioTimeBase) {
33362
+ for (const pkt of audioPackets) {
33363
+ const sec = packetPtsSec(pkt, audioTimeBase);
33364
+ if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
33365
+ }
33366
+ }
33325
33367
  if (audioDec && audioPackets && audioPackets.length > 0) {
33326
33368
  await decodeAudioBatch(audioPackets, myToken);
33327
33369
  }
@@ -33578,6 +33620,9 @@ async function startHybridDecoder(opts) {
33578
33620
  (err) => console.error("[avbridge] hybrid pump failed (post-seek):", err)
33579
33621
  );
33580
33622
  },
33623
+ bufferedUntilSec() {
33624
+ return bufferedUntilSec;
33625
+ },
33581
33626
  stats() {
33582
33627
  return {
33583
33628
  decoderType: "webcodecs-hybrid",
@@ -33705,6 +33750,13 @@ async function createHybridSession(ctx, target, transport) {
33705
33750
  configurable: true,
33706
33751
  get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
33707
33752
  });
33753
+ Object.defineProperty(target, "buffered", {
33754
+ configurable: true,
33755
+ get: () => {
33756
+ const end = handles.bufferedUntilSec();
33757
+ return makeTimeRanges(end > 0 ? [[0, end]] : []);
33758
+ }
33759
+ });
33708
33760
  async function waitForBuffer() {
33709
33761
  const start = performance.now();
33710
33762
  while (true) {
@@ -33718,6 +33770,7 @@ async function createHybridSession(ctx, target, transport) {
33718
33770
  }
33719
33771
  async function doSeek(timeSec) {
33720
33772
  const wasPlaying = audio.isPlaying();
33773
+ target.dispatchEvent(new Event("seeking"));
33721
33774
  await audio.pause().catch(() => {
33722
33775
  });
33723
33776
  await handles.seek(timeSec).catch(
@@ -33729,7 +33782,14 @@ async function createHybridSession(ctx, target, transport) {
33729
33782
  await waitForBuffer();
33730
33783
  await audio.start();
33731
33784
  }
33785
+ target.dispatchEvent(new Event("seeked"));
33732
33786
  }
33787
+ queueMicrotask(() => {
33788
+ try {
33789
+ target.dispatchEvent(new Event("loadedmetadata"));
33790
+ } catch {
33791
+ }
33792
+ });
33733
33793
  let fatalErrorHandler = null;
33734
33794
  handles.onFatalError((reason) => fatalErrorHandler?.(reason));
33735
33795
  return {
@@ -33916,6 +33976,7 @@ async function startDecoder(opts) {
33916
33976
  let pumpRunning = null;
33917
33977
  let packetsRead = 0;
33918
33978
  let videoFramesDecoded = 0;
33979
+ let bufferedUntilSec = 0;
33919
33980
  let audioFramesDecoded = 0;
33920
33981
  let watchdogFirstFrameMs = 0;
33921
33982
  let watchdogSlowSinceMs = 0;
@@ -33941,6 +34002,18 @@ async function startDecoder(opts) {
33941
34002
  if (myToken !== pumpToken || destroyed) return;
33942
34003
  const videoPackets = videoStream ? packets[videoStream.index] : void 0;
33943
34004
  const audioPackets = audioStream ? packets[audioStream.index] : void 0;
34005
+ if (videoPackets && videoTimeBase) {
34006
+ for (const pkt of videoPackets) {
34007
+ const sec = packetPtsSec(pkt, videoTimeBase);
34008
+ if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
34009
+ }
34010
+ }
34011
+ if (audioPackets && audioTimeBase) {
34012
+ for (const pkt of audioPackets) {
34013
+ const sec = packetPtsSec(pkt, audioTimeBase);
34014
+ if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
34015
+ }
34016
+ }
33944
34017
  if (audioDec && audioPackets && audioPackets.length > 0) {
33945
34018
  await decodeAudioBatch(audioPackets, myToken);
33946
34019
  }
@@ -34222,6 +34295,9 @@ async function startDecoder(opts) {
34222
34295
  (err) => console.error("[avbridge] decoder pump failed (post-seek):", err)
34223
34296
  );
34224
34297
  },
34298
+ bufferedUntilSec() {
34299
+ return bufferedUntilSec;
34300
+ },
34225
34301
  stats() {
34226
34302
  return {
34227
34303
  decoderType: "libav-wasm",
@@ -34322,6 +34398,13 @@ async function createFallbackSession(ctx, target, transport) {
34322
34398
  configurable: true,
34323
34399
  get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
34324
34400
  });
34401
+ Object.defineProperty(target, "buffered", {
34402
+ configurable: true,
34403
+ get: () => {
34404
+ const end = handles.bufferedUntilSec();
34405
+ return makeTimeRanges(end > 0 ? [[0, end]] : []);
34406
+ }
34407
+ });
34325
34408
  async function waitForBuffer() {
34326
34409
  const start = performance.now();
34327
34410
  let firstFrameAtMs = 0;
@@ -34361,6 +34444,7 @@ async function createFallbackSession(ctx, target, transport) {
34361
34444
  }
34362
34445
  async function doSeek(timeSec) {
34363
34446
  const wasPlaying = audio.isPlaying();
34447
+ target.dispatchEvent(new Event("seeking"));
34364
34448
  await audio.pause().catch(() => {
34365
34449
  });
34366
34450
  await handles.seek(timeSec).catch(
@@ -34372,7 +34456,14 @@ async function createFallbackSession(ctx, target, transport) {
34372
34456
  await waitForBuffer();
34373
34457
  await audio.start();
34374
34458
  }
34459
+ target.dispatchEvent(new Event("seeked"));
34375
34460
  }
34461
+ queueMicrotask(() => {
34462
+ try {
34463
+ target.dispatchEvent(new Event("loadedmetadata"));
34464
+ } catch {
34465
+ }
34466
+ });
34376
34467
  return {
34377
34468
  strategy: "fallback",
34378
34469
  async play() {
@@ -35455,9 +35546,10 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
35455
35546
  else this.removeAttribute("autoplay");
35456
35547
  }
35457
35548
  get muted() {
35458
- return this.hasAttribute("muted");
35549
+ return this._videoEl.muted;
35459
35550
  }
35460
35551
  set muted(value) {
35552
+ this._videoEl.muted = value;
35461
35553
  if (value) this.setAttribute("muted", "");
35462
35554
  else this.removeAttribute("muted");
35463
35555
  }
@@ -35522,11 +35614,16 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
35522
35614
  }
35523
35615
  /**
35524
35616
  * Buffered time ranges for the active source. Mirrors the standard
35525
- * `<video>.buffered` `TimeRanges` API. For the native and remux strategies
35526
- * this reflects the underlying SourceBuffer / progressive download state.
35527
- * For the hybrid and fallback (canvas-rendered) strategies it currently
35528
- * returns an empty TimeRanges; a future release will synthesize a coarse
35529
- * range from the decoder's read position.
35617
+ * `<video>.buffered` `TimeRanges` API.
35618
+ *
35619
+ * - **Native / remux:** pass-through to the real `<video>.buffered`
35620
+ * (reflects the browser's SourceBuffer / progressive-download state).
35621
+ * - **Hybrid / fallback:** a single `[0, frontier]` range synthesized
35622
+ * from the demuxer's read progress — "how far libav has ever pumped
35623
+ * packets through." Monotonic; does not shrink on seek. This is an
35624
+ * approximation, not MSE-fidelity: decoded frames on canvas strategies
35625
+ * are consumed in flight, so we can't report per-range availability
35626
+ * the way MSE does. Enough for a seek-bar buffered indicator.
35530
35627
  */
35531
35628
  get buffered() {
35532
35629
  return this._videoEl.buffered;