UNPKG

stumble

Version:
213 lines (171 loc) 5.02 kB
'use strict'; const path = require('path'); const ffmpeg = require('fluent-ffmpeg'); const Stream = require('stream'); const minmax = require('../gutil').minmax; const playfile = { handle: 'audio::playfile', exec: function playfile (data) { if (this.io.input) return false; ffmpeg.ffprobe(data.filename, (error, meta) => { if (error) { this.emit('error', error); if (data.done) data.done(error, true); return; } if (!this.io.input) { const conf = this.config.extensions.audio; const stream = meta.streams[0]; const samples = 48000; this.space.set('audio.streaming', true); this.io.establish({ input: true, inputOptions: { channels: stream.channels, sampleRate: samples, gain: minmax(0.01, conf.gain, 1.0) } }); const pass = new Stream.PassThrough(); // ffmpeg's PassThrough is missing an unpipe method. // (╯ರ ~ ರ)╯︵ ┻━┻ // Another pipe ought to fix it. ffmpeg(data.filename) .withAudioBitrate(stream.bit_rate) .withAudioFrequency(samples) .withAudioChannels(stream.channels) .format('s16le') .pipe(pass); let lived = true; pass .on('end', () => { if (lived) setTimeout(() => { this.execute('audio::stopfile'); if (data.done) data.done(null, lived); }, 250); }) .on('kill', () => { lived = false; if (data.done) data.done({ code: 'APKILL', message: 'Audio playback was killed.', }, lived); pass.end(); }) .pipe(this.io.input); this.space.set('audio.pass', pass); } }); return true; } }; const stopfile = { handle: 'audio::stopfile', exec: function stopfile (data) { if (this.space.get('audio.streaming')) { const pass = this.space.get('audio.pass'); // Work around for noisey bits. this.io.input.setGain(0.01); pass.unpipe(this.io.input); if (data && data.force) pass.emit('kill'); else pass.end(); this.io.nullify({ input: true, output: false }); this.space.set('audio.streaming', false); this.space.set('audio.pass', null); } } }; const feedback = { handle: 'audio::feedback', init: stumble => stumble.space.set('audio.feedback', false), term: stumble => stumble.space.delete('audio.feedback'), exec: function feedback () { const feeding = this.space.get('audio.feedback'); if (feeding) { this.io.output.unpipe(this.io.input); this.io.nullify(); this.space.set('audio.feedback', false); } else if (!this.io.input && !this.io.output) { this.space.set('audio.feedback', true); this.io.establish(); this.io.output.pipe(this.io.input); } }, commands: [{ handle: 'feedback', exec: function feedback () { this.execute('audio::feedback'); }, info: () => `<pre> USAGE: feedback Toggles audio feedback, causing the bot to repeat everything it hears. </pre>` }] }; const mute = { handle: 'audio::mute', exec: function mute () { const value = !this.space.get('audio.muted'); this.client.connection.sendMessage('UserState', { 'self_mute': value }); this.space.set('audio.muted', value); }, commands: [{ handle: 'mute', exec: function mute () { this.execute('audio::mute'); }, info: () => `<pre> USAGE: mute Toggles a self-mute on the bot. </pre>` }] }; const gain = { handle: 'audio::gain', exec: function gain (data) { const conf = this.config.extensions.audio; if (Number.isInteger(data.gain)) { const ngain = minmax(1, data.gain, 100) / 100; if (this.space.get('audio.streaming')) this.io.input.setGain(ngain); conf.gain = ngain; } return conf.gain; }, commands: [{ handle: 'gain', exec: function gaincmd (data) { const level = this.execute('audio::gain', { gain: parseInt(data.message, 10) }); data.user.sendMessage(`Current gain: [ ${(level * 100) >> 0} ].`); }, info: () => `<pre> USAGE: gain INTEGER Sets the audio gain to an integer, between 1 and 100 inclusive. Going outside these bounds will set the gain to the nearest extreme. </pre>` }] }; module.exports = { handle: 'audio', init: stumble => { const conf = stumble.config.extensions.audio; conf.directory = path.resolve(conf.directory); conf.gain = minmax(0.01, conf.gain, 1.0); stumble.space.set('audio.streaming', false); stumble.space.set('audio.muted', false); stumble.space.set('audio.pass', null); }, term: stumble => { stumble.space.delete('audio.streaming'); stumble.space.delete('audio.muted'); stumble.space.delete('audio.pass'); }, extensions: [playfile, stopfile, feedback, mute, gain] };