node-av 6.0.0-beta.6 → 6.0.0-beta.7

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.
@@ -0,0 +1,285 @@
1
+ import { Encoder } from './encoder.js';
2
+ /**
3
+ * Bounded LRU pool of image encoders keyed by resolution and pixel format.
4
+ *
5
+ * A single encoder cannot change dimensions after opening, so encoding frames of
6
+ * varying sizes needs one encoder per resolution. This pool routes each frame to
7
+ * the encoder matching its `width x height x pixelFormat`, creating one on demand
8
+ * and reusing it afterwards. Each frame is given a monotonic PTS internally, so
9
+ * callers can feed independent snapshots without managing timestamps.
10
+ *
11
+ * Pooled encoders are never flushed between frames, so this only suits intra-only
12
+ * image codecs (MJPEG, PNG, WebP, ...). Hardware frames work as-is: the encoder
13
+ * adopts the frame's hardware context. For a single one-off conversion use
14
+ * {@link Encoder.encodeOne} instead.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { EncoderPool } from 'node-av/api';
19
+ * import { FF_ENCODER_MJPEG } from 'node-av/constants';
20
+ *
21
+ * using pool = new EncoderPool(FF_ENCODER_MJPEG);
22
+ * const a = await pool.encode(frame1920); // opens + caches
23
+ * const b = await pool.encode(frame640); // opens + caches
24
+ * const c = await pool.encode(frame1920); // reuses, no open
25
+ * ```
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // MJPEG quality: set the QSCALE flag on the pool, then carry the quality on
30
+ * // each frame (qscale 2 = best, 31 = worst). The `q` private option has no
31
+ * // effect on MJPEG.
32
+ * import { AV_CODEC_FLAG_QSCALE, FF_QP2LAMBDA } from 'node-av/constants';
33
+ *
34
+ * using pool = new EncoderPool(FF_ENCODER_MJPEG, { maxSize: 4, flags: AV_CODEC_FLAG_QSCALE });
35
+ * frame.quality = 3 * FF_QP2LAMBDA;
36
+ * const jpeg = await pool.encode(frame);
37
+ * ```
38
+ *
39
+ * @see {@link Encoder.encodeOne} For a one-shot, stateless single-frame encode
40
+ */
41
+ export class EncoderPool {
42
+ encoderCodec;
43
+ options;
44
+ maxSize;
45
+ flags;
46
+ // Insertion order doubles as LRU order: touched entries move to the end,
47
+ // so the first key is always the least-recently-used.
48
+ encoders = new Map();
49
+ /**
50
+ * Create a new encoder pool.
51
+ *
52
+ * @param encoderCodec - Encoder codec applied to every pooled encoder
53
+ *
54
+ * @param options - Encoder options forwarded to each encoder, plus the optional `maxSize` limit
55
+ *
56
+ * @throws {Error} If maxSize is less than 1
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * using pool = new EncoderPool(FF_ENCODER_MJPEG, { maxSize: 4, flags: AV_CODEC_FLAG_QSCALE });
61
+ * ```
62
+ */
63
+ constructor(encoderCodec, options = {}) {
64
+ const { maxSize = 8, flags, ...encoderOptions } = options;
65
+ if (maxSize < 1) {
66
+ throw new Error(`EncoderPool maxSize must be at least 1, got ${maxSize}`);
67
+ }
68
+ this.encoderCodec = encoderCodec;
69
+ this.options = { threadCount: 1, ...encoderOptions };
70
+ this.maxSize = maxSize;
71
+ this.flags = flags === undefined ? [] : Array.isArray(flags) ? flags : [flags];
72
+ }
73
+ /**
74
+ * Number of encoders currently held alive.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * console.log(`Pool holds ${pool.size} encoders`);
79
+ * ```
80
+ */
81
+ get size() {
82
+ return this.encoders.size;
83
+ }
84
+ /**
85
+ * Encode a frame into a self-contained image buffer.
86
+ *
87
+ * Routes the frame to the encoder matching its dimensions and pixel format,
88
+ * creating and caching one if necessary.
89
+ *
90
+ * @param frame - Frame to encode
91
+ *
92
+ * @returns Encoded image bytes
93
+ *
94
+ * @throws {FFmpegError} If the encoder is not found or encoding fails
95
+ *
96
+ * @throws {Error} If the encoder produced no output
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const jpeg = await pool.encode(frame);
101
+ * ```
102
+ *
103
+ * @see {@link encodeSync} For synchronous version
104
+ */
105
+ async encode(frame) {
106
+ const key = this.keyFor(frame);
107
+ let pooled = this.touch(key);
108
+ if (!pooled) {
109
+ const encoder = await Encoder.create(this.encoderCodec, this.options);
110
+ pooled = this.touch(key);
111
+ if (pooled) {
112
+ encoder.close();
113
+ }
114
+ else {
115
+ pooled = this.set(key, encoder);
116
+ }
117
+ }
118
+ const savedPts = frame.pts;
119
+ frame.pts = pooled.nextPts++;
120
+ try {
121
+ return this.extract(await pooled.encoder.encodeAll(frame), pooled.encoder);
122
+ }
123
+ finally {
124
+ frame.pts = savedPts;
125
+ }
126
+ }
127
+ /**
128
+ * Encode a frame into a self-contained image buffer synchronously.
129
+ * Synchronous version of encode.
130
+ *
131
+ * @param frame - Frame to encode
132
+ *
133
+ * @returns Encoded image bytes
134
+ *
135
+ * @throws {FFmpegError} If the encoder is not found or encoding fails
136
+ *
137
+ * @throws {Error} If the encoder produced no output
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * const jpeg = pool.encodeSync(frame);
142
+ * ```
143
+ *
144
+ * @see {@link encode} For asynchronous version
145
+ */
146
+ encodeSync(frame) {
147
+ const key = this.keyFor(frame);
148
+ let pooled = this.touch(key);
149
+ pooled ??= this.set(key, Encoder.createSync(this.encoderCodec, this.options));
150
+ const savedPts = frame.pts;
151
+ frame.pts = pooled.nextPts++;
152
+ try {
153
+ return this.extract(pooled.encoder.encodeAllSync(frame), pooled.encoder);
154
+ }
155
+ finally {
156
+ frame.pts = savedPts;
157
+ }
158
+ }
159
+ /**
160
+ * Close all pooled encoders and clear the pool.
161
+ *
162
+ * Automatically called by Symbol.dispose.
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * pool.close();
167
+ * ```
168
+ *
169
+ * @see {@link Symbol.dispose} For automatic cleanup
170
+ */
171
+ close() {
172
+ for (const { encoder } of this.encoders.values()) {
173
+ encoder.close();
174
+ }
175
+ this.encoders.clear();
176
+ }
177
+ /**
178
+ * Build the cache key from frame dimensions and pixel format.
179
+ *
180
+ * Pixel format is part of the key because the encoder is opened with the frame's
181
+ * format; a cached encoder for the same size but a different format would be invalid.
182
+ *
183
+ * @param frame - Frame to derive the key from
184
+ *
185
+ * @returns Cache key `width x height x pixelFormat`
186
+ *
187
+ * @internal
188
+ */
189
+ keyFor(frame) {
190
+ return `${frame.width}x${frame.height}x${frame.format}`;
191
+ }
192
+ /**
193
+ * Look up an encoder and mark it as most-recently-used.
194
+ *
195
+ * @param key - Cache key to look up
196
+ *
197
+ * @returns Pooled encoder, or undefined if not cached
198
+ *
199
+ * @internal
200
+ */
201
+ touch(key) {
202
+ const pooled = this.encoders.get(key);
203
+ if (pooled) {
204
+ this.encoders.delete(key);
205
+ this.encoders.set(key, pooled);
206
+ }
207
+ return pooled;
208
+ }
209
+ /**
210
+ * Insert a freshly created encoder, evicting the least-recently-used one if full.
211
+ *
212
+ * @param key - Cache key for the encoder
213
+ *
214
+ * @param encoder - Freshly created encoder to cache
215
+ *
216
+ * @returns The pooled encoder entry
217
+ *
218
+ * @internal
219
+ */
220
+ set(key, encoder) {
221
+ if (this.flags.length > 0) {
222
+ encoder.setCodecFlags(...this.flags);
223
+ }
224
+ const pooled = { encoder, nextPts: 0n };
225
+ this.encoders.set(key, pooled);
226
+ if (this.encoders.size > this.maxSize) {
227
+ const oldestKey = this.encoders.keys().next().value;
228
+ if (oldestKey !== undefined) {
229
+ const oldest = this.encoders.get(oldestKey);
230
+ this.encoders.delete(oldestKey);
231
+ oldest?.encoder.close();
232
+ }
233
+ }
234
+ return pooled;
235
+ }
236
+ /**
237
+ * Extract the encoded bytes from the produced packets and free them.
238
+ *
239
+ * packet.data returns a JS-owned copy, so the buffer stays valid after freeing.
240
+ *
241
+ * @param packets - Packets produced by the encoder
242
+ *
243
+ * @param encoder - Encoder that produced the packets (for error messages)
244
+ *
245
+ * @returns Encoded image bytes
246
+ *
247
+ * @throws {Error} If no packet was produced
248
+ *
249
+ * @internal
250
+ */
251
+ extract(packets, encoder) {
252
+ try {
253
+ const data = packets[0]?.data;
254
+ if (!data) {
255
+ throw new Error(`Encoder '${encoder.getCodec().name}' produced no output for frame`);
256
+ }
257
+ return data;
258
+ }
259
+ finally {
260
+ for (const packet of packets) {
261
+ packet.free();
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Dispose of the pool.
267
+ *
268
+ * Implements Disposable interface for automatic cleanup.
269
+ * Equivalent to calling close().
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * {
274
+ * using pool = new EncoderPool(FF_ENCODER_MJPEG, { maxSize: 4 });
275
+ * // Encode frames...
276
+ * } // All encoders automatically closed
277
+ * ```
278
+ *
279
+ * @see {@link close} For manual cleanup
280
+ */
281
+ [Symbol.dispose]() {
282
+ this.close();
283
+ }
284
+ }
285
+ //# sourceMappingURL=encoder-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoder-pool.js","sourceRoot":"","sources":["../../src/api/encoder-pool.ts"],"names":[],"mappings":"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"}
@@ -304,6 +304,64 @@ export declare class Encoder implements Disposable {
304
304
  * @see {@link create} For async version
305
305
  */
306
306
  static createSync<const C extends FFEncoderCodec | AVCodecID | Codec>(encoderCodec: C, options?: EncoderOptions<C>): Encoder;
307
+ /**
308
+ * Encode a single frame into a self-contained image buffer.
309
+ *
310
+ * One-shot, stateless helper for intra-only image codecs (MJPEG, PNG, WebP, ...).
311
+ * Creates a fresh encoder, encodes the frame, flushes and frees everything in one call.
312
+ * The encoder adopts dimensions, pixel format and hardware context from the frame,
313
+ * so any frame size works without reconfiguration.
314
+ *
315
+ * @param encoderCodec - Encoder codec (name, ID, branded constant, or Codec)
316
+ *
317
+ * @param frame - Frame to encode
318
+ *
319
+ * @param options - Optional encoder configuration (e.g. `{ options: { q: 3 } }` for MJPEG quality)
320
+ *
321
+ * @returns Encoded image bytes
322
+ *
323
+ * @throws {FFmpegError} If the encoder is not found or encoding fails
324
+ *
325
+ * @throws {Error} If the encoder produced no output
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * const jpeg = await Encoder.encodeOne(FF_ENCODER_MJPEG, frame, { options: { q: 3 } });
330
+ * ```
331
+ *
332
+ * @see {@link EncoderPool} For reusing encoders across recurring resolutions
333
+ */
334
+ static encodeOne<const C extends FFEncoderCodec | AVCodecID | Codec>(encoderCodec: C, frame: Frame, options?: EncoderOptions<C>): Promise<Buffer>;
335
+ /**
336
+ * Encode a single frame into a self-contained image buffer synchronously.
337
+ * Synchronous version of encodeOne.
338
+ *
339
+ * One-shot, stateless helper for intra-only image codecs (MJPEG, PNG, WebP, ...).
340
+ * Creates a fresh encoder, encodes the frame, flushes and frees everything in one call.
341
+ * The encoder adopts dimensions, pixel format and hardware context from the frame,
342
+ * so any frame size works without reconfiguration.
343
+ *
344
+ * @param encoderCodec - Encoder codec (name, ID, branded constant, or Codec)
345
+ *
346
+ * @param frame - Frame to encode
347
+ *
348
+ * @param options - Optional encoder configuration (e.g. `{ options: { q: 3 } }` for MJPEG quality)
349
+ *
350
+ * @returns Encoded image bytes
351
+ *
352
+ * @throws {FFmpegError} If the encoder is not found or encoding fails
353
+ *
354
+ * @throws {Error} If the encoder produced no output
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * const jpeg = Encoder.encodeOneSync(FF_ENCODER_MJPEG, frame, { options: { q: 3 } });
359
+ * ```
360
+ *
361
+ * @see {@link encodeOne} For async version
362
+ * @see {@link EncoderPool} For reusing encoders across recurring resolutions
363
+ */
364
+ static encodeOneSync<const C extends FFEncoderCodec | AVCodecID | Codec>(encoderCodec: C, frame: Frame, options?: EncoderOptions<C>): Buffer;
307
365
  /**
308
366
  * Check if encoder is open.
309
367
  *
@@ -389,6 +389,114 @@ export class Encoder {
389
389
  }
390
390
  return encoder;
391
391
  }
392
+ /**
393
+ * Encode a single frame into a self-contained image buffer.
394
+ *
395
+ * One-shot, stateless helper for intra-only image codecs (MJPEG, PNG, WebP, ...).
396
+ * Creates a fresh encoder, encodes the frame, flushes and frees everything in one call.
397
+ * The encoder adopts dimensions, pixel format and hardware context from the frame,
398
+ * so any frame size works without reconfiguration.
399
+ *
400
+ * @param encoderCodec - Encoder codec (name, ID, branded constant, or Codec)
401
+ *
402
+ * @param frame - Frame to encode
403
+ *
404
+ * @param options - Optional encoder configuration (e.g. `{ options: { q: 3 } }` for MJPEG quality)
405
+ *
406
+ * @returns Encoded image bytes
407
+ *
408
+ * @throws {FFmpegError} If the encoder is not found or encoding fails
409
+ *
410
+ * @throws {Error} If the encoder produced no output
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const jpeg = await Encoder.encodeOne(FF_ENCODER_MJPEG, frame, { options: { q: 3 } });
415
+ * ```
416
+ *
417
+ * @see {@link EncoderPool} For reusing encoders across recurring resolutions
418
+ */
419
+ static async encodeOne(encoderCodec, frame, options = {}) {
420
+ const env_1 = { stack: [], error: void 0, hasError: false };
421
+ try {
422
+ const encoder = __addDisposableResource(env_1, await Encoder.create(encoderCodec, options), false);
423
+ const packets = [...(await encoder.encodeAll(frame)), ...(await encoder.encodeAll(null))];
424
+ try {
425
+ const data = packets[0]?.data;
426
+ if (!data) {
427
+ throw new Error(`Encoder '${encoder.getCodec().name}' produced no output for frame`);
428
+ }
429
+ return data;
430
+ }
431
+ finally {
432
+ for (const packet of packets) {
433
+ packet.free();
434
+ }
435
+ }
436
+ }
437
+ catch (e_1) {
438
+ env_1.error = e_1;
439
+ env_1.hasError = true;
440
+ }
441
+ finally {
442
+ __disposeResources(env_1);
443
+ }
444
+ }
445
+ /**
446
+ * Encode a single frame into a self-contained image buffer synchronously.
447
+ * Synchronous version of encodeOne.
448
+ *
449
+ * One-shot, stateless helper for intra-only image codecs (MJPEG, PNG, WebP, ...).
450
+ * Creates a fresh encoder, encodes the frame, flushes and frees everything in one call.
451
+ * The encoder adopts dimensions, pixel format and hardware context from the frame,
452
+ * so any frame size works without reconfiguration.
453
+ *
454
+ * @param encoderCodec - Encoder codec (name, ID, branded constant, or Codec)
455
+ *
456
+ * @param frame - Frame to encode
457
+ *
458
+ * @param options - Optional encoder configuration (e.g. `{ options: { q: 3 } }` for MJPEG quality)
459
+ *
460
+ * @returns Encoded image bytes
461
+ *
462
+ * @throws {FFmpegError} If the encoder is not found or encoding fails
463
+ *
464
+ * @throws {Error} If the encoder produced no output
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * const jpeg = Encoder.encodeOneSync(FF_ENCODER_MJPEG, frame, { options: { q: 3 } });
469
+ * ```
470
+ *
471
+ * @see {@link encodeOne} For async version
472
+ * @see {@link EncoderPool} For reusing encoders across recurring resolutions
473
+ */
474
+ static encodeOneSync(encoderCodec, frame, options = {}) {
475
+ const env_2 = { stack: [], error: void 0, hasError: false };
476
+ try {
477
+ const encoder = __addDisposableResource(env_2, Encoder.createSync(encoderCodec, options), false);
478
+ const packets = [...encoder.encodeAllSync(frame), ...encoder.encodeAllSync(null)];
479
+ try {
480
+ const data = packets[0]?.data;
481
+ if (!data) {
482
+ throw new Error(`Encoder '${encoder.getCodec().name}' produced no output for frame`);
483
+ }
484
+ return data;
485
+ }
486
+ finally {
487
+ for (const packet of packets) {
488
+ packet.free();
489
+ }
490
+ }
491
+ }
492
+ catch (e_2) {
493
+ env_2.error = e_2;
494
+ env_2.hasError = true;
495
+ }
496
+ finally {
497
+ __disposeResources(env_2);
498
+ }
499
+ }
392
500
  /**
393
501
  * Check if encoder is open.
394
502
  *
@@ -912,9 +1020,9 @@ export class Encoder {
912
1020
  return;
913
1021
  }
914
1022
  for await (const frame_1 of frames) {
915
- const env_1 = { stack: [], error: void 0, hasError: false };
1023
+ const env_3 = { stack: [], error: void 0, hasError: false };
916
1024
  try {
917
- const frame = __addDisposableResource(env_1, frame_1, false);
1025
+ const frame = __addDisposableResource(env_3, frame_1, false);
918
1026
  this.signal?.throwIfAborted();
919
1027
  if (frame === null) {
920
1028
  yield* finalize();
@@ -922,12 +1030,12 @@ export class Encoder {
922
1030
  }
923
1031
  yield* processFrame(frame);
924
1032
  }
925
- catch (e_1) {
926
- env_1.error = e_1;
927
- env_1.hasError = true;
1033
+ catch (e_3) {
1034
+ env_3.error = e_3;
1035
+ env_3.hasError = true;
928
1036
  }
929
1037
  finally {
930
- __disposeResources(env_1);
1038
+ __disposeResources(env_3);
931
1039
  }
932
1040
  }
933
1041
  }
@@ -1023,9 +1131,9 @@ export class Encoder {
1023
1131
  }
1024
1132
  // Case 3: Iterable of frames
1025
1133
  for (const frame_2 of frames) {
1026
- const env_2 = { stack: [], error: void 0, hasError: false };
1134
+ const env_4 = { stack: [], error: void 0, hasError: false };
1027
1135
  try {
1028
- const frame = __addDisposableResource(env_2, frame_2, false);
1136
+ const frame = __addDisposableResource(env_4, frame_2, false);
1029
1137
  // Check for EOF signal from upstream
1030
1138
  if (frame === null) {
1031
1139
  yield* finalize();
@@ -1033,12 +1141,12 @@ export class Encoder {
1033
1141
  }
1034
1142
  yield* processFrame(frame);
1035
1143
  }
1036
- catch (e_2) {
1037
- env_2.error = e_2;
1038
- env_2.hasError = true;
1144
+ catch (e_4) {
1145
+ env_4.error = e_4;
1146
+ env_4.hasError = true;
1039
1147
  }
1040
1148
  finally {
1041
- __disposeResources(env_2);
1149
+ __disposeResources(env_4);
1042
1150
  }
1043
1151
  }
1044
1152
  // No fallback flush - only flush on explicit EOF
@@ -1081,17 +1189,17 @@ export class Encoder {
1081
1189
  // For the final frame, we pad or truncate as needed
1082
1190
  let _bufferedFrame;
1083
1191
  while (!this.isClosed && (_bufferedFrame = await this.audioFrameBuffer.pull()) !== null) {
1084
- const env_3 = { stack: [], error: void 0, hasError: false };
1192
+ const env_5 = { stack: [], error: void 0, hasError: false };
1085
1193
  try {
1086
- const bufferedFrame = __addDisposableResource(env_3, _bufferedFrame, false);
1194
+ const bufferedFrame = __addDisposableResource(env_5, _bufferedFrame, false);
1087
1195
  await this.codecContext.sendFrame(bufferedFrame);
1088
1196
  }
1089
- catch (e_3) {
1090
- env_3.error = e_3;
1091
- env_3.hasError = true;
1197
+ catch (e_5) {
1198
+ env_5.error = e_5;
1199
+ env_5.hasError = true;
1092
1200
  }
1093
1201
  finally {
1094
- __disposeResources(env_3);
1202
+ __disposeResources(env_5);
1095
1203
  }
1096
1204
  }
1097
1205
  }
@@ -1141,17 +1249,17 @@ export class Encoder {
1141
1249
  // For the final frame, we pad or truncate as needed
1142
1250
  let _bufferedFrame;
1143
1251
  while (!this.isClosed && (_bufferedFrame = this.audioFrameBuffer.pullSync()) !== null) {
1144
- const env_4 = { stack: [], error: void 0, hasError: false };
1252
+ const env_6 = { stack: [], error: void 0, hasError: false };
1145
1253
  try {
1146
- const bufferedFrame = __addDisposableResource(env_4, _bufferedFrame, false);
1254
+ const bufferedFrame = __addDisposableResource(env_6, _bufferedFrame, false);
1147
1255
  this.codecContext.sendFrameSync(bufferedFrame);
1148
1256
  }
1149
- catch (e_4) {
1150
- env_4.error = e_4;
1151
- env_4.hasError = true;
1257
+ catch (e_6) {
1258
+ env_6.error = e_6;
1259
+ env_6.hasError = true;
1152
1260
  }
1153
1261
  finally {
1154
- __disposeResources(env_4);
1262
+ __disposeResources(env_6);
1155
1263
  }
1156
1264
  }
1157
1265
  }
@@ -1294,19 +1402,19 @@ export class Encoder {
1294
1402
  // Clear previous packet data
1295
1403
  this.packet.unref();
1296
1404
  if (this.audioFrameBuffer?.hasFrame()) {
1297
- const env_5 = { stack: [], error: void 0, hasError: false };
1405
+ const env_7 = { stack: [], error: void 0, hasError: false };
1298
1406
  try {
1299
- const bufferedFrame = __addDisposableResource(env_5, await this.audioFrameBuffer.pull(), false);
1407
+ const bufferedFrame = __addDisposableResource(env_7, await this.audioFrameBuffer.pull(), false);
1300
1408
  if (bufferedFrame) {
1301
1409
  await this.codecContext.sendFrame(bufferedFrame);
1302
1410
  }
1303
1411
  }
1304
- catch (e_5) {
1305
- env_5.error = e_5;
1306
- env_5.hasError = true;
1412
+ catch (e_7) {
1413
+ env_7.error = e_7;
1414
+ env_7.hasError = true;
1307
1415
  }
1308
1416
  finally {
1309
- __disposeResources(env_5);
1417
+ __disposeResources(env_7);
1310
1418
  }
1311
1419
  }
1312
1420
  const ret = await this.codecContext.receivePacket(this.packet);
@@ -1399,19 +1507,19 @@ export class Encoder {
1399
1507
  // Clear previous packet data
1400
1508
  this.packet.unref();
1401
1509
  if (this.audioFrameBuffer?.hasFrame()) {
1402
- const env_6 = { stack: [], error: void 0, hasError: false };
1510
+ const env_8 = { stack: [], error: void 0, hasError: false };
1403
1511
  try {
1404
- const bufferedFrame = __addDisposableResource(env_6, this.audioFrameBuffer.pullSync(), false);
1512
+ const bufferedFrame = __addDisposableResource(env_8, this.audioFrameBuffer.pullSync(), false);
1405
1513
  if (bufferedFrame) {
1406
1514
  this.codecContext.sendFrameSync(bufferedFrame);
1407
1515
  }
1408
1516
  }
1409
- catch (e_6) {
1410
- env_6.error = e_6;
1411
- env_6.hasError = true;
1517
+ catch (e_8) {
1518
+ env_8.error = e_8;
1519
+ env_8.hasError = true;
1412
1520
  }
1413
1521
  finally {
1414
- __disposeResources(env_6);
1522
+ __disposeResources(env_8);
1415
1523
  }
1416
1524
  }
1417
1525
  const ret = this.codecContext.receivePacketSync(this.packet);
@@ -1541,9 +1649,9 @@ export class Encoder {
1541
1649
  try {
1542
1650
  // Outer loop - receive frames
1543
1651
  while (!this.inputQueue.isClosed) {
1544
- const env_7 = { stack: [], error: void 0, hasError: false };
1652
+ const env_9 = { stack: [], error: void 0, hasError: false };
1545
1653
  try {
1546
- const frame = __addDisposableResource(env_7, await this.inputQueue.receive(), false);
1654
+ const frame = __addDisposableResource(env_9, await this.inputQueue.receive(), false);
1547
1655
  if (!frame)
1548
1656
  break;
1549
1657
  // Open encoder if not already done
@@ -1562,12 +1670,12 @@ export class Encoder {
1562
1670
  await this.outputQueue.send(packet); // Only send actual packets
1563
1671
  }
1564
1672
  }
1565
- catch (e_7) {
1566
- env_7.error = e_7;
1567
- env_7.hasError = true;
1673
+ catch (e_9) {
1674
+ env_9.error = e_9;
1675
+ env_9.hasError = true;
1568
1676
  }
1569
1677
  finally {
1570
- __disposeResources(env_7);
1678
+ __disposeResources(env_9);
1571
1679
  }
1572
1680
  }
1573
1681
  // Flush encoder at end