Skip to content

Commit 7b2e12b

Browse files
authored
feat(Voice): implement support for @discordjs/voice (#5402)
1 parent c4f1c75 commit 7b2e12b

27 files changed

Lines changed: 97 additions & 2393 deletions

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ labels: 's: unverified, type: bug'
77
assignees: ''
88
---
99
<!-- Use Discord for questions: https://discord.gg/bRCvFy9 -->
10+
<!-- If you are reporting a voice issue, please post your issue at https://github.com/discordjs/voice/issues -->
1011

1112
**Please describe the problem you are having in as much detail as possible:**
1213

README.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,6 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to
4444
**Node.js 14.0.0 or newer is required.**
4545
Ignore any warnings about unmet peer dependencies, as they're all optional.
4646

47-
Without voice support: `npm install discord.js`
48-
With voice support ([@discordjs/opus](https://www.npmjs.com/package/@discordjs/opus)): `npm install discord.js @discordjs/opus`
49-
With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript`
50-
51-
### Audio engines
52-
53-
The preferred audio engine is @discordjs/opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose @discordjs/opus.
54-
Using opusscript is only recommended for development environments where @discordjs/opus is tough to get working.
55-
For production bots, using @discordjs/opus should be considered a necessity, especially if they're going to be running on multiple servers.
56-
5747
### Optional packages
5848

5949
- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for WebSocket data compression and inflation (`npm install zlib-sync`)
@@ -63,6 +53,7 @@ For production bots, using @discordjs/opus should be considered a necessity, esp
6353
- [libsodium.js](https://www.npmjs.com/package/libsodium-wrappers) (`npm install libsodium-wrappers`)
6454
- [bufferutil](https://www.npmjs.com/package/bufferutil) for a much faster WebSocket connection (`npm install bufferutil`)
6555
- [utf-8-validate](https://www.npmjs.com/package/utf-8-validate) in combination with `bufferutil` for much faster WebSocket processing (`npm install utf-8-validate`)
56+
- [@discordjs/voice](https://github.com/discordjs/voice) for interacting with the Discord Voice API
6657

6758
## Example usage
6859

package-lock.json

Lines changed: 42 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,13 @@
4949
"abort-controller": "^3.0.0",
5050
"discord-api-types": "^0.18.1",
5151
"node-fetch": "^2.6.1",
52-
"prism-media": "^1.2.9",
53-
"tweetnacl": "^1.0.3",
5452
"ws": "^7.4.6"
5553
},
5654
"devDependencies": {
5755
"@commitlint/cli": "^12.1.4",
5856
"@commitlint/config-angular": "^12.1.4",
5957
"@discordjs/docgen": "^0.10.0",
58+
"@discordjs/voice": "^0.3.0",
6059
"@types/node": "^12.12.6",
6160
"conventional-changelog-cli": "^2.1.1",
6261
"cross-env": "^7.0.3",

src/client/actions/GuildDelete.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class GuildDeleteAction extends Action {
3737
}
3838

3939
for (const channel of guild.channels.cache.values()) this.client.channels.remove(channel.id);
40-
guild.me?.voice.connection?.disconnect();
40+
client.voice.adapters.get(data.id)?.destroy();
4141

4242
// Delete guild
4343
client.guilds.cache.delete(guild.id);
Lines changed: 16 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
'use strict';
22

3-
const VoiceBroadcast = require('./VoiceBroadcast');
4-
const VoiceConnection = require('./VoiceConnection');
5-
const { Error } = require('../../errors');
6-
const Collection = require('../../util/Collection');
3+
const { Events } = require('../../util/Constants');
74

85
/**
96
* Manages voice connections for the client
@@ -19,98 +16,29 @@ class ClientVoiceManager {
1916
Object.defineProperty(this, 'client', { value: client });
2017

2118
/**
22-
* A collection mapping connection IDs to the Connection objects
23-
* @type {Collection<Snowflake, VoiceConnection>}
19+
* Maps guild IDs to voice adapters created for use with @discordjs/voice.
20+
* @type {Map<Snowflake, Object>}
2421
*/
25-
this.connections = new Collection();
22+
this.adapters = new Map();
2623

27-
/**
28-
* Active voice broadcasts that have been created
29-
* @type {VoiceBroadcast[]}
30-
*/
31-
this.broadcasts = [];
32-
}
33-
34-
/**
35-
* Creates a voice broadcast.
36-
* @returns {VoiceBroadcast}
37-
*/
38-
createBroadcast() {
39-
const broadcast = new VoiceBroadcast(this.client);
40-
this.broadcasts.push(broadcast);
41-
return broadcast;
24+
client.on(Events.SHARD_DISCONNECT, (_, shardID) => {
25+
for (const [guildID, adapter] of this.adapters.entries()) {
26+
if (client.guilds.cache.get(guildID)?.shardID === shardID) {
27+
adapter.destroy();
28+
}
29+
}
30+
});
4231
}
4332

44-
onVoiceServer({ guild_id, token, endpoint }) {
45-
this.client.emit('debug', `[VOICE] voiceServer guild: ${guild_id} token: ${token} endpoint: ${endpoint}`);
46-
const connection = this.connections.get(guild_id);
47-
if (connection) connection.setTokenAndEndpoint(token, endpoint);
33+
onVoiceServer(payload) {
34+
this.adapters.get(payload.guild_id)?.onVoiceServerUpdate(payload);
4835
}
4936

50-
onVoiceStateUpdate({ guild_id, session_id, channel_id }) {
51-
const connection = this.connections.get(guild_id);
52-
this.client.emit('debug', `[VOICE] connection? ${!!connection}, ${guild_id} ${session_id} ${channel_id}`);
53-
if (!connection) return;
54-
if (!channel_id) {
55-
connection._disconnect();
56-
this.connections.delete(guild_id);
57-
return;
58-
}
59-
const channel = this.client.channels.cache.get(channel_id);
60-
if (channel) {
61-
connection.channel = channel;
62-
connection.setSessionID(session_id);
63-
} else {
64-
this.client.emit('debug', `[VOICE] disconnecting from guild ${guild_id} as channel ${channel_id} is uncached`);
65-
connection.disconnect();
37+
onVoiceStateUpdate(payload) {
38+
if (payload.guild_id && payload.session_id && payload.user_id === this.client.user?.id) {
39+
this.adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload);
6640
}
6741
}
68-
69-
/**
70-
* Sets up a request to join a voice or stage channel.
71-
* @param {VoiceChannel|StageChannel} channel The channel to join
72-
* @returns {Promise<VoiceConnection>}
73-
* @private
74-
*/
75-
joinChannel(channel) {
76-
return new Promise((resolve, reject) => {
77-
if (!channel.joinable) {
78-
throw new Error('VOICE_JOIN_CHANNEL', channel.full);
79-
}
80-
81-
let connection = this.connections.get(channel.guild.id);
82-
83-
if (connection) {
84-
if (connection.channel.id !== channel.id) {
85-
this.connections.get(channel.guild.id).updateChannel(channel);
86-
}
87-
resolve(connection);
88-
return;
89-
} else {
90-
connection = new VoiceConnection(this, channel);
91-
connection.on('debug', msg =>
92-
this.client.emit('debug', `[VOICE (${channel.guild.id}:${connection.status})]: ${msg}`),
93-
);
94-
connection.authenticate();
95-
this.connections.set(channel.guild.id, connection);
96-
}
97-
98-
connection.once('failed', reason => {
99-
this.connections.delete(channel.guild.id);
100-
reject(reason);
101-
});
102-
103-
connection.on('error', reject);
104-
105-
connection.once('authenticated', () => {
106-
connection.once('ready', () => {
107-
resolve(connection);
108-
connection.removeListener('error', reject);
109-
});
110-
connection.once('disconnect', () => this.connections.delete(channel.guild.id));
111-
});
112-
});
113-
}
11442
}
11543

11644
module.exports = ClientVoiceManager;

0 commit comments

Comments
 (0)