@@ -22,6 +22,7 @@ namespace Discord | |||
_commands = new List<Command>(); | |||
CommandChar = '~'; | |||
UseCommandChar = true; | |||
RequireCommandCharInPublic = true; | |||
RequireCommandCharInPrivate = true; | |||
@@ -32,7 +33,7 @@ namespace Discord | |||
return; | |||
//Ignore messages from ourselves | |||
if (e.Message.UserId == _myId) | |||
if (e.Message.UserId == _myId) | |||
return; | |||
//Check for the command character | |||
@@ -49,8 +49,7 @@ namespace Discord | |||
private bool _isDebugMode; | |||
#if !DNXCORE50 | |||
public Server CurrentVoiceServer => _currentVoiceToken != null ? _servers[_currentVoiceServerId] : null; | |||
private string _currentVoiceServerId, _currentVoiceToken; | |||
public Server CurrentVoiceServer => GetServer(_voiceWebSocket.CurrentVoiceServerId); | |||
#endif | |||
//Constructor | |||
@@ -142,13 +141,12 @@ namespace Discord | |||
//Reconnect if we didn't cause the disconnect | |||
if (e.WasUnexpected) | |||
{ | |||
await Task.Delay(_config.ReconnectDelay); | |||
while (!_disconnectToken.IsCancellationRequested) | |||
{ | |||
try | |||
{ | |||
await Task.Delay(_config.ReconnectDelay); | |||
await _voiceWebSocket.ReconnectAsync(); | |||
await _voiceWebSocket.Login(_currentVoiceServerId, _myId, _sessionId, _currentVoiceToken); | |||
break; | |||
} | |||
catch (Exception ex) | |||
@@ -425,11 +423,10 @@ namespace Discord | |||
try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } | |||
#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.Login(_currentVoiceServerId, _myId, _sessionId, data.Token); | |||
} | |||
#endif | |||
} | |||
@@ -627,19 +624,19 @@ namespace Discord | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
await LeaveVoiceServer(); | |||
_currentVoiceServerId = channel.ServerId; | |||
//_currentVoiceServerId = channel.ServerId; | |||
_webSocket.JoinVoice(channel); | |||
await _voiceWebSocket.BeginConnect(); | |||
} | |||
public async Task LeaveVoiceServer() | |||
{ | |||
CheckReady(); | |||
if (!_config.EnableVoice) throw new InvalidOperationException("Voice is not enabled for this client."); | |||
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> | |||
@@ -3,8 +3,7 @@ | |||
public class DiscordClientConfig | |||
{ | |||
#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; | |||
#endif | |||
/// <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; | |||
/// <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; | |||
/// <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() { } | |||
} | |||
@@ -20,6 +20,11 @@ namespace Discord | |||
_connectWaitOnLogin2 = new ManualResetEventSlim(false); | |||
} | |||
public override Task ConnectAsync(string url) | |||
{ | |||
BeginConnect(); | |||
return base.ConnectAsync(url); | |||
} | |||
public async Task Login(string token) | |||
{ | |||
var cancelToken = _disconnectToken.Token; | |||
@@ -1,4 +1,4 @@ | |||
//#define USE_THREAD | |||
#define USE_THREAD | |||
#if !DNXCORE50 | |||
using Discord.API.Models; | |||
using Discord.Opus; | |||
@@ -35,10 +35,14 @@ namespace Discord | |||
private ushort _sequence; | |||
private string _mode; | |||
private byte[] _encodingBuffer; | |||
private string _serverId, _userId, _sessionId, _token; | |||
#if USE_THREAD | |||
private Thread _sendThread; | |||
#endif | |||
public string CurrentVoiceServerId => _serverId; | |||
public DiscordVoiceSocket(DiscordClient client, int timeout, int interval, int audioBufferLength, bool isDebug) | |||
: base(client, timeout, interval, isDebug) | |||
{ | |||
@@ -54,25 +58,54 @@ namespace Discord | |||
protected override void OnConnect() | |||
{ | |||
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | |||
#if !DNX451 && !MONO | |||
#if !DNX451 | |||
_udp.AllowNatTraversal(true); | |||
#endif | |||
_isReady = 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() | |||
{ | |||
_udp = null; | |||
_serverId = null; | |||
_userId = null; | |||
_sessionId = null; | |||
_token = null; | |||
#if USE_THREAD | |||
_sendThread.Join(); | |||
_sendThread = null; | |||
#endif | |||
} | |||
} | |||
protected override Task[] CreateTasks() | |||
{ | |||
#if USE_THREAD | |||
_sendThread = new Thread(new ThreadStart(() => SendAsync(_disconnectToken))); | |||
_sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_disconnectToken))); | |||
_sendThread.Start(); | |||
#endif | |||
return new Task[] | |||
@@ -85,19 +118,20 @@ namespace Discord | |||
}.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 | |||
{ | |||
if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on JoinServer message | |||
@@ -110,8 +144,6 @@ namespace Discord | |||
else | |||
_disconnectReason.Throw(); | |||
} | |||
SetConnected(); | |||
} | |||
private async Task ReceiveVoiceAsync() | |||
@@ -143,6 +175,7 @@ namespace Discord | |||
var cancelSource = _disconnectToken; | |||
var cancelToken = cancelSource.Token; | |||
await Task.Yield(); | |||
#endif | |||
byte[] packet; | |||
try | |||
@@ -189,7 +222,7 @@ namespace Discord | |||
rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); | |||
Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); | |||
#if USE_THREAD | |||
_udp.Send(rtpPacket, packet.Count + 12); | |||
_udp.Send(rtpPacket, packet.Length + 12); | |||
#else | |||
await _udp.SendAsync(rtpPacket, packet.Length + 12); | |||
#endif | |||
@@ -222,8 +255,7 @@ namespace Discord | |||
catch (ObjectDisposedException) { } | |||
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() | |||
{ | |||
var cancelToken = _disconnectToken.Token; | |||
@@ -40,12 +40,15 @@ namespace Discord | |||
_sendQueue = new ConcurrentQueue<byte[]>(); | |||
} | |||
public async Task ConnectAsync(string url) | |||
protected void BeginConnect() | |||
{ | |||
await DisconnectAsync(); | |||
_disconnectToken = new CancellationTokenSource(); | |||
_disconnectReason = null; | |||
} | |||
public virtual async Task ConnectAsync(string url) | |||
{ | |||
await DisconnectAsync(); | |||
var cancelToken = _disconnectToken.Token; | |||
_webSocket = new ClientWebSocket(); | |||