import assert from 'assert'; import { logger } from '../logger/index.ts'; import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; import recorders from '../recorder/recorders/index.ts'; import Stream from 'stream'; export type RecordingOptions = { /* 采样率,默认为16000 */ sampleRate?: number; /* 声道数,默认为1 */ channels?: number; /* 是否压缩音频,默认为false */ compress?: boolean; /* 录音开始的音量阈值,默认为0.5 */ threshold?: number; /* 开始录音的音量阈值 */ thresholdStart?: number; /* 结束录音的音量阈值 */ thresholdEnd?: number; /* 录音结束的静默时间,默认为'1.0'秒 */ silence?: string; /* 使用的录音器,默认为'sox' */ recorder?: string; /* 是否在静默时结束录音,默认为false */ endOnSilence?: boolean; /* 音频类型,默认为'wav' */ audioType?: string; }; /** * node-record-lpcm16 * https://github.com/gillesdemey/node-record-lpcm16 */ export class Recording { options: RecordingOptions; cmd: string; args: string[]; cmdOptions: any; process: ChildProcessWithoutNullStreams; _stream: Stream.Readable; constructor(options?: RecordingOptions) { const defaults = { sampleRate: 16000, channels: 1, compress: false, threshold: 0.5, thresholdStart: null, thresholdEnd: null, silence: '1.0', recorder: 'sox', endOnSilence: false, audioType: 'wav', }; this.options = Object.assign(defaults, options); const recorder = recorders[this.options.recorder]; if (!recorder) { throw new Error(`No such recorder found: ${this.options.recorder}`); } const { cmd, args, spawnOptions = {} } = recorder(this.options); this.cmd = cmd; this.args = args; this.cmdOptions = Object.assign({ encoding: 'binary', stdio: 'pipe' }, spawnOptions); logger.debug(`Started recording`); logger.debug('options', this.options); logger.debug(` ${this.cmd} ${this.args.join(' ')}`); return this.start(); } start() { const { cmd, args, cmdOptions } = this; const cp = spawn(cmd, args, cmdOptions); const rec = cp.stdout; const err = cp.stderr; this.process = cp; // expose child process this._stream = rec; // expose output stream cp.on('close', (code) => { if (code === 0) return; rec.emit( 'error', `${this.cmd} has exited with error code ${code}. Enable debugging with the environment variable DEBUG=record.`, ); }); err.on('data', (chunk) => { logger.debug(`STDERR: ${chunk}`); }); rec.on('data', (chunk) => { logger.debug(`Recording ${chunk.length} bytes`); }); rec.on('end', () => { logger.debug('Recording ended'); }); return this; } stop() { assert(this.process, 'Recording not yet started'); this.process.kill(); } pause() { assert(this.process, 'Recording not yet started'); this.process.kill('SIGSTOP'); this._stream.pause(); logger.debug('Paused recording'); } resume() { assert(this.process, 'Recording not yet started'); this.process.kill('SIGCONT'); this._stream.resume(); logger.debug('Resumed recording'); } isPaused() { assert(this.process, 'Recording not yet started'); return this._stream.isPaused(); } stream() { assert(this._stream, 'Recording not yet started'); return this._stream; } } export const record = (...args) => new Recording(...args);