diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index 1808bfe75..6a572edf1 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -243,6 +243,7 @@ class ClientVoiceManager { this.connections.set(channel.guild.id, voiceConnection); voiceConnection.once('ready', () => resolve(voiceConnection)); voiceConnection.once('error', reject); + voiceConnection.once('disconnected', () => this.connections.delete(channel.guild.id)); }); }); } diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index a367718d3..dbe54dfaf 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -42,7 +42,6 @@ class VoiceConnection extends EventEmitter { this.player.on('error', e => { this.emit('warn', e); - console.log('so yeah uh' + e); this.player.cleanup(); }); @@ -63,9 +62,26 @@ class VoiceConnection extends EventEmitter { speaking: true, delay: 0, }, + }) + .catch(e => { + this.emit('debug', e); }); } + disconnect() { + this.emit('closing'); + this.voiceManager.client.ws.send({ + op: Constants.OPCodes.VOICE_STATE_UPDATE, + d: { + guild_id: this.channel.guild.id, + channel_id: null, + self_mute: false, + self_deaf: false, + }, + }); + this.emit('disconnected'); + } + connect() { if (this.sockets.ws) { throw new Error('There is already an existing WebSocket connection!'); diff --git a/src/client/voice/VoiceUDPClient.js b/src/client/voice/VoiceUDPClient.js index 9ae701f40..ef6b4936f 100644 --- a/src/client/voice/VoiceUDPClient.js +++ b/src/client/voice/VoiceUDPClient.js @@ -49,6 +49,17 @@ class VoiceConnectionUDPClient extends EventEmitter { * @type {?string} */ this.localPort = null; + this.voiceConnection.on('closing', this.shutdown.bind(this)); + } + shutdown() { + if (this.socket) { + try { + this.socket.close(); + } catch (e) { + return; + } + this.socket = null; + } } /** * The port of the discord voice server @@ -57,7 +68,6 @@ class VoiceConnectionUDPClient extends EventEmitter { get discordPort() { return this.voiceConnection.authentication.port; } - /** * Tries to resolve the voice server endpoint to an address * @returns {Promise} diff --git a/src/client/voice/VoiceWebSocket.js b/src/client/voice/VoiceWebSocket.js index 688951c5c..30c3d98c1 100644 --- a/src/client/voice/VoiceWebSocket.js +++ b/src/client/voice/VoiceWebSocket.js @@ -22,6 +22,15 @@ class VoiceWebSocket extends EventEmitter { */ this.attempts = 0; this.connect(); + + this.dead = false; + + this.voiceConnection.on('closing', this.shutdown.bind(this)); + } + + shutdown() { + this.dead = true; + this.reset(); } /** @@ -50,6 +59,9 @@ class VoiceWebSocket extends EventEmitter { * Starts connecting to the Voice WebSocket Server. */ connect() { + if (this.dead) { + return; + } if (this.ws) { this.reset(); } @@ -76,7 +88,7 @@ class VoiceWebSocket extends EventEmitter { */ send(data) { return new Promise((resolve, reject) => { - if (this.ws.readyState === WebSocket.OPEN) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(data, null, error => { if (error) { reject(error); @@ -85,7 +97,7 @@ class VoiceWebSocket extends EventEmitter { } }); } else { - reject(new Error('websocket not open')); + reject(new Error('websocket not open for ' + data)); } }); } @@ -140,7 +152,7 @@ class VoiceWebSocket extends EventEmitter { */ onClose(event) { // #todo see if the connection is open before reconnecting - this.client.setTimeout(this.connect.bind(this), this.attempts * 1000); + if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000); } /** diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 63d335e4a..c49a146cc 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -134,7 +134,10 @@ 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.voiceConnection.sockets.udp.send(packet).catch(e => console.log('so uhhh', e)); + while (repeats--) { + this.player.voiceConnection.sockets.udp.send(packet) + .catch(e => this.emit('debug', `failed to send a packet ${e}`)); + } } _createPacket(sequence, timestamp, buffer) { diff --git a/src/client/voice/player/AudioPlayer.js b/src/client/voice/player/AudioPlayer.js index 0774bfe14..9b6735271 100644 --- a/src/client/voice/player/AudioPlayer.js +++ b/src/client/voice/player/AudioPlayer.js @@ -20,12 +20,13 @@ class AudioPlayer extends EventEmitter { timestamp: 0, pausedTime: 0, }; + this.voiceConnection.on('closing', () => this.cleanup(null, 'voice connection is closing')); } playUnknownStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) { const options = { seek, volume, passes }; stream.on('end', () => { - console.log(Date.now(), 'real input stream ended'); + this.emit('debug', 'input stream to converter has ended'); }); stream.on('error', e => this.emit('error', e)); const conversionProcess = this.audioToPCM.createConvertStream(options.seek); @@ -36,7 +37,7 @@ class AudioPlayer extends EventEmitter { cleanup(checkStream, reason) { // cleanup is a lot less aggressive than v9 because it doesn't try to kill every single stream it is aware of - console.log(Date.now(), 'clean up triggered due to', reason); + this.emit('debug', `clean up triggered due to ${reason}`); const filter = checkStream && this.currentDispatcher && this.currentDispatcher.stream === checkStream; if (this.currentConverter && (checkStream ? filter : true)) { this.currentConverter.destroy(); @@ -46,9 +47,7 @@ class AudioPlayer extends EventEmitter { playPCMStream(stream, converter, { seek = 0, volume = 1, passes = 1 } = {}) { const options = { seek, volume, passes }; - stream.on('end', () => { - console.log(Date.now(), 'pcm input stream ended'); - }); + stream.on('end', () => this.emit('debug', 'pcm input stream ended')); this.cleanup(null, 'outstanding play stream'); this.currentConverter = converter; if (this.currentDispatcher) { diff --git a/src/client/voice/player/BasePlayer.js b/src/client/voice/player/BasePlayer.js index 3b02deca5..a38e4906d 100644 --- a/src/client/voice/player/BasePlayer.js +++ b/src/client/voice/player/BasePlayer.js @@ -101,6 +101,9 @@ class VoiceConnectionPlayer extends EventEmitter { speaking: true, delay: 0, }, + }) + .catch(e => { + this.emit('debug', e); }); } diff --git a/test/random.js b/test/random.js index 2121c5775..efc8662f6 100644 --- a/test/random.js +++ b/test/random.js @@ -160,6 +160,7 @@ let disp, con; client.on('message', msg => { if (msg.content.startsWith('/play')) { + console.log('I am now going to play', msg.content); const chan = msg.content.split(' ').slice(1).join(' '); con.playStream(ytdl(chan, {filter : 'audioonly'}), { passes : 4 }); }