Added VOICE_SERVER_UPDATE handling
This commit is contained in:
@@ -28,24 +28,19 @@ namespace Discord.Audio
|
|||||||
private readonly AsyncEvent<Func<VoiceOpCode, object, Task>> _receivedEvent = new AsyncEvent<Func<VoiceOpCode, object, Task>>();
|
private readonly AsyncEvent<Func<VoiceOpCode, object, Task>> _receivedEvent = new AsyncEvent<Func<VoiceOpCode, object, Task>>();
|
||||||
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
|
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
|
||||||
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
||||||
|
|
||||||
private readonly ulong _userId;
|
|
||||||
private readonly string _token;
|
|
||||||
private readonly JsonSerializer _serializer;
|
private readonly JsonSerializer _serializer;
|
||||||
private readonly IWebSocketClient _gatewayClient;
|
private readonly IWebSocketClient _gatewayClient;
|
||||||
private readonly SemaphoreSlim _connectionLock;
|
private readonly SemaphoreSlim _connectionLock;
|
||||||
private CancellationTokenSource _connectCancelToken;
|
private CancellationTokenSource _connectCancelToken;
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
public ulong GuildId { get; }
|
public ulong GuildId { get; }
|
||||||
public string SessionId { get; }
|
|
||||||
public ConnectionState ConnectionState { get; private set; }
|
public ConnectionState ConnectionState { get; private set; }
|
||||||
|
|
||||||
internal DiscordVoiceAPIClient(ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, JsonSerializer serializer = null)
|
internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, JsonSerializer serializer = null)
|
||||||
{
|
{
|
||||||
GuildId = guildId;
|
GuildId = guildId;
|
||||||
_userId = userId;
|
|
||||||
SessionId = sessionId;
|
|
||||||
_token = token;
|
|
||||||
_connectionLock = new SemaphoreSlim(1, 1);
|
_connectionLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
_gatewayClient = webSocketProvider();
|
_gatewayClient = webSocketProvider();
|
||||||
@@ -78,6 +73,19 @@ namespace Discord.Audio
|
|||||||
|
|
||||||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
||||||
}
|
}
|
||||||
|
void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_connectCancelToken?.Dispose();
|
||||||
|
(_gatewayClient as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void Dispose() => Dispose(true);
|
||||||
|
|
||||||
public Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null)
|
public Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
@@ -105,16 +113,16 @@ namespace Discord.Audio
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConnectAsync(string url)
|
public async Task ConnectAsync(string url, ulong userId, string sessionId, string token)
|
||||||
{
|
{
|
||||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ConnectInternalAsync(url).ConfigureAwait(false);
|
await ConnectInternalAsync(url, userId, sessionId, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally { _connectionLock.Release(); }
|
finally { _connectionLock.Release(); }
|
||||||
}
|
}
|
||||||
private async Task ConnectInternalAsync(string url)
|
private async Task ConnectInternalAsync(string url, ulong userId, string sessionId, string token)
|
||||||
{
|
{
|
||||||
ConnectionState = ConnectionState.Connecting;
|
ConnectionState = ConnectionState.Connecting;
|
||||||
try
|
try
|
||||||
@@ -123,7 +131,7 @@ namespace Discord.Audio
|
|||||||
_gatewayClient.SetCancelToken(_connectCancelToken.Token);
|
_gatewayClient.SetCancelToken(_connectCancelToken.Token);
|
||||||
await _gatewayClient.ConnectAsync(url).ConfigureAwait(false);
|
await _gatewayClient.ConnectAsync(url).ConfigureAwait(false);
|
||||||
|
|
||||||
await SendIdentityAsync(GuildId, _userId, SessionId, _token).ConfigureAwait(false);
|
await SendIdentityAsync(GuildId, userId, sessionId, token).ConfigureAwait(false);
|
||||||
|
|
||||||
ConnectionState = ConnectionState.Connected;
|
ConnectionState = ConnectionState.Connected;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Discord.API.Voice;
|
using Discord.API.Voice;
|
||||||
using Discord.Logging;
|
using Discord.Logging;
|
||||||
using Discord.Net.Converters;
|
using Discord.Net.Converters;
|
||||||
using Discord.Net.WebSockets;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -9,7 +8,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Discord.Audio
|
namespace Discord.Audio
|
||||||
{
|
{
|
||||||
internal class AudioClient : IAudioClient
|
internal class AudioClient : IAudioClient, IDisposable
|
||||||
{
|
{
|
||||||
public event Func<Task> Connected
|
public event Func<Task> Connected
|
||||||
{
|
{
|
||||||
@@ -17,12 +16,12 @@ namespace Discord.Audio
|
|||||||
remove { _connectedEvent.Remove(value); }
|
remove { _connectedEvent.Remove(value); }
|
||||||
}
|
}
|
||||||
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
|
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
|
||||||
public event Func<Task> Disconnected
|
public event Func<Exception, Task> Disconnected
|
||||||
{
|
{
|
||||||
add { _disconnectedEvent.Add(value); }
|
add { _disconnectedEvent.Add(value); }
|
||||||
remove { _disconnectedEvent.Remove(value); }
|
remove { _disconnectedEvent.Remove(value); }
|
||||||
}
|
}
|
||||||
private readonly AsyncEvent<Func<Task>> _disconnectedEvent = new AsyncEvent<Func<Task>>();
|
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
||||||
public event Func<int, int, Task> LatencyUpdated
|
public event Func<int, int, Task> LatencyUpdated
|
||||||
{
|
{
|
||||||
add { _latencyUpdatedEvent.Add(value); }
|
add { _latencyUpdatedEvent.Add(value); }
|
||||||
@@ -34,28 +33,30 @@ namespace Discord.Audio
|
|||||||
#if BENCHMARK
|
#if BENCHMARK
|
||||||
private readonly ILogger _benchmarkLogger;
|
private readonly ILogger _benchmarkLogger;
|
||||||
#endif
|
#endif
|
||||||
private readonly JsonSerializer _serializer;
|
|
||||||
internal readonly SemaphoreSlim _connectionLock;
|
internal readonly SemaphoreSlim _connectionLock;
|
||||||
|
private readonly JsonSerializer _serializer;
|
||||||
|
|
||||||
private TaskCompletionSource<bool> _connectTask;
|
private TaskCompletionSource<bool> _connectTask;
|
||||||
private CancellationTokenSource _cancelToken;
|
private CancellationTokenSource _cancelToken;
|
||||||
private Task _heartbeatTask, _reconnectTask;
|
private Task _heartbeatTask;
|
||||||
private long _heartbeatTime;
|
private long _heartbeatTime;
|
||||||
private bool _isReconnecting;
|
|
||||||
private string _url;
|
private string _url;
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
private DiscordSocketClient Discord { get; }
|
public CachedGuild Guild { get; }
|
||||||
public DiscordVoiceAPIClient ApiClient { get; private set; }
|
public DiscordVoiceAPIClient ApiClient { get; private set; }
|
||||||
public ConnectionState ConnectionState { get; private set; }
|
public ConnectionState ConnectionState { get; private set; }
|
||||||
public int Latency { get; private set; }
|
public int Latency { get; private set; }
|
||||||
|
|
||||||
/// <summary> Creates a new REST/WebSocket discord client. </summary>
|
private DiscordSocketClient Discord => Guild.Discord;
|
||||||
internal AudioClient(DiscordSocketClient discord, ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, ILogManager logManager)
|
|
||||||
{
|
|
||||||
Discord = discord;
|
|
||||||
|
|
||||||
_webSocketLogger = logManager.CreateLogger("Audio");
|
/// <summary> Creates a new REST/WebSocket discord client. </summary>
|
||||||
_udpLogger = logManager.CreateLogger("AudioUDP");
|
internal AudioClient(CachedGuild guild)
|
||||||
|
{
|
||||||
|
Guild = guild;
|
||||||
|
|
||||||
|
_webSocketLogger = Discord.LogManager.CreateLogger("Audio");
|
||||||
|
_udpLogger = Discord.LogManager.CreateLogger("AudioUDP");
|
||||||
#if BENCHMARK
|
#if BENCHMARK
|
||||||
_benchmarkLogger = logManager.CreateLogger("Benchmark");
|
_benchmarkLogger = logManager.CreateLogger("Benchmark");
|
||||||
#endif
|
#endif
|
||||||
@@ -69,38 +70,34 @@ namespace Discord.Audio
|
|||||||
e.ErrorContext.Handled = true;
|
e.ErrorContext.Handled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
ApiClient = new DiscordVoiceAPIClient(guildId, userId, sessionId, token, webSocketProvider);
|
ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider);
|
||||||
|
|
||||||
ApiClient.SentGatewayMessage += async opCode => await _webSocketLogger.DebugAsync($"Sent {(VoiceOpCode)opCode}").ConfigureAwait(false);
|
ApiClient.SentGatewayMessage += async opCode => await _webSocketLogger.DebugAsync($"Sent {(VoiceOpCode)opCode}").ConfigureAwait(false);
|
||||||
ApiClient.ReceivedEvent += ProcessMessageAsync;
|
ApiClient.ReceivedEvent += ProcessMessageAsync;
|
||||||
ApiClient.Disconnected += async ex =>
|
ApiClient.Disconnected += async ex =>
|
||||||
{
|
{
|
||||||
if (ex != null)
|
if (ex != null)
|
||||||
{
|
|
||||||
await _webSocketLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
|
await _webSocketLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
|
||||||
await StartReconnectAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
await _webSocketLogger.WarningAsync($"Connection Closed").ConfigureAwait(false);
|
await _webSocketLogger.WarningAsync($"Connection Closed").ConfigureAwait(false);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task ConnectAsync(string url)
|
public async Task ConnectAsync(string url, ulong userId, string sessionId, string token)
|
||||||
{
|
{
|
||||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_isReconnecting = false;
|
await ConnectInternalAsync(url, userId, sessionId, token).ConfigureAwait(false);
|
||||||
await ConnectInternalAsync(url).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
finally { _connectionLock.Release(); }
|
finally { _connectionLock.Release(); }
|
||||||
}
|
}
|
||||||
private async Task ConnectInternalAsync(string url)
|
private async Task ConnectInternalAsync(string url, ulong userId, string sessionId, string token)
|
||||||
{
|
{
|
||||||
var state = ConnectionState;
|
var state = ConnectionState;
|
||||||
if (state == ConnectionState.Connecting || state == ConnectionState.Connected)
|
if (state == ConnectionState.Connecting || state == ConnectionState.Connected)
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||||
|
|
||||||
ConnectionState = ConnectionState.Connecting;
|
ConnectionState = ConnectionState.Connecting;
|
||||||
await _webSocketLogger.InfoAsync("Connecting").ConfigureAwait(false);
|
await _webSocketLogger.InfoAsync("Connecting").ConfigureAwait(false);
|
||||||
@@ -109,7 +106,7 @@ namespace Discord.Audio
|
|||||||
_url = url;
|
_url = url;
|
||||||
_connectTask = new TaskCompletionSource<bool>();
|
_connectTask = new TaskCompletionSource<bool>();
|
||||||
_cancelToken = new CancellationTokenSource();
|
_cancelToken = new CancellationTokenSource();
|
||||||
await ApiClient.ConnectAsync(url).ConfigureAwait(false);
|
await ApiClient.ConnectAsync(url, userId, sessionId, token).ConfigureAwait(false);
|
||||||
await _connectedEvent.InvokeAsync().ConfigureAwait(false);
|
await _connectedEvent.InvokeAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
await _connectTask.Task.ConfigureAwait(false);
|
await _connectTask.Task.ConfigureAwait(false);
|
||||||
@@ -119,7 +116,7 @@ namespace Discord.Audio
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,12 +126,20 @@ namespace Discord.Audio
|
|||||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_isReconnecting = false;
|
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
finally { _connectionLock.Release(); }
|
finally { _connectionLock.Release(); }
|
||||||
}
|
}
|
||||||
private async Task DisconnectInternalAsync()
|
private async Task DisconnectAsync(Exception ex)
|
||||||
|
{
|
||||||
|
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await DisconnectInternalAsync(ex).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally { _connectionLock.Release(); }
|
||||||
|
}
|
||||||
|
private async Task DisconnectInternalAsync(Exception ex)
|
||||||
{
|
{
|
||||||
if (ConnectionState == ConnectionState.Disconnected) return;
|
if (ConnectionState == ConnectionState.Disconnected) return;
|
||||||
ConnectionState = ConnectionState.Disconnecting;
|
ConnectionState = ConnectionState.Disconnecting;
|
||||||
@@ -155,61 +160,7 @@ namespace Discord.Audio
|
|||||||
ConnectionState = ConnectionState.Disconnected;
|
ConnectionState = ConnectionState.Disconnected;
|
||||||
await _webSocketLogger.InfoAsync("Disconnected").ConfigureAwait(false);
|
await _webSocketLogger.InfoAsync("Disconnected").ConfigureAwait(false);
|
||||||
|
|
||||||
await _disconnectedEvent.InvokeAsync().ConfigureAwait(false);
|
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StartReconnectAsync()
|
|
||||||
{
|
|
||||||
//TODO: Is this thread-safe?
|
|
||||||
if (_reconnectTask != null) return;
|
|
||||||
|
|
||||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_reconnectTask != null) return;
|
|
||||||
_isReconnecting = true;
|
|
||||||
_reconnectTask = ReconnectInternalAsync();
|
|
||||||
}
|
|
||||||
finally { _connectionLock.Release(); }
|
|
||||||
}
|
|
||||||
private async Task ReconnectInternalAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int nextReconnectDelay = 1000;
|
|
||||||
while (_isReconnecting)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(nextReconnectDelay).ConfigureAwait(false);
|
|
||||||
nextReconnectDelay *= 2;
|
|
||||||
if (nextReconnectDelay > 30000)
|
|
||||||
nextReconnectDelay = 30000;
|
|
||||||
|
|
||||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ConnectInternalAsync(_url).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
finally { _connectionLock.Release(); }
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await _webSocketLogger.WarningAsync("Reconnect failed", ex).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_isReconnecting = false;
|
|
||||||
_reconnectTask = null;
|
|
||||||
}
|
|
||||||
finally { _connectionLock.Release(); }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
|
private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
|
||||||
@@ -285,7 +236,7 @@ namespace Discord.Audio
|
|||||||
if (ConnectionState == ConnectionState.Connected)
|
if (ConnectionState == ConnectionState.Connected)
|
||||||
{
|
{
|
||||||
await _webSocketLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false);
|
await _webSocketLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false);
|
||||||
await StartReconnectAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync(new Exception("Server missed last heartbeat")).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,5 +247,14 @@ namespace Discord.Audio
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
_isDisposed = true;
|
||||||
|
ApiClient.Dispose();
|
||||||
|
}
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose() => Dispose(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Discord.Audio
|
|||||||
public interface IAudioClient
|
public interface IAudioClient
|
||||||
{
|
{
|
||||||
event Func<Task> Connected;
|
event Func<Task> Connected;
|
||||||
event Func<Task> Disconnected;
|
event Func<Exception, Task> Disconnected;
|
||||||
event Func<int, int, Task> LatencyUpdated;
|
event Func<int, int, Task> LatencyUpdated;
|
||||||
|
|
||||||
DiscordVoiceAPIClient ApiClient { get; }
|
DiscordVoiceAPIClient ApiClient { get; }
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ namespace Discord
|
|||||||
|
|
||||||
internal readonly ILogger _discordLogger, _restLogger, _queueLogger;
|
internal readonly ILogger _discordLogger, _restLogger, _queueLogger;
|
||||||
internal readonly SemaphoreSlim _connectionLock;
|
internal readonly SemaphoreSlim _connectionLock;
|
||||||
internal readonly LogManager _log;
|
|
||||||
internal readonly RequestQueue _requestQueue;
|
internal readonly RequestQueue _requestQueue;
|
||||||
internal bool _isDisposed;
|
internal bool _isDisposed;
|
||||||
internal SelfUser _currentUser;
|
internal SelfUser _currentUser;
|
||||||
|
|
||||||
|
public API.DiscordApiClient ApiClient { get; }
|
||||||
|
internal LogManager LogManager { get; }
|
||||||
public LoginState LoginState { get; private set; }
|
public LoginState LoginState { get; private set; }
|
||||||
public API.DiscordApiClient ApiClient { get; private set; }
|
|
||||||
|
|
||||||
/// <summary> Creates a new REST-only discord client. </summary>
|
/// <summary> Creates a new REST-only discord client. </summary>
|
||||||
public DiscordClient()
|
public DiscordClient()
|
||||||
@@ -40,11 +40,11 @@ namespace Discord
|
|||||||
/// <summary> Creates a new REST-only discord client. </summary>
|
/// <summary> Creates a new REST-only discord client. </summary>
|
||||||
public DiscordClient(DiscordConfig config)
|
public DiscordClient(DiscordConfig config)
|
||||||
{
|
{
|
||||||
_log = new LogManager(config.LogLevel);
|
LogManager = new LogManager(config.LogLevel);
|
||||||
_log.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
|
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
|
||||||
_discordLogger = _log.CreateLogger("Discord");
|
_discordLogger = LogManager.CreateLogger("Discord");
|
||||||
_restLogger = _log.CreateLogger("Rest");
|
_restLogger = LogManager.CreateLogger("Rest");
|
||||||
_queueLogger = _log.CreateLogger("Queue");
|
_queueLogger = LogManager.CreateLogger("Queue");
|
||||||
|
|
||||||
_connectionLock = new SemaphoreSlim(1, 1);
|
_connectionLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
@@ -267,6 +267,8 @@ namespace Discord
|
|||||||
public void Dispose() => Dispose(true);
|
public void Dispose() => Dispose(true);
|
||||||
|
|
||||||
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;
|
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;
|
||||||
|
ILogManager IDiscordClient.LogManager => LogManager;
|
||||||
|
|
||||||
Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); }
|
Task IDiscordClient.ConnectAsync() { throw new NotSupportedException(); }
|
||||||
Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); }
|
Task IDiscordClient.DisconnectAsync() { throw new NotSupportedException(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ namespace Discord
|
|||||||
remove { _connectedEvent.Remove(value); }
|
remove { _connectedEvent.Remove(value); }
|
||||||
}
|
}
|
||||||
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
|
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
|
||||||
public event Func<Task> Disconnected
|
public event Func<Exception, Task> Disconnected
|
||||||
{
|
{
|
||||||
add { _disconnectedEvent.Add(value); }
|
add { _disconnectedEvent.Add(value); }
|
||||||
remove { _disconnectedEvent.Remove(value); }
|
remove { _disconnectedEvent.Remove(value); }
|
||||||
}
|
}
|
||||||
private readonly AsyncEvent<Func<Task>> _disconnectedEvent = new AsyncEvent<Func<Task>>();
|
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
||||||
public event Func<Task> Ready
|
public event Func<Task> Ready
|
||||||
{
|
{
|
||||||
add { _readyEvent.Add(value); }
|
add { _readyEvent.Add(value); }
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace Discord
|
|||||||
AudioMode = config.AudioMode;
|
AudioMode = config.AudioMode;
|
||||||
WebSocketProvider = config.WebSocketProvider;
|
WebSocketProvider = config.WebSocketProvider;
|
||||||
|
|
||||||
_gatewayLogger = _log.CreateLogger("Gateway");
|
_gatewayLogger = LogManager.CreateLogger("Gateway");
|
||||||
#if BENCHMARK
|
#if BENCHMARK
|
||||||
_benchmarkLogger = _log.CreateLogger("Benchmark");
|
_benchmarkLogger = _log.CreateLogger("Benchmark");
|
||||||
#endif
|
#endif
|
||||||
@@ -94,7 +94,7 @@ namespace Discord
|
|||||||
if (ex != null)
|
if (ex != null)
|
||||||
{
|
{
|
||||||
await _gatewayLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
|
await _gatewayLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
|
||||||
await StartReconnectAsync().ConfigureAwait(false);
|
await StartReconnectAsync(ex).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
await _gatewayLogger.WarningAsync($"Connection Closed").ConfigureAwait(false);
|
await _gatewayLogger.WarningAsync($"Connection Closed").ConfigureAwait(false);
|
||||||
@@ -112,7 +112,7 @@ namespace Discord
|
|||||||
protected override async Task OnLogoutAsync()
|
protected override async Task OnLogoutAsync()
|
||||||
{
|
{
|
||||||
if (ConnectionState != ConnectionState.Disconnected)
|
if (ConnectionState != ConnectionState.Disconnected)
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||||
|
|
||||||
_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
|
_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ namespace Discord
|
|||||||
|
|
||||||
var state = ConnectionState;
|
var state = ConnectionState;
|
||||||
if (state == ConnectionState.Connecting || state == ConnectionState.Connected)
|
if (state == ConnectionState.Connecting || state == ConnectionState.Connected)
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||||
|
|
||||||
ConnectionState = ConnectionState.Connecting;
|
ConnectionState = ConnectionState.Connecting;
|
||||||
await _gatewayLogger.InfoAsync("Connecting").ConfigureAwait(false);
|
await _gatewayLogger.InfoAsync("Connecting").ConfigureAwait(false);
|
||||||
@@ -165,7 +165,7 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,11 +176,11 @@ namespace Discord
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally { _connectionLock.Release(); }
|
finally { _connectionLock.Release(); }
|
||||||
}
|
}
|
||||||
private async Task DisconnectInternalAsync()
|
private async Task DisconnectInternalAsync(Exception ex)
|
||||||
{
|
{
|
||||||
ulong guildId;
|
ulong guildId;
|
||||||
|
|
||||||
@@ -211,10 +211,10 @@ namespace Discord
|
|||||||
ConnectionState = ConnectionState.Disconnected;
|
ConnectionState = ConnectionState.Disconnected;
|
||||||
await _gatewayLogger.InfoAsync("Disconnected").ConfigureAwait(false);
|
await _gatewayLogger.InfoAsync("Disconnected").ConfigureAwait(false);
|
||||||
|
|
||||||
await _disconnectedEvent.InvokeAsync().ConfigureAwait(false);
|
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartReconnectAsync()
|
private async Task StartReconnectAsync(Exception ex)
|
||||||
{
|
{
|
||||||
//TODO: Is this thread-safe?
|
//TODO: Is this thread-safe?
|
||||||
if (_reconnectTask != null) return;
|
if (_reconnectTask != null) return;
|
||||||
@@ -222,6 +222,7 @@ namespace Discord
|
|||||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await DisconnectInternalAsync(ex).ConfigureAwait(false);
|
||||||
if (_reconnectTask != null) return;
|
if (_reconnectTask != null) return;
|
||||||
_isReconnecting = true;
|
_isReconnecting = true;
|
||||||
_reconnectTask = ReconnectInternalAsync();
|
_reconnectTask = ReconnectInternalAsync();
|
||||||
@@ -469,7 +470,7 @@ namespace Discord
|
|||||||
await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false);
|
await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false);
|
||||||
await _gatewayLogger.WarningAsync("Server requested a reconnect").ConfigureAwait(false);
|
await _gatewayLogger.WarningAsync("Server requested a reconnect").ConfigureAwait(false);
|
||||||
|
|
||||||
await StartReconnectAsync().ConfigureAwait(false);
|
await StartReconnectAsync(new Exception("Server requested a reconnect")).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GatewayOpCode.Dispatch:
|
case GatewayOpCode.Dispatch:
|
||||||
@@ -1113,9 +1114,7 @@ namespace Discord
|
|||||||
|
|
||||||
var user = guild.GetUser(data.UserId);
|
var user = guild.GetUser(data.UserId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
|
||||||
await _userVoiceStateUpdatedEvent.InvokeAsync(user, before, after).ConfigureAwait(false);
|
await _userVoiceStateUpdatedEvent.InvokeAsync(user, before, after).ConfigureAwait(false);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown user.").ConfigureAwait(false);
|
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown user.").ConfigureAwait(false);
|
||||||
@@ -1131,7 +1130,21 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "VOICE_SERVER_UPDATE":
|
case "VOICE_SERVER_UPDATE":
|
||||||
await _gatewayLogger.DebugAsync("Ignored Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false);
|
await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (AudioMode != AudioMode.Disabled)
|
||||||
|
{
|
||||||
|
var data = (payload as JToken).ToObject<VoiceServerUpdateEvent>(_serializer);
|
||||||
|
var guild = DataStore.GetGuild(data.GuildId);
|
||||||
|
if (guild != null)
|
||||||
|
await guild.ConnectAudio("wss://" + data.Endpoint, data.Token).ConfigureAwait(false);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _gatewayLogger.WarningAsync("VOICE_SERVER_UPDATE referenced an unknown guild.").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//Ignored (User only)
|
//Ignored (User only)
|
||||||
@@ -1183,7 +1196,7 @@ namespace Discord
|
|||||||
if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? false))
|
if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? false))
|
||||||
{
|
{
|
||||||
await _gatewayLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false);
|
await _gatewayLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false);
|
||||||
await StartReconnectAsync().ConfigureAwait(false);
|
await StartReconnectAsync(new Exception("Server missed last heartbeat")).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ChannelModel = Discord.API.Channel;
|
using ChannelModel = Discord.API.Channel;
|
||||||
using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent;
|
using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent;
|
||||||
@@ -17,8 +18,9 @@ using VoiceStateModel = Discord.API.VoiceState;
|
|||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
internal class CachedGuild : Guild, IUserGuild, ICachedEntity<ulong>
|
internal class CachedGuild : Guild, ICachedEntity<ulong>, IGuild, IUserGuild
|
||||||
{
|
{
|
||||||
|
private readonly SemaphoreSlim _audioLock;
|
||||||
private TaskCompletionSource<bool> _downloaderPromise;
|
private TaskCompletionSource<bool> _downloaderPromise;
|
||||||
private ConcurrentHashSet<ulong> _channels;
|
private ConcurrentHashSet<ulong> _channels;
|
||||||
private ConcurrentDictionary<ulong, CachedGuildUser> _members;
|
private ConcurrentDictionary<ulong, CachedGuildUser> _members;
|
||||||
@@ -27,7 +29,7 @@ namespace Discord
|
|||||||
public bool Available { get; private set; }
|
public bool Available { get; private set; }
|
||||||
public int MemberCount { get; private set; }
|
public int MemberCount { get; private set; }
|
||||||
public int DownloadedMemberCount { get; private set; }
|
public int DownloadedMemberCount { get; private set; }
|
||||||
public IAudioClient AudioClient { get; private set; }
|
public AudioClient AudioClient { get; private set; }
|
||||||
|
|
||||||
public bool HasAllMembers => _downloaderPromise.Task.IsCompleted;
|
public bool HasAllMembers => _downloaderPromise.Task.IsCompleted;
|
||||||
public Task DownloaderPromise => _downloaderPromise.Task;
|
public Task DownloaderPromise => _downloaderPromise.Task;
|
||||||
@@ -48,6 +50,7 @@ namespace Discord
|
|||||||
|
|
||||||
public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model)
|
public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model)
|
||||||
{
|
{
|
||||||
|
_audioLock = new SemaphoreSlim(1, 1);
|
||||||
_downloaderPromise = new TaskCompletionSource<bool>();
|
_downloaderPromise = new TaskCompletionSource<bool>();
|
||||||
Update(model, UpdateSource.Creation, dataStore);
|
Update(model, UpdateSource.Creation, dataStore);
|
||||||
}
|
}
|
||||||
@@ -236,6 +239,55 @@ namespace Discord
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ConnectAudio(string url, string token)
|
||||||
|
{
|
||||||
|
AudioClient audioClient;
|
||||||
|
await _audioLock.WaitAsync().ConfigureAwait(false);
|
||||||
|
var voiceState = GetVoiceState(CurrentUser.Id).Value;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
audioClient = AudioClient;
|
||||||
|
if (audioClient == null)
|
||||||
|
{
|
||||||
|
audioClient = new AudioClient(this);
|
||||||
|
audioClient.Disconnected += async ex =>
|
||||||
|
{
|
||||||
|
await _audioLock.WaitAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ex != null)
|
||||||
|
{
|
||||||
|
//Reconnect if we still have channel info.
|
||||||
|
//TODO: Is this threadsafe? Could channel data be deleted before we access it?
|
||||||
|
var voiceState2 = GetVoiceState(CurrentUser.Id);
|
||||||
|
if (voiceState2.HasValue)
|
||||||
|
{
|
||||||
|
var voiceChannelId = voiceState2.Value.VoiceChannel?.Id;
|
||||||
|
if (voiceChannelId != null)
|
||||||
|
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try { AudioClient.Dispose(); } catch { }
|
||||||
|
AudioClient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_audioLock.Release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AudioClient = audioClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_audioLock.Release();
|
||||||
|
}
|
||||||
|
await audioClient.ConnectAsync(url, CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
public CachedGuild Clone() => MemberwiseClone() as CachedGuild;
|
public CachedGuild Clone() => MemberwiseClone() as CachedGuild;
|
||||||
|
|
||||||
new internal ICachedGuildChannel ToChannel(ChannelModel model)
|
new internal ICachedGuildChannel ToChannel(ChannelModel model)
|
||||||
@@ -253,5 +305,6 @@ namespace Discord
|
|||||||
|
|
||||||
bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id;
|
bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id;
|
||||||
GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions;
|
GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions;
|
||||||
|
IAudioClient IGuild.AudioClient => AudioClient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Discord.API;
|
using Discord.API;
|
||||||
|
using Discord.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -13,6 +14,7 @@ namespace Discord
|
|||||||
ConnectionState ConnectionState { get; }
|
ConnectionState ConnectionState { get; }
|
||||||
|
|
||||||
DiscordApiClient ApiClient { get; }
|
DiscordApiClient ApiClient { get; }
|
||||||
|
ILogManager LogManager { get; }
|
||||||
|
|
||||||
Task LoginAsync(TokenType tokenType, string token, bool validateToken = true);
|
Task LoginAsync(TokenType tokenType, string token, bool validateToken = true);
|
||||||
Task LogoutAsync();
|
Task LogoutAsync();
|
||||||
|
|||||||
Reference in New Issue
Block a user