audio.js 4.0 KB

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