@pproenca/node-webcodecs 0.1.1-alpha.0 → 0.1.1-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +75 -233
  2. package/binding.gyp +123 -0
  3. package/dist/audio-decoder.js +1 -2
  4. package/dist/audio-encoder.d.ts +4 -0
  5. package/dist/audio-encoder.js +28 -2
  6. package/dist/binding.d.ts +0 -2
  7. package/dist/binding.js +43 -125
  8. package/dist/control-message-queue.js +0 -1
  9. package/dist/control-message-queue.js.map +1 -1
  10. package/dist/demuxer.d.ts +7 -0
  11. package/dist/demuxer.js +9 -0
  12. package/dist/encoded-chunks.d.ts +16 -0
  13. package/dist/encoded-chunks.js +82 -2
  14. package/dist/image-decoder.js +4 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.js +3 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/native-types.d.ts +20 -0
  19. package/dist/platform.d.ts +1 -10
  20. package/dist/platform.js +1 -39
  21. package/dist/resource-manager.d.ts +1 -2
  22. package/dist/resource-manager.js +3 -17
  23. package/dist/resource-manager.js.map +1 -1
  24. package/dist/types.d.ts +12 -0
  25. package/dist/types.js.map +1 -1
  26. package/dist/video-decoder.d.ts +21 -0
  27. package/dist/video-decoder.js +74 -2
  28. package/dist/video-encoder.d.ts +22 -0
  29. package/dist/video-encoder.js +83 -8
  30. package/lib/audio-decoder.ts +1 -2
  31. package/lib/audio-encoder.ts +31 -2
  32. package/lib/binding.ts +45 -104
  33. package/lib/control-message-queue.ts +0 -1
  34. package/lib/demuxer.ts +10 -0
  35. package/lib/encoded-chunks.ts +90 -2
  36. package/lib/image-decoder.ts +5 -0
  37. package/lib/index.ts +3 -0
  38. package/lib/native-types.ts +22 -0
  39. package/lib/platform.ts +1 -41
  40. package/lib/resource-manager.ts +3 -19
  41. package/lib/types.ts +13 -0
  42. package/lib/video-decoder.ts +84 -2
  43. package/lib/video-encoder.ts +90 -8
  44. package/package.json +48 -32
  45. package/src/addon.cc +57 -0
  46. package/src/async_decode_worker.cc +241 -33
  47. package/src/async_decode_worker.h +55 -3
  48. package/src/async_encode_worker.cc +103 -35
  49. package/src/async_encode_worker.h +23 -4
  50. package/src/audio_data.cc +38 -15
  51. package/src/audio_data.h +1 -0
  52. package/src/audio_decoder.cc +24 -3
  53. package/src/audio_encoder.cc +55 -4
  54. package/src/common.cc +125 -17
  55. package/src/common.h +34 -4
  56. package/src/demuxer.cc +16 -2
  57. package/src/encoded_audio_chunk.cc +10 -0
  58. package/src/encoded_audio_chunk.h +2 -0
  59. package/src/encoded_video_chunk.h +1 -0
  60. package/src/error_builder.cc +0 -4
  61. package/src/image_decoder.cc +127 -90
  62. package/src/image_decoder.h +11 -4
  63. package/src/muxer.cc +1 -0
  64. package/src/test_video_generator.cc +3 -2
  65. package/src/video_decoder.cc +169 -19
  66. package/src/video_decoder.h +9 -11
  67. package/src/video_encoder.cc +389 -32
  68. package/src/video_encoder.h +15 -0
  69. package/src/video_filter.cc +22 -11
  70. package/src/video_frame.cc +160 -5
  71. package/src/warnings.cc +0 -4
  72. package/dist/audio-data.js.map +0 -1
  73. package/dist/audio-decoder.js.map +0 -1
  74. package/dist/audio-encoder.js.map +0 -1
  75. package/dist/binding.js.map +0 -1
  76. package/dist/codec-base.js.map +0 -1
  77. package/dist/demuxer.js.map +0 -1
  78. package/dist/encoded-chunks.js.map +0 -1
  79. package/dist/errors.js.map +0 -1
  80. package/dist/ffmpeg.d.ts +0 -21
  81. package/dist/ffmpeg.js +0 -112
  82. package/dist/image-decoder.js.map +0 -1
  83. package/dist/image-track-list.js.map +0 -1
  84. package/dist/image-track.js.map +0 -1
  85. package/dist/is.js.map +0 -1
  86. package/dist/muxer.js.map +0 -1
  87. package/dist/native-types.js.map +0 -1
  88. package/dist/platform.js.map +0 -1
  89. package/dist/test-video-generator.js.map +0 -1
  90. package/dist/transfer.js.map +0 -1
  91. package/dist/video-decoder.js.map +0 -1
  92. package/dist/video-encoder.js.map +0 -1
  93. package/dist/video-filter.js.map +0 -1
  94. package/dist/video-frame.js.map +0 -1
  95. package/install/build.js +0 -51
  96. package/install/check.js +0 -192
  97. package/lib/ffmpeg.ts +0 -78
package/lib/binding.ts CHANGED
@@ -1,116 +1,59 @@
1
1
  // Copyright 2024 The node-webcodecs Authors
2
2
  // SPDX-License-Identifier: MIT
3
3
  //
4
- // Native binding loader with fallback chain and enhanced error messages.
4
+ // Native binding loader using esbuild-style platform resolution.
5
+ // Tries platform-specific package first, falls back to node-gyp-build for local dev.
5
6
 
6
- import * as fs from 'node:fs';
7
- import * as path from 'node:path';
8
- import { getPrebuiltPackageName, isPrebuiltAvailable, runtimePlatformArch } from './platform';
7
+ import { resolve, dirname, join } from 'node:path';
9
8
 
10
- const rootDir = path.resolve(__dirname, '..');
11
-
12
- type LoadCandidate = string | (() => unknown);
13
-
14
- const candidates: LoadCandidate[] = [
15
- // Development build (node-gyp output)
16
- path.join(rootDir, 'build', 'Release', 'node_webcodecs.node'),
17
- path.join(rootDir, 'build', 'Debug', 'node_webcodecs.node'),
18
-
19
- // node-gyp-build compatible
20
- () => {
21
- try {
22
- return require('node-gyp-build')(rootDir);
23
- } catch {
24
- throw new Error('node-gyp-build not available');
25
- }
26
- },
27
-
28
- // Prebuilt from platform package (sharp pattern: @scope/pkg-platform/binding.node)
29
- () => {
30
- const pkg = getPrebuiltPackageName();
31
- const bindingPath = `${pkg}/node-webcodecs.node`;
32
- return require(bindingPath);
33
- },
34
- ];
35
-
36
- function getPlatformBuildInstructions(): string {
37
- const platform = process.platform;
38
-
39
- if (platform === 'darwin') {
40
- return ` brew install ffmpeg pkg-config
41
- npm run build:native`;
42
- }
43
- if (platform === 'linux') {
44
- return ` sudo apt-get install libavcodec-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev pkg-config
45
- npm run build:native`;
46
- }
47
- if (platform === 'win32') {
48
- return ` Download FFmpeg from https://github.com/BtbN/FFmpeg-Builds/releases
49
- Set FFMPEG_PATH environment variable
50
- npm run build:native`;
51
- }
52
- return ` Install FFmpeg development libraries
53
- npm run build:native`;
54
- }
55
-
56
- function buildHelpMessage(errors: Array<{ path: string; error: Error }>): string {
57
- const platform = runtimePlatformArch();
58
- const hasPrebuilt = isPrebuiltAvailable();
59
-
60
- let msg = `Could not load native binding for ${platform}.\n\n`;
61
- msg += `Node.js: ${process.version}\n\n`;
9
+ const PLATFORMS: Record<string, string> = {
10
+ 'darwin-arm64': '@pproenca/node-webcodecs-darwin-arm64',
11
+ 'darwin-x64': '@pproenca/node-webcodecs-darwin-x64',
12
+ 'linux-x64': '@pproenca/node-webcodecs-linux-x64',
13
+ };
62
14
 
63
- msg += 'Attempted paths:\n';
64
- for (const { path: p, error } of errors) {
65
- msg += ` - ${p}: ${error.message}\n`;
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
+ */
22
+ function loadBinding(): unknown {
23
+ const platform = `${process.platform}-${process.arch}`;
24
+ const pkg = PLATFORMS[platform];
25
+
26
+ if (!pkg) {
27
+ throw new Error(
28
+ `Unsupported platform: ${platform}. ` +
29
+ `Supported platforms: ${Object.keys(PLATFORMS).join(', ')}`
30
+ );
66
31
  }
67
32
 
68
- msg += '\nPossible solutions:\n';
69
-
70
- if (hasPrebuilt) {
71
- msg += ' 1. Install with optional dependencies:\n';
72
- msg += ' npm install --include=optional\n\n';
73
- msg += ' 2. Build from source:\n';
74
- } else {
75
- msg += ' 1. Build from source:\n';
33
+ // Try platform-specific package first (production path)
34
+ try {
35
+ const pkgPath = require.resolve(`${pkg}/package.json`);
36
+ const binPath = join(dirname(pkgPath), 'bin', 'node.napi.node');
37
+ return require(binPath);
38
+ } catch {
39
+ // Platform package not installed - fallback to local build
76
40
  }
77
41
 
78
- msg += getPlatformBuildInstructions();
79
-
80
- return msg;
81
- }
82
-
83
- function loadBinding(): unknown {
84
- const errors: Array<{ path: string; error: Error }> = [];
85
-
86
- for (const candidate of candidates) {
87
- try {
88
- if (typeof candidate === 'function') {
89
- const binding = candidate();
90
- if (binding && typeof (binding as Record<string, unknown>).VideoEncoder === 'function') {
91
- return binding;
92
- }
93
- throw new Error('Invalid binding: missing VideoEncoder');
94
- }
95
-
96
- if (!fs.existsSync(candidate)) {
97
- continue;
98
- }
99
-
100
- const binding = require(candidate);
101
- if (typeof binding.VideoEncoder !== 'function') {
102
- throw new Error('Invalid binding: missing VideoEncoder');
103
- }
104
- return binding;
105
- } catch (err) {
106
- errors.push({
107
- path: typeof candidate === 'string' ? candidate : 'dynamic loader',
108
- error: err instanceof Error ? err : new Error(String(err)),
109
- });
110
- }
42
+ // Fallback to node-gyp-build for local development
43
+ try {
44
+ const nodeGypBuild = require('node-gyp-build');
45
+ const rootDir = resolve(__dirname, '..');
46
+ return nodeGypBuild(rootDir);
47
+ } catch (err) {
48
+ throw new Error(
49
+ `Could not load the node-webcodecs native binding for ${platform}.\n` +
50
+ `Error: ${err instanceof Error ? err.message : String(err)}\n\n` +
51
+ `Solutions:\n` +
52
+ ` 1. Install the main package: npm install @pproenca/node-webcodecs\n` +
53
+ ` 2. Build from source: npm rebuild --build-from-source\n` +
54
+ ` 3. Ensure FFmpeg dev libs: pkg-config --exists libavcodec\n`
55
+ );
111
56
  }
112
-
113
- throw new Error(buildHelpMessage(errors));
114
57
  }
115
58
 
116
59
  export const binding = loadBinding();
@@ -118,8 +61,6 @@ export const binding = loadBinding();
118
61
  export const platformInfo = {
119
62
  platform: process.platform,
120
63
  arch: process.arch,
121
- runtimePlatform: runtimePlatformArch(),
122
64
  nodeVersion: process.version,
123
65
  napiVersion: (process.versions as Record<string, string>).napi ?? 'unknown',
124
- prebuiltAvailable: isPrebuiltAvailable(),
125
66
  };
@@ -77,7 +77,6 @@ export class ControlMessageQueue {
77
77
 
78
78
  this.processing = false;
79
79
 
80
- // Continue processing if more messages
81
80
  if (this.queue.length > 0) {
82
81
  this.scheduleProcessing();
83
82
  }
package/lib/demuxer.ts CHANGED
@@ -45,6 +45,16 @@ export class Demuxer {
45
45
  return this._native.demux();
46
46
  }
47
47
 
48
+ /**
49
+ * Read packets from the file in chunks.
50
+ * This is useful for yielding to the event loop during demuxing.
51
+ * @param maxPackets - Maximum number of packets to read. 0 = unlimited (reads all).
52
+ * @returns The number of packets actually read.
53
+ */
54
+ demuxPackets(maxPackets?: number): number {
55
+ return this._native.demuxPackets(maxPackets ?? 0);
56
+ }
57
+
48
58
  close(): void {
49
59
  this._native.close();
50
60
  }
@@ -10,11 +10,42 @@ import type {
10
10
  NativeEncodedVideoChunk,
11
11
  NativeModule,
12
12
  } from './native-types';
13
+ import { detachArrayBuffers } from './transfer';
13
14
  import type { EncodedAudioChunkInit, EncodedVideoChunkInit } from './types';
14
15
 
15
16
  // Load native addon with type assertion
16
17
  const native = binding as NativeModule;
17
18
 
19
+ /**
20
+ * FinalizationRegistry for automatic cleanup of native EncodedVideoChunk objects.
21
+ * When a JS EncodedVideoChunk wrapper becomes unreachable, the registry callback
22
+ * fires and releases the native memory via close().
23
+ *
24
+ * This provides a safety net for users who forget to call close(), preventing
25
+ * memory leaks in high-throughput scenarios where GC may be delayed.
26
+ */
27
+ const videoChunkRegistry = new FinalizationRegistry<NativeEncodedVideoChunk>((native) => {
28
+ // The weak reference to the JS wrapper is now dead, but the native object
29
+ // may still be valid. Call close() to release its internal data buffer.
30
+ // close() is idempotent - safe to call even if already closed.
31
+ try {
32
+ native.close();
33
+ } catch {
34
+ // Ignore errors - native object may already be destroyed
35
+ }
36
+ });
37
+
38
+ /**
39
+ * FinalizationRegistry for automatic cleanup of native EncodedAudioChunk objects.
40
+ */
41
+ const audioChunkRegistry = new FinalizationRegistry<NativeEncodedAudioChunk>((native) => {
42
+ try {
43
+ native.close();
44
+ } catch {
45
+ // Ignore errors - native object may already be destroyed
46
+ }
47
+ });
48
+
18
49
  export class EncodedVideoChunk {
19
50
  /** @internal */
20
51
  _native: NativeEncodedVideoChunk;
@@ -24,7 +55,6 @@ export class EncodedVideoChunk {
24
55
  if (init.type !== 'key' && init.type !== 'delta') {
25
56
  throw new TypeError(`Invalid type: ${init.type}`);
26
57
  }
27
- // Convert BufferSource to Buffer for native
28
58
  let dataBuffer: Buffer;
29
59
  if (init.data instanceof ArrayBuffer) {
30
60
  dataBuffer = Buffer.from(init.data);
@@ -39,6 +69,29 @@ export class EncodedVideoChunk {
39
69
  duration: init.duration,
40
70
  data: dataBuffer,
41
71
  });
72
+
73
+ // Register with FinalizationRegistry for automatic cleanup.
74
+ // When this JS wrapper is GC'd, the registry callback will call close()
75
+ // on the native object to release memory.
76
+ videoChunkRegistry.register(this, this._native, this);
77
+
78
+ // Handle ArrayBuffer transfer semantics per W3C spec
79
+ if (init.transfer && Array.isArray(init.transfer)) {
80
+ detachArrayBuffers(init.transfer.filter((b): b is ArrayBuffer => b instanceof ArrayBuffer));
81
+ }
82
+ }
83
+
84
+ /**
85
+ * @internal
86
+ * Wrap an existing native EncodedVideoChunk without copying data.
87
+ * Used by the encoder's async output path to avoid double-copying.
88
+ */
89
+ static _fromNative(nativeChunk: NativeEncodedVideoChunk): EncodedVideoChunk {
90
+ const chunk = Object.create(EncodedVideoChunk.prototype) as EncodedVideoChunk;
91
+ chunk._native = nativeChunk;
92
+ // Register with FinalizationRegistry for automatic cleanup
93
+ videoChunkRegistry.register(chunk, nativeChunk, chunk);
94
+ return chunk;
42
95
  }
43
96
 
44
97
  get type(): 'key' | 'delta' {
@@ -75,13 +128,28 @@ export class EncodedVideoChunk {
75
128
  throw new TypeError('Destination must be ArrayBuffer or ArrayBufferView');
76
129
  }
77
130
  }
131
+
132
+ /**
133
+ * Releases the internal data buffer.
134
+ * Per W3C WebCodecs spec, this allows early release of memory.
135
+ */
136
+ close(): void {
137
+ // Unregister from FinalizationRegistry to prevent double-close.
138
+ // If close() is called explicitly, we don't need the registry callback.
139
+ videoChunkRegistry.unregister(this);
140
+ this._native.close();
141
+ }
78
142
  }
79
143
 
80
144
  export class EncodedAudioChunk {
81
145
  private _native: NativeEncodedAudioChunk;
82
146
 
83
147
  constructor(init: EncodedAudioChunkInit) {
84
- // Convert BufferSource to Buffer for native
148
+ // W3C spec: type must be 'key' or 'delta'
149
+ if (init.type !== 'key' && init.type !== 'delta') {
150
+ throw new TypeError(`Invalid type: ${init.type}`);
151
+ }
152
+
85
153
  let dataBuffer: Buffer;
86
154
  if (init.data instanceof ArrayBuffer) {
87
155
  dataBuffer = Buffer.from(init.data);
@@ -96,6 +164,16 @@ export class EncodedAudioChunk {
96
164
  duration: init.duration,
97
165
  data: dataBuffer,
98
166
  });
167
+
168
+ // Register with FinalizationRegistry for automatic cleanup.
169
+ // When this JS wrapper is GC'd, the registry callback will call close()
170
+ // on the native object to release memory.
171
+ audioChunkRegistry.register(this, this._native, this);
172
+
173
+ // Handle ArrayBuffer transfer semantics per W3C spec
174
+ if (init.transfer && Array.isArray(init.transfer)) {
175
+ detachArrayBuffers(init.transfer.filter((b): b is ArrayBuffer => b instanceof ArrayBuffer));
176
+ }
99
177
  }
100
178
 
101
179
  get type(): 'key' | 'delta' {
@@ -128,6 +206,16 @@ export class EncodedAudioChunk {
128
206
  }
129
207
  }
130
208
 
209
+ /**
210
+ * Releases the internal data buffer.
211
+ * Per W3C WebCodecs spec, this allows early release of memory.
212
+ */
213
+ close(): void {
214
+ // Unregister from FinalizationRegistry to prevent double-close.
215
+ audioChunkRegistry.unregister(this);
216
+ this._native.close();
217
+ }
218
+
131
219
  get _nativeChunk(): NativeEncodedAudioChunk {
132
220
  return this._native;
133
221
  }
@@ -49,6 +49,7 @@ export class ImageDecoder {
49
49
  this._initOptions = {
50
50
  type: init.type,
51
51
  colorSpaceConversion: init.colorSpaceConversion,
52
+ premultiplyAlpha: init.premultiplyAlpha,
52
53
  desiredWidth: init.desiredWidth,
53
54
  desiredHeight: init.desiredHeight,
54
55
  preferAnimation: init.preferAnimation,
@@ -86,6 +87,7 @@ export class ImageDecoder {
86
87
  type: string;
87
88
  data: Buffer;
88
89
  colorSpaceConversion?: string;
90
+ premultiplyAlpha?: string;
89
91
  desiredWidth?: number;
90
92
  desiredHeight?: number;
91
93
  preferAnimation?: boolean;
@@ -98,6 +100,9 @@ export class ImageDecoder {
98
100
  if ('colorSpaceConversion' in init && init.colorSpaceConversion) {
99
101
  nativeInit.colorSpaceConversion = init.colorSpaceConversion;
100
102
  }
103
+ if ('premultiplyAlpha' in init && init.premultiplyAlpha !== undefined) {
104
+ nativeInit.premultiplyAlpha = init.premultiplyAlpha;
105
+ }
101
106
  if ('desiredWidth' in init && init.desiredWidth !== undefined) {
102
107
  nativeInit.desiredWidth = init.desiredWidth;
103
108
  }
package/lib/index.ts CHANGED
@@ -47,6 +47,9 @@ export const ErrorBuilder = native.ErrorBuilder;
47
47
  export const testAttrAsEnum = native.testAttrAsEnum;
48
48
  export const createEncoderConfigDescriptor = native.createEncoderConfigDescriptor;
49
49
 
50
+ // Export instance counters for monitoring and leak detection
51
+ export const getCounters = native.getCounters;
52
+
50
53
  export type { ErrorCodeType } from './errors';
51
54
  // Re-export error classes and codes
52
55
  export {
@@ -80,6 +80,7 @@ export interface NativeVideoDecoder {
80
80
  readonly state: CodecState;
81
81
  readonly decodeQueueSize: number;
82
82
  readonly codecSaturated: boolean;
83
+ readonly pendingFrames: number;
83
84
 
84
85
  configure(config: VideoDecoderConfig): void;
85
86
  decode(chunk: NativeEncodedVideoChunk): void;
@@ -98,6 +99,7 @@ export interface NativeEncodedVideoChunk {
98
99
  readonly byteLength: number;
99
100
 
100
101
  copyTo(dest: Uint8Array | ArrayBuffer): void;
102
+ close(): void;
101
103
  }
102
104
 
103
105
  /**
@@ -127,6 +129,7 @@ export interface NativeEncodedAudioChunk {
127
129
  readonly byteLength: number;
128
130
 
129
131
  copyTo(dest: Uint8Array | ArrayBuffer): void;
132
+ close(): void;
130
133
  }
131
134
 
132
135
  /**
@@ -173,6 +176,12 @@ export interface NativeVideoFilter {
173
176
  export interface NativeDemuxer {
174
177
  open(path: string): void;
175
178
  demux(): void;
179
+ /**
180
+ * Read packets from the file in chunks.
181
+ * @param maxPackets - Maximum number of packets to read. 0 = unlimited.
182
+ * @returns The number of packets actually read.
183
+ */
184
+ demuxPackets(maxPackets: number): number;
176
185
  close(): void;
177
186
  getVideoTrack(): TrackInfo | null;
178
187
  getAudioTrack(): TrackInfo | null;
@@ -509,6 +518,19 @@ export interface NativeModule {
509
518
  // Test helpers
510
519
  testAttrAsEnum: (obj: object, attr: string) => string;
511
520
 
521
+ // Instance counters for monitoring and leak detection
522
+ getCounters: () => {
523
+ videoFrames: number;
524
+ audioData: number;
525
+ videoEncoders: number;
526
+ videoDecoders: number;
527
+ audioEncoders: number;
528
+ audioDecoders: number;
529
+ queue: number;
530
+ process: number;
531
+ frames: number;
532
+ };
533
+
512
534
  // Descriptor factories
513
535
  createEncoderConfigDescriptor: (config: object) => {
514
536
  codec: string;
package/lib/platform.ts CHANGED
@@ -5,35 +5,11 @@
5
5
 
6
6
  import * as os from 'node:os';
7
7
 
8
- // Try to detect musl vs glibc on Linux
9
- function detectLibc(): 'glibc' | 'musl' | null {
10
- if (os.platform() !== 'linux') return null;
11
-
12
- try {
13
- const { familySync } = require('detect-libc');
14
- return familySync() === 'musl' ? 'musl' : 'glibc';
15
- } catch {
16
- // detect-libc not available, assume glibc
17
- return 'glibc';
18
- }
19
- }
20
-
21
8
  /**
22
9
  * Get the runtime platform-architecture string.
23
- * Handles musl vs glibc distinction on Linux.
24
10
  */
25
11
  export function runtimePlatformArch(): string {
26
- const platform = os.platform();
27
- const arch = os.arch();
28
-
29
- if (platform === 'linux') {
30
- const libc = detectLibc();
31
- if (libc === 'musl') {
32
- return `linuxmusl-${arch}`;
33
- }
34
- }
35
-
36
- return `${platform}-${arch}`;
12
+ return `${os.platform()}-${os.arch()}`;
37
13
  }
38
14
 
39
15
  /**
@@ -57,8 +33,6 @@ export const prebuiltPlatforms = [
57
33
  'darwin-arm64',
58
34
  'darwin-x64',
59
35
  'linux-x64',
60
- 'linuxmusl-x64',
61
- 'win32-x64',
62
36
  ] as const;
63
37
 
64
38
  export type PrebuiltPlatform = (typeof prebuiltPlatforms)[number];
@@ -70,17 +44,3 @@ export function isPrebuiltAvailable(): boolean {
70
44
  const platform = runtimePlatformArch();
71
45
  return prebuiltPlatforms.includes(platform as PrebuiltPlatform);
72
46
  }
73
-
74
- /**
75
- * Get the npm package name for the prebuilt binary.
76
- */
77
- export function getPrebuiltPackageName(): string {
78
- return `@pproenca/node-webcodecs-${runtimePlatformArch()}`;
79
- }
80
-
81
- /**
82
- * Get the npm package name for prebuilt FFmpeg.
83
- */
84
- export function getFFmpegPackageName(): string {
85
- return `@pproenca/ffmpeg-${runtimePlatformArch()}`;
86
- }
@@ -23,10 +23,9 @@ export class ResourceManager {
23
23
  private static instance: ResourceManager | null = null;
24
24
  private codecs: Map<symbol, CodecEntry> = new Map();
25
25
  private inactivityTimeout: number = 10000; // 10 seconds per spec
26
- private checkInterval: ReturnType<typeof setInterval> | null = null;
27
26
 
28
27
  private constructor() {
29
- this.startMonitoring();
28
+ // Monitoring happens on-demand via getReclaimableCodecs()
30
29
  }
31
30
 
32
31
  static getInstance(): ResourceManager {
@@ -132,26 +131,11 @@ export class ResourceManager {
132
131
  this.inactivityTimeout = ms;
133
132
  }
134
133
 
135
- private startMonitoring(): void {
136
- // Check every 5 seconds
137
- this.checkInterval = setInterval(() => {
138
- // Just track, don't auto-reclaim
139
- // Actual reclamation would be triggered by memory pressure
140
- }, 5000);
141
-
142
- // Don't keep process alive
143
- if (this.checkInterval.unref) {
144
- this.checkInterval.unref();
145
- }
146
- }
147
-
148
134
  /**
149
135
  * Stop monitoring (for cleanup).
136
+ * @deprecated No longer needed - monitoring happens on-demand via getReclaimableCodecs()
150
137
  */
151
138
  stopMonitoring(): void {
152
- if (this.checkInterval) {
153
- clearInterval(this.checkInterval);
154
- this.checkInterval = null;
155
- }
139
+ // No-op: monitoring now happens on-demand
156
140
  }
157
141
  }
package/lib/types.ts CHANGED
@@ -82,6 +82,17 @@ export type HardwareAcceleration = 'no-preference' | 'prefer-hardware' | 'prefer
82
82
  */
83
83
  export type AlphaOption = 'keep' | 'discard';
84
84
 
85
+ /**
86
+ * WebIDL:
87
+ * enum PremultiplyAlpha { "none", "premultiply", "default" };
88
+ *
89
+ * Per W3C WebCodecs spec:
90
+ * - "none": Do not premultiply alpha
91
+ * - "premultiply": Premultiply RGB values by alpha
92
+ * - "default": Use default behavior (typically none)
93
+ */
94
+ export type PremultiplyAlpha = 'none' | 'premultiply' | 'default';
95
+
85
96
  // =============================================================================
86
97
  // LATENCY MODE
87
98
  // =============================================================================
@@ -816,6 +827,7 @@ export type ImageBufferSource = AllowSharedBufferSource | ReadableStream<Uint8Ar
816
827
  * required DOMString type;
817
828
  * required ImageBufferSource data;
818
829
  * ColorSpaceConversion colorSpaceConversion = "default";
830
+ * PremultiplyAlpha premultiplyAlpha = "default";
819
831
  * [EnforceRange] unsigned long desiredWidth;
820
832
  * [EnforceRange] unsigned long desiredHeight;
821
833
  * boolean preferAnimation;
@@ -826,6 +838,7 @@ export interface ImageDecoderInit {
826
838
  type: string;
827
839
  data: ImageBufferSource;
828
840
  colorSpaceConversion?: ColorSpaceConversion;
841
+ premultiplyAlpha?: PremultiplyAlpha;
829
842
  desiredWidth?: number;
830
843
  desiredHeight?: number;
831
844
  preferAnimation?: boolean;