forked from OpenGamers/abaddon
rewrite DiscordVoiceClient and stuff
This commit is contained in:
@@ -1019,7 +1019,7 @@ void ChannelList::OnVoiceChannelSubmenuPopup() {
|
||||
if (!iter) return;
|
||||
const auto id = static_cast<Snowflake>((*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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Snowflake> GetUsersInVoiceChannel(Snowflake channel_id);
|
||||
[[nodiscard]] std::optional<uint32_t> GetSSRCOfUser(Snowflake id) const;
|
||||
|
||||
@@ -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<sockaddr *>(&m_server), sizeof(m_server));
|
||||
}
|
||||
|
||||
@@ -94,6 +95,11 @@ std::vector<uint8_t> 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<uint8_t> &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<Websocket>("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<std::mutex> _(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<uint32_t> 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<int>(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,44 +319,103 @@ 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<uint8_t> 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<const char *>(&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<const char *>(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::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 {
|
||||
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;
|
||||
|
||||
const auto ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count());
|
||||
|
||||
m_log->trace("Heartbeat: {}", ms);
|
||||
|
||||
VoiceHeartbeatMessage msg;
|
||||
msg.Nonce = ms;
|
||||
m_ws.Send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordVoiceClient::KeepaliveThread() {
|
||||
while (true) {
|
||||
if (!m_heartbeat_waiter.wait_for(std::chrono::seconds(10))) break;
|
||||
|
||||
if (IsConnected()) {
|
||||
const static uint8_t KEEPALIVE[] = { 0x13, 0x37 };
|
||||
m_udp.Send(KEEPALIVE, sizeof(KEEPALIVE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordVoiceClient::SetState(State state) {
|
||||
m_log->debug("Changing state to {}", GetStateName(state));
|
||||
m_state = state;
|
||||
}
|
||||
|
||||
void DiscordVoiceClient::OnUDPData(std::vector<uint8_t> data) {
|
||||
@@ -381,72 +433,16 @@ void DiscordVoiceClient::OnUDPData(std::vector<uint8_t> data) {
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordVoiceClient::HeartbeatThread() {
|
||||
while (true) {
|
||||
if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec)))
|
||||
break;
|
||||
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
VoiceHeartbeatMessage msg;
|
||||
msg.Nonce = static_cast<uint64_t>(ms);
|
||||
m_ws->Send(msg);
|
||||
void DiscordVoiceClient::OnDispatch() {
|
||||
m_dispatch_mutex.lock();
|
||||
if (m_dispatch_queue.empty()) {
|
||||
m_dispatch_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordVoiceClient::KeepaliveThread() {
|
||||
while (true) {
|
||||
if (!m_keepalive_waiter.wait_for(std::chrono::seconds(10)))
|
||||
break;
|
||||
|
||||
if (IsConnected()) {
|
||||
const static uint8_t KEEPALIVE[] = { 0x13, 0x37 };
|
||||
m_udp.Send(KEEPALIVE, sizeof(KEEPALIVE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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() {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <string>
|
||||
#include <glibmm/dispatcher.h>
|
||||
#include <sigc++/sigc++.h>
|
||||
#include <spdlog/logger.h>
|
||||
#include <unordered_map>
|
||||
// clang-format on
|
||||
|
||||
@@ -196,10 +197,21 @@ public:
|
||||
|
||||
[[nodiscard]] std::optional<uint32_t> 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<uint8_t> 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<uint8_t> 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<uint8_t, 32> m_secret_key;
|
||||
|
||||
std::string m_ip;
|
||||
uint16_t m_port;
|
||||
uint32_t m_ssrc;
|
||||
|
||||
std::unordered_map<Snowflake, uint32_t> m_ssrc_map;
|
||||
|
||||
std::array<uint8_t, 32> 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<Websocket> m_ws;
|
||||
UDPSocket m_udp;
|
||||
|
||||
Glib::Dispatcher m_dispatcher;
|
||||
std::queue<std::string> 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;
|
||||
|
||||
Websocket m_ws;
|
||||
UDPSocket m_udp;
|
||||
|
||||
Glib::Dispatcher m_dispatcher;
|
||||
std::queue<std::string> m_dispatch_queue;
|
||||
std::mutex m_dispatch_mutex;
|
||||
|
||||
void OnDispatch();
|
||||
|
||||
std::array<uint8_t, 1275> m_opus_buffer;
|
||||
|
||||
std::atomic<bool> 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::shared_ptr<spdlog::logger> m_log;
|
||||
|
||||
std::atomic<State> m_state;
|
||||
|
||||
|
||||
@@ -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<decltype(msg)>(msg)); });
|
||||
m_websocket.setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works
|
||||
m_websocket.start();
|
||||
|
||||
m_websocket = std::make_unique<ix::WebSocket>();
|
||||
|
||||
m_websocket->disableAutomaticReconnection();
|
||||
m_websocket->setUrl(url);
|
||||
m_websocket->setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward<decltype(msg)>(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) {
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
private:
|
||||
void OnMessage(const ix::WebSocketMessagePtr &msg);
|
||||
|
||||
ix::WebSocket m_websocket;
|
||||
std::unique_ptr<ix::WebSocket> m_websocket;
|
||||
std::string m_agent;
|
||||
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user