/
AudioEchoExample.java
275 lines (245 loc) · 10.5 KB
/
AudioEchoExample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
* Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.audio.AudioReceiveHandler;
import net.dv8tion.jda.api.audio.AudioSendHandler;
import net.dv8tion.jda.api.audio.CombinedAudio;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.managers.AudioManager;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class AudioEchoExample extends ListenerAdapter
{
public static void main(String[] args)
{
if (args.length == 0)
{
System.err.println("Unable to start without token!");
System.exit(1);
}
String token = args[0];
// We only need 3 gateway intents enabled for this example:
EnumSet<GatewayIntent> intents = EnumSet.of(
// We need messages in guilds to accept commands from users
GatewayIntent.GUILD_MESSAGES,
// We need voice states to connect to the voice channel
GatewayIntent.GUILD_VOICE_STATES,
// Enable access to message.getContentRaw()
GatewayIntent.MESSAGE_CONTENT
);
// Start the JDA session with default mode (voice member cache)
JDABuilder.createDefault(token, intents) // Use provided token from command line arguments
.addEventListeners(new AudioEchoExample()) // Start listening with this listener
.setActivity(Activity.listening("to jams")) // Inform users that we are jammin' it out
.setStatus(OnlineStatus.DO_NOT_DISTURB) // Please don't disturb us while we're jammin'
.enableCache(CacheFlag.VOICE_STATE) // Enable the VOICE_STATE cache to find a user's connected voice channel
.build(); // Login with these options
}
@Override
public void onMessageReceived(MessageReceivedEvent event)
{
Message message = event.getMessage();
User author = message.getAuthor();
String content = message.getContentRaw();
Guild guild = event.getGuild();
// Ignore message if bot
if (author.isBot())
return;
// We only want to handle message in Guilds
if (!event.isFromGuild())
{
return;
}
if (content.startsWith("!echo "))
{
String arg = content.substring("!echo ".length());
onEchoCommand(event, guild, arg);
}
else if (content.equals("!echo"))
{
onEchoCommand(event);
}
}
/**
* Handle command without arguments.
*
* @param event
* The event for this command
*/
private void onEchoCommand(MessageReceivedEvent event)
{
// Note: None of these can be null due to our configuration with the JDABuilder!
Member member = event.getMember(); // Member is the context of the user for the specific guild, containing voice state and roles
GuildVoiceState voiceState = member.getVoiceState(); // Check the current voice state of the user
AudioChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to
if (channel != null)
{
connectTo(channel); // Join the channel of the user
onConnecting(channel, event.getChannel()); // Tell the user about our success
}
else
{
onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure
}
}
/**
* Handle command with arguments.
*
* @param event
* The event for this command
* @param guild
* The guild where its happening
* @param arg
* The input argument
*/
private void onEchoCommand(MessageReceivedEvent event, Guild guild, String arg)
{
boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits
VoiceChannel channel = null;
if (isNumber) // The input is an id?
{
channel = guild.getVoiceChannelById(arg);
}
if (channel == null) // Then the input must be a name?
{
List<VoiceChannel> channels = guild.getVoiceChannelsByName(arg, true);
if (!channels.isEmpty()) // Make sure we found at least one exact match
channel = channels.get(0); // We found a channel! This cannot be null.
}
MessageChannel messageChannel = event.getChannel();
if (channel == null) // I have no idea what you want mr user
{
onUnknownChannel(messageChannel, arg); // Let the user know about our failure
return;
}
connectTo(channel); // We found a channel to connect to!
onConnecting(channel, messageChannel); // Let the user know, we were successful!
}
/**
* Inform user about successful connection.
*
* @param channel
* The voice channel we connected to
* @param messageChannel
* The text channel to send the message in
*/
private void onConnecting(AudioChannel channel, MessageChannel messageChannel)
{
messageChannel.sendMessage("Connecting to " + channel.getName()).queue(); // never forget to queue()!
}
/**
* The channel to connect to is not known to us.
*
* @param channel
* The message channel (text channel abstraction) to send failure information to
* @param comment
* The information of this channel
*/
private void onUnknownChannel(MessageChannel channel, String comment)
{
channel.sendMessage("Unable to connect to ``" + comment + "``, no such channel!").queue(); // never forget to queue()!
}
/**
* Connect to requested channel and start echo handler
*
* @param channel
* The channel to connect to
*/
private void connectTo(AudioChannel channel)
{
Guild guild = channel.getGuild();
// Get an audio manager for this guild, this will be created upon first use for each guild
AudioManager audioManager = guild.getAudioManager();
// Create our Send/Receive handler for the audio connection
EchoHandler handler = new EchoHandler();
// The order of the following instructions does not matter!
// Set the sending handler to our echo system
audioManager.setSendingHandler(handler);
// Set the receiving handler to the same echo system, otherwise we can't echo anything
audioManager.setReceivingHandler(handler);
// Connect to the voice channel
audioManager.openAudioConnection(channel);
}
public static class EchoHandler implements AudioSendHandler, AudioReceiveHandler
{
/*
All methods in this class are called by JDA threads when resources are available/ready for processing.
The receiver will be provided with the latest 20ms of PCM stereo audio
Note you can receive even while setting yourself to deafened!
The sender will provide 20ms of PCM stereo audio (pass-through) once requested by JDA
When audio is provided JDA will automatically set the bot to speaking!
*/
private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>();
/* Receive Handling */
@Override // combine multiple user audio-streams into a single one
public boolean canReceiveCombined()
{
// limit queue to 10 entries, if that is exceeded we can not receive more until the send system catches up
return queue.size() < 10;
}
@Override
public void handleCombinedAudio(CombinedAudio combinedAudio)
{
// we only want to send data when a user actually sent something, otherwise we would just send silence
if (combinedAudio.getUsers().isEmpty())
return;
byte[] data = combinedAudio.getAudioData(1.0f); // volume at 100% = 1.0 (50% = 0.5 / 55% = 0.55)
queue.add(data);
}
/*
Disable per-user audio since we want to echo the entire channel and not specific users.
@Override // give audio separately for each user that is speaking
public boolean canReceiveUser()
{
// this is not useful if we want to echo the audio of the voice channel, thus disabled for this purpose
return false;
}
@Override
public void handleUserAudio(UserAudio userAudio) {} // per-user is not helpful in an echo system
*/
/* Send Handling */
@Override
public boolean canProvide()
{
// If we have something in our buffer we can provide it to the send system
return !queue.isEmpty();
}
@Override
public ByteBuffer provide20MsAudio()
{
// use what we have in our buffer to send audio as PCM
byte[] data = queue.poll();
return data == null ? null : ByteBuffer.wrap(data); // Wrap this in a java.nio.ByteBuffer
}
@Override
public boolean isOpus()
{
// since we send audio that is received from discord we don't have opus but PCM
return false;
}
}
}