From a79b2d418e1a67b3af034d81fa3df09afc92329e Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:44:33 -0400 Subject: synchronize ws close/open to creating thread --- src/discord/websocket.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src/discord/websocket.cpp') diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index 46a4b90..04970d3 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -1,7 +1,16 @@ #include "websocket.hpp" #include -Websocket::Websocket() = default; +Websocket::Websocket() + : m_close_code(ix::WebSocketCloseConstants::kNormalClosureCode) { + m_open_dispatcher.connect([this]() { + m_signal_open.emit(); + }); + + m_close_dispatcher.connect([this]() { + m_signal_close.emit(m_close_code); + }); +} void Websocket::StartConnection(const std::string &url) { m_websocket.disableAutomaticReconnection(); @@ -44,9 +53,11 @@ void Websocket::Send(const nlohmann::json &j) { void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) { switch (msg->type) { case ix::WebSocketMessageType::Open: { - m_signal_open.emit(); + m_open_dispatcher.emit(); } break; case ix::WebSocketMessageType::Close: { + m_close_code = msg->closeInfo.code; + m_close_dispatcher.emit(); m_signal_close.emit(msg->closeInfo.code); } break; case ix::WebSocketMessageType::Message: { -- cgit v1.2.3 From 05acb8c857c923391a651f38018204b5dc35e39d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 3 Oct 2022 00:16:56 -0400 Subject: try and handle voice socket closure properly maybe --- src/discord/voiceclient.cpp | 17 +++++++++-------- src/discord/websocket.cpp | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'src/discord/websocket.cpp') diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 0b87032..c0679ce 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -132,6 +132,7 @@ DiscordVoiceClient::DiscordVoiceClient() { m_ws.signal_close().connect([this](uint16_t code) { printf("vws close %u\n", code); + Stop(); }); m_ws.signal_message().connect([this](const std::string &str) { @@ -175,16 +176,18 @@ void DiscordVoiceClient::Start() { m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); m_heartbeat_waiter.revive(); m_keepalive_waiter.revive(); + m_connected = true; + m_signal_connected.emit(); } void DiscordVoiceClient::Stop() { + m_ws.Stop(); + m_udp.Stop(); + m_heartbeat_waiter.kill(); + if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); + m_keepalive_waiter.kill(); + if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); if (m_connected) { - m_ws.Stop(); - m_udp.Stop(); - m_heartbeat_waiter.kill(); - if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); - m_keepalive_waiter.kill(); - if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); m_connected = false; m_signal_disconnected.emit(); } @@ -288,8 +291,6 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); m_udp.Run(); - m_connected = true; - m_signal_connected.emit(); } void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index 04970d3..cf0111c 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -8,6 +8,7 @@ Websocket::Websocket() }); m_close_dispatcher.connect([this]() { + Stop(); m_signal_close.emit(m_close_code); }); } @@ -58,7 +59,6 @@ void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) { case ix::WebSocketMessageType::Close: { m_close_code = msg->closeInfo.code; m_close_dispatcher.emit(); - m_signal_close.emit(msg->closeInfo.code); } break; case ix::WebSocketMessageType::Message: { m_signal_message.emit(msg->str); -- cgit v1.2.3 From 929ebf1a6008d861473e3ceffd11cd4eca90d620 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 15 Nov 2022 02:14:49 -0500 Subject: mess with some websocket stuff to try and fix things to be honest, im not sure what ive done here. whatever memory i have of the issue i was trying to fix has long disappeared by the time im committing this. theres still some issues with being force disconnected and i really dont understand it ill figure it out eventually... maybe :/ --- src/discord/discord.cpp | 13 ++++-- src/discord/discord.hpp | 2 +- src/discord/voiceclient.cpp | 103 +++++++++++++++++++++++++++++++++++--------- src/discord/voiceclient.hpp | 24 ++++++++++- src/discord/websocket.cpp | 20 ++++++--- src/discord/websocket.hpp | 9 ++-- 6 files changed, 137 insertions(+), 34 deletions(-) (limited to 'src/discord/websocket.cpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2a08574..cd16aa8 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -9,7 +9,8 @@ using namespace std::string_literals; DiscordClient::DiscordClient(bool mem_store) : m_decompress_buf(InflateChunkSize) - , m_store(mem_store) { + , m_store(mem_store) + , m_websocket("gateway-ws") { m_msg_dispatch.connect(sigc::mem_fun(*this, &DiscordClient::MessageDispatch)); auto dispatch_cb = [this]() { m_generic_mutex.lock(); @@ -2171,6 +2172,11 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { if (data.UserID == m_user_data.ID) { spdlog::get("discord")->debug("Voice session ID: {}", data.SessionID); m_voice.SetSessionID(data.SessionID); + + // channel_id = null means disconnect. stop cuz out of order maybe + if (!data.ChannelID.has_value()) { + m_voice.Stop(); + } } else { if (data.GuildID.has_value() && data.Member.has_value()) { if (data.Member->User.has_value()) { @@ -2504,9 +2510,8 @@ void DiscordClient::SetSuperPropertiesFromIdentity(const IdentifyMessage &identi void DiscordClient::HandleSocketOpen() { } -void DiscordClient::HandleSocketClose(uint16_t code) { - printf("got socket close code: %d\n", code); - auto close_code = static_cast(code); +void DiscordClient::HandleSocketClose(const ix::WebSocketCloseInfo &info) { + auto close_code = static_cast(info.code); auto cb = [this, close_code]() { m_heartbeat_waiter.kill(); if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fc714e6..2d8db91 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -289,7 +289,7 @@ private: void SetSuperPropertiesFromIdentity(const IdentifyMessage &identity); void HandleSocketOpen(); - void HandleSocketClose(uint16_t code); + void HandleSocketClose(const ix::WebSocketCloseInfo &info); static bool CheckCode(const http::response_type &r); static bool CheckCode(const http::response_type &r, int expected); diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 291b975..3118cec 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -126,18 +126,26 @@ UDPSocket::type_signal_data UDPSocket::signal_data() { } DiscordVoiceClient::DiscordVoiceClient() { - sodium_init(); + if (sodium_init() == -1) { + spdlog::get("voice")->critical("sodium_init() failed"); + } + + m_ws = std::make_unique("voice-ws"); - m_ws.signal_open().connect([this]() { + m_ws->signal_open().connect([this]() { spdlog::get("voice")->info("Websocket open"); + SetState(State::Opened); }); - m_ws.signal_close().connect([this](uint16_t code) { - spdlog::get("voice")->info("Websocket closed with code {}", code); + m_ws->signal_close().connect([this](const ix::WebSocketCloseInfo &info) { + if (info.remote) { + SetState(State::ClosingByServer); + } Stop(); }); - m_ws.signal_message().connect([this](const std::string &str) { + m_ws->signal_message().connect([this](const std::string &str) { + spdlog::get("voice-ws")->trace("Recv: {}", str); std::lock_guard _(m_dispatch_mutex); m_message_queue.push(str); m_dispatcher.emit(); @@ -175,7 +183,8 @@ DiscordVoiceClient::~DiscordVoiceClient() { } void DiscordVoiceClient::Start() { - m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); + SetState(State::Opening); + m_ws->StartConnection("wss://" + m_endpoint + "/?v=7"); m_heartbeat_waiter.revive(); m_keepalive_waiter.revive(); m_connected = true; @@ -183,15 +192,29 @@ void DiscordVoiceClient::Start() { } void DiscordVoiceClient::Stop() { - m_ws.Stop(); - m_udp.Stop(); - m_heartbeat_waiter.kill(); - if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); - m_keepalive_waiter.kill(); - if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); - if (m_connected) { - m_connected = false; - m_signal_disconnected.emit(); + if (IsOpening() || IsOpened()) { + spdlog::get("voice")->debug("Requested voice client stop"); + SetState(State::ClosingByClient); + m_ws->Stop(); + } else if (IsClosing()) { + spdlog::get("voice")->debug("Completing stop in closing"); + if (m_state == State::ClosingByClient) { + SetState(State::ClosedByClient); + } else if (m_state == State::ClosingByServer) { + SetState(State::ClosedByServer); + } + m_ws->Stop(); + m_udp.Stop(); + m_heartbeat_waiter.kill(); + if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); + m_keepalive_waiter.kill(); + if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); + if (m_connected) { + m_connected = false; + m_signal_disconnected.emit(); + } + } else { + spdlog::get("voice")->debug("Stop called, but already stopped"); } } @@ -228,7 +251,6 @@ bool DiscordVoiceClient::IsConnected() const noexcept { void DiscordVoiceClient::OnGatewayMessage(const std::string &str) { VoiceGatewayMessage msg = nlohmann::json::parse(str); - puts(msg.Data.dump(4).c_str()); switch (msg.Opcode) { case VoiceGatewayOp::Hello: { HandleGatewayHello(msg); @@ -278,7 +300,7 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa msg.Delay = 0; msg.SSRC = m_ssrc; msg.Speaking = VoiceSpeakingType::Microphone; - m_ws.Send(msg); + m_ws->Send(msg); m_secret_key = d.SecretKey; m_udp.SetSSRC(m_ssrc); @@ -304,7 +326,7 @@ void DiscordVoiceClient::Identify() { msg.SessionID = m_session_id; msg.Token = m_token; msg.Video = true; - m_ws.Send(msg); + m_ws->Send(msg); } void DiscordVoiceClient::Discovery() { @@ -341,7 +363,7 @@ void DiscordVoiceClient::SelectProtocol(std::string_view ip, uint16_t port) { msg.Address = ip; msg.Port = port; msg.Protocol = "udp"; - m_ws.Send(msg); + m_ws->Send(msg); } void DiscordVoiceClient::OnUDPData(std::vector data) { @@ -370,7 +392,7 @@ void DiscordVoiceClient::HeartbeatThread() { VoiceHeartbeatMessage msg; msg.Nonce = static_cast(ms); - m_ws.Send(msg); + m_ws->Send(msg); } } @@ -386,6 +408,47 @@ void DiscordVoiceClient::KeepaliveThread() { } } +void DiscordVoiceClient::SetState(State state) { + m_state = state; + + switch (state) { + case State::Opening: + spdlog::get("voice")->debug("WS state: Opening"); + break; + case State::Opened: + spdlog::get("voice")->debug("WS state: Opened"); + break; + case State::ClosingByClient: + spdlog::get("voice")->debug("WS state: Closing (Client)"); + break; + case State::ClosingByServer: + spdlog::get("voice")->debug("WS state: Closing (Server)"); + break; + case State::ClosedByClient: + spdlog::get("voice")->debug("WS state: Closed (Client)"); + break; + case State::ClosedByServer: + spdlog::get("voice")->debug("WS state: Closed (Server)"); + break; + } +} + +bool DiscordVoiceClient::IsOpening() const noexcept { + return m_state == State::Opening; +} + +bool DiscordVoiceClient::IsOpened() const noexcept { + return m_state == State::Opened; +} + +bool DiscordVoiceClient::IsClosing() const noexcept { + return m_state == State::ClosingByClient || m_state == State::ClosingByServer; +} + +bool DiscordVoiceClient::IsClosed() const noexcept { + return m_state == State::ClosedByClient || m_state == State::ClosedByServer; +} + DiscordVoiceClient::type_signal_disconnected DiscordVoiceClient::signal_connected() { return m_signal_connected; } diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 1d8b952..61b329c 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -15,6 +15,7 @@ // clang-format on enum class VoiceGatewayCloseCode : uint16_t { + Normal = 4000, UnknownOpcode = 4001, InvalidPayload = 4002, NotAuthenticated = 4003, @@ -228,7 +229,10 @@ private: std::array m_secret_key; - Websocket m_ws; + // this is a unique_ptr because Websocket/ixwebsocket seems to have some strange behavior + // and quite frankly i do not feel like figuring out what is wrong + // so using a unique_ptr will just let me nuke the whole thing and make a new one + std::unique_ptr m_ws; UDPSocket m_udp; Glib::Dispatcher m_dispatcher; @@ -246,6 +250,24 @@ private: std::atomic m_connected = false; + enum class State { + Opening, + Opened, + ClosingByClient, + ClosedByClient, + ClosingByServer, + ClosedByServer, + }; + + void SetState(State state); + + [[nodiscard]] bool IsOpening() const noexcept; + [[nodiscard]] bool IsOpened() const noexcept; + [[nodiscard]] bool IsClosing() const noexcept; + [[nodiscard]] bool IsClosed() const noexcept; + + std::atomic m_state; + using type_signal_connected = sigc::signal; using type_signal_disconnected = sigc::signal; using type_signal_speaking = sigc::signal; diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index cf0111c..349913a 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -1,19 +1,25 @@ #include "websocket.hpp" +#include #include -Websocket::Websocket() - : m_close_code(ix::WebSocketCloseConstants::kNormalClosureCode) { +Websocket::Websocket(const std::string &id) + : m_close_info { 1000, "Normal", false } { + if (m_log = spdlog::get(id); !m_log) { + m_log = spdlog::stdout_color_mt(id); + } + m_open_dispatcher.connect([this]() { m_signal_open.emit(); }); m_close_dispatcher.connect([this]() { Stop(); - m_signal_close.emit(m_close_code); + m_signal_close.emit(m_close_info); }); } void Websocket::StartConnection(const std::string &url) { + m_log->debug("Starting connection to {}", url); m_websocket.disableAutomaticReconnection(); m_websocket.setUrl(url); m_websocket.setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward(msg)); }); @@ -34,16 +40,18 @@ void Websocket::SetPrintMessages(bool show) noexcept { } void Websocket::Stop() { + m_log->debug("Stopping with default close code"); Stop(ix::WebSocketCloseConstants::kNormalClosureCode); } void Websocket::Stop(uint16_t code) { + m_log->debug("Stopping with close code {}", code); m_websocket.stop(code); } void Websocket::Send(const std::string &str) { if (m_print_messages) - printf("sending %s\n", str.c_str()); + m_log->trace("Send: {}", str); m_websocket.sendText(str); } @@ -54,10 +62,12 @@ void Websocket::Send(const nlohmann::json &j) { void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) { switch (msg->type) { case ix::WebSocketMessageType::Open: { + m_log->debug("Received open frame, dispatching"); m_open_dispatcher.emit(); } break; case ix::WebSocketMessageType::Close: { - m_close_code = msg->closeInfo.code; + m_log->debug("Received close frame, dispatching. {} ({}){}", msg->closeInfo.code, msg->closeInfo.reason, msg->closeInfo.remote ? " Remote" : ""); + m_close_info = msg->closeInfo; m_close_dispatcher.emit(); } break; case ix::WebSocketMessageType::Message: { diff --git a/src/discord/websocket.hpp b/src/discord/websocket.hpp index ba55254..768121e 100644 --- a/src/discord/websocket.hpp +++ b/src/discord/websocket.hpp @@ -6,10 +6,11 @@ #include #include #include +#include class Websocket { public: - Websocket(); + Websocket(const std::string &id); void StartConnection(const std::string &url); void SetUserAgent(std::string agent); @@ -30,7 +31,7 @@ private: public: using type_signal_open = sigc::signal; - using type_signal_close = sigc::signal; + using type_signal_close = sigc::signal; using type_signal_message = sigc::signal; type_signal_open signal_open(); @@ -46,5 +47,7 @@ private: Glib::Dispatcher m_open_dispatcher; Glib::Dispatcher m_close_dispatcher; - std::atomic m_close_code; + ix::WebSocketCloseInfo m_close_info; + + std::shared_ptr m_log; }; -- cgit v1.2.3 From 4740965f4c1ee879a733b4068f4026a0dfa626fd Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 3 Jan 2023 19:01:33 -0500 Subject: rewrite DiscordVoiceClient and stuff --- src/components/channels.cpp | 4 +- src/discord/discord.cpp | 8 +- src/discord/discord.hpp | 4 +- src/discord/voiceclient.cpp | 326 ++++++++++++++++++++++---------------------- src/discord/voiceclient.hpp | 63 +++++---- src/discord/websocket.cpp | 17 ++- src/discord/websocket.hpp | 2 +- 7 files changed, 214 insertions(+), 210 deletions(-) (limited to 'src/discord/websocket.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index a36b215..09e40c3 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1019,7 +1019,7 @@ void ChannelList::OnVoiceChannelSubmenuPopup() { if (!iter) return; const auto id = static_cast((*iter)[m_columns.m_id]); auto &discord = Abaddon::Get().GetDiscordClient(); - if (discord.IsConnectedToVoice()) { + if (discord.IsVoiceConnected() || discord.IsVoiceConnecting()) { m_menu_voice_channel_join.set_sensitive(false); m_menu_voice_channel_disconnect.set_sensitive(discord.GetVoiceChannelID() == id); } else { @@ -1040,7 +1040,7 @@ void ChannelList::OnDMSubmenuPopup() { m_menu_dm_toggle_mute.set_label("Mute"); #ifdef WITH_VOICE - if (discord.IsConnectedToVoice()) { + if (discord.IsVoiceConnected() || discord.IsVoiceConnecting()) { m_menu_dm_join_voice.set_sensitive(false); m_menu_dm_disconnect_voice.set_sensitive(discord.GetVoiceChannelID() == id); } else { diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index cd16aa8..c068243 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1194,10 +1194,14 @@ void DiscordClient::DisconnectFromVoice() { m_websocket.Send(m); } -bool DiscordClient::IsConnectedToVoice() const noexcept { +bool DiscordClient::IsVoiceConnected() const noexcept { return m_voice.IsConnected(); } +bool DiscordClient::IsVoiceConnecting() const noexcept { + return m_voice.IsConnecting(); +} + Snowflake DiscordClient::GetVoiceChannelID() const noexcept { return m_voice_channel_id; } @@ -2174,7 +2178,7 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { m_voice.SetSessionID(data.SessionID); // channel_id = null means disconnect. stop cuz out of order maybe - if (!data.ChannelID.has_value()) { + if (!data.ChannelID.has_value() && (m_voice.IsConnected() || m_voice.IsConnecting())) { m_voice.Stop(); } } else { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 2d8db91..1e500e9 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -183,7 +183,9 @@ public: #ifdef WITH_VOICE void ConnectToVoice(Snowflake channel_id); void DisconnectFromVoice(); - [[nodiscard]] bool IsConnectedToVoice() const noexcept; + // Is fully connected + [[nodiscard]] bool IsVoiceConnected() const noexcept; + [[nodiscard]] bool IsVoiceConnecting() const noexcept; [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; [[nodiscard]] std::unordered_set GetUsersInVoiceChannel(Snowflake channel_id); [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 765c2af..fe72f87 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -17,8 +17,8 @@ #endif // clang-format on -UDPSocket::UDPSocket() { - m_socket = socket(AF_INET, SOCK_DGRAM, 0); +UDPSocket::UDPSocket() + : m_socket(INVALID_SOCKET) { } UDPSocket::~UDPSocket() { @@ -30,6 +30,7 @@ void UDPSocket::Connect(std::string_view ip, uint16_t port) { m_server.sin_family = AF_INET; S_ADDR(m_server) = inet_addr(ip.data()); m_server.sin_port = htons(port); + m_socket = socket(AF_INET, SOCK_DGRAM, 0); bind(m_socket, reinterpret_cast(&m_server), sizeof(m_server)); } @@ -94,6 +95,11 @@ std::vector UDPSocket::Receive() { } void UDPSocket::Stop() { + #ifdef _WIN32 + closesocket(m_socket); + #else + close(m_socket); + #endif m_running = false; if (m_thread.joinable()) m_thread.join(); } @@ -125,97 +131,62 @@ UDPSocket::type_signal_data UDPSocket::signal_data() { return m_signal_data; } -DiscordVoiceClient::DiscordVoiceClient() { +DiscordVoiceClient::DiscordVoiceClient() + : m_state(State::DisconnectedByClient) + , m_ws("voice-ws") + , m_log(spdlog::get("voice")) { if (sodium_init() == -1) { - spdlog::get("voice")->critical("sodium_init() failed"); + m_log->critical("sodium_init() failed"); } m_udp.signal_data().connect([this](const std::vector &data) { OnUDPData(data); }); - m_dispatcher.connect([this]() { - m_dispatch_mutex.lock(); - if (m_message_queue.empty()) { - m_dispatch_mutex.unlock(); - return; - } - auto msg = std::move(m_message_queue.front()); - m_message_queue.pop(); - m_dispatch_mutex.unlock(); - OnGatewayMessage(msg); - }); + m_ws.signal_open().connect(sigc::mem_fun(*this, &DiscordVoiceClient::OnWebsocketOpen)); + m_ws.signal_close().connect(sigc::mem_fun(*this, &DiscordVoiceClient::OnWebsocketClose)); + m_ws.signal_message().connect(sigc::mem_fun(*this, &DiscordVoiceClient::OnWebsocketMessage)); + + m_dispatcher.connect(sigc::mem_fun(*this, &DiscordVoiceClient::OnDispatch)); + // idle or else singleton deadlock Glib::signal_idle().connect_once([this]() { - // cant put in ctor or deadlock in singleton initialization - auto &aud = Abaddon::Get().GetAudio(); - aud.SetOpusBuffer(m_opus_buffer.data()); - aud.signal_opus_packet().connect([this](int payload_size) { - if (m_connected) - m_udp.SendEncrypted(m_opus_buffer.data(), payload_size); + auto &audio = Abaddon::Get().GetAudio(); + audio.SetOpusBuffer(m_opus_buffer.data()); + audio.signal_opus_packet().connect([this](int payload_size) { + // SendEncrypted if udp is connected }); }); } DiscordVoiceClient::~DiscordVoiceClient() { - Stop(); + if (IsConnected() || IsConnecting()) Stop(); } void DiscordVoiceClient::Start() { - m_ws = std::make_unique("voice-ws"); - - m_ws->signal_open().connect([this]() { - spdlog::get("voice")->info("Websocket open"); - SetState(State::Opened); - }); - - m_ws->signal_close().connect([this](const ix::WebSocketCloseInfo &info) { - if (info.remote) { - SetState(State::ClosingByServer); - } - Stop(); - }); - - m_ws->signal_message().connect([this](const std::string &str) { - spdlog::get("voice-ws")->trace("Recv: {}", str); - std::lock_guard _(m_dispatch_mutex); - m_message_queue.push(str); - m_dispatcher.emit(); - }); - - SetState(State::Opening); - m_ws->StartConnection("wss://" + m_endpoint + "/?v=7"); + SetState(State::ConnectingToWebsocket); m_heartbeat_waiter.revive(); m_keepalive_waiter.revive(); - m_connected = true; + m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); + m_signal_connected.emit(); } void DiscordVoiceClient::Stop() { - if (IsOpening() || IsOpened()) { - spdlog::get("voice")->debug("Requested voice client stop"); - SetState(State::ClosingByClient); - m_ws->Stop(); - } else if (IsClosing()) { - spdlog::get("voice")->debug("Completing stop in closing"); - if (m_state == State::ClosingByClient) { - SetState(State::ClosedByClient); - } else if (m_state == State::ClosingByServer) { - SetState(State::ClosedByServer); - } - m_ws->Stop(); - m_udp.Stop(); - m_heartbeat_waiter.kill(); - if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); - m_keepalive_waiter.kill(); - if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); - if (m_connected) { - m_connected = false; - m_signal_disconnected.emit(); - } - } else { - spdlog::get("voice")->debug("Stop called, but already stopped"); + if (!IsConnected() && !IsConnecting()) { + m_log->warn("Requested stop while not connected (from {})", GetStateName(m_state)); + return; } + + SetState(State::DisconnectedByClient); + m_ws.Stop(4014); + m_udp.Stop(); + m_heartbeat_waiter.kill(); + if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); + m_keepalive_waiter.kill(); + if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); + + m_signal_disconnected.emit(); } void DiscordVoiceClient::SetSessionID(std::string_view session_id) { @@ -239,37 +210,59 @@ void DiscordVoiceClient::SetUserID(Snowflake id) { } std::optional DiscordVoiceClient::GetSSRCOfUser(Snowflake id) const { - if (const auto it = m_ssrc_map.find(id); it != m_ssrc_map.end()) { - return it->second; - } - return {}; + return std::nullopt; } bool DiscordVoiceClient::IsConnected() const noexcept { - return m_connected; + return m_state == State::Connected; +} + +bool DiscordVoiceClient::IsConnecting() const noexcept { + return m_state == State::ConnectingToWebsocket || m_state == State::EstablishingConnection; } void DiscordVoiceClient::OnGatewayMessage(const std::string &str) { VoiceGatewayMessage msg = nlohmann::json::parse(str); switch (msg.Opcode) { - case VoiceGatewayOp::Hello: { + case VoiceGatewayOp::Hello: HandleGatewayHello(msg); - } break; - case VoiceGatewayOp::Ready: { + break; + case VoiceGatewayOp::Ready: HandleGatewayReady(msg); - } break; - case VoiceGatewayOp::SessionDescription: { + break; + case VoiceGatewayOp::SessionDescription: HandleGatewaySessionDescription(msg); - } break; - case VoiceGatewayOp::Speaking: { + break; + case VoiceGatewayOp::Speaking: HandleGatewaySpeaking(msg); - } break; - default: break; + break; + default: + m_log->warn("Unhandled opcode: {}", static_cast(msg.Opcode)); + } +} + +const char *DiscordVoiceClient::GetStateName(State state) { + switch (state) { + case State::DisconnectedByClient: + return "DisconnectedByClient"; + case State::DisconnectedByServer: + return "DisconnectedByServer"; + case State::ConnectingToWebsocket: + return "ConnectingToWebsocket"; + case State::EstablishingConnection: + return "EstablishingConnection"; + case State::Connected: + return "Connected"; + default: + return "Unknown"; } } void DiscordVoiceClient::HandleGatewayHello(const VoiceGatewayMessage &m) { VoiceHelloData d = m.Data; + + m_log->debug("Received hello: {}ms", d.HeartbeatInterval); + m_heartbeat_msec = d.HeartbeatInterval; m_heartbeat_thread = std::thread(&DiscordVoiceClient::HeartbeatThread, this); @@ -278,13 +271,16 @@ void DiscordVoiceClient::HandleGatewayHello(const VoiceGatewayMessage &m) { void DiscordVoiceClient::HandleGatewayReady(const VoiceGatewayMessage &m) { VoiceReadyData d = m.Data; + + m_log->debug("Received ready: {}:{} (ssrc: {})", d.IP, d.Port, d.SSRC); + m_ip = d.IP; m_port = d.Port; m_ssrc = d.SSRC; + if (std::find(d.Modes.begin(), d.Modes.end(), "xsalsa20_poly1305") == d.Modes.end()) { - spdlog::get("voice")->error("xsalsa20_poly1305 not in encryption modes"); + m_log->warn("xsalsa20_poly1305 not in modes"); } - spdlog::get("voice")->info("connect to {}:{} ssrc {}", m_ip, m_port, m_ssrc); m_udp.Connect(m_ip, m_port); m_keepalive_thread = std::thread(&DiscordVoiceClient::KeepaliveThread, this); @@ -294,29 +290,26 @@ void DiscordVoiceClient::HandleGatewayReady(const VoiceGatewayMessage &m) { void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessage &m) { VoiceSessionDescriptionData d = m.Data; - spdlog::get("voice")->debug("receiving with {}, secret key: {:ns}", d.Mode, spdlog::to_hex(std::begin(d.SecretKey), std::end(d.SecretKey))); + + m_log->debug("Received session description (mode: {}) (key: {:ns}) ", d.Mode, spdlog::to_hex(d.SecretKey.begin(), d.SecretKey.end())); VoiceSpeakingMessage msg; msg.Delay = 0; msg.SSRC = m_ssrc; msg.Speaking = VoiceSpeakingType::Microphone; - m_ws->Send(msg); + m_ws.Send(msg); m_secret_key = d.SecretKey; m_udp.SetSSRC(m_ssrc); m_udp.SetSecretKey(m_secret_key); m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); - m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); - m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); - m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); - m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); m_udp.Run(); } void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { - VoiceSpeakingData data = m.Data; - m_ssrc_map[data.UserID] = data.SSRC; - m_signal_speaking.emit(data); + VoiceSpeakingData d = m.Data; + // ssrc map + m_signal_speaking.emit(d); } void DiscordVoiceClient::Identify() { @@ -326,80 +319,92 @@ void DiscordVoiceClient::Identify() { msg.SessionID = m_session_id; msg.Token = m_token; msg.Video = true; - m_ws->Send(msg); + + m_ws.Send(msg); } void DiscordVoiceClient::Discovery() { std::vector payload; - // 2 bytes = 1, request + // request payload.push_back(0x00); payload.push_back(0x01); - // 2 bytes = 70, pl length + // payload length (70) payload.push_back(0x00); - payload.push_back(70); - // 4 bytes = ssrc + payload.push_back(0x46); + // ssrc payload.push_back((m_ssrc >> 24) & 0xFF); payload.push_back((m_ssrc >> 16) & 0xFF); payload.push_back((m_ssrc >> 8) & 0xFF); payload.push_back((m_ssrc >> 0) & 0xFF); - // address and port - for (int i = 0; i < 66; i++) - payload.push_back(0); + // space for address and port + for (int i = 0; i < 66; i++) payload.push_back(0x00); + m_udp.Send(payload.data(), payload.size()); - auto response = m_udp.Receive(); - if (response.size() >= 74 && response[0] == 0x00 && response[1] == 0x02) { - const char *our_ip = reinterpret_cast(&response[8]); - uint16_t our_port = (response[73] << 8) | response[74]; - spdlog::get("voice")->debug("IP address discovered: {}:{}\n", our_ip, our_port); - SelectProtocol(our_ip, our_port); - } else { - spdlog::get("voice")->error("Received non-discovery packet after discovery"); + + constexpr int MAX_TRIES = 100; + for (int i = 0; i < MAX_TRIES; i++) { + const auto response = m_udp.Receive(); + if (response.size() >= 74 && response[0] == 0x00 && response[1] == 0x02) { + const char *ip = reinterpret_cast(response.data() + 8); + uint16_t port = (response[73] << 8) | response[74]; + m_log->info("Discovered IP and port: {}:{}", ip, port); + SelectProtocol(ip, port); + break; + } else { + m_log->error("Received non-discovery packet after sending request (try {}/{})", i + 1, MAX_TRIES); + } } } -void DiscordVoiceClient::SelectProtocol(std::string_view ip, uint16_t port) { +void DiscordVoiceClient::SelectProtocol(const char *ip, uint16_t port) { VoiceSelectProtocolMessage msg; msg.Mode = "xsalsa20_poly1305"; msg.Address = ip; msg.Port = port; msg.Protocol = "udp"; - m_ws->Send(msg); + + m_ws.Send(msg); } -void DiscordVoiceClient::OnUDPData(std::vector data) { - uint8_t *payload = data.data() + 12; - uint32_t ssrc = (data[8] << 24) | - (data[9] << 16) | - (data[10] << 8) | - (data[11] << 0); - static std::array nonce = {}; - std::memcpy(nonce.data(), data.data(), 12); - if (crypto_secretbox_open_easy(payload, payload, data.size() - 12, nonce.data(), m_secret_key.data())) { - // spdlog::get("voice")->trace("UDP payload decryption failure"); +void DiscordVoiceClient::OnWebsocketOpen() { + m_log->info("Websocket opened"); + SetState(State::EstablishingConnection); +} + +void DiscordVoiceClient::OnWebsocketClose(const ix::WebSocketCloseInfo &info) { + if (info.remote) { + m_log->debug("Websocket closed (remote): {} ({})", info.code, info.reason); } else { - Abaddon::Get().GetAudio().FeedMeOpus(ssrc, { payload, payload + data.size() - 12 - crypto_box_MACBYTES }); + m_log->debug("Websocket closed (local): {} ({})", info.code, info.reason); } } +void DiscordVoiceClient::OnWebsocketMessage(const std::string &data) { + m_dispatch_mutex.lock(); + m_dispatch_queue.push(data); + m_dispatcher.emit(); + m_dispatch_mutex.unlock(); +} + void DiscordVoiceClient::HeartbeatThread() { while (true) { - if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) - break; + if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) break; + + const auto ms = static_cast(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()); - const auto ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + m_log->trace("Heartbeat: {}", ms); VoiceHeartbeatMessage msg; - msg.Nonce = static_cast(ms); - m_ws->Send(msg); + msg.Nonce = ms; + m_ws.Send(msg); } } void DiscordVoiceClient::KeepaliveThread() { while (true) { - if (!m_keepalive_waiter.wait_for(std::chrono::seconds(10))) - break; + if (!m_heartbeat_waiter.wait_for(std::chrono::seconds(10))) break; if (IsConnected()) { const static uint8_t KEEPALIVE[] = { 0x13, 0x37 }; @@ -409,44 +414,35 @@ void DiscordVoiceClient::KeepaliveThread() { } void DiscordVoiceClient::SetState(State state) { + m_log->debug("Changing state to {}", GetStateName(state)); m_state = state; - - switch (state) { - case State::Opening: - spdlog::get("voice")->debug("WS state: Opening"); - break; - case State::Opened: - spdlog::get("voice")->debug("WS state: Opened"); - break; - case State::ClosingByClient: - spdlog::get("voice")->debug("WS state: Closing (Client)"); - break; - case State::ClosingByServer: - spdlog::get("voice")->debug("WS state: Closing (Server)"); - break; - case State::ClosedByClient: - spdlog::get("voice")->debug("WS state: Closed (Client)"); - break; - case State::ClosedByServer: - spdlog::get("voice")->debug("WS state: Closed (Server)"); - break; - } -} - -bool DiscordVoiceClient::IsOpening() const noexcept { - return m_state == State::Opening; } -bool DiscordVoiceClient::IsOpened() const noexcept { - return m_state == State::Opened; -} - -bool DiscordVoiceClient::IsClosing() const noexcept { - return m_state == State::ClosingByClient || m_state == State::ClosingByServer; +void DiscordVoiceClient::OnUDPData(std::vector data) { + uint8_t *payload = data.data() + 12; + uint32_t ssrc = (data[8] << 24) | + (data[9] << 16) | + (data[10] << 8) | + (data[11] << 0); + static std::array nonce = {}; + std::memcpy(nonce.data(), data.data(), 12); + if (crypto_secretbox_open_easy(payload, payload, data.size() - 12, nonce.data(), m_secret_key.data())) { + // spdlog::get("voice")->trace("UDP payload decryption failure"); + } else { + Abaddon::Get().GetAudio().FeedMeOpus(ssrc, { payload, payload + data.size() - 12 - crypto_box_MACBYTES }); + } } -bool DiscordVoiceClient::IsClosed() const noexcept { - return m_state == State::ClosedByClient || m_state == State::ClosedByServer; +void DiscordVoiceClient::OnDispatch() { + m_dispatch_mutex.lock(); + if (m_dispatch_queue.empty()) { + m_dispatch_mutex.unlock(); + return; + } + auto msg = std::move(m_dispatch_queue.front()); + m_dispatch_queue.pop(); + m_dispatch_mutex.unlock(); + OnGatewayMessage(msg); } DiscordVoiceClient::type_signal_disconnected DiscordVoiceClient::signal_connected() { diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 61b329c..47caec6 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include // clang-format on @@ -196,10 +197,21 @@ public: [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; + // Is a websocket and udp connection fully established [[nodiscard]] bool IsConnected() const noexcept; + [[nodiscard]] bool IsConnecting() const noexcept; private: - void OnGatewayMessage(const std::string &str); + enum class State { + ConnectingToWebsocket, + EstablishingConnection, + Connected, + DisconnectedByClient, + DisconnectedByServer, + }; + static const char *GetStateName(State state); + + void OnGatewayMessage(const std::string &msg); void HandleGatewayHello(const VoiceGatewayMessage &m); void HandleGatewayReady(const VoiceGatewayMessage &m); void HandleGatewaySessionDescription(const VoiceGatewayMessage &m); @@ -207,13 +219,19 @@ private: void Identify(); void Discovery(); - void SelectProtocol(std::string_view ip, uint16_t port); + void SelectProtocol(const char *ip, uint16_t port); - void OnUDPData(std::vector data); + void OnWebsocketOpen(); + void OnWebsocketClose(const ix::WebSocketCloseInfo &info); + void OnWebsocketMessage(const std::string &str); void HeartbeatThread(); void KeepaliveThread(); + void SetState(State state); + + void OnUDPData(std::vector data); + std::string m_session_id; std::string m_endpoint; std::string m_token; @@ -221,24 +239,12 @@ private: Snowflake m_channel_id; Snowflake m_user_id; + std::array m_secret_key; + std::string m_ip; uint16_t m_port; uint32_t m_ssrc; - std::unordered_map m_ssrc_map; - - std::array m_secret_key; - - // this is a unique_ptr because Websocket/ixwebsocket seems to have some strange behavior - // and quite frankly i do not feel like figuring out what is wrong - // so using a unique_ptr will just let me nuke the whole thing and make a new one - std::unique_ptr m_ws; - UDPSocket m_udp; - - Glib::Dispatcher m_dispatcher; - std::queue m_message_queue; - std::mutex m_dispatch_mutex; - int m_heartbeat_msec; Waiter m_heartbeat_waiter; std::thread m_heartbeat_thread; @@ -246,25 +252,18 @@ private: Waiter m_keepalive_waiter; std::thread m_keepalive_thread; - std::array m_opus_buffer; + Websocket m_ws; + UDPSocket m_udp; - std::atomic m_connected = false; + Glib::Dispatcher m_dispatcher; + std::queue m_dispatch_queue; + std::mutex m_dispatch_mutex; - enum class State { - Opening, - Opened, - ClosingByClient, - ClosedByClient, - ClosingByServer, - ClosedByServer, - }; + void OnDispatch(); - void SetState(State state); + std::array m_opus_buffer; - [[nodiscard]] bool IsOpening() const noexcept; - [[nodiscard]] bool IsOpened() const noexcept; - [[nodiscard]] bool IsClosing() const noexcept; - [[nodiscard]] bool IsClosed() const noexcept; + std::shared_ptr m_log; std::atomic m_state; diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index 349913a..d40d057 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -20,11 +20,14 @@ Websocket::Websocket(const std::string &id) void Websocket::StartConnection(const std::string &url) { m_log->debug("Starting connection to {}", url); - m_websocket.disableAutomaticReconnection(); - m_websocket.setUrl(url); - m_websocket.setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward(msg)); }); - m_websocket.setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works - m_websocket.start(); + + m_websocket = std::make_unique(); + + m_websocket->disableAutomaticReconnection(); + m_websocket->setUrl(url); + m_websocket->setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward(msg)); }); + m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works + m_websocket->start(); } void Websocket::SetUserAgent(std::string agent) { @@ -46,13 +49,13 @@ void Websocket::Stop() { void Websocket::Stop(uint16_t code) { m_log->debug("Stopping with close code {}", code); - m_websocket.stop(code); + m_websocket-> stop(code); } void Websocket::Send(const std::string &str) { if (m_print_messages) m_log->trace("Send: {}", str); - m_websocket.sendText(str); + m_websocket->sendText(str); } void Websocket::Send(const nlohmann::json &j) { diff --git a/src/discord/websocket.hpp b/src/discord/websocket.hpp index 768121e..a77bf55 100644 --- a/src/discord/websocket.hpp +++ b/src/discord/websocket.hpp @@ -26,7 +26,7 @@ public: private: void OnMessage(const ix::WebSocketMessagePtr &msg); - ix::WebSocket m_websocket; + std::unique_ptr m_websocket; std::string m_agent; public: -- cgit v1.2.3 From 75158f2c295bdc52ab4743b3529ce9092ccfd502 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 8 May 2023 00:28:59 -0400 Subject: handle channel moves + region change --- src/components/voiceinfobox.cpp | 39 +++++++++++++++++++++++------------- src/components/voiceinfobox.hpp | 2 ++ src/discord/discord.cpp | 44 +++++++++++++++++++++++++++++++++++++++++ src/discord/discord.hpp | 3 +++ src/discord/voiceclient.cpp | 4 ++++ src/discord/websocket.cpp | 7 ++++++- 6 files changed, 84 insertions(+), 15 deletions(-) (limited to 'src/discord/websocket.cpp') diff --git a/src/components/voiceinfobox.cpp b/src/components/voiceinfobox.cpp index b870794..4c5a127 100644 --- a/src/components/voiceinfobox.cpp +++ b/src/components/voiceinfobox.cpp @@ -29,20 +29,7 @@ VoiceInfoBox::VoiceInfoBox() Abaddon::Get().GetDiscordClient().signal_voice_requested_connect().connect([this](Snowflake channel_id) { show(); - - if (const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(channel_id); channel.has_value() && channel->Name.has_value()) { - if (channel->GuildID.has_value()) { - if (const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(*channel->GuildID); guild.has_value()) { - m_location.set_label(*channel->Name + " / " + guild->Name); - return; - } - } - - m_location.set_label(*channel->Name); - return; - } - - m_location.set_label("Unknown"); + UpdateLocation(); }); Abaddon::Get().GetDiscordClient().signal_voice_requested_disconnect().connect([this]() { @@ -72,6 +59,10 @@ VoiceInfoBox::VoiceInfoBox() m_status.set_label(label); }); + Abaddon::Get().GetDiscordClient().signal_voice_channel_changed().connect([this](Snowflake channel_id) { + UpdateLocation(); + }); + AddPointerCursor(m_status_ev); m_status_ev.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_PRIMARY) { @@ -100,4 +91,24 @@ VoiceInfoBox::VoiceInfoBox() show_all_children(); } +void VoiceInfoBox::UpdateLocation() { + auto &discord = Abaddon::Get().GetDiscordClient(); + + const auto channel_id = discord.GetVoiceChannelID(); + + if (const auto channel = discord.GetChannel(channel_id); channel.has_value() && channel->Name.has_value()) { + if (channel->GuildID.has_value()) { + if (const auto guild = discord.GetGuild(*channel->GuildID); guild.has_value()) { + m_location.set_label(*channel->Name + " / " + guild->Name); + return; + } + } + + m_location.set_label(*channel->Name); + return; + } + + m_location.set_label("Unknown"); +} + #endif diff --git a/src/components/voiceinfobox.hpp b/src/components/voiceinfobox.hpp index 74aad27..9988c63 100644 --- a/src/components/voiceinfobox.hpp +++ b/src/components/voiceinfobox.hpp @@ -12,6 +12,8 @@ public: VoiceInfoBox(); private: + void UpdateLocation(); + Gtk::Box m_left; Gtk::EventBox m_status_ev; Gtk::Label m_status; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 64988d2..675b640 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2207,7 +2207,42 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { } #ifdef WITH_VOICE + +/* + * When you connect to a voice channel: + * C->S: VOICE_STATE_UPDATE + * S->C: VOICE_STATE_UPDATE + * S->C: VOICE_SERVER_UPDATE + * + * A new websocket is opened to the ws specified by VOICE_SERVER_UPDATE then: + * S->C: HELLO + * C->S: IDENTIFY + * S->C: READY + * C->U: discover + * U->C: discover result + * C->S: SELECT_PROTOCOL + * S->C: SESSION_DESCRIPTION + * Done!!! + * + * When you get disconnected (no particular order): + * S->C: 4014 Disconnected (Server to voice gateway) + * S->C: VOICE_STATE_UPDATE (Server to main gateway) + * + * When you get moved: + * S->C: VOICE_STATE_UPDATE + * S->C: VOICE_SERVER_UPDATE (usually) + * S->C: 4014 Disconnected (Server to voice gateway) + * + * Key thing: 4014 Disconnected can come before or after or in between main gateway messages + * + * Region change: + * Same thing but close code 4000 + * + */ + void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { + spdlog::get("discord")->trace("VOICE_STATE_UPDATE"); + VoiceState data = msg.Data; if (data.UserID == m_user_data.ID) { @@ -2217,6 +2252,9 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { // channel_id = null means disconnect. stop cuz out of order maybe if (!data.ChannelID.has_value() && (m_voice.IsConnected() || m_voice.IsConnecting())) { m_voice.Stop(); + } else if (data.ChannelID.has_value()) { + m_voice_channel_id = *data.ChannelID; + m_signal_voice_channel_changed.emit(m_voice_channel_id); } } else { if (data.GuildID.has_value() && data.Member.has_value()) { @@ -2245,6 +2283,8 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { } void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { + spdlog::get("discord")->trace("VOICE_SERVER_UPDATE"); + VoiceServerUpdateData data = msg.Data; spdlog::get("discord")->debug("Voice server endpoint: {}", data.Endpoint); spdlog::get("discord")->debug("Voice token: {}", data.Token); @@ -3055,4 +3095,8 @@ DiscordClient::type_signal_voice_requested_disconnect DiscordClient::signal_voic DiscordClient::type_signal_voice_client_state_update DiscordClient::signal_voice_client_state_update() { return m_signal_voice_client_state_update; } + +DiscordClient::type_signal_voice_channel_changed DiscordClient::signal_voice_channel_changed() { + return m_signal_voice_channel_changed; +} #endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index aadb72d..cb76ef6 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -454,6 +454,7 @@ public: using type_signal_voice_requested_connect = sigc::signal; using type_signal_voice_requested_disconnect = sigc::signal; using type_signal_voice_client_state_update = sigc::signal; + using type_signal_voice_channel_changed = sigc::signal; #endif type_signal_gateway_ready signal_gateway_ready(); @@ -520,6 +521,7 @@ public: type_signal_voice_requested_connect signal_voice_requested_connect(); type_signal_voice_requested_disconnect signal_voice_requested_disconnect(); type_signal_voice_client_state_update signal_voice_client_state_update(); + type_signal_voice_channel_changed signal_voice_channel_changed(); #endif protected: @@ -587,5 +589,6 @@ protected: type_signal_voice_requested_connect m_signal_voice_requested_connect; type_signal_voice_requested_disconnect m_signal_voice_requested_disconnect; type_signal_voice_client_state_update m_signal_voice_client_state_update; + type_signal_voice_channel_changed m_signal_voice_channel_changed; #endif }; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index c58d4b8..c37ba7b 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -166,6 +166,10 @@ DiscordVoiceClient::~DiscordVoiceClient() { } void DiscordVoiceClient::Start() { + if (IsConnected() || IsConnecting()) { + Stop(); + } + SetState(State::ConnectingToWebsocket); m_ssrc_map.clear(); m_heartbeat_waiter.revive(); diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index d40d057..f886e69 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -49,7 +49,12 @@ void Websocket::Stop() { void Websocket::Stop(uint16_t code) { m_log->debug("Stopping with close code {}", code); - m_websocket-> stop(code); + m_websocket->stop(code); + m_log->trace("Socket::stop complete"); + while (Gtk::Main::events_pending()) { + Gtk::Main::iteration(); + } + m_log->trace("No events pending"); } void Websocket::Send(const std::string &str) { -- cgit v1.2.3