38 template<>
struct core<discord_core_api::voice_session_description_data> {
39 using value_type = discord_core_api::voice_session_description_data;
40 static constexpr auto parseValue = createValue(
"secret_key", &value_type::secretKey);
43 template<>
struct core<discord_core_api::discord_core_internal::websocket_message_data<discord_core_api::speaking_data>> {
44 using value_type = discord_core_api::discord_core_internal::websocket_message_data<discord_core_api::speaking_data>;
45 static constexpr auto parseValue = createValue(
"d", &value_type::d);
48 template<>
struct core<discord_core_api::speaking_data> {
49 using value_type = discord_core_api::speaking_data;
50 static constexpr auto parseValue = createValue(
"ssrc", &value_type::ssrc,
"user_id", &value_type::userId);
53 template<>
struct core<discord_core_api::voice_connection_hello_data> {
54 using value_type = discord_core_api::voice_connection_hello_data;
55 static constexpr auto parseValue = createValue(
"heartbeat_interval", &value_type::heartBeatInterval);
58 template<>
struct core<discord_core_api::voice_user_disconnect_data> {
59 using value_type = discord_core_api::voice_user_disconnect_data;
60 static constexpr auto parseValue = createValue(
"user_id", &value_type::userId);
63 template<>
struct core<discord_core_api::voice_socket_ready_data> {
64 using value_type = discord_core_api::voice_socket_ready_data;
65 static constexpr auto parseValue = createValue(
"modes", &value_type::modes,
"ip", &value_type::ip,
"port", &value_type::port,
"ssrc", &value_type::ssrc);
72 static DCA_INLINE
thread_local discord_core_internal::audio_mixer audioMixer{};
74 voice_user::voice_user(
snowflake userIdNew) {
78 voice_user& voice_user::operator=(voice_user&& other)
noexcept {
79 payloads = std::move(other.payloads);
80 decoder = std::move(other.decoder);
81 userId = other.userId;
89 void voice_user::insertPayload(jsonifier::string_view_base<uint8_t> data) {
90 payloads.writeData(data.data(), data.size());
93 jsonifier::string_view_base<uint8_t> voice_user::extractPayload() {
94 return payloads.readData();
101 rtp_packet_encrypter::rtp_packet_encrypter(uint32_t ssrcNew,
const jsonifier::string_base<uint8_t>& keysNew) {
107 if (keys.size() > 0) {
109 timeStamp +=
static_cast<uint32_t
>(audioData.sampleCount);
110 static constexpr uint8_t headerSize{ 12 };
111 static constexpr uint8_t version{ 0x80 };
112 static constexpr uint8_t flags{ 0x78 };
113 uint8_t header[headerSize]{};
119 uint8_t nonceForLibSodium[crypto_secretbox_NONCEBYTES]{};
120 for (int8_t x = 0; x < headerSize; ++x) {
121 nonceForLibSodium[x] = header[x];
123 const uint64_t numOfBytes{ headerSize + audioData.data.size() + crypto_secretbox_MACBYTES };
124 if (data.size() < numOfBytes) {
125 data.resize(numOfBytes);
127 for (uint8_t x = 0; x < headerSize; ++x) {
128 data.at(x) =
static_cast<uint8_t
>(header[x]);
130 if (crypto_secretbox_easy(data.data() + headerSize, audioData.data.data(), audioData.data.size(), nonceForLibSodium, keys.data()) != 0) {
133 return jsonifier::string_view_base<uint8_t>{ data.data(), numOfBytes };
138 moving_averager::moving_averager(uint64_t collectionCountNew) {
139 collectionCount = collectionCountNew;
142 moving_averager moving_averager::operator+=(int64_t value) {
143 values.emplace_front(value);
144 if (values.size() >= collectionCount) {
150 moving_averager::operator float() {
152 if (values.size() > 0) {
153 for (
auto& value: values) {
154 returnData +=
static_cast<float>(value);
156 return returnData /
static_cast<float>(values.size());
162 voice_connection_bridge::voice_connection_bridge(unordered_map<uint64_t,
unique_ptr<voice_user>>* voiceUsersPtrNew, jsonifier::string_base<uint8_t>& encryptionKeyNew,
163 stream_type streamType,
const jsonifier::string& baseUrlNew,
const uint16_t portNew,
snowflake guildIdNew,
164 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type>* tokenNew)
165 : udp_connection{ baseUrlNew, portNew, streamType, tokenNew } {
166 voiceUsersPtr = voiceUsersPtrNew;
167 encryptionKey = encryptionKeyNew;
168 guildId = guildIdNew;
172 DCA_INLINE
void voice_connection_bridge::applyGainRamp(int64_t sampleCount) {
173 increment = (endGain - currentGain) /
static_cast<float>(sampleCount);
174 for (int64_t x = 0; x < sampleCount / audioMixer.byteBlocksPerRegister; ++x) {
175 audioMixer.collectSingleRegister(upSampledVector.data() + (x * audioMixer.byteBlocksPerRegister), downSampledVector.data() + (x * audioMixer.byteBlocksPerRegister),
176 currentGain, increment);
177 currentGain += increment *
static_cast<float>(audioMixer.byteBlocksPerRegister);
181 bool compareUint8Strings(jsonifier::string_view_base<uint8_t> stringToCheck,
const char* wordToCheck) {
182 jsonifier::string newString{};
183 auto stringLength = std::char_traits<char>::length(wordToCheck);
184 if (stringLength == stringToCheck.size()) {
185 newString.resize(stringToCheck.size());
186 std::memcpy(newString.data(), stringToCheck.data(), stringLength);
187 return wordToCheck == newString;
192 void voice_connection_bridge::parseOutgoingVoiceData() {
193 jsonifier::string_view_base<uint8_t> buffer = getInputBuffer();
194 if (compareUint8Strings(buffer,
"goodbye") || compareUint8Strings(buffer,
"connected1") || compareUint8Strings(buffer,
"connecting")) {
197 if (buffer.size() > 0) {
198 audio_frame_data frame{};
200 frame.type = audio_frame_type::raw_pcm;
201 discord_core_client::getInstance()->getSongAPI(guildId).audioDataBuffer.send(std::move(frame));
205 void voice_connection_bridge::disconnect() {
206 if (streamType != stream_type::none) {
207 outputBuffer.clear();
208 static constexpr char newStringArray[]{
"goodbye" };
209 jsonifier::string_base<uint8_t> newString{};
210 static constexpr auto stringSize = std::size(newStringArray);
211 newString.resize(stringSize - 1);
212 std::memcpy(newString.data(), newStringArray, stringSize - 1);
213 writeData(newString);
216 }
catch (
const dca_exception& error) {
217 message_printer::printError<print_message_type::websocket>(error.what());
220 udp_connection::disconnect();
223 void voice_connection_bridge::handleAudioBuffer() {
224 parseOutgoingVoiceData();
227 void voice_connection_bridge::mixAudio() {
228 opus_int32 voiceUserCountReal{};
229 int64_t decodedSize{};
230 std::fill(upSampledVector.data(), upSampledVector.data() + upSampledVector.size(), 0);
231 for (
auto& [key, value]: *voiceUsersPtr) {
232 jsonifier::string_view_base<uint8_t> payload{ value->extractPayload() };
233 if (payload.size() <= 44) {
236 static constexpr uint64_t headerSize{ 12 };
237 const uint64_t csrcCount{
static_cast<uint64_t
>(payload.at(0)) & 0b0000'1111 };
238 const uint64_t offsetToData{ headerSize +
sizeof(uint32_t) * csrcCount };
239 const uint64_t encryptedDataLength{ payload.size() - offsetToData };
241 if (decryptedDataString.size() < encryptedDataLength) {
242 decryptedDataString.resize(encryptedDataLength);
246 for (uint64_t x = 0; x < headerSize; ++x) {
247 nonce[x] = payload[x];
250 if (crypto_secretbox_open_easy(decryptedDataString.data(), payload.data() + offsetToData, encryptedDataLength, nonce, encryptionKey.data())) {
254 jsonifier::string_view_base newString{ decryptedDataString.data(), encryptedDataLength - crypto_secretbox_MACBYTES };
256 if (
static_cast<int8_t
>(payload[0] >> 4) & 0b0001) {
257 uint16_t extenstionLengthInWords{};
258 std::memcpy(&extenstionLengthInWords, newString.data() + 2,
sizeof(int16_t));
259 extenstionLengthInWords = ntohs(extenstionLengthInWords);
260 const uint64_t extensionLength{
sizeof(uint32_t) * extenstionLengthInWords };
261 const uint64_t extensionHeaderLength{
sizeof(uint16_t) * 2 };
262 newString = newString.substr(extensionHeaderLength + extensionLength);
265 if (newString.size() > 44) {
266 jsonifier::string_view_base<opus_int16> decodedData{};
268 decodedData = value->getDecoder().decodeData(newString);
269 }
catch (
const dca_exception& error) {
270 message_printer::printError<print_message_type::websocket>(error.what());
272 if (decodedData.size() > 0) {
273 decodedSize =
static_cast<int64_t
>(std::max(
static_cast<uint64_t
>(decodedSize), decodedData.size()));
274 ++voiceUserCountReal;
275 auto newPtr = decodedData.data();
276 auto newerPtr = upSampledVector.data();
277 for (uint64_t x = 0; x < decodedData.size() / audioMixer.byteBlocksPerRegister;
278 ++x, newPtr += audioMixer.byteBlocksPerRegister, newerPtr += audioMixer.byteBlocksPerRegister) {
279 audioMixer.combineSamples(newPtr, newerPtr);
285 if (decodedSize > 0) {
286 voiceUserCountAverage += voiceUserCountReal;
287 endGain = 1.0f / voiceUserCountAverage;
288 applyGainRamp(decodedSize);
289 if (resampleVector.size() <
static_cast<uint64_t
>(decodedSize) * 2) {
290 resampleVector.resize(
static_cast<uint64_t
>(decodedSize) * 2);
292 std::memcpy(resampleVector.data(), downSampledVector.data(),
static_cast<uint64_t
>(decodedSize) * 2);
293 writeData(jsonifier::string_view_base<uint8_t>{ resampleVector.data(),
static_cast<uint64_t
>(decodedSize * 2) });
294 currentGain = endGain;
298 voice_udpconnection::voice_udpconnection(
const jsonifier::string& baseUrlNew, uint16_t portNew, stream_type streamType, voice_connection* ptrNew,
299 std::coroutine_handle<discord_core_api::co_routine<void, false>::promise_type>* tokenNew)
300 : udp_connection{ baseUrlNew, portNew, streamType, tokenNew } {
301 voiceConnection = ptrNew;
304 void voice_udpconnection::disconnect() {
305 if (streamType != stream_type::none) {
306 outputBuffer.clear();
307 static constexpr char newStringArray[] = {
"goodbye" };
308 jsonifier::string_base<uint8_t> newString{};
309 auto stringSize = std::size(newStringArray);
310 newString.resize(stringSize - 1);
311 std::memcpy(newString.data(), newStringArray, stringSize - 1);
312 writeData(newString);
315 }
catch (
const dca_exception& error) {
316 message_printer::printError<print_message_type::websocket>(error.what());
319 udp_connection::disconnect();
323 : websocket_core(&discord_core_client::getInstance()->configManager, discord_core_internal::websocket_type::voice) {
324 dataOpCode = discord_core_internal::websocket_op_code::Op_Text;
326 samplesPerPacket = sampleRatePerSecond / 1000 * msPerPacket;
327 configManager = &discord_core_client::getInstance()->configManager;
328 baseShard = baseShardNew;
329 doWeQuit = doWeQuitNew;
333 return voiceConnectInitData.channelId;
336 void voice_connection::parseIncomingVoiceData(jsonifier::string_view_base<uint8_t> rawDataBufferNew) {
337 if ((72 <= (
static_cast<int8_t
>(rawDataBufferNew.at(1)) & 0b0111'1111) && ((
static_cast<int8_t
>(rawDataBufferNew.at(1)) & 0b0111'1111) <= 76)) ||
338 rawDataBufferNew.size() <= 44) {
341 uint32_t speakerSsrc{};
342 std::memcpy(&speakerSsrc, rawDataBufferNew.data() + 8,
sizeof(uint32_t));
343 speakerSsrc = ntohl(speakerSsrc);
344 if (!voiceUsers.contains(speakerSsrc)) {
347 voiceUsers[speakerSsrc]->insertPayload(rawDataBufferNew);
351 voiceConnectInitData = initData;
355 taskThread = runVoice();
362 return discord_core_client::getInstance()->getSongAPI(voiceConnectInitData.guildId).audioDataBuffer;
365 void voice_connection::checkForAndSendHeartBeat(
const bool isImmedate) {
366 if (heartBeatStopWatch.hasTimeElapsed() || isImmedate) {
367 discord_core_internal::websocket_message_data<uint32_t> message{};
368 message.jsonifierExcludedKeys.emplace(
"T");
369 message.jsonifierExcludedKeys.emplace(
"s");
370 jsonifier::string_base<uint8_t>
string{};
371 message.d =
static_cast<uint32_t
>(std::chrono::duration_cast<nanoseconds>(sys_clock::now().time_since_epoch()).count());
373 parser.serializeJson(message,
string);
374 createHeader(
string, dataOpCode);
375 if (!sendMessage(
string,
true)) {
382 haveWeReceivedHeartbeatAck =
false;
383 heartBeatStopWatch.reset();
387 void voice_connection::sendSpeakingMessage(
const bool isSpeaking) {
388 discord_core_internal::websocket_message_data<discord_core_internal::send_speaking_data> data{};
389 data.jsonifierExcludedKeys.emplace(
"T");
390 data.jsonifierExcludedKeys.emplace(
"s");
392 data.d.type = discord_core_internal::send_speaking_type::None;
394 if (udpConnection.processIO() != discord_core_internal::connection_status::NO_Error) {
399 data.d.type = discord_core_internal::send_speaking_type::Microphone;
402 data.d.ssrc = audioSSRC;
404 jsonifier::string_base<uint8_t>
string{};
405 parser.serializeJson(data,
string);
406 createHeader(
string, dataOpCode);
407 sendMessage(
string,
true);
410 bool voice_connection::onMessageReceived(jsonifier::string_view_base<uint8_t> data) {
411 discord_core_internal::websocket_message message{};
413 parser.parseJson(message, data);
416 discord_core_internal::websocket_message_data<voice_socket_ready_data> dataNew{};
417 parser.parseJson(dataNew, data);
418 audioSSRC = dataNew.d.ssrc;
419 voiceIp = dataNew.d.ip;
420 port = dataNew.d.port;
421 for (
auto& value: dataNew.d.modes) {
422 if (value ==
"xsalsa20_poly1305") {
423 audioEncryptionMode = value;
430 discord_core_internal::websocket_message_data<voice_session_description_data> dataNew{};
431 encryptionKey.clear();
432 parser.parseJson(dataNew, data);
433 for (
auto& value: dataNew.d.secretKey) {
434 encryptionKey.emplace_back(
static_cast<uint8_t
>(value));
436 packetEncrypter = rtp_packet_encrypter{ audioSSRC, encryptionKey };
441 discord_core_internal::websocket_message_data<speaking_data> dataNew{};
442 parser.parseJson(dataNew, data);
443 const uint32_t ssrc = dataNew.d.ssrc;
444 auto userId = dataNew.d.userId;
446 if (voiceConnectInitData.streamInfo.type != stream_type::none &&
448 if (!voiceUsers.contains(ssrc)) {
449 voiceUsers.emplace(ssrc, std::move(user));
455 haveWeReceivedHeartbeatAck =
true;
459 discord_core_internal::websocket_message_data<voice_connection_hello_data> dataNew{};
460 parser.parseJson(dataNew, data);
461 heartBeatStopWatch = stop_watch<milliseconds>{ dataNew.d.heartBeatInterval };
462 heartBeatStopWatch.reset();
463 areWeHeartBeating =
true;
465 currentState.store(discord_core_internal::websocket_state::authenticated, std::memory_order_release);
466 haveWeReceivedHeartbeatAck =
true;
474 discord_core_internal::websocket_message_data<voice_user_disconnect_data> dataNew{};
475 parser.parseJson(dataNew, data);
476 const auto userId = dataNew.d.userId;
477 for (
auto& [key, value]: voiceUsers) {
478 if (userId == value->getUserId()) {
479 voiceUsers.erase(key);
501 void voice_connection::connectInternal() {
502 stop_watch<milliseconds> stopWatch{ 10000ms };
504 if (currentReconnectTries >= maxReconnectTries) {
505 doWeQuit->store(
true, std::memory_order_release);
509 streamSocket->inputBuffer.clear();
510 streamSocket->outputBuffer.clear();
512 areWeHeartBeating =
false;
513 switch (connectionState.load(std::memory_order_acquire)) {
515 baseShard->voiceConnectionDataBufferMap[voiceConnectInitData.guildId.operator
const uint64_t&()] = &voiceConnectionDataBuffer;
516 baseShard->voiceConnectionDataBufferMap[voiceConnectInitData.guildId.operator
const uint64_t&()]->clearContents();
517 baseShard->getVoiceConnectionData(voiceConnectInitData);
519 if (waitForTimeToPass(voiceConnectionDataBuffer, voiceConnectionData, 10000)) {
523 baseUrl = voiceConnectionData.endPoint.substr(0, voiceConnectionData.endPoint.find(
":"));
529 currentState.store(discord_core_internal::websocket_state::upgrading, std::memory_order_release);
530 if (!websocket_core::connect(baseUrl,
"/?v=4", 443)) {
536 while (currentState.load(std::memory_order_acquire) != discord_core_internal::websocket_state::Collecting_Hello && !token.promise().stopRequested()) {
537 if (websocket_core::tcpConnection.processIO(10) != discord_core_internal::connection_status::NO_Error) {
549 if (stopWatch.hasTimeElapsed()) {
553 if (websocket_core::tcpConnection.processIO(10) != discord_core_internal::connection_status::NO_Error) {
557 std::this_thread::sleep_for(1ms);
559 currentReconnectTries = 0;
564 haveWeReceivedHeartbeatAck =
true;
565 discord_core_internal::websocket_message_data<discord_core_internal::voice_identify_data> data{};
566 data.jsonifierExcludedKeys.emplace(
"T");
567 data.jsonifierExcludedKeys.emplace(
"s");
568 data.d.serverId = voiceConnectInitData.guildId.operator jsonifier::string();
569 data.d.sessionId = voiceConnectionData.sessionId;
570 data.d.token = voiceConnectionData.token;
571 data.d.userId = voiceConnectInitData.userId;
574 jsonifier::string_base<uint8_t>
string{};
575 parser.serializeJson(data,
string);
576 createHeader(
string, dataOpCode);
577 if (!websocket_core::sendMessage(
string,
true)) {
588 if (stopWatch.hasTimeElapsed()) {
592 if (websocket_core::tcpConnection.processIO(100) != discord_core_internal::connection_status::NO_Error) {
596 std::this_thread::sleep_for(1ms);
602 if (!voiceConnect()) {
611 discord_core_internal::websocket_message_data<discord_core_internal::voice_socket_protocol_payload_data> data{};
612 data.jsonifierExcludedKeys.emplace(
"T");
613 data.jsonifierExcludedKeys.emplace(
"s");
614 data.d.data.mode = audioEncryptionMode;
615 data.d.data.address = externalIp;
616 data.d.data.port = port;
618 jsonifier::string_base<uint8_t>
string{};
619 parser.serializeJson(data,
string);
620 createHeader(
string, dataOpCode);
621 if (!websocket_core::sendMessage(
string,
true)) {
633 if (stopWatch.hasTimeElapsed()) {
637 if (websocket_core::tcpConnection.processIO(10) != discord_core_internal::connection_status::NO_Error) {
641 std::this_thread::sleep_for(1ms);
643 baseShard->voiceConnectionDataBufferMap[voiceConnectInitData.guildId.operator
const uint64_t&()]->clearContents();
645 activeState.store(prevActiveState.load(std::memory_order_acquire), std::memory_order_release);
646 if (voiceConnectInitData.streamInfo.type != stream_type::none) {
648 voiceConnectInitData.streamInfo.port, voiceConnectInitData.guildId, &token);
649 if (streamSocket->currentStatus != discord_core_internal::connection_status::NO_Error) {
662 stop_watch<milliseconds> stopWatch{ 20000ms };
664 stop_watch<milliseconds> sendSilenceStopWatch{ 5000ms };
665 sendSilenceStopWatch.reset();
666 while (!token.promise().stopRequested() && !doWeQuit->load(std::memory_order_acquire) && activeState.load(std::memory_order_acquire) != voice_active_state::exiting) {
668 switch (activeState.load(std::memory_order_acquire)) {
671 sendSpeakingMessage(
false);
675 sendSpeakingMessage(
false);
676 xferAudioData.clearData();
678 if (udpConnection.processIO() != discord_core_internal::connection_status::NO_Error) {
681 std::this_thread::sleep_for(1ms);
682 if (!token.promise().stopRequested() && voice_connection::areWeConnected()) {
683 if (websocket_core::tcpConnection.processIO(10) != discord_core_internal::connection_status::NO_Error) {
685 }
else if (!websocket_core::areWeConnected()) {
691 if (!token.promise().stopRequested() && voice_connection::areWeConnected()) {
692 checkForAndSendHeartBeat(
false);
698 sendSpeakingMessage(
false);
700 if (udpConnection.processIO() != discord_core_internal::connection_status::NO_Error) {
703 std::this_thread::sleep_for(1ms);
704 if (!token.promise().stopRequested() && voice_connection::areWeConnected()) {
705 if (websocket_core::tcpConnection.processIO(10) != discord_core_internal::connection_status::NO_Error) {
707 }
else if (!websocket_core::areWeConnected()) {
713 if (!token.promise().stopRequested() && voice_connection::areWeConnected()) {
714 checkForAndSendHeartBeat(
false);
720 sendSpeakingMessage(
false);
721 sendSpeakingMessage(
true);
723 xferAudioData.clearData();
725 auto targetTime{ sys_clock::now() + intervalCount };
728 int64_t bytesPerSample{ 4 };
729 if (!token.promise().stopRequested() && voice_connection::areWeConnected()) {
730 checkForAndSendHeartBeat(
false);
732 discord_core_client::getInstance()->getSongAPI(voiceConnectInitData.guildId).audioDataBuffer.tryReceive(xferAudioData);
733 if ((doWeSkip.load(std::memory_order_acquire) && xferAudioData.currentSize == 0)) {
738 uint64_t frameSize{};
739 if (xferAudioData.currentSize <= 0) {
740 xferAudioData.clearData();
742 intervalCount = nanoseconds{
static_cast<uint64_t
>(
static_cast<double>(xferAudioData.currentSize / bytesPerSample) /
743 static_cast<double>(sampleRatePerSecond) *
static_cast<double>(nsPerSecond)) };
744 uint64_t framesPerSecond = 1000 /
static_cast<uint64_t
>(msPerPacket);
745 frameSize = std::min(bytesPerSample * sampleRatePerSecond / framesPerSecond, xferAudioData.data.size());
747 intervalCount = nanoseconds{ 20000000 };
749 jsonifier::string_view_base<uint8_t> frame{};
752 auto encodedFrameData = encoder.encodeData(jsonifier::string_view_base<uint8_t>(xferAudioData.data.data(), frameSize));
753 xferAudioData.clearData();
754 if (encodedFrameData.data.size() != 0) {
755 frame = packetEncrypter.encryptPacket(encodedFrameData);
761 discord_core_internal::encoder_return_data returnData{};
762 returnData.data = { xferAudioData.data.data(),
static_cast<uint64_t
>(xferAudioData.currentSize) };
763 returnData.sampleCount = 960;
764 if (returnData.data.size() != 0) {
765 frame = packetEncrypter.encryptPacket(returnData);
766 xferAudioData.clearData();
768 }
catch (
const dca_exception& error) {
775 xferAudioData.clearData();
779 auto waitTime = targetTime - sys_clock::now();
780 auto waitTimeCount = waitTime.count();
781 int64_t minimumFreeTimeForCheckingProcessIO{
static_cast<int64_t
>(
static_cast<double>(intervalCount.count()) * 0.60l) };
782 if (voice_connection::areWeConnected()) {
783 if (waitTimeCount >= minimumFreeTimeForCheckingProcessIO && !token.promise().stopRequested()) {
784 if (websocket_core::tcpConnection.processIO(0) != discord_core_internal::connection_status::NO_Error) {
793 waitTime = targetTime - sys_clock::now();
794 waitTimeCount =
static_cast<int64_t
>(
static_cast<double>(waitTime.count()) * 0.95l);
795 if (waitTimeCount > 0) {
796 nanoSleep(waitTimeCount);
798 waitTime = targetTime - sys_clock::now();
799 waitTimeCount = waitTime.count();
800 if (waitTimeCount > 0 && waitTimeCount < intervalCount.count()) {
801 spinLock(
static_cast<uint64_t
>(waitTimeCount));
803 if (udpConnection.areWeStillConnected()) {
804 udpConnection.writeData(frame);
805 if (udpConnection.processIO() != discord_core_internal::connection_status::NO_Error) {
809 xferAudioData.clearData();
813 targetTime = sys_clock::now() + intervalCount;
816 streamSocket->mixAudio();
817 if (streamSocket->areWeStillConnected()) {
818 if (streamSocket->processIO() != discord_core_internal::connection_status::NO_Error) {
819 std::this_thread::sleep_for(5s);
823 std::this_thread::sleep_for(5s);
830 case voice_active_state::exiting: {
834 if (token.promise().stopRequested() || activeState == voice_active_state::exiting) {
837 std::this_thread::sleep_for(1ms);
838 }
catch (
const dca_exception& error) {
844 void voice_connection::skipInternal(uint32_t currentRecursionDepth) {
845 if (currentRecursionDepth >= 10) {
849 ++currentRecursionDepth;
850 song_completion_event_data completionEventData{};
851 completionEventData.guildId = voiceConnectInitData.guildId;
852 completionEventData.wasItAFail = wasItAFail.load(std::memory_order_acquire);
853 completionEventData.guildMemberId = currentUserId;
855 xferAudioData.clearData();
856 if (discord_core_client::getInstance()->getSongAPI(voiceConnectInitData.guildId).onSongCompletionEvent.functions.size() > 0) {
857 discord_core_client::getInstance()->getSongAPI(voiceConnectInitData.guildId).onSongCompletionEvent(completionEventData);
861 }
catch (
const dca_exception& error) {
863 std::this_thread::sleep_for(150ms);
864 skipInternal(currentRecursionDepth);
868 bool voice_connection::areWeCurrentlyPlaying() {
872 void voice_udpconnection::handleAudioBuffer() {
875 jsonifier::string_view_base<uint8_t>
string = getInputBuffer();
876 if (voiceConnection->streamSocket && voiceConnection->encryptionKey.size() > 0) {
877 voiceConnection->parseIncomingVoiceData(
string);
882 bool voice_connection::areWeConnected() {
883 return websocket_core::areWeConnected() && udpConnection.areWeStillConnected();
886 bool voice_connection::voiceConnect() {
887 udpConnection = voice_udpconnection{ voiceIp, port, stream_type::none,
this, &token };
888 if (!udpConnection.areWeStillConnected()) {
891 uint8_t packet[74]{};
892 static constexpr uint16_t val1601{ 0x01 };
893 static constexpr uint16_t val1602{ 70 };
894 packet[0] =
static_cast<uint8_t
>(val1601 >> 8);
895 packet[1] =
static_cast<uint8_t
>(val1601 >> 0);
896 packet[2] =
static_cast<uint8_t
>(val1602 >> 8);
897 packet[3] =
static_cast<uint8_t
>(val1602 >> 0);
898 packet[4] =
static_cast<uint8_t
>(audioSSRC >> 24);
899 packet[5] =
static_cast<uint8_t
>(audioSSRC >> 16);
900 packet[6] =
static_cast<uint8_t
>(audioSSRC >> 8);
901 packet[7] =
static_cast<uint8_t
>(audioSSRC);
902 udpConnection.getInputBuffer();
903 udpConnection.writeData(jsonifier::string_view_base<uint8_t>{ packet, std::size(packet) });
904 jsonifier::string_view_base<uint8_t> inputStringFirst{};
905 jsonifier::string_base<uint8_t> inputString{};
907 stop_watch<milliseconds> stopWatch{ 5500ms };
909 while (inputStringFirst.size() < 74 && !doWeQuit->load(std::memory_order_acquire) && activeState.load(std::memory_order_acquire) != voice_active_state::exiting) {
910 if (udpConnection.processIO() != discord_core_internal::connection_status::NO_Error) {
914 inputStringFirst = udpConnection.getInputBuffer();
915 std::this_thread::sleep_for(1ms);
916 if (stopWatch.hasTimeElapsed()) {
920 inputString.insert(inputString.begin(), inputStringFirst.begin(), inputStringFirst.end());
921 inputString = inputString.substr(8);
922 const auto endLineFind = inputString.find(
static_cast<uint8_t
>(
'\u0000'),
static_cast<uint64_t
>(6));
923 if (endLineFind != jsonifier::string::npos) {
924 inputString = inputString.substr(0, endLineFind);
926 jsonifier::string_view_base returnString{ inputStringFirst.data() + 8, inputString.size() };
927 if (externalIp.size() < returnString.size()) {
928 externalIp.resize(returnString.size());
930 std::memcpy(externalIp.data(), returnString.data(), returnString.size());
931 voiceConnectionDataBuffer.clearContents();
935 void voice_connection::sendSilence() {
936 jsonifier::vector<jsonifier::string_base<uint8_t>> frames{};
937 static constexpr uint8_t arrayNew[3]{ 0xf8, 0xff, 0xfe };
938 for (uint64_t x = 0; x < 5; ++x) {
939 discord_core_internal::encoder_return_data frame{};
940 frame.data = jsonifier::string_view_base<uint8_t>{ arrayNew, 3 };
941 frame.sampleCount = 3;
942 auto packetNew = packetEncrypter.encryptPacket(frame);
943 frames.emplace_back(jsonifier::string_base<uint8_t>{ packetNew.data(), packetNew.size() });
945 for (
auto& value: frames) {
946 udpConnection.writeData(value);
947 if (udpConnection.processIO() != discord_core_internal::connection_status::NO_Error) {
954 bool voice_connection::pauseToggle() {
966 void voice_connection::disconnect() {
967 activeState.store(voice_active_state::exiting, std::memory_order_release);
972 streamSocket->disconnect();
973 streamSocket.reset();
975 udpConnection.disconnect();
976 websocket_core::disconnect();
977 areWeHeartBeating =
false;
978 currentReconnectTries = 0;
983 currentState.store(discord_core_internal::websocket_state::disconnected, std::memory_order_release);
986 void voice_connection::onClosed() {
988 if (activeState.load(std::memory_order_acquire) != voice_active_state::exiting && currentReconnectTries < maxReconnectTries) {
990 prevActiveState.store(activeState.load(std::memory_order_acquire), std::memory_order_release);
992 websocket_core::disconnect();
993 ++currentReconnectTries;
996 streamSocket->disconnect();
998 udpConnection.disconnect();
999 }
else if (currentReconnectTries >= maxReconnectTries) {
1000 activeState.store(voice_active_state::exiting, std::memory_order_release);
1004 bool voice_connection::stop() {
1006 doWeSkip.store(
false, std::memory_order_release);
1010 bool voice_connection::play() {
1015 bool voice_connection::skip(
bool wasItAFailNew) {
1019 wasItAFail.store(wasItAFailNew, std::memory_order_release);
1020 doWeSkip.store(
true, std::memory_order_release);
A co_routine - representing a potentially asynchronous operation/function.
A websocket client, for communication via a tcp-connection.
static DCA_INLINE void printSuccess(const string_type &what, std::source_location where=std::source_location::current())
Print a success message of the specified type.
static DCA_INLINE void printError(const string_type &what, std::source_location where=std::source_location::current())
Print an error message of the specified type.
A class representing a snowflake identifier with various operations.
A thread-safe messaging block for data-structures.
A smart pointer class that provides unique ownership semantics.
static user_cache_data getCachedUser(get_user_data dataPackage)
Collects a given user from the library's cache.
snowflake getChannelId()
Collects the currently connected-to voice channel_data's id.
void connect(const voice_connect_init_data &initData)
Connects to a currently held voice channel.
voice_connection(discord_core_internal::websocket_client *baseShardNew, std::atomic_bool *doWeQuitNew)
DCA_INLINE void storeBits(value_type *to, return_type num)
Stores the bits of a number into a character array.
DCA_INLINE auto newThreadAwaitable()
An awaitable that can be used to launch the co_routine onto a new thread - as well as return the hand...
DCA_INLINE unique_ptr< value_type, deleter > makeUnique(arg_types &&... args)
Helper function to create a unique_ptr for a non-array object.
audio_frame_type
Audio frame types.
stream_type
For selecting the type of streamer that the given bot is, one must be one server and one of client pe...
@ encoded
Encoded audio data.
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.
@ resume
Resume a connection.
@ 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.
@ 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.
Structure to hold the encoded data and sample count returned by the encoder.
A wrapper class for the opus audio decoder.
For connecting to a voice-channel. "streamInfo" is used when a SOCKET is created to connect this bot ...