webcodecs-node 0.2.2 → 0.5.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 (246) hide show
  1. package/README.md +4 -0
  2. package/dist/__tests__/AudioData.test.js +2 -1
  3. package/dist/__tests__/AudioData.test.js.map +1 -1
  4. package/dist/__tests__/AudioDecoder.test.js +8 -8
  5. package/dist/__tests__/AudioDecoder.test.js.map +1 -1
  6. package/dist/__tests__/AudioEncoder.test.js +3 -3
  7. package/dist/__tests__/AudioEncoder.test.js.map +1 -1
  8. package/dist/__tests__/EncodedChunks.test.js +3 -2
  9. package/dist/__tests__/EncodedChunks.test.js.map +1 -1
  10. package/dist/__tests__/HardwareAcceleration.test.js +2 -1
  11. package/dist/__tests__/HardwareAcceleration.test.js.map +1 -1
  12. package/dist/__tests__/ImageDecoder.test.js +16 -45
  13. package/dist/__tests__/ImageDecoder.test.js.map +1 -1
  14. package/dist/__tests__/ImageDecoder.wpt.test.d.ts +8 -0
  15. package/dist/__tests__/ImageDecoder.wpt.test.d.ts.map +1 -0
  16. package/dist/__tests__/ImageDecoder.wpt.test.js +135 -0
  17. package/dist/__tests__/ImageDecoder.wpt.test.js.map +1 -0
  18. package/dist/__tests__/NodeAvDecoder.test.d.ts +2 -0
  19. package/dist/__tests__/NodeAvDecoder.test.d.ts.map +1 -0
  20. package/dist/__tests__/NodeAvDecoder.test.js +206 -0
  21. package/dist/__tests__/NodeAvDecoder.test.js.map +1 -0
  22. package/dist/__tests__/NodeAvEncoder.test.d.ts +2 -0
  23. package/dist/__tests__/NodeAvEncoder.test.d.ts.map +1 -0
  24. package/dist/__tests__/NodeAvEncoder.test.js +176 -0
  25. package/dist/__tests__/NodeAvEncoder.test.js.map +1 -0
  26. package/dist/__tests__/VideoDecoder.test.js +5 -5
  27. package/dist/__tests__/VideoDecoder.test.js.map +1 -1
  28. package/dist/__tests__/VideoEncoder.test.js +3 -3
  29. package/dist/__tests__/VideoEncoder.test.js.map +1 -1
  30. package/dist/__tests__/VideoFrame.test.js +4 -1
  31. package/dist/__tests__/VideoFrame.test.js.map +1 -1
  32. package/dist/backends/index.d.ts +3 -0
  33. package/dist/backends/index.d.ts.map +1 -0
  34. package/dist/backends/index.js +2 -0
  35. package/dist/backends/index.js.map +1 -0
  36. package/dist/backends/types.d.ts +168 -0
  37. package/dist/backends/types.d.ts.map +1 -0
  38. package/dist/backends/types.js +25 -0
  39. package/dist/backends/types.js.map +1 -0
  40. package/dist/codec-utils/audio-codecs.d.ts +60 -0
  41. package/dist/codec-utils/audio-codecs.d.ts.map +1 -0
  42. package/dist/codec-utils/audio-codecs.js +117 -0
  43. package/dist/codec-utils/audio-codecs.js.map +1 -0
  44. package/dist/codec-utils/formats.d.ts +42 -0
  45. package/dist/codec-utils/formats.d.ts.map +1 -0
  46. package/dist/codec-utils/formats.js +147 -0
  47. package/dist/codec-utils/formats.js.map +1 -0
  48. package/dist/codec-utils/index.d.ts +9 -0
  49. package/dist/codec-utils/index.d.ts.map +1 -0
  50. package/dist/codec-utils/index.js +12 -0
  51. package/dist/codec-utils/index.js.map +1 -0
  52. package/dist/codec-utils/types.d.ts +87 -0
  53. package/dist/codec-utils/types.d.ts.map +1 -0
  54. package/dist/codec-utils/types.js +10 -0
  55. package/dist/codec-utils/types.js.map +1 -0
  56. package/dist/containers/Demuxer.d.ts +114 -0
  57. package/dist/containers/Demuxer.d.ts.map +1 -0
  58. package/dist/containers/Demuxer.js +256 -0
  59. package/dist/containers/Demuxer.js.map +1 -0
  60. package/dist/containers/Muxer.d.ts +142 -0
  61. package/dist/containers/Muxer.d.ts.map +1 -0
  62. package/dist/containers/Muxer.js +295 -0
  63. package/dist/containers/Muxer.js.map +1 -0
  64. package/dist/containers/extract.d.ts +25 -0
  65. package/dist/containers/extract.d.ts.map +1 -0
  66. package/dist/containers/extract.js +64 -0
  67. package/dist/containers/extract.js.map +1 -0
  68. package/dist/containers/index.d.ts +40 -0
  69. package/dist/containers/index.d.ts.map +1 -0
  70. package/dist/containers/index.js +41 -0
  71. package/dist/containers/index.js.map +1 -0
  72. package/dist/containers/transcode.d.ts +138 -0
  73. package/dist/containers/transcode.d.ts.map +1 -0
  74. package/dist/containers/transcode.js +536 -0
  75. package/dist/containers/transcode.js.map +1 -0
  76. package/dist/core/AudioData.d.ts +5 -1
  77. package/dist/core/AudioData.d.ts.map +1 -1
  78. package/dist/core/AudioData.js +69 -13
  79. package/dist/core/AudioData.js.map +1 -1
  80. package/dist/core/EncodedAudioChunk.d.ts +1 -1
  81. package/dist/core/EncodedAudioChunk.d.ts.map +1 -1
  82. package/dist/core/EncodedAudioChunk.js +1 -1
  83. package/dist/core/EncodedAudioChunk.js.map +1 -1
  84. package/dist/core/EncodedVideoChunk.d.ts.map +1 -1
  85. package/dist/core/EncodedVideoChunk.js +3 -19
  86. package/dist/core/EncodedVideoChunk.js.map +1 -1
  87. package/dist/core/VideoFrame.d.ts +31 -11
  88. package/dist/core/VideoFrame.d.ts.map +1 -1
  89. package/dist/core/VideoFrame.js +244 -81
  90. package/dist/core/VideoFrame.js.map +1 -1
  91. package/dist/decoders/AudioDecoder.d.ts +14 -12
  92. package/dist/decoders/AudioDecoder.d.ts.map +1 -1
  93. package/dist/decoders/AudioDecoder.js +113 -173
  94. package/dist/decoders/AudioDecoder.js.map +1 -1
  95. package/dist/decoders/ImageDecoder.d.ts +1 -3
  96. package/dist/decoders/ImageDecoder.d.ts.map +1 -1
  97. package/dist/decoders/ImageDecoder.js +39 -192
  98. package/dist/decoders/ImageDecoder.js.map +1 -1
  99. package/dist/decoders/VideoDecoder.d.ts +14 -13
  100. package/dist/decoders/VideoDecoder.d.ts.map +1 -1
  101. package/dist/decoders/VideoDecoder.js +115 -166
  102. package/dist/decoders/VideoDecoder.js.map +1 -1
  103. package/dist/demos/demo-1080p-transcode.d.ts +8 -0
  104. package/dist/demos/demo-1080p-transcode.d.ts.map +1 -0
  105. package/dist/demos/demo-1080p-transcode.js +188 -0
  106. package/dist/demos/demo-1080p-transcode.js.map +1 -0
  107. package/dist/demos/demo-containers.d.ts +7 -0
  108. package/dist/demos/demo-containers.d.ts.map +1 -0
  109. package/dist/demos/demo-containers.js +140 -0
  110. package/dist/demos/demo-containers.js.map +1 -0
  111. package/dist/demos/demo-conversion.js +2 -2
  112. package/dist/demos/demo-conversion.js.map +1 -1
  113. package/dist/demos/demo-four-corners.d.ts +6 -0
  114. package/dist/demos/demo-four-corners.d.ts.map +1 -0
  115. package/dist/demos/demo-four-corners.js +218 -0
  116. package/dist/demos/demo-four-corners.js.map +1 -0
  117. package/dist/demos/demo-hwaccel-conversion.js +1 -1
  118. package/dist/demos/demo-hwaccel-conversion.js.map +1 -1
  119. package/dist/demos/demo-hwaccel.js +1 -1
  120. package/dist/demos/demo-hwaccel.js.map +1 -1
  121. package/dist/demos/demo-samples.js +232 -68
  122. package/dist/demos/demo-samples.js.map +1 -1
  123. package/dist/demos/demo-streaming.js +6 -6
  124. package/dist/demos/demo-streaming.js.map +1 -1
  125. package/dist/encoders/AudioEncoder.d.ts +14 -13
  126. package/dist/encoders/AudioEncoder.d.ts.map +1 -1
  127. package/dist/encoders/AudioEncoder.js +138 -228
  128. package/dist/encoders/AudioEncoder.js.map +1 -1
  129. package/dist/encoders/VideoEncoder.d.ts +13 -14
  130. package/dist/encoders/VideoEncoder.d.ts.map +1 -1
  131. package/dist/encoders/VideoEncoder.js +92 -161
  132. package/dist/encoders/VideoEncoder.js.map +1 -1
  133. package/dist/ffmpeg/index.d.ts +3 -2
  134. package/dist/ffmpeg/index.d.ts.map +1 -1
  135. package/dist/ffmpeg/index.js +3 -3
  136. package/dist/ffmpeg/index.js.map +1 -1
  137. package/dist/formats/conversions/batch-converter.d.ts +61 -0
  138. package/dist/formats/conversions/batch-converter.d.ts.map +1 -0
  139. package/dist/formats/conversions/batch-converter.js +274 -0
  140. package/dist/formats/conversions/batch-converter.js.map +1 -0
  141. package/dist/formats/conversions/frame-converter.d.ts +17 -0
  142. package/dist/formats/conversions/frame-converter.d.ts.map +1 -1
  143. package/dist/formats/conversions/frame-converter.js +144 -10
  144. package/dist/formats/conversions/frame-converter.js.map +1 -1
  145. package/dist/formats/conversions/index.d.ts +2 -0
  146. package/dist/formats/conversions/index.d.ts.map +1 -1
  147. package/dist/formats/conversions/index.js +4 -0
  148. package/dist/formats/conversions/index.js.map +1 -1
  149. package/dist/hardware/decoder-args.d.ts +2 -7
  150. package/dist/hardware/decoder-args.d.ts.map +1 -1
  151. package/dist/hardware/decoder-args.js +2 -32
  152. package/dist/hardware/decoder-args.js.map +1 -1
  153. package/dist/hardware/detection.d.ts +1 -1
  154. package/dist/hardware/detection.d.ts.map +1 -1
  155. package/dist/hardware/detection.js +54 -27
  156. package/dist/hardware/detection.js.map +1 -1
  157. package/dist/hardware/encoder-args.d.ts +2 -11
  158. package/dist/hardware/encoder-args.d.ts.map +1 -1
  159. package/dist/hardware/encoder-args.js +2 -71
  160. package/dist/hardware/encoder-args.js.map +1 -1
  161. package/dist/hardware/index.d.ts +2 -2
  162. package/dist/hardware/index.d.ts.map +1 -1
  163. package/dist/hardware/index.js +2 -2
  164. package/dist/hardware/index.js.map +1 -1
  165. package/dist/index.d.ts +5 -7
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +5 -6
  168. package/dist/index.js.map +1 -1
  169. package/dist/mediabunny/FFmpegAudioDecoder.d.ts +4 -17
  170. package/dist/mediabunny/FFmpegAudioDecoder.d.ts.map +1 -1
  171. package/dist/mediabunny/FFmpegAudioDecoder.js +57 -185
  172. package/dist/mediabunny/FFmpegAudioDecoder.js.map +1 -1
  173. package/dist/mediabunny/FFmpegAudioEncoder.d.ts +6 -39
  174. package/dist/mediabunny/FFmpegAudioEncoder.d.ts.map +1 -1
  175. package/dist/mediabunny/FFmpegAudioEncoder.js +66 -329
  176. package/dist/mediabunny/FFmpegAudioEncoder.js.map +1 -1
  177. package/dist/mediabunny/FFmpegVideoDecoder.d.ts +5 -38
  178. package/dist/mediabunny/FFmpegVideoDecoder.d.ts.map +1 -1
  179. package/dist/mediabunny/FFmpegVideoDecoder.js +39 -283
  180. package/dist/mediabunny/FFmpegVideoDecoder.js.map +1 -1
  181. package/dist/mediabunny/FFmpegVideoEncoder.d.ts +9 -37
  182. package/dist/mediabunny/FFmpegVideoEncoder.d.ts.map +1 -1
  183. package/dist/mediabunny/FFmpegVideoEncoder.js +49 -336
  184. package/dist/mediabunny/FFmpegVideoEncoder.js.map +1 -1
  185. package/dist/node-av/HardwarePipeline.d.ts +36 -0
  186. package/dist/node-av/HardwarePipeline.d.ts.map +1 -0
  187. package/dist/node-av/HardwarePipeline.js +243 -0
  188. package/dist/node-av/HardwarePipeline.js.map +1 -0
  189. package/dist/node-av/NodeAvAudioDecoder.d.ts +46 -0
  190. package/dist/node-av/NodeAvAudioDecoder.d.ts.map +1 -0
  191. package/dist/node-av/NodeAvAudioDecoder.js +350 -0
  192. package/dist/node-av/NodeAvAudioDecoder.js.map +1 -0
  193. package/dist/node-av/NodeAvAudioEncoder.d.ts +50 -0
  194. package/dist/node-av/NodeAvAudioEncoder.d.ts.map +1 -0
  195. package/dist/node-av/NodeAvAudioEncoder.js +506 -0
  196. package/dist/node-av/NodeAvAudioEncoder.js.map +1 -0
  197. package/dist/node-av/NodeAvImageDecoder.d.ts +114 -0
  198. package/dist/node-av/NodeAvImageDecoder.d.ts.map +1 -0
  199. package/dist/node-av/NodeAvImageDecoder.js +406 -0
  200. package/dist/node-av/NodeAvImageDecoder.js.map +1 -0
  201. package/dist/node-av/NodeAvVideoDecoder.d.ts +43 -0
  202. package/dist/node-av/NodeAvVideoDecoder.d.ts.map +1 -0
  203. package/dist/node-av/NodeAvVideoDecoder.js +365 -0
  204. package/dist/node-av/NodeAvVideoDecoder.js.map +1 -0
  205. package/dist/node-av/NodeAvVideoEncoder.d.ts +56 -0
  206. package/dist/node-av/NodeAvVideoEncoder.d.ts.map +1 -0
  207. package/dist/node-av/NodeAvVideoEncoder.js +509 -0
  208. package/dist/node-av/NodeAvVideoEncoder.js.map +1 -0
  209. package/dist/polyfill.d.ts.map +1 -1
  210. package/dist/polyfill.js +11 -9
  211. package/dist/polyfill.js.map +1 -1
  212. package/dist/utils/aac.d.ts.map +1 -1
  213. package/dist/utils/aac.js +6 -0
  214. package/dist/utils/aac.js.map +1 -1
  215. package/dist/utils/buffer-pool.d.ts +109 -0
  216. package/dist/utils/buffer-pool.d.ts.map +1 -0
  217. package/dist/utils/buffer-pool.js +278 -0
  218. package/dist/utils/buffer-pool.js.map +1 -0
  219. package/dist/utils/codec-cache.d.ts +65 -0
  220. package/dist/utils/codec-cache.d.ts.map +1 -0
  221. package/dist/utils/codec-cache.js +116 -0
  222. package/dist/utils/codec-cache.js.map +1 -0
  223. package/dist/utils/errors.d.ts +62 -0
  224. package/dist/utils/errors.d.ts.map +1 -0
  225. package/dist/utils/errors.js +84 -0
  226. package/dist/utils/errors.js.map +1 -0
  227. package/dist/utils/event-target.d.ts +42 -0
  228. package/dist/utils/event-target.d.ts.map +1 -0
  229. package/dist/utils/event-target.js +96 -0
  230. package/dist/utils/event-target.js.map +1 -0
  231. package/dist/utils/hardware-pool.d.ts +88 -0
  232. package/dist/utils/hardware-pool.d.ts.map +1 -0
  233. package/dist/utils/hardware-pool.js +266 -0
  234. package/dist/utils/hardware-pool.js.map +1 -0
  235. package/dist/utils/index.d.ts +6 -0
  236. package/dist/utils/index.d.ts.map +1 -1
  237. package/dist/utils/index.js +12 -0
  238. package/dist/utils/index.js.map +1 -1
  239. package/dist/utils/timeout.d.ts +44 -0
  240. package/dist/utils/timeout.d.ts.map +1 -0
  241. package/dist/utils/timeout.js +73 -0
  242. package/dist/utils/timeout.js.map +1 -0
  243. package/dist/utils/type-guards.d.ts +11 -1
  244. package/dist/utils/type-guards.d.ts.map +1 -1
  245. package/dist/utils/type-guards.js.map +1 -1
  246. package/package.json +20 -34
@@ -2,9 +2,10 @@
2
2
  * AudioEncoder - Encodes AudioData into EncodedAudioChunks
3
3
  * https://developer.mozilla.org/en-US/docs/Web/API/AudioEncoder
4
4
  */
5
- import { EventEmitter } from 'events';
5
+ import { WebCodecsEventTarget } from '../utils/event-target.js';
6
6
  import { AudioData } from '../core/AudioData.js';
7
7
  import { EncodedAudioChunk } from '../core/EncodedAudioChunk.js';
8
+ type EventHandler = ((event: Event) => void) | null;
8
9
  export type CodecState = 'unconfigured' | 'configured' | 'closed';
9
10
  export interface AudioEncoderConfig {
10
11
  codec: string;
@@ -31,24 +32,28 @@ export interface AudioEncoderSupport {
31
32
  supported: boolean;
32
33
  config: AudioEncoderConfig;
33
34
  }
34
- export declare class AudioEncoder extends EventEmitter {
35
+ export declare class AudioEncoder extends WebCodecsEventTarget {
35
36
  private _state;
36
37
  private _encodeQueueSize;
37
38
  private _config;
38
39
  private _outputCallback;
39
40
  private _errorCallback;
40
- private _process;
41
+ private _encoder;
41
42
  private _frameCount;
42
43
  private _firstChunk;
43
- private _accumulatedData;
44
44
  private _ffmpegCodec;
45
45
  private _bitstreamFormat;
46
46
  private _codecDescription;
47
+ private _ondequeue;
47
48
  constructor(init: AudioEncoderInit);
48
49
  get state(): CodecState;
49
50
  get encodeQueueSize(): number;
50
- private get _isProcessHealthy();
51
+ /** Event handler called when encodeQueueSize decreases */
52
+ get ondequeue(): EventHandler;
53
+ set ondequeue(handler: EventHandler);
51
54
  private _safeErrorCallback;
55
+ /** Fire the dequeue event (both EventTarget and ondequeue handler) */
56
+ private _fireDequeueEvent;
52
57
  private _safeOutputCallback;
53
58
  static isConfigSupported(config: AudioEncoderConfig): Promise<AudioEncoderSupport>;
54
59
  configure(config: AudioEncoderConfig): void;
@@ -56,14 +61,10 @@ export declare class AudioEncoder extends EventEmitter {
56
61
  flush(timeout?: number): Promise<void>;
57
62
  reset(): void;
58
63
  close(): void;
59
- private _startFFmpeg;
60
- private _stopFFmpeg;
64
+ private _startEncoder;
65
+ private _stopEncoder;
66
+ private _handleEncodedFrame;
61
67
  private _audioDataToPCM;
62
- private _parseEncodedFrames;
63
- private _findFrameEnd;
64
- private _findADTSFrame;
65
- private _findMP3Frame;
66
- private _findOggPage;
67
- private _emitChunk;
68
68
  }
69
+ export {};
69
70
  //# sourceMappingURL=AudioEncoder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AudioEncoder.d.ts","sourceRoot":"","sources":["../../src/encoders/AudioEncoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAcjE,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,YAAY,GAAG,QAAQ,CAAC;AAElE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,WAAW,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAClF,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,CAAC,EAAE;QACd,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,UAAU,CAAC;KAC1B,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAID,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,eAAe,CAA4E;IACnG,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,iBAAiB,CAA2B;gBAExC,IAAI,EAAE,gBAAgB;IAclC,IAAI,KAAK,IAAI,UAAU,CAAwB;IAC/C,IAAI,eAAe,IAAI,MAAM,CAAkC;IAE/D,OAAO,KAAK,iBAAiB,GAE5B;IAED,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,mBAAmB;WAQd,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAWxF,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAiD3C,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IA6BvB,KAAK,CAAC,OAAO,GAAE,MAA8B,GAAG,OAAO,CAAC,IAAI,CAAC;IAqDnE,KAAK,IAAI,IAAI;IAeb,KAAK,IAAI,IAAI;IAUb,OAAO,CAAC,YAAY;IA8DpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,eAAe;IA+BvB,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,UAAU;CA8CnB"}
1
+ {"version":3,"file":"AudioEncoder.d.ts","sourceRoot":"","sources":["../../src/encoders/AudioEncoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,KAAK,YAAY,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;AAiBpD,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,YAAY,GAAG,QAAQ,CAAC;AAElE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,WAAW,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAClF,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,CAAC,EAAE;QACd,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,UAAU,CAAC;KAC1B,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAKD,qBAAa,YAAa,SAAQ,oBAAoB;IACpD,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,eAAe,CAA4E;IACnG,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,UAAU,CAA6B;gBAEnC,IAAI,EAAE,gBAAgB;IAclC,IAAI,KAAK,IAAI,UAAU,CAAwB;IAC/C,IAAI,eAAe,IAAI,MAAM,CAAkC;IAE/D,0DAA0D;IAC1D,IAAI,SAAS,IAAI,YAAY,CAA4B;IACzD,IAAI,SAAS,CAAC,OAAO,EAAE,YAAY,EAAgC;IAEnE,OAAO,CAAC,kBAAkB;IAQ1B,sEAAsE;IACtE,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,mBAAmB;WAQd,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAWxF,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAwC3C,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAqDvB,KAAK,CAAC,OAAO,GAAE,MAA8B,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDnE,KAAK,IAAI,IAAI;IAcb,KAAK,IAAI,IAAI;IAUb,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,mBAAmB;IAqD3B,OAAO,CAAC,eAAe;CAoCxB"}
@@ -2,29 +2,31 @@
2
2
  * AudioEncoder - Encodes AudioData into EncodedAudioChunks
3
3
  * https://developer.mozilla.org/en-US/docs/Web/API/AudioEncoder
4
4
  */
5
- import { EventEmitter } from 'events';
6
- import { spawn } from 'child_process';
5
+ import { WebCodecsEventTarget } from '../utils/event-target.js';
7
6
  import { AudioData } from '../core/AudioData.js';
8
7
  import { EncodedAudioChunk } from '../core/EncodedAudioChunk.js';
9
8
  import { DOMException } from '../types/index.js';
10
- import { createLogger } from '../utils/index.js';
11
- import { getAudioEncoderCodec, getAudioEncoderFormat, getAudioFrameSize, AUDIO_ENCODER_CODEC_MAP, } from '../ffmpeg/audio-codecs.js';
9
+ import { getAudioEncoderCodec, getAudioFrameSize, AUDIO_ENCODER_CODEC_MAP, } from '../codec-utils/audio-codecs.js';
12
10
  import { buildAudioSpecificConfig, stripAdtsHeader } from '../utils/aac.js';
13
- const logger = createLogger('AudioEncoder');
11
+ import { NodeAvAudioEncoder } from '../node-av/NodeAvAudioEncoder.js';
12
+ import { validateNonEmptyString, validatePositiveInteger, validateRequired, } from '../utils/validation.js';
13
+ import { getCodecBase } from '../utils/codec-cache.js';
14
+ import { encodingError, wrapAsWebCodecsError } from '../utils/errors.js';
14
15
  const DEFAULT_FLUSH_TIMEOUT = 30000;
15
- export class AudioEncoder extends EventEmitter {
16
+ const MAX_QUEUE_SIZE = 100; // Prevent unbounded memory growth
17
+ export class AudioEncoder extends WebCodecsEventTarget {
16
18
  _state = 'unconfigured';
17
19
  _encodeQueueSize = 0;
18
20
  _config = null;
19
21
  _outputCallback;
20
22
  _errorCallback;
21
- _process = null;
23
+ _encoder = null;
22
24
  _frameCount = 0;
23
25
  _firstChunk = true;
24
- _accumulatedData = Buffer.alloc(0);
25
26
  _ffmpegCodec = '';
26
27
  _bitstreamFormat = 'adts';
27
28
  _codecDescription = null;
29
+ _ondequeue = null;
28
30
  constructor(init) {
29
31
  super();
30
32
  if (!init || typeof init.output !== 'function') {
@@ -38,9 +40,9 @@ export class AudioEncoder extends EventEmitter {
38
40
  }
39
41
  get state() { return this._state; }
40
42
  get encodeQueueSize() { return this._encodeQueueSize; }
41
- get _isProcessHealthy() {
42
- return this._process !== null && this._process.stdin?.writable === true;
43
- }
43
+ /** Event handler called when encodeQueueSize decreases */
44
+ get ondequeue() { return this._ondequeue; }
45
+ set ondequeue(handler) { this._ondequeue = handler; }
44
46
  _safeErrorCallback(error) {
45
47
  try {
46
48
  this._errorCallback(error);
@@ -49,19 +51,31 @@ export class AudioEncoder extends EventEmitter {
49
51
  this.emit('callbackError', error);
50
52
  }
51
53
  }
54
+ /** Fire the dequeue event (both EventTarget and ondequeue handler) */
55
+ _fireDequeueEvent() {
56
+ this.emit('dequeue');
57
+ if (this._ondequeue) {
58
+ try {
59
+ this._ondequeue(new Event('dequeue'));
60
+ }
61
+ catch {
62
+ // Ignore errors in user handler per spec
63
+ }
64
+ }
65
+ }
52
66
  _safeOutputCallback(chunk, metadata) {
53
67
  try {
54
68
  this._outputCallback(chunk, metadata);
55
69
  }
56
70
  catch (err) {
57
- this._safeErrorCallback(err instanceof Error ? err : new Error(String(err)));
71
+ this._safeErrorCallback(wrapAsWebCodecsError(err, 'EncodingError'));
58
72
  }
59
73
  }
60
74
  static async isConfigSupported(config) {
61
75
  if (!config.codec || !config.sampleRate || !config.numberOfChannels) {
62
76
  return { supported: false, config };
63
77
  }
64
- const codecBase = config.codec.split('.')[0].toLowerCase();
78
+ const codecBase = getCodecBase(config.codec);
65
79
  const supported = codecBase in AUDIO_ENCODER_CODEC_MAP || config.codec in AUDIO_ENCODER_CODEC_MAP;
66
80
  return { supported, config };
67
81
  }
@@ -69,18 +83,13 @@ export class AudioEncoder extends EventEmitter {
69
83
  if (this._state === 'closed') {
70
84
  throw new DOMException('Encoder is closed', 'InvalidStateError');
71
85
  }
72
- if (!config || typeof config !== 'object') {
86
+ validateRequired(config, 'config');
87
+ if (typeof config !== 'object') {
73
88
  throw new TypeError('config must be an object');
74
89
  }
75
- if (typeof config.codec !== 'string' || config.codec.length === 0) {
76
- throw new TypeError('codec must be a non-empty string');
77
- }
78
- if (typeof config.sampleRate !== 'number' || config.sampleRate <= 0 || !Number.isInteger(config.sampleRate)) {
79
- throw new TypeError('sampleRate must be a positive integer');
80
- }
81
- if (typeof config.numberOfChannels !== 'number' || config.numberOfChannels <= 0 || !Number.isInteger(config.numberOfChannels)) {
82
- throw new TypeError('numberOfChannels must be a positive integer');
83
- }
90
+ validateNonEmptyString(config.codec, 'codec');
91
+ validatePositiveInteger(config.sampleRate, 'sampleRate');
92
+ validatePositiveInteger(config.numberOfChannels, 'numberOfChannels');
84
93
  if (config.bitrate !== undefined && (typeof config.bitrate !== 'number' || config.bitrate <= 0)) {
85
94
  throw new TypeError('bitrate must be a positive number');
86
95
  }
@@ -94,18 +103,14 @@ export class AudioEncoder extends EventEmitter {
94
103
  if (!ffmpegCodec) {
95
104
  throw new DOMException(`Codec '${config.codec}' is not supported`, 'NotSupportedError');
96
105
  }
97
- if (this._process) {
98
- this._process.kill();
99
- this._process = null;
100
- }
106
+ this._stopEncoder();
101
107
  this._config = { ...config };
102
108
  this._state = 'configured';
103
109
  this._frameCount = 0;
104
110
  this._firstChunk = true;
105
- this._accumulatedData = Buffer.alloc(0);
106
111
  this._bitstreamFormat = config.format ?? 'adts';
107
112
  this._codecDescription = null;
108
- this._startFFmpeg();
113
+ this._startEncoder();
109
114
  }
110
115
  encode(data) {
111
116
  if (this._state !== 'configured') {
@@ -114,18 +119,39 @@ export class AudioEncoder extends EventEmitter {
114
119
  if (!(data instanceof AudioData)) {
115
120
  throw new TypeError('data must be an AudioData');
116
121
  }
117
- if (!this._isProcessHealthy) {
118
- this._safeErrorCallback(new Error('Encoder process is not healthy'));
122
+ if (!this._encoder?.isHealthy) {
123
+ this._safeErrorCallback(encodingError('Encoder is not healthy'));
124
+ return;
125
+ }
126
+ // Check queue saturation to prevent unbounded memory growth
127
+ if (this._encodeQueueSize >= MAX_QUEUE_SIZE) {
128
+ this._safeErrorCallback(new DOMException(`Encoder queue saturated (${MAX_QUEUE_SIZE} frames pending). Wait for dequeue events before encoding more frames.`, 'QuotaExceededError'));
119
129
  return;
120
130
  }
121
131
  this._encodeQueueSize++;
122
- const pcmData = this._audioDataToPCM(data);
123
- try {
124
- this._process.stdin.write(pcmData);
132
+ const nativeFrame = data._native ?? null;
133
+ let writeSuccess = false;
134
+ if (nativeFrame && typeof this._encoder.writeFrame === 'function') {
135
+ const clone = typeof nativeFrame.clone === 'function' ? nativeFrame.clone() : null;
136
+ const frameForEncode = clone ?? nativeFrame;
137
+ try {
138
+ writeSuccess = this._encoder.writeFrame(frameForEncode, true);
139
+ }
140
+ catch {
141
+ writeSuccess = false;
142
+ }
143
+ if (!writeSuccess) {
144
+ const pcmData = this._audioDataToPCM(data);
145
+ writeSuccess = this._encoder.write(pcmData);
146
+ }
125
147
  }
126
- catch {
148
+ else {
149
+ const pcmData = this._audioDataToPCM(data);
150
+ writeSuccess = this._encoder.write(pcmData);
151
+ }
152
+ if (!writeSuccess) {
127
153
  this._encodeQueueSize = Math.max(0, this._encodeQueueSize - 1);
128
- this._safeErrorCallback(new Error('Failed to write audio data to encoder'));
154
+ this._safeErrorCallback(encodingError('Failed to write audio data to encoder'));
129
155
  return;
130
156
  }
131
157
  this._frameCount += data.numberOfFrames;
@@ -135,7 +161,7 @@ export class AudioEncoder extends EventEmitter {
135
161
  throw new DOMException('Encoder is not configured', 'InvalidStateError');
136
162
  }
137
163
  return new Promise((resolve, reject) => {
138
- if (!this._process) {
164
+ if (!this._encoder) {
139
165
  resolve();
140
166
  return;
141
167
  }
@@ -155,10 +181,9 @@ export class AudioEncoder extends EventEmitter {
155
181
  this._encodeQueueSize = 0;
156
182
  this._frameCount = 0;
157
183
  this._firstChunk = true;
158
- this._accumulatedData = Buffer.alloc(0);
159
- this._process = null;
184
+ this._encoder = null;
160
185
  if (this._config) {
161
- this._startFFmpeg();
186
+ this._startEncoder();
162
187
  }
163
188
  resolve();
164
189
  };
@@ -172,227 +197,83 @@ export class AudioEncoder extends EventEmitter {
172
197
  timeoutId = setTimeout(() => {
173
198
  doReject(new DOMException('Flush operation timed out', 'TimeoutError'));
174
199
  }, timeout);
175
- this._process.once('close', doResolve);
176
- this._process.once('error', doReject);
177
- this._process.stdin?.end();
200
+ this._encoder.once('close', doResolve);
201
+ this._encoder.once('error', doReject);
202
+ this._encoder.end();
178
203
  });
179
204
  }
180
205
  reset() {
181
206
  if (this._state === 'closed') {
182
207
  throw new DOMException('Encoder is closed', 'InvalidStateError');
183
208
  }
184
- this._stopFFmpeg();
209
+ this._stopEncoder();
185
210
  this._state = 'unconfigured';
186
211
  this._config = null;
187
212
  this._encodeQueueSize = 0;
188
213
  this._frameCount = 0;
189
214
  this._firstChunk = true;
190
- this._accumulatedData = Buffer.alloc(0);
191
215
  this._codecDescription = null;
192
216
  }
193
217
  close() {
194
218
  if (this._state === 'closed')
195
219
  return;
196
- this._stopFFmpeg();
220
+ this._stopEncoder();
197
221
  this._state = 'closed';
198
222
  this._config = null;
199
223
  this._encodeQueueSize = 0;
200
224
  this._codecDescription = null;
201
225
  }
202
- _startFFmpeg() {
226
+ _startEncoder() {
203
227
  if (!this._config)
204
228
  return;
205
- // Codec was already validated in configure(), so we can safely use a fallback here
206
229
  this._ffmpegCodec = getAudioEncoderCodec(this._config.codec) || 'aac';
207
- const format = getAudioEncoderFormat(this._ffmpegCodec);
208
- const args = [
209
- '-hide_banner', '-loglevel', 'error',
210
- '-f', 'f32le',
211
- '-ar', String(this._config.sampleRate),
212
- '-ac', String(this._config.numberOfChannels),
213
- '-i', 'pipe:0',
214
- '-c:a', this._ffmpegCodec,
215
- ];
216
- if (this._config.bitrate) {
217
- args.push('-b:a', String(this._config.bitrate));
218
- }
219
- const isRealtime = this._config.latencyMode === 'realtime';
220
- if (this._ffmpegCodec === 'libopus') {
221
- args.push('-application', isRealtime ? 'voip' : 'audio');
222
- if (isRealtime) {
223
- args.push('-frame_duration', '10');
224
- }
225
- if (this._config.sampleRate !== 48000) {
226
- args.push('-ar', '48000');
227
- }
228
- }
229
- else if (this._ffmpegCodec === 'aac') {
230
- if (isRealtime) {
231
- args.push('-profile:a', 'aac_low');
232
- }
233
- }
234
- args.push('-f', format, 'pipe:1');
235
- this._process = spawn('ffmpeg', args, { stdio: ['pipe', 'pipe', 'pipe'] });
236
- this._process.stdout?.on('data', (data) => {
237
- this._accumulatedData = Buffer.concat([this._accumulatedData, data]);
238
- this._parseEncodedFrames();
230
+ this._encoder = new NodeAvAudioEncoder();
231
+ this._encoder.startEncoder({
232
+ codec: this._config.codec,
233
+ sampleRate: this._config.sampleRate,
234
+ numberOfChannels: this._config.numberOfChannels,
235
+ bitrate: this._config.bitrate,
236
+ bitrateMode: this._config.bitrateMode,
237
+ latencyMode: this._config.latencyMode,
239
238
  });
240
- this._process.stderr?.on('data', (data) => {
241
- const msg = data.toString();
242
- if (!msg.includes('Discarding ID3')) {
243
- logger.warn('FFmpeg stderr', { message: msg });
244
- }
239
+ this._encoder.on('encodedFrame', (frame) => {
240
+ this._handleEncodedFrame(frame);
245
241
  });
246
- this._process.on('close', () => {
247
- if (this._accumulatedData.length > 0) {
248
- this._emitChunk(this._accumulatedData, 'key');
249
- this._accumulatedData = Buffer.alloc(0);
250
- }
242
+ this._encoder.on('frameAccepted', () => {
243
+ // Frame has started processing - decrement queue and fire dequeue event
244
+ this._encodeQueueSize = Math.max(0, this._encodeQueueSize - 1);
245
+ this._fireDequeueEvent();
246
+ });
247
+ this._encoder.on('error', (err) => {
248
+ this._safeErrorCallback(err);
251
249
  });
252
- this._process.stdin?.on('error', () => { });
253
- }
254
- _stopFFmpeg() {
255
- if (this._process) {
256
- this._process.kill('SIGTERM');
257
- this._process = null;
258
- }
259
- }
260
- _audioDataToPCM(data) {
261
- const numFrames = data.numberOfFrames;
262
- const numChannels = data.numberOfChannels;
263
- const bufferSize = numFrames * numChannels * 4;
264
- const buffer = Buffer.alloc(bufferSize);
265
- const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
266
- const isPlanar = data.format.endsWith('-planar');
267
- const tempBuffer = new Float32Array(numFrames);
268
- if (isPlanar) {
269
- for (let ch = 0; ch < numChannels; ch++) {
270
- data.copyTo(new Uint8Array(tempBuffer.buffer), {
271
- planeIndex: ch,
272
- format: 'f32-planar',
273
- });
274
- for (let frame = 0; frame < numFrames; frame++) {
275
- const offset = (frame * numChannels + ch) * 4;
276
- view.setFloat32(offset, tempBuffer[frame], true);
277
- }
278
- }
279
- }
280
- else {
281
- const srcBuffer = new Uint8Array(bufferSize);
282
- data.copyTo(srcBuffer, { planeIndex: 0, format: 'f32' });
283
- buffer.set(srcBuffer);
284
- }
285
- return buffer;
286
- }
287
- _parseEncodedFrames() {
288
- const minChunkSize = 64;
289
- while (this._accumulatedData.length >= minChunkSize) {
290
- let frameEnd = this._findFrameEnd();
291
- if (frameEnd > 0) {
292
- const frameData = Buffer.from(this._accumulatedData.subarray(0, frameEnd));
293
- this._accumulatedData = this._accumulatedData.subarray(frameEnd);
294
- this._emitChunk(frameData, 'key');
295
- }
296
- else {
297
- break;
298
- }
299
- }
300
- }
301
- _findFrameEnd() {
302
- if (this._ffmpegCodec === 'aac') {
303
- return this._findADTSFrame();
304
- }
305
- else if (this._ffmpegCodec === 'libmp3lame') {
306
- return this._findMP3Frame();
307
- }
308
- else if (this._ffmpegCodec === 'libopus' || this._ffmpegCodec === 'libvorbis') {
309
- return this._findOggPage();
310
- }
311
- else {
312
- return Math.min(this._accumulatedData.length, 4096);
313
- }
314
- }
315
- _findADTSFrame() {
316
- if (this._accumulatedData.length < 7)
317
- return 0;
318
- if ((this._accumulatedData[0] !== 0xFF) || ((this._accumulatedData[1] & 0xF0) !== 0xF0)) {
319
- for (let i = 1; i < this._accumulatedData.length - 1; i++) {
320
- if (this._accumulatedData[i] === 0xFF && (this._accumulatedData[i + 1] & 0xF0) === 0xF0) {
321
- this._accumulatedData = this._accumulatedData.subarray(i);
322
- return 0;
323
- }
324
- }
325
- return 0;
326
- }
327
- const frameLength = ((this._accumulatedData[3] & 0x03) << 11) |
328
- (this._accumulatedData[4] << 3) |
329
- ((this._accumulatedData[5] & 0xE0) >> 5);
330
- if (frameLength > this._accumulatedData.length)
331
- return 0;
332
- return frameLength;
333
250
  }
334
- _findMP3Frame() {
335
- if (this._accumulatedData.length < 4)
336
- return 0;
337
- if (this._accumulatedData[0] !== 0xFF || (this._accumulatedData[1] & 0xE0) !== 0xE0) {
338
- for (let i = 1; i < this._accumulatedData.length - 1; i++) {
339
- if (this._accumulatedData[i] === 0xFF && (this._accumulatedData[i + 1] & 0xE0) === 0xE0) {
340
- this._accumulatedData = this._accumulatedData.subarray(i);
341
- return 0;
342
- }
343
- }
344
- return 0;
251
+ _stopEncoder() {
252
+ if (this._encoder) {
253
+ this._encoder.kill();
254
+ this._encoder = null;
345
255
  }
346
- const header = this._accumulatedData.readUInt32BE(0);
347
- const bitrateIndex = (header >> 12) & 0x0F;
348
- const samplingRateIndex = (header >> 10) & 0x03;
349
- const padding = (header >> 9) & 0x01;
350
- const bitrates = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0];
351
- const sampleRates = [44100, 48000, 32000, 0];
352
- const bitrate = bitrates[bitrateIndex] * 1000;
353
- const sampleRate = sampleRates[samplingRateIndex];
354
- if (bitrate === 0 || sampleRate === 0)
355
- return 0;
356
- const frameSize = Math.floor((144 * bitrate) / sampleRate) + padding;
357
- if (frameSize > this._accumulatedData.length)
358
- return 0;
359
- return frameSize;
360
256
  }
361
- _findOggPage() {
362
- if (this._accumulatedData.length < 27)
363
- return 0;
364
- if (this._accumulatedData.toString('ascii', 0, 4) !== 'OggS') {
365
- for (let i = 1; i < this._accumulatedData.length - 3; i++) {
366
- if (this._accumulatedData.toString('ascii', i, i + 4) === 'OggS') {
367
- this._accumulatedData = this._accumulatedData.subarray(i);
368
- return 0;
369
- }
370
- }
371
- return this._accumulatedData.length;
372
- }
373
- const numSegments = this._accumulatedData[26];
374
- if (this._accumulatedData.length < 27 + numSegments)
375
- return 0;
376
- let pageSize = 27 + numSegments;
377
- for (let i = 0; i < numSegments; i++) {
378
- pageSize += this._accumulatedData[27 + i];
379
- }
380
- if (pageSize > this._accumulatedData.length)
381
- return 0;
382
- return pageSize;
383
- }
384
- _emitChunk(data, type) {
385
- if (!this._config || data.length === 0)
257
+ _handleEncodedFrame(frame) {
258
+ if (!this._config)
386
259
  return;
387
260
  const samplesPerFrame = getAudioFrameSize(this._ffmpegCodec) || 1024;
388
- const timestamp = (this._frameCount * 1_000_000) / this._config.sampleRate;
261
+ // Use timestamp from backend (in samples) converted to microseconds
262
+ // Clamp to non-negative (AAC encoder has priming delay causing negative initial timestamps)
263
+ const timestamp = Math.max(0, (frame.timestamp * 1_000_000) / this._config.sampleRate);
389
264
  const duration = (samplesPerFrame * 1_000_000) / this._config.sampleRate;
390
- let payload = data;
391
- const codecBase = this._config.codec.split('.')[0].toLowerCase();
265
+ let payload = frame.data;
266
+ const codecBase = getCodecBase(this._config.codec);
392
267
  const isAac = codecBase === 'mp4a' || codecBase === 'aac';
268
+ // Use description from backend if provided (AAC, FLAC, Vorbis, etc.)
269
+ // For AAC, the backend provides proper AudioSpecificConfig with correct channelConfiguration
270
+ if (frame.description && !this._codecDescription) {
271
+ this._codecDescription = new Uint8Array(frame.description);
272
+ }
393
273
  if (this._bitstreamFormat === 'aac' && isAac) {
394
- const stripped = stripAdtsHeader(new Uint8Array(data));
274
+ const stripped = stripAdtsHeader(new Uint8Array(frame.data));
395
275
  payload = Buffer.from(stripped);
276
+ // Only build AudioSpecificConfig if backend didn't provide one
396
277
  if (!this._codecDescription) {
397
278
  this._codecDescription = buildAudioSpecificConfig({
398
279
  samplingRate: this._config.sampleRate,
@@ -401,13 +282,11 @@ export class AudioEncoder extends EventEmitter {
401
282
  }
402
283
  }
403
284
  const chunk = new EncodedAudioChunk({
404
- type,
285
+ type: frame.keyFrame ? 'key' : 'delta',
405
286
  timestamp,
406
287
  duration,
407
288
  data: new Uint8Array(payload),
408
289
  });
409
- this._encodeQueueSize = Math.max(0, this._encodeQueueSize - 1);
410
- this.emit('dequeue');
411
290
  const metadata = this._firstChunk
412
291
  ? {
413
292
  decoderConfig: {
@@ -421,5 +300,36 @@ export class AudioEncoder extends EventEmitter {
421
300
  this._firstChunk = false;
422
301
  this._safeOutputCallback(chunk, metadata);
423
302
  }
303
+ _audioDataToPCM(data) {
304
+ const numFrames = data.numberOfFrames;
305
+ const numChannels = data.numberOfChannels;
306
+ const format = data.format;
307
+ if (!format) {
308
+ throw encodingError('Cannot convert closed AudioData to PCM');
309
+ }
310
+ const bufferSize = numFrames * numChannels * 4;
311
+ const buffer = Buffer.alloc(bufferSize);
312
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
313
+ const isPlanar = format.endsWith('-planar');
314
+ const tempBuffer = new Float32Array(numFrames);
315
+ if (isPlanar) {
316
+ for (let ch = 0; ch < numChannels; ch++) {
317
+ data.copyTo(new Uint8Array(tempBuffer.buffer), {
318
+ planeIndex: ch,
319
+ format: 'f32-planar',
320
+ });
321
+ for (let frame = 0; frame < numFrames; frame++) {
322
+ const offset = (frame * numChannels + ch) * 4;
323
+ view.setFloat32(offset, tempBuffer[frame], true);
324
+ }
325
+ }
326
+ }
327
+ else {
328
+ const srcBuffer = new Uint8Array(bufferSize);
329
+ data.copyTo(srcBuffer, { planeIndex: 0, format: 'f32' });
330
+ buffer.set(srcBuffer);
331
+ }
332
+ return buffer;
333
+ }
424
334
  }
425
335
  //# sourceMappingURL=AudioEncoder.js.map