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
31#pragma once
32
40#include <sodium.h>
41
42namespace DiscordCoreAPI {
43
44 /// @brief Voice Websocket close codes.
46 public:
47 /// @brief Voice Websocket close codes.
48 enum class VoiceWebSocketCloseCode : uint16_t {
49 Unset = 1 << 0,///< Unset.
50 Normal_Close = 1 << 1,///< Normal close.
51 Unknown_Opcode = 1 << 2,///< You sent an invalid opcode.
52 Failed_To_Decode = 1 << 3,///< You sent an invalid payload in your identifying to the Gateway.
53 Not_Authenticated = 1 << 4,///< You sent a payload before identifying with the Gateway.
54 Authentication_Failed = 1 << 5,///< The token you sent in your identify payload is incorrect.
55 Already_Authenticated = 1 << 6,///< You sent more than one identify payload. Stahp.
56 Session_No_Longer_Valid = 1 << 7,///< Your session is no longer valid.
57 Session_Timeout = 1 << 8,///< Your session has timed out.
58 Server_Not_Found = 1 << 9,///< We can't find the server you're trying to connect to.
59 Unknown_Protocol = 1 << 10,///< We didn't recognize the protocol you sent.
60 Disconnected = 1 << 11,///< ChannelData was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not reconnect.
61 Voice_Server_Crashed = 1 << 12,///< The server crashed. Our bad! Try resuming.
62 Unknown_Encryption_Mode = 1 << 13///< We didn't recognize your encryption.
63 };
64
65 static inline UnorderedMap<int32_t, VoiceWebSocketCloseCode> mappingValues{ { 0, VoiceWebSocketCloseCode::Unset }, { 1000, VoiceWebSocketCloseCode::Normal_Close },
71
72 static inline UnorderedMap<VoiceWebSocketCloseCode, std::string_view> outputErrorValues{ { VoiceWebSocketCloseCode::Unset, "Unset." },
73 { VoiceWebSocketCloseCode::Normal_Close, "Normal close." }, { VoiceWebSocketCloseCode::Unknown_Opcode, "You sent an invalid opcode." },
74 { VoiceWebSocketCloseCode::Failed_To_Decode, "You sent an invalid payload in your identifying to the Gateway." },
75 { VoiceWebSocketCloseCode::Not_Authenticated, "You sent a payload before identifying with the Gateway." },
76 { VoiceWebSocketCloseCode::Authentication_Failed, "The token you sent in your identify payload is incorrect." },
77 { VoiceWebSocketCloseCode::Already_Authenticated, "You sent more than one identify payload. Stahp." },
78 { VoiceWebSocketCloseCode::Session_No_Longer_Valid, "Your session is no longer valid." }, { VoiceWebSocketCloseCode::Session_Timeout, "Your session has timed out." },
79 { VoiceWebSocketCloseCode::Server_Not_Found, "We can't find the server you're trying to connect to." },
80 { VoiceWebSocketCloseCode::Unknown_Protocol, "We didn't recognize the protocol you sent." },
82 "ChannelData was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not "
83 "reconnect." },
84 { VoiceWebSocketCloseCode::Voice_Server_Crashed, "The server crashed. Our bad! Try resuming." },
85 { VoiceWebSocketCloseCode::Unknown_Encryption_Mode, "We didn't recognize your encryption." } };
86
88
89 inline VoiceWebSocketClose& operator=(uint16_t valueNew) {
90 value = static_cast<VoiceWebSocketCloseCode>(valueNew);
91 return *this;
92 };
93
94 inline VoiceWebSocketClose(uint16_t value) {
95 *this = value;
96 };
97
98 inline operator std::string_view() {
99 return VoiceWebSocketClose::outputErrorValues[mappingValues[static_cast<uint16_t>(value)]];
100 }
101
102 inline operator bool() {
103 return true;
104 }
105 };
106
107 struct VoiceSocketReadyData {
108 jsonifier::vector<std::string> modes{};
109 std::string ip{};
110 uint16_t port{};
111 uint32_t ssrc{};
112 };
113
114 struct VoiceSessionDescriptionData {
115 jsonifier::vector<uint8_t> secretKey{};
116 };
117
118 struct SpeakingData {
119 Snowflake userId{};
120 uint32_t ssrc{};
121 };
122
123 struct VoiceConnectionHelloData {
124 uint32_t heartBeatInterval{};
125 };
126
127 struct VoiceUserDisconnectData {
128 Snowflake userId{};
129 };
130
131 struct DiscordCoreAPI_Dll VoiceUser {
132 VoiceUser() = default;
133
134 VoiceUser(Snowflake userId);
135
136 VoiceUser& operator=(VoiceUser&&) noexcept;
137
138 VoiceUser& operator=(const VoiceUser&) = delete;
139
140 VoiceUser(const VoiceUser&) = delete;
141
142 DiscordCoreInternal::OpusDecoderWrapper& getDecoder();
143
144 std::basic_string_view<uint8_t> extractPayload();
145
146 void insertPayload(std::basic_string_view<uint8_t>);
147
148 Snowflake getUserId();
149
150 protected:
151 DiscordCoreInternal::RingBuffer<uint8_t, 10> payloads{};
152 DiscordCoreInternal::OpusDecoderWrapper decoder{};
153 Snowflake userId{};
154 };
155
156 struct DiscordCoreAPI_Dll RTPPacketEncrypter {
157 RTPPacketEncrypter() = default;
158
159 RTPPacketEncrypter(uint32_t ssrcNew, const std::basic_string<uint8_t>& keysNew);
160
161 std::basic_string_view<uint8_t> encryptPacket(DiscordCoreInternal::EncoderReturnData& audioData);
162
163 protected:
164 std::basic_string<uint8_t> data{};
165 std::basic_string<uint8_t> keys{};
166 uint8_t version{ 0x80 };
167 uint8_t flags{ 0x78 };
168 uint32_t timeStamp{};
169 uint16_t sequence{};
170 uint32_t ssrc{};
171 };
172
173 struct DiscordCoreAPI_Dll MovingAverager {
174 MovingAverager(uint64_t collectionCountNew);
175
176 MovingAverager operator+=(int64_t value);
177
178 operator float();
179
180 protected:
181 std::deque<int64_t> values{};
182 uint64_t collectionCount{};
183 };
184
185 /// @brief The various opcodes that could be sent/received by the voice-websocket.
187 Identify = 0,///< Begin a voice websocket connection.
188 Select_Protocol = 1,///< Select the voice protocol.
189 Ready_Server = 2,///< Complete the websocket handshake.
190 Heartbeat = 3,///< Keep the websocket connection alive.
191 Session_Description = 4,///< Describe the session.
192 Speaking = 5,///< Indicate which users are speaking.
193 Heartbeat_ACK = 6,///< Sent to acknowledge a received client heartbeat.
194 Resume = 7,///< Resume a connection.
195 Hello = 8,///< Time to wait between sending heartbeats in milliseconds.
196 Resumed = 9,///< Acknowledge a successful session resume.
197 Client_Disconnect = 13,///< A client has disconnected from the voice channel.
198 };
199
200 /// @brief For the various connection states of the VoiceConnection class.
201 enum class VoiceConnectionState : uint8_t {
202 Collecting_Init_Data = 0,///< Collecting initialization data.
203 Initializing_WebSocket = 1,///< Initializing the WebSocket.
204 Collecting_Hello = 2,///< Collecting the client hello.
205 Sending_Identify = 3,///< Sending the identify payload.
206 Collecting_Ready = 4,///< Collecting the client ready.
207 Initializing_DatagramSocket = 5,///< Initializing the datagram udp socket.
208 Sending_Select_Protocol = 6,///< Sending the select-protocol payload.
209 Collecting_Session_Description = 7///< Collecting the session-description payload.
210 };
211
212 /// @brief For the various active states of the VoiceConnection class.
213 enum class VoiceActiveState : int8_t {
214 Connecting = 0,///< Connecting.
215 Playing = 1,///< Playing.
216 Stopped = 2,///< Stopped.
217 Paused = 3,///< Paused.
218 Exiting = 4//< Exiting.
219 };
220
221 class DiscordCoreAPI_Dll VoiceConnectionBridge : public DiscordCoreInternal::UDPConnection {
222 public:
223 friend class VoiceConnection;
224
225 VoiceConnectionBridge(DiscordCoreClient* voiceConnectionNew, std::basic_string<uint8_t>& encryptionKeyNew, StreamType streamType, const std::string& baseUrlNew,
226 const uint16_t portNew, Snowflake guildIdNew, std::coroutine_handle<DiscordCoreAPI::CoRoutine<void, false>::promise_type>* tokenNew);
227
228 inline void applyGainRamp(int64_t sampleCount);
229
230 void parseOutgoingVoiceData();
231
232 void handleAudioBuffer() override;
233
234 void mixAudio();
235
236 void disconnect() override;
237
238 protected:
239 std::coroutine_handle<DiscordCoreAPI::CoRoutine<void, false>::promise_type>* token{};
240 std::array<opus_int16, 23040> downSampledVector{};
241 std::basic_string<uint8_t> decryptedDataString{};
242 std::array<opus_int32, 23040> upSampledVector{};
243 std::basic_string<uint8_t> encryptionKey{};
244 MovingAverager voiceUserCountAverage{ 25 };
245 DiscordCoreClient* discordCoreClient{};
246 jsonifier::vector<uint8_t> resampleVector{};
247 Snowflake guildId{};
248 float currentGain{};
249 float increment{};
250 float endGain{};
251 };
252
253 class DiscordCoreAPI_Dll VoiceUDPConnection : public DiscordCoreInternal::UDPConnection {
254 public:
255 VoiceUDPConnection() = default;
256
257 VoiceUDPConnection(const std::string& baseUrlNew, uint16_t portNew, StreamType streamType, VoiceConnection* ptrNew,
258 std::coroutine_handle<DiscordCoreAPI::CoRoutine<void, false>::promise_type>* stopToken);
259
260 void handleAudioBuffer() override;
261
262 void disconnect() override;
263
264 protected:
265 VoiceConnection* voiceConnection{};
266 };
267
268 /**
269 * \addtogroup voice_connection
270 * @{
271 */
272 /// @brief VoiceConnection class - represents the connection to a given voice ChannelData.
273 class DiscordCoreAPI_Dll VoiceConnection : public DiscordCoreInternal::WebSocketCore {
274 public:
275 friend class DiscordCoreInternal::BaseSocketAgent;
276 friend class DiscordCoreInternal::SoundCloudAPI;
277 friend class DiscordCoreInternal::YouTubeAPI;
278 friend class VoiceConnectionBridge;
279 friend class VoiceUDPConnection;
280 friend class DiscordCoreClient;
281 friend class GuildCacheData;
282 friend class GuildData;
283 friend class SongAPI;
284
285 /// The constructor.
286 /// @param discordCoreClientNew A pointer to the main isntance of DiscordCoreClient.
287 /// @param baseShardNew A pointer to the base shard that this voice connection belongs to.
288 /// @param doWeQuitNew A pointer to the global signalling boolean for exiting the application.
289 VoiceConnection(DiscordCoreClient* discordCoreClientNew, DiscordCoreInternal::WebSocketClient* baseShardNew, std::atomic_bool* doWeQuitNew);
290
291 bool areWeConnected();
292
293 /// @brief Collects the currently connected-to voice ChannelData's id.
294 /// @returns Snowflake A Snowflake containing the ChannelData's id.
295 Snowflake getChannelId();
296
297 /// @brief Connects to a currently held voice channel.
298 /// @param initData A DiscordCoerAPI::VoiceConnectInitDat structure.
299 void connect(const VoiceConnectInitData& initData);
300
301 ~VoiceConnection() = default;
302
303 protected:
304 std::atomic<VoiceConnectionState> connectionState{ VoiceConnectionState::Collecting_Init_Data };
306 Nanoseconds intervalCount{ static_cast<int64_t>(960.0l / 48000.0l * 1000000000.0l) };
307 std::coroutine_handle<DiscordCoreAPI::CoRoutine<void, false>::promise_type> token{};
308 std::atomic<VoiceActiveState> prevActiveState{ VoiceActiveState::Stopped };
309 std::atomic<VoiceActiveState> activeState{ VoiceActiveState::Connecting };
310 DiscordCoreInternal::VoiceConnectionData voiceConnectionData{};
311 UnorderedMap<uint64_t, UniquePtr<VoiceUser>> voiceUsers{};
313 DiscordCoreInternal::WebSocketClient* baseShard{};
315 VoiceConnectInitData voiceConnectInitData{};
316 std::basic_string<uint8_t> encryptionKey{};
317 DiscordCoreClient* discordCoreClient{};
318 int64_t sampleRatePerSecond{ 48000 };
319 RTPPacketEncrypter packetEncrypter{};
320 CoRoutine<void, false> taskThread{};
321 VoiceUDPConnection udpConnection{};
322 int64_t nsPerSecond{ 1000000000 };
323 std::string audioEncryptionMode{};
324 AudioFrameData xferAudioData{};
325 std::atomic_bool wasItAFail{};
326 std::atomic_bool* doWeQuit{};
327 std::atomic_bool doWeSkip{};
328 int64_t samplesPerPacket{};
329 std::string externalIp{};
330 int64_t msPerPacket{};
331 std::string voiceIp{};
332 std::string baseUrl{};
333 uint32_t audioSSRC{};
334 uint16_t port{};
335
336 void parseIncomingVoiceData(std::basic_string_view<uint8_t> rawDataBufferNew);
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 bool onMessageReceived(std::string_view data);
347
348 CoRoutine<void, false> runVoice();
349
350 void sendVoiceConnectionData();
351
352 bool areWeCurrentlyPlaying();
353
354 bool skip(bool wasItAFail);
355
356 void connectInternal();
357
358 bool voiceConnect();
359
360 void sendSilence();
361
362 bool pauseToggle();
363
364 void disconnect();
365
366 void reconnect();
367
368 void onClosed();
369
370 bool stop();
371
372 bool play();
373 };
374 /**@}*/
375
376};
VoiceConnectionState
For the various connection states of the VoiceConnection 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.
VoiceSocketOpCodes
The various opcodes that could be sent/received by the voice-websocket.
@ Speaking
Indicate which users are speaking.
@ Ready_Server
Complete the websocket handshake.
@ Heartbeat
Keep the websocket connection alive.
@ Hello
Time to wait between sending heartbeats in milliseconds.
@ Identify
Begin a voice websocket connection.
@ Session_Description
Describe the session.
@ Heartbeat_ACK
Sent to acknowledge a received client heartbeat.
@ Resumed
Acknowledge a successful session resume.
@ Client_Disconnect
A client has disconnected from the voice channel.
@ Select_Protocol
Select the voice protocol.
@ Resume
Resume a connection.
VoiceActiveState
For the various active states of the VoiceConnection class.
A CoRoutine - representing a potentially asynchronous operation/function.
Definition: CoRoutine.hpp:88
DiscordCoreClient - The main class for this library.
A discord Guild. Used to connect to/disconnect from voice.
Data structure representing a single Guild, for the purposes of populating the cache.
A class representing the Song APIs.
Definition: SongAPI.hpp:46
Voice Websocket close codes.
VoiceWebSocketCloseCode
Voice Websocket close codes.
@ Voice_Server_Crashed
The server crashed. Our bad! Try resuming.
@ Session_No_Longer_Valid
Your session is no longer valid.
@ 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.
@ Unknown_Encryption_Mode
We didn't recognize your encryption.
@ Not_Authenticated
You sent a payload before identifying with the Gateway.
@ Disconnected
ChannelData was deleted, you were kicked, voice server changed, or the main gateway session was dropp...
VoiceConnection class - represents the connection to a given voice ChannelData.
Wrapper class for the Opus audio encoder.
A class representing a Snowflake identifier with various operations.
Definition: Base.hpp:771
A thread-safe messaging block for data-structures.
A smart pointer class that provides unique ownership semantics.
Definition: UniquePtr.hpp:49
Represents a single frame of audio data.
Definition: Utilities.hpp:367
For connecting to a voice-channel. "streamInfo" is used when a socket is created to connect this bot ...
Definition: Utilities.hpp:388