DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
VoiceConnection.hpp
Go to the documentation of this file.
1/*
2 MIT License
3
4 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
5
6 Copyright 2022, 2023 Chris M. (RealTimeChris)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26/// VoiceConnection.hpp - Header for the voice connection class.
27/// Jul 15, 2021
28/// https://discordcoreapi.com
29/// \file VoiceConnection.hpp
30#pragma once
31
39#include <sodium.h>
40
41namespace discord_core_api {
42
43 /// @brief Voice websocket close codes.
45 public:
46 /// @brief Voice websocket close codes.
47 enum class voice_websocket_close_code : uint16_t {
48 Unset = 1 << 0,///< Unset.
49 Normal_Close = 1 << 1,///< Normal close.
50 Unknown_Opcode = 1 << 2,///< You sent an invalid opcode.
51 Failed_To_Decode = 1 << 3,///< You sent an invalid payload in your identifying to the gateway.
52 Not_Authenticated = 1 << 4,///< You sent a payload before identifying with the gateway.
53 Authentication_Failed = 1 << 5,///< the token you sent in your identify payload is incorrect.
54 Already_Authenticated = 1 << 6,///< you sent more than one identify payload. stahp.
55 Session_No_Longer_Valid = 1 << 7,///< Your session is no longer valid.
56 Session_Timeout = 1 << 8,///< Your session has timed out.
57 Server_Not_Found = 1 << 9,///< We can't find the server you're trying to connect to.
58 Unknown_Protocol = 1 << 10,///< We didn't recognize the protocol you sent.
59 disconnected = 1 << 11,///< channel_data was deleted, you were kicked, voice server changed, or the main gateway session was dropped. should not reconnect.
60 Voice_Server_Crashed = 1 << 12,///< The server crashed. our bad! try resuming.
61 Unknown_Encryption_Mode = 1 << 13///< We didn't recognize your encryption.
62 };
63
64 DCA_INLINE static unordered_map<int32_t, voice_websocket_close_code> mappingValues{ { 0, voice_websocket_close_code::Unset },
71
72 DCA_INLINE static unordered_map<voice_websocket_close_code, jsonifier::string> outputErrorValues{ { voice_websocket_close_code::Unset, "Unset." },
73 { voice_websocket_close_code::Normal_Close, "normal close." }, { voice_websocket_close_code::Unknown_Opcode, "you sent an invalid opcode." },
74 { voice_websocket_close_code::Failed_To_Decode, "you sent an invalid payload in your identifying to the gateway." },
75 { voice_websocket_close_code::Not_Authenticated, "you sent a payload before identifying with the gateway." },
76 { voice_websocket_close_code::Authentication_Failed, "the token you sent in your identify payload is incorrect." },
77 { voice_websocket_close_code::Already_Authenticated, "you sent more than one identify payload. stahp." },
78 { voice_websocket_close_code::Session_No_Longer_Valid, "your session is no longer valid." },
79 { voice_websocket_close_code::Session_Timeout, "your session has timed out." },
80 { voice_websocket_close_code::Server_Not_Found, "we can't find the server you're trying to connect to." },
81 { voice_websocket_close_code::Unknown_Protocol, "we didn't recognize the protocol you sent." },
83 "channel_data was deleted, you were kicked, voice server changed, or the main gateway session was dropped. should not "
84 "reconnect." },
85 { voice_websocket_close_code::Voice_Server_Crashed, "the server crashed. our bad! try resuming." },
86 { voice_websocket_close_code::Unknown_Encryption_Mode, "we didn't recognize your encryption." } };
88
89 DCA_INLINE voice_websocket_close& operator=(uint16_t valueNew) {
90 value = static_cast<voice_websocket_close_code>(valueNew);
91 return *this;
92 };
93
94 DCA_INLINE voice_websocket_close(uint16_t value) {
95 *this = value;
96 };
97
98 DCA_INLINE operator jsonifier::string_view() {
99 return voice_websocket_close::outputErrorValues[mappingValues[static_cast<uint16_t>(value)]];
100 }
101
102 DCA_INLINE operator bool() {
103 return true;
104 }
105 };
106
107 struct voice_socket_ready_data {
108 jsonifier::vector<jsonifier::string> modes{};
109 jsonifier::string ip{};
110 uint16_t port{};
111 uint32_t ssrc{};
112 };
113
114 struct voice_session_description_data {
115 jsonifier::vector<uint8_t> secretKey{};
116 };
117
118 struct speaking_data {
119 snowflake userId{};
120 uint32_t ssrc{};
121 };
122
123 struct voice_connection_hello_data {
124 uint32_t heartBeatInterval{};
125 };
126
127 struct voice_user_disconnect_data {
128 snowflake userId{};
129 };
130
131 /// @brief The voice data for a given user, as raw PCM data, along with their Id.
133 jsonifier::string_view_base<uint8_t> voiceData{};
134 snowflake userId{};
135 };
136
137 struct DiscordCoreAPI_Dll voice_user {
138 voice_user() = default;
139
140 voice_user(snowflake userId);
141
142 voice_user& operator=(voice_user&& data) noexcept;
143
144 voice_user& operator=(const voice_user&) = delete;
145
146 voice_user(const voice_user&) = delete;
147
148 discord_core_internal::opus_decoder_wrapper& getDecoder();
149
150 jsonifier::string_view_base<uint8_t> extractPayload();
151
152 void insertPayload(jsonifier::string_view_base<uint8_t>);
153
154 snowflake getUserId();
155
156 protected:
157 discord_core_internal::ring_buffer<uint8_t, 10> payloads{};
158 discord_core_internal::opus_decoder_wrapper decoder{};
159 snowflake userId{};
160 };
161
162 struct DiscordCoreAPI_Dll rtp_packet_encrypter {
163 rtp_packet_encrypter() = default;
164
165 rtp_packet_encrypter(uint32_t ssrcNew, const jsonifier::string_base<uint8_t>& keysNew);
166
167 jsonifier::string_view_base<uint8_t> encryptPacket(discord_core_internal::encoder_return_data& audioData);
168
169 protected:
170 jsonifier::string_base<uint8_t> data{};
171 jsonifier::string_base<uint8_t> keys{};
172 uint32_t timeStamp{};
173 uint16_t sequence{};
174 uint32_t ssrc{};
175 };
176
177 struct DiscordCoreAPI_Dll moving_averager {
178 moving_averager(uint64_t collectionCountNew);
179
180 moving_averager operator+=(int64_t value);
181
182 operator float();
183
184 protected:
185 std::deque<int64_t> values{};
186 uint64_t collectionCount{};
187 };
188
189 /// @brief The various opcodes that could be sent/received by the voice-websocket.
191 identify = 0,///< Begin a voice websocket connection.
192 Select_Protocol = 1,///< Select the voice protocol.
193 Ready_Server = 2,///< complete the websocket handshake.
194 heartbeat = 3,///< Keep the websocket connection alive.
195 Session_Description = 4,///< Describe the session.
196 speaking = 5,///< Indicate which users are speaking.
197 Heartbeat_ACK = 6,///< Sent to acknowledge a received client heartbeat.
198 resume = 7,///< Resume a connection.
199 hello = 8,///< Time to wait between sending heartbeats in milliseconds.
200 resumed = 9,///< Acknowledge a successful session resume.
201 Client_Disconnect = 13,///< A client has disconnected from the voice channel.
202 };
203
204 /// @brief For the various connection states of the voice_connection class.
205 enum class voice_connection_state : uint8_t {
206 Collecting_Init_Data = 0,///< collecting initialization data.
207 Initializing_WebSocket = 1,///< Initializing the websocket.
208 Collecting_Hello = 2,///< collecting the client hello.
209 Sending_Identify = 3,///< Sending the identify payload.
210 Collecting_Ready = 4,///< collecting the client ready.
211 Initializing_DatagramSocket = 5,///< Initializing the datagram udp SOCKET.
212 Sending_Select_Protocol = 6,///< Sending the select-protocol payload.
213 Collecting_Session_Description = 7///< collecting the session-description payload.
214 };
215
216 /// @brief For the various active states of the voice_connection class.
217 enum class voice_active_state : int8_t {
218 connecting = 0,///< connecting.
219 playing = 1,///< Playing.
220 stopped = 2,///< Stopped.
221 paused = 3,///< Paused.
222 exiting = 4//< exiting.
223 };
224
225 class DiscordCoreAPI_Dll voice_connection_bridge : public discord_core_internal::udp_connection {
226 public:
227 friend class voice_connection;
228
229 voice_connection_bridge(unordered_map<uint64_t, unique_ptr<voice_user>>* voiceUsersPtrNew, jsonifier::string_base<uint8_t>& encryptionKeyNew, stream_type streamType,
230 const jsonifier::string& baseUrlNew, const uint16_t portNew, snowflake guildIdNew,
231 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type>* tokenNew);
232
233 DCA_INLINE void applyGainRamp(int64_t sampleCount);
234
235 void parseOutgoingVoiceData();
236
237 void handleAudioBuffer() override;
238
239 void mixAudio();
240
241 void disconnect() override;
242
243 protected:
244 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type>* token{};
245 unordered_map<uint64_t, unique_ptr<voice_user>>* voiceUsersPtr{};
246 jsonifier::string_base<uint8_t> decryptedDataString{};
247 std::array<opus_int16, 23040> downSampledVector{};
248 jsonifier::string_base<uint8_t> encryptionKey{};
249 std::array<opus_int32, 23040> upSampledVector{};
250 jsonifier::vector<uint8_t> resampleVector{};
251 moving_averager voiceUserCountAverage{ 25 };
252 bool doWeKeepAudioData{ false };
253 snowflake guildId{};
254 float currentGain{};
255 float increment{};
256 float endGain{};
257 };
258
259 class DiscordCoreAPI_Dll voice_udpconnection : public discord_core_internal::udp_connection {
260 public:
261 voice_udpconnection() = default;
262
263 voice_udpconnection(const jsonifier::string& baseUrlNew, uint16_t portNew, stream_type streamType, voice_connection* ptrNew,
264 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type>* stopToken);
265
266 void handleAudioBuffer() override;
267
268 void disconnect() override;
269
270 protected:
271 voice_connection* voiceConnection{};
272 };
273
274 /**
275 * \addtogroup voice_connection
276 * @{
277 */
278 /// @brief voice_connection class - represents the connection to a given voice channel_data.
279 class DiscordCoreAPI_Dll voice_connection : public discord_core_internal::websocket_core {
280 public:
281 friend class discord_core_internal::base_socket_agent;
282 friend class discord_core_internal::sound_cloud_api;
283 friend class discord_core_internal::you_tube_api;
284 friend class voice_connection_bridge;
285 friend class voice_udpconnection;
286 friend class discord_core_client;
287 friend class guild_cache_data;
288 friend class guild_data;
289 friend class song_api;
290
291 /// the constructor.
292 /// @param baseShardNew a pointer to the base shard that this voice connection belongs to.
293 /// @param doWeQuitNew a pointer to the global signalling boolean for exiting the application.
294 voice_connection(discord_core_internal::websocket_client* baseShardNew, std::atomic_bool* doWeQuitNew);
295
296 bool areWeConnected();
297
298 /// @brief Collects the currently connected-to voice channel_data's id.
299 /// @return snowflake a snowflake containing the channel_data's id.
300 snowflake getChannelId();
301
302 /// @brief Collects some voice data for a given user.
303 /// @return voice_user_payload The raw PCM audio data, along with that user's id.
304 voice_user_payload extractVoicePayload();
305
306 /// @brief Connects to a currently held voice channel.
307 /// @param initData a discord_coer_api::voice_connect_init_dat structure.
308 void connect(const voice_connect_init_data& initData);
309
310 ~voice_connection() = default;
311
312 protected:
313 std::atomic<voice_connection_state> connectionState{ voice_connection_state::Collecting_Init_Data };
314 unbounded_message_block<discord_core_internal::voice_connection_data> voiceConnectionDataBuffer{};
315 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type> token{};
316 nanoseconds intervalCount{ static_cast<int64_t>(960.0l / 48000.0l * 1000000000.0l) };
317 std::atomic<voice_active_state> prevActiveState{ voice_active_state::stopped };
318 std::atomic<voice_active_state> activeState{ voice_active_state::connecting };
319 discord_core_internal::voice_connection_data voiceConnectionData{};
320 unordered_map<uint64_t, unique_ptr<voice_user>> voiceUsers{};
321 discord_core_internal::opus_encoder_wrapper encoder{};
322 discord_core_internal::websocket_client* baseShard{};
323 unique_ptr<voice_connection_bridge> streamSocket{};
324 jsonifier::string_base<uint8_t> encryptionKey{};
325 voice_connect_init_data voiceConnectInitData{};
326 jsonifier::string audioEncryptionMode{};
327 rtp_packet_encrypter packetEncrypter{};
328 int64_t sampleRatePerSecond{ 48000 };
329 co_routine<void, false> taskThread{};
330 voice_udpconnection udpConnection{};
331 int64_t nsPerSecond{ 1000000000 };
332 audio_frame_data xferAudioData{};
333 jsonifier::string externalIp{};
334 std::atomic_bool wasItAFail{};
335 std::atomic_bool* doWeQuit{};
336 std::atomic_bool doWeSkip{};
337 jsonifier::string voiceIp{};
338 jsonifier::string baseUrl{};
339 int64_t samplesPerPacket{};
340 snowflake currentUserId{};
341 int64_t msPerPacket{};
342 uint32_t audioSSRC{};
343 uint16_t port{};
344
345 void parseIncomingVoiceData(jsonifier::string_view_base<uint8_t> rawDataBufferNew);
346
347 bool onMessageReceived(jsonifier::string_view_base<uint8_t> data);
348
349 unbounded_message_block<audio_frame_data>& getAudioBuffer();
350
351 void skipInternal(uint32_t currentRecursionDepth = 0);
352
353 void checkForAndSendHeartBeat(const bool isImmedate);
354
355 void sendSpeakingMessage(const bool isSpeaking);
356
357 co_routine<void, false> runVoice();
358
359 void sendVoiceConnectionData();
360
361 bool areWeCurrentlyPlaying();
362
363 bool skip(bool wasItAFail);
364
365 void connectInternal();
366
367 bool voiceConnect();
368
369 void sendSilence();
370
371 bool pauseToggle();
372
373 void disconnect();
374
375 void reconnect();
376
377 void onClosed();
378
379 bool stop();
380
381 bool play();
382 };
383 /**@}*/
384
385};
@ reconnect
You should attempt to reconnect and resume immediately.
voice_connection class - represents the connection to a given voice channel_data.
voice_user_payload extractVoicePayload()
Collects some voice data for a given user.
voice_websocket_close_code
Voice websocket close codes.
@ Voice_Server_Crashed
The server crashed. our bad! try resuming.
@ disconnected
channel_data was deleted, you were kicked, voice server changed, or the main gateway session was drop...
@ Already_Authenticated
you sent more than one identify payload. stahp.
@ Authentication_Failed
the token you sent in your identify payload is incorrect.
@ Failed_To_Decode
You sent an invalid payload in your identifying to the gateway.
@ Server_Not_Found
We can't find the server you're trying to connect to.
@ Not_Authenticated
You sent a payload before identifying with the gateway.
@ connect
Allows for joining of a voice channel.
The main namespace for the forward-facing interfaces.
voice_socket_op_codes
The various opcodes that could be sent/received by the voice-websocket.
@ resumed
Acknowledge a successful session resume.
@ heartbeat
Keep the websocket connection alive.
@ Ready_Server
complete the websocket handshake.
@ hello
Time to wait between sending heartbeats in milliseconds.
@ Heartbeat_ACK
Sent to acknowledge a received client heartbeat.
@ identify
Begin a voice websocket connection.
@ Client_Disconnect
A client has disconnected from the voice channel.
@ Select_Protocol
Select the voice protocol.
@ speaking
Indicate which users are speaking.
voice_connection_state
For the various connection states of the voice_connection class.
@ Collecting_Init_Data
collecting initialization data.
@ Collecting_Ready
collecting the client ready.
@ Sending_Identify
Sending the identify payload.
@ Collecting_Hello
collecting the client hello.
@ Initializing_WebSocket
Initializing the websocket.
@ Initializing_DatagramSocket
Initializing the datagram udp SOCKET.
@ Collecting_Session_Description
collecting the session-description payload.
@ Sending_Select_Protocol
Sending the select-protocol payload.
voice_active_state
For the various active states of the voice_connection class.
The voice data for a given user, as raw PCM data, along with their Id.