diff --git a/README.md b/README.md index 6c12826c5..d667ab1ff 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to - 100% coverage of the Discord API ## Installation -**Node.js 10.0.0 or newer is required.** +**Node.js 10.2.0 or newer is required.** Ignore any warnings about unmet peer dependencies, as they're all optional. Without voice support: `npm install discordjs/discord.js` diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 57fac3ac8..540dd8c74 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -422,7 +422,7 @@ class VoiceConnection extends EventEmitter { udp.on('error', err => this.emit('error', err)); ws.on('ready', this.onReady.bind(this)); ws.on('sessionDescription', this.onSessionDescription.bind(this)); - ws.on('speaking', this.onSpeaking.bind(this)); + ws.on('startSpeaking', this.onStartSpeaking.bind(this)); this.sockets.ws.connect(); } @@ -465,16 +465,19 @@ class VoiceConnection extends EventEmitter { }); } + onStartSpeaking({ user_id, ssrc, speaking }) { + this.ssrcMap.set(+ssrc, { userID: user_id, speaking: speaking }); + } + /** * Invoked when a speaking event is received. * @param {Object} data The received data * @private */ - onSpeaking({ user_id, ssrc, speaking }) { + onSpeaking({ user_id, speaking }) { speaking = new Speaking(speaking).freeze(); const guild = this.channel.guild; const user = this.client.users.get(user_id); - this.ssrcMap.set(+ssrc, user_id); const old = this._speaking.get(user_id); this._speaking.set(user_id, speaking); /** @@ -504,7 +507,7 @@ class VoiceConnection extends EventEmitter { } } - play() {} // eslint-disable-line no-empty-function + play() { } // eslint-disable-line no-empty-function } PlayInterface.applyToClass(VoiceConnection); diff --git a/src/client/voice/networking/VoiceWebSocket.js b/src/client/voice/networking/VoiceWebSocket.js index 6ddabe626..ac6405e14 100644 --- a/src/client/voice/networking/VoiceWebSocket.js +++ b/src/client/voice/networking/VoiceWebSocket.js @@ -201,9 +201,9 @@ class VoiceWebSocket extends EventEmitter { /** * Emitted whenever a speaking packet is received. * @param {Object} data - * @event VoiceWebSocket#speaking + * @event VoiceWebSocket#startSpeaking */ - this.emit('speaking', packet.d); + this.emit('startSpeaking', packet.d); break; default: /** diff --git a/src/client/voice/receiver/PacketHandler.js b/src/client/voice/receiver/PacketHandler.js index 8ea9576ef..d468ababc 100644 --- a/src/client/voice/receiver/PacketHandler.js +++ b/src/client/voice/receiver/PacketHandler.js @@ -3,6 +3,10 @@ const secretbox = require('../util/Secretbox'); const EventEmitter = require('events'); +// The delay between packets when a user is considered to have stopped speaking +// https://github.com/discordjs/discord.js/issues/3524#issuecomment-540373200 +const DISCORD_SPEAKING_DELAY = 250; + class Readable extends require('stream').Readable { _read() {} } // eslint-disable-line no-empty-function class PacketHandler extends EventEmitter { @@ -11,6 +15,7 @@ class PacketHandler extends EventEmitter { this.nonce = Buffer.alloc(24); this.receiver = receiver; this.streams = new Map(); + this.speakingTimeouts = new Map(); } get connection() { @@ -72,13 +77,29 @@ class PacketHandler extends EventEmitter { return packet; } - userFromSSRC(ssrc) { return this.connection.client.users.get(this.connection.ssrcMap.get(ssrc)); } - push(buffer) { const ssrc = buffer.readUInt32BE(8); - const user = this.userFromSSRC(ssrc); - if (!user) return; - let stream = this.streams.get(user.id); + const userStat = this.connection.ssrcMap.get(ssrc); + if (!userStat) return; + + let speakingTimeout = this.speakingTimeouts.get(ssrc); + if (typeof speakingTimeout === 'undefined') { + this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: userStat.speaking }); + speakingTimeout = this.receiver.connection.client.setTimeout(() => { + try { + this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: 0 }); + this.receiver.connection.client.clearTimeout(speakingTimeout); + this.speakingTimeouts.delete(ssrc); + } catch (ex) { + // Connection already closed, ignore + } + }, DISCORD_SPEAKING_DELAY); + this.speakingTimeouts.set(ssrc, speakingTimeout); + } else { + speakingTimeout.refresh(); + } + + let stream = this.streams.get(userStat.userID); if (!stream) return; stream = stream.stream; const opusPacket = this.parseBuffer(buffer);