diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index cbc88feb5..1808bfe75 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -241,7 +241,7 @@ class ClientVoiceManager { pendingConnection.on('pass', voiceConnection => { this.pending.delete(channel.guild.id); this.connections.set(channel.guild.id, voiceConnection); - voiceConnection.once('ready', resolve); + voiceConnection.once('ready', () => resolve(voiceConnection)); voiceConnection.once('error', reject); }); }); diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 82b4d86e0..0e37e4e10 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -2,8 +2,8 @@ const VoiceWebSocket = require('./VoiceWebSocket'); const VoiceUDP = require('./VoiceUDPClient'); const VoiceReceiver = require('./receiver/VoiceReceiver'); const Constants = require('../../util/Constants'); +const AudioPlayer = require('./player/AudioPlayer'); const EventEmitter = require('events').EventEmitter; -const DefaultPlayer = require('./player/DefaultPlayer'); /** * Represents a connection to a Voice Channel in Discord. @@ -37,6 +37,8 @@ class VoiceConnection extends EventEmitter { */ this.authentication = pendingConnection.data; + this.player = new AudioPlayer(this); + /** * Object that wraps contains the `ws` and `udp` sockets of this voice connection * @type {object} @@ -45,6 +47,18 @@ class VoiceConnection extends EventEmitter { this.connect(); } + setSpeaking(value) { + if (this.speaking === value) return; + this.speaking = value; + this.sockets.ws.sendPacket({ + op: Constants.VoiceOPCodes.SPEAKING, + d: { + speaking: true, + delay: 0, + }, + }); + } + connect() { if (this.sockets.ws) { throw new Error('There is already an existing WebSocket connection!'); @@ -58,6 +72,7 @@ class VoiceConnection extends EventEmitter { this.sockets.udp.on('error', e => this.emit('error', e)); this.sockets.ws.once('ready', d => { this.authentication.port = d.port; + this.authentication.ssrc = d.ssrc; this.sockets.udp.findEndpointAddress() .then(address => { this.sockets.udp.createUDPSocket(address); diff --git a/src/client/voice/VoiceUDPClient.js b/src/client/voice/VoiceUDPClient.js index f59ffbf3c..9ae701f40 100644 --- a/src/client/voice/VoiceUDPClient.js +++ b/src/client/voice/VoiceUDPClient.js @@ -87,6 +87,7 @@ class VoiceConnectionUDPClient extends EventEmitter { reject(new Error('malformed UDP address or port')); return; } + // console.log('sendin', packet); this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => { if (error) { reject(error); diff --git a/src/client/voice/VoiceWebSocket.js b/src/client/voice/VoiceWebSocket.js index 688951c5c..e2d13be16 100644 --- a/src/client/voice/VoiceWebSocket.js +++ b/src/client/voice/VoiceWebSocket.js @@ -77,6 +77,7 @@ class VoiceWebSocket extends EventEmitter { send(data) { return new Promise((resolve, reject) => { if (this.ws.readyState === WebSocket.OPEN) { + console.log('sending', data); this.ws.send(data, null, error => { if (error) { reject(error); diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index fa23a364e..20846b91a 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -132,7 +132,7 @@ class StreamDispatcher extends EventEmitter { _sendBuffer(buffer, sequence, timestamp) { let repeats = this.passes; const packet = this._createPacket(sequence, timestamp, this.player.opusEncoder.encode(buffer)); - while (repeats--) this.player.connection.udp.send(packet); + while (repeats--) this.player.voiceConnection.sockets.udp.send(packet); } _createPacket(sequence, timestamp, buffer) { @@ -143,10 +143,10 @@ class StreamDispatcher extends EventEmitter { packetBuffer.writeUIntBE(sequence, 2, 2); packetBuffer.writeUIntBE(timestamp, 4, 4); - packetBuffer.writeUIntBE(this.player.connection.data.ssrc, 8, 4); + packetBuffer.writeUIntBE(this.player.voiceConnection.authentication.ssrc, 8, 4); packetBuffer.copy(nonce, 0, 0, 12); - buffer = NaCl.secretbox(buffer, nonce, this.player.connection.data.secret); + buffer = NaCl.secretbox(buffer, nonce, this.player.voiceConnection.authentication.secretKey.key); for (let i = 0; i < buffer.length; i++) packetBuffer[i + 12] = buffer[i]; @@ -183,7 +183,7 @@ class StreamDispatcher extends EventEmitter { if (this.paused) { // data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0; data.pausedTime += data.length * 10; - this.player.connection.manager.client.setTimeout(() => this._send(), data.length * 10); + this.player.voiceConnection.voiceManager.client.setTimeout(() => this._send(), data.length * 10); return; } @@ -203,7 +203,7 @@ class StreamDispatcher extends EventEmitter { if (!buffer) { data.missed++; data.pausedTime += data.length * 10; - this.player.connection.manager.client.setTimeout(() => this._send(), data.length * 10); + this.player.voiceConnection.voiceManager.client.setTimeout(() => this._send(), data.length * 10); return; } @@ -224,7 +224,8 @@ class StreamDispatcher extends EventEmitter { this._sendBuffer(buffer, data.sequence, data.timestamp); const nextTime = data.length + (data.startTime + data.pausedTime + (data.count * data.length) - Date.now()); - this.player.connection.manager.client.setTimeout(() => this._send(), nextTime); + console.log('again! in', nextTime); + this.player.voiceConnection.voiceManager.client.setTimeout(() => this._send(), nextTime); } catch (e) { this._triggerTerminalState('error', e); } @@ -250,7 +251,7 @@ class StreamDispatcher extends EventEmitter { _triggerTerminalState(state, err) { if (this._triggered) return; - + console.log(state, err); /** * Emitted when the stream wants to give debug information. * @event StreamDispatcher#debug diff --git a/src/client/voice/pcm/FfmpegConverterEngine.js b/src/client/voice/pcm/FfmpegConverterEngine.js index e1f39d029..21d0449c3 100644 --- a/src/client/voice/pcm/FfmpegConverterEngine.js +++ b/src/client/voice/pcm/FfmpegConverterEngine.js @@ -1,5 +1,16 @@ const ConverterEngine = require('./ConverterEngine'); const ChildProcess = require('child_process'); +const EventEmitter = require('events').EventEmitter; + +class PCMConversionProcess extends EventEmitter { + + constructor(process) { + super(); + this.process = process; + this.process.on('error', e => this.emit('error', e)); + } + +} class FfmpegConverterEngine extends ConverterEngine { constructor(player) { @@ -24,10 +35,7 @@ class FfmpegConverterEngine extends ConverterEngine { '-ss', String(seek), 'pipe:1', ], { stdio: ['pipe', 'pipe', 'ignore'] }); - encoder.on('error', e => this.handleError(encoder, e)); - encoder.stdin.on('error', e => this.handleError(encoder, e)); - encoder.stdout.on('error', e => this.handleError(encoder, e)); - return encoder; + return new PCMConversionProcess(encoder); } } diff --git a/src/client/voice/player/AudioPlayer.js b/src/client/voice/player/AudioPlayer.js new file mode 100644 index 000000000..baef5acf4 --- /dev/null +++ b/src/client/voice/player/AudioPlayer.js @@ -0,0 +1,41 @@ +const PCMConverters = require('../pcm/ConverterEngineList'); +const OpusEncoders = require('../opus/OpusEngineList'); +const EventEmitter = require('events').EventEmitter; +const StreamDispatcher = require('../dispatcher/StreamDispatcher'); + +class AudioPlayer extends EventEmitter { + + constructor(voiceConnection) { + super(); + this.voiceConnection = voiceConnection; + this.audioToPCM = new (PCMConverters.fetch())(); + this.opusEncoder = OpusEncoders.fetch(); + + this.audioToPCM.on('error', e => this.emit('error', e)); + } + + playUnknownStream(stream) { + const conversionProcess = this.audioToPCM.createConvertStream(0); + stream.pipe(conversionProcess.process.stdin, { end: false }); + return this.playPCMStream(conversionProcess.process.stdout); + } + + playPCMStream(stream) { + stream.on('error', e => this.emit('error', e)); + const dispatcher = new StreamDispatcher(this, stream, { + channels: 2, + count: 0, + sequence: 0, + timestamp: 0, + pausedTime: 0, + }, { + volume: 1, + }); + dispatcher.on('error', e => console.log('error', e)); + dispatcher.on('end', e => console.log('end', e)); + dispatcher.on('speaking', value => this.voiceConnection.setSpeaking(value)); + } + +} + +module.exports = AudioPlayer; diff --git a/test/random.js b/test/random.js index 9ad71b3a4..1d2773959 100644 --- a/test/random.js +++ b/test/random.js @@ -168,10 +168,9 @@ client.on('message', msg => { .then(conn => { con = conn; msg.reply('done'); - disp = conn.player.playStream(ytdl('https://www.youtube.com/watch?v=oQBiPwklN_Q', {filter : 'audioonly'}), { passes : 3 }); + disp = conn.player.playUnknownStream(fs.createReadStream('C:/Users/Amish/Desktop/04 Out of the Woods.m4a'), { passes : 3 }); conn.player.on('debug', console.log); conn.player.on('error', err => console.log(123, err)); - disp.on('error', err => console.log(123, err)); }) .catch(console.error); }