@napi-rs/webcodecs 0.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/LICENSE +21 -0
- package/README.md +398 -0
- package/index.d.ts +1299 -0
- package/index.js +772 -0
- package/package.json +150 -0
- package/polyfill.d.ts +33 -0
- package/polyfill.js +20 -0
- package/standard.d.ts +413 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 N-API for Rust
|
|
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,398 @@
|
|
|
1
|
+
# @napi-rs/webcodecs
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Brooooooklyn/webcodecs-node/actions/workflows/CI.yml)
|
|
4
|
+
|
|
5
|
+
WebCodecs API implementation for Node.js using FFmpeg, built with [NAPI-RS](https://napi.rs).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **W3C WebCodecs API compliant** - Full implementation of the WebCodecs specification with native `DOMException` errors
|
|
10
|
+
- **Video encoding/decoding** - H.264, H.265, VP8, VP9, AV1
|
|
11
|
+
- **Audio encoding/decoding** - AAC, Opus, MP3, FLAC, Vorbis, PCM variants
|
|
12
|
+
- **Image decoding** - JPEG, PNG, WebP, GIF, BMP, AVIF
|
|
13
|
+
- **Hardware acceleration** - Zero-copy GPU encoding with VideoToolbox (macOS), NVENC (NVIDIA), VAAPI (Linux), QSV (Intel)
|
|
14
|
+
- **Cross-platform** - macOS, Windows, Linux (glibc/musl, x64/arm64/armv7)
|
|
15
|
+
- **Structured logging** - FFmpeg logs redirected to Rust `tracing` crate for easy integration
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @napi-rs/webcodecs
|
|
21
|
+
# or
|
|
22
|
+
pnpm add @napi-rs/webcodecs
|
|
23
|
+
# or
|
|
24
|
+
yarn add @napi-rs/webcodecs
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Video Encoding
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { VideoEncoder, VideoFrame } from '@napi-rs/webcodecs'
|
|
33
|
+
|
|
34
|
+
const encoder = new VideoEncoder({
|
|
35
|
+
output: (chunk, metadata) => {
|
|
36
|
+
console.log(`Encoded ${chunk.type} chunk: ${chunk.byteLength} bytes`)
|
|
37
|
+
},
|
|
38
|
+
error: (e) => console.error(e),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
encoder.configure({
|
|
42
|
+
codec: 'avc1.42001E', // H.264 Baseline
|
|
43
|
+
width: 1920,
|
|
44
|
+
height: 1080,
|
|
45
|
+
bitrate: 5_000_000,
|
|
46
|
+
hardwareAcceleration: 'prefer-hardware', // Use GPU when available
|
|
47
|
+
latencyMode: 'realtime', // Optimize for low latency
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Create and encode frames
|
|
51
|
+
const frameData = new Uint8Array(1920 * 1080 * 4) // RGBA
|
|
52
|
+
const frame = new VideoFrame(frameData, {
|
|
53
|
+
format: 'RGBA',
|
|
54
|
+
codedWidth: 1920,
|
|
55
|
+
codedHeight: 1080,
|
|
56
|
+
timestamp: 0,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
encoder.encode(frame)
|
|
60
|
+
frame.close()
|
|
61
|
+
|
|
62
|
+
// Force a keyframe for seeking/streaming
|
|
63
|
+
const frame2 = new VideoFrame(frameData, {
|
|
64
|
+
format: 'RGBA',
|
|
65
|
+
codedWidth: 1920,
|
|
66
|
+
codedHeight: 1080,
|
|
67
|
+
timestamp: 33333, // 30fps
|
|
68
|
+
})
|
|
69
|
+
encoder.encode(frame2, { keyFrame: true }) // Force I-frame
|
|
70
|
+
frame2.close()
|
|
71
|
+
|
|
72
|
+
await encoder.flush()
|
|
73
|
+
encoder.close()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Video Decoding
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { VideoDecoder, EncodedVideoChunk } from '@napi-rs/webcodecs'
|
|
80
|
+
|
|
81
|
+
const decoder = new VideoDecoder({
|
|
82
|
+
output: (frame) => {
|
|
83
|
+
console.log(`Decoded frame: ${frame.codedWidth}x${frame.codedHeight}`)
|
|
84
|
+
frame.close()
|
|
85
|
+
},
|
|
86
|
+
error: (e) => console.error(e),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
decoder.configure({
|
|
90
|
+
codec: 'avc1.42001E',
|
|
91
|
+
codedWidth: 1920,
|
|
92
|
+
codedHeight: 1080,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Decode chunks
|
|
96
|
+
const chunk = new EncodedVideoChunk({
|
|
97
|
+
type: 'key',
|
|
98
|
+
timestamp: 0,
|
|
99
|
+
data: encodedData,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
decoder.decode(chunk)
|
|
103
|
+
await decoder.flush()
|
|
104
|
+
decoder.close()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Audio Encoding
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { AudioEncoder, AudioData } from '@napi-rs/webcodecs'
|
|
111
|
+
|
|
112
|
+
const encoder = new AudioEncoder({
|
|
113
|
+
output: (chunk, metadata) => {
|
|
114
|
+
console.log(`Encoded audio: ${chunk.byteLength} bytes`)
|
|
115
|
+
},
|
|
116
|
+
error: (e) => console.error(e),
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
encoder.configure({
|
|
120
|
+
codec: 'opus',
|
|
121
|
+
sampleRate: 48000,
|
|
122
|
+
numberOfChannels: 2,
|
|
123
|
+
bitrate: 128000,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const audioData = new AudioData({
|
|
127
|
+
format: 'f32-planar',
|
|
128
|
+
sampleRate: 48000,
|
|
129
|
+
numberOfFrames: 1024,
|
|
130
|
+
numberOfChannels: 2,
|
|
131
|
+
timestamp: 0,
|
|
132
|
+
data: new Float32Array(1024 * 2),
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
encoder.encode(audioData)
|
|
136
|
+
audioData.close()
|
|
137
|
+
|
|
138
|
+
await encoder.flush()
|
|
139
|
+
encoder.close()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Image Decoding
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { ImageDecoder } from '@napi-rs/webcodecs'
|
|
146
|
+
import { readFileSync } from 'fs'
|
|
147
|
+
|
|
148
|
+
const imageData = readFileSync('image.png')
|
|
149
|
+
const decoder = new ImageDecoder({
|
|
150
|
+
data: imageData,
|
|
151
|
+
type: 'image/png',
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const result = await decoder.decode()
|
|
155
|
+
console.log(`Image: ${result.image.codedWidth}x${result.image.codedHeight}`)
|
|
156
|
+
result.image.close()
|
|
157
|
+
decoder.close()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Supported Codecs
|
|
161
|
+
|
|
162
|
+
### Video
|
|
163
|
+
|
|
164
|
+
| Codec | Codec String | Encoding | Decoding |
|
|
165
|
+
| ----- | ----------------------- | -------- | -------- |
|
|
166
|
+
| H.264 | `avc1.*` | ✅ | ✅ |
|
|
167
|
+
| H.265 | `hev1.*`, `hvc1.*` | ✅ | ✅ |
|
|
168
|
+
| VP8 | `vp8` | ✅ | ✅ |
|
|
169
|
+
| VP9 | `vp09.*`, `vp9` | ✅ | ✅ |
|
|
170
|
+
| AV1 | `av01.*`, `av01`, `av1` | ✅ | ✅ |
|
|
171
|
+
|
|
172
|
+
**Note:** Short form codec strings (`vp9`, `av01`, `av1`) are accepted for compatibility with browser implementations.
|
|
173
|
+
|
|
174
|
+
### Audio
|
|
175
|
+
|
|
176
|
+
| Codec | Codec String | Encoding | Decoding |
|
|
177
|
+
| ------ | ------------ | -------- | -------- |
|
|
178
|
+
| AAC | `mp4a.40.2` | ✅ | ✅ |
|
|
179
|
+
| Opus | `opus` | ✅ | ✅ |
|
|
180
|
+
| MP3 | `mp3` | ✅ | ✅ |
|
|
181
|
+
| FLAC | `flac` | ✅ | ✅ |
|
|
182
|
+
| Vorbis | `vorbis` | ❌ | ✅ |
|
|
183
|
+
| PCM | `pcm-*` | ❌ | ✅ |
|
|
184
|
+
|
|
185
|
+
### Image
|
|
186
|
+
|
|
187
|
+
| Format | MIME Type | Decoding |
|
|
188
|
+
| ------ | ------------ | -------- |
|
|
189
|
+
| JPEG | `image/jpeg` | ✅ |
|
|
190
|
+
| PNG | `image/png` | ✅ |
|
|
191
|
+
| WebP | `image/webp` | ✅ |
|
|
192
|
+
| GIF | `image/gif` | ✅ |
|
|
193
|
+
| BMP | `image/bmp` | ✅ |
|
|
194
|
+
| AVIF | `image/avif` | ✅ |
|
|
195
|
+
|
|
196
|
+
## Platform Support
|
|
197
|
+
|
|
198
|
+
Pre-built binaries are available for:
|
|
199
|
+
|
|
200
|
+
| Platform | Architecture |
|
|
201
|
+
| ------------------------ | ------------ |
|
|
202
|
+
| macOS | x64, arm64 |
|
|
203
|
+
| Windows | x64, arm64 |
|
|
204
|
+
| Linux (glibc) | x64, arm64 |
|
|
205
|
+
| Linux (musl) | x64, arm64 |
|
|
206
|
+
| Linux (glibc, gnueabihf) | armv7 |
|
|
207
|
+
|
|
208
|
+
## W3C Web Platform Tests Compliance
|
|
209
|
+
|
|
210
|
+
This implementation is validated against the [W3C Web Platform Tests](https://github.com/web-platform-tests/wpt) for WebCodecs.
|
|
211
|
+
|
|
212
|
+
### Ported Tests Status
|
|
213
|
+
|
|
214
|
+
| Status | Count | Percentage |
|
|
215
|
+
| ----------- | ----- | ---------- |
|
|
216
|
+
| **Passing** | 522 | 99.1% |
|
|
217
|
+
| **Skipped** | 5 | 0.9% |
|
|
218
|
+
| **Failing** | 0 | 0% |
|
|
219
|
+
|
|
220
|
+
**Skipped tests** are due to platform-specific features or edge cases.
|
|
221
|
+
|
|
222
|
+
### Tests Not Ported (Browser-Only)
|
|
223
|
+
|
|
224
|
+
19 WPT test files require browser APIs unavailable in Node.js:
|
|
225
|
+
|
|
226
|
+
| Category | Tests | APIs Required |
|
|
227
|
+
| ---------------------- | ----- | -------------------------------------- |
|
|
228
|
+
| Serialization/Transfer | 5 | MessageChannel, structured clone |
|
|
229
|
+
| WebGL/Canvas | 5 | WebGL textures, ImageBitmap, Canvas 2D |
|
|
230
|
+
| Cross-Origin Isolation | 8 | COOP/COEP headers |
|
|
231
|
+
| WebIDL | 1 | IDL interface validation |
|
|
232
|
+
|
|
233
|
+
See [`__test__/wpt/README.md`](./__test__/wpt/README.md) for detailed test status.
|
|
234
|
+
|
|
235
|
+
## Hardware Acceleration
|
|
236
|
+
|
|
237
|
+
Hardware encoding is fully supported with automatic GPU selection and fallback:
|
|
238
|
+
|
|
239
|
+
| Platform | Encoders | Features |
|
|
240
|
+
| -------- | ------------ | ------------------------------------------------------ |
|
|
241
|
+
| macOS | VideoToolbox | H.264, HEVC; realtime mode, allow_sw control |
|
|
242
|
+
| NVIDIA | NVENC | H.264, HEVC, AV1; presets p1-p7, spatial-aq, lookahead |
|
|
243
|
+
| Linux | VAAPI | H.264, HEVC, VP9, AV1; quality 0-8 |
|
|
244
|
+
| Intel | QSV | H.264, HEVC, VP9, AV1; presets, lookahead |
|
|
245
|
+
|
|
246
|
+
### Configuration
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
encoder.configure({
|
|
250
|
+
codec: 'avc1.42001E',
|
|
251
|
+
width: 1920,
|
|
252
|
+
height: 1080,
|
|
253
|
+
// Hardware acceleration preference
|
|
254
|
+
hardwareAcceleration: 'prefer-hardware', // 'no-preference' | 'prefer-hardware' | 'prefer-software'
|
|
255
|
+
// Latency mode affects encoder tuning
|
|
256
|
+
latencyMode: 'realtime', // 'quality' | 'realtime'
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
- `latencyMode: 'realtime'` - Enables low-latency encoder options (smaller GOP, no B-frames, fast presets)
|
|
261
|
+
- `latencyMode: 'quality'` - Enables quality-focused options (larger GOP, B-frames, lookahead)
|
|
262
|
+
|
|
263
|
+
The encoder automatically applies optimal settings for each hardware encoder based on the latency mode.
|
|
264
|
+
|
|
265
|
+
## Limitations
|
|
266
|
+
|
|
267
|
+
### Scalable Video Coding (SVC)
|
|
268
|
+
|
|
269
|
+
All scalability modes (L1Tx, L2Tx, L3Tx, S2Tx, S3Tx, and variants) are accepted and populate `metadata.svc.temporalLayerId` when temporal layers >= 2.
|
|
270
|
+
|
|
271
|
+
The W3C WebCodecs spec only defines `temporalLayerId` in `SvcOutputMetadata` - there is no `spatialLayerId` field in the spec. See [W3C WebCodecs §6.7](https://w3c.github.io/webcodecs/#encoded-video-chunk-metadata).
|
|
272
|
+
|
|
273
|
+
Note: This implementation computes temporal layer IDs algorithmically from frame index per W3C spec. FFmpeg is not configured for actual SVC encoding, so base layer frames are not independently decodable.
|
|
274
|
+
|
|
275
|
+
### Error Handling
|
|
276
|
+
|
|
277
|
+
Synchronous errors (e.g., calling `encode()` on a closed encoder) throw native `DOMException` instances that pass `instanceof DOMException` checks per W3C spec:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
try {
|
|
281
|
+
encoder.encode(frame) // on closed encoder
|
|
282
|
+
} catch (e) {
|
|
283
|
+
console.log(e instanceof DOMException) // true
|
|
284
|
+
console.log(e.name) // "InvalidStateError"
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Asynchronous error callbacks receive standard `Error` objects with the DOMException name in the message:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const encoder = new VideoEncoder({
|
|
292
|
+
output: (chunk) => {},
|
|
293
|
+
error: (e) => {
|
|
294
|
+
console.log(e.message) // "EncodingError: ..."
|
|
295
|
+
},
|
|
296
|
+
})
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### ImageDecoder Options
|
|
300
|
+
|
|
301
|
+
ImageDecoder supports all W3C spec options:
|
|
302
|
+
|
|
303
|
+
| Option | Status | Notes |
|
|
304
|
+
| ---------------------- | ------ | --------------------------------------------------------------------------------- |
|
|
305
|
+
| `desiredWidth/Height` | ✅ | Scales decoded frames to specified dimensions |
|
|
306
|
+
| `preferAnimation` | ✅ | When `false`, only decodes first frame for animated formats |
|
|
307
|
+
| `colorSpaceConversion` | ✅ | `"default"` extracts color space metadata, `"none"` ignores it (Chromium-aligned) |
|
|
308
|
+
|
|
309
|
+
**Note:** Per W3C spec, `desiredWidth` and `desiredHeight` must both be specified or both omitted.
|
|
310
|
+
|
|
311
|
+
### Platform-Specific Notes
|
|
312
|
+
|
|
313
|
+
- **ImageDecoder GIF animation**: FFmpeg may return only the first frame. Use `VideoDecoder` with GIF codec for full animation.
|
|
314
|
+
|
|
315
|
+
## Logging
|
|
316
|
+
|
|
317
|
+
This library uses Rust's `tracing` crate for structured logging. Enable logging via the `WEBCODECS_LOG` environment variable:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
# Enable all logs at info level
|
|
321
|
+
WEBCODECS_LOG=info node your-app.js
|
|
322
|
+
|
|
323
|
+
# Enable FFmpeg logs at debug level
|
|
324
|
+
WEBCODECS_LOG=ffmpeg=debug node your-app.js
|
|
325
|
+
|
|
326
|
+
# Enable WebCodecs codec errors at warn, FFmpeg at info
|
|
327
|
+
WEBCODECS_LOG=webcodecs=warn,ffmpeg=info node your-app.js
|
|
328
|
+
|
|
329
|
+
# Enable trace-level logging for everything
|
|
330
|
+
WEBCODECS_LOG=trace node your-app.js
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Log Targets
|
|
334
|
+
|
|
335
|
+
| Target | Description |
|
|
336
|
+
| ----------- | ---------------------------------------------------------------------- |
|
|
337
|
+
| `ffmpeg` | FFmpeg internal logs (codec initialization, encoding/decoding details) |
|
|
338
|
+
| `webcodecs` | WebCodecs API logs (codec errors, state transitions) |
|
|
339
|
+
|
|
340
|
+
### FFmpeg Log Level Mapping
|
|
341
|
+
|
|
342
|
+
| FFmpeg Level | Tracing Level |
|
|
343
|
+
| ------------ | ------------- |
|
|
344
|
+
| ERROR/FATAL | `error` |
|
|
345
|
+
| WARNING | `warn` |
|
|
346
|
+
| INFO | `info` |
|
|
347
|
+
| VERBOSE | `debug` |
|
|
348
|
+
| DEBUG/TRACE | `trace` |
|
|
349
|
+
|
|
350
|
+
Without `WEBCODECS_LOG` set, all logs are silently discarded.
|
|
351
|
+
|
|
352
|
+
## API Reference
|
|
353
|
+
|
|
354
|
+
This package implements the [W3C WebCodecs API](https://w3c.github.io/webcodecs/). Key classes:
|
|
355
|
+
|
|
356
|
+
- `VideoEncoder` / `VideoDecoder` - Video encoding and decoding with EventTarget support
|
|
357
|
+
- `AudioEncoder` / `AudioDecoder` - Audio encoding and decoding with EventTarget support
|
|
358
|
+
- `VideoFrame` - Raw video frame data (RGBA formats default to sRGB colorSpace)
|
|
359
|
+
- `AudioData` - Raw audio sample data
|
|
360
|
+
- `EncodedVideoChunk` / `EncodedAudioChunk` - Encoded media data
|
|
361
|
+
- `ImageDecoder` - Static image decoding
|
|
362
|
+
- `VideoColorSpace` - Color space information
|
|
363
|
+
|
|
364
|
+
All encoders and decoders implement the `EventTarget` interface with `addEventListener()`, `removeEventListener()`, and `dispatchEvent()`.
|
|
365
|
+
|
|
366
|
+
For full API documentation, see the [W3C WebCodecs specification](https://w3c.github.io/webcodecs/).
|
|
367
|
+
|
|
368
|
+
## Development
|
|
369
|
+
|
|
370
|
+
### Requirements
|
|
371
|
+
|
|
372
|
+
- Rust (latest stable)
|
|
373
|
+
- Node.js 18+
|
|
374
|
+
- pnpm
|
|
375
|
+
|
|
376
|
+
### Build
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
pnpm install
|
|
380
|
+
pnpm build
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Test
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
pnpm test
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Lint
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
pnpm lint
|
|
393
|
+
cargo clippy
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## License
|
|
397
|
+
|
|
398
|
+
MIT
|