@pproenca/node-webcodecs 0.1.1-alpha.0 → 0.1.1-alpha.5
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/README.md +75 -233
- package/binding.gyp +123 -0
- package/dist/audio-decoder.js +1 -2
- package/dist/audio-encoder.d.ts +4 -0
- package/dist/audio-encoder.js +28 -2
- package/dist/binding.d.ts +0 -2
- package/dist/binding.js +43 -125
- package/dist/control-message-queue.js +0 -1
- package/dist/demuxer.d.ts +7 -0
- package/dist/demuxer.js +9 -0
- package/dist/encoded-chunks.d.ts +16 -0
- package/dist/encoded-chunks.js +82 -2
- package/dist/image-decoder.js +4 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +3 -1
- package/dist/native-types.d.ts +20 -0
- package/dist/platform.d.ts +1 -10
- package/dist/platform.js +1 -39
- package/dist/resource-manager.d.ts +1 -2
- package/dist/resource-manager.js +3 -17
- package/dist/types.d.ts +12 -0
- package/dist/video-decoder.d.ts +21 -0
- package/dist/video-decoder.js +74 -2
- package/dist/video-encoder.d.ts +22 -0
- package/dist/video-encoder.js +83 -8
- package/lib/audio-decoder.ts +1 -2
- package/lib/audio-encoder.ts +31 -2
- package/lib/binding.ts +45 -104
- package/lib/control-message-queue.ts +0 -1
- package/lib/demuxer.ts +10 -0
- package/lib/encoded-chunks.ts +90 -2
- package/lib/image-decoder.ts +5 -0
- package/lib/index.ts +3 -0
- package/lib/native-types.ts +22 -0
- package/lib/platform.ts +1 -41
- package/lib/resource-manager.ts +3 -19
- package/lib/types.ts +13 -0
- package/lib/video-decoder.ts +84 -2
- package/lib/video-encoder.ts +90 -8
- package/package.json +49 -32
- package/src/addon.cc +57 -0
- package/src/async_decode_worker.cc +241 -33
- package/src/async_decode_worker.h +55 -3
- package/src/async_encode_worker.cc +103 -35
- package/src/async_encode_worker.h +23 -4
- package/src/audio_data.cc +38 -15
- package/src/audio_data.h +1 -0
- package/src/audio_decoder.cc +24 -3
- package/src/audio_encoder.cc +55 -4
- package/src/common.cc +125 -17
- package/src/common.h +34 -4
- package/src/demuxer.cc +16 -2
- package/src/encoded_audio_chunk.cc +10 -0
- package/src/encoded_audio_chunk.h +2 -0
- package/src/encoded_video_chunk.h +1 -0
- package/src/error_builder.cc +0 -4
- package/src/image_decoder.cc +127 -90
- package/src/image_decoder.h +11 -4
- package/src/muxer.cc +1 -0
- package/src/test_video_generator.cc +3 -2
- package/src/video_decoder.cc +169 -19
- package/src/video_decoder.h +9 -11
- package/src/video_encoder.cc +389 -32
- package/src/video_encoder.h +15 -0
- package/src/video_filter.cc +22 -11
- package/src/video_frame.cc +160 -5
- package/src/warnings.cc +0 -4
- package/dist/audio-data.js.map +0 -1
- package/dist/audio-decoder.js.map +0 -1
- package/dist/audio-encoder.js.map +0 -1
- package/dist/binding.js.map +0 -1
- package/dist/codec-base.js.map +0 -1
- package/dist/control-message-queue.js.map +0 -1
- package/dist/demuxer.js.map +0 -1
- package/dist/encoded-chunks.js.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/ffmpeg.d.ts +0 -21
- package/dist/ffmpeg.js +0 -112
- package/dist/image-decoder.js.map +0 -1
- package/dist/image-track-list.js.map +0 -1
- package/dist/image-track.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/is.js.map +0 -1
- package/dist/muxer.js.map +0 -1
- package/dist/native-types.js.map +0 -1
- package/dist/platform.js.map +0 -1
- package/dist/resource-manager.js.map +0 -1
- package/dist/test-video-generator.js.map +0 -1
- package/dist/transfer.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/video-decoder.js.map +0 -1
- package/dist/video-encoder.js.map +0 -1
- package/dist/video-filter.js.map +0 -1
- package/dist/video-frame.js.map +0 -1
- package/install/build.js +0 -51
- package/install/check.js +0 -192
- package/lib/ffmpeg.ts +0 -78
package/README.md
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
# node-webcodecs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
WebCodecs API for Node.js — encode and decode video/audio with browser-compatible APIs, powered by FFmpeg.
|
|
4
4
|
|
|
5
|
-
[](test/)
|
|
6
5
|
[](LICENSE)
|
|
7
|
-
[](package.json)
|
|
8
7
|
|
|
9
8
|
## Why node-webcodecs?
|
|
10
9
|
|
|
11
|
-
| Feature
|
|
12
|
-
|
|
13
|
-
| **Video Codecs**
|
|
14
|
-
| **Audio Codecs**
|
|
15
|
-
| **Container Muxing** | MP4
|
|
16
|
-
| **Demuxing**
|
|
17
|
-
| **
|
|
18
|
-
| **Pixel Formats** | 20+ formats (8/10/12-bit) | Limited |
|
|
19
|
-
| **Server-side** | ✅ | ❌ Browser only |
|
|
20
|
-
|
|
21
|
-
*\* Decode only*
|
|
10
|
+
| Feature | node-webcodecs | Browser WebCodecs |
|
|
11
|
+
| -------------------- | --------------------------- | ----------------- |
|
|
12
|
+
| **Video Codecs** | H.264, H.265, VP8, VP9, AV1 | Varies by browser |
|
|
13
|
+
| **Audio Codecs** | AAC, Opus, MP3, FLAC | Varies by browser |
|
|
14
|
+
| **Container Muxing** | MP4 | Not in spec |
|
|
15
|
+
| **Demuxing** | Any FFmpeg format | Not in spec |
|
|
16
|
+
| **Server-side** | Yes | Browser only |
|
|
22
17
|
|
|
23
18
|
## Installation
|
|
24
19
|
|
|
@@ -26,309 +21,156 @@ W3C WebCodecs API implementation for Node.js - encode and decode video/audio wit
|
|
|
26
21
|
npm install @pproenca/node-webcodecs
|
|
27
22
|
```
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
| Platform | Architecture |
|
|
32
|
-
|----------|--------------|
|
|
33
|
-
| macOS | Apple Silicon (arm64), Intel (x64) |
|
|
34
|
-
| Linux | x64 (glibc), x64 (musl/Alpine) |
|
|
35
|
-
| Windows | x64 |
|
|
36
|
-
|
|
37
|
-
### Building from Source
|
|
24
|
+
Prebuilt binaries with FFmpeg statically linked are included for:
|
|
38
25
|
|
|
39
|
-
|
|
26
|
+
- macOS ARM64 (Apple Silicon)
|
|
27
|
+
- macOS x64 (Intel)
|
|
28
|
+
- Linux x64 (musl, fully static)
|
|
40
29
|
|
|
41
30
|
<details>
|
|
42
|
-
<summary><strong>
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
brew install ffmpeg pkg-config
|
|
46
|
-
npm install @pproenca/node-webcodecs
|
|
47
|
-
```
|
|
48
|
-
</details>
|
|
31
|
+
<summary><strong>Building from Source</strong></summary>
|
|
49
32
|
|
|
50
|
-
|
|
51
|
-
<summary><strong>Ubuntu/Debian</strong></summary>
|
|
33
|
+
For other platforms or to force a source build:
|
|
52
34
|
|
|
53
35
|
```bash
|
|
54
|
-
|
|
55
|
-
libavcodec-dev libavformat-dev libavutil-dev \
|
|
56
|
-
libswscale-dev libswresample-dev libavfilter-dev \
|
|
57
|
-
pkg-config
|
|
58
|
-
npm install @pproenca/node-webcodecs
|
|
36
|
+
npm install @pproenca/node-webcodecs --build-from-source
|
|
59
37
|
```
|
|
60
|
-
</details>
|
|
61
38
|
|
|
62
|
-
|
|
63
|
-
|
|
39
|
+
This requires FFmpeg 5.0+ development libraries:
|
|
40
|
+
|
|
41
|
+
**macOS:**
|
|
64
42
|
|
|
65
43
|
```bash
|
|
66
|
-
|
|
67
|
-
npm install @pproenca/node-webcodecs
|
|
44
|
+
brew install ffmpeg pkg-config
|
|
68
45
|
```
|
|
69
|
-
</details>
|
|
70
46
|
|
|
71
|
-
|
|
72
|
-
<summary><strong>Windows</strong></summary>
|
|
47
|
+
**Ubuntu/Debian:**
|
|
73
48
|
|
|
74
49
|
```bash
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
npm install @pproenca/node-webcodecs
|
|
50
|
+
sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev \
|
|
51
|
+
libswscale-dev libswresample-dev libavfilter-dev pkg-config
|
|
78
52
|
```
|
|
79
|
-
</details>
|
|
80
53
|
|
|
81
|
-
|
|
54
|
+
**Fedora/RHEL:**
|
|
82
55
|
|
|
83
56
|
```bash
|
|
84
|
-
|
|
57
|
+
sudo dnf install ffmpeg-devel pkg-config
|
|
85
58
|
```
|
|
86
59
|
|
|
60
|
+
</details>
|
|
61
|
+
|
|
87
62
|
## Quick Start
|
|
88
63
|
|
|
89
|
-
### Encode
|
|
64
|
+
### Encode
|
|
90
65
|
|
|
91
66
|
```javascript
|
|
92
|
-
import { VideoEncoder, VideoFrame } from
|
|
93
|
-
|
|
94
|
-
const chunks = [];
|
|
67
|
+
import { VideoEncoder, VideoFrame } from "@pproenca/node-webcodecs";
|
|
95
68
|
|
|
96
69
|
const encoder = new VideoEncoder({
|
|
97
|
-
output: (chunk
|
|
98
|
-
|
|
99
|
-
if (metadata?.decoderConfig?.description) {
|
|
100
|
-
// Save codec extradata for container
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
error: (e) => console.error('Encode error:', e),
|
|
70
|
+
output: (chunk) => chunks.push(chunk),
|
|
71
|
+
error: console.error,
|
|
104
72
|
});
|
|
105
73
|
|
|
106
74
|
encoder.configure({
|
|
107
|
-
codec:
|
|
75
|
+
codec: "avc1.42001e",
|
|
108
76
|
width: 1920,
|
|
109
77
|
height: 1080,
|
|
110
78
|
bitrate: 5_000_000,
|
|
111
|
-
framerate: 30,
|
|
112
79
|
});
|
|
113
80
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
timestamp: i * (1_000_000 / 30), // microseconds
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
encoder.encode(frame, { keyFrame: i === 0 });
|
|
124
|
-
frame.close();
|
|
125
|
-
}
|
|
81
|
+
const frame = new VideoFrame(rgbaBuffer, {
|
|
82
|
+
format: "RGBA",
|
|
83
|
+
codedWidth: 1920,
|
|
84
|
+
codedHeight: 1080,
|
|
85
|
+
timestamp: 0,
|
|
86
|
+
});
|
|
126
87
|
|
|
88
|
+
encoder.encode(frame);
|
|
89
|
+
frame.close();
|
|
127
90
|
await encoder.flush();
|
|
128
|
-
encoder.close();
|
|
129
91
|
```
|
|
130
92
|
|
|
131
|
-
### Decode
|
|
93
|
+
### Decode
|
|
132
94
|
|
|
133
95
|
```javascript
|
|
134
|
-
import { VideoDecoder, EncodedVideoChunk } from
|
|
96
|
+
import { VideoDecoder, EncodedVideoChunk } from "@pproenca/node-webcodecs";
|
|
135
97
|
|
|
136
98
|
const decoder = new VideoDecoder({
|
|
137
99
|
output: (frame) => {
|
|
138
|
-
|
|
139
|
-
// Process frame data...
|
|
100
|
+
// Process frame...
|
|
140
101
|
frame.close();
|
|
141
102
|
},
|
|
142
|
-
error:
|
|
103
|
+
error: console.error,
|
|
143
104
|
});
|
|
144
105
|
|
|
145
106
|
decoder.configure({
|
|
146
|
-
codec:
|
|
107
|
+
codec: "avc1.42001e",
|
|
147
108
|
codedWidth: 1920,
|
|
148
109
|
codedHeight: 1080,
|
|
149
|
-
description: codecExtradata, // From container or encoder
|
|
150
110
|
});
|
|
151
111
|
|
|
152
|
-
|
|
153
|
-
decoder.decode(new EncodedVideoChunk({
|
|
154
|
-
type: chunk.isKeyframe ? 'key' : 'delta',
|
|
155
|
-
timestamp: chunk.timestamp,
|
|
156
|
-
data: chunk.data,
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
159
|
-
|
|
112
|
+
decoder.decode(new EncodedVideoChunk({ type: "key", timestamp: 0, data }));
|
|
160
113
|
await decoder.flush();
|
|
161
|
-
decoder.close();
|
|
162
114
|
```
|
|
163
115
|
|
|
164
|
-
### Mux to MP4
|
|
116
|
+
### Mux to MP4
|
|
165
117
|
|
|
166
118
|
```javascript
|
|
167
|
-
import { Muxer
|
|
119
|
+
import { Muxer } from "@pproenca/node-webcodecs";
|
|
168
120
|
|
|
169
|
-
const muxer = new Muxer({ filename:
|
|
170
|
-
let videoTrackId;
|
|
121
|
+
const muxer = new Muxer({ filename: "output.mp4" });
|
|
171
122
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (metadata?.decoderConfig?.description) {
|
|
175
|
-
// Add video track on first keyframe
|
|
176
|
-
videoTrackId = muxer.addVideoTrack({
|
|
177
|
-
codec: 'avc1.42001e',
|
|
178
|
-
width: 1920,
|
|
179
|
-
height: 1080,
|
|
180
|
-
description: metadata.decoderConfig.description,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
muxer.writeVideoChunk(videoTrackId, chunk);
|
|
184
|
-
},
|
|
185
|
-
error: console.error,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
encoder.configure({
|
|
189
|
-
codec: 'avc1.42001e',
|
|
123
|
+
muxer.addVideoTrack({
|
|
124
|
+
codec: "avc1.42001e",
|
|
190
125
|
width: 1920,
|
|
191
126
|
height: 1080,
|
|
192
|
-
|
|
193
|
-
avc: { format: 'avc' }, // Use AVCC format for MP4
|
|
127
|
+
description: codecDescription,
|
|
194
128
|
});
|
|
195
129
|
|
|
196
|
-
|
|
197
|
-
await encoder.flush();
|
|
198
|
-
encoder.close();
|
|
130
|
+
muxer.writeVideoChunk(chunk);
|
|
199
131
|
muxer.finalize();
|
|
200
132
|
```
|
|
201
133
|
|
|
202
|
-
### Demux
|
|
134
|
+
### Demux
|
|
203
135
|
|
|
204
136
|
```javascript
|
|
205
|
-
import { Demuxer } from
|
|
137
|
+
import { Demuxer } from "@pproenca/node-webcodecs";
|
|
206
138
|
|
|
207
139
|
const demuxer = new Demuxer({
|
|
208
|
-
onTrack: (track) =>
|
|
209
|
-
|
|
210
|
-
if (track.type === 'video') {
|
|
211
|
-
// Configure decoder with track.extradata
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
onChunk: (chunk, trackIndex) => {
|
|
215
|
-
// Feed chunk to decoder
|
|
216
|
-
},
|
|
140
|
+
onTrack: (track) => console.log(track.codec),
|
|
141
|
+
onChunk: (chunk, trackIndex) => decoder.decode(chunk),
|
|
217
142
|
onError: console.error,
|
|
218
143
|
});
|
|
219
144
|
|
|
220
|
-
demuxer.open(
|
|
221
|
-
demuxer.demux();
|
|
145
|
+
demuxer.open("input.mp4");
|
|
146
|
+
demuxer.demux();
|
|
222
147
|
demuxer.close();
|
|
223
148
|
```
|
|
224
149
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
```javascript
|
|
228
|
-
import { ImageDecoder } from '@pproenca/node-webcodecs';
|
|
229
|
-
import { readFileSync } from 'fs';
|
|
230
|
-
|
|
231
|
-
const imageData = readFileSync('animation.gif');
|
|
232
|
-
|
|
233
|
-
const decoder = new ImageDecoder({
|
|
234
|
-
type: 'image/gif',
|
|
235
|
-
data: imageData,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
await decoder.completed;
|
|
239
|
-
|
|
240
|
-
console.log(`${decoder.tracks.length} track(s)`);
|
|
241
|
-
console.log(`${decoder.tracks[0].frameCount} frames`);
|
|
242
|
-
console.log(`Animated: ${decoder.tracks[0].animated}`);
|
|
243
|
-
|
|
244
|
-
// Decode each frame
|
|
245
|
-
for (let i = 0; i < decoder.tracks[0].frameCount; i++) {
|
|
246
|
-
const { image, complete } = await decoder.decode({ frameIndex: i });
|
|
247
|
-
console.log(`Frame ${i}: ${image.codedWidth}x${image.codedHeight}`);
|
|
248
|
-
image.close();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
decoder.close();
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## API Reference
|
|
255
|
-
|
|
256
|
-
This library implements the [W3C WebCodecs specification](https://www.w3.org/TR/webcodecs/):
|
|
150
|
+
See [examples/](examples/) for complete working code.
|
|
257
151
|
|
|
258
|
-
|
|
152
|
+
## API
|
|
259
153
|
|
|
260
|
-
|
|
261
|
-
|-------|-------------|
|
|
262
|
-
| `VideoEncoder` | Encode VideoFrame to EncodedVideoChunk |
|
|
263
|
-
| `VideoDecoder` | Decode EncodedVideoChunk to VideoFrame |
|
|
264
|
-
| `AudioEncoder` | Encode AudioData to EncodedAudioChunk |
|
|
265
|
-
| `AudioDecoder` | Decode EncodedAudioChunk to AudioData |
|
|
266
|
-
| `VideoFrame` | Raw video frame with pixel data |
|
|
267
|
-
| `AudioData` | Raw audio samples |
|
|
268
|
-
| `EncodedVideoChunk` | Compressed video data |
|
|
269
|
-
| `EncodedAudioChunk` | Compressed audio data |
|
|
270
|
-
| `ImageDecoder` | Decode images (JPEG, PNG, WebP, GIF) |
|
|
271
|
-
| `VideoColorSpace` | Color space metadata |
|
|
154
|
+
This library implements the [W3C WebCodecs specification](https://www.w3.org/TR/webcodecs/).
|
|
272
155
|
|
|
273
|
-
|
|
156
|
+
| Class | Description |
|
|
157
|
+
| ----------------------------------------- | ------------------------------- |
|
|
158
|
+
| `VideoEncoder` / `VideoDecoder` | Compress / decompress video |
|
|
159
|
+
| `AudioEncoder` / `AudioDecoder` | Compress / decompress audio |
|
|
160
|
+
| `VideoFrame` / `AudioData` | Raw media containers |
|
|
161
|
+
| `EncodedVideoChunk` / `EncodedAudioChunk` | Compressed media packets |
|
|
162
|
+
| `ImageDecoder` | Decode JPEG, PNG, WebP, GIF |
|
|
163
|
+
| | |
|
|
164
|
+
| `Muxer` / `Demuxer` | Container I/O (beyond W3C spec) |
|
|
274
165
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
| `Muxer` | Write to MP4 containers |
|
|
278
|
-
| `Demuxer` | Read from any FFmpeg-supported format |
|
|
279
|
-
| `VideoFilter` | Apply blur filters (content moderation) |
|
|
280
|
-
| `TestVideoGenerator` | Generate test patterns |
|
|
166
|
+
**Video codecs:** H.264, H.265, VP8, VP9, AV1
|
|
167
|
+
**Audio codecs:** AAC, Opus, MP3 (decode), FLAC (decode)
|
|
281
168
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
| Type | Codec | Encode | Decode | Codec String |
|
|
285
|
-
|------|-------|--------|--------|--------------|
|
|
286
|
-
| Video | H.264/AVC | ✅ | ✅ | `avc1.*` |
|
|
287
|
-
| Video | H.265/HEVC | ✅ | ✅ | `hvc1.*`, `hev1.*` |
|
|
288
|
-
| Video | VP8 | ✅ | ✅ | `vp8` |
|
|
289
|
-
| Video | VP9 | ✅ | ✅ | `vp09.*` |
|
|
290
|
-
| Video | AV1 | ✅ | ✅ | `av01.*` |
|
|
291
|
-
| Audio | AAC | ✅ | ✅ | `mp4a.40.2` |
|
|
292
|
-
| Audio | Opus | ✅ | ✅ | `opus` |
|
|
293
|
-
| Audio | MP3 | ❌ | ✅ | `mp3` |
|
|
294
|
-
| Audio | FLAC | ❌ | ✅ | `flac` |
|
|
295
|
-
|
|
296
|
-
### Pixel Formats
|
|
297
|
-
|
|
298
|
-
8-bit: `I420`, `I420A`, `I422`, `I422A`, `I444`, `I444A`, `NV12`, `NV21`, `NV12A`, `RGBA`, `RGBX`, `BGRA`, `BGRX`
|
|
299
|
-
|
|
300
|
-
10-bit: `I420P10`, `I422P10`, `I444P10`, `NV12P10`, `I420AP10`, `I422AP10`, `I444AP10`
|
|
301
|
-
|
|
302
|
-
12-bit: `I420P12`, `I422P12`, `I444P12`
|
|
303
|
-
|
|
304
|
-
## Interactive Demos
|
|
305
|
-
|
|
306
|
-
Run the interactive demo with web UI:
|
|
307
|
-
|
|
308
|
-
```bash
|
|
309
|
-
git clone https://github.com/pproenca/node-webcodecs
|
|
310
|
-
cd node-webcodecs
|
|
311
|
-
npm install && npm run build
|
|
312
|
-
node examples/run-demo.js
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
Or use Docker:
|
|
316
|
-
|
|
317
|
-
```bash
|
|
318
|
-
docker compose up demo
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
## Test Results
|
|
322
|
-
|
|
323
|
-
```
|
|
324
|
-
Test Files: 45 passed / 51 total
|
|
325
|
-
Tests: 428 passed / 442 total
|
|
326
|
-
Duration: ~2 minutes
|
|
327
|
-
```
|
|
169
|
+
See [docs/codecs.md](docs/codecs.md) for codec strings and pixel formats.
|
|
328
170
|
|
|
329
171
|
## Contributing
|
|
330
172
|
|
|
331
|
-
See [CONTRIBUTING.md](.github/CONTRIBUTING.md)
|
|
173
|
+
See [CONTRIBUTING.md](.github/CONTRIBUTING.md).
|
|
332
174
|
|
|
333
175
|
## License
|
|
334
176
|
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"variables": {
|
|
3
|
+
"enable_sanitizers%": 0
|
|
4
|
+
},
|
|
5
|
+
"targets": [
|
|
6
|
+
{
|
|
7
|
+
"target_name": "node_webcodecs",
|
|
8
|
+
"sources": [
|
|
9
|
+
"src/addon.cc",
|
|
10
|
+
"src/common.cc",
|
|
11
|
+
"src/video_encoder.cc",
|
|
12
|
+
"src/video_decoder.cc",
|
|
13
|
+
"src/video_frame.cc",
|
|
14
|
+
"src/audio_encoder.cc",
|
|
15
|
+
"src/audio_decoder.cc",
|
|
16
|
+
"src/audio_data.cc",
|
|
17
|
+
"src/encoded_video_chunk.cc",
|
|
18
|
+
"src/encoded_audio_chunk.cc",
|
|
19
|
+
"src/video_filter.cc",
|
|
20
|
+
"src/demuxer.cc",
|
|
21
|
+
"src/muxer.cc",
|
|
22
|
+
"src/image_decoder.cc",
|
|
23
|
+
"src/test_video_generator.cc",
|
|
24
|
+
"src/async_encode_worker.cc",
|
|
25
|
+
"src/async_decode_worker.cc",
|
|
26
|
+
"src/warnings.cc",
|
|
27
|
+
"src/error_builder.cc",
|
|
28
|
+
"src/descriptors.cc"
|
|
29
|
+
],
|
|
30
|
+
"include_dirs": [
|
|
31
|
+
"<!@(node -p \"require('node-addon-api').include\")",
|
|
32
|
+
"."
|
|
33
|
+
],
|
|
34
|
+
"defines": [
|
|
35
|
+
"NAPI_VERSION=8",
|
|
36
|
+
"NAPI_CPP_EXCEPTIONS",
|
|
37
|
+
"NODE_ADDON_API_DISABLE_DEPRECATED"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": [
|
|
40
|
+
"<!(node -p \"require('node-addon-api').gyp\")"
|
|
41
|
+
],
|
|
42
|
+
"conditions": [
|
|
43
|
+
["OS=='mac'", {
|
|
44
|
+
"include_dirs": [
|
|
45
|
+
"<!@(node gyp/ffmpeg-paths.js include 2>/dev/null || pkg-config --cflags-only-I libavcodec libavutil libswscale libswresample libavfilter 2>/dev/null | sed s/-I//g || echo '/opt/homebrew/include /usr/local/include')"
|
|
46
|
+
],
|
|
47
|
+
"libraries": [
|
|
48
|
+
"<!@(node gyp/ffmpeg-paths.js lib 2>/dev/null || pkg-config --libs --static libavcodec libavformat libavutil libswscale libswresample libavfilter 2>/dev/null || echo '-L/opt/homebrew/lib -L/usr/local/lib -lavcodec -lavformat -lavutil -lswscale -lswresample -lavfilter')",
|
|
49
|
+
"-framework VideoToolbox",
|
|
50
|
+
"-framework AudioToolbox",
|
|
51
|
+
"-framework CoreMedia",
|
|
52
|
+
"-framework CoreVideo",
|
|
53
|
+
"-framework CoreFoundation",
|
|
54
|
+
"-framework CoreServices",
|
|
55
|
+
"-framework Security",
|
|
56
|
+
"-framework Metal",
|
|
57
|
+
"-framework CoreImage",
|
|
58
|
+
"-framework AppKit",
|
|
59
|
+
"-liconv",
|
|
60
|
+
"-lbz2",
|
|
61
|
+
"-lz"
|
|
62
|
+
],
|
|
63
|
+
"xcode_settings": {
|
|
64
|
+
"CLANG_CXX_LANGUAGE_STANDARD": "c++20",
|
|
65
|
+
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
|
66
|
+
"GCC_ENABLE_CPP_RTTI": "YES",
|
|
67
|
+
"MACOSX_DEPLOYMENT_TARGET": "11.0",
|
|
68
|
+
"OTHER_CPLUSPLUSFLAGS": [
|
|
69
|
+
"-fexceptions",
|
|
70
|
+
"-Wall",
|
|
71
|
+
"-Wextra",
|
|
72
|
+
"-Wno-unused-parameter"
|
|
73
|
+
],
|
|
74
|
+
"OTHER_LDFLAGS": [
|
|
75
|
+
"-mmacosx-version-min=11.0"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}],
|
|
79
|
+
["OS=='linux'", {
|
|
80
|
+
"include_dirs": [
|
|
81
|
+
"<!@(node gyp/ffmpeg-paths.js include 2>/dev/null || pkg-config --cflags-only-I libavcodec libavutil libswscale libswresample libavfilter | sed s/-I//g)"
|
|
82
|
+
],
|
|
83
|
+
"libraries": [
|
|
84
|
+
"<!@(node gyp/ffmpeg-paths.js lib 2>/dev/null || pkg-config --libs --static libavcodec libavformat libavutil libswscale libswresample libavfilter)",
|
|
85
|
+
"-lpthread",
|
|
86
|
+
"-lm",
|
|
87
|
+
"-ldl",
|
|
88
|
+
"-lz"
|
|
89
|
+
],
|
|
90
|
+
"ldflags": [
|
|
91
|
+
"-Wl,-Bsymbolic"
|
|
92
|
+
],
|
|
93
|
+
"cflags_cc": [
|
|
94
|
+
"-std=c++20",
|
|
95
|
+
"-fexceptions",
|
|
96
|
+
"-Wall",
|
|
97
|
+
"-Wextra",
|
|
98
|
+
"-Wno-unused-parameter",
|
|
99
|
+
"-fPIC"
|
|
100
|
+
]
|
|
101
|
+
}],
|
|
102
|
+
["enable_sanitizers==1", {
|
|
103
|
+
"cflags_cc": [
|
|
104
|
+
"-fsanitize=address,undefined",
|
|
105
|
+
"-fno-omit-frame-pointer"
|
|
106
|
+
],
|
|
107
|
+
"ldflags": [
|
|
108
|
+
"-fsanitize=address,undefined"
|
|
109
|
+
],
|
|
110
|
+
"xcode_settings": {
|
|
111
|
+
"OTHER_CFLAGS": [
|
|
112
|
+
"-fsanitize=address,undefined",
|
|
113
|
+
"-fno-omit-frame-pointer"
|
|
114
|
+
],
|
|
115
|
+
"OTHER_LDFLAGS": [
|
|
116
|
+
"-fsanitize=address,undefined"
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
}]
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
package/dist/audio-decoder.js
CHANGED
|
@@ -55,7 +55,6 @@ class AudioDecoder extends codec_base_1.CodecBase {
|
|
|
55
55
|
this._errorCallback = init.error;
|
|
56
56
|
this._controlQueue.setErrorHandler(init.error);
|
|
57
57
|
const outputCallback = (nativeData) => {
|
|
58
|
-
// Decrement queue size when output received
|
|
59
58
|
this._decodeQueueSize = Math.max(0, this._decodeQueueSize - 1);
|
|
60
59
|
// biome-ignore lint/suspicious/noExplicitAny: Object.create wrapper pattern requires any for property assignment
|
|
61
60
|
const wrapper = Object.create(audio_data_1.AudioData.prototype);
|
|
@@ -92,7 +91,7 @@ class AudioDecoder extends codec_base_1.CodecBase {
|
|
|
92
91
|
decode(chunk) {
|
|
93
92
|
// W3C spec: throw InvalidStateError if not configured
|
|
94
93
|
if (this.state === 'unconfigured') {
|
|
95
|
-
throw new DOMException('Decoder is
|
|
94
|
+
throw new DOMException('Decoder is unconfigured', 'InvalidStateError');
|
|
96
95
|
}
|
|
97
96
|
if (this.state === 'closed') {
|
|
98
97
|
throw new DOMException('Decoder is closed', 'InvalidStateError');
|
package/dist/audio-encoder.d.ts
CHANGED
|
@@ -10,10 +10,14 @@ export declare class AudioEncoder extends CodecBase {
|
|
|
10
10
|
private _native;
|
|
11
11
|
private _controlQueue;
|
|
12
12
|
private _encodeQueueSize;
|
|
13
|
+
private _maxQueueDepth;
|
|
13
14
|
constructor(init: AudioEncoderInit);
|
|
14
15
|
get state(): CodecState;
|
|
15
16
|
get encodeQueueSize(): number;
|
|
16
17
|
get codecSaturated(): boolean;
|
|
18
|
+
get maxQueueDepth(): number;
|
|
19
|
+
set maxQueueDepth(value: number);
|
|
20
|
+
get ready(): Promise<void>;
|
|
17
21
|
configure(config: AudioEncoderConfig): void;
|
|
18
22
|
encode(data: AudioData): void;
|
|
19
23
|
flush(): Promise<void>;
|
package/dist/audio-encoder.js
CHANGED
|
@@ -46,10 +46,12 @@ const encoded_chunks_1 = require("./encoded-chunks");
|
|
|
46
46
|
const is = __importStar(require("./is"));
|
|
47
47
|
// Load native addon with type assertion
|
|
48
48
|
const native = binding_1.binding;
|
|
49
|
+
const DEFAULT_MAX_QUEUE_DEPTH = 16;
|
|
49
50
|
class AudioEncoder extends codec_base_1.CodecBase {
|
|
50
51
|
constructor(init) {
|
|
51
52
|
super();
|
|
52
53
|
this._encodeQueueSize = 0;
|
|
54
|
+
this._maxQueueDepth = DEFAULT_MAX_QUEUE_DEPTH;
|
|
53
55
|
// W3C spec: output and error callbacks are required
|
|
54
56
|
is.assertPlainObject(init, 'init');
|
|
55
57
|
is.assertFunction(init.output, 'init.output');
|
|
@@ -57,7 +59,6 @@ class AudioEncoder extends codec_base_1.CodecBase {
|
|
|
57
59
|
this._controlQueue = new control_message_queue_1.ControlMessageQueue();
|
|
58
60
|
this._controlQueue.setErrorHandler(init.error);
|
|
59
61
|
const outputCallback = (chunk, metadata) => {
|
|
60
|
-
// Decrement queue size when output received
|
|
61
62
|
this._encodeQueueSize = Math.max(0, this._encodeQueueSize - 1);
|
|
62
63
|
// biome-ignore lint/suspicious/noExplicitAny: Object.create wrapper pattern requires any for property assignment
|
|
63
64
|
const wrapper = Object.create(encoded_chunks_1.EncodedAudioChunk.prototype);
|
|
@@ -80,6 +81,31 @@ class AudioEncoder extends codec_base_1.CodecBase {
|
|
|
80
81
|
get codecSaturated() {
|
|
81
82
|
return this._native.codecSaturated;
|
|
82
83
|
}
|
|
84
|
+
get maxQueueDepth() {
|
|
85
|
+
return this._maxQueueDepth;
|
|
86
|
+
}
|
|
87
|
+
set maxQueueDepth(value) {
|
|
88
|
+
if (value < 1) {
|
|
89
|
+
throw new RangeError('maxQueueDepth must be at least 1');
|
|
90
|
+
}
|
|
91
|
+
this._maxQueueDepth = value;
|
|
92
|
+
}
|
|
93
|
+
get ready() {
|
|
94
|
+
if (this._encodeQueueSize < this._maxQueueDepth) {
|
|
95
|
+
return Promise.resolve();
|
|
96
|
+
}
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
const checkCapacity = () => {
|
|
99
|
+
if (this._encodeQueueSize < this._maxQueueDepth) {
|
|
100
|
+
resolve();
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
setTimeout(checkCapacity, 1);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
setTimeout(checkCapacity, 1);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
83
109
|
configure(config) {
|
|
84
110
|
// W3C spec: throw if closed
|
|
85
111
|
if (this.state === 'closed') {
|
|
@@ -95,7 +121,7 @@ class AudioEncoder extends codec_base_1.CodecBase {
|
|
|
95
121
|
encode(data) {
|
|
96
122
|
// W3C spec: throw InvalidStateError if not configured
|
|
97
123
|
if (this.state === 'unconfigured') {
|
|
98
|
-
throw new DOMException('Encoder is
|
|
124
|
+
throw new DOMException('Encoder is unconfigured', 'InvalidStateError');
|
|
99
125
|
}
|
|
100
126
|
if (this.state === 'closed') {
|
|
101
127
|
throw new DOMException('Encoder is closed', 'InvalidStateError');
|
package/dist/binding.d.ts
CHANGED