summaryrefslogtreecommitdiff
path: root/src/discord/voiceclient.hpp
diff options
context:
space:
mode:
authorouwou <26526779+ouwou@users.noreply.github.com>2023-05-29 06:29:39 +0000
committerGitHub <noreply@github.com>2023-05-29 06:29:39 +0000
commit3fbc71e369fedddd7ebc2eb08c9721860ae6b597 (patch)
tree297c9e2d905b0ba33978be5f64b9624773661a30 /src/discord/voiceclient.hpp
parent8e9fb27e0bec4c9dd4b706e15987cbb16cf42005 (diff)
parent7da37a2fa90cebf43631c74aa1abfb9842502291 (diff)
downloadabaddon-portaudio-3fbc71e369fedddd7ebc2eb08c9721860ae6b597.tar.gz
abaddon-portaudio-3fbc71e369fedddd7ebc2eb08c9721860ae6b597.zip
Merge pull request #105 from uowuo/voice
Voice support 🤯
Diffstat (limited to 'src/discord/voiceclient.hpp')
-rw-r--r--src/discord/voiceclient.hpp291
1 files changed, 291 insertions, 0 deletions
diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp
new file mode 100644
index 0000000..0112749
--- /dev/null
+++ b/src/discord/voiceclient.hpp
@@ -0,0 +1,291 @@
+#pragma once
+#ifdef WITH_VOICE
+// clang-format off
+
+#include "snowflake.hpp"
+#include "waiter.hpp"
+#include "websocket.hpp"
+#include <mutex>
+#include <optional>
+#include <queue>
+#include <string>
+#include <glibmm/dispatcher.h>
+#include <sigc++/sigc++.h>
+#include <spdlog/logger.h>
+#include <unordered_map>
+// clang-format on
+
+enum class VoiceGatewayCloseCode : uint16_t {
+ Normal = 4000,
+ UnknownOpcode = 4001,
+ InvalidPayload = 4002,
+ NotAuthenticated = 4003,
+ AuthenticationFailed = 4004,
+ AlreadyAuthenticated = 4005,
+ SessionInvalid = 4006,
+ SessionTimedOut = 4009,
+ ServerNotFound = 4011,
+ UnknownProtocol = 4012,
+ Disconnected = 4014,
+ ServerCrashed = 4015,
+ UnknownEncryption = 4016,
+};
+
+enum class VoiceGatewayOp : int {
+ Identify = 0,
+ SelectProtocol = 1,
+ Ready = 2,
+ Heartbeat = 3,
+ SessionDescription = 4,
+ Speaking = 5,
+ HeartbeatAck = 6,
+ Resume = 7,
+ Hello = 8,
+ Resumed = 9,
+ ClientDisconnect = 13,
+};
+
+struct VoiceGatewayMessage {
+ VoiceGatewayOp Opcode;
+ nlohmann::json Data;
+
+ friend void from_json(const nlohmann::json &j, VoiceGatewayMessage &m);
+};
+
+struct VoiceHelloData {
+ int HeartbeatInterval;
+
+ friend void from_json(const nlohmann::json &j, VoiceHelloData &m);
+};
+
+struct VoiceHeartbeatMessage {
+ uint64_t Nonce;
+
+ friend void to_json(nlohmann::json &j, const VoiceHeartbeatMessage &m);
+};
+
+struct VoiceIdentifyMessage {
+ Snowflake ServerID;
+ Snowflake UserID;
+ std::string SessionID;
+ std::string Token;
+ bool Video;
+ // todo streams i guess?
+
+ friend void to_json(nlohmann::json &j, const VoiceIdentifyMessage &m);
+};
+
+struct VoiceReadyData {
+ struct VoiceStream {
+ bool IsActive;
+ int Quality;
+ std::string RID;
+ int RTXSSRC;
+ int SSRC;
+ std::string Type;
+
+ friend void from_json(const nlohmann::json &j, VoiceStream &m);
+ };
+
+ std::vector<std::string> Experiments;
+ std::string IP;
+ std::vector<std::string> Modes;
+ uint16_t Port;
+ uint32_t SSRC;
+ std::vector<VoiceStream> Streams;
+
+ friend void from_json(const nlohmann::json &j, VoiceReadyData &m);
+};
+
+struct VoiceSelectProtocolMessage {
+ std::string Address;
+ uint16_t Port;
+ std::string Mode;
+ std::string Protocol;
+
+ friend void to_json(nlohmann::json &j, const VoiceSelectProtocolMessage &m);
+};
+
+struct VoiceSessionDescriptionData {
+ // std::string AudioCodec;
+ // std::string VideoCodec;
+ // std::string MediaSessionID;
+ std::string Mode;
+ std::array<uint8_t, 32> SecretKey;
+
+ friend void from_json(const nlohmann::json &j, VoiceSessionDescriptionData &m);
+};
+
+enum class VoiceSpeakingType {
+ Microphone = 1 << 0,
+ Soundshare = 1 << 1,
+ Priority = 1 << 2,
+};
+
+struct VoiceSpeakingMessage {
+ VoiceSpeakingType Speaking;
+ int Delay;
+ uint32_t SSRC;
+
+ friend void to_json(nlohmann::json &j, const VoiceSpeakingMessage &m);
+};
+
+struct VoiceSpeakingData {
+ Snowflake UserID;
+ uint32_t SSRC;
+ VoiceSpeakingType Speaking;
+
+ friend void from_json(const nlohmann::json &j, VoiceSpeakingData &m);
+};
+
+class UDPSocket {
+public:
+ UDPSocket();
+ ~UDPSocket();
+
+ void Connect(std::string_view ip, uint16_t port);
+ void Run();
+ void SetSecretKey(std::array<uint8_t, 32> key);
+ void SetSSRC(uint32_t ssrc);
+ void SendEncrypted(const uint8_t *data, size_t len);
+ void SendEncrypted(const std::vector<uint8_t> &data);
+ void Send(const uint8_t *data, size_t len);
+ std::vector<uint8_t> Receive();
+ void Stop();
+
+private:
+ void ReadThread();
+
+ #ifdef _WIN32
+ SOCKET m_socket;
+ #else
+ int m_socket;
+ #endif
+ sockaddr_in m_server;
+
+ std::atomic<bool> m_running = false;
+
+ std::thread m_thread;
+
+ std::array<uint8_t, 32> m_secret_key;
+ uint32_t m_ssrc;
+
+ uint16_t m_sequence = 0;
+
+public:
+ using type_signal_data = sigc::signal<void, std::vector<uint8_t>>;
+ type_signal_data signal_data();
+
+private:
+ type_signal_data m_signal_data;
+};
+
+class DiscordVoiceClient {
+public:
+ DiscordVoiceClient();
+ ~DiscordVoiceClient();
+
+ void Start();
+ void Stop();
+
+ void SetSessionID(std::string_view session_id);
+ void SetEndpoint(std::string_view endpoint);
+ void SetToken(std::string_view token);
+ void SetServerID(Snowflake id);
+ void SetUserID(Snowflake id);
+
+ // todo serialize
+ void SetUserVolume(Snowflake id, float volume);
+ [[nodiscard]] float GetUserVolume(Snowflake id) const;
+ [[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;
+
+ enum class State {
+ ConnectingToWebsocket,
+ EstablishingConnection,
+ Connected,
+ DisconnectedByClient,
+ DisconnectedByServer,
+ };
+
+private:
+ 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);
+ void HandleGatewaySpeaking(const VoiceGatewayMessage &m);
+
+ void Identify();
+ void Discovery();
+ void SelectProtocol(const char *ip, uint16_t port);
+
+ 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;
+ Snowflake m_server_id;
+ Snowflake m_channel_id;
+ Snowflake m_user_id;
+
+ std::unordered_map<Snowflake, uint32_t> m_ssrc_map;
+ std::unordered_map<Snowflake, float> m_user_volumes;
+
+ std::array<uint8_t, 32> m_secret_key;
+
+ std::string m_ip;
+ uint16_t m_port;
+ uint32_t m_ssrc;
+
+ int m_heartbeat_msec;
+ Waiter m_heartbeat_waiter;
+ std::thread m_heartbeat_thread;
+
+ 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::shared_ptr<spdlog::logger> m_log;
+
+ std::atomic<State> m_state;
+
+ using type_signal_connected = sigc::signal<void()>;
+ using type_signal_disconnected = sigc::signal<void()>;
+ using type_signal_speaking = sigc::signal<void(VoiceSpeakingData)>;
+ using type_signal_state_update = sigc::signal<void(State)>;
+ type_signal_connected m_signal_connected;
+ type_signal_disconnected m_signal_disconnected;
+ type_signal_speaking m_signal_speaking;
+ type_signal_state_update m_signal_state_update;
+
+public:
+ type_signal_connected signal_connected();
+ type_signal_disconnected signal_disconnected();
+ type_signal_speaking signal_speaking();
+ type_signal_state_update signal_state_update();
+};
+#endif