Finished AudioClient connection handshake
This commit is contained in:
@@ -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)
|
||||
|
||||
16
src/Discord.Net/API/Voice/ReadyEvent.cs
Normal file
16
src/Discord.Net/API/Voice/ReadyEvent.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/API/Voice/SelectProtocolParams.cs
Normal file
12
src/Discord.Net/API/Voice/SelectProtocolParams.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/API/Voice/SessionDescriptionEvent.cs
Normal file
12
src/Discord.Net/API/Voice/SessionDescriptionEvent.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/API/Voice/SpeakingParams.cs
Normal file
12
src/Discord.Net/API/Voice/SpeakingParams.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
14
src/Discord.Net/API/Voice/UdpProtocolInfo.cs
Normal file
14
src/Discord.Net/API/Voice/UdpProtocolInfo.cs
Normal file
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user