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