diff --git a/src/Discord.Net.Commands/DiscordBotClient.Events.cs b/src/Discord.Net.Commands/DiscordBotClient.Events.cs index 1930d6240..93c0ea44d 100644 --- a/src/Discord.Net.Commands/DiscordBotClient.Events.cs +++ b/src/Discord.Net.Commands/DiscordBotClient.Events.cs @@ -21,11 +21,11 @@ namespace Discord public CommandEventArgs(Message message, Command command, string commandText, int? permissions, string[] args) { - this.Message = message; - this.Command = command; - this.CommandText = commandText; - this.Permissions = permissions; - this.Args = args; + Message = message; + Command = command; + CommandText = commandText; + Permissions = permissions; + Args = args; } } public class CommandErrorEventArgs : CommandEventArgs @@ -35,7 +35,7 @@ namespace Discord public CommandErrorEventArgs(CommandEventArgs baseArgs, Exception ex) : base(baseArgs.Message, baseArgs.Command, baseArgs.CommandText, baseArgs.Permissions, baseArgs.Args) { - this.Exception = ex; + Exception = ex; } } public partial class DiscordBotClient : DiscordClient diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 3b1eaccd7..388383b57 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -103,6 +103,9 @@ Audio\Opus.cs + + Audio\OpusDecoder.cs + Audio\OpusEncoder.cs diff --git a/src/Discord.Net/Audio/Opus.cs b/src/Discord.Net/Audio/Opus.cs index 021165cfd..20f65be06 100644 --- a/src/Discord.Net/Audio/Opus.cs +++ b/src/Discord.Net/Audio/Opus.cs @@ -3,21 +3,21 @@ using System.Runtime.InteropServices; namespace Discord.Audio { - internal static unsafe class Opus + internal unsafe static class Opus { [DllImport("lib/opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out Error error); [DllImport("lib/opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)] public static extern void DestroyEncoder(IntPtr encoder); [DllImport("lib/opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)] - public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte* data, int max_data_bytes); + public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte[] data, int max_data_bytes); - /*[DllImport("lib/opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr CreateDecoder(int Fs, int channels, out Errors error); + [DllImport("lib/opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateDecoder(int Fs, int channels, out Error error); [DllImport("lib/opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)] public static extern void DestroyDecoder(IntPtr decoder); [DllImport("lib/opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)] - public static extern int Decode(IntPtr st, byte[] data, int len, IntPtr pcm, int frame_size, int decode_fec);*/ + public static extern int Decode(IntPtr st, byte* data, int len, byte[] pcm, int frame_size, int decode_fec); [DllImport("lib/opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)] public static extern int EncoderCtl(IntPtr st, Ctl request, int value); diff --git a/src/Discord.Net/Audio/OpusDecoder.cs b/src/Discord.Net/Audio/OpusDecoder.cs new file mode 100644 index 000000000..e6c9cd400 --- /dev/null +++ b/src/Discord.Net/Audio/OpusDecoder.cs @@ -0,0 +1,105 @@ +using System; + +namespace Discord.Audio +{ + /// Opus codec wrapper. + internal class OpusDecoder : IDisposable + { + private readonly IntPtr _ptr; + + /// Gets the bit rate of the encoder. + public const int BitRate = 16; + /// Gets the input sampling rate of the encoder. + public int InputSamplingRate { get; private set; } + /// Gets the number of channels of the encoder. + public int InputChannels { get; private set; } + /// Gets the milliseconds per frame. + public int FrameLength { get; private set; } + /// Gets the number of samples per frame. + public int SamplesPerFrame { get; private set; } + /// Gets the bytes per sample. + public int SampleSize { get; private set; } + /// Gets the bytes per frame. + public int FrameSize { get; private set; } + + /// Creates a new Opus encoder. + /// Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000. + /// Number of channels (1 or 2) in input signal. + /// Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60 + /// Coding mode. + /// A new OpusEncoder + public OpusDecoder(int samplingRate, int channels, int frameLength) + { + if (samplingRate != 8000 && samplingRate != 12000 && + samplingRate != 16000 && samplingRate != 24000 && + samplingRate != 48000) + throw new ArgumentOutOfRangeException(nameof(samplingRate)); + if (channels != 1 && channels != 2) + throw new ArgumentOutOfRangeException(nameof(channels)); + + InputSamplingRate = samplingRate; + InputChannels = channels; + FrameLength = frameLength; + SampleSize = (BitRate / 8) * channels; + SamplesPerFrame = samplingRate / 1000 * FrameLength; + FrameSize = SamplesPerFrame * SampleSize; + + Opus.Error error; + _ptr = Opus.CreateDecoder(samplingRate, channels, out error); + if (error != Opus.Error.OK) + throw new InvalidOperationException($"Error occured while creating decoder: {error}"); + + SetForwardErrorCorrection(true); + } + + /// Produces Opus encoded audio from PCM samples. + /// PCM samples to encode. + /// Offset of the frame in pcmSamples. + /// Buffer to store the encoded frame. + /// Length of the frame contained in outputBuffer. + public unsafe int DecodeFrame(byte[] input, int inputOffset, byte[] output) + { + if (disposed) + throw new ObjectDisposedException(nameof(OpusDecoder)); + + int result = 0; + fixed (byte* inPtr = input) + result = Opus.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length); + + if (result < 0) + throw new Exception("Decoding failed: " + ((Opus.Error)result).ToString()); + return result; + } + + /// Gets or sets whether Forward Error Correction is enabled. + public void SetForwardErrorCorrection(bool value) + { + if (disposed) + throw new ObjectDisposedException(nameof(OpusDecoder)); + + var result = Opus.EncoderCtl(_ptr, Opus.Ctl.SetInbandFECRequest, value ? 1 : 0); + if (result < 0) + throw new Exception("Decoder error: " + ((Opus.Error)result).ToString()); + } + + #region IDisposable + private bool disposed; + public void Dispose() + { + if (disposed) + return; + + GC.SuppressFinalize(this); + + if (_ptr != IntPtr.Zero) + Opus.DestroyEncoder(_ptr); + + disposed = true; + } + ~OpusDecoder() + { + Dispose(); + } + #endregion + } +} \ No newline at end of file diff --git a/src/Discord.Net/Audio/OpusEncoder.cs b/src/Discord.Net/Audio/OpusEncoder.cs index 56f48e3f8..c5e033a2b 100644 --- a/src/Discord.Net/Audio/OpusEncoder.cs +++ b/src/Discord.Net/Audio/OpusEncoder.cs @@ -5,7 +5,7 @@ namespace Discord.Audio /// Opus codec wrapper. internal class OpusEncoder : IDisposable { - private readonly IntPtr _encoderPtr; + private readonly IntPtr _ptr; /// Gets the bit rate of the encoder. public const int BitRate = 16; @@ -48,7 +48,7 @@ namespace Discord.Audio FrameSize = SamplesPerFrame * SampleSize; Opus.Error error; - _encoderPtr = Opus.CreateEncoder(samplingRate, channels, (int)application, out error); + _ptr = Opus.CreateEncoder(samplingRate, channels, (int)application, out error); if (error != Opus.Error.OK) throw new InvalidOperationException($"Error occured while creating encoder: {error}"); @@ -56,19 +56,18 @@ namespace Discord.Audio } /// Produces Opus encoded audio from PCM samples. - /// PCM samples to encode. - /// Offset of the frame in pcmSamples. - /// Buffer to store the encoded frame. + /// PCM samples to encode. + /// Offset of the frame in pcmSamples. + /// Buffer to store the encoded frame. /// Length of the frame contained in outputBuffer. - public unsafe int EncodeFrame(byte[] pcmSamples, int offset, byte[] outputBuffer) + public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output) { if (disposed) - throw new ObjectDisposedException("OpusEncoder"); + throw new ObjectDisposedException(nameof(OpusEncoder)); int result = 0; - fixed (byte* inPtr = pcmSamples) - fixed (byte* outPtr = outputBuffer) - result = Opus.Encode(_encoderPtr, inPtr + offset, SamplesPerFrame, outPtr, outputBuffer.Length); + fixed (byte* inPtr = input) + result = Opus.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length); if (result < 0) throw new Exception("Encoding failed: " + ((Opus.Error)result).ToString()); @@ -79,9 +78,9 @@ namespace Discord.Audio public void SetForwardErrorCorrection(bool value) { if (disposed) - throw new ObjectDisposedException("OpusEncoder"); + throw new ObjectDisposedException(nameof(OpusEncoder)); - var result = Opus.EncoderCtl(_encoderPtr, Opus.Ctl.SetInbandFECRequest, value ? 1 : 0); + var result = Opus.EncoderCtl(_ptr, Opus.Ctl.SetInbandFECRequest, value ? 1 : 0); if (result < 0) throw new Exception("Encoder error: " + ((Opus.Error)result).ToString()); } @@ -95,8 +94,8 @@ namespace Discord.Audio GC.SuppressFinalize(this); - if (_encoderPtr != IntPtr.Zero) - Opus.DestroyEncoder(_encoderPtr); + if (_ptr != IntPtr.Zero) + Opus.DestroyEncoder(_ptr); disposed = true; } diff --git a/src/Discord.Net/Audio/Sodium.cs b/src/Discord.Net/Audio/Sodium.cs index 793eb61a9..ec7dde612 100644 --- a/src/Discord.Net/Audio/Sodium.cs +++ b/src/Discord.Net/Audio/Sodium.cs @@ -2,14 +2,25 @@ namespace Discord.Audio { - internal static class Sodium - { - [DllImport("lib/libsodium", EntryPoint = "crypto_stream_xor", CallingConvention = CallingConvention.Cdecl)] - private static extern int StreamXOR(byte[] output, byte[] msg, long msgLength, byte[] nonce, byte[] secret); + internal unsafe static class Sodium + { + [DllImport("lib/libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] + private static extern int SecretBoxEasy(byte* output, byte[] input, long inputLength, byte[] nonce, byte[] secret); - public static int Encrypt(byte[] buffer, int inputLength, byte[] output, byte[] nonce, byte[] secret) + public static int Encrypt(byte[] input, long inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) { - return StreamXOR(output, buffer, inputLength, nonce, secret); + fixed (byte* outPtr = output) + return SecretBoxEasy(outPtr + outputOffset, input, inputLength, nonce, secret); + } + + + [DllImport("lib/libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)] + private static extern int SecretBoxOpenEasy(byte[] output, byte* input, long inputLength, byte[] nonce, byte[] secret); + + public static int Decrypt(byte[] input, int inputOffset, long inputLength, byte[] output, byte[] nonce, byte[] secret) + { + fixed (byte* inPtr = input) + return SecretBoxOpenEasy(output, inPtr + inputLength, inputLength, nonce, secret); } } } diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs index 888b1437d..eddd000ce 100644 --- a/src/Discord.Net/DiscordClient.Events.cs +++ b/src/Discord.Net/DiscordClient.Events.cs @@ -28,8 +28,8 @@ namespace Discord internal DisconnectedEventArgs(bool wasUnexpected, Exception error) { - this.WasUnexpected = wasUnexpected; - this.Error = error; + WasUnexpected = wasUnexpected; + Error = error; } } public sealed class LogMessageEventArgs : EventArgs @@ -40,9 +40,9 @@ namespace Discord internal LogMessageEventArgs(LogMessageSeverity severity, LogMessageSource source, string msg) { - this.Severity = severity; - this.Source = source; - this.Message = msg; + Severity = severity; + Source = source; + Message = msg; } } @@ -51,7 +51,7 @@ namespace Discord public Server Server { get; } public string ServerId => Server.Id; - internal ServerEventArgs(Server server) { this.Server = server; } + internal ServerEventArgs(Server server) { Server = server; } } public sealed class ChannelEventArgs : EventArgs { @@ -60,14 +60,14 @@ namespace Discord public Server Server => Channel.Server; public string ServerId => Channel.ServerId; - internal ChannelEventArgs(Channel channel) { this.Channel = channel; } + internal ChannelEventArgs(Channel channel) { Channel = channel; } } public sealed class UserEventArgs : EventArgs { public User User { get; } public string UserId => User.Id; - internal UserEventArgs(User user) { this.User = user; } + internal UserEventArgs(User user) { User = user; } } public sealed class MessageEventArgs : EventArgs { @@ -81,7 +81,7 @@ namespace Discord public User User => Member.User; public string UserId => Message.UserId; - internal MessageEventArgs(Message msg) { this.Message = msg; } + internal MessageEventArgs(Message msg) { Message = msg; } } public sealed class RoleEventArgs : EventArgs { @@ -90,7 +90,7 @@ namespace Discord public Server Server => Role.Server; public string ServerId => Role.ServerId; - internal RoleEventArgs(Role role) { this.Role = role; } + internal RoleEventArgs(Role role) { Role = role; } } public sealed class BanEventArgs : EventArgs { @@ -101,9 +101,9 @@ namespace Discord internal BanEventArgs(User user, string userId, Server server) { - this.User = user; - this.UserId = userId; - this.Server = server; + User = user; + UserId = userId; + Server = server; } } public sealed class MemberEventArgs : EventArgs @@ -114,7 +114,7 @@ namespace Discord public Server Server => Member.Server; public string ServerId => Member.ServerId; - internal MemberEventArgs(Member member) { this.Member = member; } + internal MemberEventArgs(Member member) { Member = member; } } public sealed class UserTypingEventArgs : EventArgs { @@ -127,8 +127,8 @@ namespace Discord internal UserTypingEventArgs(User user, Channel channel) { - this.User = user; - this.Channel = channel; + User = user; + Channel = channel; } } public sealed class UserIsSpeakingEventArgs : EventArgs @@ -144,10 +144,26 @@ namespace Discord internal UserIsSpeakingEventArgs(Member member, bool isSpeaking) { - this.Member = member; - this.IsSpeaking = isSpeaking; + Member = member; + IsSpeaking = isSpeaking; } } + public sealed class VoicePacketEventArgs + { + public string UserId { get; } + public string ChannelId { get; } + public byte[] Buffer { get; } + public int Offset { get; } + public int Count { get; } + + internal VoicePacketEventArgs(string userId, string channelId, byte[] buffer, int offset, int count) + { + UserId = userId; + Buffer = buffer; + Offset = offset; + Count = count; + } + } public partial class DiscordClient { @@ -340,5 +356,12 @@ namespace Discord if (VoiceDisconnected != null) RaiseEvent(nameof(UserIsSpeaking), () => VoiceDisconnected(this, e)); } + + public event EventHandler OnVoicePacket; + internal void RaiseOnVoicePacket(VoicePacketEventArgs e) + { + if (OnVoicePacket != null) + OnVoicePacket(this, e); + } } } diff --git a/src/Discord.Net/DiscordClient.Voice.cs b/src/Discord.Net/DiscordClient.Voice.cs index fd32c36f1..0bc054cbd 100644 --- a/src/Discord.Net/DiscordClient.Voice.cs +++ b/src/Discord.Net/DiscordClient.Voice.cs @@ -8,25 +8,26 @@ namespace Discord public partial class DiscordClient { public Task JoinVoiceServer(Channel channel) - => JoinVoiceServer(channel.ServerId, channel.Id); - public async Task JoinVoiceServer(string serverId, string channelId) + => JoinVoiceServer(channel?.Server, channel); + public Task JoinVoiceServer(string serverId, string channelId) + => JoinVoiceServer(_servers[serverId], _channels[channelId]); + public Task JoinVoiceServer(Server server, string channelId) + => JoinVoiceServer(server, _channels[channelId]); + private async Task JoinVoiceServer(Server server, Channel channel) { CheckReady(checkVoice: true); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (server == null) throw new ArgumentNullException(nameof(server)); + if (channel == null) throw new ArgumentNullException(nameof(channel)); await LeaveVoiceServer().ConfigureAwait(false); + _voiceSocket.SetChannel(server, channel); + _dataSocket.SendJoinVoice(server.Id, channel.Id); try { - await Task.Run(() => - { - _voiceSocket.SetServer(serverId); - _dataSocket.SendJoinVoice(serverId, channelId); - _voiceSocket.WaitForConnection(); - }) - .Timeout(_config.ConnectionTimeout) - .ConfigureAwait(false); + await Task.Run(() => _voiceSocket.WaitForConnection()) + .Timeout(_config.ConnectionTimeout) + .ConfigureAwait(false); } catch (TaskCanceledException) { @@ -39,11 +40,11 @@ namespace Discord if (_voiceSocket.State != WebSocketState.Disconnected) { - var serverId = _voiceSocket.CurrentVoiceServerId; - if (serverId != null) + var server = _voiceSocket.CurrentVoiceServer; + if (server != null) { await _voiceSocket.Disconnect().ConfigureAwait(false); - _dataSocket.SendLeaveVoice(serverId); + _dataSocket.SendLeaveVoice(server.Id); } } } diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index df8dc2027..3445dd761 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -45,10 +45,8 @@ namespace Discord /// Returns the current logged-in user. public User CurrentUser => _currentUser; private User _currentUser; - /// Returns the id of the server this user is currently connected to for voice. - public string CurrentVoiceServerId => _voiceSocket.CurrentVoiceServerId; /// Returns the server this user is currently connected to for voice. - public Server CurrentVoiceServer => _servers[_voiceSocket.CurrentVoiceServerId]; + public Server CurrentVoiceServer => _voiceSocket.CurrentVoiceServer; /// Returns the current connection state of this client. public DiscordClientState State => (DiscordClientState)_state; @@ -103,7 +101,7 @@ namespace Discord if (e.WasUnexpected) await _dataSocket.Reconnect(_token); }; - if (_config.EnableVoice) + if (_config.VoiceMode != DiscordVoiceMode.Disabled) { _voiceSocket = new VoiceWebSocket(this); _voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); @@ -125,7 +123,7 @@ namespace Discord { if (_voiceSocket.State == WebSocketState.Connected) { - var member = _members[e.UserId, _voiceSocket.CurrentVoiceServerId]; + var member = _members[e.UserId, _voiceSocket.CurrentVoiceServer.Id]; bool value = e.IsSpeaking; if (member.IsSpeaking != value) { @@ -147,14 +145,14 @@ namespace Discord _users = new Users(this, cacheLock); _dataSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.DataWebSocket, e.Message); - if (_config.EnableVoice) + if (_config.VoiceMode != DiscordVoiceMode.Disabled) _voiceSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.VoiceWebSocket, e.Message); if (_config.LogLevel >= LogMessageSeverity.Info) { _dataSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Connected"); _dataSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Disconnected"); //_dataSocket.ReceivedEvent += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, $"Received {e.Type}"); - if (_config.EnableVoice) + if (_config.VoiceMode != DiscordVoiceMode.Disabled) { _voiceSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Connected"); _voiceSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Disconnected"); @@ -535,28 +533,6 @@ namespace Discord } } break; - case "VOICE_STATE_UPDATE": - { - var data = e.Payload.ToObject(_serializer); - var member = _members[data.UserId, data.GuildId]; - /*if (_config.TrackActivity) - { - var user = _users[data.User.Id]; - if (user != null) - user.UpdateActivity(DateTime.UtcNow); - }*/ - if (member != null) - { - member.Update(data); - if (member.IsSpeaking) - { - member.IsSpeaking = false; - RaiseUserIsSpeaking(member, false); - } - RaiseUserVoiceStateUpdated(member); - } - } - break; case "TYPING_START": { var data = e.Payload.ToObject(_serializer); @@ -586,13 +562,35 @@ namespace Discord break; //Voice + case "VOICE_STATE_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var member = _members[data.UserId, data.GuildId]; + /*if (_config.TrackActivity) + { + var user = _users[data.User.Id]; + if (user != null) + user.UpdateActivity(DateTime.UtcNow); + }*/ + if (member != null) + { + member.Update(data); + if (member.IsSpeaking) + { + member.IsSpeaking = false; + RaiseUserIsSpeaking(member, false); + } + RaiseUserVoiceStateUpdated(member); + } + } + break; case "VOICE_SERVER_UPDATE": { var data = e.Payload.ToObject(_serializer); - if (data.GuildId == _voiceSocket.CurrentVoiceServerId) + if (data.GuildId == _voiceSocket.CurrentVoiceServer.Id) { var server = _servers[data.GuildId]; - if (_config.EnableVoice) + if (_config.VoiceMode != DiscordVoiceMode.Disabled) { _voiceSocket.Host = "wss://" + data.Endpoint.Split(':')[0]; await _voiceSocket.Login(_currentUserId, _dataSocket.SessionId, data.Token, _cancelToken).ConfigureAwait(false); @@ -770,7 +768,7 @@ namespace Discord _wasDisconnectUnexpected = false; await _dataSocket.Disconnect().ConfigureAwait(false); - if (_config.EnableVoice) + if (_config.VoiceMode != DiscordVoiceMode.Disabled) await _voiceSocket.Disconnect().ConfigureAwait(false); if (_config.UseMessageQueue) @@ -817,7 +815,7 @@ namespace Discord throw new InvalidOperationException("The client is connecting."); } - if (checkVoice && !_config.EnableVoice) + if (checkVoice && _config.VoiceMode == DiscordVoiceMode.Disabled) throw new InvalidOperationException("Voice is not enabled for this client."); } private void RaiseEvent(string name, Action action) diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index a483b8b2a..49006a990 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -2,6 +2,15 @@ namespace Discord { + [Flags] + public enum DiscordVoiceMode + { + Disabled = 0x00, + Incoming = 0x01, + Outgoing = 0x02, + Both = Outgoing | Incoming + } + public class DiscordClientConfig { /// Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. @@ -33,15 +42,19 @@ namespace Discord //Experimental Features #if !DNXCORE50 - /// (Experimental) Enables the voice websocket and UDP client. This option requires the opus .dll or .so be in the local lib/ folder. - public bool EnableVoice { get { return _enableVoice; } set { SetValue(ref _enableVoice, value); } } - private bool _enableVoice = false; + /// (Experimental) Enables the voice websocket and UDP client and specifies how it will be used. Any option other than Disabled requires the opus .dll or .so be in the local lib/ folder. + public DiscordVoiceMode VoiceMode { get { return _voiceMode; } set { SetValue(ref _voiceMode, value); } } + private DiscordVoiceMode _voiceMode = DiscordVoiceMode.Disabled; /// (Experimental) Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local lib/ folder. public bool EnableVoiceEncryption { get { return _enableVoiceEncryption; } set { SetValue(ref _enableVoiceEncryption, value); } } - private bool _enableVoiceEncryption = false; + private bool _enableVoiceEncryption = true; + /// (Experimental) Enables the client to be simultaneously connected to multiple channels at once (Discord still limits you to one channel per server). + public bool EnableVoiceMultiserver { get { return _enableVoiceMultiserver; } set { SetValue(ref _enableVoiceMultiserver, value); } } + private bool _enableVoiceMultiserver = false; #else - internal bool EnableVoice => false; + internal DiscordVoiceMode VoiceMode => DiscordVoiceMode.Disabled; internal bool EnableVoiceEncryption => false; + internal bool EnableVoiceMultiserver => false; #endif /// (Experimental) Enables or disables the internal message queue. This will allow SendMessage to return immediately and handle messages internally. Messages will set the IsQueued and HasFailed properties to show their progress. public bool UseMessageQueue { get { return _useMessageQueue; } set { SetValue(ref _useMessageQueue, value); } } diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index 22532c770..c9df8a4cc 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -17,6 +17,7 @@ namespace Discord private readonly DiscordClient _client; private ConcurrentDictionary _messages; + private ConcurrentDictionary _ssrcMapping; /// Returns the unique identifier for this channel. public string Id { get; } @@ -69,6 +70,8 @@ namespace Discord { Name = model.Name; Type = model.Type; + if (Type == ChannelTypes.Voice && _ssrcMapping == null) + _ssrcMapping = new ConcurrentDictionary(); } internal void Update(API.ChannelInfo model) { @@ -101,5 +104,12 @@ namespace Discord bool ignored; return _messages.TryRemove(messageId, out ignored); } + + internal string GetUserId(uint ssrc) + { + string userId = null; + _ssrcMapping.TryGetValue(ssrc, out userId); + return userId; + } } } diff --git a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs index de6d43569..2de2aa918 100644 --- a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs +++ b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs @@ -21,5 +21,12 @@ namespace Discord.WebSockets.Voice if (IsSpeaking != null) IsSpeaking(this, new IsTalkingEventArgs(userId, isSpeaking)); } + + public event EventHandler OnPacket; + internal void RaiseOnPacket(string userId, string channelId, byte[] buffer, int offset, int count) + { + if (OnPacket != null) + OnPacket(this, new VoicePacketEventArgs(userId, channelId, buffer, offset, count)); + } } } diff --git a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs index cf51aee49..b0a4c2ce5 100644 --- a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs +++ b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs @@ -1,4 +1,5 @@ -using Discord.Audio; +#define USE_THREAD +using Discord.Audio; using Discord.Helpers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -16,46 +17,51 @@ namespace Discord.WebSockets.Voice { internal partial class VoiceWebSocket : WebSocket { - private const string EncryptedMode = "xsalsa20_poly1305"; + private const int MaxOpusSize = 4000; + private const string EncryptedMode = "xsalsa20_poly1305"; private const string UnencryptedMode = "plain"; - private readonly int _targetAudioBufferLength; + private readonly Random _rand; + private readonly int _targetAudioBufferLength; + private OpusEncoder _encoder; + private readonly ConcurrentDictionary _decoders; private ManualResetEventSlim _connectWaitOnLogin; private uint _ssrc; - private readonly Random _rand = new Random(); - private OpusEncoder _encoder; private ConcurrentQueue _sendQueue; private ManualResetEventSlim _sendQueueWait, _sendQueueEmptyWait; private UdpClient _udp; private IPEndPoint _endpoint; private bool _isClearing, _isEncrypted; - private byte[] _secretKey; + private byte[] _secretKey, _encodingBuffer; private ushort _sequence; - private byte[] _encodingBuffer; - private string _serverId, _userId, _sessionId, _token, _encryptionMode; + private string _userId, _sessionId, _token, _encryptionMode; + private Server _server; + private Channel _channel; #if USE_THREAD private Thread _sendThread; #endif - public string CurrentVoiceServerId => _serverId; + public Server CurrentVoiceServer => _server; public VoiceWebSocket(DiscordClient client) : base(client) { - _connectWaitOnLogin = new ManualResetEventSlim(false); + _rand = new Random(); + _connectWaitOnLogin = new ManualResetEventSlim(false); + _decoders = new ConcurrentDictionary(); _sendQueue = new ConcurrentQueue(); _sendQueueWait = new ManualResetEventSlim(true); _sendQueueEmptyWait = new ManualResetEventSlim(true); - _encoder = new OpusEncoder(48000, 1, 20, Opus.Application.Audio); - _encodingBuffer = new byte[4000]; _targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames - } + _encodingBuffer = new byte[MaxOpusSize]; + } - public void SetServer(string serverId) + public void SetChannel(Server server, Channel channel) { - _serverId = serverId; + _server = server; + _channel = channel; } public async Task Login(string userId, string sessionId, string token, CancellationToken cancelToken) { @@ -69,7 +75,7 @@ namespace Discord.WebSockets.Voice _userId = userId; _sessionId = sessionId; _token = token; - + await Connect().ConfigureAwait(false); } public async Task Reconnect() @@ -107,26 +113,29 @@ namespace Discord.WebSockets.Voice #endif LoginCommand msg = new LoginCommand(); - msg.Payload.ServerId = _serverId; + msg.Payload.ServerId = _server.Id; msg.Payload.SessionId = _sessionId; msg.Payload.Token = _token; msg.Payload.UserId = _userId; QueueMessage(msg); #if USE_THREAD - _sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_disconnectToken))); + _sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_cancelToken))); _sendThread.Start(); +#if !DNXCORE50 + return new Task[] { WatcherAsync() }.Concat(base.Run()).ToArray(); +#else + return base.Run(); #endif - return new Task[] - { +#else //!USE_THREAD + return new Task[] { Task.WhenAll( ReceiveVoiceAsync(), -#if !USE_THREAD SendVoiceAsync(), -#endif #if !DNXCORE50 WatcherAsync() #endif - }.Concat(base.Run()).ToArray(); + )}.Concat(base.Run()).ToArray(); +#endif } protected override Task Cleanup() { @@ -134,6 +143,13 @@ namespace Discord.WebSockets.Voice _sendThread.Join(); _sendThread = null; #endif + + OpusDecoder decoder; + foreach (var pair in _decoders) + { + if (_decoders.TryRemove(pair.Key, out decoder)) + decoder.Dispose(); + } ClearPCMFrames(); if (!_wasDisconnectUnexpected) @@ -147,39 +163,137 @@ namespace Discord.WebSockets.Voice return base.Cleanup(); } - private async Task ReceiveVoiceAsync() +#if USE_THREAD + private void ReceiveVoiceAsync(CancellationToken cancelToken) + { +#else + private Task ReceiveVoiceAsync() { var cancelToken = _cancelToken; - await Task.Run(async () => + return Task.Run(async () => + { +#endif + try { - try + byte[] packet, decodingBuffer = null, nonce = null, result; + int packetLength, resultOffset, resultLength; + IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); + + if ((_client.Config.VoiceMode & DiscordVoiceMode.Incoming) != 0) { - while (!cancelToken.IsCancellationRequested) + decodingBuffer = new byte[MaxOpusSize]; + nonce = new byte[24]; + } + + while (!cancelToken.IsCancellationRequested) + { +#if USE_THREAD + Thread.Sleep(1); +#elif DNXCORE50 + await Task.Delay(1).ConfigureAwait(false); +#endif +#if USE_THREAD || DNXCORE50 + if (_udp.Available > 0) { -#if DNXCORE50 - if (_udp.Available > 0) - { + packet = _udp.Receive(ref endpoint); +#else + var msg = await _udp.ReceiveAsync().ConfigureAwait(false); + endpoint = msg.Endpoint; + receievedPacket = msg.Buffer; #endif - var result = await _udp.ReceiveAsync().ConfigureAwait(false); - ProcessUdpMessage(result); -#if DNXCORE50 + packetLength = packet.Length; + + if (packetLength > 0 && endpoint.Equals(_endpoint)) + { + if (_state != (int)WebSocketState.Connected) + { + if (packetLength != 70) + return; + + int port = packet[68] | packet[69] << 8; + string ip = Encoding.ASCII.GetString(packet, 4, 70 - 6).TrimEnd('\0'); + + CompleteConnect(); + + var login2 = new Login2Command(); + login2.Payload.Protocol = "udp"; + login2.Payload.SocketData.Address = ip; + login2.Payload.SocketData.Mode = _encryptionMode; + login2.Payload.SocketData.Port = port; + QueueMessage(login2); + if ((_client.Config.VoiceMode & DiscordVoiceMode.Incoming) == 0) + return; + } + else + { + //Parse RTP Data + if (packetLength < 12) + return; + + byte flags = packet[0]; + if (flags != 0x80) + return; + + byte payloadType = packet[1]; + if (payloadType != 0x78) + return; + + 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 = Sodium.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.");*/ + + string userId = _channel.GetUserId(ssrc); + if (userId != null) + RaiseOnPacket(userId, _channel.Id, result, resultOffset, resultLength); + } } - else - await Task.Delay(1).ConfigureAwait(false); -#endif +#if USE_THREAD || DNXCORE50 } +#endif } - catch (OperationCanceledException) { } - catch (InvalidOperationException) { } //Includes ObjectDisposedException - catch (Exception ex) { await DisconnectInternal(ex); } + } + catch (OperationCanceledException) { } + catch (InvalidOperationException) { } //Includes ObjectDisposedException +#if !USE_THREAD }).ConfigureAwait(false); +#endif } #if USE_THREAD - private void SendVoiceAsync(CancellationTokenSource cancelSource) + private void SendVoiceAsync(CancellationToken cancelToken) { - var cancelToken = cancelSource.Token; #else private Task SendVoiceAsync() { @@ -189,103 +303,114 @@ namespace Discord.WebSockets.Voice { #endif - byte[] packet; - try + try + { + while (!cancelToken.IsCancellationRequested && _state != (int)WebSocketState.Connected) { - while (!cancelToken.IsCancellationRequested && _state != (int)WebSocketState.Connected) - { #if USE_THREAD - Thread.Sleep(1); + Thread.Sleep(1); #else - await Task.Delay(1); + await Task.Delay(1); #endif - } + } - if (cancelToken.IsCancellationRequested) - return; - - uint timestamp = 0; - double nextTicks = 0.0; - double ticksPerMillisecond = Stopwatch.Frequency / 1000.0; - double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength; - double spinLockThreshold = 1.5 * ticksPerMillisecond; - uint samplesPerFrame = (uint)_encoder.SamplesPerFrame; - Stopwatch sw = Stopwatch.StartNew(); - - byte[] rtpPacket = new byte[_encodingBuffer.Length + 12]; - byte[] nonce = null; - rtpPacket[0] = 0x80; //Flags; - rtpPacket[1] = 0x78; //Payload Type - rtpPacket[8] = (byte)((_ssrc >> 24) & 0xFF); - rtpPacket[9] = (byte)((_ssrc >> 16) & 0xFF); - rtpPacket[10] = (byte)((_ssrc >> 8) & 0xFF); - rtpPacket[11] = (byte)((_ssrc >> 0) & 0xFF); - if (_isEncrypted) - { - nonce = new byte[24]; - Buffer.BlockCopy(rtpPacket, 0, nonce, 0, 12); - } + if (cancelToken.IsCancellationRequested) + return; - while (!cancelToken.IsCancellationRequested) + byte[] queuedPacket, result, nonce = null; + uint timestamp = 0; + double nextTicks = 0.0; + double ticksPerMillisecond = Stopwatch.Frequency / 1000.0; + double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength; + double spinLockThreshold = 1.5 * ticksPerMillisecond; + uint samplesPerFrame = (uint)_encoder.SamplesPerFrame; + Stopwatch sw = Stopwatch.StartNew(); + + if (_isEncrypted) + { + nonce = new byte[24]; + result = new byte[MaxOpusSize + 12 + 16]; + } + else + result = new byte[MaxOpusSize + 12]; + + int rtpPacketLength = 0; + result[0] = 0x80; //Flags; + result[1] = 0x78; //Payload Type + result[8] = (byte)((_ssrc >> 24) & 0xFF); + result[9] = (byte)((_ssrc >> 16) & 0xFF); + result[10] = (byte)((_ssrc >> 8) & 0xFF); + result[11] = (byte)((_ssrc >> 0) & 0xFF); + + if (_isEncrypted) + Buffer.BlockCopy(result, 0, nonce, 0, 12); + + while (!cancelToken.IsCancellationRequested) + { + double ticksToNextFrame = nextTicks - sw.ElapsedTicks; + if (ticksToNextFrame <= 0.0) { - double ticksToNextFrame = nextTicks - sw.ElapsedTicks; - if (ticksToNextFrame <= 0.0) + while (sw.ElapsedTicks > nextTicks) { - while (sw.ElapsedTicks > nextTicks) + if (!_isClearing) { - if (!_isClearing) + if (_sendQueue.TryDequeue(out queuedPacket)) { - if (_sendQueue.TryDequeue(out packet)) + ushort sequence = unchecked(_sequence++); + result[2] = (byte)((sequence >> 8) & 0xFF); + result[3] = (byte)((sequence >> 0) & 0xFF); + result[4] = (byte)((timestamp >> 24) & 0xFF); + result[5] = (byte)((timestamp >> 16) & 0xFF); + result[6] = (byte)((timestamp >> 8) & 0xFF); + result[7] = (byte)((timestamp >> 0) & 0xFF); + + if (_isEncrypted) + { + Buffer.BlockCopy(result, 2, nonce, 2, 6); //Update nonce + int ret = Sodium.Encrypt(queuedPacket, queuedPacket.Length, result, 12, nonce, _secretKey); + if (ret != 0) + continue; + rtpPacketLength = queuedPacket.Length + 12 + 16; + } + else { - ushort sequence = unchecked(_sequence++); - rtpPacket[2] = (byte)((sequence >> 8) & 0xFF); - rtpPacket[3] = (byte)((sequence >> 0) & 0xFF); - rtpPacket[4] = (byte)((timestamp >> 24) & 0xFF); - rtpPacket[5] = (byte)((timestamp >> 16) & 0xFF); - rtpPacket[6] = (byte)((timestamp >> 8) & 0xFF); - rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); - if (_isEncrypted) - { - Buffer.BlockCopy(rtpPacket, 2, nonce, 2, 6); //Update nonce - int ret = Sodium.Encrypt(packet, packet.Length, packet, nonce, _secretKey); - if (ret != 0) - continue; - } - Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); + Buffer.BlockCopy(queuedPacket, 0, result, 12, queuedPacket.Length); + rtpPacketLength = queuedPacket.Length + 12; + } #if USE_THREAD - _udp.Send(rtpPacket, packet.Length + 12); + _udp.Send(result, rtpPacketLength); #else - await _udp.SendAsync(rtpPacket, packet.Length + 12).ConfigureAwait(false); + await _udp.SendAsync(rtpPacket, rtpPacketLength).ConfigureAwait(false); #endif - } - timestamp = unchecked(timestamp + samplesPerFrame); - nextTicks += ticksPerFrame; + } + timestamp = unchecked(timestamp + samplesPerFrame); + nextTicks += ticksPerFrame; - //If we have less than our target data buffered, request more - int count = _sendQueue.Count; - if (count == 0) - { - _sendQueueWait.Set(); - _sendQueueEmptyWait.Set(); - } - else if (count < _targetAudioBufferLength) - _sendQueueWait.Set(); + //If we have less than our target data buffered, request more + int count = _sendQueue.Count; + if (count == 0) + { + _sendQueueWait.Set(); + _sendQueueEmptyWait.Set(); } + else if (count < _targetAudioBufferLength) + _sendQueueWait.Set(); } } - //Dont sleep for 1 millisecond if we need to output audio in the next 1.5 - else if (_sendQueue.Count == 0 || ticksToNextFrame >= spinLockThreshold) + } + //Dont sleep for 1 millisecond if we need to output audio in the next 1.5 + else if (_sendQueue.Count == 0 || ticksToNextFrame >= spinLockThreshold) #if USE_THREAD Thread.Sleep(1); #else - await Task.Delay(1).ConfigureAwait(false); + await Task.Delay(1).ConfigureAwait(false); #endif - } } - catch (OperationCanceledException) { } - catch (InvalidOperationException) { } //Includes ObjectDisposedException + } + catch (OperationCanceledException) { } + catch (InvalidOperationException) { } //Includes ObjectDisposedException #if !USE_THREAD - }); + }).ConfigureAwait(false); #endif } #if !DNXCORE50 @@ -331,16 +456,12 @@ namespace Discord.WebSockets.Voice _sequence = (ushort)_rand.Next(0, ushort.MaxValue); //No thread issue here because SendAsync doesn't start until _isReady is true - await _udp.SendAsync(new byte[70] { - (byte)((_ssrc >> 24) & 0xFF), - (byte)((_ssrc >> 16) & 0xFF), - (byte)((_ssrc >> 8) & 0xFF), - (byte)((_ssrc >> 0) & 0xFF), - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }, 70).ConfigureAwait(false); + 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; @@ -365,98 +486,6 @@ namespace Discord.WebSockets.Voice } } - private void ProcessUdpMessage(UdpReceiveResult msg) - { - if (msg.Buffer.Length > 0 && msg.RemoteEndPoint.Equals(_endpoint)) - { - byte[] buffer = msg.Buffer; - int length = msg.Buffer.Length; - if (_state != (int)WebSocketState.Connected) - { - if (length != 70) - { - if (_logLevel >= LogMessageSeverity.Warning) - RaiseOnLog(LogMessageSeverity.Warning, $"Unexpected message length. Expected 70, got {length}."); - return; - } - - int port = buffer[68] | buffer[69] << 8; - string ip = Encoding.ASCII.GetString(buffer, 4, 70 - 6).TrimEnd('\0'); - - CompleteConnect(); - - var login2 = new Login2Command(); - login2.Payload.Protocol = "udp"; - login2.Payload.SocketData.Address = ip; - login2.Payload.SocketData.Mode = _encryptionMode; - login2.Payload.SocketData.Port = port; - QueueMessage(login2); - } - else - { - //Parse RTP Data - if (length < 12) - { - if (_logLevel >= LogMessageSeverity.Warning) - RaiseOnLog(LogMessageSeverity.Warning, $"Unexpected message length. Expected >= 12, got {length}."); - return; - } - - byte flags = buffer[0]; - if (flags != 0x80) - { - if (_logLevel >= LogMessageSeverity.Warning) - RaiseOnLog(LogMessageSeverity.Warning, $"Unexpected Flags: {flags}"); - return; - } - - byte payloadType = buffer[1]; - if (payloadType != 0x78) - { - if (_logLevel >= LogMessageSeverity.Warning) - RaiseOnLog(LogMessageSeverity.Warning, $"Unexpected Payload Type: {payloadType}"); - return; - } - - ushort sequenceNumber = (ushort)((buffer[2] << 8) | - buffer[3] << 0); - uint timestamp = (uint)((buffer[4] << 24) | - (buffer[5] << 16) | - (buffer[6] << 8) | - (buffer[7] << 0)); - uint ssrc = (uint)((buffer[8] << 24) | - (buffer[9] << 16) | - (buffer[10] << 8) | - (buffer[11] << 0)); - - //Decrypt - /*if (_mode == "xsalsa20_poly1305") - { - if (length < 36) //12 + 24 - throw new Exception($"Unexpected message length. Expected >= 36, got {length}."); - - byte[] nonce = new byte[24]; //16 bytes static, 8 bytes incrementing? - Buffer.BlockCopy(buffer, 12, nonce, 0, 24); - - byte[] cipherText = new byte[buffer.Length - 36]; - Buffer.BlockCopy(buffer, 36, cipherText, 0, cipherText.Length); - - Sodium.SecretBox.Open(cipherText, nonce, _secretKey); - } - else //Plain - { - byte[] newBuffer = new byte[buffer.Length - 12]; - Buffer.BlockCopy(buffer, 12, newBuffer, 0, newBuffer.Length); - buffer = newBuffer; - }*/ - - if (_logLevel >= LogMessageSeverity.Debug) - RaiseOnLog(LogMessageSeverity.Debug, $"Received {buffer.Length - 12} bytes."); - //TODO: Use Voice Data - } - } - } - public void SendPCMFrames(byte[] data, int bytes) { int frameSize = _encoder.FrameSize; @@ -491,17 +520,11 @@ namespace Discord.WebSockets.Voice //Wipe the end of the buffer for (int j = lastFrameSize; j < frameSize; j++) data[j] = 0; - } //Encode the frame int encodedLength = _encoder.EncodeFrame(data, pos, _encodingBuffer); - //TODO: Handle Encryption - if (_isEncrypted) - { - } - //Copy result to the queue payload = new byte[encodedLength]; Buffer.BlockCopy(_encodingBuffer, 0, payload, 0, encodedLength); @@ -515,8 +538,8 @@ namespace Discord.WebSockets.Voice } } - if (_logLevel >= LogMessageSeverity.Debug) - RaiseOnLog(LogMessageSeverity.Debug, $"Queued {bytes} bytes for voice output."); + /*if (_logLevel >= LogMessageSeverity.Debug) + RaiseOnLog(LogMessageSeverity.Debug, $"Queued {bytes} bytes for voice output.");*/ } public void ClearPCMFrames() { diff --git a/src/Discord.Net/WebSockets/WebSocket.cs b/src/Discord.Net/WebSockets/WebSocket.cs index 258596d79..c9cc27a62 100644 --- a/src/Discord.Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/WebSockets/WebSocket.cs @@ -88,9 +88,9 @@ namespace Discord.WebSockets _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken).Token; else _cancelToken = _cancelTokenSource.Token; - - await _engine.Connect(Host, _cancelToken).ConfigureAwait(false); + _lastHeartbeat = DateTime.UtcNow; + await _engine.Connect(Host, _cancelToken).ConfigureAwait(false); _state = (int)WebSocketState.Connecting; _runTask = RunTasks();