webcodecs-node 0.5.0 → 0.5.1

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 (185) hide show
  1. package/package.json +1 -1
  2. package/dist/AudioData.d.ts +0 -6
  3. package/dist/AudioData.d.ts.map +0 -1
  4. package/dist/AudioData.js +0 -6
  5. package/dist/AudioData.js.map +0 -1
  6. package/dist/AudioDecoder.d.ts +0 -6
  7. package/dist/AudioDecoder.d.ts.map +0 -1
  8. package/dist/AudioDecoder.js +0 -6
  9. package/dist/AudioDecoder.js.map +0 -1
  10. package/dist/AudioEncoder.d.ts +0 -6
  11. package/dist/AudioEncoder.d.ts.map +0 -1
  12. package/dist/AudioEncoder.js +0 -6
  13. package/dist/AudioEncoder.js.map +0 -1
  14. package/dist/EncodedAudioChunk.d.ts +0 -6
  15. package/dist/EncodedAudioChunk.d.ts.map +0 -1
  16. package/dist/EncodedAudioChunk.js +0 -6
  17. package/dist/EncodedAudioChunk.js.map +0 -1
  18. package/dist/EncodedVideoChunk.d.ts +0 -6
  19. package/dist/EncodedVideoChunk.d.ts.map +0 -1
  20. package/dist/EncodedVideoChunk.js +0 -6
  21. package/dist/EncodedVideoChunk.js.map +0 -1
  22. package/dist/FFmpegProcess.d.ts +0 -6
  23. package/dist/FFmpegProcess.d.ts.map +0 -1
  24. package/dist/FFmpegProcess.js +0 -6
  25. package/dist/FFmpegProcess.js.map +0 -1
  26. package/dist/HardwareAcceleration.d.ts +0 -6
  27. package/dist/HardwareAcceleration.d.ts.map +0 -1
  28. package/dist/HardwareAcceleration.js +0 -6
  29. package/dist/HardwareAcceleration.js.map +0 -1
  30. package/dist/ImageDecoder.d.ts +0 -6
  31. package/dist/ImageDecoder.d.ts.map +0 -1
  32. package/dist/ImageDecoder.js +0 -6
  33. package/dist/ImageDecoder.js.map +0 -1
  34. package/dist/Logger.d.ts +0 -6
  35. package/dist/Logger.d.ts.map +0 -1
  36. package/dist/Logger.js +0 -6
  37. package/dist/Logger.js.map +0 -1
  38. package/dist/MediaCapabilities.d.ts +0 -9
  39. package/dist/MediaCapabilities.d.ts.map +0 -1
  40. package/dist/MediaCapabilities.js +0 -8
  41. package/dist/MediaCapabilities.js.map +0 -1
  42. package/dist/VideoDecoder.d.ts +0 -6
  43. package/dist/VideoDecoder.d.ts.map +0 -1
  44. package/dist/VideoDecoder.js +0 -6
  45. package/dist/VideoDecoder.js.map +0 -1
  46. package/dist/VideoEncoder.d.ts +0 -6
  47. package/dist/VideoEncoder.d.ts.map +0 -1
  48. package/dist/VideoEncoder.js +0 -6
  49. package/dist/VideoEncoder.js.map +0 -1
  50. package/dist/VideoFrame.d.ts +0 -10
  51. package/dist/VideoFrame.d.ts.map +0 -1
  52. package/dist/VideoFrame.js +0 -9
  53. package/dist/VideoFrame.js.map +0 -1
  54. package/dist/__tests__/AudioData.test.d.ts +0 -5
  55. package/dist/__tests__/AudioData.test.d.ts.map +0 -1
  56. package/dist/__tests__/AudioData.test.js +0 -180
  57. package/dist/__tests__/AudioData.test.js.map +0 -1
  58. package/dist/__tests__/AudioDecoder.test.d.ts +0 -5
  59. package/dist/__tests__/AudioDecoder.test.d.ts.map +0 -1
  60. package/dist/__tests__/AudioDecoder.test.js +0 -733
  61. package/dist/__tests__/AudioDecoder.test.js.map +0 -1
  62. package/dist/__tests__/AudioEncoder.test.d.ts +0 -5
  63. package/dist/__tests__/AudioEncoder.test.d.ts.map +0 -1
  64. package/dist/__tests__/AudioEncoder.test.js +0 -247
  65. package/dist/__tests__/AudioEncoder.test.js.map +0 -1
  66. package/dist/__tests__/EncodedChunks.test.d.ts +0 -5
  67. package/dist/__tests__/EncodedChunks.test.d.ts.map +0 -1
  68. package/dist/__tests__/EncodedChunks.test.js +0 -100
  69. package/dist/__tests__/EncodedChunks.test.js.map +0 -1
  70. package/dist/__tests__/HardwareAcceleration.test.d.ts +0 -5
  71. package/dist/__tests__/HardwareAcceleration.test.d.ts.map +0 -1
  72. package/dist/__tests__/HardwareAcceleration.test.js +0 -85
  73. package/dist/__tests__/HardwareAcceleration.test.js.map +0 -1
  74. package/dist/__tests__/ImageDecoder.test.d.ts +0 -5
  75. package/dist/__tests__/ImageDecoder.test.d.ts.map +0 -1
  76. package/dist/__tests__/ImageDecoder.test.js +0 -558
  77. package/dist/__tests__/ImageDecoder.test.js.map +0 -1
  78. package/dist/__tests__/ImageDecoder.wpt.test.d.ts +0 -8
  79. package/dist/__tests__/ImageDecoder.wpt.test.d.ts.map +0 -1
  80. package/dist/__tests__/ImageDecoder.wpt.test.js +0 -135
  81. package/dist/__tests__/ImageDecoder.wpt.test.js.map +0 -1
  82. package/dist/__tests__/MediaCapabilities.test.d.ts +0 -5
  83. package/dist/__tests__/MediaCapabilities.test.d.ts.map +0 -1
  84. package/dist/__tests__/MediaCapabilities.test.js +0 -346
  85. package/dist/__tests__/MediaCapabilities.test.js.map +0 -1
  86. package/dist/__tests__/NodeAvDecoder.test.d.ts +0 -2
  87. package/dist/__tests__/NodeAvDecoder.test.d.ts.map +0 -1
  88. package/dist/__tests__/NodeAvDecoder.test.js +0 -206
  89. package/dist/__tests__/NodeAvDecoder.test.js.map +0 -1
  90. package/dist/__tests__/NodeAvEncoder.test.d.ts +0 -2
  91. package/dist/__tests__/NodeAvEncoder.test.d.ts.map +0 -1
  92. package/dist/__tests__/NodeAvEncoder.test.js +0 -176
  93. package/dist/__tests__/NodeAvEncoder.test.js.map +0 -1
  94. package/dist/__tests__/VideoDecoder.test.d.ts +0 -5
  95. package/dist/__tests__/VideoDecoder.test.d.ts.map +0 -1
  96. package/dist/__tests__/VideoDecoder.test.js +0 -675
  97. package/dist/__tests__/VideoDecoder.test.js.map +0 -1
  98. package/dist/__tests__/VideoEncoder.test.d.ts +0 -5
  99. package/dist/__tests__/VideoEncoder.test.d.ts.map +0 -1
  100. package/dist/__tests__/VideoEncoder.test.js +0 -468
  101. package/dist/__tests__/VideoEncoder.test.js.map +0 -1
  102. package/dist/__tests__/VideoFrame.test.d.ts +0 -5
  103. package/dist/__tests__/VideoFrame.test.d.ts.map +0 -1
  104. package/dist/__tests__/VideoFrame.test.js +0 -246
  105. package/dist/__tests__/VideoFrame.test.js.map +0 -1
  106. package/dist/__tests__/aac-utils.test.d.ts +0 -2
  107. package/dist/__tests__/aac-utils.test.d.ts.map +0 -1
  108. package/dist/__tests__/aac-utils.test.js +0 -37
  109. package/dist/__tests__/aac-utils.test.js.map +0 -1
  110. package/dist/__tests__/avc-utils.test.d.ts +0 -2
  111. package/dist/__tests__/avc-utils.test.d.ts.map +0 -1
  112. package/dist/__tests__/avc-utils.test.js +0 -63
  113. package/dist/__tests__/avc-utils.test.js.map +0 -1
  114. package/dist/__tests__/hevc-utils.test.d.ts +0 -2
  115. package/dist/__tests__/hevc-utils.test.d.ts.map +0 -1
  116. package/dist/__tests__/hevc-utils.test.js +0 -78
  117. package/dist/__tests__/hevc-utils.test.js.map +0 -1
  118. package/dist/demos/demo-conversion.d.ts +0 -13
  119. package/dist/demos/demo-conversion.d.ts.map +0 -1
  120. package/dist/demos/demo-conversion.js +0 -301
  121. package/dist/demos/demo-conversion.js.map +0 -1
  122. package/dist/demos/demo-hwaccel-conversion.d.ts +0 -8
  123. package/dist/demos/demo-hwaccel-conversion.d.ts.map +0 -1
  124. package/dist/demos/demo-hwaccel-conversion.js +0 -161
  125. package/dist/demos/demo-hwaccel-conversion.js.map +0 -1
  126. package/dist/demos/demo-mediabunny.d.ts +0 -7
  127. package/dist/demos/demo-mediabunny.d.ts.map +0 -1
  128. package/dist/demos/demo-mediabunny.js +0 -78
  129. package/dist/demos/demo-mediabunny.js.map +0 -1
  130. package/dist/ffmpeg/FFmpegProcess.d.ts +0 -111
  131. package/dist/ffmpeg/FFmpegProcess.d.ts.map +0 -1
  132. package/dist/ffmpeg/FFmpegProcess.js +0 -543
  133. package/dist/ffmpeg/FFmpegProcess.js.map +0 -1
  134. package/dist/ffmpeg/audio-codecs.d.ts +0 -59
  135. package/dist/ffmpeg/audio-codecs.d.ts.map +0 -1
  136. package/dist/ffmpeg/audio-codecs.js +0 -99
  137. package/dist/ffmpeg/audio-codecs.js.map +0 -1
  138. package/dist/ffmpeg/formats.d.ts +0 -42
  139. package/dist/ffmpeg/formats.d.ts.map +0 -1
  140. package/dist/ffmpeg/formats.js +0 -147
  141. package/dist/ffmpeg/formats.js.map +0 -1
  142. package/dist/ffmpeg/index.d.ts +0 -10
  143. package/dist/ffmpeg/index.d.ts.map +0 -1
  144. package/dist/ffmpeg/index.js +0 -13
  145. package/dist/ffmpeg/index.js.map +0 -1
  146. package/dist/ffmpeg/parsers/annexb.d.ts +0 -88
  147. package/dist/ffmpeg/parsers/annexb.d.ts.map +0 -1
  148. package/dist/ffmpeg/parsers/annexb.js +0 -201
  149. package/dist/ffmpeg/parsers/annexb.js.map +0 -1
  150. package/dist/ffmpeg/parsers/index.d.ts +0 -6
  151. package/dist/ffmpeg/parsers/index.d.ts.map +0 -1
  152. package/dist/ffmpeg/parsers/index.js +0 -8
  153. package/dist/ffmpeg/parsers/index.js.map +0 -1
  154. package/dist/ffmpeg/parsers/ivf.d.ts +0 -54
  155. package/dist/ffmpeg/parsers/ivf.d.ts.map +0 -1
  156. package/dist/ffmpeg/parsers/ivf.js +0 -109
  157. package/dist/ffmpeg/parsers/ivf.js.map +0 -1
  158. package/dist/ffmpeg/types.d.ts +0 -85
  159. package/dist/ffmpeg/types.d.ts.map +0 -1
  160. package/dist/ffmpeg/types.js +0 -8
  161. package/dist/ffmpeg/types.js.map +0 -1
  162. package/dist/mediabunny/FFmpegAudioDecoder.d.ts +0 -24
  163. package/dist/mediabunny/FFmpegAudioDecoder.d.ts.map +0 -1
  164. package/dist/mediabunny/FFmpegAudioDecoder.js +0 -126
  165. package/dist/mediabunny/FFmpegAudioDecoder.js.map +0 -1
  166. package/dist/mediabunny/FFmpegAudioEncoder.d.ts +0 -44
  167. package/dist/mediabunny/FFmpegAudioEncoder.d.ts.map +0 -1
  168. package/dist/mediabunny/FFmpegAudioEncoder.js +0 -221
  169. package/dist/mediabunny/FFmpegAudioEncoder.js.map +0 -1
  170. package/dist/mediabunny/FFmpegVideoDecoder.d.ts +0 -25
  171. package/dist/mediabunny/FFmpegVideoDecoder.d.ts.map +0 -1
  172. package/dist/mediabunny/FFmpegVideoDecoder.js +0 -122
  173. package/dist/mediabunny/FFmpegVideoDecoder.js.map +0 -1
  174. package/dist/mediabunny/FFmpegVideoEncoder.d.ts +0 -49
  175. package/dist/mediabunny/FFmpegVideoEncoder.d.ts.map +0 -1
  176. package/dist/mediabunny/FFmpegVideoEncoder.js +0 -264
  177. package/dist/mediabunny/FFmpegVideoEncoder.js.map +0 -1
  178. package/dist/mediabunny/index.d.ts +0 -21
  179. package/dist/mediabunny/index.d.ts.map +0 -1
  180. package/dist/mediabunny/index.js +0 -33
  181. package/dist/mediabunny/index.js.map +0 -1
  182. package/dist/types.d.ts +0 -6
  183. package/dist/types.d.ts.map +0 -1
  184. package/dist/types.js +0 -6
  185. package/dist/types.js.map +0 -1
@@ -1,675 +0,0 @@
1
- /**
2
- * Tests for VideoDecoder class
3
- */
4
- import { describe, it, expect } from 'vitest';
5
- import { VideoDecoder } from '../decoders/VideoDecoder.js';
6
- import { VideoEncoder } from '../encoders/VideoEncoder.js';
7
- import { VideoFrame } from '../core/VideoFrame.js';
8
- import { EncodedVideoChunk } from '../core/EncodedVideoChunk.js';
9
- describe('VideoDecoder', () => {
10
- describe('isConfigSupported', () => {
11
- it('should support H.264', async () => {
12
- const support = await VideoDecoder.isConfigSupported({
13
- codec: 'avc1.42001E',
14
- codedWidth: 640,
15
- codedHeight: 480,
16
- });
17
- expect(support.supported).toBe(true);
18
- });
19
- it('should support VP8', async () => {
20
- const support = await VideoDecoder.isConfigSupported({
21
- codec: 'vp8',
22
- codedWidth: 640,
23
- codedHeight: 480,
24
- });
25
- expect(support.supported).toBe(true);
26
- });
27
- it('should support VP9', async () => {
28
- const support = await VideoDecoder.isConfigSupported({
29
- codec: 'vp09.00.10.08',
30
- codedWidth: 1920,
31
- codedHeight: 1080,
32
- });
33
- expect(support.supported).toBe(true);
34
- });
35
- it('should support HEVC', async () => {
36
- const support = await VideoDecoder.isConfigSupported({
37
- codec: 'hev1.1.6.L93.B0',
38
- codedWidth: 1920,
39
- codedHeight: 1080,
40
- });
41
- expect(support.supported).toBe(true);
42
- });
43
- it('should support AV1', async () => {
44
- const support = await VideoDecoder.isConfigSupported({
45
- codec: 'av01.0.01M.08',
46
- codedWidth: 1920,
47
- codedHeight: 1080,
48
- });
49
- expect(support.supported).toBe(true);
50
- });
51
- it('should not support unknown codec', async () => {
52
- const support = await VideoDecoder.isConfigSupported({
53
- codec: 'unknown-codec',
54
- codedWidth: 640,
55
- codedHeight: 480,
56
- });
57
- expect(support.supported).toBe(false);
58
- });
59
- });
60
- describe('constructor', () => {
61
- it('should create decoder with callbacks', () => {
62
- const output = jest.fn();
63
- const error = jest.fn();
64
- const decoder = new VideoDecoder({ output, error });
65
- expect(decoder.state).toBe('unconfigured');
66
- decoder.close();
67
- });
68
- it('should throw without output callback', () => {
69
- expect(() => new VideoDecoder({ output: null, error: () => { } })).toThrow();
70
- });
71
- it('should throw without error callback', () => {
72
- expect(() => new VideoDecoder({ output: () => { }, error: null })).toThrow();
73
- });
74
- });
75
- describe('configure', () => {
76
- it('should configure decoder', () => {
77
- const decoder = new VideoDecoder({
78
- output: () => { },
79
- error: () => { },
80
- });
81
- decoder.configure({
82
- codec: 'avc1.42001E',
83
- codedWidth: 320,
84
- codedHeight: 240,
85
- });
86
- expect(decoder.state).toBe('configured');
87
- decoder.close();
88
- });
89
- it('should throw without codec', () => {
90
- const decoder = new VideoDecoder({
91
- output: () => { },
92
- error: () => { },
93
- });
94
- expect(() => decoder.configure({
95
- codec: '',
96
- codedWidth: 320,
97
- codedHeight: 240,
98
- })).toThrow();
99
- decoder.close();
100
- });
101
- it('should throw on closed decoder', () => {
102
- const decoder = new VideoDecoder({
103
- output: () => { },
104
- error: () => { },
105
- });
106
- decoder.close();
107
- expect(() => decoder.configure({
108
- codec: 'avc1.42001E',
109
- codedWidth: 320,
110
- codedHeight: 240,
111
- })).toThrow();
112
- });
113
- });
114
- describe('decode', () => {
115
- it('should throw when not configured', () => {
116
- const decoder = new VideoDecoder({
117
- output: () => { },
118
- error: () => { },
119
- });
120
- const chunk = new EncodedVideoChunk({
121
- type: 'key',
122
- timestamp: 0,
123
- data: new Uint8Array(100),
124
- });
125
- expect(() => decoder.decode(chunk)).toThrow();
126
- decoder.close();
127
- });
128
- it('should throw with invalid chunk', () => {
129
- const decoder = new VideoDecoder({
130
- output: () => { },
131
- error: () => { },
132
- });
133
- decoder.configure({
134
- codec: 'avc1.42001E',
135
- codedWidth: 64,
136
- codedHeight: 64,
137
- });
138
- expect(() => decoder.decode('invalid')).toThrow();
139
- decoder.close();
140
- });
141
- });
142
- describe('reset', () => {
143
- it('should reset decoder to unconfigured state', () => {
144
- const decoder = new VideoDecoder({
145
- output: () => { },
146
- error: () => { },
147
- });
148
- decoder.configure({
149
- codec: 'vp8',
150
- codedWidth: 320,
151
- codedHeight: 240,
152
- });
153
- expect(decoder.state).toBe('configured');
154
- decoder.reset();
155
- expect(decoder.state).toBe('unconfigured');
156
- decoder.close();
157
- });
158
- it('should throw on closed decoder', () => {
159
- const decoder = new VideoDecoder({
160
- output: () => { },
161
- error: () => { },
162
- });
163
- decoder.close();
164
- expect(() => decoder.reset()).toThrow();
165
- });
166
- });
167
- describe('close', () => {
168
- it('should close decoder', () => {
169
- const decoder = new VideoDecoder({
170
- output: () => { },
171
- error: () => { },
172
- });
173
- decoder.close();
174
- expect(decoder.state).toBe('closed');
175
- });
176
- it('should be idempotent', () => {
177
- const decoder = new VideoDecoder({
178
- output: () => { },
179
- error: () => { },
180
- });
181
- decoder.close();
182
- decoder.close(); // Should not throw
183
- expect(decoder.state).toBe('closed');
184
- });
185
- });
186
- describe('flush', () => {
187
- it('should throw when not configured', async () => {
188
- const decoder = new VideoDecoder({
189
- output: () => { },
190
- error: () => { },
191
- });
192
- await expect(decoder.flush()).rejects.toThrow();
193
- decoder.close();
194
- });
195
- });
196
- describe('decodeQueueSize', () => {
197
- it('should start at 0', () => {
198
- const decoder = new VideoDecoder({
199
- output: () => { },
200
- error: () => { },
201
- });
202
- expect(decoder.decodeQueueSize).toBe(0);
203
- decoder.close();
204
- });
205
- });
206
- });
207
- describe('VideoDecoder encode-decode roundtrip', () => {
208
- it('should decode H.264 encoded frames', async () => {
209
- const width = 64;
210
- const height = 64;
211
- const frameCount = 3;
212
- // Step 1: Encode frames
213
- const encodedChunks = [];
214
- const encoder = new VideoEncoder({
215
- output: (chunk) => encodedChunks.push(chunk),
216
- error: (err) => { throw err; },
217
- });
218
- encoder.configure({
219
- codec: 'avc1.42001E',
220
- width,
221
- height,
222
- bitrate: 500_000,
223
- framerate: 30,
224
- });
225
- // Create and encode test frames (red, green, blue)
226
- const colors = [
227
- [255, 0, 0, 255], // Red
228
- [0, 255, 0, 255], // Green
229
- [0, 0, 255, 255], // Blue
230
- ];
231
- for (let i = 0; i < frameCount; i++) {
232
- const data = new Uint8Array(width * height * 4);
233
- const [r, g, b, a] = colors[i];
234
- for (let p = 0; p < width * height; p++) {
235
- data[p * 4] = r;
236
- data[p * 4 + 1] = g;
237
- data[p * 4 + 2] = b;
238
- data[p * 4 + 3] = a;
239
- }
240
- const frame = new VideoFrame(data, {
241
- format: 'RGBA',
242
- codedWidth: width,
243
- codedHeight: height,
244
- timestamp: i * 33333,
245
- });
246
- encoder.encode(frame, { keyFrame: i === 0 });
247
- frame.close();
248
- }
249
- await encoder.flush();
250
- encoder.close();
251
- expect(encodedChunks.length).toBeGreaterThan(0);
252
- // Step 2: Decode frames
253
- const decodedFrames = [];
254
- const decoder = new VideoDecoder({
255
- output: (frame) => decodedFrames.push(frame),
256
- error: (err) => { throw err; },
257
- });
258
- decoder.configure({
259
- codec: 'avc1.42001E',
260
- codedWidth: width,
261
- codedHeight: height,
262
- outputFormat: 'RGBA',
263
- });
264
- for (const chunk of encodedChunks) {
265
- decoder.decode(chunk);
266
- }
267
- await decoder.flush();
268
- decoder.close();
269
- // Verify decoded frames
270
- expect(decodedFrames.length).toBe(frameCount);
271
- for (const frame of decodedFrames) {
272
- expect(frame.codedWidth).toBe(width);
273
- expect(frame.codedHeight).toBe(height);
274
- expect(frame.format).toBe('RGBA');
275
- frame.close();
276
- }
277
- }, 30000);
278
- it('should decode VP9 encoded frames', async () => {
279
- const width = 64;
280
- const height = 64;
281
- const frameCount = 3;
282
- // Step 1: Encode frames
283
- const encodedChunks = [];
284
- const encoder = new VideoEncoder({
285
- output: (chunk) => encodedChunks.push(chunk),
286
- error: (err) => { throw err; },
287
- });
288
- encoder.configure({
289
- codec: 'vp09.00.10.08',
290
- width,
291
- height,
292
- bitrate: 500_000,
293
- framerate: 30,
294
- });
295
- for (let i = 0; i < frameCount; i++) {
296
- const data = new Uint8Array(width * height * 4);
297
- // Fill with gradient (no alpha transparency issues with VP9)
298
- for (let p = 0; p < width * height; p++) {
299
- data[p * 4] = (i * 80) % 256;
300
- data[p * 4 + 1] = (p % width) * 4;
301
- data[p * 4 + 2] = Math.floor(p / width) * 4;
302
- data[p * 4 + 3] = 255;
303
- }
304
- const frame = new VideoFrame(data, {
305
- format: 'RGBA',
306
- codedWidth: width,
307
- codedHeight: height,
308
- timestamp: i * 33333,
309
- });
310
- encoder.encode(frame, { keyFrame: i === 0 });
311
- frame.close();
312
- }
313
- await encoder.flush();
314
- encoder.close();
315
- expect(encodedChunks.length).toBeGreaterThan(0);
316
- // Step 2: Decode frames
317
- const decodedFrames = [];
318
- const decoder = new VideoDecoder({
319
- output: (frame) => decodedFrames.push(frame),
320
- error: (err) => { throw err; },
321
- });
322
- decoder.configure({
323
- codec: 'vp09.00.10.08',
324
- codedWidth: width,
325
- codedHeight: height,
326
- });
327
- for (const chunk of encodedChunks) {
328
- decoder.decode(chunk);
329
- }
330
- await decoder.flush();
331
- decoder.close();
332
- // Verify decoded frames
333
- expect(decodedFrames.length).toBe(frameCount);
334
- for (const frame of decodedFrames) {
335
- expect(frame.codedWidth).toBe(width);
336
- expect(frame.codedHeight).toBe(height);
337
- frame.close();
338
- }
339
- }, 30000);
340
- it('should pass chunk timestamps to decoded frames', async () => {
341
- const width = 64;
342
- const height = 64;
343
- const frameCount = 3;
344
- // Encode frames first
345
- const encodedChunks = [];
346
- const encoder = new VideoEncoder({
347
- output: (chunk) => encodedChunks.push(chunk),
348
- error: (err) => { throw err; },
349
- });
350
- encoder.configure({
351
- codec: 'avc1.42001E',
352
- width,
353
- height,
354
- bitrate: 500_000,
355
- framerate: 30,
356
- });
357
- for (let i = 0; i < frameCount; i++) {
358
- const data = new Uint8Array(width * height * 4);
359
- const frame = new VideoFrame(data, {
360
- format: 'RGBA',
361
- codedWidth: width,
362
- codedHeight: height,
363
- timestamp: i * 33333,
364
- });
365
- encoder.encode(frame, { keyFrame: i === 0 });
366
- frame.close();
367
- }
368
- await encoder.flush();
369
- encoder.close();
370
- expect(encodedChunks.length).toBe(frameCount);
371
- // Now decode - the decoder should pass timestamps from chunks to output frames
372
- const decodedFrames = [];
373
- const decoder = new VideoDecoder({
374
- output: (frame) => decodedFrames.push(frame),
375
- error: (err) => { throw err; },
376
- });
377
- decoder.configure({
378
- codec: 'avc1.42001E',
379
- codedWidth: width,
380
- codedHeight: height,
381
- });
382
- for (const chunk of encodedChunks) {
383
- decoder.decode(chunk);
384
- }
385
- await decoder.flush();
386
- decoder.close();
387
- // The decoder should output same number of frames as input chunks
388
- expect(decodedFrames.length).toBe(encodedChunks.length);
389
- // Each decoded frame should have the timestamp from the corresponding chunk
390
- for (let i = 0; i < decodedFrames.length; i++) {
391
- expect(decodedFrames[i].timestamp).toBe(encodedChunks[i].timestamp);
392
- decodedFrames[i].close();
393
- }
394
- }, 30000);
395
- });
396
- describe('VideoDecoder output formats', () => {
397
- // Helper to create encoded chunks for testing
398
- async function createEncodedChunks(format) {
399
- const width = 64;
400
- const height = 64;
401
- const encodedChunks = [];
402
- const encoder = new VideoEncoder({
403
- output: (chunk) => encodedChunks.push(chunk),
404
- error: (err) => { throw err; },
405
- });
406
- encoder.configure({
407
- codec: 'vp09.00.10.08',
408
- width,
409
- height,
410
- bitrate: 500_000,
411
- framerate: 30,
412
- });
413
- // Create test frame in the specified format
414
- let data;
415
- if (format === 'RGBA') {
416
- data = new Uint8Array(width * height * 4);
417
- for (let p = 0; p < width * height; p++) {
418
- data[p * 4] = 128; // R
419
- data[p * 4 + 1] = 64; // G
420
- data[p * 4 + 2] = 192; // B
421
- data[p * 4 + 3] = 255; // A
422
- }
423
- }
424
- else {
425
- // I420: Y + U + V planes
426
- const ySize = width * height;
427
- const uvSize = (width / 2) * (height / 2);
428
- data = new Uint8Array(ySize + 2 * uvSize);
429
- // Y plane (gray)
430
- for (let i = 0; i < ySize; i++)
431
- data[i] = 128;
432
- // U plane
433
- for (let i = 0; i < uvSize; i++)
434
- data[ySize + i] = 128;
435
- // V plane
436
- for (let i = 0; i < uvSize; i++)
437
- data[ySize + uvSize + i] = 128;
438
- }
439
- const frame = new VideoFrame(data, {
440
- format,
441
- codedWidth: width,
442
- codedHeight: height,
443
- timestamp: 0,
444
- });
445
- encoder.encode(frame, { keyFrame: true });
446
- frame.close();
447
- await encoder.flush();
448
- encoder.close();
449
- return encodedChunks;
450
- }
451
- it('should decode to I420 format (default)', async () => {
452
- const encodedChunks = await createEncodedChunks('RGBA');
453
- expect(encodedChunks.length).toBeGreaterThan(0);
454
- const decodedFrames = [];
455
- const decoder = new VideoDecoder({
456
- output: (frame) => decodedFrames.push(frame),
457
- error: (err) => { throw err; },
458
- });
459
- decoder.configure({
460
- codec: 'vp09.00.10.08',
461
- codedWidth: 64,
462
- codedHeight: 64,
463
- // outputFormat defaults to I420
464
- });
465
- for (const chunk of encodedChunks) {
466
- decoder.decode(chunk);
467
- }
468
- await decoder.flush();
469
- decoder.close();
470
- expect(decodedFrames.length).toBeGreaterThan(0);
471
- for (const frame of decodedFrames) {
472
- expect(frame.format).toBe('I420');
473
- expect(frame.numberOfPlanes).toBe(3);
474
- // I420 frame size = Y + U + V = 64*64 + 32*32 + 32*32 = 4096 + 1024 + 1024 = 6144
475
- expect(frame.allocationSize()).toBe(6144);
476
- frame.close();
477
- }
478
- }, 30000);
479
- it('should decode to NV12 format', async () => {
480
- const encodedChunks = await createEncodedChunks('RGBA');
481
- expect(encodedChunks.length).toBeGreaterThan(0);
482
- const decodedFrames = [];
483
- const decoder = new VideoDecoder({
484
- output: (frame) => decodedFrames.push(frame),
485
- error: (err) => { throw err; },
486
- });
487
- decoder.configure({
488
- codec: 'vp09.00.10.08',
489
- codedWidth: 64,
490
- codedHeight: 64,
491
- outputFormat: 'NV12',
492
- });
493
- for (const chunk of encodedChunks) {
494
- decoder.decode(chunk);
495
- }
496
- await decoder.flush();
497
- decoder.close();
498
- expect(decodedFrames.length).toBeGreaterThan(0);
499
- for (const frame of decodedFrames) {
500
- expect(frame.format).toBe('NV12');
501
- expect(frame.numberOfPlanes).toBe(2);
502
- // NV12 frame size = Y + UV = 64*64 + 64*32 = 4096 + 2048 = 6144
503
- expect(frame.allocationSize()).toBe(6144);
504
- frame.close();
505
- }
506
- }, 30000);
507
- it('should decode to BGRA format', async () => {
508
- const encodedChunks = await createEncodedChunks('RGBA');
509
- expect(encodedChunks.length).toBeGreaterThan(0);
510
- const decodedFrames = [];
511
- const decoder = new VideoDecoder({
512
- output: (frame) => decodedFrames.push(frame),
513
- error: (err) => { throw err; },
514
- });
515
- decoder.configure({
516
- codec: 'vp09.00.10.08',
517
- codedWidth: 64,
518
- codedHeight: 64,
519
- outputFormat: 'BGRA',
520
- });
521
- for (const chunk of encodedChunks) {
522
- decoder.decode(chunk);
523
- }
524
- await decoder.flush();
525
- decoder.close();
526
- expect(decodedFrames.length).toBeGreaterThan(0);
527
- for (const frame of decodedFrames) {
528
- expect(frame.format).toBe('BGRA');
529
- expect(frame.numberOfPlanes).toBe(1);
530
- // BGRA frame size = 64*64*4 = 16384
531
- expect(frame.allocationSize()).toBe(16384);
532
- frame.close();
533
- }
534
- }, 30000);
535
- it('should throw with invalid outputFormat', () => {
536
- const decoder = new VideoDecoder({
537
- output: () => { },
538
- error: () => { },
539
- });
540
- expect(() => {
541
- decoder.configure({
542
- codec: 'vp09.00.10.08',
543
- codedWidth: 64,
544
- codedHeight: 64,
545
- outputFormat: 'INVALID',
546
- });
547
- }).toThrow(TypeError);
548
- decoder.close();
549
- });
550
- });
551
- describe('VideoEncoder input formats', () => {
552
- it('should encode I420 input format', async () => {
553
- const width = 64;
554
- const height = 64;
555
- const encodedChunks = [];
556
- const encoder = new VideoEncoder({
557
- output: (chunk) => encodedChunks.push(chunk),
558
- error: (err) => { throw err; },
559
- });
560
- encoder.configure({
561
- codec: 'vp09.00.10.08',
562
- width,
563
- height,
564
- bitrate: 500_000,
565
- framerate: 30,
566
- });
567
- // Create I420 frame
568
- const ySize = width * height;
569
- const uvSize = (width / 2) * (height / 2);
570
- const data = new Uint8Array(ySize + 2 * uvSize);
571
- // Y plane
572
- for (let i = 0; i < ySize; i++)
573
- data[i] = 128;
574
- // U plane
575
- for (let i = 0; i < uvSize; i++)
576
- data[ySize + i] = 128;
577
- // V plane
578
- for (let i = 0; i < uvSize; i++)
579
- data[ySize + uvSize + i] = 128;
580
- const frame = new VideoFrame(data, {
581
- format: 'I420',
582
- codedWidth: width,
583
- codedHeight: height,
584
- timestamp: 0,
585
- });
586
- encoder.encode(frame, { keyFrame: true });
587
- frame.close();
588
- await encoder.flush();
589
- encoder.close();
590
- expect(encodedChunks.length).toBeGreaterThan(0);
591
- // Verify encoding succeeded - chunk type depends on FFmpeg keyframe detection
592
- expect(['key', 'delta']).toContain(encodedChunks[0].type);
593
- }, 30000);
594
- it('should encode NV12 input format', async () => {
595
- const width = 64;
596
- const height = 64;
597
- const encodedChunks = [];
598
- const encoder = new VideoEncoder({
599
- output: (chunk) => encodedChunks.push(chunk),
600
- error: (err) => { throw err; },
601
- });
602
- encoder.configure({
603
- codec: 'vp09.00.10.08',
604
- width,
605
- height,
606
- bitrate: 500_000,
607
- framerate: 30,
608
- });
609
- // Create NV12 frame (Y + interleaved UV)
610
- const ySize = width * height;
611
- const uvSize = width * (height / 2);
612
- const data = new Uint8Array(ySize + uvSize);
613
- // Y plane
614
- for (let i = 0; i < ySize; i++)
615
- data[i] = 128;
616
- // UV interleaved
617
- for (let i = 0; i < uvSize; i++)
618
- data[ySize + i] = 128;
619
- const frame = new VideoFrame(data, {
620
- format: 'NV12',
621
- codedWidth: width,
622
- codedHeight: height,
623
- timestamp: 0,
624
- });
625
- encoder.encode(frame, { keyFrame: true });
626
- frame.close();
627
- await encoder.flush();
628
- encoder.close();
629
- expect(encodedChunks.length).toBeGreaterThan(0);
630
- // Verify encoding succeeded - chunk type depends on FFmpeg keyframe detection
631
- expect(['key', 'delta']).toContain(encodedChunks[0].type);
632
- }, 30000);
633
- it('should reject mixed input formats', async () => {
634
- const width = 64;
635
- const height = 64;
636
- let errorCalled = false;
637
- const encoder = new VideoEncoder({
638
- output: () => { },
639
- error: () => { errorCalled = true; },
640
- });
641
- encoder.configure({
642
- codec: 'vp09.00.10.08',
643
- width,
644
- height,
645
- bitrate: 500_000,
646
- framerate: 30,
647
- });
648
- // First frame in RGBA
649
- const rgbaData = new Uint8Array(width * height * 4);
650
- const frame1 = new VideoFrame(rgbaData, {
651
- format: 'RGBA',
652
- codedWidth: width,
653
- codedHeight: height,
654
- timestamp: 0,
655
- });
656
- encoder.encode(frame1, { keyFrame: true });
657
- frame1.close();
658
- // Second frame in I420 (different format)
659
- const ySize = width * height;
660
- const uvSize = (width / 2) * (height / 2);
661
- const i420Data = new Uint8Array(ySize + 2 * uvSize);
662
- const frame2 = new VideoFrame(i420Data, {
663
- format: 'I420',
664
- codedWidth: width,
665
- codedHeight: height,
666
- timestamp: 33333,
667
- });
668
- encoder.encode(frame2);
669
- frame2.close();
670
- // Error callback should have been called due to format mismatch
671
- expect(errorCalled).toBe(true);
672
- encoder.close();
673
- }, 30000);
674
- });
675
- //# sourceMappingURL=VideoDecoder.test.js.map