@@ -27,8 +27,8 @@ namespace Discord.API | |||
public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>(); | |||
public event Func<int, Task> SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<int, Task>> _sentGatewayMessageEvent = new AsyncEvent<Func<int, Task>>(); | |||
public event Func<GatewayOpCode, Task> SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<GatewayOpCode, Task>> _sentGatewayMessageEvent = new AsyncEvent<Func<GatewayOpCode, Task>>(); | |||
public event Func<GatewayOpCode, int?, string, object, Task> ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<GatewayOpCode, int?, string, object, Task>> _receivedGatewayEvent = new AsyncEvent<Func<GatewayOpCode, int?, string, object, Task>>(); | |||
@@ -352,7 +352,7 @@ namespace Discord.API | |||
if (payload != null) | |||
bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); | |||
await _requestQueue.SendAsync(new WebSocketRequest(_gatewayClient, bytes, true, options), group, bucketId, guildId).ConfigureAwait(false); | |||
await _sentGatewayMessageEvent.InvokeAsync((int)opCode).ConfigureAwait(false); | |||
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | |||
} | |||
//Auth | |||
@@ -11,28 +11,37 @@ using System.IO.Compression; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using System.Net.Sockets; | |||
using System.Net; | |||
namespace Discord.Audio | |||
{ | |||
public class DiscordVoiceAPIClient | |||
{ | |||
public const int MaxBitrate = 128; | |||
private const string Mode = "xsalsa20_poly1305"; | |||
public const string Mode = "xsalsa20_poly1305"; | |||
public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>(); | |||
public event Func<int, Task> SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<int, Task>> _sentGatewayMessageEvent = new AsyncEvent<Func<int, Task>>(); | |||
public event Func<VoiceOpCode, Task> SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<VoiceOpCode, Task>> _sentGatewayMessageEvent = new AsyncEvent<Func<VoiceOpCode, Task>>(); | |||
public event Func<Task> SentDiscovery { add { _sentDiscoveryEvent.Add(value); } remove { _sentDiscoveryEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<Task>> _sentDiscoveryEvent = new AsyncEvent<Func<Task>>(); | |||
public event Func<VoiceOpCode, object, Task> ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<VoiceOpCode, object, Task>> _receivedEvent = new AsyncEvent<Func<VoiceOpCode, object, Task>>(); | |||
public event Func<byte[], Task> ReceivedPacket { add { _receivedPacketEvent.Add(value); } remove { _receivedPacketEvent.Remove(value); } } | |||
private readonly AsyncEvent<Func<byte[], Task>> _receivedPacketEvent = new AsyncEvent<Func<byte[], Task>>(); | |||
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 JsonSerializer _serializer; | |||
private readonly IWebSocketClient _gatewayClient; | |||
private readonly IWebSocketClient _webSocketClient; | |||
private readonly SemaphoreSlim _connectionLock; | |||
private CancellationTokenSource _connectCancelToken; | |||
private UdpClient _udp; | |||
private IPEndPoint _udpEndpoint; | |||
private Task _udpRecieveTask; | |||
private bool _isDisposed; | |||
public ulong GuildId { get; } | |||
@@ -42,10 +51,11 @@ namespace Discord.Audio | |||
{ | |||
GuildId = guildId; | |||
_connectionLock = new SemaphoreSlim(1, 1); | |||
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | |||
_gatewayClient = webSocketProvider(); | |||
_webSocketClient = webSocketProvider(); | |||
//_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) | |||
_gatewayClient.BinaryMessage += async (data, index, count) => | |||
_webSocketClient.BinaryMessage += async (data, index, count) => | |||
{ | |||
using (var compressed = new MemoryStream(data, index + 2, count - 2)) | |||
using (var decompressed = new MemoryStream()) | |||
@@ -60,12 +70,12 @@ namespace Discord.Audio | |||
} | |||
} | |||
}; | |||
_gatewayClient.TextMessage += async text => | |||
_webSocketClient.TextMessage += async text => | |||
{ | |||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(text); | |||
await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); | |||
}; | |||
_gatewayClient.Closed += async ex => | |||
_webSocketClient.Closed += async ex => | |||
{ | |||
await DisconnectAsync().ConfigureAwait(false); | |||
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | |||
@@ -80,21 +90,29 @@ namespace Discord.Audio | |||
if (disposing) | |||
{ | |||
_connectCancelToken?.Dispose(); | |||
(_gatewayClient as IDisposable)?.Dispose(); | |||
(_webSocketClient as IDisposable)?.Dispose(); | |||
} | |||
_isDisposed = true; | |||
} | |||
} | |||
public void Dispose() => Dispose(true); | |||
public Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) | |||
public async Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) | |||
{ | |||
byte[] bytes = null; | |||
payload = new WebSocketMessage { Operation = (int)opCode, Payload = payload }; | |||
if (payload != null) | |||
bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); | |||
//TODO: Send | |||
return Task.CompletedTask; | |||
await _webSocketClient.SendAsync(bytes, 0, bytes.Length, true).ConfigureAwait(false); | |||
await _sentGatewayMessageEvent.InvokeAsync(opCode); | |||
} | |||
public async Task SendAsync(byte[] data, int bytes) | |||
{ | |||
if (_udpEndpoint != null) | |||
{ | |||
await _udp.SendAsync(data, bytes, _udpEndpoint).ConfigureAwait(false); | |||
await _sentDiscoveryEvent.InvokeAsync().ConfigureAwait(false); | |||
} | |||
} | |||
//WebSocket | |||
@@ -102,36 +120,56 @@ namespace Discord.Audio | |||
{ | |||
await SendAsync(VoiceOpCode.Heartbeat, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task SendIdentityAsync(ulong guildId, ulong userId, string sessionId, string token) | |||
public async Task SendIdentityAsync(ulong userId, string sessionId, string token) | |||
{ | |||
await SendAsync(VoiceOpCode.Identify, new IdentifyParams | |||
{ | |||
GuildId = guildId, | |||
GuildId = GuildId, | |||
UserId = userId, | |||
SessionId = sessionId, | |||
Token = token | |||
}); | |||
} | |||
public async Task SendSelectProtocol(string externalIp, int externalPort) | |||
{ | |||
await SendAsync(VoiceOpCode.SelectProtocol, new SelectProtocolParams | |||
{ | |||
Protocol = "udp", | |||
Data = new UdpProtocolInfo | |||
{ | |||
Address = externalIp, | |||
Port = externalPort, | |||
Mode = Mode | |||
} | |||
}); | |||
} | |||
public async Task SendSetSpeaking(bool value) | |||
{ | |||
await SendAsync(VoiceOpCode.Speaking, new SpeakingParams | |||
{ | |||
IsSpeaking = value, | |||
Delay = 0 | |||
}); | |||
} | |||
public async Task ConnectAsync(string url, ulong userId, string sessionId, string token) | |||
public async Task ConnectAsync(string url) | |||
{ | |||
await _connectionLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
await ConnectInternalAsync(url, userId, sessionId, token).ConfigureAwait(false); | |||
await ConnectInternalAsync(url).ConfigureAwait(false); | |||
} | |||
finally { _connectionLock.Release(); } | |||
} | |||
private async Task ConnectInternalAsync(string url, ulong userId, string sessionId, string token) | |||
private async Task ConnectInternalAsync(string url) | |||
{ | |||
ConnectionState = ConnectionState.Connecting; | |||
try | |||
{ | |||
_connectCancelToken = new CancellationTokenSource(); | |||
_gatewayClient.SetCancelToken(_connectCancelToken.Token); | |||
await _gatewayClient.ConnectAsync(url).ConfigureAwait(false); | |||
await SendIdentityAsync(GuildId, userId, sessionId, token).ConfigureAwait(false); | |||
_webSocketClient.SetCancelToken(_connectCancelToken.Token); | |||
await _webSocketClient.ConnectAsync(url).ConfigureAwait(false); | |||
_udpRecieveTask = ReceiveAsync(_connectCancelToken.Token); | |||
ConnectionState = ConnectionState.Connected; | |||
} | |||
@@ -159,11 +197,43 @@ namespace Discord.Audio | |||
try { _connectCancelToken?.Cancel(false); } | |||
catch { } | |||
await _gatewayClient.DisconnectAsync().ConfigureAwait(false); | |||
//Wait for tasks to complete | |||
await _udpRecieveTask.ConfigureAwait(false); | |||
await _webSocketClient.DisconnectAsync().ConfigureAwait(false); | |||
ConnectionState = ConnectionState.Disconnected; | |||
} | |||
//Udp | |||
public async Task SendDiscoveryAsync(uint ssrc) | |||
{ | |||
var packet = new byte[70]; | |||
packet[0] = (byte)(ssrc >> 24); | |||
packet[1] = (byte)(ssrc >> 16); | |||
packet[2] = (byte)(ssrc >> 8); | |||
packet[3] = (byte)(ssrc >> 0); | |||
await SendAsync(packet, 70).ConfigureAwait(false); | |||
} | |||
public void SetUdpEndpoint(IPEndPoint endpoint) | |||
{ | |||
_udpEndpoint = endpoint; | |||
} | |||
private async Task ReceiveAsync(CancellationToken cancelToken) | |||
{ | |||
var closeTask = Task.Delay(-1, cancelToken); | |||
while (!cancelToken.IsCancellationRequested) | |||
{ | |||
var receiveTask = _udp.ReceiveAsync(); | |||
var task = await Task.WhenAny(closeTask, receiveTask).ConfigureAwait(false); | |||
if (task == closeTask) | |||
break; | |||
await _receivedPacketEvent.InvokeAsync(receiveTask.Result.Buffer).ConfigureAwait(false); | |||
} | |||
} | |||
//Helpers | |||
private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); | |||
private string SerializeJson(object value) | |||
@@ -0,0 +1,16 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Voice | |||
{ | |||
public class ReadyEvent | |||
{ | |||
[JsonProperty("ssrc")] | |||
public uint SSRC { get; set; } | |||
[JsonProperty("port")] | |||
public ushort Port { get; set; } | |||
[JsonProperty("modes")] | |||
public string[] Modes { get; set; } | |||
[JsonProperty("heartbeat_interval")] | |||
public int HeartbeatInterval { get; set; } | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Voice | |||
{ | |||
public class SelectProtocolParams | |||
{ | |||
[JsonProperty("protocol")] | |||
public string Protocol { get; set; } | |||
[JsonProperty("data")] | |||
public UdpProtocolInfo Data { get; set; } | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Voice | |||
{ | |||
public class SessionDescriptionEvent | |||
{ | |||
[JsonProperty("secret_key")] | |||
public byte[] SecretKey { get; set; } | |||
[JsonProperty("mode")] | |||
public string Mode { get; set; } | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Voice | |||
{ | |||
public class SpeakingParams | |||
{ | |||
[JsonProperty("speaking")] | |||
public bool IsSpeaking { get; set; } | |||
[JsonProperty("delay")] | |||
public int Delay { get; set; } | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Voice | |||
{ | |||
public class UdpProtocolInfo | |||
{ | |||
[JsonProperty("address")] | |||
public string Address { get; set; } | |||
[JsonProperty("port")] | |||
public int Port { get; set; } | |||
[JsonProperty("mode")] | |||
public string Mode { get; set; } | |||
} | |||
} |
@@ -2,7 +2,11 @@ | |||
using Discord.Logging; | |||
using Discord.Net.Converters; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
@@ -29,7 +33,7 @@ namespace Discord.Audio | |||
} | |||
private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | |||
private readonly ILogger _webSocketLogger, _udpLogger; | |||
private readonly ILogger _audioLogger; | |||
#if BENCHMARK | |||
private readonly ILogger _benchmarkLogger; | |||
#endif | |||
@@ -42,6 +46,8 @@ namespace Discord.Audio | |||
private long _heartbeatTime; | |||
private string _url; | |||
private bool _isDisposed; | |||
private uint _ssrc; | |||
private byte[] _secretKey; | |||
public CachedGuild Guild { get; } | |||
public DiscordVoiceAPIClient ApiClient { get; private set; } | |||
@@ -51,12 +57,11 @@ namespace Discord.Audio | |||
private DiscordSocketClient Discord => Guild.Discord; | |||
/// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
internal AudioClient(CachedGuild guild) | |||
internal AudioClient(CachedGuild guild, int id) | |||
{ | |||
Guild = guild; | |||
_webSocketLogger = Discord.LogManager.CreateLogger("Audio"); | |||
_udpLogger = Discord.LogManager.CreateLogger("AudioUDP"); | |||
_audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}"); | |||
#if BENCHMARK | |||
_benchmarkLogger = logManager.CreateLogger("Benchmark"); | |||
#endif | |||
@@ -66,20 +71,22 @@ namespace Discord.Audio | |||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||
_serializer.Error += (s, e) => | |||
{ | |||
_webSocketLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | |||
_audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); | |||
e.ErrorContext.Handled = true; | |||
}; | |||
ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider); | |||
ApiClient.SentGatewayMessage += async opCode => await _webSocketLogger.DebugAsync($"Sent {(VoiceOpCode)opCode}").ConfigureAwait(false); | |||
ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||
ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | |||
ApiClient.ReceivedEvent += ProcessMessageAsync; | |||
ApiClient.ReceivedPacket += ProcessPacketAsync; | |||
ApiClient.Disconnected += async ex => | |||
{ | |||
if (ex != null) | |||
await _webSocketLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false); | |||
await _audioLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false); | |||
else | |||
await _webSocketLogger.WarningAsync($"Connection Closed").ConfigureAwait(false); | |||
await _audioLogger.WarningAsync($"Connection Closed").ConfigureAwait(false); | |||
}; | |||
} | |||
@@ -100,19 +107,20 @@ namespace Discord.Audio | |||
await DisconnectInternalAsync(null).ConfigureAwait(false); | |||
ConnectionState = ConnectionState.Connecting; | |||
await _webSocketLogger.InfoAsync("Connecting").ConfigureAwait(false); | |||
await _audioLogger.InfoAsync("Connecting").ConfigureAwait(false); | |||
try | |||
{ | |||
_url = url; | |||
_connectTask = new TaskCompletionSource<bool>(); | |||
_cancelToken = new CancellationTokenSource(); | |||
await ApiClient.ConnectAsync(url, userId, sessionId, token).ConfigureAwait(false); | |||
await _connectedEvent.InvokeAsync().ConfigureAwait(false); | |||
await ApiClient.ConnectAsync("wss://" + url).ConfigureAwait(false); | |||
await ApiClient.SendIdentityAsync(userId, sessionId, token).ConfigureAwait(false); | |||
await _connectTask.Task.ConfigureAwait(false); | |||
await _connectedEvent.InvokeAsync().ConfigureAwait(false); | |||
ConnectionState = ConnectionState.Connected; | |||
await _webSocketLogger.InfoAsync("Connected").ConfigureAwait(false); | |||
await _audioLogger.InfoAsync("Connected").ConfigureAwait(false); | |||
} | |||
catch (Exception) | |||
{ | |||
@@ -143,7 +151,7 @@ namespace Discord.Audio | |||
{ | |||
if (ConnectionState == ConnectionState.Disconnected) return; | |||
ConnectionState = ConnectionState.Disconnecting; | |||
await _webSocketLogger.InfoAsync("Disconnecting").ConfigureAwait(false); | |||
await _audioLogger.InfoAsync("Disconnecting").ConfigureAwait(false); | |||
//Signal tasks to complete | |||
try { _cancelToken.Cancel(); } catch { } | |||
@@ -158,7 +166,7 @@ namespace Discord.Audio | |||
_heartbeatTask = null; | |||
ConnectionState = ConnectionState.Disconnected; | |||
await _webSocketLogger.InfoAsync("Disconnected").ConfigureAwait(false); | |||
await _audioLogger.InfoAsync("Disconnected").ConfigureAwait(false); | |||
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | |||
} | |||
@@ -174,25 +182,49 @@ namespace Discord.Audio | |||
{ | |||
switch (opCode) | |||
{ | |||
/*case VoiceOpCode.Ready: | |||
case VoiceOpCode.Ready: | |||
{ | |||
await _webSocketLogger.DebugAsync("Received Ready").ConfigureAwait(false); | |||
await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | |||
_ssrc = data.SSRC; | |||
if (!data.Modes.Contains(DiscordVoiceAPIClient.Mode)) | |||
throw new InvalidOperationException($"Discord does not support {DiscordVoiceAPIClient.Mode}"); | |||
_heartbeatTime = 0; | |||
_heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token); | |||
var entry = await Dns.GetHostEntryAsync(_url).ConfigureAwait(false); | |||
ApiClient.SetUdpEndpoint(new IPEndPoint(entry.AddressList[0], data.Port)); | |||
await ApiClient.SendDiscoveryAsync(_ssrc).ConfigureAwait(false); | |||
} | |||
break;*/ | |||
break; | |||
case VoiceOpCode.SessionDescription: | |||
{ | |||
await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<SessionDescriptionEvent>(_serializer); | |||
if (data.Mode != DiscordVoiceAPIClient.Mode) | |||
throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | |||
_secretKey = data.SecretKey; | |||
await ApiClient.SendSetSpeaking(true).ConfigureAwait(false); | |||
_connectTask.TrySetResult(true); | |||
} | |||
break; | |||
case VoiceOpCode.HeartbeatAck: | |||
{ | |||
await _webSocketLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); | |||
await _audioLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); | |||
var heartbeatTime = _heartbeatTime; | |||
if (heartbeatTime != 0) | |||
{ | |||
int latency = (int)(Environment.TickCount - _heartbeatTime); | |||
_heartbeatTime = 0; | |||
await _webSocketLogger.VerboseAsync($"Latency = {latency} ms").ConfigureAwait(false); | |||
await _audioLogger.VerboseAsync($"Latency = {latency} ms").ConfigureAwait(false); | |||
int before = Latency; | |||
Latency = latency; | |||
@@ -202,13 +234,13 @@ namespace Discord.Audio | |||
} | |||
break; | |||
default: | |||
await _webSocketLogger.WarningAsync($"Unknown OpCode ({opCode})").ConfigureAwait(false); | |||
await _audioLogger.WarningAsync($"Unknown OpCode ({opCode})").ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
await _webSocketLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false); | |||
await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false); | |||
return; | |||
} | |||
#if BENCHMARK | |||
@@ -222,6 +254,27 @@ namespace Discord.Audio | |||
#endif | |||
} | |||
private async Task ProcessPacketAsync(byte[] packet) | |||
{ | |||
if (!_connectTask.Task.IsCompleted) | |||
{ | |||
if (packet.Length == 70) | |||
{ | |||
string ip; | |||
int port; | |||
try | |||
{ | |||
ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0'); | |||
port = packet[68] | packet[69] << 8; | |||
} | |||
catch { return; } | |||
await _audioLogger.DebugAsync("Received Discovery").ConfigureAwait(false); | |||
await ApiClient.SendSelectProtocol(ip, port); | |||
} | |||
} | |||
} | |||
private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken) | |||
{ | |||
//Clean this up when Discord's session patch is live | |||
@@ -235,7 +288,7 @@ namespace Discord.Audio | |||
{ | |||
if (ConnectionState == ConnectionState.Connected) | |||
{ | |||
await _webSocketLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false); | |||
await _audioLogger.WarningAsync("Server missed last heartbeat").ConfigureAwait(false); | |||
await DisconnectInternalAsync(new Exception("Server missed last heartbeat")).ConfigureAwait(false); | |||
return; | |||
} | |||
@@ -35,8 +35,7 @@ namespace Discord | |||
public LoginState LoginState { get; private set; } | |||
/// <summary> Creates a new REST-only discord client. </summary> | |||
public DiscordClient() | |||
: this(new DiscordConfig()) { } | |||
public DiscordClient() : this(new DiscordConfig()) { } | |||
/// <summary> Creates a new REST-only discord client. </summary> | |||
public DiscordClient(DiscordConfig config) | |||
{ | |||
@@ -35,6 +35,7 @@ namespace Discord | |||
private bool _isReconnecting; | |||
private int _unavailableGuilds; | |||
private long _lastGuildAvailableTime; | |||
private int _nextAudioId; | |||
/// <summary> Gets the shard if of this client. </summary> | |||
public int ShardId { get; } | |||
@@ -74,6 +75,7 @@ namespace Discord | |||
LargeThreshold = config.LargeThreshold; | |||
AudioMode = config.AudioMode; | |||
WebSocketProvider = config.WebSocketProvider; | |||
_nextAudioId = 1; | |||
_gatewayLogger = LogManager.CreateLogger("Gateway"); | |||
#if BENCHMARK | |||
@@ -87,7 +89,7 @@ namespace Discord | |||
e.ErrorContext.Handled = true; | |||
}; | |||
ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {(GatewayOpCode)opCode}").ConfigureAwait(false); | |||
ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||
ApiClient.ReceivedGatewayEvent += ProcessMessageAsync; | |||
ApiClient.Disconnected += async ex => | |||
{ | |||
@@ -1173,8 +1175,8 @@ namespace Discord | |||
var guild = DataStore.GetGuild(data.GuildId); | |||
if (guild != null) | |||
{ | |||
string endpoint = "wss://" + data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':')); | |||
await guild.ConnectAudio(endpoint, data.Token).ConfigureAwait(false); | |||
string endpoint = data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':')); | |||
var _ = guild.ConnectAudio(_nextAudioId++, endpoint, data.Token).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
@@ -261,7 +261,7 @@ namespace Discord | |||
return null; | |||
} | |||
public async Task ConnectAudio(string url, string token) | |||
public async Task ConnectAudio(int id, string url, string token) | |||
{ | |||
AudioClient audioClient; | |||
await _audioLock.WaitAsync().ConfigureAwait(false); | |||
@@ -271,7 +271,7 @@ namespace Discord | |||
audioClient = AudioClient; | |||
if (audioClient == null) | |||
{ | |||
audioClient = new AudioClient(this); | |||
audioClient = new AudioClient(this, id); | |||
audioClient.Disconnected += async ex => | |||
{ | |||
await _audioLock.WaitAsync().ConfigureAwait(false); | |||
@@ -26,6 +26,8 @@ | |||
"System.IO.Compression": "4.1.0", | |||
"System.IO.FileSystem": "4.0.1", | |||
"System.Net.Http": "4.1.0", | |||
"System.Net.NameResolution": "4.0.0", | |||
"System.Net.Sockets": "4.1.0", | |||
"System.Net.WebSockets.Client": "4.0.0", | |||
"System.Reflection.Extensions": "4.0.1", | |||
"System.Runtime.InteropServices": "4.1.0", | |||