diff --git a/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs index 03d985dba..d96ddbb4e 100644 --- a/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs @@ -1,5 +1,4 @@ -using Discord.API; -using Discord.API.Client; +using Discord.API.Client; using Discord.API.Client.VoiceSocket; using Discord.Audio; using Discord.Audio.Opus; @@ -20,150 +19,150 @@ using System.Threading.Tasks; namespace Discord.Net.WebSockets { - public partial class VoiceWebSocket : WebSocket - { - private const int MaxOpusSize = 4000; + public partial class VoiceWebSocket : WebSocket + { + private const int MaxOpusSize = 4000; private const string EncryptedMode = "xsalsa20_poly1305"; - private const string UnencryptedMode = "plain"; - - private readonly int _targetAudioBufferLength; - private readonly ConcurrentDictionary _decoders; - private readonly AudioClient _audioClient; + private const string UnencryptedMode = "plain"; + + private readonly int _targetAudioBufferLength; + private readonly ConcurrentDictionary _decoders; + private readonly AudioClient _audioClient; private readonly AudioServiceConfig _config; private Thread _sendThread, _receiveThread; private VoiceBuffer _sendBuffer; private OpusEncoder _encoder; - private uint _ssrc; - private ConcurrentDictionary _ssrcMapping; - private UdpClient _udp; - private IPEndPoint _endpoint; - private bool _isEncrypted; - private byte[] _secretKey, _encodingBuffer; - private ushort _sequence; - private string _encryptionMode; - private int _ping; + private uint _ssrc; + private ConcurrentDictionary _ssrcMapping; + private UdpClient _udp; + private IPEndPoint _endpoint; + private bool _isEncrypted; + private byte[] _secretKey, _encodingBuffer; + private ushort _sequence; + private string _encryptionMode; + private int _ping; public string Token { get; internal set; } public Server Server { get; internal set; } - public Channel Channel { get; internal set; } + public Channel Channel { get; internal set; } public int Ping => _ping; - internal VoiceBuffer OutputBuffer => _sendBuffer; + internal VoiceBuffer OutputBuffer => _sendBuffer; - internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, JsonSerializer serializer, Logger logger) - : base(client, serializer, logger) - { + internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, JsonSerializer serializer, Logger logger) + : base(client, serializer, logger) + { _audioClient = audioClient; _config = client.Audio().Config; _decoders = new ConcurrentDictionary(); - _targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames - _encodingBuffer = new byte[MaxOpusSize]; - _ssrcMapping = new ConcurrentDictionary(); - _encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.Audio); - _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); + _targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames + _encodingBuffer = new byte[MaxOpusSize]; + _ssrcMapping = new ConcurrentDictionary(); + _encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.Audio); + _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); } - - public Task Connect() + + public Task Connect() => BeginConnect(); - private async Task Reconnect() - { - try - { - var cancelToken = ParentCancelToken.Value; + private async Task Reconnect() + { + try + { + var cancelToken = ParentCancelToken.Value; await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); - while (!cancelToken.IsCancellationRequested) - { - try - { - await Connect().ConfigureAwait(false); - break; - } - catch (OperationCanceledException) { throw; } - catch (Exception ex) - { - Logger.Error("Reconnect failed", ex); - //Net is down? We can keep trying to reconnect until the user runs Disconnect() - await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); - } - } - } - catch (OperationCanceledException) { } - } - public Task Disconnect() => _taskManager.Stop(); - - protected override async Task Run() - { - _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); - - List tasks = new List(); - if ((_config.Mode & AudioMode.Outgoing) != 0) - { - _sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(CancelToken))); - _sendThread.IsBackground = true; + while (!cancelToken.IsCancellationRequested) + { + try + { + await Connect().ConfigureAwait(false); + break; + } + catch (OperationCanceledException) { throw; } + catch (Exception ex) + { + Logger.Error("Reconnect failed", ex); + //Net is down? We can keep trying to reconnect until the user runs Disconnect() + await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException) { } + } + public Task Disconnect() => _taskManager.Stop(true); + + protected override async Task Run() + { + _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); + + List tasks = new List(); + if ((_config.Mode & AudioMode.Outgoing) != 0) + { + _sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(CancelToken))); + _sendThread.IsBackground = true; _sendThread.Start(); - } + } /*if ((_config.Mode & AudioMode.Incoming) != 0) {*/ - _receiveThread = new Thread(new ThreadStart(() => ReceiveVoiceAsync(CancelToken))); - _receiveThread.IsBackground = true; - _receiveThread.Start(); + _receiveThread = new Thread(new ThreadStart(() => ReceiveVoiceAsync(CancelToken))); + _receiveThread.IsBackground = true; + _receiveThread.Start(); /*} else tasks.Add(Task.Run(() => ReceiveVoiceAsync(CancelToken)));*/ - - SendIdentify(); + + SendIdentify(); #if !DOTNET5_4 - tasks.Add(WatcherAsync()); + tasks.Add(WatcherAsync()); #endif tasks.AddRange(_engine.GetTasks(CancelToken)); tasks.Add(HeartbeatAsync(CancelToken)); await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); - } - protected override Task Cleanup() - { - if (_sendThread != null) - _sendThread.Join(); - if (_receiveThread != null) - _receiveThread.Join(); - _sendThread = null; - _receiveThread = null; - - OpusDecoder decoder; - foreach (var pair in _decoders) - { - if (_decoders.TryRemove(pair.Key, out decoder)) - decoder.Dispose(); + } + protected override Task Cleanup() + { + if (_sendThread != null) + _sendThread.Join(); + if (_receiveThread != null) + _receiveThread.Join(); + _sendThread = null; + _receiveThread = null; + + OpusDecoder decoder; + foreach (var pair in _decoders) + { + if (_decoders.TryRemove(pair.Key, out decoder)) + decoder.Dispose(); } - ClearPCMFrames(); - _udp = null; - - return base.Cleanup(); - } - - private void ReceiveVoiceAsync(CancellationToken cancelToken) - { - var closeTask = cancelToken.Wait(); - try - { - byte[] packet, decodingBuffer = null, nonce = null, result; - int packetLength, resultOffset, resultLength; - IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); - - if ((_config.Mode & AudioMode.Incoming) != 0) - { - decodingBuffer = new byte[MaxOpusSize]; - nonce = new byte[24]; - } - - while (!cancelToken.IsCancellationRequested) - { - Thread.Sleep(1); - if (_udp.Available > 0) - { + ClearPCMFrames(); + _udp = null; + + return base.Cleanup(); + } + + private void ReceiveVoiceAsync(CancellationToken cancelToken) + { + var closeTask = cancelToken.Wait(); + try + { + byte[] packet, decodingBuffer = null, nonce = null, result; + int packetLength, resultOffset, resultLength; + IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); + + if ((_config.Mode & AudioMode.Incoming) != 0) + { + decodingBuffer = new byte[MaxOpusSize]; + nonce = new byte[24]; + } + + while (!cancelToken.IsCancellationRequested) + { + Thread.Sleep(1); + if (_udp.Available > 0) + { #if !DOTNET5_4 - packet = _udp.Receive(ref endpoint); + packet = _udp.Receive(ref endpoint); #else //TODO: Is this really the only way to end a Receive call in DOTNET5_4? var receiveTask = _udp.ReceiveAsync(); @@ -174,316 +173,326 @@ namespace Discord.Net.WebSockets packet = udpPacket.Buffer; endpoint = udpPacket.RemoteEndPoint; #endif - packetLength = packet.Length; + packetLength = packet.Length; if (packetLength > 0 && endpoint.Equals(_endpoint)) - { - if (State != ConnectionState.Connected) - { - if (packetLength != 70) - return; - - string ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0'); - int port = packet[68] | packet[69] << 8; - - SendSelectProtocol(ip, port); - if ((_config.Mode & AudioMode.Incoming) == 0) - return; //We dont need this thread anymore - } - else - { - //Parse RTP Data - if (packetLength < 12) return; - if (packet[0] != 0x80) return; //Flags - if (packet[1] != 0x78) return; //Payload Type - - ushort sequenceNumber = (ushort)((packet[2] << 8) | - packet[3] << 0); - uint timestamp = (uint)((packet[4] << 24) | - (packet[5] << 16) | - (packet[6] << 8) | - (packet[7] << 0)); - uint ssrc = (uint)((packet[8] << 24) | - (packet[9] << 16) | - (packet[10] << 8) | - (packet[11] << 0)); - - //Decrypt - if (_isEncrypted) - { - if (packetLength < 28) //12 + 16 (RTP + Poly1305 MAC) - return; - - Buffer.BlockCopy(packet, 0, nonce, 0, 12); - int ret = SecretBox.Decrypt(packet, 12, packetLength - 12, decodingBuffer, nonce, _secretKey); - if (ret != 0) - continue; - result = decodingBuffer; - resultOffset = 0; - resultLength = packetLength - 28; - } - else //Plain - { - result = packet; - resultOffset = 12; - resultLength = packetLength - 12; - } + { + if (State != ConnectionState.Connected) + { + if (packetLength != 70) + return; + + string ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0'); + int port = packet[68] | packet[69] << 8; + + SendSelectProtocol(ip, port); + if ((_config.Mode & AudioMode.Incoming) == 0) + return; //We dont need this thread anymore + } + else + { + //Parse RTP Data + if (packetLength < 12) return; + if (packet[0] != 0x80) return; //Flags + if (packet[1] != 0x78) return; //Payload Type + + ushort sequenceNumber = (ushort)((packet[2] << 8) | + packet[3] << 0); + uint timestamp = (uint)((packet[4] << 24) | + (packet[5] << 16) | + (packet[6] << 8) | + (packet[7] << 0)); + uint ssrc = (uint)((packet[8] << 24) | + (packet[9] << 16) | + (packet[10] << 8) | + (packet[11] << 0)); + + //Decrypt + if (_isEncrypted) + { + if (packetLength < 28) //12 + 16 (RTP + Poly1305 MAC) + return; + + Buffer.BlockCopy(packet, 0, nonce, 0, 12); + int ret = SecretBox.Decrypt(packet, 12, packetLength - 12, decodingBuffer, nonce, _secretKey); + if (ret != 0) + continue; + result = decodingBuffer; + resultOffset = 0; + resultLength = packetLength - 28; + } + else //Plain + { + result = packet; + resultOffset = 12; + resultLength = packetLength - 12; + } /*if (_logLevel >= LogMessageSeverity.Debug) RaiseOnLog(LogMessageSeverity.Debug, $"Received {buffer.Length - 12} bytes.");*/ ulong userId; - if (_ssrcMapping.TryGetValue(ssrc, out userId)) - RaiseOnPacket(userId, Channel.Id, result, resultOffset, resultLength); - } - } - } - } - } + if (_ssrcMapping.TryGetValue(ssrc, out userId)) + RaiseOnPacket(userId, Channel.Id, result, resultOffset, resultLength); + } + } + } + } + } catch (OperationCanceledException) { } - catch (InvalidOperationException) { } //Includes ObjectDisposedException - } - - private void SendVoiceAsync(CancellationToken cancelToken) - { - try - { - while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected) - Thread.Sleep(1); - - if (cancelToken.IsCancellationRequested) - return; - - byte[] frame = new byte[_encoder.FrameSize]; - byte[] encodedFrame = new byte[MaxOpusSize]; - byte[] voicePacket, pingPacket, nonce = null; - uint timestamp = 0; - double nextTicks = 0.0, nextPingTicks = 0.0; - long ticksPerSeconds = Stopwatch.Frequency; + catch (InvalidOperationException) { } //Includes ObjectDisposedException + } + + private void SendVoiceAsync(CancellationToken cancelToken) + { + try + { + while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected) + Thread.Sleep(1); + + if (cancelToken.IsCancellationRequested) + return; + + byte[] frame = new byte[_encoder.FrameSize]; + byte[] encodedFrame = new byte[MaxOpusSize]; + byte[] voicePacket, pingPacket, nonce = null; + uint timestamp = 0; + double nextTicks = 0.0, nextPingTicks = 0.0; + long ticksPerSeconds = Stopwatch.Frequency; double ticksPerMillisecond = Stopwatch.Frequency / 1000.0; - double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength; - double spinLockThreshold = 3 * ticksPerMillisecond; - uint samplesPerFrame = (uint)_encoder.SamplesPerFrame; - Stopwatch sw = Stopwatch.StartNew(); - - if (_isEncrypted) - { - nonce = new byte[24]; - voicePacket = new byte[MaxOpusSize + 12 + 16]; - } - else - voicePacket = new byte[MaxOpusSize + 12]; - - pingPacket = new byte[8]; - pingPacket[0] = 0x80; //Flags; - pingPacket[1] = 0xC9; //Payload Type - pingPacket[2] = 0x00; //Length - pingPacket[3] = 0x01; //Length (1*8 bytes) - pingPacket[4] = (byte)((_ssrc >> 24) & 0xFF); - pingPacket[5] = (byte)((_ssrc >> 16) & 0xFF); - pingPacket[6] = (byte)((_ssrc >> 8) & 0xFF); - pingPacket[7] = (byte)((_ssrc >> 0) & 0xFF); - if (_isEncrypted) - { - Buffer.BlockCopy(pingPacket, 0, nonce, 0, 8); - int ret = SecretBox.Encrypt(pingPacket, 8, encodedFrame, 0, nonce, _secretKey); - if (ret != 0) - throw new InvalidOperationException("Failed to encrypt ping packet"); - pingPacket = new byte[pingPacket.Length + 16]; - Buffer.BlockCopy(encodedFrame, 0, pingPacket, 0, pingPacket.Length); - Array.Clear(nonce, 0, nonce.Length); + double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength; + double spinLockThreshold = 3 * ticksPerMillisecond; + uint samplesPerFrame = (uint)_encoder.SamplesPerFrame; + Stopwatch sw = Stopwatch.StartNew(); + + if (_isEncrypted) + { + nonce = new byte[24]; + voicePacket = new byte[MaxOpusSize + 12 + 16]; + } + else + voicePacket = new byte[MaxOpusSize + 12]; + + pingPacket = new byte[8]; + pingPacket[0] = 0x80; //Flags; + pingPacket[1] = 0xC9; //Payload Type + pingPacket[2] = 0x00; //Length + pingPacket[3] = 0x01; //Length (1*8 bytes) + pingPacket[4] = (byte)((_ssrc >> 24) & 0xFF); + pingPacket[5] = (byte)((_ssrc >> 16) & 0xFF); + pingPacket[6] = (byte)((_ssrc >> 8) & 0xFF); + pingPacket[7] = (byte)((_ssrc >> 0) & 0xFF); + if (_isEncrypted) + { + Buffer.BlockCopy(pingPacket, 0, nonce, 0, 8); + int ret = SecretBox.Encrypt(pingPacket, 8, encodedFrame, 0, nonce, _secretKey); + if (ret != 0) + throw new InvalidOperationException("Failed to encrypt ping packet"); + pingPacket = new byte[pingPacket.Length + 16]; + Buffer.BlockCopy(encodedFrame, 0, pingPacket, 0, pingPacket.Length); + Array.Clear(nonce, 0, nonce.Length); } - int rtpPacketLength = 0; - voicePacket[0] = 0x80; //Flags; - voicePacket[1] = 0x78; //Payload Type - voicePacket[8] = (byte)((_ssrc >> 24) & 0xFF); - voicePacket[9] = (byte)((_ssrc >> 16) & 0xFF); - voicePacket[10] = (byte)((_ssrc >> 8) & 0xFF); - voicePacket[11] = (byte)((_ssrc >> 0) & 0xFF); - - if (_isEncrypted) - Buffer.BlockCopy(voicePacket, 0, nonce, 0, 12); - - bool hasFrame = false; - while (!cancelToken.IsCancellationRequested) - { - if (!hasFrame && _sendBuffer.Pop(frame)) - { - ushort sequence = unchecked(_sequence++); - voicePacket[2] = (byte)((sequence >> 8) & 0xFF); - voicePacket[3] = (byte)((sequence >> 0) & 0xFF); - voicePacket[4] = (byte)((timestamp >> 24) & 0xFF); - voicePacket[5] = (byte)((timestamp >> 16) & 0xFF); - voicePacket[6] = (byte)((timestamp >> 8) & 0xFF); - voicePacket[7] = (byte)((timestamp >> 0) & 0xFF); - - //Encode - int encodedLength = _encoder.EncodeFrame(frame, 0, encodedFrame); - - //Encrypt - if (_isEncrypted) - { - Buffer.BlockCopy(voicePacket, 2, nonce, 2, 6); //Update nonce - int ret = SecretBox.Encrypt(encodedFrame, encodedLength, voicePacket, 12, nonce, _secretKey); - if (ret != 0) - continue; - rtpPacketLength = encodedLength + 12 + 16; - } - else - { - Buffer.BlockCopy(encodedFrame, 0, voicePacket, 12, encodedLength); - rtpPacketLength = encodedLength + 12; - } - - timestamp = unchecked(timestamp + samplesPerFrame); - hasFrame = true; - } + int rtpPacketLength = 0; + voicePacket[0] = 0x80; //Flags; + voicePacket[1] = 0x78; //Payload Type + voicePacket[8] = (byte)((_ssrc >> 24) & 0xFF); + voicePacket[9] = (byte)((_ssrc >> 16) & 0xFF); + voicePacket[10] = (byte)((_ssrc >> 8) & 0xFF); + voicePacket[11] = (byte)((_ssrc >> 0) & 0xFF); + + if (_isEncrypted) + Buffer.BlockCopy(voicePacket, 0, nonce, 0, 12); + + bool hasFrame = false; + while (!cancelToken.IsCancellationRequested) + { + if (!hasFrame && _sendBuffer.Pop(frame)) + { + ushort sequence = unchecked(_sequence++); + voicePacket[2] = (byte)((sequence >> 8) & 0xFF); + voicePacket[3] = (byte)((sequence >> 0) & 0xFF); + voicePacket[4] = (byte)((timestamp >> 24) & 0xFF); + voicePacket[5] = (byte)((timestamp >> 16) & 0xFF); + voicePacket[6] = (byte)((timestamp >> 8) & 0xFF); + voicePacket[7] = (byte)((timestamp >> 0) & 0xFF); + + //Encode + int encodedLength = _encoder.EncodeFrame(frame, 0, encodedFrame); + + //Encrypt + if (_isEncrypted) + { + Buffer.BlockCopy(voicePacket, 2, nonce, 2, 6); //Update nonce + int ret = SecretBox.Encrypt(encodedFrame, encodedLength, voicePacket, 12, nonce, _secretKey); + if (ret != 0) + continue; + rtpPacketLength = encodedLength + 12 + 16; + } + else + { + Buffer.BlockCopy(encodedFrame, 0, voicePacket, 12, encodedLength); + rtpPacketLength = encodedLength + 12; + } + + timestamp = unchecked(timestamp + samplesPerFrame); + hasFrame = true; + } long currentTicks = sw.ElapsedTicks; - double ticksToNextFrame = nextTicks - currentTicks; - if (ticksToNextFrame <= 0.0) - { - if (hasFrame) - { - try - { - _udp.Send(voicePacket, rtpPacketLength); - } - catch (SocketException ex) - { - Logger.Error("Failed to send UDP packet.", ex); - } - hasFrame = false; - } - nextTicks += ticksPerFrame; - - //Is it time to send out another ping? - if (currentTicks > nextPingTicks) - { - _udp.Send(pingPacket, pingPacket.Length); - nextPingTicks = currentTicks + 5 * ticksPerSeconds; + double ticksToNextFrame = nextTicks - currentTicks; + if (ticksToNextFrame <= 0.0) + { + if (hasFrame) + { + try + { + _udp.Send(voicePacket, rtpPacketLength); + } + catch (SocketException ex) + { + Logger.Error("Failed to send UDP packet.", ex); + } + hasFrame = false; + } + nextTicks += ticksPerFrame; + + //Is it time to send out another ping? + if (currentTicks > nextPingTicks) + { + _udp.Send(pingPacket, pingPacket.Length); + nextPingTicks = currentTicks + 5 * ticksPerSeconds; } } - else - { - if (hasFrame) - { - int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); - if (time > 0) - Thread.Sleep(time); - } - else - Thread.Sleep(1); //Give as much time to the encrypter as possible - } - } - } - catch (OperationCanceledException) { } - catch (InvalidOperationException) { } //Includes ObjectDisposedException - } + else + { + if (hasFrame) + { + int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); + if (time > 0) + Thread.Sleep(time); + } + else + Thread.Sleep(1); //Give as much time to the encrypter as possible + } + } + } + catch (OperationCanceledException) { } + catch (InvalidOperationException) { } //Includes ObjectDisposedException + } #if !DOTNET5_4 - //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken - private Task WatcherAsync() + //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken + private Task WatcherAsync() => CancelToken.Wait().ContinueWith(_ => _udp.Close()); #endif - protected override async Task ProcessMessage(string json) - { - await base.ProcessMessage(json).ConfigureAwait(false); - var msg = JsonConvert.DeserializeObject(json); - var opCode = (OpCodes)msg.Operation; + protected override async Task ProcessMessage(string json) + { + await base.ProcessMessage(json).ConfigureAwait(false); + var msg = JsonConvert.DeserializeObject(json); + var opCode = (OpCodes)msg.Operation; switch (opCode) - { - case OpCodes.Ready: - { - if (State != ConnectionState.Connected) - { - var payload = (msg.Payload as JToken).ToObject(_serializer); - _heartbeatInterval = payload.HeartbeatInterval; - _ssrc = payload.SSRC; + { + case OpCodes.Ready: + { + if (State != ConnectionState.Connected) + { + var payload = (msg.Payload as JToken).ToObject(_serializer); + _heartbeatInterval = payload.HeartbeatInterval; + _ssrc = payload.SSRC; var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(); _endpoint = new IPEndPoint(address, payload.Port); - - if (_config.EnableEncryption) - { - if (payload.Modes.Contains(EncryptedMode)) - { - _encryptionMode = EncryptedMode; - _isEncrypted = true; - } - else - throw new InvalidOperationException("Unexpected encryption format."); - } - else - { - _encryptionMode = UnencryptedMode; - _isEncrypted = false; + + if (_config.EnableEncryption) + { + if (payload.Modes.Contains(EncryptedMode)) + { + _encryptionMode = EncryptedMode; + _isEncrypted = true; + } + else + throw new InvalidOperationException("Unexpected encryption format."); + } + else + { + _encryptionMode = UnencryptedMode; + _isEncrypted = false; } _udp.Connect(_endpoint); - _sequence = 0;// (ushort)_rand.Next(0, ushort.MaxValue); - //No thread issue here because SendAsync doesn't start until _isReady is true - byte[] packet = new byte[70]; - packet[0] = (byte)((_ssrc >> 24) & 0xFF); - packet[1] = (byte)((_ssrc >> 16) & 0xFF); - packet[2] = (byte)((_ssrc >> 8) & 0xFF); - packet[3] = (byte)((_ssrc >> 0) & 0xFF); - await _udp.SendAsync(packet, 70).ConfigureAwait(false); - } - } - break; - case OpCodes.Heartbeat: - { - long time = EpochTime.GetMilliseconds(); - var payload = (long)msg.Payload; - _ping = (int)(payload - time); - //TODO: Use this to estimate latency - } - break; - case OpCodes.SessionDescription: - { - var payload = (msg.Payload as JToken).ToObject(_serializer); - _secretKey = payload.SecretKey; - SendSetSpeaking(true); - EndConnect(); - } - break; - case OpCodes.Speaking: - { - var payload = (msg.Payload as JToken).ToObject(_serializer); - RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); - } - break; - default: + _sequence = 0;// (ushort)_rand.Next(0, ushort.MaxValue); + //No thread issue here because SendAsync doesn't start until _isReady is true + byte[] packet = new byte[70]; + packet[0] = (byte)((_ssrc >> 24) & 0xFF); + packet[1] = (byte)((_ssrc >> 16) & 0xFF); + packet[2] = (byte)((_ssrc >> 8) & 0xFF); + packet[3] = (byte)((_ssrc >> 0) & 0xFF); + await _udp.SendAsync(packet, 70).ConfigureAwait(false); + } + } + break; + case OpCodes.Heartbeat: + { + long time = EpochTime.GetMilliseconds(); + var payload = (long)msg.Payload; + _ping = (int)(payload - time); + //TODO: Use this to estimate latency + } + break; + case OpCodes.SessionDescription: + { + var payload = (msg.Payload as JToken).ToObject(_serializer); + _secretKey = payload.SecretKey; + SendSetSpeaking(true); + EndConnect(); + } + break; + case OpCodes.Speaking: + { + var payload = (msg.Payload as JToken).ToObject(_serializer); + RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); + } + break; + default: Logger.Warning($"Unknown Opcode: {opCode}"); - break; - } - } - - public void SendPCMFrames(byte[] data, int bytes) - { - _sendBuffer.Push(data, bytes, CancelToken); - } - public void ClearPCMFrames() - { - _sendBuffer.Clear(CancelToken); - } - - public void WaitForQueue() - { - _sendBuffer.Wait(CancelToken); - } + break; + } + } + + public void SendPCMFrames(byte[] data, int bytes) + { + _sendBuffer.Push(data, bytes, CancelToken); + } + public void ClearPCMFrames() + { + _sendBuffer.Clear(CancelToken); + } + + public void WaitForQueue() + { + _sendBuffer.Wait(CancelToken); + } public override void SendHeartbeat() => QueueMessage(new HeartbeatCommand()); public void SendIdentify() - => QueueMessage(new IdentifyCommand { GuildId = Server.Id, UserId = _client.CurrentUser.Id, - SessionId = _client.SessionId, Token = Token }); + => QueueMessage(new IdentifyCommand + { + GuildId = Server.Id, + UserId = _client.CurrentUser.Id, + SessionId = _client.SessionId, + Token = Token + }); public void SendSelectProtocol(string externalAddress, int externalPort) - => QueueMessage(new SelectProtocolCommand { Protocol = "udp", ExternalAddress = externalAddress, - ExternalPort = externalPort, EncryptionMode = _encryptionMode }); + => QueueMessage(new SelectProtocolCommand + { + Protocol = "udp", + ExternalAddress = externalAddress, + ExternalPort = externalPort, + EncryptionMode = _encryptionMode + }); public void SendSetSpeaking(bool value) => QueueMessage(new SetSpeakingCommand { IsSpeaking = value, Delay = 0 }); - } + } } \ No newline at end of file