UNPKG

node-mic

Version:

Microphone streaming library for Node.js

174 lines (173 loc) 5.63 kB
import { PassThrough } from 'stream'; import { spawn } from 'child_process'; import { extractMetadata } from './metadata.js'; import { AudioStream } from './audio.js'; import { findAlsa, findRec, findSox } from './bins.js'; import { isMac, isWindows } from '../scripts/constants.js'; export default class Mic { _rate; _channels; _device; _fileType; _debug; _audioStream; _infoStream; _format; _audioProcessOptions; _encoding; _bitwidth; _endian; _audioProcess; constructor(options = {}) { this._endian = options.endian || 'little'; this._bitwidth = String(options.bitwidth || 16); this._encoding = options.encoding || 'signed-integer'; this._rate = String(options.rate || 16000); this._channels = String(options.channels || 1); this._device = options.device || ''; this._fileType = options.fileType || 'wav'; const threshold = options.threshold || 0; this._debug = options.debug || false; const stderr = this._debug ? 'pipe' : 'ignore'; this._infoStream = new PassThrough(); this._audioStream = new AudioStream({ debug: this._debug }); this._audioProcessOptions = { stdio: ['ignore', 'pipe', stderr] }; this._audioProcess = null; // Setup format variable for arecord call const formatEndian = this._endian === 'big' ? 'BE' : 'LE'; const formatEncoding = this._encoding === 'unsigned-integer' ? 'U' : 'S'; this._format = `${formatEncoding}${this._bitwidth}_${formatEndian}`; this._audioStream.threshold = threshold; if (this._debug) { this._infoStream.on('data', (data) => { console.log(`Received Info: ${data}`); try { if (this._audioProcess !== null) { const metadata = extractMetadata(data.toString()); this._audioStream.emit('metadata', metadata); } } catch (error) { console.log(error); } }); this._infoStream.on('error', (error) => { console.log(`Error in Info Stream: ${error.message}`); }); } } start() { if (this._audioProcess !== null) { if (this._debug) { throw new Error("Microphone already active"); } } if (isWindows) { const sox = findSox(); this.spawnWindows(sox); } else if (isMac) { const rec = findRec(); this.spawnMac(rec); } else { const arecord = findAlsa(); this.spawnLinux(arecord); } this._audioProcess.on('exit', (code, sig) => { if (code && !sig) { if (this._debug) { console.log(`Recording has finished with code = ${code}`); } this._audioStream.emit('exit', code); } }); this._audioProcess.stdout.pipe(this._audioStream); if (this._debug) { this._audioProcess.stderr.pipe(this._infoStream); } this._audioStream.emit('started'); } spawnWindows(sox) { const options = [ '-q', '-t', 'waveaudio', '-d', '-r', this._rate, '-c', this._channels, '-e', this._encoding, '-b', this._bitwidth, '-t', this._fileType, '--endian', this._endian, '-' ]; this._audioProcess = spawn(sox, options, this._audioProcessOptions); } spawnMac(rec) { const options = [ '-q', '-r', this._rate, '-c', this._channels, '-t', this._fileType, '-b', this._bitwidth, '-e', this._encoding, '--endian', this._endian, '-' ]; this._audioProcess = spawn(rec, options, this._audioProcessOptions); } spawnLinux(arecord) { console.log(arecord); const options = [ '-q', '-r', this._rate, '-c', this._channels, '-t', this._fileType, '-f', this._format, ]; if (this._device) { options.push('-D', this._device); } console.log(options.join(' ')); this._audioProcess = spawn(arecord, options, this._audioProcessOptions); } stop() { if (this._audioProcess !== null) { this._audioProcess.kill('SIGTERM'); this._audioProcess = null; this._audioStream.emit('stopped'); if (this._debug) { console.log('Microphone stopped'); } } } pause() { if (this._audioProcess !== null) { if (!isWindows) { this._audioProcess.kill('SIGSTOP'); } this._audioStream.pause(); this._audioStream.emit('paused'); if (this._debug) { console.log('Microphone paused'); } } } resume() { if (this._audioProcess !== null) { if (!isWindows) { this._audioProcess.kill('SIGCONT'); } this._audioStream.resume(); this._audioStream.emit('unpaused'); if (this._debug) { console.log('Microphone resumed'); } } } getAudioStream() { return this._audioStream; } }