@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/dist/binding.js
CHANGED
|
@@ -2,140 +2,58 @@
|
|
|
2
2
|
// Copyright 2024 The node-webcodecs Authors
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
4
|
//
|
|
5
|
-
// Native binding loader
|
|
6
|
-
|
|
7
|
-
if (k2 === undefined) k2 = k;
|
|
8
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
-
}
|
|
12
|
-
Object.defineProperty(o, k2, desc);
|
|
13
|
-
}) : (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
o[k2] = m[k];
|
|
16
|
-
}));
|
|
17
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
-
}) : function(o, v) {
|
|
20
|
-
o["default"] = v;
|
|
21
|
-
});
|
|
22
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
-
var ownKeys = function(o) {
|
|
24
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
-
var ar = [];
|
|
26
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
-
return ar;
|
|
28
|
-
};
|
|
29
|
-
return ownKeys(o);
|
|
30
|
-
};
|
|
31
|
-
return function (mod) {
|
|
32
|
-
if (mod && mod.__esModule) return mod;
|
|
33
|
-
var result = {};
|
|
34
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
-
__setModuleDefault(result, mod);
|
|
36
|
-
return result;
|
|
37
|
-
};
|
|
38
|
-
})();
|
|
5
|
+
// Native binding loader using esbuild-style platform resolution.
|
|
6
|
+
// Tries platform-specific package first, falls back to node-gyp-build for local dev.
|
|
39
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
8
|
exports.platformInfo = exports.binding = void 0;
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
catch {
|
|
55
|
-
throw new Error('node-gyp-build not available');
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
// Prebuilt from platform package (sharp pattern: @scope/pkg-platform/binding.node)
|
|
59
|
-
() => {
|
|
60
|
-
const pkg = (0, platform_1.getPrebuiltPackageName)();
|
|
61
|
-
const bindingPath = `${pkg}/node-webcodecs.node`;
|
|
62
|
-
return require(bindingPath);
|
|
63
|
-
},
|
|
64
|
-
];
|
|
65
|
-
function getPlatformBuildInstructions() {
|
|
66
|
-
const platform = process.platform;
|
|
67
|
-
if (platform === 'darwin') {
|
|
68
|
-
return ` brew install ffmpeg pkg-config
|
|
69
|
-
npm run build:native`;
|
|
70
|
-
}
|
|
71
|
-
if (platform === 'linux') {
|
|
72
|
-
return ` sudo apt-get install libavcodec-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev pkg-config
|
|
73
|
-
npm run build:native`;
|
|
74
|
-
}
|
|
75
|
-
if (platform === 'win32') {
|
|
76
|
-
return ` Download FFmpeg from https://github.com/BtbN/FFmpeg-Builds/releases
|
|
77
|
-
Set FFMPEG_PATH environment variable
|
|
78
|
-
npm run build:native`;
|
|
79
|
-
}
|
|
80
|
-
return ` Install FFmpeg development libraries
|
|
81
|
-
npm run build:native`;
|
|
82
|
-
}
|
|
83
|
-
function buildHelpMessage(errors) {
|
|
84
|
-
const platform = (0, platform_1.runtimePlatformArch)();
|
|
85
|
-
const hasPrebuilt = (0, platform_1.isPrebuiltAvailable)();
|
|
86
|
-
let msg = `Could not load native binding for ${platform}.\n\n`;
|
|
87
|
-
msg += `Node.js: ${process.version}\n\n`;
|
|
88
|
-
msg += 'Attempted paths:\n';
|
|
89
|
-
for (const { path: p, error } of errors) {
|
|
90
|
-
msg += ` - ${p}: ${error.message}\n`;
|
|
91
|
-
}
|
|
92
|
-
msg += '\nPossible solutions:\n';
|
|
93
|
-
if (hasPrebuilt) {
|
|
94
|
-
msg += ' 1. Install with optional dependencies:\n';
|
|
95
|
-
msg += ' npm install --include=optional\n\n';
|
|
96
|
-
msg += ' 2. Build from source:\n';
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
msg += ' 1. Build from source:\n';
|
|
100
|
-
}
|
|
101
|
-
msg += getPlatformBuildInstructions();
|
|
102
|
-
return msg;
|
|
103
|
-
}
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const PLATFORMS = {
|
|
11
|
+
'darwin-arm64': '@pproenca/node-webcodecs-darwin-arm64',
|
|
12
|
+
'darwin-x64': '@pproenca/node-webcodecs-darwin-x64',
|
|
13
|
+
'linux-x64': '@pproenca/node-webcodecs-linux-x64',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Load the native binding.
|
|
17
|
+
*
|
|
18
|
+
* Resolution order:
|
|
19
|
+
* 1. Platform-specific npm package (production path via optionalDependencies)
|
|
20
|
+
* 2. node-gyp-build (local development fallback)
|
|
21
|
+
*/
|
|
104
22
|
function loadBinding() {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
23
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
24
|
+
const pkg = PLATFORMS[platform];
|
|
25
|
+
if (!pkg) {
|
|
26
|
+
throw new Error(`Unsupported platform: ${platform}. ` +
|
|
27
|
+
`Supported platforms: ${Object.keys(PLATFORMS).join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
// Try platform-specific package first (production path)
|
|
30
|
+
try {
|
|
31
|
+
const pkgPath = require.resolve(`${pkg}/package.json`);
|
|
32
|
+
const binPath = (0, node_path_1.join)((0, node_path_1.dirname)(pkgPath), 'bin', 'node.napi.node');
|
|
33
|
+
return require(binPath);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Platform package not installed - fallback to local build
|
|
37
|
+
}
|
|
38
|
+
// Fallback to node-gyp-build for local development
|
|
39
|
+
try {
|
|
40
|
+
const nodeGypBuild = require('node-gyp-build');
|
|
41
|
+
const rootDir = (0, node_path_1.resolve)(__dirname, '..');
|
|
42
|
+
return nodeGypBuild(rootDir);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw new Error(`Could not load the node-webcodecs native binding for ${platform}.\n` +
|
|
46
|
+
`Error: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
47
|
+
`Solutions:\n` +
|
|
48
|
+
` 1. Install the main package: npm install @pproenca/node-webcodecs\n` +
|
|
49
|
+
` 2. Build from source: npm rebuild --build-from-source\n` +
|
|
50
|
+
` 3. Ensure FFmpeg dev libs: pkg-config --exists libavcodec\n`);
|
|
130
51
|
}
|
|
131
|
-
throw new Error(buildHelpMessage(errors));
|
|
132
52
|
}
|
|
133
53
|
exports.binding = loadBinding();
|
|
134
54
|
exports.platformInfo = {
|
|
135
55
|
platform: process.platform,
|
|
136
56
|
arch: process.arch,
|
|
137
|
-
runtimePlatform: (0, platform_1.runtimePlatformArch)(),
|
|
138
57
|
nodeVersion: process.version,
|
|
139
58
|
napiVersion: process.versions.napi ?? 'unknown',
|
|
140
|
-
prebuiltAvailable: (0, platform_1.isPrebuiltAvailable)(),
|
|
141
59
|
};
|
package/dist/demuxer.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ export declare class Demuxer {
|
|
|
4
4
|
constructor(init: DemuxerInit);
|
|
5
5
|
open(path: string): Promise<void>;
|
|
6
6
|
demux(): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Read packets from the file in chunks.
|
|
9
|
+
* This is useful for yielding to the event loop during demuxing.
|
|
10
|
+
* @param maxPackets - Maximum number of packets to read. 0 = unlimited (reads all).
|
|
11
|
+
* @returns The number of packets actually read.
|
|
12
|
+
*/
|
|
13
|
+
demuxPackets(maxPackets?: number): number;
|
|
7
14
|
close(): void;
|
|
8
15
|
getVideoTrack(): TrackInfo | null;
|
|
9
16
|
getAudioTrack(): TrackInfo | null;
|
package/dist/demuxer.js
CHANGED
|
@@ -30,6 +30,15 @@ class Demuxer {
|
|
|
30
30
|
async demux() {
|
|
31
31
|
return this._native.demux();
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Read packets from the file in chunks.
|
|
35
|
+
* This is useful for yielding to the event loop during demuxing.
|
|
36
|
+
* @param maxPackets - Maximum number of packets to read. 0 = unlimited (reads all).
|
|
37
|
+
* @returns The number of packets actually read.
|
|
38
|
+
*/
|
|
39
|
+
demuxPackets(maxPackets) {
|
|
40
|
+
return this._native.demuxPackets(maxPackets ?? 0);
|
|
41
|
+
}
|
|
33
42
|
close() {
|
|
34
43
|
this._native.close();
|
|
35
44
|
}
|
package/dist/encoded-chunks.d.ts
CHANGED
|
@@ -9,11 +9,22 @@ export declare class EncodedVideoChunk {
|
|
|
9
9
|
/** @internal */
|
|
10
10
|
_native: NativeEncodedVideoChunk;
|
|
11
11
|
constructor(init: EncodedVideoChunkInit);
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
* Wrap an existing native EncodedVideoChunk without copying data.
|
|
15
|
+
* Used by the encoder's async output path to avoid double-copying.
|
|
16
|
+
*/
|
|
17
|
+
static _fromNative(nativeChunk: NativeEncodedVideoChunk): EncodedVideoChunk;
|
|
12
18
|
get type(): 'key' | 'delta';
|
|
13
19
|
get timestamp(): number;
|
|
14
20
|
get duration(): number | null;
|
|
15
21
|
get byteLength(): number;
|
|
16
22
|
copyTo(destination: ArrayBuffer | ArrayBufferView): void;
|
|
23
|
+
/**
|
|
24
|
+
* Releases the internal data buffer.
|
|
25
|
+
* Per W3C WebCodecs spec, this allows early release of memory.
|
|
26
|
+
*/
|
|
27
|
+
close(): void;
|
|
17
28
|
}
|
|
18
29
|
export declare class EncodedAudioChunk {
|
|
19
30
|
private _native;
|
|
@@ -23,5 +34,10 @@ export declare class EncodedAudioChunk {
|
|
|
23
34
|
get duration(): number | null;
|
|
24
35
|
get byteLength(): number;
|
|
25
36
|
copyTo(destination: ArrayBuffer | ArrayBufferView): void;
|
|
37
|
+
/**
|
|
38
|
+
* Releases the internal data buffer.
|
|
39
|
+
* Per W3C WebCodecs spec, this allows early release of memory.
|
|
40
|
+
*/
|
|
41
|
+
close(): void;
|
|
26
42
|
get _nativeChunk(): NativeEncodedAudioChunk;
|
|
27
43
|
}
|
package/dist/encoded-chunks.js
CHANGED
|
@@ -7,15 +7,45 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.EncodedAudioChunk = exports.EncodedVideoChunk = void 0;
|
|
9
9
|
const binding_1 = require("./binding");
|
|
10
|
+
const transfer_1 = require("./transfer");
|
|
10
11
|
// Load native addon with type assertion
|
|
11
12
|
const native = binding_1.binding;
|
|
13
|
+
/**
|
|
14
|
+
* FinalizationRegistry for automatic cleanup of native EncodedVideoChunk objects.
|
|
15
|
+
* When a JS EncodedVideoChunk wrapper becomes unreachable, the registry callback
|
|
16
|
+
* fires and releases the native memory via close().
|
|
17
|
+
*
|
|
18
|
+
* This provides a safety net for users who forget to call close(), preventing
|
|
19
|
+
* memory leaks in high-throughput scenarios where GC may be delayed.
|
|
20
|
+
*/
|
|
21
|
+
const videoChunkRegistry = new FinalizationRegistry((native) => {
|
|
22
|
+
// The weak reference to the JS wrapper is now dead, but the native object
|
|
23
|
+
// may still be valid. Call close() to release its internal data buffer.
|
|
24
|
+
// close() is idempotent - safe to call even if already closed.
|
|
25
|
+
try {
|
|
26
|
+
native.close();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Ignore errors - native object may already be destroyed
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* FinalizationRegistry for automatic cleanup of native EncodedAudioChunk objects.
|
|
34
|
+
*/
|
|
35
|
+
const audioChunkRegistry = new FinalizationRegistry((native) => {
|
|
36
|
+
try {
|
|
37
|
+
native.close();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Ignore errors - native object may already be destroyed
|
|
41
|
+
}
|
|
42
|
+
});
|
|
12
43
|
class EncodedVideoChunk {
|
|
13
44
|
constructor(init) {
|
|
14
45
|
// W3C spec: type must be 'key' or 'delta'
|
|
15
46
|
if (init.type !== 'key' && init.type !== 'delta') {
|
|
16
47
|
throw new TypeError(`Invalid type: ${init.type}`);
|
|
17
48
|
}
|
|
18
|
-
// Convert BufferSource to Buffer for native
|
|
19
49
|
let dataBuffer;
|
|
20
50
|
if (init.data instanceof ArrayBuffer) {
|
|
21
51
|
dataBuffer = Buffer.from(init.data);
|
|
@@ -32,6 +62,26 @@ class EncodedVideoChunk {
|
|
|
32
62
|
duration: init.duration,
|
|
33
63
|
data: dataBuffer,
|
|
34
64
|
});
|
|
65
|
+
// Register with FinalizationRegistry for automatic cleanup.
|
|
66
|
+
// When this JS wrapper is GC'd, the registry callback will call close()
|
|
67
|
+
// on the native object to release memory.
|
|
68
|
+
videoChunkRegistry.register(this, this._native, this);
|
|
69
|
+
// Handle ArrayBuffer transfer semantics per W3C spec
|
|
70
|
+
if (init.transfer && Array.isArray(init.transfer)) {
|
|
71
|
+
(0, transfer_1.detachArrayBuffers)(init.transfer.filter((b) => b instanceof ArrayBuffer));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @internal
|
|
76
|
+
* Wrap an existing native EncodedVideoChunk without copying data.
|
|
77
|
+
* Used by the encoder's async output path to avoid double-copying.
|
|
78
|
+
*/
|
|
79
|
+
static _fromNative(nativeChunk) {
|
|
80
|
+
const chunk = Object.create(EncodedVideoChunk.prototype);
|
|
81
|
+
chunk._native = nativeChunk;
|
|
82
|
+
// Register with FinalizationRegistry for automatic cleanup
|
|
83
|
+
videoChunkRegistry.register(chunk, nativeChunk, chunk);
|
|
84
|
+
return chunk;
|
|
35
85
|
}
|
|
36
86
|
get type() {
|
|
37
87
|
return this._native.type;
|
|
@@ -62,11 +112,24 @@ class EncodedVideoChunk {
|
|
|
62
112
|
throw new TypeError('Destination must be ArrayBuffer or ArrayBufferView');
|
|
63
113
|
}
|
|
64
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Releases the internal data buffer.
|
|
117
|
+
* Per W3C WebCodecs spec, this allows early release of memory.
|
|
118
|
+
*/
|
|
119
|
+
close() {
|
|
120
|
+
// Unregister from FinalizationRegistry to prevent double-close.
|
|
121
|
+
// If close() is called explicitly, we don't need the registry callback.
|
|
122
|
+
videoChunkRegistry.unregister(this);
|
|
123
|
+
this._native.close();
|
|
124
|
+
}
|
|
65
125
|
}
|
|
66
126
|
exports.EncodedVideoChunk = EncodedVideoChunk;
|
|
67
127
|
class EncodedAudioChunk {
|
|
68
128
|
constructor(init) {
|
|
69
|
-
//
|
|
129
|
+
// W3C spec: type must be 'key' or 'delta'
|
|
130
|
+
if (init.type !== 'key' && init.type !== 'delta') {
|
|
131
|
+
throw new TypeError(`Invalid type: ${init.type}`);
|
|
132
|
+
}
|
|
70
133
|
let dataBuffer;
|
|
71
134
|
if (init.data instanceof ArrayBuffer) {
|
|
72
135
|
dataBuffer = Buffer.from(init.data);
|
|
@@ -83,6 +146,14 @@ class EncodedAudioChunk {
|
|
|
83
146
|
duration: init.duration,
|
|
84
147
|
data: dataBuffer,
|
|
85
148
|
});
|
|
149
|
+
// Register with FinalizationRegistry for automatic cleanup.
|
|
150
|
+
// When this JS wrapper is GC'd, the registry callback will call close()
|
|
151
|
+
// on the native object to release memory.
|
|
152
|
+
audioChunkRegistry.register(this, this._native, this);
|
|
153
|
+
// Handle ArrayBuffer transfer semantics per W3C spec
|
|
154
|
+
if (init.transfer && Array.isArray(init.transfer)) {
|
|
155
|
+
(0, transfer_1.detachArrayBuffers)(init.transfer.filter((b) => b instanceof ArrayBuffer));
|
|
156
|
+
}
|
|
86
157
|
}
|
|
87
158
|
get type() {
|
|
88
159
|
return this._native.type;
|
|
@@ -106,6 +177,15 @@ class EncodedAudioChunk {
|
|
|
106
177
|
this._native.copyTo(uint8);
|
|
107
178
|
}
|
|
108
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Releases the internal data buffer.
|
|
182
|
+
* Per W3C WebCodecs spec, this allows early release of memory.
|
|
183
|
+
*/
|
|
184
|
+
close() {
|
|
185
|
+
// Unregister from FinalizationRegistry to prevent double-close.
|
|
186
|
+
audioChunkRegistry.unregister(this);
|
|
187
|
+
this._native.close();
|
|
188
|
+
}
|
|
109
189
|
get _nativeChunk() {
|
|
110
190
|
return this._native;
|
|
111
191
|
}
|
package/dist/image-decoder.js
CHANGED
|
@@ -36,6 +36,7 @@ class ImageDecoder {
|
|
|
36
36
|
this._initOptions = {
|
|
37
37
|
type: init.type,
|
|
38
38
|
colorSpaceConversion: init.colorSpaceConversion,
|
|
39
|
+
premultiplyAlpha: init.premultiplyAlpha,
|
|
39
40
|
desiredWidth: init.desiredWidth,
|
|
40
41
|
desiredHeight: init.desiredHeight,
|
|
41
42
|
preferAnimation: init.preferAnimation,
|
|
@@ -74,6 +75,9 @@ class ImageDecoder {
|
|
|
74
75
|
if ('colorSpaceConversion' in init && init.colorSpaceConversion) {
|
|
75
76
|
nativeInit.colorSpaceConversion = init.colorSpaceConversion;
|
|
76
77
|
}
|
|
78
|
+
if ('premultiplyAlpha' in init && init.premultiplyAlpha !== undefined) {
|
|
79
|
+
nativeInit.premultiplyAlpha = init.premultiplyAlpha;
|
|
80
|
+
}
|
|
77
81
|
if ('desiredWidth' in init && init.desiredWidth !== undefined) {
|
|
78
82
|
nativeInit.desiredWidth = init.desiredWidth;
|
|
79
83
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,17 @@ export declare const createEncoderConfigDescriptor: (config: object) => {
|
|
|
51
51
|
colorMatrix: string;
|
|
52
52
|
colorFullRange: boolean;
|
|
53
53
|
};
|
|
54
|
+
export declare const getCounters: () => {
|
|
55
|
+
videoFrames: number;
|
|
56
|
+
audioData: number;
|
|
57
|
+
videoEncoders: number;
|
|
58
|
+
videoDecoders: number;
|
|
59
|
+
audioEncoders: number;
|
|
60
|
+
audioDecoders: number;
|
|
61
|
+
queue: number;
|
|
62
|
+
process: number;
|
|
63
|
+
frames: number;
|
|
64
|
+
};
|
|
54
65
|
export type { ErrorCodeType } from './errors';
|
|
55
66
|
export { AllocationError, ConfigurationError, DecodingError, EncodingError, ErrorCode, ffmpegErrorMessage, InvalidDataError, InvalidStateError, UnsupportedCodecError, WebCodecsError, } from './errors';
|
|
56
67
|
export { ImageTrack } from './image-track';
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - SVC temporal layer tracking via scalabilityMode (L1T1, L1T2, L1T3)
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.TestVideoGenerator = exports.ResourceManager = exports.ImageTrackList = exports.ImageTrack = exports.WebCodecsError = exports.UnsupportedCodecError = exports.InvalidStateError = exports.InvalidDataError = exports.ffmpegErrorMessage = exports.ErrorCode = exports.EncodingError = exports.DecodingError = exports.ConfigurationError = exports.AllocationError = exports.createEncoderConfigDescriptor = exports.testAttrAsEnum = exports.ErrorBuilder = exports.clearFFmpegWarnings = exports.getFFmpegWarnings = exports.WarningAccumulator = exports.VideoFrame = exports.VideoColorSpace = exports.VideoFilter = exports.VideoEncoder = exports.VideoDecoder = exports.Muxer = exports.ImageDecoder = exports.EncodedVideoChunk = exports.EncodedAudioChunk = exports.Demuxer = exports.AudioEncoder = exports.AudioDecoder = exports.AudioData = exports.platformInfo = void 0;
|
|
19
|
+
exports.TestVideoGenerator = exports.ResourceManager = exports.ImageTrackList = exports.ImageTrack = exports.WebCodecsError = exports.UnsupportedCodecError = exports.InvalidStateError = exports.InvalidDataError = exports.ffmpegErrorMessage = exports.ErrorCode = exports.EncodingError = exports.DecodingError = exports.ConfigurationError = exports.AllocationError = exports.getCounters = exports.createEncoderConfigDescriptor = exports.testAttrAsEnum = exports.ErrorBuilder = exports.clearFFmpegWarnings = exports.getFFmpegWarnings = exports.WarningAccumulator = exports.VideoFrame = exports.VideoColorSpace = exports.VideoFilter = exports.VideoEncoder = exports.VideoDecoder = exports.Muxer = exports.ImageDecoder = exports.EncodedVideoChunk = exports.EncodedAudioChunk = exports.Demuxer = exports.AudioEncoder = exports.AudioDecoder = exports.AudioData = exports.platformInfo = void 0;
|
|
20
20
|
const binding_1 = require("./binding");
|
|
21
21
|
Object.defineProperty(exports, "platformInfo", { enumerable: true, get: function () { return binding_1.platformInfo; } });
|
|
22
22
|
// Load native addon with type assertion
|
|
@@ -54,6 +54,8 @@ exports.clearFFmpegWarnings = native.clearFFmpegWarnings;
|
|
|
54
54
|
exports.ErrorBuilder = native.ErrorBuilder;
|
|
55
55
|
exports.testAttrAsEnum = native.testAttrAsEnum;
|
|
56
56
|
exports.createEncoderConfigDescriptor = native.createEncoderConfigDescriptor;
|
|
57
|
+
// Export instance counters for monitoring and leak detection
|
|
58
|
+
exports.getCounters = native.getCounters;
|
|
57
59
|
// Re-export error classes and codes
|
|
58
60
|
var errors_1 = require("./errors");
|
|
59
61
|
Object.defineProperty(exports, "AllocationError", { enumerable: true, get: function () { return errors_1.AllocationError; } });
|
package/dist/native-types.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export interface NativeVideoDecoder {
|
|
|
65
65
|
readonly state: CodecState;
|
|
66
66
|
readonly decodeQueueSize: number;
|
|
67
67
|
readonly codecSaturated: boolean;
|
|
68
|
+
readonly pendingFrames: number;
|
|
68
69
|
configure(config: VideoDecoderConfig): void;
|
|
69
70
|
decode(chunk: NativeEncodedVideoChunk): void;
|
|
70
71
|
flush(): Promise<void>;
|
|
@@ -80,6 +81,7 @@ export interface NativeEncodedVideoChunk {
|
|
|
80
81
|
readonly duration: number | null;
|
|
81
82
|
readonly byteLength: number;
|
|
82
83
|
copyTo(dest: Uint8Array | ArrayBuffer): void;
|
|
84
|
+
close(): void;
|
|
83
85
|
}
|
|
84
86
|
/**
|
|
85
87
|
* Native AudioData object from C++ addon
|
|
@@ -111,6 +113,7 @@ export interface NativeEncodedAudioChunk {
|
|
|
111
113
|
readonly duration: number | null;
|
|
112
114
|
readonly byteLength: number;
|
|
113
115
|
copyTo(dest: Uint8Array | ArrayBuffer): void;
|
|
116
|
+
close(): void;
|
|
114
117
|
}
|
|
115
118
|
/**
|
|
116
119
|
* Native AudioEncoder object from C++ addon
|
|
@@ -151,6 +154,12 @@ export interface NativeVideoFilter {
|
|
|
151
154
|
export interface NativeDemuxer {
|
|
152
155
|
open(path: string): void;
|
|
153
156
|
demux(): void;
|
|
157
|
+
/**
|
|
158
|
+
* Read packets from the file in chunks.
|
|
159
|
+
* @param maxPackets - Maximum number of packets to read. 0 = unlimited.
|
|
160
|
+
* @returns The number of packets actually read.
|
|
161
|
+
*/
|
|
162
|
+
demuxPackets(maxPackets: number): number;
|
|
154
163
|
close(): void;
|
|
155
164
|
getVideoTrack(): TrackInfo | null;
|
|
156
165
|
getAudioTrack(): TrackInfo | null;
|
|
@@ -462,6 +471,17 @@ export interface NativeModule {
|
|
|
462
471
|
getFFmpegWarnings: () => string[];
|
|
463
472
|
clearFFmpegWarnings: () => void;
|
|
464
473
|
testAttrAsEnum: (obj: object, attr: string) => string;
|
|
474
|
+
getCounters: () => {
|
|
475
|
+
videoFrames: number;
|
|
476
|
+
audioData: number;
|
|
477
|
+
videoEncoders: number;
|
|
478
|
+
videoDecoders: number;
|
|
479
|
+
audioEncoders: number;
|
|
480
|
+
audioDecoders: number;
|
|
481
|
+
queue: number;
|
|
482
|
+
process: number;
|
|
483
|
+
frames: number;
|
|
484
|
+
};
|
|
465
485
|
createEncoderConfigDescriptor: (config: object) => {
|
|
466
486
|
codec: string;
|
|
467
487
|
width: number;
|
package/dist/platform.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Get the runtime platform-architecture string.
|
|
3
|
-
* Handles musl vs glibc distinction on Linux.
|
|
4
3
|
*/
|
|
5
4
|
export declare function runtimePlatformArch(): string;
|
|
6
5
|
/**
|
|
@@ -15,17 +14,9 @@ export declare function buildPlatformArch(): string;
|
|
|
15
14
|
* Platforms with prebuilt binaries available.
|
|
16
15
|
* Must match what release.yml actually builds.
|
|
17
16
|
*/
|
|
18
|
-
export declare const prebuiltPlatforms: readonly ["darwin-arm64", "darwin-x64", "linux-x64"
|
|
17
|
+
export declare const prebuiltPlatforms: readonly ["darwin-arm64", "darwin-x64", "linux-x64"];
|
|
19
18
|
export type PrebuiltPlatform = (typeof prebuiltPlatforms)[number];
|
|
20
19
|
/**
|
|
21
20
|
* Check if a prebuilt binary is available for the current platform.
|
|
22
21
|
*/
|
|
23
22
|
export declare function isPrebuiltAvailable(): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Get the npm package name for the prebuilt binary.
|
|
26
|
-
*/
|
|
27
|
-
export declare function getPrebuiltPackageName(): string;
|
|
28
|
-
/**
|
|
29
|
-
* Get the npm package name for prebuilt FFmpeg.
|
|
30
|
-
*/
|
|
31
|
-
export declare function getFFmpegPackageName(): string;
|
package/dist/platform.js
CHANGED
|
@@ -41,36 +41,12 @@ exports.prebuiltPlatforms = void 0;
|
|
|
41
41
|
exports.runtimePlatformArch = runtimePlatformArch;
|
|
42
42
|
exports.buildPlatformArch = buildPlatformArch;
|
|
43
43
|
exports.isPrebuiltAvailable = isPrebuiltAvailable;
|
|
44
|
-
exports.getPrebuiltPackageName = getPrebuiltPackageName;
|
|
45
|
-
exports.getFFmpegPackageName = getFFmpegPackageName;
|
|
46
44
|
const os = __importStar(require("node:os"));
|
|
47
|
-
// Try to detect musl vs glibc on Linux
|
|
48
|
-
function detectLibc() {
|
|
49
|
-
if (os.platform() !== 'linux')
|
|
50
|
-
return null;
|
|
51
|
-
try {
|
|
52
|
-
const { familySync } = require('detect-libc');
|
|
53
|
-
return familySync() === 'musl' ? 'musl' : 'glibc';
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// detect-libc not available, assume glibc
|
|
57
|
-
return 'glibc';
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
45
|
/**
|
|
61
46
|
* Get the runtime platform-architecture string.
|
|
62
|
-
* Handles musl vs glibc distinction on Linux.
|
|
63
47
|
*/
|
|
64
48
|
function runtimePlatformArch() {
|
|
65
|
-
|
|
66
|
-
const arch = os.arch();
|
|
67
|
-
if (platform === 'linux') {
|
|
68
|
-
const libc = detectLibc();
|
|
69
|
-
if (libc === 'musl') {
|
|
70
|
-
return `linuxmusl-${arch}`;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return `${platform}-${arch}`;
|
|
49
|
+
return `${os.platform()}-${os.arch()}`;
|
|
74
50
|
}
|
|
75
51
|
/**
|
|
76
52
|
* Get the build-time platform-architecture string.
|
|
@@ -92,8 +68,6 @@ exports.prebuiltPlatforms = [
|
|
|
92
68
|
'darwin-arm64',
|
|
93
69
|
'darwin-x64',
|
|
94
70
|
'linux-x64',
|
|
95
|
-
'linuxmusl-x64',
|
|
96
|
-
'win32-x64',
|
|
97
71
|
];
|
|
98
72
|
/**
|
|
99
73
|
* Check if a prebuilt binary is available for the current platform.
|
|
@@ -102,15 +76,3 @@ function isPrebuiltAvailable() {
|
|
|
102
76
|
const platform = runtimePlatformArch();
|
|
103
77
|
return exports.prebuiltPlatforms.includes(platform);
|
|
104
78
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Get the npm package name for the prebuilt binary.
|
|
107
|
-
*/
|
|
108
|
-
function getPrebuiltPackageName() {
|
|
109
|
-
return `@pproenca/node-webcodecs-${runtimePlatformArch()}`;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Get the npm package name for prebuilt FFmpeg.
|
|
113
|
-
*/
|
|
114
|
-
function getFFmpegPackageName() {
|
|
115
|
-
return `@pproenca/ffmpeg-${runtimePlatformArch()}`;
|
|
116
|
-
}
|
|
@@ -15,7 +15,6 @@ export declare class ResourceManager {
|
|
|
15
15
|
private static instance;
|
|
16
16
|
private codecs;
|
|
17
17
|
private inactivityTimeout;
|
|
18
|
-
private checkInterval;
|
|
19
18
|
private constructor();
|
|
20
19
|
static getInstance(): ResourceManager;
|
|
21
20
|
/**
|
|
@@ -51,9 +50,9 @@ export declare class ResourceManager {
|
|
|
51
50
|
* Set inactivity timeout (for testing).
|
|
52
51
|
*/
|
|
53
52
|
setInactivityTimeout(ms: number): void;
|
|
54
|
-
private startMonitoring;
|
|
55
53
|
/**
|
|
56
54
|
* Stop monitoring (for cleanup).
|
|
55
|
+
* @deprecated No longer needed - monitoring happens on-demand via getReclaimableCodecs()
|
|
57
56
|
*/
|
|
58
57
|
stopMonitoring(): void;
|
|
59
58
|
}
|
package/dist/resource-manager.js
CHANGED
|
@@ -11,8 +11,7 @@ class ResourceManager {
|
|
|
11
11
|
constructor() {
|
|
12
12
|
this.codecs = new Map();
|
|
13
13
|
this.inactivityTimeout = 10000; // 10 seconds per spec
|
|
14
|
-
|
|
15
|
-
this.startMonitoring();
|
|
14
|
+
// Monitoring happens on-demand via getReclaimableCodecs()
|
|
16
15
|
}
|
|
17
16
|
static getInstance() {
|
|
18
17
|
if (!ResourceManager.instance) {
|
|
@@ -104,25 +103,12 @@ class ResourceManager {
|
|
|
104
103
|
setInactivityTimeout(ms) {
|
|
105
104
|
this.inactivityTimeout = ms;
|
|
106
105
|
}
|
|
107
|
-
startMonitoring() {
|
|
108
|
-
// Check every 5 seconds
|
|
109
|
-
this.checkInterval = setInterval(() => {
|
|
110
|
-
// Just track, don't auto-reclaim
|
|
111
|
-
// Actual reclamation would be triggered by memory pressure
|
|
112
|
-
}, 5000);
|
|
113
|
-
// Don't keep process alive
|
|
114
|
-
if (this.checkInterval.unref) {
|
|
115
|
-
this.checkInterval.unref();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
106
|
/**
|
|
119
107
|
* Stop monitoring (for cleanup).
|
|
108
|
+
* @deprecated No longer needed - monitoring happens on-demand via getReclaimableCodecs()
|
|
120
109
|
*/
|
|
121
110
|
stopMonitoring() {
|
|
122
|
-
|
|
123
|
-
clearInterval(this.checkInterval);
|
|
124
|
-
this.checkInterval = null;
|
|
125
|
-
}
|
|
111
|
+
// No-op: monitoring now happens on-demand
|
|
126
112
|
}
|
|
127
113
|
}
|
|
128
114
|
exports.ResourceManager = ResourceManager;
|