webcodecs-node 0.5.2 → 0.7.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.
Files changed (144) hide show
  1. package/README.md +279 -0
  2. package/dist/backends/types.d.ts +2 -0
  3. package/dist/backends/types.d.ts.map +1 -1
  4. package/dist/backends/types.js.map +1 -1
  5. package/dist/canvas/canvas-utils.d.ts +115 -0
  6. package/dist/canvas/canvas-utils.d.ts.map +1 -0
  7. package/dist/canvas/canvas-utils.js +169 -0
  8. package/dist/canvas/canvas-utils.js.map +1 -0
  9. package/dist/canvas/frame-loop.d.ts +113 -0
  10. package/dist/canvas/frame-loop.d.ts.map +1 -0
  11. package/dist/canvas/frame-loop.js +291 -0
  12. package/dist/canvas/frame-loop.js.map +1 -0
  13. package/dist/canvas/gpu-context.d.ts +61 -0
  14. package/dist/canvas/gpu-context.d.ts.map +1 -0
  15. package/dist/canvas/gpu-context.js +134 -0
  16. package/dist/canvas/gpu-context.js.map +1 -0
  17. package/dist/canvas/index.d.ts +22 -0
  18. package/dist/canvas/index.d.ts.map +1 -0
  19. package/dist/canvas/index.js +25 -0
  20. package/dist/canvas/index.js.map +1 -0
  21. package/dist/canvas/types.d.ts +101 -0
  22. package/dist/canvas/types.d.ts.map +1 -0
  23. package/dist/canvas/types.js +7 -0
  24. package/dist/canvas/types.js.map +1 -0
  25. package/dist/codec-utils/formats.d.ts.map +1 -1
  26. package/dist/codec-utils/formats.js +31 -0
  27. package/dist/codec-utils/formats.js.map +1 -1
  28. package/dist/config/ffmpeg-quality.d.ts +14 -0
  29. package/dist/config/ffmpeg-quality.d.ts.map +1 -0
  30. package/dist/config/ffmpeg-quality.js +41 -0
  31. package/dist/config/ffmpeg-quality.js.map +1 -0
  32. package/dist/containers/Muxer.d.ts.map +1 -1
  33. package/dist/containers/Muxer.js +4 -1
  34. package/dist/containers/Muxer.js.map +1 -1
  35. package/dist/core/AudioData.d.ts +2 -1
  36. package/dist/core/AudioData.d.ts.map +1 -1
  37. package/dist/core/AudioData.js.map +1 -1
  38. package/dist/core/VideoFrame.d.ts +2 -2
  39. package/dist/core/VideoFrame.d.ts.map +1 -1
  40. package/dist/core/VideoFrame.js +49 -47
  41. package/dist/core/VideoFrame.js.map +1 -1
  42. package/dist/decoders/AudioDecoder.d.ts +1 -0
  43. package/dist/decoders/AudioDecoder.d.ts.map +1 -1
  44. package/dist/decoders/AudioDecoder.js +39 -17
  45. package/dist/decoders/AudioDecoder.js.map +1 -1
  46. package/dist/decoders/ImageDecoder.d.ts +1 -0
  47. package/dist/decoders/ImageDecoder.d.ts.map +1 -1
  48. package/dist/decoders/ImageDecoder.js +40 -3
  49. package/dist/decoders/ImageDecoder.js.map +1 -1
  50. package/dist/decoders/VideoDecoder.d.ts +12 -0
  51. package/dist/decoders/VideoDecoder.d.ts.map +1 -1
  52. package/dist/decoders/VideoDecoder.js +50 -8
  53. package/dist/decoders/VideoDecoder.js.map +1 -1
  54. package/dist/demos/demo-1080p-transcode.js +8 -2
  55. package/dist/demos/demo-1080p-transcode.js.map +1 -1
  56. package/dist/demos/demo-audio-visualizer.d.ts +11 -0
  57. package/dist/demos/demo-audio-visualizer.d.ts.map +1 -0
  58. package/dist/demos/demo-audio-visualizer.js +281 -0
  59. package/dist/demos/demo-audio-visualizer.js.map +1 -0
  60. package/dist/demos/demo-dvd-logo.d.ts +8 -0
  61. package/dist/demos/demo-dvd-logo.d.ts.map +1 -0
  62. package/dist/demos/demo-dvd-logo.js +196 -0
  63. package/dist/demos/demo-dvd-logo.js.map +1 -0
  64. package/dist/demos/demo-four-corners.js +9 -0
  65. package/dist/demos/demo-four-corners.js.map +1 -1
  66. package/dist/demos/demo-streaming.js +6 -0
  67. package/dist/demos/demo-streaming.js.map +1 -1
  68. package/dist/demos/demo-webcodecs.js +1 -0
  69. package/dist/demos/demo-webcodecs.js.map +1 -1
  70. package/dist/encoders/AudioEncoder.d.ts +3 -0
  71. package/dist/encoders/AudioEncoder.d.ts.map +1 -1
  72. package/dist/encoders/AudioEncoder.js +70 -28
  73. package/dist/encoders/AudioEncoder.js.map +1 -1
  74. package/dist/encoders/ImageEncoder.d.ts +80 -0
  75. package/dist/encoders/ImageEncoder.d.ts.map +1 -0
  76. package/dist/encoders/ImageEncoder.js +156 -0
  77. package/dist/encoders/ImageEncoder.js.map +1 -0
  78. package/dist/encoders/VideoEncoder.d.ts +11 -0
  79. package/dist/encoders/VideoEncoder.d.ts.map +1 -1
  80. package/dist/encoders/VideoEncoder.js +46 -4
  81. package/dist/encoders/VideoEncoder.js.map +1 -1
  82. package/dist/encoders/index.d.ts +1 -0
  83. package/dist/encoders/index.d.ts.map +1 -1
  84. package/dist/encoders/index.js +1 -0
  85. package/dist/encoders/index.js.map +1 -1
  86. package/dist/formats/color-space.d.ts +88 -0
  87. package/dist/formats/color-space.d.ts.map +1 -1
  88. package/dist/formats/color-space.js +55 -1
  89. package/dist/formats/color-space.js.map +1 -1
  90. package/dist/formats/pixel-formats.d.ts +17 -1
  91. package/dist/formats/pixel-formats.d.ts.map +1 -1
  92. package/dist/formats/pixel-formats.js +74 -4
  93. package/dist/formats/pixel-formats.js.map +1 -1
  94. package/dist/hardware/detection.d.ts.map +1 -1
  95. package/dist/hardware/detection.js +5 -2
  96. package/dist/hardware/detection.js.map +1 -1
  97. package/dist/hardware/encoder-args.d.ts.map +1 -1
  98. package/dist/hardware/encoder-args.js +6 -6
  99. package/dist/hardware/encoder-args.js.map +1 -1
  100. package/dist/index.d.ts +11 -3
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +8 -2
  103. package/dist/index.js.map +1 -1
  104. package/dist/node-av/NodeAvAudioDecoder.d.ts.map +1 -1
  105. package/dist/node-av/NodeAvAudioDecoder.js +3 -0
  106. package/dist/node-av/NodeAvAudioDecoder.js.map +1 -1
  107. package/dist/node-av/NodeAvAudioEncoder.d.ts +5 -0
  108. package/dist/node-av/NodeAvAudioEncoder.d.ts.map +1 -1
  109. package/dist/node-av/NodeAvAudioEncoder.js +102 -9
  110. package/dist/node-av/NodeAvAudioEncoder.js.map +1 -1
  111. package/dist/node-av/NodeAvVideoEncoder.d.ts +2 -0
  112. package/dist/node-av/NodeAvVideoEncoder.d.ts.map +1 -1
  113. package/dist/node-av/NodeAvVideoEncoder.js +44 -6
  114. package/dist/node-av/NodeAvVideoEncoder.js.map +1 -1
  115. package/dist/node-av/WebPImageDecoder.d.ts +54 -0
  116. package/dist/node-av/WebPImageDecoder.d.ts.map +1 -0
  117. package/dist/node-av/WebPImageDecoder.js +176 -0
  118. package/dist/node-av/WebPImageDecoder.js.map +1 -0
  119. package/dist/polyfills/OffscreenCanvas.d.ts +141 -7
  120. package/dist/polyfills/OffscreenCanvas.d.ts.map +1 -1
  121. package/dist/polyfills/OffscreenCanvas.js +217 -17
  122. package/dist/polyfills/OffscreenCanvas.js.map +1 -1
  123. package/dist/types/index.d.ts +1 -0
  124. package/dist/types/index.d.ts.map +1 -1
  125. package/dist/types/index.js +2 -0
  126. package/dist/types/index.js.map +1 -1
  127. package/dist/types/native-frame.d.ts +74 -0
  128. package/dist/types/native-frame.d.ts.map +1 -0
  129. package/dist/types/native-frame.js +32 -0
  130. package/dist/types/native-frame.js.map +1 -0
  131. package/dist/utils/index.d.ts +1 -1
  132. package/dist/utils/index.d.ts.map +1 -1
  133. package/dist/utils/index.js +1 -1
  134. package/dist/utils/index.js.map +1 -1
  135. package/dist/utils/type-guards.d.ts +29 -1
  136. package/dist/utils/type-guards.d.ts.map +1 -1
  137. package/dist/utils/type-guards.js +47 -2
  138. package/dist/utils/type-guards.js.map +1 -1
  139. package/docs/api.md +155 -0
  140. package/docs/configuration.md +27 -0
  141. package/examples/README.md +28 -1
  142. package/examples/canvas-encoding.ts +222 -0
  143. package/examples/offscreen-canvas.ts +230 -0
  144. package/package.json +9 -3
@@ -0,0 +1,113 @@
1
+ /**
2
+ * FrameLoop - High-performance frame generation with backpressure
3
+ *
4
+ * Manages frame generation with:
5
+ * - Backpressure via maxQueueSize (default: 8)
6
+ * - Automatic canvas reset before each frame
7
+ * - Proper VideoFrame lifecycle management
8
+ * - Async pipelining for optimal throughput
9
+ *
10
+ * Best practices implemented:
11
+ * 1. Memory lifecycle: Explicit frame closing with try...finally
12
+ * 2. Canvas state: ctx.reset() or clearRect at start of every frame
13
+ * 3. Color formats: RGBA output, even dimensions for YUV420
14
+ * 4. Pipeline optimization: Async pipelining with backpressure
15
+ * 5. Raw buffer export: Always uses toBuffer('raw')
16
+ */
17
+ import { Canvas } from 'skia-canvas';
18
+ import { VideoFrame } from '../core/VideoFrame.js';
19
+ import type { FrameLoopConfig, FrameLoopState } from './types.js';
20
+ type SkiaContext = any;
21
+ /**
22
+ * FrameLoop class for generating video frames with backpressure control
23
+ */
24
+ export declare class FrameLoop {
25
+ private canvas;
26
+ private ctx;
27
+ private config;
28
+ private state;
29
+ private frameIndex;
30
+ private queueSize;
31
+ private pendingFrames;
32
+ private resolveWaitForDrain;
33
+ private startTime;
34
+ private frameDurationUs;
35
+ constructor(config: FrameLoopConfig);
36
+ /**
37
+ * Get current state
38
+ */
39
+ getState(): FrameLoopState;
40
+ /**
41
+ * Get current queue size
42
+ */
43
+ getQueueSize(): number;
44
+ /**
45
+ * Get canvas width
46
+ */
47
+ getWidth(): number;
48
+ /**
49
+ * Get canvas height
50
+ */
51
+ getHeight(): number;
52
+ /**
53
+ * Get the underlying canvas for direct access
54
+ */
55
+ getCanvas(): Canvas;
56
+ /**
57
+ * Get the 2D context for direct access
58
+ */
59
+ getContext(): SkiaContext;
60
+ /**
61
+ * Start frame generation
62
+ *
63
+ * @param totalFrames - Total number of frames to generate (Infinity for continuous)
64
+ */
65
+ start(totalFrames?: number): Promise<void>;
66
+ /**
67
+ * Stop frame generation immediately
68
+ */
69
+ stop(): void;
70
+ /**
71
+ * Pause frame generation
72
+ */
73
+ pause(): void;
74
+ /**
75
+ * Resume frame generation
76
+ */
77
+ resume(): void;
78
+ /**
79
+ * Get the next frame from the loop
80
+ *
81
+ * Called by the encoder to consume frames. The caller takes ownership
82
+ * of the frame and MUST call frame.close() when done.
83
+ *
84
+ * @returns The next VideoFrame, or null if none available
85
+ */
86
+ takeFrame(): VideoFrame | null;
87
+ /**
88
+ * Signal that a frame has been consumed externally
89
+ *
90
+ * Use this when frames are consumed outside of takeFrame(),
91
+ * to maintain correct backpressure accounting.
92
+ */
93
+ frameConsumed(): void;
94
+ /**
95
+ * Check if there are frames available
96
+ */
97
+ hasFrames(): boolean;
98
+ /**
99
+ * Get the current frame index
100
+ */
101
+ getCurrentFrameIndex(): number;
102
+ private generateFrame;
103
+ private waitForDrain;
104
+ }
105
+ /**
106
+ * Create a FrameLoop with the given configuration
107
+ *
108
+ * @param config - FrameLoop configuration
109
+ * @returns A new FrameLoop instance
110
+ */
111
+ export declare function createFrameLoop(config: FrameLoopConfig): FrameLoop;
112
+ export {};
113
+ //# sourceMappingURL=frame-loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-loop.d.ts","sourceRoot":"","sources":["../../src/canvas/frame-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAInD,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EACf,MAAM,YAAY,CAAC;AAOpB,KAAK,WAAW,GAAG,GAAG,CAAC;AAEvB;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,GAAG,CAAc;IACzB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,mBAAmB,CAA6B;IAGxD,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,eAAe,CAAS;gBAEpB,MAAM,EAAE,eAAe;IAmCnC;;OAEG;IACH,QAAQ,IAAI,cAAc;IAI1B;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,QAAQ,IAAI,MAAM;IAIlB;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,UAAU,IAAI,WAAW;IAIzB;;;;OAIG;IACG,KAAK,CAAC,WAAW,GAAE,MAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwC1D;;OAEG;IACH,IAAI,IAAI,IAAI;IAqBZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,MAAM,IAAI,IAAI;IAWd;;;;;;;OAOG;IACH,SAAS,IAAI,UAAU,GAAG,IAAI;IAgB9B;;;;;OAKG;IACH,aAAa,IAAI,IAAI;IAWrB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,oBAAoB,IAAI,MAAM;YAQhB,aAAa;IA6C3B,OAAO,CAAC,YAAY;CAKrB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAElE"}
@@ -0,0 +1,291 @@
1
+ /**
2
+ * FrameLoop - High-performance frame generation with backpressure
3
+ *
4
+ * Manages frame generation with:
5
+ * - Backpressure via maxQueueSize (default: 8)
6
+ * - Automatic canvas reset before each frame
7
+ * - Proper VideoFrame lifecycle management
8
+ * - Async pipelining for optimal throughput
9
+ *
10
+ * Best practices implemented:
11
+ * 1. Memory lifecycle: Explicit frame closing with try...finally
12
+ * 2. Canvas state: ctx.reset() or clearRect at start of every frame
13
+ * 3. Color formats: RGBA output, even dimensions for YUV420
14
+ * 4. Pipeline optimization: Async pipelining with backpressure
15
+ * 5. Raw buffer export: Always uses toBuffer('raw')
16
+ */
17
+ import { VideoFrame } from '../core/VideoFrame.js';
18
+ import { createLogger } from '../utils/logger.js';
19
+ import { createCanvas, ensureEvenDimensions } from './gpu-context.js';
20
+ import { getRawPixels, resetCanvas, bufferToUint8Array } from './canvas-utils.js';
21
+ const logger = createLogger('FrameLoop');
22
+ const DEFAULT_MAX_QUEUE_SIZE = 8;
23
+ /**
24
+ * FrameLoop class for generating video frames with backpressure control
25
+ */
26
+ export class FrameLoop {
27
+ canvas;
28
+ ctx;
29
+ config;
30
+ state = 'idle';
31
+ frameIndex = 0;
32
+ queueSize = 0;
33
+ pendingFrames = [];
34
+ resolveWaitForDrain = null;
35
+ // Frame timing
36
+ startTime = 0;
37
+ frameDurationUs;
38
+ constructor(config) {
39
+ // Ensure even dimensions for YUV420 compatibility
40
+ const dims = ensureEvenDimensions(config.width, config.height);
41
+ this.config = {
42
+ width: dims.width,
43
+ height: dims.height,
44
+ frameRate: config.frameRate,
45
+ maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
46
+ gpu: config.gpu ?? true,
47
+ onFrame: config.onFrame,
48
+ onComplete: config.onComplete ?? (() => { }),
49
+ onError: config.onError ?? (() => { }),
50
+ };
51
+ // Frame duration in microseconds
52
+ this.frameDurationUs = Math.round(1_000_000 / this.config.frameRate);
53
+ // Create GPU-accelerated canvas
54
+ this.canvas = createCanvas({
55
+ width: dims.width,
56
+ height: dims.height,
57
+ gpu: config.gpu,
58
+ });
59
+ this.ctx = this.canvas.getContext('2d');
60
+ // Log creation info (engine property may not be available until first render)
61
+ const engineInfo = this.canvas.engine;
62
+ logger.info(`FrameLoop created: ${dims.width}x${dims.height} @ ${config.frameRate}fps` +
63
+ (engineInfo ? `, GPU: ${engineInfo.renderer}` : ''));
64
+ }
65
+ /**
66
+ * Get current state
67
+ */
68
+ getState() {
69
+ return this.state;
70
+ }
71
+ /**
72
+ * Get current queue size
73
+ */
74
+ getQueueSize() {
75
+ return this.queueSize;
76
+ }
77
+ /**
78
+ * Get canvas width
79
+ */
80
+ getWidth() {
81
+ return this.config.width;
82
+ }
83
+ /**
84
+ * Get canvas height
85
+ */
86
+ getHeight() {
87
+ return this.config.height;
88
+ }
89
+ /**
90
+ * Get the underlying canvas for direct access
91
+ */
92
+ getCanvas() {
93
+ return this.canvas;
94
+ }
95
+ /**
96
+ * Get the 2D context for direct access
97
+ */
98
+ getContext() {
99
+ return this.ctx;
100
+ }
101
+ /**
102
+ * Start frame generation
103
+ *
104
+ * @param totalFrames - Total number of frames to generate (Infinity for continuous)
105
+ */
106
+ async start(totalFrames = Infinity) {
107
+ if (this.state === 'running') {
108
+ throw new Error('FrameLoop is already running');
109
+ }
110
+ this.state = 'running';
111
+ this.startTime = performance.now();
112
+ this.frameIndex = 0;
113
+ try {
114
+ while (this.state === 'running' && this.frameIndex < totalFrames) {
115
+ // Backpressure: wait if queue is full
116
+ if (this.queueSize >= this.config.maxQueueSize) {
117
+ logger.debug(`Backpressure: waiting (queue: ${this.queueSize})`);
118
+ await this.waitForDrain();
119
+ if (this.state !== 'running')
120
+ break;
121
+ }
122
+ // Generate frame
123
+ await this.generateFrame();
124
+ this.frameIndex++;
125
+ }
126
+ // Wait for all pending frames to be consumed
127
+ while (this.pendingFrames.length > 0 && this.getState() !== 'stopped') {
128
+ await this.waitForDrain();
129
+ }
130
+ if (this.getState() !== 'stopped') {
131
+ this.state = 'stopped';
132
+ this.config.onComplete();
133
+ }
134
+ }
135
+ catch (error) {
136
+ this.state = 'stopped';
137
+ const err = error instanceof Error ? error : new Error(String(error));
138
+ this.config.onError(err);
139
+ throw err;
140
+ }
141
+ }
142
+ /**
143
+ * Stop frame generation immediately
144
+ */
145
+ stop() {
146
+ this.state = 'stopped';
147
+ // Close any pending frames
148
+ for (const frame of this.pendingFrames) {
149
+ try {
150
+ frame.close();
151
+ }
152
+ catch {
153
+ // Ignore close errors
154
+ }
155
+ }
156
+ this.pendingFrames = [];
157
+ this.queueSize = 0;
158
+ // Unblock any waiting drain
159
+ if (this.resolveWaitForDrain) {
160
+ this.resolveWaitForDrain();
161
+ this.resolveWaitForDrain = null;
162
+ }
163
+ }
164
+ /**
165
+ * Pause frame generation
166
+ */
167
+ pause() {
168
+ if (this.state === 'running') {
169
+ this.state = 'paused';
170
+ }
171
+ }
172
+ /**
173
+ * Resume frame generation
174
+ */
175
+ resume() {
176
+ if (this.state === 'paused') {
177
+ this.state = 'running';
178
+ // Unblock any waiting drain
179
+ if (this.resolveWaitForDrain) {
180
+ this.resolveWaitForDrain();
181
+ this.resolveWaitForDrain = null;
182
+ }
183
+ }
184
+ }
185
+ /**
186
+ * Get the next frame from the loop
187
+ *
188
+ * Called by the encoder to consume frames. The caller takes ownership
189
+ * of the frame and MUST call frame.close() when done.
190
+ *
191
+ * @returns The next VideoFrame, or null if none available
192
+ */
193
+ takeFrame() {
194
+ const frame = this.pendingFrames.shift();
195
+ if (frame) {
196
+ this.queueSize--;
197
+ // Notify that there's room for more frames
198
+ if (this.resolveWaitForDrain &&
199
+ this.queueSize < this.config.maxQueueSize) {
200
+ this.resolveWaitForDrain();
201
+ this.resolveWaitForDrain = null;
202
+ }
203
+ }
204
+ return frame ?? null;
205
+ }
206
+ /**
207
+ * Signal that a frame has been consumed externally
208
+ *
209
+ * Use this when frames are consumed outside of takeFrame(),
210
+ * to maintain correct backpressure accounting.
211
+ */
212
+ frameConsumed() {
213
+ this.queueSize = Math.max(0, this.queueSize - 1);
214
+ if (this.resolveWaitForDrain &&
215
+ this.queueSize < this.config.maxQueueSize) {
216
+ this.resolveWaitForDrain();
217
+ this.resolveWaitForDrain = null;
218
+ }
219
+ }
220
+ /**
221
+ * Check if there are frames available
222
+ */
223
+ hasFrames() {
224
+ return this.pendingFrames.length > 0;
225
+ }
226
+ /**
227
+ * Get the current frame index
228
+ */
229
+ getCurrentFrameIndex() {
230
+ return this.frameIndex;
231
+ }
232
+ // ─────────────────────────────────────────────────────────────────────────────
233
+ // Private methods
234
+ // ─────────────────────────────────────────────────────────────────────────────
235
+ async generateFrame() {
236
+ const timing = {
237
+ frameIndex: this.frameIndex,
238
+ timestamp: this.frameIndex * this.frameDurationUs,
239
+ duration: this.frameDurationUs,
240
+ presentationTime: performance.now() - this.startTime,
241
+ };
242
+ // Best practice: Reset canvas at start of every frame
243
+ // This prevents Skia command history buildup
244
+ resetCanvas(this.ctx);
245
+ // Call user's frame rendering function
246
+ await this.config.onFrame(this.ctx, timing);
247
+ // Get raw RGBA pixels (never PNG!)
248
+ const pixels = getRawPixels(this.canvas);
249
+ const pixelArray = bufferToUint8Array(pixels);
250
+ // Create VideoFrame with proper lifecycle
251
+ let frame = null;
252
+ try {
253
+ frame = new VideoFrame(new Uint8Array(pixelArray), {
254
+ format: 'RGBA',
255
+ codedWidth: this.config.width,
256
+ codedHeight: this.config.height,
257
+ timestamp: timing.timestamp,
258
+ duration: timing.duration,
259
+ });
260
+ this.pendingFrames.push(frame);
261
+ this.queueSize++;
262
+ }
263
+ catch (error) {
264
+ // Best practice: Close frame on error
265
+ if (frame) {
266
+ try {
267
+ frame.close();
268
+ }
269
+ catch {
270
+ // Ignore close errors
271
+ }
272
+ }
273
+ throw error;
274
+ }
275
+ }
276
+ waitForDrain() {
277
+ return new Promise((resolve) => {
278
+ this.resolveWaitForDrain = resolve;
279
+ });
280
+ }
281
+ }
282
+ /**
283
+ * Create a FrameLoop with the given configuration
284
+ *
285
+ * @param config - FrameLoop configuration
286
+ * @returns A new FrameLoop instance
287
+ */
288
+ export function createFrameLoop(config) {
289
+ return new FrameLoop(config);
290
+ }
291
+ //# sourceMappingURL=frame-loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-loop.js","sourceRoot":"","sources":["../../src/canvas/frame-loop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * GPU Context Management for skia-canvas
3
+ *
4
+ * Provides GPU detection and canvas factory with auto-detection
5
+ * for Metal (macOS), Vulkan (Linux/Windows), and D3D (Windows).
6
+ */
7
+ import { Canvas } from 'skia-canvas';
8
+ import type { GpuEngineInfo, CanvasConfig } from './types.js';
9
+ /**
10
+ * Detect available GPU acceleration
11
+ * Uses a minimal probe canvas to check GPU availability.
12
+ * Results are cached for performance.
13
+ */
14
+ export declare function detectGpuAcceleration(): GpuEngineInfo;
15
+ /**
16
+ * Check if GPU acceleration is available
17
+ */
18
+ export declare function isGpuAvailable(): boolean;
19
+ /**
20
+ * Get the GPU API name (Metal, Vulkan, D3D, or null for CPU)
21
+ */
22
+ export declare function getGpuApi(): 'Metal' | 'Vulkan' | 'D3D' | null;
23
+ /**
24
+ * Create a canvas with optimal GPU settings
25
+ *
26
+ * @param config - Canvas configuration
27
+ * @returns A new skia-canvas Canvas instance
28
+ */
29
+ export declare function createCanvas(config: CanvasConfig): Canvas;
30
+ /**
31
+ * Ensure dimensions are even (required for YUV420 compatibility)
32
+ *
33
+ * Most video codecs (H.264, HEVC, VP9) require even dimensions
34
+ * for chroma subsampling. This utility rounds up odd dimensions.
35
+ *
36
+ * @param width - Original width
37
+ * @param height - Original height
38
+ * @returns Dimensions rounded up to even values
39
+ */
40
+ export declare function ensureEvenDimensions(width: number, height: number): {
41
+ width: number;
42
+ height: number;
43
+ };
44
+ /**
45
+ * Validate that dimensions are even (throws if not)
46
+ *
47
+ * Many hardware encoders (NVENC, QuickSync, VideoToolbox) will fail
48
+ * silently or crash if dimensions are not divisible by 2. Use this
49
+ * for strict validation in encoder configuration.
50
+ *
51
+ * @param width - Width to validate
52
+ * @param height - Height to validate
53
+ * @throws Error if width or height is odd
54
+ */
55
+ export declare function validateEvenDimensions(width: number, height: number): void;
56
+ /**
57
+ * Reset the GPU detection cache
58
+ * Useful for testing or when GPU availability may have changed
59
+ */
60
+ export declare function resetGpuCache(): void;
61
+ //# sourceMappingURL=gpu-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu-context.d.ts","sourceRoot":"","sources":["../../src/canvas/gpu-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQ9D;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,aAAa,CAyCrD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAGxC;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,IAAI,CAG7D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAYzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAKnC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAiB1E;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAGpC"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * GPU Context Management for skia-canvas
3
+ *
4
+ * Provides GPU detection and canvas factory with auto-detection
5
+ * for Metal (macOS), Vulkan (Linux/Windows), and D3D (Windows).
6
+ */
7
+ import { Canvas } from 'skia-canvas';
8
+ import { createLogger } from '../utils/logger.js';
9
+ const logger = createLogger('GpuContext');
10
+ // Cached GPU availability result
11
+ let gpuAvailability = null;
12
+ let gpuCheckPerformed = false;
13
+ /**
14
+ * Detect available GPU acceleration
15
+ * Uses a minimal probe canvas to check GPU availability.
16
+ * Results are cached for performance.
17
+ */
18
+ export function detectGpuAcceleration() {
19
+ if (gpuCheckPerformed && gpuAvailability) {
20
+ return gpuAvailability;
21
+ }
22
+ try {
23
+ // Create minimal probe canvas
24
+ const probe = new Canvas(1, 1);
25
+ probe.gpu = true; // Request GPU
26
+ // Access engine info to trigger GPU initialization
27
+ const engine = probe.engine;
28
+ gpuAvailability = {
29
+ renderer: engine.renderer === 'GPU' ? 'GPU' : 'CPU',
30
+ api: engine.api,
31
+ device: engine.device,
32
+ driver: engine.driver,
33
+ threads: engine.threads,
34
+ error: engine.error,
35
+ };
36
+ gpuCheckPerformed = true;
37
+ logger.info(`GPU Detection: ${engine.renderer}${engine.api ? ` (${engine.api})` : ''}`);
38
+ if (engine.error) {
39
+ logger.warn(`GPU fallback reason: ${engine.error}`);
40
+ }
41
+ return gpuAvailability;
42
+ }
43
+ catch (error) {
44
+ gpuAvailability = {
45
+ renderer: 'CPU',
46
+ error: error instanceof Error ? error.message : String(error),
47
+ };
48
+ gpuCheckPerformed = true;
49
+ logger.warn('GPU detection failed, using CPU rendering');
50
+ return gpuAvailability;
51
+ }
52
+ }
53
+ /**
54
+ * Check if GPU acceleration is available
55
+ */
56
+ export function isGpuAvailable() {
57
+ const info = detectGpuAcceleration();
58
+ return info.renderer === 'GPU';
59
+ }
60
+ /**
61
+ * Get the GPU API name (Metal, Vulkan, D3D, or null for CPU)
62
+ */
63
+ export function getGpuApi() {
64
+ const info = detectGpuAcceleration();
65
+ return info.api ?? null;
66
+ }
67
+ /**
68
+ * Create a canvas with optimal GPU settings
69
+ *
70
+ * @param config - Canvas configuration
71
+ * @returns A new skia-canvas Canvas instance
72
+ */
73
+ export function createCanvas(config) {
74
+ const canvas = new Canvas(config.width, config.height);
75
+ // Set GPU preference (auto-detect if not specified)
76
+ if (config.gpu !== undefined) {
77
+ canvas.gpu = config.gpu;
78
+ }
79
+ else {
80
+ // Auto-detect: use GPU if available
81
+ canvas.gpu = isGpuAvailable();
82
+ }
83
+ return canvas;
84
+ }
85
+ /**
86
+ * Ensure dimensions are even (required for YUV420 compatibility)
87
+ *
88
+ * Most video codecs (H.264, HEVC, VP9) require even dimensions
89
+ * for chroma subsampling. This utility rounds up odd dimensions.
90
+ *
91
+ * @param width - Original width
92
+ * @param height - Original height
93
+ * @returns Dimensions rounded up to even values
94
+ */
95
+ export function ensureEvenDimensions(width, height) {
96
+ return {
97
+ width: width % 2 === 0 ? width : width + 1,
98
+ height: height % 2 === 0 ? height : height + 1,
99
+ };
100
+ }
101
+ /**
102
+ * Validate that dimensions are even (throws if not)
103
+ *
104
+ * Many hardware encoders (NVENC, QuickSync, VideoToolbox) will fail
105
+ * silently or crash if dimensions are not divisible by 2. Use this
106
+ * for strict validation in encoder configuration.
107
+ *
108
+ * @param width - Width to validate
109
+ * @param height - Height to validate
110
+ * @throws Error if width or height is odd
111
+ */
112
+ export function validateEvenDimensions(width, height) {
113
+ const errors = [];
114
+ if (width % 2 !== 0) {
115
+ errors.push(`width (${width}) must be even for video encoding`);
116
+ }
117
+ if (height % 2 !== 0) {
118
+ errors.push(`height (${height}) must be even for video encoding`);
119
+ }
120
+ if (errors.length > 0) {
121
+ throw new Error(`Invalid dimensions: ${errors.join(', ')}. ` +
122
+ `Hardware encoders require even dimensions for YUV420 chroma subsampling. ` +
123
+ `Use ensureEvenDimensions() to auto-fix, or adjust your source dimensions.`);
124
+ }
125
+ }
126
+ /**
127
+ * Reset the GPU detection cache
128
+ * Useful for testing or when GPU availability may have changed
129
+ */
130
+ export function resetGpuCache() {
131
+ gpuAvailability = null;
132
+ gpuCheckPerformed = false;
133
+ }
134
+ //# sourceMappingURL=gpu-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu-context.js","sourceRoot":"","sources":["../../src/canvas/gpu-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Canvas Module - skia-canvas integration for WebCodecs
3
+ *
4
+ * Provides GPU-accelerated canvas operations using skia-canvas:
5
+ * - Metal acceleration on macOS
6
+ * - Vulkan acceleration on Linux/Windows
7
+ * - D3D acceleration on Windows
8
+ * - Automatic CPU fallback
9
+ *
10
+ * Best practices implemented:
11
+ * - Memory lifecycle with explicit frame closing
12
+ * - Canvas state reset before each frame
13
+ * - RGBA raw buffer export (never PNG)
14
+ * - Even dimensions for YUV420 compatibility
15
+ * - Backpressure with configurable queue size
16
+ */
17
+ export { Canvas, loadImage, FontLibrary } from 'skia-canvas';
18
+ export { detectGpuAcceleration, isGpuAvailable, getGpuApi, createCanvas, ensureEvenDimensions, validateEvenDimensions, resetGpuCache, } from './gpu-context.js';
19
+ export { createPixelBuffer, createPixelBufferWithColor, getRawPixels, getRawPixelsAsync, resetCanvas, pixelsToImageData, drawPixelsToCanvas, bufferToUint8Array, resizePixels, } from './canvas-utils.js';
20
+ export { FrameLoop, createFrameLoop } from './frame-loop.js';
21
+ export type { GpuEngineInfo, CanvasConfig, FrameTiming, FrameLoopConfig, FrameLoopState, RawBufferOptions, FrameCallback, SkiaCanvas, SkiaEngineInfo, } from './types.js';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG7D,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,SAAS,EACT,YAAY,EACZ,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAG7D,YAAY,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,cAAc,GACf,MAAM,YAAY,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Canvas Module - skia-canvas integration for WebCodecs
3
+ *
4
+ * Provides GPU-accelerated canvas operations using skia-canvas:
5
+ * - Metal acceleration on macOS
6
+ * - Vulkan acceleration on Linux/Windows
7
+ * - D3D acceleration on Windows
8
+ * - Automatic CPU fallback
9
+ *
10
+ * Best practices implemented:
11
+ * - Memory lifecycle with explicit frame closing
12
+ * - Canvas state reset before each frame
13
+ * - RGBA raw buffer export (never PNG)
14
+ * - Even dimensions for YUV420 compatibility
15
+ * - Backpressure with configurable queue size
16
+ */
17
+ // Re-export skia-canvas for direct use
18
+ export { Canvas, loadImage, FontLibrary } from 'skia-canvas';
19
+ // GPU context management
20
+ export { detectGpuAcceleration, isGpuAvailable, getGpuApi, createCanvas, ensureEvenDimensions, validateEvenDimensions, resetGpuCache, } from './gpu-context.js';
21
+ // Canvas utilities
22
+ export { createPixelBuffer, createPixelBufferWithColor, getRawPixels, getRawPixelsAsync, resetCanvas, pixelsToImageData, drawPixelsToCanvas, bufferToUint8Array, resizePixels, } from './canvas-utils.js';
23
+ // FrameLoop helper
24
+ export { FrameLoop, createFrameLoop } from './frame-loop.js';
25
+ //# sourceMappingURL=index.js.map