audio.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.recordAudio = exports.playAudio = void 0;
  4. const formdata_node_1 = require("formdata-node");
  5. const node_child_process_1 = require("node:child_process");
  6. const node_stream_1 = require("node:stream");
  7. const node_process_1 = require("node:process");
  8. const DEFAULT_SAMPLE_RATE = 24000;
  9. const DEFAULT_CHANNELS = 1;
  10. const isNode = Boolean(node_process_1.versions?.node);
  11. const recordingProviders = {
  12. win32: 'dshow',
  13. darwin: 'avfoundation',
  14. linux: 'alsa',
  15. aix: 'alsa',
  16. android: 'alsa',
  17. freebsd: 'alsa',
  18. haiku: 'alsa',
  19. sunos: 'alsa',
  20. netbsd: 'alsa',
  21. openbsd: 'alsa',
  22. cygwin: 'dshow',
  23. };
  24. function isResponse(stream) {
  25. return typeof stream.body !== 'undefined';
  26. }
  27. function isFile(stream) {
  28. return stream instanceof formdata_node_1.File;
  29. }
  30. async function nodejsPlayAudio(stream) {
  31. return new Promise((resolve, reject) => {
  32. try {
  33. const ffplay = (0, node_child_process_1.spawn)('ffplay', ['-autoexit', '-nodisp', '-i', 'pipe:0']);
  34. if (isResponse(stream)) {
  35. stream.body.pipe(ffplay.stdin);
  36. }
  37. else if (isFile(stream)) {
  38. node_stream_1.Readable.from(stream.stream()).pipe(ffplay.stdin);
  39. }
  40. else {
  41. stream.pipe(ffplay.stdin);
  42. }
  43. ffplay.on('close', (code) => {
  44. if (code !== 0) {
  45. reject(new Error(`ffplay process exited with code ${code}`));
  46. }
  47. resolve();
  48. });
  49. }
  50. catch (error) {
  51. reject(error);
  52. }
  53. });
  54. }
  55. async function playAudio(input) {
  56. if (isNode) {
  57. return nodejsPlayAudio(input);
  58. }
  59. throw new Error('Play audio is not supported in the browser yet. Check out https://npm.im/wavtools as an alternative.');
  60. }
  61. exports.playAudio = playAudio;
  62. function nodejsRecordAudio({ signal, device, timeout } = {}) {
  63. return new Promise((resolve, reject) => {
  64. const data = [];
  65. const provider = recordingProviders[node_process_1.platform];
  66. try {
  67. const ffmpeg = (0, node_child_process_1.spawn)('ffmpeg', [
  68. '-f',
  69. provider,
  70. '-i',
  71. `:${device ?? 0}`,
  72. '-ar',
  73. DEFAULT_SAMPLE_RATE.toString(),
  74. '-ac',
  75. DEFAULT_CHANNELS.toString(),
  76. '-f',
  77. 'wav',
  78. 'pipe:1',
  79. ], {
  80. stdio: ['ignore', 'pipe', 'pipe'],
  81. });
  82. ffmpeg.stdout.on('data', (chunk) => {
  83. data.push(chunk);
  84. });
  85. ffmpeg.on('error', (error) => {
  86. console.error(error);
  87. reject(error);
  88. });
  89. ffmpeg.on('close', (code) => {
  90. returnData();
  91. });
  92. function returnData() {
  93. const audioBuffer = Buffer.concat(data);
  94. const audioFile = new formdata_node_1.File([audioBuffer], 'audio.wav', { type: 'audio/wav' });
  95. resolve(audioFile);
  96. }
  97. if (typeof timeout === 'number' && timeout > 0) {
  98. const internalSignal = AbortSignal.timeout(timeout);
  99. internalSignal.addEventListener('abort', () => {
  100. ffmpeg.kill('SIGTERM');
  101. });
  102. }
  103. if (signal) {
  104. signal.addEventListener('abort', () => {
  105. ffmpeg.kill('SIGTERM');
  106. });
  107. }
  108. }
  109. catch (error) {
  110. reject(error);
  111. }
  112. });
  113. }
  114. async function recordAudio(options = {}) {
  115. if (isNode) {
  116. return nodejsRecordAudio(options);
  117. }
  118. throw new Error('Record audio is not supported in the browser. Check out https://npm.im/wavtools as an alternative.');
  119. }
  120. exports.recordAudio = recordAudio;
  121. //# sourceMappingURL=audio.js.map