avbridge 2.2.0 → 2.3.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 (121) hide show
  1. package/CHANGELOG.md +125 -1
  2. package/NOTICE.md +2 -2
  3. package/README.md +100 -74
  4. package/THIRD_PARTY_LICENSES.md +2 -2
  5. package/dist/avi-2JPBSHGA.js +183 -0
  6. package/dist/avi-2JPBSHGA.js.map +1 -0
  7. package/dist/avi-F6WZJK5T.cjs +185 -0
  8. package/dist/avi-F6WZJK5T.cjs.map +1 -0
  9. package/dist/{avi-GCGM7OJI.js → avi-NJXAXUXK.js} +9 -3
  10. package/dist/avi-NJXAXUXK.js.map +1 -0
  11. package/dist/{avi-6SJLWIWW.cjs → avi-W6L3BTWU.cjs} +10 -4
  12. package/dist/avi-W6L3BTWU.cjs.map +1 -0
  13. package/dist/{chunk-ILKDNBSE.js → chunk-2PGRFCWB.js} +59 -10
  14. package/dist/chunk-2PGRFCWB.js.map +1 -0
  15. package/dist/chunk-5YAWWKA3.js +18 -0
  16. package/dist/chunk-5YAWWKA3.js.map +1 -0
  17. package/dist/chunk-6UUT4BEA.cjs +219 -0
  18. package/dist/chunk-6UUT4BEA.cjs.map +1 -0
  19. package/dist/{chunk-OE66B34H.cjs → chunk-7RGG6ME7.cjs} +562 -94
  20. package/dist/chunk-7RGG6ME7.cjs.map +1 -0
  21. package/dist/{chunk-WD2ZNQA7.js → chunk-DCSOQH2N.js} +7 -4
  22. package/dist/chunk-DCSOQH2N.js.map +1 -0
  23. package/dist/chunk-F3LQJKXK.cjs +20 -0
  24. package/dist/chunk-F3LQJKXK.cjs.map +1 -0
  25. package/dist/chunk-IAYKFGFG.js +200 -0
  26. package/dist/chunk-IAYKFGFG.js.map +1 -0
  27. package/dist/chunk-NNVOHKXJ.cjs +204 -0
  28. package/dist/chunk-NNVOHKXJ.cjs.map +1 -0
  29. package/dist/{chunk-C5VA5U5O.js → chunk-NV7ILLWH.js} +556 -92
  30. package/dist/chunk-NV7ILLWH.js.map +1 -0
  31. package/dist/{chunk-HZLQNKFN.cjs → chunk-QQXBPW72.js} +54 -15
  32. package/dist/chunk-QQXBPW72.js.map +1 -0
  33. package/dist/chunk-XKPSTC34.cjs +210 -0
  34. package/dist/chunk-XKPSTC34.cjs.map +1 -0
  35. package/dist/{chunk-L4NPOJ36.cjs → chunk-Z33SBWL5.cjs} +7 -4
  36. package/dist/chunk-Z33SBWL5.cjs.map +1 -0
  37. package/dist/element-browser.js +631 -103
  38. package/dist/element-browser.js.map +1 -1
  39. package/dist/element.cjs +4 -4
  40. package/dist/element.d.cts +1 -1
  41. package/dist/element.d.ts +1 -1
  42. package/dist/element.js +3 -3
  43. package/dist/index.cjs +174 -26
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.cts +48 -4
  46. package/dist/index.d.ts +48 -4
  47. package/dist/index.js +93 -12
  48. package/dist/index.js.map +1 -1
  49. package/dist/libav-http-reader-AZLE7YFS.cjs +16 -0
  50. package/dist/{libav-http-reader-FPYDBMYK.cjs.map → libav-http-reader-AZLE7YFS.cjs.map} +1 -1
  51. package/dist/libav-http-reader-WXG3Z7AI.js +3 -0
  52. package/dist/{libav-http-reader-NQJVY273.js.map → libav-http-reader-WXG3Z7AI.js.map} +1 -1
  53. package/dist/{player-DUyvltvy.d.cts → player-B6WB74RD.d.cts} +63 -3
  54. package/dist/{player-DUyvltvy.d.ts → player-B6WB74RD.d.ts} +63 -3
  55. package/dist/player.cjs +5500 -0
  56. package/dist/player.cjs.map +1 -0
  57. package/dist/player.d.cts +649 -0
  58. package/dist/player.d.ts +649 -0
  59. package/dist/player.js +5498 -0
  60. package/dist/player.js.map +1 -0
  61. package/dist/source-73CAH6HW.cjs +28 -0
  62. package/dist/{source-CN43EI7Z.cjs.map → source-73CAH6HW.cjs.map} +1 -1
  63. package/dist/source-F656KYYV.js +3 -0
  64. package/dist/{source-FFZ7TW2B.js.map → source-F656KYYV.js.map} +1 -1
  65. package/dist/source-QJR3OHTW.js +3 -0
  66. package/dist/source-QJR3OHTW.js.map +1 -0
  67. package/dist/source-VB74JQ7Z.cjs +28 -0
  68. package/dist/source-VB74JQ7Z.cjs.map +1 -0
  69. package/dist/variant-routing-434STYAB.js +3 -0
  70. package/dist/{variant-routing-JOBWXYKD.js.map → variant-routing-434STYAB.js.map} +1 -1
  71. package/dist/variant-routing-HONNAA6R.cjs +12 -0
  72. package/dist/{variant-routing-GOHB2RZN.cjs.map → variant-routing-HONNAA6R.cjs.map} +1 -1
  73. package/package.json +9 -1
  74. package/src/classify/rules.ts +27 -5
  75. package/src/convert/remux.ts +8 -0
  76. package/src/convert/transcode.ts +41 -8
  77. package/src/element/avbridge-player.ts +845 -0
  78. package/src/element/player-icons.ts +25 -0
  79. package/src/element/player-styles.ts +472 -0
  80. package/src/errors.ts +47 -0
  81. package/src/index.ts +23 -0
  82. package/src/player-element.ts +18 -0
  83. package/src/player.ts +127 -27
  84. package/src/plugins/builtin.ts +2 -2
  85. package/src/probe/avi.ts +4 -0
  86. package/src/probe/index.ts +40 -10
  87. package/src/strategies/fallback/audio-output.ts +31 -0
  88. package/src/strategies/fallback/decoder.ts +83 -2
  89. package/src/strategies/fallback/index.ts +34 -1
  90. package/src/strategies/fallback/variant-routing.ts +7 -13
  91. package/src/strategies/fallback/video-renderer.ts +129 -33
  92. package/src/strategies/hybrid/decoder.ts +131 -20
  93. package/src/strategies/hybrid/index.ts +36 -2
  94. package/src/strategies/remux/index.ts +13 -1
  95. package/src/strategies/remux/mse.ts +12 -2
  96. package/src/strategies/remux/pipeline.ts +6 -0
  97. package/src/subtitles/index.ts +7 -3
  98. package/src/types.ts +53 -1
  99. package/src/util/libav-http-reader.ts +5 -1
  100. package/src/util/source.ts +28 -8
  101. package/src/util/transport.ts +26 -0
  102. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  103. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  104. package/dist/avi-6SJLWIWW.cjs.map +0 -1
  105. package/dist/avi-GCGM7OJI.js.map +0 -1
  106. package/dist/chunk-C5VA5U5O.js.map +0 -1
  107. package/dist/chunk-HZLQNKFN.cjs.map +0 -1
  108. package/dist/chunk-ILKDNBSE.js.map +0 -1
  109. package/dist/chunk-J5MCMN3S.js +0 -27
  110. package/dist/chunk-J5MCMN3S.js.map +0 -1
  111. package/dist/chunk-L4NPOJ36.cjs.map +0 -1
  112. package/dist/chunk-NZU7W256.cjs +0 -29
  113. package/dist/chunk-NZU7W256.cjs.map +0 -1
  114. package/dist/chunk-OE66B34H.cjs.map +0 -1
  115. package/dist/chunk-WD2ZNQA7.js.map +0 -1
  116. package/dist/libav-http-reader-FPYDBMYK.cjs +0 -16
  117. package/dist/libav-http-reader-NQJVY273.js +0 -3
  118. package/dist/source-CN43EI7Z.cjs +0 -28
  119. package/dist/source-FFZ7TW2B.js +0 -3
  120. package/dist/variant-routing-GOHB2RZN.cjs +0 -12
  121. package/dist/variant-routing-JOBWXYKD.js +0 -3
package/CHANGELOG.md CHANGED
@@ -1,9 +1,133 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to **avbridge** are documented here. The format follows
3
+ All notable changes to **avbridge.js** are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.3.0]
8
+
9
+ This release makes avbridge.js production-ready for authenticated
10
+ remote media, legacy codecs, and end-user embedding.
11
+
12
+ ### Added
13
+
14
+ - **`<avbridge-player>` controls-bearing element** (new subpath:
15
+ `avbridge/player`). Full YouTube-style player UI — play/pause, seek
16
+ bar, time display, volume/mute, settings menu (playback speed,
17
+ subtitle + audio tracks, Stats for Nerds), fullscreen. Auto-hide
18
+ controls, keyboard shortcuts (space/k, f, m, j/l, arrows, >/<, Esc),
19
+ touch gestures (tap-to-toggle, double-tap ±10s with ripple,
20
+ tap-and-hold for 2x speed). `::part()` hooks on every control.
21
+ - **Transport configurability** — `requestInit` and `fetchFn` on
22
+ `CreatePlayerOptions`; `probe()` takes a transport argument.
23
+ Threaded through probe Range requests, subtitle fetches, and the
24
+ libav HTTP reader. Unblocks signed URLs and custom auth headers.
25
+ - **Bitstream fixups** — `mpeg4_unpack_bframes` BSF wired into the
26
+ fallback and hybrid decoders for DivX packed-B-frame files.
27
+ Annex B → AVCC normalization in the libav remux path. Applied
28
+ filters visible in diagnostics as `bsfApplied`.
29
+ - **Structured errors** — new `AvbridgeError` class with
30
+ machine-readable `code` (`ERR_AVBRIDGE_*`) and human-readable
31
+ `recovery` hints. Applied to probe, codec, MSE, player-readiness,
32
+ and strategy-exhaustion paths.
33
+ - **DTS, TrueHD, Theora decoder support** in the custom libav
34
+ variant. Probe re-runs via libav when mediabunny returns unknown
35
+ codecs. Hybrid strategy now used for "native video + fallback
36
+ audio" combos (e.g. H.264 + DTS in Blu-ray MKV rips) instead of
37
+ the much slower full WASM fallback.
38
+ - **Streaming transcode output** via `outputStream` option. Pairs
39
+ with `showSaveFilePicker()` for multi-GB file transcoding without
40
+ loading the entire output into memory.
41
+ - **Background tab pause/resume** — auto-pause on `visibilitychange`
42
+ when hidden, auto-resume on return. Prevents degraded playback
43
+ from Chrome's rAF/setTimeout throttling. Configurable via
44
+ `backgroundBehavior: "pause" | "continue"`.
45
+ - **GitHub Pages demo** — deployed at `keishi.github.io/avbridge/`.
46
+ COOP/COEP service worker enables SharedArrayBuffer on static
47
+ hosting.
48
+ - **Consolidated rebrand** — public-facing name is now "avbridge.js"
49
+ across README, docs, demos. npm package name unchanged (`avbridge`).
50
+
51
+ ### Fixed
52
+
53
+ - **A/V sync for long-running hybrid playback** — video PTS and
54
+ `AudioContext.currentTime` drift ~7ms/s (different clock domains).
55
+ Now periodically re-snaps calibration every 10 seconds, keeping
56
+ max drift under 70ms (below human lip-sync threshold). See
57
+ `docs/dev/POSTMORTEMS.md` for the full investigation.
58
+ - **Hybrid pump ordering** — audio decoded before video, with
59
+ sub-batch yields during heavy audio decode (DTS) to prevent rAF
60
+ starvation that caused visible stutter.
61
+ - **Probe regression** for MKV files with unrecognized codecs —
62
+ now falls back to libav probe instead of returning "unknown".
63
+ - **Variant picker** rewritten to use an allowlist (codecs
64
+ webcodecs variant can handle) instead of a denylist. New codecs
65
+ automatically route to the avbridge variant.
66
+ - **Hybrid + fallback preserve HTMLMediaElement contract** —
67
+ dispatch standard `play`/`pause`/`volumechange` events and patch
68
+ `target.volume`/`muted` as getter/setters so `<avbridge-player>`'s
69
+ controls reflect real state.
70
+ - **Seek bar click position** — custom pointer handler replaces
71
+ native range-input click math, eliminating the thumb-vs-cursor
72
+ offset at the track edges.
73
+
74
+ ### Changed
75
+
76
+ - Classification now routes native-video + fallback-audio combos
77
+ to hybrid (WebCodecs video + libav audio) instead of full
78
+ fallback. Previously a Blu-ray MKV with H.264 + DTS went straight
79
+ to WASM software decode (unwatchable at 1080p).
80
+ - Annex B → AVCC conversion is now applied during libav remux to
81
+ produce correct fMP4 output.
82
+ - 269 unit tests across 17 files (up from 119 across 9). Three
83
+ testing tiers documented: unit (vitest + jsdom), browser
84
+ integration (Puppeteer), and strategy-to-element contract.
85
+
86
+ ## [2.2.1]
87
+
88
+ ### Fixed
89
+
90
+ - **Canvas renderer no longer stretches non-stage-aspect video.** The
91
+ fallback + hybrid renderer's canvas sat at `width:100%;height:100%`
92
+ with no `object-fit`, so portrait or otherwise non-matching content
93
+ was stretched to fill the stage. Now uses `object-fit: contain` to
94
+ letterbox the bitmap inside the stage.
95
+ - **Strategy switch to `remux` while playing now resumes playback.**
96
+ `doSetStrategy` calls `session.seek()` before `session.play()`, so
97
+ the remux pipeline used to start with `pendingAutoPlay=false`; the
98
+ subsequent `video.play()` would then hit an element whose `src`
99
+ wasn't yet assigned (the MseSink constructs lazily on first write)
100
+ and silently reject. `RemuxPipeline` gained `setAutoPlay()` so
101
+ `session.play()` can flip `pendingAutoPlay=true` mid-flight; the
102
+ MseSink fires `video.play()` as soon as buffered data lands.
103
+ - **Strategy switch from `hybrid` / `fallback` to another backend now
104
+ preserves play state.** Those strategies hide the `<video>` and
105
+ drive playback from their own Web Audio clock, so the underlying
106
+ element's native `paused` was always `true`. `doSetStrategy` read
107
+ `!target.paused` and captured `wasPlaying=false`, skipping the
108
+ restore on the new session. Both strategies now patch a
109
+ configurable `paused` getter on the target that mirrors
110
+ `audio.isPlaying()`, and clean it up on `destroy()`.
111
+ - **`initialStrategy` no longer retries the same failing strategy.**
112
+ `buildInitialDecision` inherited `natural.fallbackChain` verbatim,
113
+ so for a `RISKY_NATIVE` file with `initialStrategy: "remux"` the
114
+ chain still contained `"remux"` — on failure, `startSession` would
115
+ shift it off and retry `remux` before escalating. The synthetic
116
+ decision now filters `initial` out of the inherited chain.
117
+ - **`UnifiedPlayer.destroy()` removes the `ended` listener it
118
+ attached during `bootstrap()`.** Previously the anonymous handler
119
+ leaked across player lifecycles on long-lived target elements
120
+ (e.g. `<avbridge-video>` swapping source), causing gradual
121
+ accumulation and duplicate `ended` events after source reloads.
122
+
123
+ ### Changed
124
+
125
+ - Bundle audit ceiling for the `element-only` scenario raised to
126
+ 20 KB eager gzip. The budget's purpose is catching
127
+ order-of-magnitude regressions (e.g. libav accidentally eager-
128
+ imported), not policing ±200 bytes; realistic first-play cost is
129
+ dominated by the multi-megabyte lazy wasm load.
130
+
7
131
  ## [2.2.0]
8
132
 
9
133
  ### Added
package/NOTICE.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Third-party notices
2
2
 
3
- avbridge is distributed under the MIT License (see [LICENSE](./LICENSE)).
3
+ avbridge.js is distributed under the MIT License (see [LICENSE](./LICENSE)).
4
4
 
5
5
  This package **bundles binary builds** of third-party libraries. Each
6
6
  bundled library retains its original license, which you must honor if you
7
- redistribute avbridge or works that include it.
7
+ redistribute avbridge.js or works that include it.
8
8
 
9
9
  See [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md) for the full
10
10
  license texts. This file is a short index.
package/README.md CHANGED
@@ -1,16 +1,16 @@
1
- # avbridge
1
+ # avbridge.js
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/avbridge.svg)](https://www.npmjs.com/package/avbridge)
4
- [![bundle size](https://img.shields.io/bundlephobia/minzip/avbridge?label=gzipped)](https://bundlephobia.com/package/avbridge)
5
4
  [![license](https://img.shields.io/npm/l/avbridge.svg)](./LICENSE)
6
5
  [![CI](https://img.shields.io/github/actions/workflow/status/keishi/avbridge/ci.yml?branch=main&label=CI)](https://github.com/keishi/avbridge/actions/workflows/ci.yml)
7
6
 
8
- > **Play and convert arbitrary video files in the browser. Local files or remote URLs.**
7
+ > **VLC-style media playback for the browser. Play and convert arbitrary video files local or remote.**
9
8
 
10
9
  A media compatibility layer for the web. Drop in any file — MP4, MKV, AVI,
11
- WMV, FLV, MPEG-TS, DivX — and avbridge picks the best path: native `<video>`
12
- playback, mediabunny remux to fragmented MP4, libav.js demux + WebCodecs
13
- hardware decode, or full WASM software decode. Same API for all of them.
10
+ WMV, FLV, MPEG-TS, DivX, RMVB — and avbridge picks the best path: native
11
+ `<video>` playback, mediabunny remux to fragmented MP4, libav.js demux +
12
+ WebCodecs hardware decode, or full WASM software decode. Same API for all
13
+ of them.
14
14
 
15
15
  **Streaming-first.** Remote URLs are read via HTTP Range requests across all
16
16
  strategies — even AVI/WMV/FLV — so a 4 GB file plays without buffering 4 GB
@@ -46,6 +46,7 @@ MKV (H.264/AAC) → remux → fragmented MP4 via MSE
46
46
  MPEG-TS (H.264) → remux → fragmented MP4 via MSE
47
47
  AVI (H.264) → hybrid → libav demux + hardware decode
48
48
  AVI (DivX) → fallback → smooth software decode
49
+ RMVB (rv40/cook) → fallback → libav software decode
49
50
  ```
50
51
 
51
52
  ## Quick start
@@ -246,107 +247,132 @@ player.getDiagnostics();
246
247
  // reason: "avi container requires libav demux; codecs are hardware-decodable",
247
248
  // width: 1920, height: 1080, duration: 5400,
248
249
  // probedBy: "libav",
250
+ // transport: "http-range",
251
+ // rangeSupported: true,
252
+ // runtime: { decoderType: "webcodecs-hybrid", videoFramesDecoded: 5432, ... },
249
253
  // strategyHistory: [{ strategy: "hybrid", reason: "...", at: 1712764800000 }]
250
254
  // }
251
255
  ```
252
256
 
257
+ ### Debug logging
258
+
259
+ Enable verbose per-stage logging for hard-to-diagnose issues:
260
+
261
+ ```js
262
+ // In the browser console, or before avbridge loads:
263
+ globalThis.AVBRIDGE_DEBUG = true;
264
+ ```
265
+
266
+ The demo pages also accept `?avbridge_debug` in the URL. When enabled,
267
+ every decision point emits a `[avbridge:<tag>]` log covering probe,
268
+ classify, libav load, bootstrap, strategy execute, and cold-start gate
269
+ timings.
270
+
271
+ The following **unconditional diagnostics** also fire — even without the
272
+ flag — when something smells off:
273
+
274
+ - `[avbridge:bootstrap]` — bootstrap chain took >5 s end-to-end
275
+ - `[avbridge:probe]` — probe took >3 s
276
+ - `[avbridge:libav-load]` — libav variant load took >5 s (usually a
277
+ misconfigured base path or server MIME type)
278
+ - `[avbridge:cold-start]` — fallback cold-start gate timed out or
279
+ released on video-only grace after waiting for audio
280
+ - `[avbridge:decode-rate]` — fallback decoder is running under 60% of
281
+ realtime fps for more than 5 seconds (one-shot per session)
282
+ - `[avbridge:overflow-drop]` — renderer is dropping more than 10% of
283
+ decoded frames because the decoder is bursting faster than the
284
+ canvas can drain (one-shot per session)
285
+
286
+ These are designed so "it works on my machine but stutters on your
287
+ file" surfaces the specific reason in the console instead of requiring
288
+ a live debug session.
289
+
253
290
  ## Install
254
291
 
255
292
  ```bash
256
293
  npm install avbridge
257
294
  ```
258
295
 
259
- This gives you the **core package**: probe, classify, native playback, remux,
260
- transcode, and subtitles. No WASM. The full library is ~17 KB gzipped, but
261
- tree-shaking is aggressive what you actually pay for depends on which
262
- exports you import:
296
+ That's it. **No optional peers to install, no binaries to build, no static
297
+ file path to configure.** Both libav.js variants (the 5 MB webcodecs build
298
+ and the 6.5 MB custom avbridge build with AVI/WMV/DivX/rv40 decoders) ship
299
+ inside the tarball under `node_modules/avbridge/vendor/libav/` and are
300
+ lazy-loaded at runtime only if a file actually needs them.
263
301
 
264
- | Import | Eager (gzip) |
265
- |---|---|
266
- | `srtToVtt` | **0.5 KB** |
267
- | `probe`, `classify` | **3 KB** |
268
- | `transcode` | **3.3 KB** |
269
- | `remux` | **4.1 KB** |
270
- | `createPlayer` | **14 KB** |
271
- | `*` (everything) | **17 KB** |
302
+ Packed tarball is **~4 MB**, unpacked **~15 MB** (mostly the two WASM
303
+ binaries). If you only ever play native MP4, you never download a single
304
+ byte of the libav WASM — the loader is behind a dynamic `import()` that
305
+ never fires.
272
306
 
273
- The libav-loader path is split into a lazy chunk (~5 KB extra) that only
274
- loads when a consumer actually invokes the AVI/ASF/FLV remux path.
307
+ ### Two ways to consume
275
308
 
276
- Run `npm run audit:bundle` to verify these numbers in your fork.
309
+ **Bundler (Vite, webpack, Rollup, esbuild):**
277
310
 
278
- ### Optional: fallback / hybrid strategies
311
+ ```ts
312
+ import { createPlayer, remux, transcode, probe, classify } from "avbridge";
313
+ // or
314
+ import "avbridge/element"; // registers <avbridge-video> custom element
315
+ ```
279
316
 
280
- For files that need software decode or libav.js demux (AVI, WMV, FLV,
281
- legacy codecs):
317
+ The tree-shaking budgets below apply to this path. Your bundler resolves
318
+ `mediabunny` and `libavjs-webcodecs-bridge` through normal dependency
319
+ resolution. libav.js binaries live at
320
+ `node_modules/avbridge/vendor/libav/` — the loader finds them
321
+ automatically via `import.meta.url` in the generated chunk.
282
322
 
283
- ```bash
284
- npm install @libav.js/variant-webcodecs libavjs-webcodecs-bridge
285
- ```
323
+ **Plain `<script type="module">` (no bundler):**
286
324
 
287
- This handles MKV/WebM/MP4 containers via the hybrid/fallback strategies.
325
+ ```html
326
+ <script type="module"
327
+ src="/node_modules/avbridge/dist/element-browser.js"></script>
288
328
 
289
- ### Optional: AVI, WMV3, DivX, and other legacy formats
329
+ <avbridge-video src="/video.mkv" autoplay playsinline></avbridge-video>
330
+ ```
290
331
 
291
- For **AVI, WMV3, MPEG-4 Part 2, DivX**, and other legacy formats, you need
292
- a custom libav.js build see [`vendor/libav/README.md`](./vendor/libav/README.md)
293
- for the build recipe.
332
+ This is a second tsup entry (`dist/element-browser.js`) that inlines
333
+ mediabunny + libavjs-webcodecs-bridge into a single ~1.3 MB file with
334
+ zero bare specifiers at runtime. Perfect for self-hosted tools or static
335
+ sites that don't want a build step. It loads libav.js from the same
336
+ co-located `vendor/libav/` tree.
294
337
 
295
- ### Package boundary summary
338
+ ### Bundle sizes (bundler path)
296
339
 
297
- | What you need | What to install |
340
+ | Import | Eager (gzip) |
298
341
  |---|---|
299
- | Playback of MP4/MKV/WebM/**MPEG-TS** + remux/transcode export | `avbridge` (core, no WASM) |
300
- | Fallback/hybrid decode for modern codecs in legacy containers (AVI/ASF/FLV) | + `@libav.js/variant-webcodecs` + `libavjs-webcodecs-bridge` |
301
- | AVI, WMV3, DivX, MPEG-4 Part 2, VC-1 | + custom libav build (`scripts/build-libav.sh`) |
302
-
303
- ### Serving the libav.js binaries
342
+ | `srtToVtt` | **0.5 KB** |
343
+ | `probe`, `classify` | **2.5 KB** |
344
+ | `transcode` | **3 KB** |
345
+ | `remux` | **3.7 KB** |
346
+ | `createPlayer` | **15 KB** |
347
+ | `*` (everything) | **17.5 KB** |
348
+ | `avbridge/element` | **17 KB** |
304
349
 
305
- The optional libav variants ship as `.wasm` + `.mjs` files that need to be
306
- served by your app at a known URL. avbridge looks for them at
307
- `/libav/<variant>/libav-<variant>.mjs` (where `<variant>` is `webcodecs` or
308
- `avbridge`). You can override the base URL with
309
- `globalThis.AVBRIDGE_LIBAV_BASE = "/my-static-path"` before any avbridge
310
- code runs.
350
+ Run `npm run audit:bundle` to verify in your fork.
311
351
 
312
- #### Vite
352
+ ### Overriding the libav path (advanced)
313
353
 
314
- Copy the variant binaries into your `public/libav/` directory at build
315
- time. The avbridge demo does this via `scripts/copy-libav.mjs`:
354
+ If you want to host the libav binaries somewhere other than
355
+ `node_modules/avbridge/vendor/libav/` for example a CDN, a custom
356
+ libav build, or a patched version — set `AVBRIDGE_LIBAV_BASE` **before**
357
+ any avbridge code runs:
316
358
 
317
- ```bash
318
- # In your project, after npm install:
319
- mkdir -p public/libav/webcodecs
320
- cp node_modules/@libav.js/variant-webcodecs/dist/* public/libav/webcodecs/
359
+ ```html
360
+ <script>globalThis.AVBRIDGE_LIBAV_BASE = "https://cdn.example.com/libav";</script>
361
+ <script type="module" src="..."></script>
321
362
  ```
322
363
 
323
- For the custom `avbridge` variant, after running `./scripts/build-libav.sh`
324
- in the avbridge repo, copy `vendor/libav/*` into `public/libav/avbridge/`.
325
-
326
- #### Webpack
327
-
328
- Use `copy-webpack-plugin` to ship the binaries to your output directory at
329
- the same `libav/<variant>/` path.
330
-
331
- #### Plain `<script>` / no bundler
332
-
333
- Drop the variant directory anywhere on your origin and set
334
- `globalThis.AVBRIDGE_LIBAV_BASE` to the matching URL before importing
335
- avbridge.
336
-
337
- If a libav-backed strategy is selected and the binary isn't reachable,
338
- avbridge throws a clear error mentioning the URL it tried to load. The
339
- core (native + remux for modern containers) doesn't need any of this.
364
+ The loader will then fetch `<base>/<variant>/libav-<variant>.mjs` and its
365
+ sibling `.wasm` files. This is the documented replaceability hook for
366
+ LGPL compliance — see [`NOTICE.md`](./NOTICE.md) and
367
+ [`THIRD_PARTY_LICENSES.md`](./THIRD_PARTY_LICENSES.md).
340
368
 
341
369
  ## Known limitations
342
370
 
343
- - The **fallback strategy** uses WASM software decoding and is CPU-intensive, especially for HD video on mobile devices.
344
- - **Remux of AVI/ASF/FLV** requires libav.js — the core package cannot demux these containers.
371
+ - The **fallback strategy** uses WASM software decoding and is CPU-intensive, especially for HD video on mobile devices. The `[avbridge:decode-rate]` diagnostic fires if the decoder falls below 60% of realtime so you know that's what's happening. Codecs with no WebCodecs support (rv40, mpeg4 @ 720p+, wmv3, vc1 at high resolutions) are the usual suspects.
345
372
  - **Remote URL playback requires HTTP Range requests.** Servers that don't support `Range: bytes=...` will fail fast with a clear error rather than silently downloading the whole file. This applies to all strategies.
346
373
  - **H.264 + MP3 in MP4** is a best-effort combination that may produce playback issues in some browsers. Use `strict: true` to reject it, or re-encode audio to AAC via `transcode()`.
347
- - AVI files with **packed B-frames** (some DivX encodes) may have timing issues until the `mpeg4_unpack_bframes` BSF is wired in.
348
- - libav.js **threading is disabled** due to bugs in v6.8.8 decode runs single-threaded with SIMD acceleration.
349
- - `transcode()` v1 only accepts mediabunny-readable inputs (MP4/MKV/WebM/OGG/MOV/MP3/FLAC/WAV). AVI/ASF/FLV transcoding is planned for v1.1.
374
+ - libav.js **threading is disabled** due to known runtime bugs in the v6.8.8 pthreads build decode runs single-threaded with WASM SIMD acceleration.
375
+ - `transcode()` only accepts mediabunny-readable inputs (MP4/MKV/WebM/OGG/MOV/MP3/FLAC/WAV). AVI/ASF/FLV/RM transcoding means "play it first, record the output" — not yet plumbed.
350
376
  - `transcode()` uses **WebCodecs encoders only** — codec availability depends on the browser. AV1 encoding is not yet universal.
351
377
  - For the **hybrid and fallback strategies**, `<avbridge-video>.buffered` returns an empty `TimeRanges` because the canvas-based renderers don't track buffered ranges yet. Native and remux strategies expose the full `<video>.buffered` set as expected.
352
378
 
@@ -1,9 +1,9 @@
1
1
  # Third-party license texts
2
2
 
3
3
  This document contains the full license text of every third-party
4
- component bundled with avbridge. The short overview lives in
4
+ component bundled with avbridge.js. The short overview lives in
5
5
  [NOTICE.md](./NOTICE.md); this file is what you need if you redistribute
6
- avbridge and have to comply with the underlying licenses.
6
+ avbridge.js and have to comply with the underlying licenses.
7
7
 
8
8
  ---
9
9
 
@@ -0,0 +1,183 @@
1
+ import { loadLibav } from './chunk-IAYKFGFG.js';
2
+ import { prepareLibavInput } from './chunk-DCSOQH2N.js';
3
+
4
+ // src/probe/avi.ts
5
+ async function probeWithLibav(source, sniffed) {
6
+ const libav = await loadLibav("avbridge");
7
+ const filename = source.name ?? `input.${sniffed === "unknown" ? "bin" : sniffed}`;
8
+ const handle = await prepareLibavInput(libav, filename, source);
9
+ let fmt_ctx;
10
+ let streams = [];
11
+ try {
12
+ const result = await libav.ff_init_demuxer_file(filename);
13
+ fmt_ctx = result[0];
14
+ streams = result[1];
15
+ } catch (err) {
16
+ await handle.detach().catch(() => {
17
+ });
18
+ const inner = err instanceof Error ? err.message : typeof err === "object" && err !== null ? JSON.stringify(err) : String(err);
19
+ console.error("[avbridge] ff_init_demuxer_file raw error:", err);
20
+ throw new Error(
21
+ `libav.js could not demux ${filename}. The current libav variant likely lacks the required demuxer (e.g. AVI). See vendor/libav/README.md for build instructions. (${inner || "no message \u2014 see console for raw error"})`
22
+ );
23
+ }
24
+ const videoTracks = [];
25
+ const audioTracks = [];
26
+ for (const stream of streams) {
27
+ const codecName = await safe(() => libav.avcodec_get_name(stream.codec_id)) ?? `unknown(${stream.codec_id})`;
28
+ const codecpar = await safe(() => libav.ff_copyout_codecpar(stream.codecpar));
29
+ if (stream.codec_type === libav.AVMEDIA_TYPE_VIDEO) {
30
+ videoTracks.push({
31
+ id: stream.index,
32
+ codec: ffmpegToAvbridgeVideo(codecName),
33
+ width: codecpar?.width ?? 0,
34
+ height: codecpar?.height ?? 0,
35
+ fps: framerate(stream)
36
+ });
37
+ } else if (stream.codec_type === libav.AVMEDIA_TYPE_AUDIO) {
38
+ audioTracks.push({
39
+ id: stream.index,
40
+ codec: ffmpegToAvbridgeAudio(codecName),
41
+ channels: codecpar?.channels ?? codecpar?.ch_layout_nb_channels ?? 0,
42
+ sampleRate: codecpar?.sample_rate ?? 0
43
+ });
44
+ }
45
+ }
46
+ const duration = await safeDuration(libav, fmt_ctx);
47
+ await libav.avformat_close_input_js(fmt_ctx).catch(() => {
48
+ });
49
+ await handle.detach().catch(() => {
50
+ });
51
+ return {
52
+ source: source.original,
53
+ name: source.name,
54
+ byteLength: source.byteLength,
55
+ container: sniffed === "unknown" ? "unknown" : sniffed,
56
+ videoTracks,
57
+ audioTracks,
58
+ subtitleTracks: [],
59
+ probedBy: "libav",
60
+ duration
61
+ };
62
+ }
63
+ function framerate(stream) {
64
+ if (typeof stream.avg_frame_rate_num === "number" && stream.avg_frame_rate_den) {
65
+ return stream.avg_frame_rate_num / stream.avg_frame_rate_den;
66
+ }
67
+ if (stream.avg_frame_rate && typeof stream.avg_frame_rate === "object") {
68
+ if (stream.avg_frame_rate.den === 0) return void 0;
69
+ return stream.avg_frame_rate.num / stream.avg_frame_rate.den;
70
+ }
71
+ return void 0;
72
+ }
73
+ async function safeDuration(libav, fmt_ctx) {
74
+ try {
75
+ const lo = await libav.AVFormatContext_duration?.(fmt_ctx);
76
+ const hi = await libav.AVFormatContext_durationhi?.(fmt_ctx);
77
+ if (typeof lo !== "number" || typeof hi !== "number") return void 0;
78
+ if (hi === -2147483648 && lo === 0) return void 0;
79
+ const us = typeof libav.i64tof64 === "function" ? libav.i64tof64(lo, hi) : hi * 4294967296 + lo + (lo < 0 ? 4294967296 : 0);
80
+ if (!Number.isFinite(us) || us <= 0) return void 0;
81
+ return us / 1e6;
82
+ } catch {
83
+ return void 0;
84
+ }
85
+ }
86
+ async function safe(fn) {
87
+ try {
88
+ return await fn();
89
+ } catch {
90
+ return void 0;
91
+ }
92
+ }
93
+ function ffmpegToAvbridgeVideo(name) {
94
+ switch (name) {
95
+ case "h264":
96
+ return "h264";
97
+ case "hevc":
98
+ return "h265";
99
+ case "vp8":
100
+ return "vp8";
101
+ case "vp9":
102
+ return "vp9";
103
+ case "av1":
104
+ return "av1";
105
+ case "mpeg4":
106
+ return "mpeg4";
107
+ // MPEG-4 Part 2 / DivX / Xvid
108
+ case "msmpeg4v1":
109
+ case "msmpeg4v2":
110
+ case "msmpeg4v3":
111
+ return "mpeg4";
112
+ case "wmv1":
113
+ case "wmv2":
114
+ case "wmv3":
115
+ return "wmv3";
116
+ case "vc1":
117
+ return "vc1";
118
+ case "mpeg2video":
119
+ return "mpeg2";
120
+ case "mpeg1video":
121
+ return "mpeg1";
122
+ case "theora":
123
+ return "theora";
124
+ case "rv10":
125
+ return "rv10";
126
+ case "rv20":
127
+ return "rv20";
128
+ case "rv30":
129
+ return "rv30";
130
+ case "rv40":
131
+ return "rv40";
132
+ default:
133
+ return name;
134
+ }
135
+ }
136
+ function ffmpegToAvbridgeAudio(name) {
137
+ switch (name) {
138
+ case "aac":
139
+ return "aac";
140
+ case "mp3":
141
+ case "mp3float":
142
+ return "mp3";
143
+ case "opus":
144
+ return "opus";
145
+ case "vorbis":
146
+ return "vorbis";
147
+ case "flac":
148
+ return "flac";
149
+ case "ac3":
150
+ return "ac3";
151
+ case "eac3":
152
+ return "eac3";
153
+ case "wmav1":
154
+ case "wmav2":
155
+ return "wmav2";
156
+ case "wmapro":
157
+ return "wmapro";
158
+ case "alac":
159
+ return "alac";
160
+ case "cook":
161
+ return "cook";
162
+ case "ra_144":
163
+ return "ra_144";
164
+ case "ra_288":
165
+ return "ra_288";
166
+ case "sipr":
167
+ return "sipr";
168
+ case "atrac3":
169
+ return "atrac3";
170
+ case "dca":
171
+ case "dts":
172
+ return "dts";
173
+ case "truehd":
174
+ case "mlp":
175
+ return "truehd";
176
+ default:
177
+ return name;
178
+ }
179
+ }
180
+
181
+ export { probeWithLibav };
182
+ //# sourceMappingURL=avi-2JPBSHGA.js.map
183
+ //# sourceMappingURL=avi-2JPBSHGA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/probe/avi.ts"],"names":[],"mappings":"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","file":"avi-2JPBSHGA.js","sourcesContent":["import type {\n AudioCodec,\n AudioTrackInfo,\n ContainerKind,\n MediaContext,\n VideoCodec,\n VideoTrackInfo,\n} from \"../types.js\";\nimport type { NormalizedSource } from \"../util/source.js\";\nimport { prepareLibavInput, type LibavInputHandle } from \"../util/libav-http-reader.js\";\nimport { loadLibav } from \"../strategies/fallback/libav-loader.js\";\n\n/**\n * Probe AVI/ASF/FLV (and any other format mediabunny doesn't speak) via\n * libav.js. This module is `import()`-ed only when sniffing identifies one of\n * those containers.\n *\n * Critical: codec identification goes through `libav.avcodec_get_name(id)`\n * which returns the FFmpeg codec name as a string (e.g. \"h264\", \"mpeg4\",\n * \"wmv3\"). The numeric AV_CODEC_ID_* enum is *not* exposed on the libav\n * instance (only AVMEDIA_TYPE_*, AV_PIX_FMT_*, AV_SAMPLE_FMT_* and a handful\n * of others are), so comparing codec_ids against constants does not work.\n */\nexport async function probeWithLibav(\n source: NormalizedSource,\n sniffed: ContainerKind,\n): Promise<MediaContext> {\n // AVI/ASF/FLV demuxers are not in any libav.js npm variant — they live in\n // the custom \"avbridge\" build produced by `scripts/build-libav.sh`. The loader\n // emits an actionable error if the build hasn't been run yet. Threading\n // is OFF by default in `loadLibav` (see the comment there for why).\n const libav = (await loadLibav(\"avbridge\")) as unknown as LibavInstance;\n\n const filename = source.name ?? `input.${sniffed === \"unknown\" ? \"bin\" : sniffed}`;\n // For Blob/File sources we use libav's in-memory readahead file. For URL\n // sources we attach an HTTP block reader so libav demuxes via Range\n // requests instead of buffering the whole file.\n const handle: LibavInputHandle = await prepareLibavInput(libav as unknown as Parameters<typeof prepareLibavInput>[0], filename, source);\n\n let fmt_ctx: number | undefined;\n let streams: LibavStream[] = [];\n try {\n const result = await libav.ff_init_demuxer_file(filename);\n fmt_ctx = result[0];\n streams = result[1];\n } catch (err) {\n await handle.detach().catch(() => {});\n // Errors thrown across the libav.js worker/pthread boundary aren't\n // always Error instances — they can be plain objects, numbers (errno\n // codes), or strings. Stringify defensively so the user-facing message\n // never has `(undefined)` in it.\n const inner =\n err instanceof Error\n ? err.message\n : typeof err === \"object\" && err !== null\n ? JSON.stringify(err)\n : String(err);\n // eslint-disable-next-line no-console\n console.error(\"[avbridge] ff_init_demuxer_file raw error:\", err);\n throw new Error(\n `libav.js could not demux ${filename}. The current libav variant likely lacks the required demuxer (e.g. AVI). See vendor/libav/README.md for build instructions. (${inner || \"no message — see console for raw error\"})`,\n );\n }\n\n const videoTracks: VideoTrackInfo[] = [];\n const audioTracks: AudioTrackInfo[] = [];\n\n for (const stream of streams) {\n const codecName = (await safe(() => libav.avcodec_get_name(stream.codec_id))) ?? `unknown(${stream.codec_id})`;\n // codecpar holds width/height/channels/sample_rate/profile/level/extradata\n // for the actual stream. We have to copy it out of WASM memory.\n const codecpar = await safe(() => libav.ff_copyout_codecpar(stream.codecpar));\n\n if (stream.codec_type === libav.AVMEDIA_TYPE_VIDEO) {\n videoTracks.push({\n id: stream.index,\n codec: ffmpegToAvbridgeVideo(codecName),\n width: codecpar?.width ?? 0,\n height: codecpar?.height ?? 0,\n fps: framerate(stream),\n });\n } else if (stream.codec_type === libav.AVMEDIA_TYPE_AUDIO) {\n audioTracks.push({\n id: stream.index,\n codec: ffmpegToAvbridgeAudio(codecName),\n channels: codecpar?.channels ?? codecpar?.ch_layout_nb_channels ?? 0,\n sampleRate: codecpar?.sample_rate ?? 0,\n });\n }\n }\n\n // We need this duration but cannot reliably get it from the streams alone\n // for AVI; libav.js exposes it via the AVFormatContext duration helper.\n const duration = await safeDuration(libav, fmt_ctx!);\n\n // Close the demuxer; the strategy will reopen it later if it ends up being\n // chosen. Probing should not pin native resources.\n await libav.avformat_close_input_js(fmt_ctx!).catch(() => {});\n await handle.detach().catch(() => {});\n\n return {\n source: source.original,\n name: source.name,\n byteLength: source.byteLength,\n container: sniffed === \"unknown\" ? \"unknown\" : sniffed,\n videoTracks,\n audioTracks,\n subtitleTracks: [],\n probedBy: \"libav\",\n duration,\n };\n}\n\nfunction framerate(stream: LibavStream): number | undefined {\n if (typeof stream.avg_frame_rate_num === \"number\" && stream.avg_frame_rate_den) {\n return stream.avg_frame_rate_num / stream.avg_frame_rate_den;\n }\n if (stream.avg_frame_rate && typeof stream.avg_frame_rate === \"object\") {\n if (stream.avg_frame_rate.den === 0) return undefined;\n return stream.avg_frame_rate.num / stream.avg_frame_rate.den;\n }\n return undefined;\n}\n\nasync function safeDuration(libav: LibavInstance, fmt_ctx: number): Promise<number | undefined> {\n try {\n // `AVFormatContext.duration` is an int64 in microseconds (AV_TIME_BASE).\n // libav.js exposes it as a split lo/hi pair the same way it does for\n // packet pts — `AVFormatContext_duration(ctx)` returns the low 32 bits,\n // `AVFormatContext_durationhi(ctx)` returns the high 32 bits. Reading\n // only the low half (the previous bug) gave garbage for any file whose\n // duration > ~35 minutes, and zero for shorter files where the value\n // happened to live in the high half.\n const lo = await libav.AVFormatContext_duration?.(fmt_ctx);\n const hi = await libav.AVFormatContext_durationhi?.(fmt_ctx);\n if (typeof lo !== \"number\" || typeof hi !== \"number\") return undefined;\n\n // AV_NOPTS_VALUE = -2^63 → ptshi = -2147483648, pts = 0. Means \"unknown\".\n if (hi === -2147483648 && lo === 0) return undefined;\n\n // Reconstruct the 64-bit value. Prefer libav's helper when available\n // because it correctly handles signed 32-bit two's complement.\n const us =\n typeof libav.i64tof64 === \"function\"\n ? libav.i64tof64(lo, hi)\n : hi * 0x100000000 + lo + (lo < 0 ? 0x100000000 : 0);\n\n if (!Number.isFinite(us) || us <= 0) return undefined;\n return us / 1_000_000;\n } catch {\n return undefined;\n }\n}\n\nasync function safe<T>(fn: () => Promise<T> | T): Promise<T | undefined> {\n try { return await fn(); } catch { return undefined; }\n}\n\n/** Map FFmpeg codec names to avbridge video codec identifiers. */\nfunction ffmpegToAvbridgeVideo(name: string): VideoCodec {\n switch (name) {\n case \"h264\": return \"h264\";\n case \"hevc\": return \"h265\";\n case \"vp8\": return \"vp8\";\n case \"vp9\": return \"vp9\";\n case \"av1\": return \"av1\";\n case \"mpeg4\": return \"mpeg4\"; // MPEG-4 Part 2 / DivX / Xvid\n case \"msmpeg4v1\":\n case \"msmpeg4v2\":\n case \"msmpeg4v3\": // a.k.a. DIV3\n return \"mpeg4\";\n case \"wmv1\":\n case \"wmv2\":\n case \"wmv3\":\n return \"wmv3\";\n case \"vc1\": return \"vc1\";\n case \"mpeg2video\": return \"mpeg2\";\n case \"mpeg1video\": return \"mpeg1\";\n case \"theora\": return \"theora\";\n case \"rv10\": return \"rv10\";\n case \"rv20\": return \"rv20\";\n case \"rv30\": return \"rv30\";\n case \"rv40\": return \"rv40\";\n default: return name as VideoCodec;\n }\n}\n\nfunction ffmpegToAvbridgeAudio(name: string): AudioCodec {\n switch (name) {\n case \"aac\": return \"aac\";\n case \"mp3\":\n case \"mp3float\":\n return \"mp3\";\n case \"opus\": return \"opus\";\n case \"vorbis\": return \"vorbis\";\n case \"flac\": return \"flac\";\n case \"ac3\": return \"ac3\";\n case \"eac3\": return \"eac3\";\n case \"wmav1\":\n case \"wmav2\": return \"wmav2\";\n case \"wmapro\": return \"wmapro\";\n case \"alac\": return \"alac\";\n case \"cook\": return \"cook\";\n case \"ra_144\": return \"ra_144\";\n case \"ra_288\": return \"ra_288\";\n case \"sipr\": return \"sipr\";\n case \"atrac3\": return \"atrac3\";\n case \"dca\":\n case \"dts\": return \"dts\";\n case \"truehd\":\n case \"mlp\": return \"truehd\";\n default: return name as AudioCodec;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Minimal structural types for the slice of libav.js we touch.\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface LibavStream {\n index: number;\n codec_type: number;\n codec_id: number;\n codecpar: number;\n avg_frame_rate?: { num: number; den: number };\n avg_frame_rate_num?: number;\n avg_frame_rate_den?: number;\n}\n\ninterface LibavCodecpar {\n width?: number;\n height?: number;\n channels?: number;\n ch_layout_nb_channels?: number;\n sample_rate?: number;\n profile?: number;\n level?: number;\n}\n\ninterface LibavInstance {\n mkreadaheadfile(name: string, blob: Blob): Promise<void>;\n unlinkreadaheadfile(name: string): Promise<void>;\n ff_init_demuxer_file(name: string): Promise<[number, LibavStream[]]>;\n ff_copyout_codecpar(codecpar: number): Promise<LibavCodecpar>;\n avcodec_get_name(codec_id: number): Promise<string>;\n avformat_close_input_js(ctx: number): Promise<void>;\n AVFormatContext_duration?(ctx: number): Promise<number>;\n AVFormatContext_durationhi?(ctx: number): Promise<number>;\n i64tof64?(lo: number, hi: number): number;\n\n AVMEDIA_TYPE_VIDEO: number;\n AVMEDIA_TYPE_AUDIO: number;\n}\n"]}