generated from tailored/router-template
144 lines
3.5 KiB
TypeScript
144 lines
3.5 KiB
TypeScript
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);
|