144 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);