You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

DiscordVoiceApiClient.cs 12 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #pragma warning disable CS1591
  2. using Discord.API;
  3. using Discord.API.Voice;
  4. using Discord.Net.Converters;
  5. using Discord.Net.Udp;
  6. using Discord.Net.WebSockets;
  7. using Newtonsoft.Json;
  8. using System;
  9. using System.Diagnostics;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.IO.Compression;
  13. using System.Text;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. namespace Discord.Audio
  17. {
  18. internal class DiscordVoiceAPIClient : IDisposable
  19. {
  20. public const int MaxBitrate = 128 * 1024;
  21. public const string Mode = "xsalsa20_poly1305";
  22. public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } }
  23. private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>();
  24. public event Func<VoiceOpCode, Task> SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } }
  25. private readonly AsyncEvent<Func<VoiceOpCode, Task>> _sentGatewayMessageEvent = new AsyncEvent<Func<VoiceOpCode, Task>>();
  26. public event Func<Task> SentDiscovery { add { _sentDiscoveryEvent.Add(value); } remove { _sentDiscoveryEvent.Remove(value); } }
  27. private readonly AsyncEvent<Func<Task>> _sentDiscoveryEvent = new AsyncEvent<Func<Task>>();
  28. public event Func<int, Task> SentData { add { _sentDataEvent.Add(value); } remove { _sentDataEvent.Remove(value); } }
  29. private readonly AsyncEvent<Func<int, Task>> _sentDataEvent = new AsyncEvent<Func<int, Task>>();
  30. public event Func<VoiceOpCode, object, Task> ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } }
  31. private readonly AsyncEvent<Func<VoiceOpCode, object, Task>> _receivedEvent = new AsyncEvent<Func<VoiceOpCode, object, Task>>();
  32. public event Func<byte[], Task> ReceivedPacket { add { _receivedPacketEvent.Add(value); } remove { _receivedPacketEvent.Remove(value); } }
  33. private readonly AsyncEvent<Func<byte[], Task>> _receivedPacketEvent = new AsyncEvent<Func<byte[], Task>>();
  34. public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
  35. private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
  36. private readonly JsonSerializer _serializer;
  37. private readonly SemaphoreSlim _connectionLock;
  38. private readonly IUdpSocket _udp;
  39. private CancellationTokenSource _connectCancelToken;
  40. private bool _isDisposed;
  41. private ulong _nextKeepalive;
  42. public ulong GuildId { get; }
  43. internal IWebSocketClient WebSocketClient { get; }
  44. public ConnectionState ConnectionState { get; private set; }
  45. public ushort UdpPort => _udp.Port;
  46. internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, JsonSerializer serializer = null)
  47. {
  48. GuildId = guildId;
  49. _connectionLock = new SemaphoreSlim(1, 1);
  50. _udp = udpSocketProvider();
  51. _udp.ReceivedDatagram += async (data, index, count) =>
  52. {
  53. if (index != 0 || count != data.Length)
  54. {
  55. var newData = new byte[count];
  56. Buffer.BlockCopy(data, index, newData, 0, count);
  57. data = newData;
  58. }
  59. await _receivedPacketEvent.InvokeAsync(data).ConfigureAwait(false);
  60. };
  61. WebSocketClient = webSocketProvider();
  62. //_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); //(Causes issues in .Net 4.6+)
  63. WebSocketClient.BinaryMessage += async (data, index, count) =>
  64. {
  65. using (var compressed = new MemoryStream(data, index + 2, count - 2))
  66. using (var decompressed = new MemoryStream())
  67. {
  68. using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
  69. zlib.CopyTo(decompressed);
  70. decompressed.Position = 0;
  71. using (var reader = new StreamReader(decompressed))
  72. {
  73. var msg = JsonConvert.DeserializeObject<SocketFrame>(reader.ReadToEnd());
  74. await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false);
  75. }
  76. }
  77. };
  78. WebSocketClient.TextMessage += async text =>
  79. {
  80. var msg = JsonConvert.DeserializeObject<SocketFrame>(text);
  81. await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false);
  82. };
  83. WebSocketClient.Closed += async ex =>
  84. {
  85. await DisconnectAsync().ConfigureAwait(false);
  86. await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
  87. };
  88. _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
  89. }
  90. private void Dispose(bool disposing)
  91. {
  92. if (!_isDisposed)
  93. {
  94. if (disposing)
  95. {
  96. _connectCancelToken?.Dispose();
  97. _udp?.Dispose();
  98. WebSocketClient?.Dispose();
  99. _connectionLock?.Dispose();
  100. }
  101. _isDisposed = true;
  102. }
  103. }
  104. public void Dispose() => Dispose(true);
  105. public async Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null)
  106. {
  107. byte[] bytes = null;
  108. payload = new SocketFrame { Operation = (int)opCode, Payload = payload };
  109. if (payload != null)
  110. bytes = Encoding.UTF8.GetBytes(SerializeJson(payload));
  111. await WebSocketClient.SendAsync(bytes, 0, bytes.Length, true).ConfigureAwait(false);
  112. await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false);
  113. }
  114. public async Task SendAsync(byte[] data, int offset, int bytes)
  115. {
  116. await _udp.SendAsync(data, offset, bytes).ConfigureAwait(false);
  117. await _sentDataEvent.InvokeAsync(bytes).ConfigureAwait(false);
  118. }
  119. //WebSocket
  120. public async Task SendHeartbeatAsync(RequestOptions options = null)
  121. {
  122. await SendAsync(VoiceOpCode.Heartbeat, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), options: options).ConfigureAwait(false);
  123. }
  124. public async Task SendIdentityAsync(ulong userId, string sessionId, string token)
  125. {
  126. await SendAsync(VoiceOpCode.Identify, new IdentifyParams
  127. {
  128. GuildId = GuildId,
  129. UserId = userId,
  130. SessionId = sessionId,
  131. Token = token
  132. }).ConfigureAwait(false);
  133. }
  134. public async Task SendSelectProtocol(string externalIp, int externalPort)
  135. {
  136. await SendAsync(VoiceOpCode.SelectProtocol, new SelectProtocolParams
  137. {
  138. Protocol = "udp",
  139. Data = new UdpProtocolInfo
  140. {
  141. Address = externalIp,
  142. Port = externalPort,
  143. Mode = Mode
  144. }
  145. }).ConfigureAwait(false);
  146. }
  147. public async Task SendSetSpeaking(bool value)
  148. {
  149. await SendAsync(VoiceOpCode.Speaking, new SpeakingParams
  150. {
  151. IsSpeaking = value,
  152. Delay = 0
  153. }).ConfigureAwait(false);
  154. }
  155. public async Task ConnectAsync(string url)
  156. {
  157. await _connectionLock.WaitAsync().ConfigureAwait(false);
  158. try
  159. {
  160. await ConnectInternalAsync(url).ConfigureAwait(false);
  161. }
  162. finally { _connectionLock.Release(); }
  163. }
  164. private async Task ConnectInternalAsync(string url)
  165. {
  166. ConnectionState = ConnectionState.Connecting;
  167. try
  168. {
  169. _connectCancelToken?.Dispose();
  170. _connectCancelToken = new CancellationTokenSource();
  171. var cancelToken = _connectCancelToken.Token;
  172. WebSocketClient.SetCancelToken(cancelToken);
  173. await WebSocketClient.ConnectAsync(url).ConfigureAwait(false);
  174. _udp.SetCancelToken(cancelToken);
  175. await _udp.StartAsync().ConfigureAwait(false);
  176. ConnectionState = ConnectionState.Connected;
  177. }
  178. catch
  179. {
  180. await DisconnectInternalAsync().ConfigureAwait(false);
  181. throw;
  182. }
  183. }
  184. public async Task DisconnectAsync()
  185. {
  186. await _connectionLock.WaitAsync().ConfigureAwait(false);
  187. try
  188. {
  189. await DisconnectInternalAsync().ConfigureAwait(false);
  190. }
  191. finally { _connectionLock.Release(); }
  192. }
  193. private async Task DisconnectInternalAsync()
  194. {
  195. if (ConnectionState == ConnectionState.Disconnected) return;
  196. ConnectionState = ConnectionState.Disconnecting;
  197. try { _connectCancelToken?.Cancel(false); }
  198. catch { }
  199. //Wait for tasks to complete
  200. await _udp.StopAsync().ConfigureAwait(false);
  201. await WebSocketClient.DisconnectAsync().ConfigureAwait(false);
  202. ConnectionState = ConnectionState.Disconnected;
  203. }
  204. //Udp
  205. public async Task SendDiscoveryAsync(uint ssrc)
  206. {
  207. var packet = new byte[70];
  208. packet[0] = (byte)(ssrc >> 24);
  209. packet[1] = (byte)(ssrc >> 16);
  210. packet[2] = (byte)(ssrc >> 8);
  211. packet[3] = (byte)(ssrc >> 0);
  212. await SendAsync(packet, 0, 70).ConfigureAwait(false);
  213. await _sentDiscoveryEvent.InvokeAsync().ConfigureAwait(false);
  214. }
  215. public async Task<ulong> SendKeepaliveAsync()
  216. {
  217. var value = _nextKeepalive++;
  218. var packet = new byte[8];
  219. packet[0] = (byte)(value >> 0);
  220. packet[1] = (byte)(value >> 8);
  221. packet[2] = (byte)(value >> 16);
  222. packet[3] = (byte)(value >> 24);
  223. packet[4] = (byte)(value >> 32);
  224. packet[5] = (byte)(value >> 40);
  225. packet[6] = (byte)(value >> 48);
  226. packet[7] = (byte)(value >> 56);
  227. await SendAsync(packet, 0, 8).ConfigureAwait(false);
  228. return value;
  229. }
  230. public void SetUdpEndpoint(string ip, int port)
  231. {
  232. _udp.SetDestination(ip, port);
  233. }
  234. //Helpers
  235. private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2);
  236. private string SerializeJson(object value)
  237. {
  238. var sb = new StringBuilder(256);
  239. using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture))
  240. using (JsonWriter writer = new JsonTextWriter(text))
  241. _serializer.Serialize(writer, value);
  242. return sb.ToString();
  243. }
  244. private T DeserializeJson<T>(Stream jsonStream)
  245. {
  246. using (TextReader text = new StreamReader(jsonStream))
  247. using (JsonReader reader = new JsonTextReader(text))
  248. return _serializer.Deserialize<T>(reader);
  249. }
  250. }
  251. }