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 inline static unordered_map<int32_t, voice_websocket_close_code> mappingValues{ { 0, voice_websocket_close_code::unset },
71
72 inline static unordered_map<voice_websocket_close_code, jsonifier::string_view> 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 inline voice_websocket_close& operator=(uint16_t valueNew) {
90 value = static_cast<voice_websocket_close_code>(valueNew);
91 return *this;
92 };
93
94 inline voice_websocket_close(uint16_t value) {
95 *this = value;
96 };
97
98 inline operator jsonifier::string_view() {
99 return voice_websocket_close::outputErrorValues[mappingValues[static_cast<uint16_t>(value)]];
100 }
101
102 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 struct DiscordCoreAPI_Dll voice_user {
132 voice_user() = default;
133
134 voice_user(snowflake userId);
135
136 voice_user& operator=(voice_user&& data) noexcept;
137
138 voice_user& operator=(const voice_user&) = delete;
139
140 voice_user(const voice_user&) = delete;
141
142 discord_core_internal::opus_decoder_wrapper& getDecoder();
143
144 jsonifier::string_view_base<uint8_t> extractPayload();
145
146 void insertPayload(jsonifier::string_view_base<uint8_t>);
147
148 snowflake getUserId();
149
150 protected:
151 discord_core_internal::ring_buffer<uint8_t, 10> payloads{};
152 discord_core_internal::opus_decoder_wrapper decoder{};
153 snowflake userId{};
154 };
155
156 struct DiscordCoreAPI_Dll rtppacket_encrypter {
157 rtppacket_encrypter() = default;
158
159 rtppacket_encrypter(uint32_t ssrcNew, const jsonifier::string_base<uint8_t>& keysNew);
160
161 jsonifier::string_view_base<uint8_t> encryptPacket(discord_core_internal::encoder_return_data& audioData);
162
163 protected:
164 jsonifier::string_base<uint8_t> data{};
165 jsonifier::string_base<uint8_t> keys{};
166 uint32_t timeStamp{};
167 uint16_t sequence{};
168 uint32_t ssrc{};
169 };
170
171 struct DiscordCoreAPI_Dll moving_averager {
172 moving_averager(uint64_t collectionCountNew);
173
174 moving_averager operator+=(int64_t value);
175
176 operator float();
177
178 protected:
179 std::deque<int64_t> values{};
180 uint64_t collectionCount{};
181 };
182
183 /// @brief The various opcodes that could be sent/received by the voice-websocket.
185 identify = 0,///< Begin a voice websocket connection.
186 Select_Protocol = 1,///< Select the voice protocol.
187 Ready_Server = 2,///< complete the websocket handshake.
188 heartbeat = 3,///< Keep the websocket connection alive.
189 Session_Description = 4,///< Describe the session.
190 speaking = 5,///< Indicate which users are speaking.
191 Heartbeat_ACK = 6,///< Sent to acknowledge a received client heartbeat.
192 resume = 7,///< Resume a connection.
193 hello = 8,///< Time to wait between sending heartbeats in milliseconds.
194 resumed = 9,///< Acknowledge a successful session resume.
195 Client_Disconnect = 13,///< A client has disconnected from the voice channel.
196 };
197
198 /// @brief For the various connection states of the voice_connection class.
199 enum class voice_connection_state : uint8_t {
200 Collecting_Init_Data = 0,///< collecting initialization data.
201 Initializing_WebSocket = 1,///< Initializing the websocket.
202 Collecting_Hello = 2,///< collecting the client hello.
203 Sending_Identify = 3,///< Sending the identify payload.
204 Collecting_Ready = 4,///< collecting the client ready.
205 Initializing_DatagramSocket = 5,///< Initializing the datagram udp SOCKET.
206 Sending_Select_Protocol = 6,///< Sending the select-protocol payload.
207 Collecting_Session_Description = 7///< collecting the session-description payload.
208 };
209
210 /// @brief For the various active states of the voice_connection class.
211 enum class voice_active_state : int8_t {
212 connecting = 0,///< connecting.
213 playing = 1,///< Playing.
214 stopped = 2,///< Stopped.
215 paused = 3,///< Paused.
216 exiting = 4//< exiting.
217 };
218
219 class DiscordCoreAPI_Dll voice_connection_bridge : public discord_core_internal::udp_connection {
220 public:
221 friend class voice_connection;
222
223 voice_connection_bridge(unordered_map<uint64_t, unique_ptr<voice_user>>* voiceUsersPtrNew, jsonifier::string_base<uint8_t>& encryptionKeyNew, stream_type streamType,
224 const jsonifier::string& baseUrlNew, const uint16_t portNew, snowflake guildIdNew,
225 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type>* tokenNew);
226
227 inline void applyGainRamp(int64_t sampleCount);
228
229 void parseOutgoingVoiceData();
230
231 void handleAudioBuffer() override;
232
233 void mixAudio();
234
235 void disconnect() override;
236
237 protected:
238 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type>* token{};
239 unordered_map<uint64_t, unique_ptr<voice_user>>* voiceUsersPtr{};
240 jsonifier::string_base<uint8_t> decryptedDataString{};
241 std::array<opus_int16, 23040> downSampledVector{};
242 jsonifier::string_base<uint8_t> encryptionKey{};
243 std::array<opus_int32, 23040> upSampledVector{};
244 jsonifier::vector<uint8_t> resampleVector{};
245 moving_averager voiceUserCountAverage{ 25 };
246 snowflake guildId{};
247 float currentGain{};
248 float increment{};
249 float endGain{};
250 };
251
252 class DiscordCoreAPI_Dll voice_udpconnection : public discord_core_internal::udp_connection {
253 public:
254 voice_udpconnection() = default;
255
256 voice_udpconnection(const jsonifier::string& baseUrlNew, uint16_t portNew, stream_type streamType, voice_connection* ptrNew,
257 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type>* stopToken);
258
259 void handleAudioBuffer() override;
260
261 void disconnect() override;
262
263 protected:
264 voice_connection* voiceConnection{};
265 };
266
267 /**
268 * \addtogroup voice_connection
269 * @{
270 */
271 /// @brief voice_connection class - represents the connection to a given voice channel_data.
272 class DiscordCoreAPI_Dll voice_connection : public discord_core_internal::websocket_core {
273 public:
274 friend class discord_core_internal::base_socket_agent;
275 friend class discord_core_internal::sound_cloud_api;
276 friend class discord_core_internal::you_tube_api;
277 friend class voice_connection_bridge;
278 friend class voice_udpconnection;
279 friend class discord_core_client;
280 friend class guild_cache_data;
281 friend class guild_data;
282 friend class song_api;
283
284 /// the constructor.
285 /// @param baseShardNew a pointer to the base shard that this voice connection belongs to.
286 /// @param doWeQuitNew a pointer to the global signalling boolean for exiting the application.
287 voice_connection(discord_core_internal::websocket_client* baseShardNew, std::atomic_bool* doWeQuitNew);
288
289 bool areWeConnected();
290
291 /// @brief Collects the currently connected-to voice channel_data's id.
292 /// @return snowflake a snowflake containing the channel_data's id.
293 snowflake getChannelId();
294
295 /// @brief Connects to a currently held voice channel.
296 /// @param initData a discord_coer_api::voice_connect_init_dat structure.
297 void connect(const voice_connect_init_data& initData);
298
299 ~voice_connection() = default;
300
301 protected:
302 std::atomic<voice_connection_state> connectionState{ voice_connection_state::Collecting_Init_Data };
304 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type> token{};
305 nanoseconds intervalCount{ static_cast<int64_t>(960.0l / 48000.0l * 1000000000.0l) };
306 std::atomic<voice_active_state> prevActiveState{ voice_active_state::stopped };
307 std::atomic<voice_active_state> activeState{ voice_active_state::connecting };
308 discord_core_internal::voice_connection_data voiceConnectionData{};
309 unordered_map<uint64_t, unique_ptr<voice_user>> voiceUsers{};
313 jsonifier::string_base<uint8_t> encryptionKey{};
314 voice_connect_init_data voiceConnectInitData{};
315 jsonifier::string audioEncryptionMode{};
316 rtppacket_encrypter packetEncrypter{};
317 int64_t sampleRatePerSecond{ 48000 };
318 co_routine<void, false> taskThread{};
319 voice_udpconnection udpConnection{};
320 int64_t nsPerSecond{ 1000000000 };
321 audio_frame_data xferAudioData{};
322 jsonifier::string externalIp{};
323 std::atomic_bool wasItAFail{};
324 std::atomic_bool* doWeQuit{};
325 std::atomic_bool doWeSkip{};
326 jsonifier::string voiceIp{};
327 jsonifier::string baseUrl{};
328 int64_t samplesPerPacket{};
329 snowflake currentUserId{};
330 int64_t msPerPacket{};
331 uint32_t audioSSRC{};
332 uint16_t port{};
333
334 void parseIncomingVoiceData(jsonifier::string_view_base<uint8_t> rawDataBufferNew);
335
336 bool onMessageReceived(jsonifier::string_view_base<uint8_t> data);
337
339
340 void skipInternal(uint32_t currentRecursionDepth = 0);
341
342 void checkForAndSendHeartBeat(const bool isImmedate);
343
344 void sendSpeakingMessage(const bool isSpeaking);
345
346 co_routine<void, false> runVoice();
347
348 void sendVoiceConnectionData();
349
350 bool areWeCurrentlyPlaying();
351
352 bool skip(bool wasItAFail);
353
354 void connectInternal();
355
356 bool voiceConnect();
357
358 void sendSilence();
359
360 bool pauseToggle();
361
362 void disconnect();
363
364 void reconnect();
365
366 void onClosed();
367
368 bool stop();
369
370 bool play();
371 };
372 /**@}*/
373
374};
A co_routine - representing a potentially asynchronous operation/function.
Definition: CoRoutine.hpp:83
discord_core_client - the main class for this library.
A websocket client, for communication via a tcp-connection.
Data structure representing a single guild, for the purposes of populating the cache.
A discord guild. used to connect to/disconnect from voice.
A class representing a snowflake identifier with various operations.
Definition: Base.hpp:680
A class representing the song apis.
Definition: SongAPI.hpp:45
A thread-safe messaging block for data-structures.
A smart pointer class that provides unique ownership semantics.
Definition: UniquePtr.hpp:44
voice_connection class - represents the connection to a given voice channel_data.
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.
@ Unknown_Protocol
We didn't recognize the protocol you sent.
@ 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.
@ Session_Description
Describe the session.
@ 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.
Represents a single frame of audio data.
Definition: Utilities.hpp:381
For connecting to a voice-channel. "streamInfo" is used when a SOCKET is created to connect this bot ...
Definition: Utilities.hpp:403