diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 9f8c38d89..21d1f2669 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -2,6 +2,7 @@ const VoiceWebSocket = require('./VoiceWebSocket'); const VoiceUDP = require('./VoiceUDPClient'); const Constants = require('../../util/Constants'); const AudioPlayer = require('./player/AudioPlayer'); +const VoiceReceiver = require('./receiver/VoiceReceiver'); const EventEmitter = require('events').EventEmitter; const fs = require('fs'); @@ -31,6 +32,8 @@ class VoiceConnection extends EventEmitter { */ this.channel = pendingConnection.channel; + this.receivers = []; + /** * The authentication data needed to connect to the voice server * @type {object} @@ -48,6 +51,8 @@ class VoiceConnection extends EventEmitter { this.player.cleanup(); }); + this.ssrcMap = new Map(); + /** * Object that wraps contains the `ws` and `udp` sockets of this voice connection * @type {object} @@ -110,6 +115,35 @@ class VoiceConnection extends EventEmitter { this.authentication.secretKey = secret; this.emit('ready'); }); + this.sockets.ws.on('speaking', data => { + const guild = this.channel.guild; + const user = this.voiceManager.client.users.get(data.user_id); + this.ssrcMap.set(+data.ssrc, user); + if (!data.speaking) { + for (const receiver of this.receivers) { + const opusStream = receiver.opusStreams.get(user.id); + const pcmStream = receiver.pcmStreams.get(user.id); + if (opusStream) { + opusStream.push(null); + opusStream.open = false; + receiver.opusStreams.delete(user.id); + } + if (pcmStream) { + pcmStream.push(null); + pcmStream.open = false; + receiver.pcmStreams.delete(user.id); + } + } + } + /** + * Emitted whenever a user starts/stops speaking + * @event VoiceConnection#speaking + * @param {User} user The user that has started/stopped speaking + * @param {boolean} speaking Whether or not the user is speaking + */ + if (this.ready) this.emit('speaking', user, data.speaking); + guild._memberSpeakUpdate(data.user_id, data.speaking); + }); } playFile(file, options) { @@ -126,6 +160,12 @@ class VoiceConnection extends EventEmitter { return this.player.playPCMStream(stream, options); } + createReceiver() { + const receiver = new VoiceReceiver(this); + this.receivers.push(receiver); + return receiver; + } + } module.exports = VoiceConnection; diff --git a/src/client/voice/receiver/VoiceReceiver.js b/src/client/voice/receiver/VoiceReceiver.js index 3e1c24c82..cf7582e50 100644 --- a/src/client/voice/receiver/VoiceReceiver.js +++ b/src/client/voice/receiver/VoiceReceiver.js @@ -34,10 +34,10 @@ class VoiceReceiver extends EventEmitter { * The VoiceConnection that instantiated this * @type {VoiceConnection} */ - this.connection = connection; + this.voiceConnection = connection; this._listener = (msg => { const ssrc = +msg.readUInt32BE(8).toString(10); - const user = this.connection.ssrcMap.get(ssrc); + const user = this.voiceConnection.ssrcMap.get(ssrc); if (!user) { if (!this.queues.has(ssrc)) this.queues.set(ssrc, []); this.queues.get(ssrc).push(msg); @@ -51,7 +51,7 @@ class VoiceReceiver extends EventEmitter { this.handlePacket(msg, user); } }).bind(this); - this.connection.udp.udpSocket.on('message', this._listener); + this.voiceConnection.sockets.udp.socket.on('message', this._listener); } /** @@ -61,7 +61,7 @@ class VoiceReceiver extends EventEmitter { */ recreate() { if (!this.destroyed) return; - this.connection.udp.udpSocket.on('message', this._listener); + this.voiceConnection.sockets.udp.socket.on('message', this._listener); this.destroyed = false; return; } @@ -70,7 +70,7 @@ class VoiceReceiver extends EventEmitter { * Destroy this VoiceReceiver, also ending any streams that it may be controlling. */ destroy() { - this.connection.udp.udpSocket.removeListener('message', this._listener); + this.voiceConnection.sockets.udp.socket.removeListener('message', this._listener); for (const stream of this.pcmStreams) { stream[1]._push(null); this.pcmStreams.delete(stream[0]); @@ -89,7 +89,7 @@ class VoiceReceiver extends EventEmitter { * @returns {ReadableStream} */ createOpusStream(user) { - user = this.connection.manager.client.resolver.resolveUser(user); + user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user); if (!user) throw new Error('Couldn\'t resolve the user to create Opus stream.'); if (this.opusStreams.get(user.id)) throw new Error('There is already an existing stream for that user.'); const stream = new Readable(); @@ -104,7 +104,7 @@ class VoiceReceiver extends EventEmitter { * @returns {ReadableStream} */ createPCMStream(user) { - user = this.connection.manager.client.resolver.resolveUser(user); + user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user); if (!user) throw new Error('Couldn\'t resolve the user to create PCM stream.'); if (this.pcmStreams.get(user.id)) throw new Error('There is already an existing stream for that user.'); const stream = new Readable(); @@ -114,7 +114,7 @@ class VoiceReceiver extends EventEmitter { handlePacket(msg, user) { msg.copy(nonce, 0, 0, 12); - let data = NaCl.secretbox.open(msg.slice(12), nonce, this.connection.data.secret); + let data = NaCl.secretbox.open(msg.slice(12), nonce, this.voiceConnection.authentication.secretKey.key); if (!data) { /** * Emitted whenever a voice packet cannot be decrypted @@ -141,7 +141,7 @@ class VoiceReceiver extends EventEmitter { * @param {User} user The user that is sending the buffer (is speaking) * @param {Buffer} buffer The decoded buffer */ - const pcm = this.connection.player.opusEncoder.decode(data); + const pcm = this.voiceConnection.player.opusEncoder.decode(data); if (this.pcmStreams.get(user.id)) this.pcmStreams.get(user.id)._push(pcm); this.emit('pcm', user, pcm); }