import { spawn } from 'child_process' import createDebug from 'debug' import { EventEmitter } from 'events' import process from 'process' const debug = createDebug('meter') export class AlsaMeter extends EventEmitter { constructor (opts = {}) { super() if (!opts.input) this.input = { host: 'alsa', device: 'default' } else if (typeof opts.input === 'string') this.input = { host: 'alsa', input: opts.input } else this.input = opts.input // setInterval(() => console.log(this._lastMessage), 1000) } open () { if (!this._opening) this._opening = this._open() return this._opening } async _open () { const cmd = 'ffmpeg' let args = [ '-nostats', '-f', this.input.host, '-i', this.input.device, '-filter_complex', 'ebur128=peak=true', '-f', 'null', '-' ] debug('spawn: ' + `${cmd} ${args.map(a => `"${a}"`).join(' ')}`) this.proc = spawn(cmd, args) // const onclose = (ev) => { // this._closed = true // this.proc.kill() // } // process.on('exit', onclose.bind('exit')) // process.on('SIGINT', onclose.bind('SIGINT')) // process.on('SIGTERM', onclose.bind('SIGTERM')) this.proc.stderr.on('data', msg => { this.parseMessage(msg) }) return new Promise((resolve, reject) => { this.proc.stderr.once('data', msg => { this._open = true resolve() }) }) } parseMessage (msg) { let str = msg.toString('utf-8') let match = str.match(/\[.*\](.*)/) if (!match || match.length !== 2) return null str = match[1].trim() let parts = str.split(/([a-zA-Z]+:)/).filter(f => f) let last = null let map = parts.reduce((agg, token, i) => { token = token.trim() if (last) { agg[last] = token last = null } else { last = token.replace(/[:\s]/g, '') } return agg }, {}) let res = {} if (map.LRA) res.LRA = parseFloat(map.LRA.replace('LU', '')) if (map.t) res.t = parseFloat(map.t) if (map.TARGET) res.TARGET = parseFloat(map.TARGET.replace('LUFS', '')) if (map.I) res.I = parseFloat(map.I.replace('LUFS', '')) if (map.M) res.M = parseFloat(map.M) if (map.S) res.S = parseFloat(map.S) for (let key of ['FTPK', 'TPK']) { if (map[key]) { let parts = map[key].split(/\s+/) if (parts.length >= 2) { res[key] = [parseFloat(parts[0]), parseFloat(parts[1])] } else { res[key] = null } } } if (Object.keys(res).length) { // debug(Object.entries(res).map(([k, v]) => `${k}: ${v}`).join(' | ')) this._lastMessage = res this.emit('meter', res) } } }