@@ -22,6 +22,7 @@ namespace Discord | |||||
_commands = new List<Command>(); | _commands = new List<Command>(); | ||||
CommandChar = '~'; | CommandChar = '~'; | ||||
UseCommandChar = true; | |||||
RequireCommandCharInPublic = true; | RequireCommandCharInPublic = true; | ||||
RequireCommandCharInPrivate = true; | RequireCommandCharInPrivate = true; | ||||
@@ -32,7 +33,7 @@ namespace Discord | |||||
return; | return; | ||||
//Ignore messages from ourselves | //Ignore messages from ourselves | ||||
if (e.Message.UserId == _myId) | |||||
if (e.Message.UserId == _myId) | |||||
return; | return; | ||||
//Check for the command character | //Check for the command character | ||||
@@ -49,8 +49,7 @@ namespace Discord | |||||
private bool _isDebugMode; | private bool _isDebugMode; | ||||
#if !DNXCORE50 | #if !DNXCORE50 | ||||
public Server CurrentVoiceServer => _currentVoiceToken != null ? _servers[_currentVoiceServerId] : null; | |||||
private string _currentVoiceServerId, _currentVoiceToken; | |||||
public Server CurrentVoiceServer => GetServer(_voiceWebSocket.CurrentVoiceServerId); | |||||
#endif | #endif | ||||
//Constructor | //Constructor | ||||
@@ -142,13 +141,12 @@ namespace Discord | |||||
//Reconnect if we didn't cause the disconnect | //Reconnect if we didn't cause the disconnect | ||||
if (e.WasUnexpected) | if (e.WasUnexpected) | ||||
{ | { | ||||
await Task.Delay(_config.ReconnectDelay); | |||||
while (!_disconnectToken.IsCancellationRequested) | while (!_disconnectToken.IsCancellationRequested) | ||||
{ | { | ||||
try | try | ||||
{ | { | ||||
await Task.Delay(_config.ReconnectDelay); | |||||
await _voiceWebSocket.ReconnectAsync(); | await _voiceWebSocket.ReconnectAsync(); | ||||
await _voiceWebSocket.Login(_currentVoiceServerId, _myId, _sessionId, _currentVoiceToken); | |||||
break; | break; | ||||
} | } | ||||
catch (Exception ex) | catch (Exception ex) | ||||
@@ -425,11 +423,10 @@ namespace Discord | |||||
try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } | try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } | ||||
#if !DNXCORE50 | #if !DNXCORE50 | ||||
if (_config.EnableVoice && data.ServerId == _currentVoiceServerId) | |||||
if (_config.EnableVoice) | |||||
{ | { | ||||
_currentVoiceToken = data.Token; | |||||
_voiceWebSocket.SetSessionData(data.ServerId, _myId, _sessionId, data.Token); | |||||
await _voiceWebSocket.ConnectAsync("wss://" + data.Endpoint.Split(':')[0]); | await _voiceWebSocket.ConnectAsync("wss://" + data.Endpoint.Split(':')[0]); | ||||
await _voiceWebSocket.Login(_currentVoiceServerId, _myId, _sessionId, data.Token); | |||||
} | } | ||||
#endif | #endif | ||||
} | } | ||||
@@ -627,19 +624,19 @@ namespace Discord | |||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
await LeaveVoiceServer(); | await LeaveVoiceServer(); | ||||
_currentVoiceServerId = channel.ServerId; | |||||
//_currentVoiceServerId = channel.ServerId; | |||||
_webSocket.JoinVoice(channel); | _webSocket.JoinVoice(channel); | ||||
await _voiceWebSocket.BeginConnect(); | |||||
} | } | ||||
public async Task LeaveVoiceServer() | public async Task LeaveVoiceServer() | ||||
{ | { | ||||
CheckReady(); | |||||
if (!_config.EnableVoice) throw new InvalidOperationException("Voice is not enabled for this client."); | if (!_config.EnableVoice) throw new InvalidOperationException("Voice is not enabled for this client."); | ||||
await _voiceWebSocket.DisconnectAsync(); | await _voiceWebSocket.DisconnectAsync(); | ||||
if (_currentVoiceServerId != null) | |||||
_webSocket.LeaveVoice(); | |||||
_currentVoiceServerId = null; | |||||
_currentVoiceToken = null; | |||||
//if (_voiceWebSocket.CurrentVoiceServerId != null) | |||||
_webSocket.LeaveVoice(); | |||||
} | } | ||||
/// <summary> Sends a PCM frame to the voice server. </summary> | /// <summary> Sends a PCM frame to the voice server. </summary> | ||||
@@ -3,8 +3,7 @@ | |||||
public class DiscordClientConfig | public class DiscordClientConfig | ||||
{ | { | ||||
#if !DNXCORE50 | #if !DNXCORE50 | ||||
/// <summary> Enables the voice websocket and UDP client (Experimental!). </summary> | |||||
/// <remarks> This option requires the opus .dll or .so be in the local lib/ folder. </remarks> | |||||
/// <summary> Enables the voice websocket and UDP client (Experimental!). This option requires the opus .dll or .so be in the local lib/ folder. </remarks> | |||||
public bool EnableVoice { get; set; } = false; | public bool EnableVoice { get; set; } = false; | ||||
#endif | #endif | ||||
/// <summary> Enables the verbose DebugMessage event handler. May hinder performance but should help debug any issues. </summary> | /// <summary> Enables the verbose DebugMessage event handler. May hinder performance but should help debug any issues. </summary> | ||||
@@ -24,9 +23,8 @@ | |||||
public bool UseMessageQueue { get; set; } = false; | public bool UseMessageQueue { get; set; } = false; | ||||
/// <summary> Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again. </summary> | /// <summary> Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again. </summary> | ||||
public int MessageQueueInterval { get; set; } = 100; | public int MessageQueueInterval { get; set; } = 100; | ||||
/// <summary> Gets or sets the max buffer length (in milliseconds) for outgoing voice packets. </summary> | |||||
/// <remarks> This value is the target maximum but is not guaranteed. The buffer will often go a bit above this value. </remarks> | |||||
public int VoiceBufferLength { get; set; } = 1000; | |||||
/// <summary> Gets or sets the max buffer length (in milliseconds) for outgoing voice packets. This value is the target maximum but is not guaranteed, the buffer will often go slightly above this value. </remarks> | |||||
public int VoiceBufferLength { get; set; } = 3000; | |||||
public DiscordClientConfig() { } | public DiscordClientConfig() { } | ||||
} | } | ||||
@@ -20,6 +20,11 @@ namespace Discord | |||||
_connectWaitOnLogin2 = new ManualResetEventSlim(false); | _connectWaitOnLogin2 = new ManualResetEventSlim(false); | ||||
} | } | ||||
public override Task ConnectAsync(string url) | |||||
{ | |||||
BeginConnect(); | |||||
return base.ConnectAsync(url); | |||||
} | |||||
public async Task Login(string token) | public async Task Login(string token) | ||||
{ | { | ||||
var cancelToken = _disconnectToken.Token; | var cancelToken = _disconnectToken.Token; | ||||
@@ -1,4 +1,4 @@ | |||||
//#define USE_THREAD | |||||
#define USE_THREAD | |||||
#if !DNXCORE50 | #if !DNXCORE50 | ||||
using Discord.API.Models; | using Discord.API.Models; | ||||
using Discord.Opus; | using Discord.Opus; | ||||
@@ -35,10 +35,14 @@ namespace Discord | |||||
private ushort _sequence; | private ushort _sequence; | ||||
private string _mode; | private string _mode; | ||||
private byte[] _encodingBuffer; | private byte[] _encodingBuffer; | ||||
private string _serverId, _userId, _sessionId, _token; | |||||
#if USE_THREAD | #if USE_THREAD | ||||
private Thread _sendThread; | private Thread _sendThread; | ||||
#endif | #endif | ||||
public string CurrentVoiceServerId => _serverId; | |||||
public DiscordVoiceSocket(DiscordClient client, int timeout, int interval, int audioBufferLength, bool isDebug) | public DiscordVoiceSocket(DiscordClient client, int timeout, int interval, int audioBufferLength, bool isDebug) | ||||
: base(client, timeout, interval, isDebug) | : base(client, timeout, interval, isDebug) | ||||
{ | { | ||||
@@ -54,25 +58,54 @@ namespace Discord | |||||
protected override void OnConnect() | protected override void OnConnect() | ||||
{ | { | ||||
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | ||||
#if !DNX451 && !MONO | |||||
#if !DNX451 | |||||
_udp.AllowNatTraversal(true); | _udp.AllowNatTraversal(true); | ||||
#endif | #endif | ||||
_isReady = false; | _isReady = false; | ||||
_isClearing = false; | _isClearing = false; | ||||
} | |||||
var cancelToken = _disconnectToken.Token; | |||||
Task.Factory.StartNew(async () => | |||||
{ | |||||
_connectWaitOnLogin.Reset(); | |||||
VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); | |||||
msg.Payload.ServerId = _serverId; | |||||
msg.Payload.SessionId = _sessionId; | |||||
msg.Payload.Token = _token; | |||||
msg.Payload.UserId = _userId; | |||||
await SendMessage(msg, cancelToken); | |||||
try | |||||
{ | |||||
if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) | |||||
return; | |||||
} | |||||
catch (OperationCanceledException) | |||||
{ | |||||
return; | |||||
} | |||||
SetConnected(); | |||||
}); | |||||
} | |||||
protected override void OnDisconnect() | protected override void OnDisconnect() | ||||
{ | { | ||||
_udp = null; | _udp = null; | ||||
_serverId = null; | |||||
_userId = null; | |||||
_sessionId = null; | |||||
_token = null; | |||||
#if USE_THREAD | #if USE_THREAD | ||||
_sendThread.Join(); | _sendThread.Join(); | ||||
_sendThread = null; | _sendThread = null; | ||||
#endif | #endif | ||||
} | |||||
} | |||||
protected override Task[] CreateTasks() | protected override Task[] CreateTasks() | ||||
{ | { | ||||
#if USE_THREAD | #if USE_THREAD | ||||
_sendThread = new Thread(new ThreadStart(() => SendAsync(_disconnectToken))); | |||||
_sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_disconnectToken))); | |||||
_sendThread.Start(); | _sendThread.Start(); | ||||
#endif | #endif | ||||
return new Task[] | return new Task[] | ||||
@@ -85,19 +118,20 @@ namespace Discord | |||||
}.Concat(base.CreateTasks()).ToArray(); | }.Concat(base.CreateTasks()).ToArray(); | ||||
} | } | ||||
public async Task Login(string serverId, string userId, string sessionId, string token) | |||||
public void SetSessionData(string serverId, string userId, string sessionId, string token) | |||||
{ | { | ||||
var cancelToken = _disconnectToken.Token; | |||||
_connectWaitOnLogin.Reset(); | |||||
_serverId = serverId; | |||||
_userId = userId; | |||||
_sessionId = sessionId; | |||||
_token = token; | |||||
} | |||||
VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); | |||||
msg.Payload.ServerId = serverId; | |||||
msg.Payload.SessionId = sessionId; | |||||
msg.Payload.Token = token; | |||||
msg.Payload.UserId = userId; | |||||
await SendMessage(msg, cancelToken); | |||||
public new async Task BeginConnect() | |||||
{ | |||||
base.BeginConnect(); | |||||
var cancelToken = _disconnectToken.Token; | |||||
await Task.Yield(); | |||||
try | try | ||||
{ | { | ||||
if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on JoinServer message | if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on JoinServer message | ||||
@@ -110,8 +144,6 @@ namespace Discord | |||||
else | else | ||||
_disconnectReason.Throw(); | _disconnectReason.Throw(); | ||||
} | } | ||||
SetConnected(); | |||||
} | } | ||||
private async Task ReceiveVoiceAsync() | private async Task ReceiveVoiceAsync() | ||||
@@ -143,6 +175,7 @@ namespace Discord | |||||
var cancelSource = _disconnectToken; | var cancelSource = _disconnectToken; | ||||
var cancelToken = cancelSource.Token; | var cancelToken = cancelSource.Token; | ||||
await Task.Yield(); | await Task.Yield(); | ||||
#endif | |||||
byte[] packet; | byte[] packet; | ||||
try | try | ||||
@@ -189,7 +222,7 @@ namespace Discord | |||||
rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); | rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); | ||||
Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); | Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); | ||||
#if USE_THREAD | #if USE_THREAD | ||||
_udp.Send(rtpPacket, packet.Count + 12); | |||||
_udp.Send(rtpPacket, packet.Length + 12); | |||||
#else | #else | ||||
await _udp.SendAsync(rtpPacket, packet.Length + 12); | await _udp.SendAsync(rtpPacket, packet.Length + 12); | ||||
#endif | #endif | ||||
@@ -222,8 +255,7 @@ namespace Discord | |||||
catch (ObjectDisposedException) { } | catch (ObjectDisposedException) { } | ||||
catch (Exception ex) { DisconnectInternal(ex); } | catch (Exception ex) { DisconnectInternal(ex); } | ||||
} | } | ||||
#endif | |||||
//Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken | |||||
//Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken | |||||
private async Task WatcherAsync() | private async Task WatcherAsync() | ||||
{ | { | ||||
var cancelToken = _disconnectToken.Token; | var cancelToken = _disconnectToken.Token; | ||||
@@ -40,12 +40,15 @@ namespace Discord | |||||
_sendQueue = new ConcurrentQueue<byte[]>(); | _sendQueue = new ConcurrentQueue<byte[]>(); | ||||
} | } | ||||
public async Task ConnectAsync(string url) | |||||
protected void BeginConnect() | |||||
{ | { | ||||
await DisconnectAsync(); | |||||
_disconnectToken = new CancellationTokenSource(); | _disconnectToken = new CancellationTokenSource(); | ||||
_disconnectReason = null; | _disconnectReason = null; | ||||
} | |||||
public virtual async Task ConnectAsync(string url) | |||||
{ | |||||
await DisconnectAsync(); | |||||
var cancelToken = _disconnectToken.Token; | var cancelToken = _disconnectToken.Token; | ||||
_webSocket = new ClientWebSocket(); | _webSocket = new ClientWebSocket(); | ||||