avbridge 2.1.2 → 2.2.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 (62) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/dist/{avi-GNTV5ZOH.cjs → avi-6SJLWIWW.cjs} +19 -4
  3. package/dist/avi-6SJLWIWW.cjs.map +1 -0
  4. package/dist/{avi-V6HYQVR2.js → avi-GCGM7OJI.js} +18 -3
  5. package/dist/avi-GCGM7OJI.js.map +1 -0
  6. package/dist/{chunk-EJH67FXG.js → chunk-5DMTJVIU.js} +99 -3
  7. package/dist/chunk-5DMTJVIU.js.map +1 -0
  8. package/dist/{chunk-3AUGRKPY.js → chunk-C5VA5U5O.js} +94 -16
  9. package/dist/chunk-C5VA5U5O.js.map +1 -0
  10. package/dist/{chunk-JQH6D4OE.cjs → chunk-G4APZMCP.cjs} +100 -3
  11. package/dist/chunk-G4APZMCP.cjs.map +1 -0
  12. package/dist/{chunk-Y5FYF5KG.cjs → chunk-HZLQNKFN.cjs} +5 -2
  13. package/dist/chunk-HZLQNKFN.cjs.map +1 -0
  14. package/dist/{chunk-PQTZS7OA.js → chunk-ILKDNBSE.js} +5 -2
  15. package/dist/chunk-ILKDNBSE.js.map +1 -0
  16. package/dist/{chunk-DPVIOYGC.cjs → chunk-OE66B34H.cjs} +98 -20
  17. package/dist/chunk-OE66B34H.cjs.map +1 -0
  18. package/dist/element-browser.js +210 -10
  19. package/dist/element-browser.js.map +1 -1
  20. package/dist/element.cjs +4 -4
  21. package/dist/element.d.cts +1 -1
  22. package/dist/element.d.ts +1 -1
  23. package/dist/element.js +3 -3
  24. package/dist/index.cjs +18 -18
  25. package/dist/index.d.cts +2 -2
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +5 -5
  28. package/dist/libav-loader-27RDIN2I.js +3 -0
  29. package/dist/{libav-loader-XKH2TKUW.js.map → libav-loader-27RDIN2I.js.map} +1 -1
  30. package/dist/libav-loader-IV4AJ2HW.cjs +12 -0
  31. package/dist/{libav-loader-6APXVNIV.cjs.map → libav-loader-IV4AJ2HW.cjs.map} +1 -1
  32. package/dist/{player-BdtUG4rh.d.cts → player-DUyvltvy.d.cts} +3 -3
  33. package/dist/{player-BdtUG4rh.d.ts → player-DUyvltvy.d.ts} +3 -3
  34. package/dist/source-CN43EI7Z.cjs +28 -0
  35. package/dist/{source-SC6ZEQYR.cjs.map → source-CN43EI7Z.cjs.map} +1 -1
  36. package/dist/source-FFZ7TW2B.js +3 -0
  37. package/dist/{source-ZFS4H7J3.js.map → source-FFZ7TW2B.js.map} +1 -1
  38. package/package.json +1 -1
  39. package/src/classify/rules.ts +9 -2
  40. package/src/player.ts +22 -1
  41. package/src/probe/avi.ts +8 -1
  42. package/src/strategies/fallback/audio-output.ts +25 -3
  43. package/src/strategies/fallback/decoder.ts +96 -8
  44. package/src/strategies/fallback/index.ts +90 -6
  45. package/src/strategies/fallback/libav-loader.ts +12 -0
  46. package/src/types.ts +10 -1
  47. package/src/util/debug.ts +131 -0
  48. package/src/util/source.ts +4 -0
  49. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  50. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  51. package/dist/avi-GNTV5ZOH.cjs.map +0 -1
  52. package/dist/avi-V6HYQVR2.js.map +0 -1
  53. package/dist/chunk-3AUGRKPY.js.map +0 -1
  54. package/dist/chunk-DPVIOYGC.cjs.map +0 -1
  55. package/dist/chunk-EJH67FXG.js.map +0 -1
  56. package/dist/chunk-JQH6D4OE.cjs.map +0 -1
  57. package/dist/chunk-PQTZS7OA.js.map +0 -1
  58. package/dist/chunk-Y5FYF5KG.cjs.map +0 -1
  59. package/dist/libav-loader-6APXVNIV.cjs +0 -12
  60. package/dist/libav-loader-XKH2TKUW.js +0 -3
  61. package/dist/source-SC6ZEQYR.cjs +0 -28
  62. package/dist/source-ZFS4H7J3.js +0 -3
package/dist/element.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkDPVIOYGC_cjs = require('./chunk-DPVIOYGC.cjs');
4
- require('./chunk-Y5FYF5KG.cjs');
5
- require('./chunk-JQH6D4OE.cjs');
3
+ var chunkOE66B34H_cjs = require('./chunk-OE66B34H.cjs');
4
+ require('./chunk-HZLQNKFN.cjs');
5
+ require('./chunk-G4APZMCP.cjs');
6
6
  require('./chunk-NZU7W256.cjs');
7
7
 
8
8
  // src/element/avbridge-video.ts
@@ -226,7 +226,7 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
226
226
  this._dispatch("loadstart", {});
227
227
  let player;
228
228
  try {
229
- player = await chunkDPVIOYGC_cjs.createPlayer({
229
+ player = await chunkOE66B34H_cjs.createPlayer({
230
230
  source,
231
231
  target: this._videoEl,
232
232
  // Honor the consumer's preferred initial strategy. "auto" means
@@ -1,4 +1,4 @@
1
- import { a as MediaInput, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-BdtUG4rh.cjs';
1
+ import { a as MediaInput, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-DUyvltvy.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, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-BdtUG4rh.js';
1
+ import { a as MediaInput, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-DUyvltvy.js';
2
2
 
3
3
  /**
4
4
  * `<avbridge-video>` — `HTMLMediaElement`-compatible primitive backed by the
package/dist/element.js CHANGED
@@ -1,6 +1,6 @@
1
- import { createPlayer } from './chunk-3AUGRKPY.js';
2
- import './chunk-PQTZS7OA.js';
3
- import './chunk-EJH67FXG.js';
1
+ import { createPlayer } from './chunk-C5VA5U5O.js';
2
+ import './chunk-ILKDNBSE.js';
3
+ import './chunk-5DMTJVIU.js';
4
4
  import './chunk-J5MCMN3S.js';
5
5
 
6
6
  // src/element/avbridge-video.ts
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunkDPVIOYGC_cjs = require('./chunk-DPVIOYGC.cjs');
4
- var chunkY5FYF5KG_cjs = require('./chunk-Y5FYF5KG.cjs');
3
+ var chunkOE66B34H_cjs = require('./chunk-OE66B34H.cjs');
4
+ var chunkHZLQNKFN_cjs = require('./chunk-HZLQNKFN.cjs');
5
5
  var chunkL4NPOJ36_cjs = require('./chunk-L4NPOJ36.cjs');
6
- require('./chunk-JQH6D4OE.cjs');
6
+ require('./chunk-G4APZMCP.cjs');
7
7
  require('./chunk-NZU7W256.cjs');
8
8
 
9
9
  // src/convert/remux.ts
@@ -21,7 +21,7 @@ var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
21
21
  async function remux(source, options = {}) {
22
22
  const outputFormat = options.outputFormat ?? "mp4";
23
23
  options.signal?.throwIfAborted();
24
- const ctx = await chunkDPVIOYGC_cjs.probe(source);
24
+ const ctx = await chunkOE66B34H_cjs.probe(source);
25
25
  options.signal?.throwIfAborted();
26
26
  validateRemuxEligibility(ctx, options.strict ?? false);
27
27
  if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {
@@ -33,7 +33,7 @@ function validateRemuxEligibility(ctx, strict) {
33
33
  const video = ctx.videoTracks[0];
34
34
  const audio = ctx.audioTracks[0];
35
35
  if (video) {
36
- const mbCodec = chunkDPVIOYGC_cjs.avbridgeVideoToMediabunny(video.codec);
36
+ const mbCodec = chunkOE66B34H_cjs.avbridgeVideoToMediabunny(video.codec);
37
37
  if (!mbCodec) {
38
38
  throw new Error(
39
39
  `Cannot remux: video codec "${video.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
@@ -41,7 +41,7 @@ function validateRemuxEligibility(ctx, strict) {
41
41
  }
42
42
  }
43
43
  if (audio) {
44
- const mbCodec = chunkDPVIOYGC_cjs.avbridgeAudioToMediabunny(audio.codec);
44
+ const mbCodec = chunkOE66B34H_cjs.avbridgeAudioToMediabunny(audio.codec);
45
45
  if (!mbCodec) {
46
46
  throw new Error(
47
47
  `Cannot remux: audio codec "${audio.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
@@ -60,7 +60,7 @@ function validateRemuxEligibility(ctx, strict) {
60
60
  async function remuxViaMediAbunny(ctx, outputFormat, options) {
61
61
  const mb = await import('mediabunny');
62
62
  const input = new mb.Input({
63
- source: await chunkDPVIOYGC_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
63
+ source: await chunkOE66B34H_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
64
64
  formats: mb.ALL_FORMATS
65
65
  });
66
66
  const target = new mb.BufferTarget();
@@ -117,7 +117,7 @@ async function remuxViaLibav(ctx, outputFormat, options) {
117
117
  let loadLibav;
118
118
  let pickLibavVariant;
119
119
  try {
120
- const loader = await import('./libav-loader-6APXVNIV.cjs');
120
+ const loader = await import('./libav-loader-IV4AJ2HW.cjs');
121
121
  const routing = await import('./variant-routing-GOHB2RZN.cjs');
122
122
  loadLibav = loader.loadLibav;
123
123
  pickLibavVariant = routing.pickLibavVariant;
@@ -128,7 +128,7 @@ async function remuxViaLibav(ctx, outputFormat, options) {
128
128
  }
129
129
  const variant = pickLibavVariant(ctx);
130
130
  const libav = await loadLibav(variant);
131
- const normalized = await chunkY5FYF5KG_cjs.normalizeSource(ctx.source);
131
+ const normalized = await chunkHZLQNKFN_cjs.normalizeSource(ctx.source);
132
132
  const filename = ctx.name ?? `remux-input-${Date.now()}`;
133
133
  const handle = await chunkL4NPOJ36_cjs.prepareLibavInput(libav, filename, normalized);
134
134
  try {
@@ -146,8 +146,8 @@ async function doLibavRemux(libav, filename, ctx, outputFormat, options) {
146
146
  const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
147
147
  const videoTrackInfo = ctx.videoTracks[0];
148
148
  const audioTrackInfo = ctx.audioTracks[0];
149
- const mbVideoCodec = videoTrackInfo ? chunkDPVIOYGC_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
150
- const mbAudioCodec = audioTrackInfo ? chunkDPVIOYGC_cjs.avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
149
+ const mbVideoCodec = videoTrackInfo ? chunkOE66B34H_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
150
+ const mbAudioCodec = audioTrackInfo ? chunkOE66B34H_cjs.avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
151
151
  const target = new mb.BufferTarget();
152
152
  const output = new mb.Output({
153
153
  format: createOutputFormat(mb, outputFormat),
@@ -358,7 +358,7 @@ async function transcode(source, options = {}) {
358
358
  const quality = options.quality ?? "medium";
359
359
  validateCodecCompatibility(outputFormat, videoCodec, audioCodec);
360
360
  options.signal?.throwIfAborted();
361
- const ctx = await chunkDPVIOYGC_cjs.probe(source);
361
+ const ctx = await chunkOE66B34H_cjs.probe(source);
362
362
  options.signal?.throwIfAborted();
363
363
  if (!MEDIABUNNY_CONTAINERS2.has(ctx.container)) {
364
364
  throw new Error(
@@ -370,7 +370,7 @@ async function transcode(source, options = {}) {
370
370
  async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
371
371
  const mb = await import('mediabunny');
372
372
  const input = new mb.Input({
373
- source: await chunkDPVIOYGC_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
373
+ source: await chunkOE66B34H_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
374
374
  formats: mb.ALL_FORMATS
375
375
  });
376
376
  const target = new mb.BufferTarget();
@@ -552,23 +552,23 @@ function qualityToMediabunny(mb, quality) {
552
552
 
553
553
  Object.defineProperty(exports, "UnifiedPlayer", {
554
554
  enumerable: true,
555
- get: function () { return chunkDPVIOYGC_cjs.UnifiedPlayer; }
555
+ get: function () { return chunkOE66B34H_cjs.UnifiedPlayer; }
556
556
  });
557
557
  Object.defineProperty(exports, "classify", {
558
558
  enumerable: true,
559
- get: function () { return chunkDPVIOYGC_cjs.classifyContext; }
559
+ get: function () { return chunkOE66B34H_cjs.classifyContext; }
560
560
  });
561
561
  Object.defineProperty(exports, "createPlayer", {
562
562
  enumerable: true,
563
- get: function () { return chunkDPVIOYGC_cjs.createPlayer; }
563
+ get: function () { return chunkOE66B34H_cjs.createPlayer; }
564
564
  });
565
565
  Object.defineProperty(exports, "probe", {
566
566
  enumerable: true,
567
- get: function () { return chunkDPVIOYGC_cjs.probe; }
567
+ get: function () { return chunkOE66B34H_cjs.probe; }
568
568
  });
569
569
  Object.defineProperty(exports, "srtToVtt", {
570
570
  enumerable: true,
571
- get: function () { return chunkDPVIOYGC_cjs.srtToVtt; }
571
+ get: function () { return chunkOE66B34H_cjs.srtToVtt; }
572
572
  });
573
573
  exports.remux = remux;
574
574
  exports.transcode = transcode;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-BdtUG4rh.cjs';
2
- export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-BdtUG4rh.cjs';
1
+ import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-DUyvltvy.cjs';
2
+ export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-DUyvltvy.cjs';
3
3
 
4
4
  /**
5
5
  * Pure classification — no I/O, no async. Test-friendly.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-BdtUG4rh.js';
2
- export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-BdtUG4rh.js';
1
+ import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-DUyvltvy.js';
2
+ export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-DUyvltvy.js';
3
3
 
4
4
  /**
5
5
  * Pure classification — no I/O, no async. Test-friendly.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-3AUGRKPY.js';
2
- export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-3AUGRKPY.js';
3
- import { normalizeSource } from './chunk-PQTZS7OA.js';
1
+ import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-C5VA5U5O.js';
2
+ export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-C5VA5U5O.js';
3
+ import { normalizeSource } from './chunk-ILKDNBSE.js';
4
4
  import { prepareLibavInput } from './chunk-WD2ZNQA7.js';
5
- import './chunk-EJH67FXG.js';
5
+ import './chunk-5DMTJVIU.js';
6
6
  import './chunk-J5MCMN3S.js';
7
7
 
8
8
  // src/convert/remux.ts
@@ -116,7 +116,7 @@ async function remuxViaLibav(ctx, outputFormat, options) {
116
116
  let loadLibav;
117
117
  let pickLibavVariant;
118
118
  try {
119
- const loader = await import('./libav-loader-XKH2TKUW.js');
119
+ const loader = await import('./libav-loader-27RDIN2I.js');
120
120
  const routing = await import('./variant-routing-JOBWXYKD.js');
121
121
  loadLibav = loader.loadLibav;
122
122
  pickLibavVariant = routing.pickLibavVariant;
@@ -0,0 +1,3 @@
1
+ export { loadLibav } from './chunk-5DMTJVIU.js';
2
+ //# sourceMappingURL=libav-loader-27RDIN2I.js.map
3
+ //# sourceMappingURL=libav-loader-27RDIN2I.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-XKH2TKUW.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-27RDIN2I.js"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "loadLibav", {
8
+ enumerable: true,
9
+ get: function () { return chunkG4APZMCP_cjs.loadLibav; }
10
+ });
11
+ //# sourceMappingURL=libav-loader-IV4AJ2HW.cjs.map
12
+ //# sourceMappingURL=libav-loader-IV4AJ2HW.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-6APXVNIV.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-IV4AJ2HW.cjs"}
@@ -13,11 +13,11 @@
13
13
  */
14
14
  type MediaInput = File | Blob | string | URL | ArrayBuffer | Uint8Array;
15
15
  /** Container format families we know about. */
16
- type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
16
+ type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "rm" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
17
17
  /** Video codec families. Strings, not enums, so plugins can extend. */
18
- type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv40" | "mpeg2" | "mpeg1" | "theora" | (string & {});
18
+ type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv10" | "rv20" | "rv30" | "rv40" | "mpeg2" | "mpeg1" | "theora" | (string & {});
19
19
  /** Audio codec families. */
20
- type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | (string & {});
20
+ type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | "cook" | "ra_144" | "ra_288" | "sipr" | "atrac3" | (string & {});
21
21
  interface VideoTrackInfo {
22
22
  id: number;
23
23
  codec: VideoCodec;
@@ -13,11 +13,11 @@
13
13
  */
14
14
  type MediaInput = File | Blob | string | URL | ArrayBuffer | Uint8Array;
15
15
  /** Container format families we know about. */
16
- type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
16
+ type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "rm" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
17
17
  /** Video codec families. Strings, not enums, so plugins can extend. */
18
- type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv40" | "mpeg2" | "mpeg1" | "theora" | (string & {});
18
+ type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv10" | "rv20" | "rv30" | "rv40" | "mpeg2" | "mpeg1" | "theora" | (string & {});
19
19
  /** Audio codec families. */
20
- type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | (string & {});
20
+ type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | "cook" | "ra_144" | "ra_288" | "sipr" | "atrac3" | (string & {});
21
21
  interface VideoTrackInfo {
22
22
  id: number;
23
23
  codec: VideoCodec;
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var chunkHZLQNKFN_cjs = require('./chunk-HZLQNKFN.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "isInMemorySource", {
8
+ enumerable: true,
9
+ get: function () { return chunkHZLQNKFN_cjs.isInMemorySource; }
10
+ });
11
+ Object.defineProperty(exports, "normalizeSource", {
12
+ enumerable: true,
13
+ get: function () { return chunkHZLQNKFN_cjs.normalizeSource; }
14
+ });
15
+ Object.defineProperty(exports, "sniffContainer", {
16
+ enumerable: true,
17
+ get: function () { return chunkHZLQNKFN_cjs.sniffContainer; }
18
+ });
19
+ Object.defineProperty(exports, "sniffContainerFromBytes", {
20
+ enumerable: true,
21
+ get: function () { return chunkHZLQNKFN_cjs.sniffContainerFromBytes; }
22
+ });
23
+ Object.defineProperty(exports, "sniffNormalizedSource", {
24
+ enumerable: true,
25
+ get: function () { return chunkHZLQNKFN_cjs.sniffNormalizedSource; }
26
+ });
27
+ //# sourceMappingURL=source-CN43EI7Z.cjs.map
28
+ //# sourceMappingURL=source-CN43EI7Z.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"source-SC6ZEQYR.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"source-CN43EI7Z.cjs"}
@@ -0,0 +1,3 @@
1
+ export { isInMemorySource, normalizeSource, sniffContainer, sniffContainerFromBytes, sniffNormalizedSource } from './chunk-ILKDNBSE.js';
2
+ //# sourceMappingURL=source-FFZ7TW2B.js.map
3
+ //# sourceMappingURL=source-FFZ7TW2B.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"source-ZFS4H7J3.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"source-FFZ7TW2B.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avbridge",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "Play and convert arbitrary video files in the browser. Native, remux, hybrid, fallback, and transcode — one API.",
5
5
  "license": "MIT",
6
6
  "author": "Keishi Hattori",
@@ -25,8 +25,15 @@ const NATIVE_AUDIO_CODECS = new Set<AudioCodec>([
25
25
  /**
26
26
  * Codecs no major browser plays, period. These force the WASM fallback.
27
27
  */
28
- const FALLBACK_VIDEO_CODECS = new Set<VideoCodec>(["wmv3", "vc1", "mpeg4", "rv40", "mpeg2", "mpeg1", "theora"]);
29
- const FALLBACK_AUDIO_CODECS = new Set<AudioCodec>(["wmav2", "wmapro", "ac3", "eac3"]);
28
+ const FALLBACK_VIDEO_CODECS = new Set<VideoCodec>([
29
+ "wmv3", "vc1", "mpeg4",
30
+ "rv10", "rv20", "rv30", "rv40",
31
+ "mpeg2", "mpeg1", "theora",
32
+ ]);
33
+ const FALLBACK_AUDIO_CODECS = new Set<AudioCodec>([
34
+ "wmav2", "wmapro", "ac3", "eac3",
35
+ "cook", "ra_144", "ra_288", "sipr", "atrac3",
36
+ ]);
30
37
 
31
38
  /**
32
39
  * Containers `<video>` plays directly. Anything else with otherwise-supported
package/src/player.ts CHANGED
@@ -5,6 +5,7 @@ import { Diagnostics } from "./diagnostics.js";
5
5
  import { PluginRegistry } from "./plugins/registry.js";
6
6
  import { registerBuiltins } from "./plugins/builtin.js";
7
7
  import { discoverSidecars, attachSubtitleTracks, SubtitleResourceBag } from "./subtitles/index.js";
8
+ import { dbg } from "./util/debug.js";
8
9
  import type {
9
10
  Classification,
10
11
  CreatePlayerOptions,
@@ -65,8 +66,14 @@ export class UnifiedPlayer {
65
66
  }
66
67
 
67
68
  private async bootstrap(): Promise<void> {
69
+ const bootstrapStart = performance.now();
68
70
  try {
69
- const ctx = await probe(this.options.source);
71
+ dbg.info("bootstrap", "start");
72
+ const ctx = await dbg.timed("probe", "probe", 3000, () => probe(this.options.source));
73
+ dbg.info("probe",
74
+ `container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} ` +
75
+ `audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`,
76
+ );
70
77
  this.diag.recordProbe(ctx);
71
78
  this.mediaContext = ctx;
72
79
 
@@ -99,6 +106,10 @@ export class UnifiedPlayer {
99
106
  const decision = this.options.initialStrategy
100
107
  ? buildInitialDecision(this.options.initialStrategy, ctx)
101
108
  : classify(ctx);
109
+ dbg.info("classify",
110
+ `strategy=${decision.strategy} class=${decision.class} reason="${decision.reason}"` +
111
+ (decision.fallbackChain ? ` fallback=${decision.fallbackChain.join("→")}` : ""),
112
+ );
102
113
  this.classification = decision;
103
114
  this.diag.recordClassification(decision);
104
115
 
@@ -137,6 +148,16 @@ export class UnifiedPlayer {
137
148
  this.startTimeupdateLoop();
138
149
  this.options.target.addEventListener("ended", () => this.emitter.emit("ended", undefined));
139
150
  this.emitter.emitSticky("ready", undefined);
151
+ const bootstrapElapsed = performance.now() - bootstrapStart;
152
+ dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
153
+ if (bootstrapElapsed > 5000) {
154
+ // eslint-disable-next-line no-console
155
+ console.warn(
156
+ "[avbridge:bootstrap]",
157
+ `total bootstrap time ${bootstrapElapsed.toFixed(0)}ms — unusually slow. ` +
158
+ `Enable globalThis.AVBRIDGE_DEBUG for a per-phase breakdown.`,
159
+ );
160
+ }
140
161
  } catch (err) {
141
162
  const e = err instanceof Error ? err : new Error(String(err));
142
163
  this.diag.recordError(e);
package/src/probe/avi.ts CHANGED
@@ -177,7 +177,9 @@ function ffmpegToAvbridgeVideo(name: string): VideoCodec {
177
177
  case "mpeg2video": return "mpeg2";
178
178
  case "mpeg1video": return "mpeg1";
179
179
  case "theora": return "theora";
180
- case "rv30":
180
+ case "rv10": return "rv10";
181
+ case "rv20": return "rv20";
182
+ case "rv30": return "rv30";
181
183
  case "rv40": return "rv40";
182
184
  default: return name as VideoCodec;
183
185
  }
@@ -198,6 +200,11 @@ function ffmpegToAvbridgeAudio(name: string): AudioCodec {
198
200
  case "wmav2": return "wmav2";
199
201
  case "wmapro": return "wmapro";
200
202
  case "alac": return "alac";
203
+ case "cook": return "cook";
204
+ case "ra_144": return "ra_144";
205
+ case "ra_288": return "ra_288";
206
+ case "sipr": return "sipr";
207
+ case "atrac3": return "atrac3";
201
208
  default: return name as AudioCodec;
202
209
  }
203
210
  }
@@ -172,9 +172,31 @@ export class AudioOutput implements ClockSource {
172
172
  node.connect(this.gain);
173
173
 
174
174
  // Convert media time → ctx time using the anchor.
175
- const ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
176
- const safeStart = Math.max(ctxStart, this.ctx.currentTime);
177
- node.start(safeStart);
175
+ let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
176
+
177
+ // When the decoder is slower than realtime, `ctxStart` falls into
178
+ // the past (ctx.currentTime has already passed it). Clamping each
179
+ // sample to `ctx.currentTime` individually (the old behavior)
180
+ // caused every stale sample in a burst to start at *the same
181
+ // instant*, stacking them on top of each other — the audible
182
+ // symptom was a series of clicks / a chord of stuttering cook
183
+ // packets.
184
+ //
185
+ // Correct behavior: when the first sample of a burst is behind,
186
+ // *rebase the anchor forward* so ctxStart = ctx.currentTime now.
187
+ // Subsequent samples in the same burst then schedule at
188
+ // ctxStart + offset as usual, laying out sequentially on the
189
+ // timeline instead of piling up. The downside is a visible jump
190
+ // in the audio clock — but the alternative was silent corruption.
191
+ // `now()` readers (the video renderer) just see the clock step
192
+ // forward and drop any frames older than the new time.
193
+ if (ctxStart < this.ctx.currentTime) {
194
+ this.ctxTimeAtAnchor = this.ctx.currentTime;
195
+ this.mediaTimeOfAnchor = this.mediaTimeOfNext;
196
+ ctxStart = this.ctx.currentTime;
197
+ }
198
+
199
+ node.start(ctxStart);
178
200
 
179
201
  this.mediaTimeOfNext += frameCount / sampleRate;
180
202
  this.framesScheduled++;
@@ -135,6 +135,20 @@ export async function startDecoder(opts: StartDecoderOptions): Promise<DecoderHa
135
135
  let videoFramesDecoded = 0;
136
136
  let audioFramesDecoded = 0;
137
137
 
138
+ // Decode-rate watchdog. Samples framesDecoded every second and
139
+ // compares against realtime expected frames for the source fps. If
140
+ // the decoder sustains less than 60% of realtime for more than
141
+ // 5 seconds (counting only time since the first frame emerged),
142
+ // emits a one-shot diagnostic so users know why playback is
143
+ // stuttering instead of guessing. A second one-shot fires if the
144
+ // renderer's overflow-drop rate exceeds 10% of decoded frames —
145
+ // that symptom means the decoder is BURSTING faster than the
146
+ // renderer can drain, which is a different bug from "decoder slow".
147
+ let watchdogFirstFrameMs = 0;
148
+ let watchdogSlowSinceMs = 0;
149
+ let watchdogSlowWarned = false;
150
+ let watchdogOverflowWarned = false;
151
+
138
152
  // Synthetic timestamp counters. Reset on seek.
139
153
  let syntheticVideoUs = 0;
140
154
  let syntheticAudioUs = 0;
@@ -150,10 +164,18 @@ export async function startDecoder(opts: StartDecoderOptions): Promise<DecoderHa
150
164
  let readErr: number;
151
165
  let packets: Record<number, LibavPacket[]>;
152
166
  try {
153
- // Smaller batch = fewer frames per decode round = less queue burst.
154
- // 16 KB 4 video packets + ~12 audio packets at typical DivX
155
- // bitrates. The renderer drains ~1 frame per 33ms rAF tick, so
156
- // keeping bursts 4-6 frames prevents queue overflow.
167
+ // Batch size tunes the tradeoff between JS↔WASM call overhead
168
+ // (small = more crossings per second) and queue burstiness
169
+ // (large = decoder hands the renderer big bursts at once that
170
+ // can blow past the renderer's 64-frame hard cap before the
171
+ // per-batch `queueHighWater` throttle runs).
172
+ //
173
+ // We tried 64 KB and saw ~30% overflow drops on RMVB:rv40 at
174
+ // 1024x768 because one decode batch regularly produced >30
175
+ // frames. 16 KB keeps each batch ≈ 4-6 video packets at
176
+ // typical bitrates, so the worst-case queue spike stays under
177
+ // `queueHighWater` and the throttle has a chance to apply
178
+ // backpressure *between* batches rather than within one.
157
179
  [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
158
180
  limit: 16 * 1024,
159
181
  });
@@ -167,16 +189,82 @@ export async function startDecoder(opts: StartDecoderOptions): Promise<DecoderHa
167
189
  const videoPackets = videoStream ? packets[videoStream.index] : undefined;
168
190
  const audioPackets = audioStream ? packets[audioStream.index] : undefined;
169
191
 
170
- if (videoDec && videoPackets && videoPackets.length > 0) {
171
- await decodeVideoBatch(videoPackets, myToken);
172
- }
173
- if (myToken !== pumpToken || destroyed) return;
192
+ // Decode audio BEFORE video. On software-decode-bound content
193
+ // (rv40/mpeg4/wmv3 @ 720p+) a single video batch can take
194
+ // 200-400 ms of wall time; if the scheduler hasn't been fed
195
+ // during that window, audio output runs dry and the user hears
196
+ // clicks/gaps. Audio is time-critical; video can drop a frame
197
+ // and nobody notices. Audio decode is also typically <1 ms per
198
+ // packet for cook/mp3/aac, so doing it first barely delays
199
+ // video decoding at all.
174
200
  if (audioDec && audioPackets && audioPackets.length > 0) {
175
201
  await decodeAudioBatch(audioPackets, myToken);
176
202
  }
203
+ if (myToken !== pumpToken || destroyed) return;
204
+ if (videoDec && videoPackets && videoPackets.length > 0) {
205
+ await decodeVideoBatch(videoPackets, myToken);
206
+ }
177
207
 
178
208
  packetsRead += (videoPackets?.length ?? 0) + (audioPackets?.length ?? 0);
179
209
 
210
+ // ── Decode-rate watchdog ──────────────────────────────────────
211
+ if (videoFramesDecoded > 0) {
212
+ if (watchdogFirstFrameMs === 0) {
213
+ watchdogFirstFrameMs = performance.now();
214
+ }
215
+ const elapsedSinceFirst = (performance.now() - watchdogFirstFrameMs) / 1000;
216
+
217
+ // 1. Slow-decode detection (sustained <60% of realtime fps).
218
+ if (elapsedSinceFirst > 1 && !watchdogSlowWarned) {
219
+ const expectedFrames = elapsedSinceFirst * videoFps;
220
+ const ratio = videoFramesDecoded / expectedFrames;
221
+ if (ratio < 0.6) {
222
+ if (watchdogSlowSinceMs === 0) watchdogSlowSinceMs = performance.now();
223
+ if ((performance.now() - watchdogSlowSinceMs) / 1000 > 5) {
224
+ watchdogSlowWarned = true;
225
+ console.warn(
226
+ "[avbridge:decode-rate]",
227
+ `decoder is running slower than realtime: ` +
228
+ `${videoFramesDecoded} frames in ${elapsedSinceFirst.toFixed(1)}s ` +
229
+ `(${(videoFramesDecoded / elapsedSinceFirst).toFixed(1)} fps vs ${videoFps} fps source — ` +
230
+ `${(ratio * 100).toFixed(0)}% of realtime). ` +
231
+ `Playback will stutter. Typical causes: software decode of a codec with no WebCodecs support ` +
232
+ `(rv40, mpeg4 @ 720p+, wmv3), or a resolution too large for single-threaded WASM to keep up with.`,
233
+ );
234
+ }
235
+ } else {
236
+ watchdogSlowSinceMs = 0;
237
+ }
238
+ }
239
+
240
+ // 2. Overflow-drop detection (>10% of decoded frames dropped
241
+ // by the renderer's hard cap). This means the decoder
242
+ // produces BURSTS — it's fast enough on average but one
243
+ // batch delivers >30 frames at a time, overflowing before
244
+ // the queueHighWater throttle can apply backpressure.
245
+ // Symptom is different from "decoder slow": here the fps
246
+ // ratio looks fine but the user sees choppy playback.
247
+ if (
248
+ !watchdogOverflowWarned &&
249
+ videoFramesDecoded > 100 // wait for a meaningful sample
250
+ ) {
251
+ const rendererStats = opts.renderer.stats() as { framesDroppedOverflow?: number };
252
+ const overflow = rendererStats.framesDroppedOverflow ?? 0;
253
+ if (overflow / videoFramesDecoded > 0.1) {
254
+ watchdogOverflowWarned = true;
255
+ console.warn(
256
+ "[avbridge:overflow-drop]",
257
+ `renderer is dropping ${overflow}/${videoFramesDecoded} frames ` +
258
+ `(${((overflow / videoFramesDecoded) * 100).toFixed(0)}%) because the decoder ` +
259
+ `is producing bursts faster than the canvas can drain. Symptom: choppy ` +
260
+ `playback despite decoder keeping up on average. Fix would be smaller ` +
261
+ `read batches in the pump loop or a lower queueHighWater cap — see ` +
262
+ `src/strategies/fallback/decoder.ts.`,
263
+ );
264
+ }
265
+ }
266
+ }
267
+
180
268
  // Throttle: don't run too far ahead of playback. Two backpressure
181
269
  // signals:
182
270
  // - Audio buffer (mediaTimeOfNext - now()) > 2 sec — we have