avbridge 1.0.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.
- package/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/README.md +415 -0
- package/dist/avi-M5B4SHRM.cjs +164 -0
- package/dist/avi-M5B4SHRM.cjs.map +1 -0
- package/dist/avi-POCGZ4JX.js +162 -0
- package/dist/avi-POCGZ4JX.js.map +1 -0
- package/dist/chunk-5ISVAODK.js +80 -0
- package/dist/chunk-5ISVAODK.js.map +1 -0
- package/dist/chunk-F7YS2XOA.cjs +2966 -0
- package/dist/chunk-F7YS2XOA.cjs.map +1 -0
- package/dist/chunk-FKM7QBZU.js +2957 -0
- package/dist/chunk-FKM7QBZU.js.map +1 -0
- package/dist/chunk-J5MCMN3S.js +27 -0
- package/dist/chunk-J5MCMN3S.js.map +1 -0
- package/dist/chunk-L4NPOJ36.cjs +180 -0
- package/dist/chunk-L4NPOJ36.cjs.map +1 -0
- package/dist/chunk-NZU7W256.cjs +29 -0
- package/dist/chunk-NZU7W256.cjs.map +1 -0
- package/dist/chunk-PQTZS7OA.js +147 -0
- package/dist/chunk-PQTZS7OA.js.map +1 -0
- package/dist/chunk-WD2ZNQA7.js +177 -0
- package/dist/chunk-WD2ZNQA7.js.map +1 -0
- package/dist/chunk-Y5FYF5KG.cjs +153 -0
- package/dist/chunk-Y5FYF5KG.cjs.map +1 -0
- package/dist/chunk-Z2FJ5TJC.cjs +82 -0
- package/dist/chunk-Z2FJ5TJC.cjs.map +1 -0
- package/dist/element.cjs +433 -0
- package/dist/element.cjs.map +1 -0
- package/dist/element.d.cts +158 -0
- package/dist/element.d.ts +158 -0
- package/dist/element.js +431 -0
- package/dist/element.js.map +1 -0
- package/dist/index.cjs +576 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +554 -0
- package/dist/index.js.map +1 -0
- package/dist/libav-http-reader-FPYDBMYK.cjs +16 -0
- package/dist/libav-http-reader-FPYDBMYK.cjs.map +1 -0
- package/dist/libav-http-reader-NQJVY273.js +3 -0
- package/dist/libav-http-reader-NQJVY273.js.map +1 -0
- package/dist/libav-import-2JURFHEW.js +8 -0
- package/dist/libav-import-2JURFHEW.js.map +1 -0
- package/dist/libav-import-GST2AMPL.cjs +30 -0
- package/dist/libav-import-GST2AMPL.cjs.map +1 -0
- package/dist/libav-loader-KA2MAWLM.js +3 -0
- package/dist/libav-loader-KA2MAWLM.js.map +1 -0
- package/dist/libav-loader-ZHOERPHW.cjs +12 -0
- package/dist/libav-loader-ZHOERPHW.cjs.map +1 -0
- package/dist/player-BBwbCkdL.d.cts +365 -0
- package/dist/player-BBwbCkdL.d.ts +365 -0
- package/dist/source-SC6ZEQYR.cjs +28 -0
- package/dist/source-SC6ZEQYR.cjs.map +1 -0
- package/dist/source-ZFS4H7J3.js +3 -0
- package/dist/source-ZFS4H7J3.js.map +1 -0
- package/dist/variant-routing-GOHB2RZN.cjs +12 -0
- package/dist/variant-routing-GOHB2RZN.cjs.map +1 -0
- package/dist/variant-routing-JOBWXYKD.js +3 -0
- package/dist/variant-routing-JOBWXYKD.js.map +1 -0
- package/package.json +95 -0
- package/src/classify/index.ts +1 -0
- package/src/classify/rules.ts +214 -0
- package/src/convert/index.ts +2 -0
- package/src/convert/remux.ts +522 -0
- package/src/convert/transcode.ts +329 -0
- package/src/diagnostics.ts +99 -0
- package/src/element/avbridge-player.ts +576 -0
- package/src/element.ts +19 -0
- package/src/events.ts +71 -0
- package/src/index.ts +42 -0
- package/src/libav-stubs.d.ts +24 -0
- package/src/player.ts +455 -0
- package/src/plugins/builtin.ts +37 -0
- package/src/plugins/registry.ts +32 -0
- package/src/probe/avi.ts +242 -0
- package/src/probe/index.ts +59 -0
- package/src/probe/mediabunny.ts +194 -0
- package/src/strategies/fallback/audio-output.ts +293 -0
- package/src/strategies/fallback/clock.ts +7 -0
- package/src/strategies/fallback/decoder.ts +660 -0
- package/src/strategies/fallback/index.ts +170 -0
- package/src/strategies/fallback/libav-import.ts +27 -0
- package/src/strategies/fallback/libav-loader.ts +190 -0
- package/src/strategies/fallback/variant-routing.ts +43 -0
- package/src/strategies/fallback/video-renderer.ts +216 -0
- package/src/strategies/hybrid/decoder.ts +641 -0
- package/src/strategies/hybrid/index.ts +139 -0
- package/src/strategies/native.ts +107 -0
- package/src/strategies/remux/annexb.ts +112 -0
- package/src/strategies/remux/index.ts +79 -0
- package/src/strategies/remux/mse.ts +234 -0
- package/src/strategies/remux/pipeline.ts +254 -0
- package/src/subtitles/index.ts +91 -0
- package/src/subtitles/render.ts +62 -0
- package/src/subtitles/srt.ts +62 -0
- package/src/subtitles/vtt.ts +5 -0
- package/src/types-shim.d.ts +3 -0
- package/src/types.ts +360 -0
- package/src/util/codec-strings.ts +86 -0
- package/src/util/libav-http-reader.ts +315 -0
- package/src/util/source.ts +274 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **avbridge** are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`createPlayer()`** — universal browser media player with automatic strategy
|
|
12
|
+
selection (native → remux → hybrid → fallback), runtime fallback escalation,
|
|
13
|
+
manual `setStrategy()`, typed events, diagnostics, and subtitle support.
|
|
14
|
+
- **`probe()` / `classify()`** — standalone analysis functions. Probe sniffs
|
|
15
|
+
the container via magic bytes, then routes to mediabunny (modern containers)
|
|
16
|
+
or libav.js (AVI/ASF/FLV). Classify decides the best playback strategy with
|
|
17
|
+
a fallback chain.
|
|
18
|
+
- **`remux()`** — standalone repackage from any avbridge-readable container
|
|
19
|
+
into a finalized downloadable MP4, WebM, or MKV. Built on mediabunny's
|
|
20
|
+
`Conversion` for modern containers and libav.js demux for AVI/ASF/FLV.
|
|
21
|
+
Lossless. Supports `signal`, `onProgress`, and `strict` mode.
|
|
22
|
+
- **`transcode()`** — standalone re-encode via WebCodecs encoders. Configurable
|
|
23
|
+
output container (mp4 / webm / mkv), video codec (h264 / h265 / vp9 / av1),
|
|
24
|
+
audio codec (aac / opus / flac), quality preset, explicit bitrate override,
|
|
25
|
+
resize, frame rate, drop-tracks, and `hardwareAcceleration` hint.
|
|
26
|
+
- **Native strategy** — direct `<video src>` playback for files browsers play
|
|
27
|
+
out of the box.
|
|
28
|
+
- **HTTP Range streaming for URL sources across all strategies.** Local files
|
|
29
|
+
(`File` / `Blob`) and remote URLs use the same API. URL inputs are read via
|
|
30
|
+
HTTP Range requests by every strategy:
|
|
31
|
+
- **Native**: passes the URL straight to `<video src>`; the browser drives
|
|
32
|
+
its own progressive download.
|
|
33
|
+
- **Remux**: uses mediabunny's `UrlSource` (Range requests + prefetch + cache).
|
|
34
|
+
- **Hybrid / fallback** (libav.js): uses a new HTTP block reader that wires
|
|
35
|
+
`libav.mkblockreaderdev` + `onblockread` to issue Range requests on demand.
|
|
36
|
+
Servers without Range support fail fast with a clear error rather than
|
|
37
|
+
silently downloading the whole file. The initial sniff is a single
|
|
38
|
+
`Range: bytes=0-32767` request — no full GET, ever.
|
|
39
|
+
- **Remux strategy** — mediabunny demux → fragmented MP4 → MSE for files whose
|
|
40
|
+
codecs are browser-supported but whose container isn't. Backpressure on the
|
|
41
|
+
SourceBuffer queue, deferred-seek across discontinuous ranges, automatic
|
|
42
|
+
re-creation of the muxer on seek to satisfy mediabunny's monotonic-timestamp
|
|
43
|
+
requirement.
|
|
44
|
+
- **MPEG-TS support** in the remux strategy — mediabunny demuxes TS natively,
|
|
45
|
+
Annex B H.264 packets are passed through (mediabunny extracts the AVC
|
|
46
|
+
decoder config from in-band SPS/PPS), and the MseSink snaps `video.currentTime`
|
|
47
|
+
to the start of the first buffered range to handle TS sources whose PTS
|
|
48
|
+
doesn't start at 0.
|
|
49
|
+
- **Hybrid strategy** — libav.js demux + WebCodecs `VideoDecoder` (hardware) +
|
|
50
|
+
libav.js audio decode for AVI/ASF/FLV files with browser-supported codecs.
|
|
51
|
+
Falls back to wall-clock timing when no audio decoder is available.
|
|
52
|
+
- **Fallback strategy** — full WASM software decode via libav.js, canvas
|
|
53
|
+
rendering, Web Audio output, audio-driven master clock with wall-clock
|
|
54
|
+
fallback when audio decode fails.
|
|
55
|
+
- **Subtitles** — SRT → VTT conversion, sidecar discovery, native `<track>`
|
|
56
|
+
for video strategies, overlay renderer for the fallback strategy.
|
|
57
|
+
- **Plugin system** — strategy registry with `canHandle()` / `execute()`
|
|
58
|
+
interface for injecting custom playback strategies.
|
|
59
|
+
- **Custom libav.js variant** — build script (`scripts/build-libav.sh`) for
|
|
60
|
+
AVI / WMV3 / MPEG-4 Part 2 / DivX / VC-1 and 15+ legacy codecs.
|
|
61
|
+
- **`<avbridge-player>.buffered`** — `TimeRanges` getter and `progress` event
|
|
62
|
+
forwarded from the underlying `<video>` element. Native and remux strategies
|
|
63
|
+
expose real buffered ranges; hybrid and fallback (canvas-rendered) currently
|
|
64
|
+
return an empty `TimeRanges` (synthesizing from the decoder is on the v1.1
|
|
65
|
+
list).
|
|
66
|
+
- **Streaming diagnostics** — `DiagnosticsSnapshot` now includes `sourceType`
|
|
67
|
+
(`"blob" | "url"`), `transport` (`"memory" | "http-range"`), and
|
|
68
|
+
`rangeSupported`, so consumers can show what's actually happening.
|
|
69
|
+
- **Demos** — Player demo (`demo/index.html`) and HandBrake-like Converter
|
|
70
|
+
demo (`demo/convert.html`).
|
|
71
|
+
|
|
72
|
+
### Public API
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
createPlayer(options): Promise<UnifiedPlayer>
|
|
76
|
+
probe(source): Promise<MediaContext>
|
|
77
|
+
classify(context): Classification
|
|
78
|
+
remux(source, options?): Promise<ConvertResult>
|
|
79
|
+
transcode(source, options?): Promise<ConvertResult>
|
|
80
|
+
srtToVtt(srt): string
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Public types: `MediaInput`, `CreatePlayerOptions`, `MediaContext`,
|
|
84
|
+
`Classification`, `StrategyName`, `StrategyClass`, `PlaybackSession`, `Plugin`,
|
|
85
|
+
`DiagnosticsSnapshot`, `PlayerEventMap`, `PlayerEventName`, `VideoTrackInfo`,
|
|
86
|
+
`AudioTrackInfo`, `SubtitleTrackInfo`, `ContainerKind`, `VideoCodec`,
|
|
87
|
+
`AudioCodec`, `OutputFormat`, `ConvertOptions`, `ConvertResult`, `ProgressInfo`,
|
|
88
|
+
`TranscodeOptions`, `TranscodeQuality`, `OutputVideoCodec`, `OutputAudioCodec`,
|
|
89
|
+
`HardwareAccelerationHint`.
|
|
90
|
+
|
|
91
|
+
### Package boundary
|
|
92
|
+
|
|
93
|
+
- `avbridge` core (probe + classify + native + remux + transcode) — ~110 KB
|
|
94
|
+
ESM, no WASM.
|
|
95
|
+
- Optional fallback / hybrid: `@libav.js/variant-webcodecs` +
|
|
96
|
+
`libavjs-webcodecs-bridge` (peer-installed by the consumer).
|
|
97
|
+
- Custom libav.js build for AVI / WMV3 / DivX: documented in
|
|
98
|
+
`vendor/libav/README.md`.
|
|
99
|
+
|
|
100
|
+
### Reliability
|
|
101
|
+
|
|
102
|
+
- **`transcode()` automatically retries on encoder init failures** (up to 2
|
|
103
|
+
retries with backoff). This works around a known headless Chromium bug
|
|
104
|
+
where the H.264 WebCodecs encoder fails on its first call per page and
|
|
105
|
+
recovers on retry. When retries occur, the cause is recorded in
|
|
106
|
+
`ConvertResult.notes` so consumers can detect and report the issue.
|
|
107
|
+
- Real browsers (Chrome/Edge/Safari) typically don't hit this bug; the
|
|
108
|
+
retry path is silent in those environments.
|
|
109
|
+
|
|
110
|
+
### Known limitations
|
|
111
|
+
|
|
112
|
+
- Fallback / hybrid require optional libav.js installs.
|
|
113
|
+
- `transcode()` v1 only accepts inputs in mediabunny-readable containers
|
|
114
|
+
(MP4 / MKV / WebM / OGG / MOV / MP3 / FLAC / WAV). AVI/ASF/FLV transcoding
|
|
115
|
+
is planned for v1.1.
|
|
116
|
+
- `transcode()` uses WebCodecs encoders only; codec availability depends on
|
|
117
|
+
the browser. AV1 encoding is not yet universal.
|
|
118
|
+
- libav.js threading is disabled due to bugs in v6.8.8; decode runs
|
|
119
|
+
single-threaded with WASM SIMD acceleration.
|
|
120
|
+
- Multi-audio track selection in the remux strategy is not yet implemented.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Keishi Hattori
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# avbridge
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/avbridge)
|
|
4
|
+
[](https://bundlephobia.com/package/avbridge)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://github.com/keishi/avbridge/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
> **Play and convert arbitrary video files in the browser. Local files or remote URLs.**
|
|
9
|
+
|
|
10
|
+
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.
|
|
14
|
+
|
|
15
|
+
**Streaming-first.** Remote URLs are read via HTTP Range requests across all
|
|
16
|
+
strategies — even AVI/WMV/FLV — so a 4 GB file plays without buffering 4 GB
|
|
17
|
+
into RAM. Local files (`File` / `Blob`) work the same way through the same API.
|
|
18
|
+
|
|
19
|
+
Designed for personal media libraries, local file managers, and
|
|
20
|
+
"open anything" web apps — not streaming platforms.
|
|
21
|
+
|
|
22
|
+
## When should I use avbridge?
|
|
23
|
+
|
|
24
|
+
- You need to **play arbitrary user-provided video files** in the browser
|
|
25
|
+
- You want to **convert media to a browser-friendly format** without a server
|
|
26
|
+
- You **don't control the input format** — users may drop AVI, MKV, WMV, anything
|
|
27
|
+
- You want **one API** that handles format detection, strategy selection, and fallback automatically
|
|
28
|
+
|
|
29
|
+
## How it works
|
|
30
|
+
|
|
31
|
+
Browsers only support a narrow set of containers and codecs. avbridge bridges
|
|
32
|
+
that gap with a multi-strategy pipeline:
|
|
33
|
+
|
|
34
|
+
1. **Native** — hand the file to `<video>` (zero overhead)
|
|
35
|
+
2. **Remux** — repackage to fragmented MP4 via MSE (preserves hardware decode)
|
|
36
|
+
3. **Hybrid** — libav.js demux + WebCodecs hardware decode (for legacy containers with modern codecs)
|
|
37
|
+
4. **Fallback** — full WASM software decode via libav.js (universal, CPU-intensive)
|
|
38
|
+
|
|
39
|
+
avbridge **always prefers native**, **prefers remux over decode**, and uses WASM
|
|
40
|
+
decode only when there is no other option. If a strategy fails or stalls, it
|
|
41
|
+
automatically escalates to the next one.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
MP4 (H.264/AAC) → native → direct <video> playback
|
|
45
|
+
MKV (H.264/AAC) → remux → fragmented MP4 via MSE
|
|
46
|
+
MPEG-TS (H.264) → remux → fragmented MP4 via MSE
|
|
47
|
+
AVI (H.264) → hybrid → libav demux + hardware decode
|
|
48
|
+
AVI (DivX) → fallback → smooth software decode
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick start
|
|
52
|
+
|
|
53
|
+
### Playback
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { createPlayer } from "avbridge";
|
|
57
|
+
|
|
58
|
+
const video = document.querySelector("video")!;
|
|
59
|
+
const player = await createPlayer({
|
|
60
|
+
source: file, // File / Blob / URL / ArrayBuffer
|
|
61
|
+
target: video,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
player.on("strategy", ({ strategy, reason }) => {
|
|
65
|
+
console.log(`Using ${strategy}: ${reason}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await player.play();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Remux / export
|
|
72
|
+
|
|
73
|
+
Convert a file to a modern format without re-encoding:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { remux } from "avbridge";
|
|
77
|
+
|
|
78
|
+
const result = await remux(file, {
|
|
79
|
+
outputFormat: "mp4", // "mp4" | "webm" | "mkv"
|
|
80
|
+
onProgress: ({ percent }) => console.log(`${percent.toFixed(0)}%`),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// result.blob is a downloadable MP4
|
|
84
|
+
const url = URL.createObjectURL(result.blob);
|
|
85
|
+
const a = document.createElement("a");
|
|
86
|
+
a.href = url;
|
|
87
|
+
a.download = result.filename ?? "output.mp4";
|
|
88
|
+
a.click();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Transcode / re-encode
|
|
92
|
+
|
|
93
|
+
When the source codecs are legacy (or you want a different modern codec like AV1):
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { transcode } from "avbridge";
|
|
97
|
+
|
|
98
|
+
const result = await transcode(file, {
|
|
99
|
+
outputFormat: "mp4",
|
|
100
|
+
videoCodec: "av1", // h264 | h265 | vp9 | av1
|
|
101
|
+
audioCodec: "opus", // aac | opus | flac
|
|
102
|
+
quality: "high", // low | medium | high | very-high
|
|
103
|
+
// Or override quality with explicit bitrate (in bps):
|
|
104
|
+
// videoBitrate: 4_000_000,
|
|
105
|
+
// audioBitrate: 192_000,
|
|
106
|
+
width: 1280, // optional resize
|
|
107
|
+
height: 720,
|
|
108
|
+
hardwareAcceleration: "prefer-software", // for archival quality
|
|
109
|
+
onProgress: ({ percent }) => console.log(`${percent.toFixed(0)}%`),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const url = URL.createObjectURL(result.blob);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Analysis (standalone)
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { probe, classify } from "avbridge";
|
|
119
|
+
|
|
120
|
+
const context = await probe(file);
|
|
121
|
+
console.log(context.container, context.videoTracks, context.audioTracks);
|
|
122
|
+
|
|
123
|
+
const decision = classify(context);
|
|
124
|
+
console.log(decision.strategy, decision.reason);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Playback API
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
createPlayer(options: CreatePlayerOptions): Promise<UnifiedPlayer>
|
|
131
|
+
|
|
132
|
+
interface UnifiedPlayer {
|
|
133
|
+
play(): Promise<void>;
|
|
134
|
+
pause(): void;
|
|
135
|
+
seek(time: number): Promise<void>;
|
|
136
|
+
setStrategy(strategy): Promise<void>;
|
|
137
|
+
setAudioTrack(id: number): Promise<void>;
|
|
138
|
+
setSubtitleTrack(id: number | null): Promise<void>;
|
|
139
|
+
getDuration(): number;
|
|
140
|
+
getCurrentTime(): number;
|
|
141
|
+
on(event, listener): () => void;
|
|
142
|
+
getDiagnostics(): DiagnosticsSnapshot;
|
|
143
|
+
destroy(): Promise<void>;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Conversion API
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
remux(source, options?): Promise<ConvertResult>
|
|
151
|
+
transcode(source, options?): Promise<ConvertResult>
|
|
152
|
+
|
|
153
|
+
interface ConvertOptions {
|
|
154
|
+
outputFormat?: "mp4" | "webm" | "mkv"; // default: "mp4"
|
|
155
|
+
signal?: AbortSignal;
|
|
156
|
+
onProgress?: (info: { percent: number; bytesWritten: number }) => void;
|
|
157
|
+
strict?: boolean; // reject uncertain combos like H.264 + MP3
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface TranscodeOptions extends ConvertOptions {
|
|
161
|
+
videoCodec?: "h264" | "h265" | "vp9" | "av1";
|
|
162
|
+
audioCodec?: "aac" | "opus" | "flac";
|
|
163
|
+
quality?: "low" | "medium" | "high" | "very-high"; // default: "medium"
|
|
164
|
+
videoBitrate?: number; // bits per second; overrides quality
|
|
165
|
+
audioBitrate?: number; // bits per second; overrides quality
|
|
166
|
+
width?: number; // resize; height auto-deduced if not set
|
|
167
|
+
height?: number;
|
|
168
|
+
frameRate?: number; // override frame rate
|
|
169
|
+
dropVideo?: boolean; // audio-only output
|
|
170
|
+
dropAudio?: boolean; // silent output
|
|
171
|
+
hardwareAcceleration?: "no-preference" | "prefer-hardware" | "prefer-software";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface ConvertResult {
|
|
175
|
+
blob: Blob; // downloadable file
|
|
176
|
+
mimeType: string; // "video/mp4", "video/webm", "video/x-matroska"
|
|
177
|
+
container: string;
|
|
178
|
+
videoCodec?: string;
|
|
179
|
+
audioCodec?: string;
|
|
180
|
+
duration?: number;
|
|
181
|
+
filename?: string; // suggested download name
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### What `remux()` guarantees
|
|
186
|
+
|
|
187
|
+
- Outputs a **finalized downloadable file** — not fragmented-for-streaming
|
|
188
|
+
- Does **not** decode or re-encode — lossless repackaging only
|
|
189
|
+
- Rejects unsupported codecs with a clear error pointing to `transcode()`
|
|
190
|
+
- In `strict` mode, rejects uncertain combinations (e.g. H.264 + MP3)
|
|
191
|
+
|
|
192
|
+
### What `transcode()` does
|
|
193
|
+
|
|
194
|
+
- Decodes the source and re-encodes via **WebCodecs encoders** (hardware-accelerated when available)
|
|
195
|
+
- Mux pipeline is provided by mediabunny — it handles encoder selection, sample sync, and finalization
|
|
196
|
+
- Output format is fully configurable: container × video codec × audio codec × quality
|
|
197
|
+
- **Automatic retry on encoder init failures** — works around a headless-Chromium-specific
|
|
198
|
+
WebCodecs H.264 first-call init bug. When a retry happens, it's recorded in `result.notes`.
|
|
199
|
+
- Use the `hardwareAcceleration` hint to trade speed vs quality:
|
|
200
|
+
- `"prefer-hardware"` — fastest, may produce slightly lower quality at low bitrates
|
|
201
|
+
- `"prefer-software"` — slower, higher quality (recommended for archival)
|
|
202
|
+
- `"no-preference"` — let the browser pick (default)
|
|
203
|
+
|
|
204
|
+
### Transcode codec compatibility
|
|
205
|
+
|
|
206
|
+
Which video/audio codec combinations are valid for each output container:
|
|
207
|
+
|
|
208
|
+
| Container | Video codecs | Audio codecs |
|
|
209
|
+
|-----------|-------------------------|--------------------|
|
|
210
|
+
| **MP4** | H.264, H.265/HEVC, AV1 | AAC, FLAC |
|
|
211
|
+
| **WebM** | VP9, AV1 | Opus |
|
|
212
|
+
| **MKV** | H.264, H.265, VP9, AV1 | AAC, Opus, FLAC |
|
|
213
|
+
|
|
214
|
+
Picking an incompatible combo (e.g. WebM + H.264) throws an error before any encoding starts.
|
|
215
|
+
|
|
216
|
+
> **Browser support note:** transcode availability depends on what the browser's WebCodecs implementation supports. Chrome/Edge have the broadest encoder set; Safari is narrower; Firefox is the most limited. AV1 encoding in particular is not yet universally supported.
|
|
217
|
+
|
|
218
|
+
## Conversion support
|
|
219
|
+
|
|
220
|
+
| Input | Best path | Notes |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| MP4 (H.264/AAC) | **Native playback** | No conversion needed |
|
|
223
|
+
| MKV (H.264/AAC) | **Safe remux** | Repackage to MP4/WebM/MKV losslessly |
|
|
224
|
+
| MKV (H.265/Opus) | **Safe remux** | Any modern codec combo |
|
|
225
|
+
| MPEG-TS (H.264/AAC) | **Safe remux** | TS demuxed by mediabunny; repackaged to fragmented MP4 |
|
|
226
|
+
| MP4 (H.264/AAC) → MP4 AV1 | **Transcode** | Re-encode via WebCodecs `VideoEncoder` |
|
|
227
|
+
| MP4 (H.264) → WebM (VP9) | **Transcode** | Container + video codec change requires re-encode |
|
|
228
|
+
| AVI (H.264/MP3) | **Best-effort remux** | Requires libav.js for demux; `strict` mode rejects |
|
|
229
|
+
| AVI (DivX/Xvid) | **Requires transcode** | Codec has no browser decoder (input not yet supported by `transcode()` in v1) |
|
|
230
|
+
| WMV (WMV3) | **Requires transcode** | Codec has no browser decoder (input not yet supported by `transcode()` in v1) |
|
|
231
|
+
|
|
232
|
+
> **Note:** `transcode()` v1 only accepts inputs in mediabunny-readable containers (MP4, MKV, WebM, OGG, MOV, MP3, FLAC, WAV). Transcoding from AVI/ASF/FLV is planned for v1.1.
|
|
233
|
+
|
|
234
|
+
## Diagnostics
|
|
235
|
+
|
|
236
|
+
Every decision avbridge makes is inspectable:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
player.getDiagnostics();
|
|
240
|
+
// {
|
|
241
|
+
// container: "avi",
|
|
242
|
+
// videoCodec: "h264",
|
|
243
|
+
// audioCodec: "mp3",
|
|
244
|
+
// strategy: "hybrid",
|
|
245
|
+
// strategyClass: "HYBRID_CANDIDATE",
|
|
246
|
+
// reason: "avi container requires libav demux; codecs are hardware-decodable",
|
|
247
|
+
// width: 1920, height: 1080, duration: 5400,
|
|
248
|
+
// probedBy: "libav",
|
|
249
|
+
// strategyHistory: [{ strategy: "hybrid", reason: "...", at: 1712764800000 }]
|
|
250
|
+
// }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Install
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm install avbridge
|
|
257
|
+
```
|
|
258
|
+
|
|
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:
|
|
263
|
+
|
|
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** |
|
|
272
|
+
|
|
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.
|
|
275
|
+
|
|
276
|
+
Run `npm run audit:bundle` to verify these numbers in your fork.
|
|
277
|
+
|
|
278
|
+
### Optional: fallback / hybrid strategies
|
|
279
|
+
|
|
280
|
+
For files that need software decode or libav.js demux (AVI, WMV, FLV,
|
|
281
|
+
legacy codecs):
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
npm install @libav.js/variant-webcodecs libavjs-webcodecs-bridge
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
This handles MKV/WebM/MP4 containers via the hybrid/fallback strategies.
|
|
288
|
+
|
|
289
|
+
### Optional: AVI, WMV3, DivX, and other legacy formats
|
|
290
|
+
|
|
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.
|
|
294
|
+
|
|
295
|
+
### Package boundary summary
|
|
296
|
+
|
|
297
|
+
| What you need | What to install |
|
|
298
|
+
|---|---|
|
|
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
|
|
304
|
+
|
|
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.
|
|
311
|
+
|
|
312
|
+
#### Vite
|
|
313
|
+
|
|
314
|
+
Copy the variant binaries into your `public/libav/` directory at build
|
|
315
|
+
time. The avbridge demo does this via `scripts/copy-libav.mjs`:
|
|
316
|
+
|
|
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/
|
|
321
|
+
```
|
|
322
|
+
|
|
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.
|
|
340
|
+
|
|
341
|
+
## Known limitations
|
|
342
|
+
|
|
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.
|
|
345
|
+
- **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
|
+
- **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.
|
|
350
|
+
- `transcode()` uses **WebCodecs encoders only** — codec availability depends on the browser. AV1 encoding is not yet universal.
|
|
351
|
+
- For the **hybrid and fallback strategies**, `<avbridge-player>.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
|
+
|
|
353
|
+
## Demos
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
npm install
|
|
357
|
+
npm run demo
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Two pages share the dev server:
|
|
361
|
+
|
|
362
|
+
- **Player** (`/`) — file picker, custom controls, strategy badge, manual
|
|
363
|
+
backend switcher, live diagnostics. Drop a media file and watch the
|
|
364
|
+
strategy chain pick the best path.
|
|
365
|
+
- **Converter** (`/convert.html`) — HandBrake-like UI with container/codec/
|
|
366
|
+
quality/bitrate/resize options. Picks remux when codecs already match
|
|
367
|
+
the target, transcode when they don't. Progress bar, cancel, download.
|
|
368
|
+
|
|
369
|
+
## Build & test
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
npm run build # tsup → dist/ (ESM + CJS + d.ts, code-split lazy chunks)
|
|
373
|
+
npm run typecheck # tsc --noEmit
|
|
374
|
+
npm test # vitest unit tests
|
|
375
|
+
npm run audit:bundle # verify tree-shaking — bundle each public export and check size
|
|
376
|
+
npm run fixtures # regenerate the test fixture corpus from BBB source via ffmpeg
|
|
377
|
+
|
|
378
|
+
# Browser smoke tests (require `npm run demo` running in another terminal)
|
|
379
|
+
npm run test:playback -- tests/fixtures/ # walk the corpus through the player
|
|
380
|
+
npm run test:convert # exercise the converter via puppeteer
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Architecture
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
probe(source) → MediaContext (container, codecs, tracks, resolution, ...)
|
|
387
|
+
classify(MediaContext) → Classification (strategy, reason, fallbackChain)
|
|
388
|
+
strategy.start() → PlaybackSession (play/pause/seek/destroy)
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
If the chosen strategy fails or stalls, the player walks the `fallbackChain`
|
|
392
|
+
automatically (unless `autoEscalate: false` is set). Users can also call
|
|
393
|
+
`player.setStrategy()` at any time to switch manually.
|
|
394
|
+
|
|
395
|
+
## Third-party licenses
|
|
396
|
+
|
|
397
|
+
avbridge itself is [MIT licensed](./LICENSE). It depends on:
|
|
398
|
+
|
|
399
|
+
| Library | License | Role |
|
|
400
|
+
|---|---|---|
|
|
401
|
+
| [mediabunny](https://mediabunny.dev) | MPL-2.0 | Demux/mux for modern containers (remux strategy + conversion) |
|
|
402
|
+
| [libav.js](https://github.com/Yahweasel/libav.js) | LGPL-2.1 | Demux + decode for legacy codecs (fallback/hybrid strategies) |
|
|
403
|
+
| [libavjs-webcodecs-bridge](https://github.com/Yahweasel/libavjs-webcodecs-bridge) | ISC | AVFrame <-> VideoFrame/AudioData conversion |
|
|
404
|
+
|
|
405
|
+
**LGPL-2.1 compliance**: The libav.js WASM binary in `vendor/libav/` is built
|
|
406
|
+
from source via [`scripts/build-libav.sh`](./scripts/build-libav.sh). The build
|
|
407
|
+
script, source repository URL, and version tag are provided so users can rebuild
|
|
408
|
+
or modify the library. See [`vendor/libav/README.md`](./vendor/libav/README.md).
|
|
409
|
+
|
|
410
|
+
**MPL-2.0 compliance**: mediabunny is used as an unmodified npm dependency. Its
|
|
411
|
+
source is available at the npm registry.
|
|
412
|
+
|
|
413
|
+
## License
|
|
414
|
+
|
|
415
|
+
[MIT](./LICENSE)
|