@@ -19,6 +19,9 @@ | |||
<Compile Include="..\Discord.Net.WebSocket\Net\DefaultWebSocketClient.cs"> | |||
<Link>Net\DefaultWebSocketClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net.WebSocket\ConnectionManager.cs"> | |||
<Link>ConnectionManager.cs</Link> | |||
</Compile> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||
@@ -12,12 +12,12 @@ namespace Discord.Rpc | |||
remove { _connectedEvent.Remove(value); } | |||
} | |||
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); | |||
public event Func<Exception, bool, Task> Disconnected | |||
public event Func<Exception, Task> Disconnected | |||
{ | |||
add { _disconnectedEvent.Add(value); } | |||
remove { _disconnectedEvent.Remove(value); } | |||
} | |||
private readonly AsyncEvent<Func<Exception, bool, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, bool, Task>>(); | |||
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | |||
public event Func<Task> Ready | |||
{ | |||
add { _readyEvent.Add(value); } | |||
@@ -40,6 +40,8 @@ namespace Discord.Rpc | |||
_rpcLogger = LogManager.CreateLogger("RPC"); | |||
_connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout, | |||
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | |||
_connection.Connected += () => _connectedEvent.InvokeAsync(); | |||
_connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | |||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||
_serializer.Error += (s, e) => | |||
@@ -59,28 +59,28 @@ namespace Discord.Audio | |||
internal AudioClient(SocketGuild guild, int id) | |||
{ | |||
Guild = guild; | |||
_audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); | |||
ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | |||
ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||
ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | |||
//ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | |||
ApiClient.ReceivedEvent += ProcessMessageAsync; | |||
ApiClient.ReceivedPacket += ProcessPacketAsync; | |||
_stateLock = new SemaphoreSlim(1, 1); | |||
_connection = new ConnectionManager(_stateLock, _audioLogger, 30000, | |||
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | |||
_connection.Connected += () => _connectedEvent.InvokeAsync(); | |||
_connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | |||
_heartbeatTimes = new ConcurrentQueue<long>(); | |||
_audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); | |||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||
_serializer.Error += (s, e) => | |||
{ | |||
_audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | |||
e.ErrorContext.Handled = true; | |||
}; | |||
ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | |||
ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||
ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | |||
//ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | |||
ApiClient.ReceivedEvent += ProcessMessageAsync; | |||
ApiClient.ReceivedPacket += ProcessPacketAsync; | |||
}; | |||
LatencyUpdated += async (old, val) => await _audioLogger.VerboseAsync($"Latency = {val} ms").ConfigureAwait(false); | |||
} | |||
@@ -98,25 +98,32 @@ namespace Discord.Audio | |||
private async Task OnConnectingAsync() | |||
{ | |||
await _audioLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); | |||
await ApiClient.ConnectAsync("wss://" + _url).ConfigureAwait(false); | |||
await _audioLogger.DebugAsync("Sending Identity").ConfigureAwait(false); | |||
await ApiClient.SendIdentityAsync(_userId, _sessionId, _token).ConfigureAwait(false); | |||
//Wait for READY | |||
await _connection.WaitAsync().ConfigureAwait(false); | |||
} | |||
private async Task OnDisconnectingAsync(Exception ex) | |||
{ | |||
//Disconnect from server | |||
await _audioLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | |||
await ApiClient.DisconnectAsync().ConfigureAwait(false); | |||
//Wait for tasks to complete | |||
await _audioLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | |||
var heartbeatTask = _heartbeatTask; | |||
if (heartbeatTask != null) | |||
await heartbeatTask.ConfigureAwait(false); | |||
_heartbeatTask = null; | |||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false); | |||
long time; | |||
while (_heartbeatTimes.TryDequeue(out time)) { } | |||
_lastMessageTime = 0; | |||
await _audioLogger.DebugAsync("Sending Voice State").ConfigureAwait(false); | |||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false); | |||
} | |||
public AudioOutStream CreateOpusStream(int samplesPerFrame, int bufferMillis) | |||
@@ -2,6 +2,7 @@ using Discord.Logging; | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Discord.Net; | |||
namespace Discord | |||
{ | |||
@@ -39,7 +40,13 @@ namespace Discord | |||
clientDisconnectHandler(ex => | |||
{ | |||
if (ex != null) | |||
Error(new Exception("WebSocket connection was closed", ex)); | |||
{ | |||
var ex2 = ex as WebSocketClosedException; | |||
if (ex2?.CloseCode == 4006) | |||
CriticalError(new Exception("WebSocket session expired", ex)); | |||
else | |||
Error(new Exception("WebSocket connection was closed", ex)); | |||
} | |||
else | |||
Error(new Exception("WebSocket connection was closed")); | |||
return Task.Delay(0); | |||
@@ -50,7 +57,7 @@ namespace Discord | |||
{ | |||
await AcquireConnectionLock().ConfigureAwait(false); | |||
var reconnectCancelToken = new CancellationTokenSource(); | |||
_reconnectCancelToken = new CancellationTokenSource(); | |||
_reconnectCancelToken = reconnectCancelToken; | |||
_task = Task.Run(async () => | |||
{ | |||
try |
@@ -95,6 +95,8 @@ namespace Discord.WebSocket | |||
_gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | |||
_connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, | |||
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | |||
_connection.Connected += () => _connectedEvent.InvokeAsync(); | |||
_connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | |||
_nextAudioId = 1; | |||
_connectionGroupLock = groupLock; | |||
@@ -173,8 +175,6 @@ namespace Discord.WebSocket | |||
{ | |||
await _gatewayLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); | |||
await ApiClient.ConnectAsync().ConfigureAwait(false); | |||
await _gatewayLogger.DebugAsync("Raising Event").ConfigureAwait(false); | |||
await _connectedEvent.InvokeAsync().ConfigureAwait(false); | |||
if (_sessionId != null) | |||
{ | |||
@@ -189,7 +189,7 @@ namespace Discord.WebSocket | |||
//Wait for READY | |||
await _connection.WaitAsync().ConfigureAwait(false); | |||
await _gatewayLogger.DebugAsync("Sending Status").ConfigureAwait(false); | |||
await SendStatusAsync().ConfigureAwait(false); | |||
@@ -506,15 +506,16 @@ namespace Discord.WebSocket | |||
} | |||
internal async Task FinishConnectAudio(int id, string url, string token) | |||
{ | |||
//TODO: Mem Leak: Disconnected/Connected handlers arent cleaned up | |||
var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value; | |||
await _audioLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
var promise = _audioConnectPromise; | |||
if (_audioClient == null) | |||
{ | |||
var audioClient = new AudioClient(this, id); | |||
var promise = _audioConnectPromise; | |||
audioClient.Disconnected += async ex => | |||
{ | |||
if (!promise.Task.IsCompleted) | |||
@@ -532,7 +533,7 @@ namespace Discord.WebSocket | |||
} | |||
_audioClient.Connected += () => | |||
{ | |||
var _ = _audioConnectPromise.TrySetResultAsync(_audioClient); | |||
var _ = promise.TrySetResultAsync(_audioClient); | |||
return Task.Delay(0); | |||
}; | |||
await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); | |||