avbridge 2.0.0 → 2.1.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 (40) hide show
  1. package/CHANGELOG.md +52 -1
  2. package/NOTICE.md +82 -0
  3. package/THIRD_PARTY_LICENSES.md +121 -0
  4. package/dist/{avi-M5B4SHRM.cjs → avi-GNTV5ZOH.cjs} +4 -4
  5. package/dist/{avi-M5B4SHRM.cjs.map → avi-GNTV5ZOH.cjs.map} +1 -1
  6. package/dist/{avi-POCGZ4JX.js → avi-V6HYQVR2.js} +3 -3
  7. package/dist/{avi-POCGZ4JX.js.map → avi-V6HYQVR2.js.map} +1 -1
  8. package/dist/{chunk-HZF5JDOO.js → chunk-CUQD23WO.js} +4 -4
  9. package/dist/{chunk-HZF5JDOO.js.map → chunk-CUQD23WO.js.map} +1 -1
  10. package/dist/{chunk-5ISVAODK.js → chunk-EJH67FXG.js} +30 -6
  11. package/dist/chunk-EJH67FXG.js.map +1 -0
  12. package/dist/{chunk-Z2FJ5TJC.cjs → chunk-JQH6D4OE.cjs} +31 -6
  13. package/dist/chunk-JQH6D4OE.cjs.map +1 -0
  14. package/dist/{chunk-TTV56KDB.cjs → chunk-O34444ID.cjs} +6 -6
  15. package/dist/{chunk-TTV56KDB.cjs.map → chunk-O34444ID.cjs.map} +1 -1
  16. package/dist/element-browser.js +33647 -0
  17. package/dist/element-browser.js.map +1 -0
  18. package/dist/element.cjs +3 -3
  19. package/dist/element.js +2 -2
  20. package/dist/index.cjs +16 -16
  21. package/dist/index.js +4 -4
  22. package/dist/libav-loader-6APXVNIV.cjs +12 -0
  23. package/dist/{libav-loader-ZHOERPHW.cjs.map → libav-loader-6APXVNIV.cjs.map} +1 -1
  24. package/dist/libav-loader-XKH2TKUW.js +3 -0
  25. package/dist/{libav-loader-KA2MAWLM.js.map → libav-loader-XKH2TKUW.js.map} +1 -1
  26. package/package.json +19 -7
  27. package/src/strategies/fallback/libav-loader.ts +49 -7
  28. package/src/stubs/node-fs-promises.ts +29 -0
  29. package/vendor/libav/README.md +139 -0
  30. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +688 -0
  31. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  32. package/vendor/libav/avbridge/libav-avbridge.mjs +1 -0
  33. package/vendor/libav/webcodecs/libav-6.8.8.0-webcodecs.wasm.mjs +747 -0
  34. package/vendor/libav/webcodecs/libav-6.8.8.0-webcodecs.wasm.wasm +0 -0
  35. package/vendor/libav/webcodecs/libav-webcodecs.mjs +1 -0
  36. package/vendor/libav/webcodecs/libav.types.d.ts +5222 -0
  37. package/dist/chunk-5ISVAODK.js.map +0 -1
  38. package/dist/chunk-Z2FJ5TJC.cjs.map +0 -1
  39. package/dist/libav-loader-KA2MAWLM.js +0 -3
  40. package/dist/libav-loader-ZHOERPHW.cjs +0 -12
package/dist/element.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkTTV56KDB_cjs = require('./chunk-TTV56KDB.cjs');
3
+ var chunkO34444ID_cjs = require('./chunk-O34444ID.cjs');
4
4
  require('./chunk-Y5FYF5KG.cjs');
5
- require('./chunk-Z2FJ5TJC.cjs');
5
+ require('./chunk-JQH6D4OE.cjs');
6
6
  require('./chunk-NZU7W256.cjs');
7
7
 
8
8
  // src/element/avbridge-video.ts
@@ -222,7 +222,7 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
222
222
  this._dispatch("loadstart", {});
223
223
  let player;
224
224
  try {
225
- player = await chunkTTV56KDB_cjs.createPlayer({
225
+ player = await chunkO34444ID_cjs.createPlayer({
226
226
  source,
227
227
  target: this._videoEl,
228
228
  // Honor the consumer's preferred initial strategy. "auto" means
package/dist/element.js CHANGED
@@ -1,6 +1,6 @@
1
- import { createPlayer } from './chunk-HZF5JDOO.js';
1
+ import { createPlayer } from './chunk-CUQD23WO.js';
2
2
  import './chunk-PQTZS7OA.js';
3
- import './chunk-5ISVAODK.js';
3
+ import './chunk-EJH67FXG.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 chunkTTV56KDB_cjs = require('./chunk-TTV56KDB.cjs');
3
+ var chunkO34444ID_cjs = require('./chunk-O34444ID.cjs');
4
4
  var chunkY5FYF5KG_cjs = require('./chunk-Y5FYF5KG.cjs');
5
5
  var chunkL4NPOJ36_cjs = require('./chunk-L4NPOJ36.cjs');
6
- require('./chunk-Z2FJ5TJC.cjs');
6
+ require('./chunk-JQH6D4OE.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 chunkTTV56KDB_cjs.probe(source);
24
+ const ctx = await chunkO34444ID_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 = chunkTTV56KDB_cjs.avbridgeVideoToMediabunny(video.codec);
36
+ const mbCodec = chunkO34444ID_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 = chunkTTV56KDB_cjs.avbridgeAudioToMediabunny(audio.codec);
44
+ const mbCodec = chunkO34444ID_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 chunkTTV56KDB_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
63
+ source: await chunkO34444ID_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-ZHOERPHW.cjs');
120
+ const loader = await import('./libav-loader-6APXVNIV.cjs');
121
121
  const routing = await import('./variant-routing-GOHB2RZN.cjs');
122
122
  loadLibav = loader.loadLibav;
123
123
  pickLibavVariant = routing.pickLibavVariant;
@@ -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 ? chunkTTV56KDB_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
150
- const mbAudioCodec = audioTrackInfo ? chunkTTV56KDB_cjs.avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
149
+ const mbVideoCodec = videoTrackInfo ? chunkO34444ID_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
150
+ const mbAudioCodec = audioTrackInfo ? chunkO34444ID_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 chunkTTV56KDB_cjs.probe(source);
361
+ const ctx = await chunkO34444ID_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 chunkTTV56KDB_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
373
+ source: await chunkO34444ID_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 chunkTTV56KDB_cjs.UnifiedPlayer; }
555
+ get: function () { return chunkO34444ID_cjs.UnifiedPlayer; }
556
556
  });
557
557
  Object.defineProperty(exports, "classify", {
558
558
  enumerable: true,
559
- get: function () { return chunkTTV56KDB_cjs.classifyContext; }
559
+ get: function () { return chunkO34444ID_cjs.classifyContext; }
560
560
  });
561
561
  Object.defineProperty(exports, "createPlayer", {
562
562
  enumerable: true,
563
- get: function () { return chunkTTV56KDB_cjs.createPlayer; }
563
+ get: function () { return chunkO34444ID_cjs.createPlayer; }
564
564
  });
565
565
  Object.defineProperty(exports, "probe", {
566
566
  enumerable: true,
567
- get: function () { return chunkTTV56KDB_cjs.probe; }
567
+ get: function () { return chunkO34444ID_cjs.probe; }
568
568
  });
569
569
  Object.defineProperty(exports, "srtToVtt", {
570
570
  enumerable: true,
571
- get: function () { return chunkTTV56KDB_cjs.srtToVtt; }
571
+ get: function () { return chunkO34444ID_cjs.srtToVtt; }
572
572
  });
573
573
  exports.remux = remux;
574
574
  exports.transcode = transcode;
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-HZF5JDOO.js';
2
- export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-HZF5JDOO.js';
1
+ import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-CUQD23WO.js';
2
+ export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-CUQD23WO.js';
3
3
  import { normalizeSource } from './chunk-PQTZS7OA.js';
4
4
  import { prepareLibavInput } from './chunk-WD2ZNQA7.js';
5
- import './chunk-5ISVAODK.js';
5
+ import './chunk-EJH67FXG.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-KA2MAWLM.js');
119
+ const loader = await import('./libav-loader-XKH2TKUW.js');
120
120
  const routing = await import('./variant-routing-JOBWXYKD.js');
121
121
  loadLibav = loader.loadLibav;
122
122
  pickLibavVariant = routing.pickLibavVariant;
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var chunkJQH6D4OE_cjs = require('./chunk-JQH6D4OE.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "loadLibav", {
8
+ enumerable: true,
9
+ get: function () { return chunkJQH6D4OE_cjs.loadLibav; }
10
+ });
11
+ //# sourceMappingURL=libav-loader-6APXVNIV.cjs.map
12
+ //# sourceMappingURL=libav-loader-6APXVNIV.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-ZHOERPHW.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-6APXVNIV.cjs"}
@@ -0,0 +1,3 @@
1
+ export { loadLibav } from './chunk-EJH67FXG.js';
2
+ //# sourceMappingURL=libav-loader-XKH2TKUW.js.map
3
+ //# sourceMappingURL=libav-loader-XKH2TKUW.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-KA2MAWLM.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-XKH2TKUW.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avbridge",
3
- "version": "2.0.0",
3
+ "version": "2.1.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",
@@ -34,7 +34,8 @@
34
34
  "type": "module",
35
35
  "sideEffects": [
36
36
  "./dist/element.js",
37
- "./dist/element.cjs"
37
+ "./dist/element.cjs",
38
+ "./dist/element-browser.js"
38
39
  ],
39
40
  "main": "./dist/index.cjs",
40
41
  "module": "./dist/index.js",
@@ -49,16 +50,27 @@
49
50
  "types": "./dist/element.d.ts",
50
51
  "import": "./dist/element.js",
51
52
  "require": "./dist/element.cjs"
52
- }
53
+ },
54
+ "./element-browser": {
55
+ "types": "./dist/element.d.ts",
56
+ "import": "./dist/element-browser.js"
57
+ },
58
+ "./vendor/*": "./vendor/*"
53
59
  },
54
60
  "files": [
55
61
  "dist",
56
62
  "src",
63
+ "vendor/libav/avbridge",
64
+ "vendor/libav/webcodecs",
65
+ "vendor/libav/README.md",
57
66
  "README.md",
58
67
  "CHANGELOG.md",
59
- "LICENSE"
68
+ "LICENSE",
69
+ "NOTICE.md",
70
+ "THIRD_PARTY_LICENSES.md"
60
71
  ],
61
72
  "scripts": {
73
+ "prebuild": "node scripts/copy-libav.mjs",
62
74
  "build": "tsup",
63
75
  "typecheck": "tsc --noEmit",
64
76
  "test": "vitest run",
@@ -76,12 +88,12 @@
76
88
  "audit:bundle": "node scripts/bundle-audit.mjs"
77
89
  },
78
90
  "dependencies": {
91
+ "@libav.js/variant-webcodecs": "^6.8.8",
92
+ "libavjs-webcodecs-bridge": "^0.3.2",
79
93
  "mediabunny": "^1.40.1"
80
94
  },
81
95
  "optionalDependencies": {
82
- "@libav.js/types": "^6.8.8",
83
- "@libav.js/variant-webcodecs": "^6.8.8",
84
- "libavjs-webcodecs-bridge": "^0.3.2"
96
+ "@libav.js/types": "^6.8.8"
85
97
  },
86
98
  "devDependencies": {
87
99
  "@types/node": "^20.11.0",
@@ -92,6 +92,31 @@ async function loadVariant(
92
92
  // the same convention (`libav-webcodecs.mjs`, `libav-default.mjs`).
93
93
  const variantUrl = `${base}/libav-${variant}.mjs`;
94
94
 
95
+ // Preflight HEAD-ish check: issue a bytes=0-0 range request so a missing
96
+ // file fails fast with a clear error instead of hanging deep inside the
97
+ // dynamic import or inside libav's own WASM instantiation. Surfaces the
98
+ // most common mistake ("libav base path is wrong") in <100 ms instead of
99
+ // an indeterminate stall.
100
+ if (typeof fetch === "function") {
101
+ try {
102
+ const head = await fetch(variantUrl, { method: "GET", headers: { Range: "bytes=0-0" } });
103
+ if (!head.ok && head.status !== 206) {
104
+ throw new Error(
105
+ `HTTP ${head.status} ${head.statusText} — check that libav files are served ` +
106
+ `at ${base}/ (override via globalThis.AVBRIDGE_LIBAV_BASE)`,
107
+ );
108
+ }
109
+ // Drain the tiny response so the connection can be reused.
110
+ try { await head.arrayBuffer(); } catch { /* ignore */ }
111
+ } catch (err) {
112
+ cache.delete(key);
113
+ throw chain(
114
+ `libav.js "${variant}" variant not reachable at ${variantUrl}`,
115
+ err,
116
+ );
117
+ }
118
+ }
119
+
95
120
  let mod: LoadedVariant;
96
121
  try {
97
122
  // @ts-ignore runtime URL
@@ -105,10 +130,9 @@ async function loadVariant(
105
130
  const hint =
106
131
  variant === "avbridge"
107
132
  ? `The "avbridge" variant is a custom local build. Run \`./scripts/build-libav.sh\` ` +
108
- `to produce it (requires Emscripten; ~15-30 min the first time), then ` +
109
- `\`npm run predemo\` to copy it into the demo asset path.`
110
- : `Make sure the variant files are present (run \`npm run predemo\` or copy ` +
111
- `node_modules/@libav.js/variant-${variant}/dist/* into the URL space).`;
133
+ `to produce it (requires Emscripten; ~15-30 min the first time).`
134
+ : `Make sure the variant files are present at ${base}/ (set ` +
135
+ `globalThis.AVBRIDGE_LIBAV_BASE to override the default lookup path).`;
112
136
  throw new Error(
113
137
  `failed to load libav.js "${variant}" variant from ${variantUrl}. ${hint} ` +
114
138
  `Original error: ${(err as Error).message || String(err)}`,
@@ -161,15 +185,33 @@ function buildOpts(base: string, wantThreads: boolean): Record<string, unknown>
161
185
  }
162
186
 
163
187
  function libavBaseUrl(): string {
188
+ // Consumer override — the documented "LGPL replaceability" hook.
189
+ // Setting `globalThis.AVBRIDGE_LIBAV_BASE = "/my/path"` lets anyone swap
190
+ // in a different libav build (custom fragments, security patches, etc.)
191
+ // without rebuilding avbridge.
164
192
  const override =
165
193
  typeof globalThis !== "undefined"
166
194
  ? (globalThis as { AVBRIDGE_LIBAV_BASE?: string }).AVBRIDGE_LIBAV_BASE
167
195
  : undefined;
168
196
  if (override) return override;
169
- if (typeof location !== "undefined" && location.protocol.startsWith("http")) {
170
- return `${location.origin}/libav`;
197
+
198
+ // Default: resolve relative to this module's URL. When avbridge is installed
199
+ // under `node_modules/avbridge/`, this module lives at `dist/chunk-*.js` (or
200
+ // `dist/element-browser.js` for the browser entry) and `../vendor/libav`
201
+ // resolves to `node_modules/avbridge/vendor/libav`, where the build step
202
+ // vendored every variant's binaries. That's the zero-config path.
203
+ //
204
+ // `import.meta.url` throws in some synthetic environments (CJS tests, some
205
+ // SSR evaluators). If it fails, fall back to the legacy `/libav` path so
206
+ // consumers who relied on the pre-2.1 behavior still work.
207
+ try {
208
+ return new URL("../vendor/libav", import.meta.url).href;
209
+ } catch {
210
+ if (typeof location !== "undefined" && location.protocol.startsWith("http")) {
211
+ return `${location.origin}/libav`;
212
+ }
213
+ return "/libav";
171
214
  }
172
- return "/libav";
173
215
  }
174
216
 
175
217
  function chain(message: string, err: unknown): Error {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Browser stub for `node:fs/promises`.
3
+ *
4
+ * mediabunny's ESM entry transitively imports `node:fs/promises` from its
5
+ * Node-compat `node.js` file (used by `FilePathSource` / `FilePathTarget`).
6
+ * Its `package.json` has a `browser` field that tells bundlers to stub this
7
+ * out at build time — but import maps in the browser can't read
8
+ * `package.json`, so for the pre-bundled `dist/element-browser.js` entry we
9
+ * have tsup/esbuild alias `node:fs/promises` → this file at bundle time.
10
+ *
11
+ * Any caller that reaches this code is trying to use a Node-only feature in
12
+ * the browser and will fail loudly with a clear message instead of hanging
13
+ * or throwing an opaque `undefined is not a function` somewhere deep in
14
+ * mediabunny.
15
+ */
16
+ function notAvailable(): never {
17
+ throw new Error(
18
+ "node:fs/promises is not available in the browser. " +
19
+ "The file-path APIs (FilePathSource / FilePathTarget) only work in Node.js.",
20
+ );
21
+ }
22
+
23
+ export const open = notAvailable;
24
+ export const readFile = notAvailable;
25
+ export const writeFile = notAvailable;
26
+ export const stat = notAvailable;
27
+ export const mkdir = notAvailable;
28
+ export const rm = notAvailable;
29
+ export const unlink = notAvailable;
@@ -0,0 +1,139 @@
1
+ # vendor/libav — avbridge custom libav.js build
2
+
3
+ This directory holds a **custom-built libav.js variant** with the demuxers
4
+ and decoders avbridge needs for legacy file playback. The npm-published variants
5
+ of libav.js are intentionally minimal — none of them include the AVI/ASF/FLV
6
+ demuxers or the legacy codec decoders (WMV3, MPEG-4 Part 2, MS-MPEG4, VC-1,
7
+ …). The supported way to get those is to build a custom variant locally.
8
+
9
+ ## TL;DR
10
+
11
+ ```bash
12
+ ./scripts/build-libav.sh # 15-30 minutes the first time
13
+ npm run predemo # copies vendor/libav/* into demo/public/libav/avbridge/
14
+ npm run demo
15
+ ```
16
+
17
+ After the first run, `~/.cache/avbridge/` contains the emsdk and libav.js source
18
+ trees so subsequent rebuilds (e.g. after editing the fragment list) are
19
+ incremental and much faster.
20
+
21
+ ## What gets built
22
+
23
+ The fragment list in `scripts/build-libav.sh` enables:
24
+
25
+ | Demuxers | Decoders (video) | Decoders (audio) | Bitstream filters |
26
+ |---|---|---|---|
27
+ | avi, asf, flv, matroska, mov, mp3, ogg, wav, aac | h264, hevc, mpeg4 (Part 2 / DivX / Xvid), msmpeg4 v1/v2/v3, wmv1/2/3, vc1, mpeg1, mpeg2 | aac, mp3, ac3, eac3, wmav1/v2, wmapro | mpeg4_unpack_bframes |
28
+
29
+ Plus the parsers needed by each codec, plus `swscale` (video colorspace
30
+ conversion) and `swresample` (audio resampling).
31
+
32
+ The `mpeg4_unpack_bframes` BSF fixes the "packed B-frames" oddity in some
33
+ DivX files where two frames are stored in one packet — without the BSF the
34
+ decoder produces frames with fuzzy timing that the renderer drops as late.
35
+
36
+ Output binary is roughly 10–15 MB. It is loaded **lazily** by avbridge — only
37
+ when probe or classification routes a file to it. Users who only ever play
38
+ modern MP4/MKV/WebM never download it.
39
+
40
+ ## Compile flags
41
+
42
+ The default libav.js Makefile uses `OPTFLAGS=-Oz` (size-optimized, slow).
43
+ We override to `-O3 -msimd128`:
44
+
45
+ - `-O3` — full optimization, ~1.5–2× speedup over `-Oz` for video decode.
46
+ - `-msimd128` — emit WebAssembly SIMD instructions. emscripten translates
47
+ ffmpeg's SSE2-style intrinsics into WASM SIMD ops, which gives another
48
+ ~1.5–2× for IDCT, motion compensation, deblocking. Requires a browser
49
+ with WASM SIMD (Chrome 91+, Firefox 89+, Safari 16.4+).
50
+
51
+ Override at invocation time:
52
+
53
+ ```bash
54
+ avbridge_LIBAV_OPTFLAGS="-O2" ./scripts/build-libav.sh
55
+ ```
56
+
57
+ The script tracks the hash of `(fragments, OPTFLAGS)` in
58
+ `~/.cache/avbridge/libav.js/.avbridge-build-inputs`. If you re-run with different
59
+ inputs, it wipes `build/ffmpeg-*` and `build/inst` to force a clean
60
+ rebuild — Make can't detect OPTFLAGS changes on its own, so without the
61
+ hash check stale `-Oz` objects would silently survive.
62
+
63
+ ## Adding or removing codecs
64
+
65
+ Edit the `VARIANT_FRAGMENTS` heredoc in `scripts/build-libav.sh`. The
66
+ fragment names follow FFmpeg's `--enable-decoder=<name>` /
67
+ `--enable-demuxer=<name>` conventions:
68
+
69
+ - `demuxer-<format>` — read this container
70
+ - `decoder-<codec>` — decode this codec
71
+ - `parser-<codec>` — parse stream metadata for this codec (tiny, usually
72
+ needed alongside the matching decoder for seeking)
73
+
74
+ See [`docs/CONFIG.md`][config-md] in the libav.js repo for the full grammar
75
+ and [`configs/mkconfigs.js`][mkconfigs] for the names of every fragment.
76
+
77
+ [config-md]: https://github.com/Yahweasel/libav.js/blob/master/docs/CONFIG.md
78
+ [mkconfigs]: https://github.com/Yahweasel/libav.js/blob/master/configs/mkconfigs.js
79
+
80
+ After editing, re-run `./scripts/build-libav.sh` — the make-based build is
81
+ incremental, so changes only rebuild the affected pieces.
82
+
83
+ ## Build script behavior
84
+
85
+ - Caches everything under `$avbridge_BUILD_CACHE` (default `~/.cache/avbridge`).
86
+ Override the cache location by setting that env var.
87
+ - Installs **emsdk** into `~/.cache/avbridge/emsdk` — does **not** touch your
88
+ system Python, Homebrew, or any global package manager. Only the
89
+ `emsdk/` directory is modified.
90
+ - Clones **libav.js** into `~/.cache/avbridge/libav.js` and checks out the
91
+ pinned `v6.8.8.0` tag.
92
+ - Writes a custom variant config via `node configs/mkconfig.js avbridge [...]`.
93
+ - Runs `make build-avbridge`, which downloads ffmpeg sources, applies libav.js's
94
+ patches, and compiles to WASM.
95
+ - Copies the resulting `libav-6.8.8.0-avbridge.{mjs,wasm.mjs,wasm.wasm,…}` into
96
+ this directory.
97
+
98
+ To force a clean rebuild from scratch:
99
+
100
+ ```bash
101
+ avbridge_LIBAV_CLEAN=1 ./scripts/build-libav.sh
102
+ ```
103
+
104
+ ## Loader integration
105
+
106
+ The avbridge runtime knows about three variants — `webcodecs`, `default`, `avbridge`
107
+ — defined in `src/strategies/fallback/libav-loader.ts`. The variant routing
108
+ in `src/strategies/fallback/variant-routing.ts` decides which one a given
109
+ `MediaContext` needs:
110
+
111
+ - Modern containers + browser-supported codecs → `webcodecs`
112
+ - AVI / ASF / FLV containers, or any of the legacy codec set → `avbridge`
113
+
114
+ The loader fetches each variant via a runtime URL with `/* @vite-ignore */`,
115
+ so Vite never pre-bundles it. The variant's `import.meta.url` resolves to
116
+ `/libav/<variant>/libav-<variant>.mjs` and its sibling `.wasm.mjs` /
117
+ `.wasm.wasm` files are served from the same directory.
118
+
119
+ ## Licensing
120
+
121
+ libav.js is **LGPL-2.1**. If you ship a custom variant in a product, you
122
+ must also distribute the corresponding source / build script. Keep
123
+ `scripts/build-libav.sh` and the libav.js repo URL alongside any binaries
124
+ you redistribute.
125
+
126
+ ## Files in this directory after a successful build
127
+
128
+ ```
129
+ vendor/libav/
130
+ ├── README.md ← this file
131
+ ├── libav-avbridge.mjs ← entry point (loaded by avbridge)
132
+ ├── libav-6.8.8.0-avbridge.wasm.mjs ← WASM build factory
133
+ ├── libav-6.8.8.0-avbridge.wasm.wasm ← compiled binary
134
+ ├── libav-6.8.8.0-avbridge.thr.mjs ← threaded build factory (optional)
135
+ ├── libav-6.8.8.0-avbridge.thr.wasm ← threaded binary (optional)
136
+ └── libav-6.8.8.0-avbridge.asm.{js,mjs} ← asm.js fallback (very rarely used)
137
+ ```
138
+
139
+ The script also copies `.dbg.*` debug builds when present.