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();