Browse Source

Finished AudioClient connection handshake

tags/1.0-rc
RogueException 9 years ago
parent
commit
689c9ff31a
12 changed files with 247 additions and 55 deletions
  1. +3
    -3
      src/Discord.Net/API/DiscordAPIClient.cs
  2. +92
    -22
      src/Discord.Net/API/DiscordVoiceAPIClient.cs
  3. +16
    -0
      src/Discord.Net/API/Voice/ReadyEvent.cs
  4. +12
    -0
      src/Discord.Net/API/Voice/SelectProtocolParams.cs
  5. +12
    -0
      src/Discord.Net/API/Voice/SessionDescriptionEvent.cs
  6. +12
    -0
      src/Discord.Net/API/Voice/SpeakingParams.cs
  7. +14
    -0
      src/Discord.Net/API/Voice/UdpProtocolInfo.cs
  8. +76
    -23
      src/Discord.Net/Audio/AudioClient.cs
  9. +1
    -2
      src/Discord.Net/DiscordClient.cs
  10. +5
    -3
      src/Discord.Net/DiscordSocketClient.cs
  11. +2
    -2
      src/Discord.Net/Entities/WebSocket/CachedGuild.cs
  12. +2
    -0
      src/Discord.Net/project.json

+ 3
- 3
src/Discord.Net/API/DiscordAPIClient.cs View File

@@ -27,8 +27,8 @@ namespace Discord.API


public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } 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>>(); 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); } } 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>>(); 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) if (payload != null)
bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); bytes = Encoding.UTF8.GetBytes(SerializeJson(payload));
await _requestQueue.SendAsync(new WebSocketRequest(_gatewayClient, bytes, true, options), group, bucketId, guildId).ConfigureAwait(false); 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 //Auth


+ 92
- 22
src/Discord.Net/API/DiscordVoiceAPIClient.cs View File

@@ -11,28 +11,37 @@ using System.IO.Compression;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;


namespace Discord.Audio namespace Discord.Audio
{ {
public class DiscordVoiceAPIClient public class DiscordVoiceAPIClient
{ {
public const int MaxBitrate = 128; 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); } } 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>>(); 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); } } 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>>(); 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); } } 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 JsonSerializer _serializer; private readonly JsonSerializer _serializer;
private readonly IWebSocketClient _gatewayClient;
private readonly IWebSocketClient _webSocketClient;
private readonly SemaphoreSlim _connectionLock; private readonly SemaphoreSlim _connectionLock;
private CancellationTokenSource _connectCancelToken; private CancellationTokenSource _connectCancelToken;
private UdpClient _udp;
private IPEndPoint _udpEndpoint;
private Task _udpRecieveTask;
private bool _isDisposed; private bool _isDisposed;


public ulong GuildId { get; } public ulong GuildId { get; }
@@ -42,10 +51,11 @@ namespace Discord.Audio
{ {
GuildId = guildId; GuildId = guildId;
_connectionLock = new SemaphoreSlim(1, 1); _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.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 compressed = new MemoryStream(data, index + 2, count - 2))
using (var decompressed = new MemoryStream()) 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); var msg = JsonConvert.DeserializeObject<WebSocketMessage>(text);
await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false);
}; };
_gatewayClient.Closed += async ex =>
_webSocketClient.Closed += async ex =>
{ {
await DisconnectAsync().ConfigureAwait(false); await DisconnectAsync().ConfigureAwait(false);
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
@@ -80,21 +90,29 @@ namespace Discord.Audio
if (disposing) if (disposing)
{ {
_connectCancelToken?.Dispose(); _connectCancelToken?.Dispose();
(_gatewayClient as IDisposable)?.Dispose();
(_webSocketClient as IDisposable)?.Dispose();
} }
_isDisposed = true; _isDisposed = true;
} }
} }
public void Dispose() => Dispose(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; byte[] bytes = null;
payload = new WebSocketMessage { Operation = (int)opCode, Payload = payload }; payload = new WebSocketMessage { Operation = (int)opCode, Payload = payload };
if (payload != null) if (payload != null)
bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); 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 //WebSocket
@@ -102,36 +120,56 @@ namespace Discord.Audio
{ {
await SendAsync(VoiceOpCode.Heartbeat, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), options: options).ConfigureAwait(false); 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 await SendAsync(VoiceOpCode.Identify, new IdentifyParams
{ {
GuildId = guildId,
GuildId = GuildId,
UserId = userId, UserId = userId,
SessionId = sessionId, SessionId = sessionId,
Token = token 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); await _connectionLock.WaitAsync().ConfigureAwait(false);
try try
{ {
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, ulong userId, string sessionId, string token)
private async Task ConnectInternalAsync(string url)
{ {
ConnectionState = ConnectionState.Connecting; ConnectionState = ConnectionState.Connecting;
try try
{ {
_connectCancelToken = new CancellationTokenSource(); _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; ConnectionState = ConnectionState.Connected;
} }
@@ -159,11 +197,43 @@ namespace Discord.Audio
try { _connectCancelToken?.Cancel(false); } try { _connectCancelToken?.Cancel(false); }
catch { } catch { }


await _gatewayClient.DisconnectAsync().ConfigureAwait(false);
//Wait for tasks to complete
await _udpRecieveTask.ConfigureAwait(false);

await _webSocketClient.DisconnectAsync().ConfigureAwait(false);


ConnectionState = ConnectionState.Disconnected; 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 //Helpers
private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2);
private string SerializeJson(object value) private string SerializeJson(object value)


+ 16
- 0
src/Discord.Net/API/Voice/ReadyEvent.cs View 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
- 0
src/Discord.Net/API/Voice/SelectProtocolParams.cs View 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
- 0
src/Discord.Net/API/Voice/SessionDescriptionEvent.cs View 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
- 0
src/Discord.Net/API/Voice/SpeakingParams.cs View 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
- 0
src/Discord.Net/API/Voice/UdpProtocolInfo.cs View 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; }
}
}

+ 76
- 23
src/Discord.Net/Audio/AudioClient.cs View File

@@ -2,7 +2,11 @@
using Discord.Logging; using Discord.Logging;
using Discord.Net.Converters; using Discord.Net.Converters;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System; using System;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; 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 AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>();


private readonly ILogger _webSocketLogger, _udpLogger;
private readonly ILogger _audioLogger;
#if BENCHMARK #if BENCHMARK
private readonly ILogger _benchmarkLogger; private readonly ILogger _benchmarkLogger;
#endif #endif
@@ -42,6 +46,8 @@ namespace Discord.Audio
private long _heartbeatTime; private long _heartbeatTime;
private string _url; private string _url;
private bool _isDisposed; private bool _isDisposed;
private uint _ssrc;
private byte[] _secretKey;


public CachedGuild Guild { get; } public CachedGuild Guild { get; }
public DiscordVoiceAPIClient ApiClient { get; private set; } public DiscordVoiceAPIClient ApiClient { get; private set; }
@@ -51,12 +57,11 @@ namespace Discord.Audio
private DiscordSocketClient Discord => Guild.Discord; private DiscordSocketClient Discord => Guild.Discord;


/// <summary> Creates a new REST/WebSocket discord client. </summary> /// <summary> Creates a new REST/WebSocket discord client. </summary>
internal AudioClient(CachedGuild guild)
internal AudioClient(CachedGuild guild, int id)
{ {
Guild = guild; Guild = guild;


_webSocketLogger = Discord.LogManager.CreateLogger("Audio");
_udpLogger = Discord.LogManager.CreateLogger("AudioUDP");
_audioLogger = Discord.LogManager.CreateLogger($"Audio #{id}");
#if BENCHMARK #if BENCHMARK
_benchmarkLogger = logManager.CreateLogger("Benchmark"); _benchmarkLogger = logManager.CreateLogger("Benchmark");
#endif #endif
@@ -66,20 +71,22 @@ namespace Discord.Audio
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
_serializer.Error += (s, e) => _serializer.Error += (s, e) =>
{ {
_webSocketLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult();
_audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult();
e.ErrorContext.Handled = true; e.ErrorContext.Handled = true;
}; };
ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.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 _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false);
ApiClient.ReceivedEvent += ProcessMessageAsync; ApiClient.ReceivedEvent += ProcessMessageAsync;
ApiClient.ReceivedPacket += ProcessPacketAsync;
ApiClient.Disconnected += async ex => ApiClient.Disconnected += async ex =>
{ {
if (ex != null) if (ex != null)
await _webSocketLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
await _audioLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
else 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); await DisconnectInternalAsync(null).ConfigureAwait(false);


ConnectionState = ConnectionState.Connecting; ConnectionState = ConnectionState.Connecting;
await _webSocketLogger.InfoAsync("Connecting").ConfigureAwait(false);
await _audioLogger.InfoAsync("Connecting").ConfigureAwait(false);
try try
{ {
_url = url; _url = url;
_connectTask = new TaskCompletionSource<bool>(); _connectTask = new TaskCompletionSource<bool>();
_cancelToken = new CancellationTokenSource(); _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 _connectTask.Task.ConfigureAwait(false);


await _connectedEvent.InvokeAsync().ConfigureAwait(false);
ConnectionState = ConnectionState.Connected; ConnectionState = ConnectionState.Connected;
await _webSocketLogger.InfoAsync("Connected").ConfigureAwait(false);
await _audioLogger.InfoAsync("Connected").ConfigureAwait(false);
} }
catch (Exception) catch (Exception)
{ {
@@ -143,7 +151,7 @@ namespace Discord.Audio
{ {
if (ConnectionState == ConnectionState.Disconnected) return; if (ConnectionState == ConnectionState.Disconnected) return;
ConnectionState = ConnectionState.Disconnecting; ConnectionState = ConnectionState.Disconnecting;
await _webSocketLogger.InfoAsync("Disconnecting").ConfigureAwait(false);
await _audioLogger.InfoAsync("Disconnecting").ConfigureAwait(false);


//Signal tasks to complete //Signal tasks to complete
try { _cancelToken.Cancel(); } catch { } try { _cancelToken.Cancel(); } catch { }
@@ -158,7 +166,7 @@ namespace Discord.Audio
_heartbeatTask = null; _heartbeatTask = null;


ConnectionState = ConnectionState.Disconnected; ConnectionState = ConnectionState.Disconnected;
await _webSocketLogger.InfoAsync("Disconnected").ConfigureAwait(false);
await _audioLogger.InfoAsync("Disconnected").ConfigureAwait(false);


await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
} }
@@ -174,25 +182,49 @@ namespace Discord.Audio
{ {
switch (opCode) 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); 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; _heartbeatTime = 0;
_heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _cancelToken.Token); _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: case VoiceOpCode.HeartbeatAck:
{ {
await _webSocketLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false);
await _audioLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false);


var heartbeatTime = _heartbeatTime; var heartbeatTime = _heartbeatTime;
if (heartbeatTime != 0) if (heartbeatTime != 0)
{ {
int latency = (int)(Environment.TickCount - _heartbeatTime); int latency = (int)(Environment.TickCount - _heartbeatTime);
_heartbeatTime = 0; _heartbeatTime = 0;
await _webSocketLogger.VerboseAsync($"Latency = {latency} ms").ConfigureAwait(false);
await _audioLogger.VerboseAsync($"Latency = {latency} ms").ConfigureAwait(false);


int before = Latency; int before = Latency;
Latency = latency; Latency = latency;
@@ -202,13 +234,13 @@ namespace Discord.Audio
} }
break; break;
default: default:
await _webSocketLogger.WarningAsync($"Unknown OpCode ({opCode})").ConfigureAwait(false);
await _audioLogger.WarningAsync($"Unknown OpCode ({opCode})").ConfigureAwait(false);
return; return;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await _webSocketLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false);
await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false);
return; return;
} }
#if BENCHMARK #if BENCHMARK
@@ -222,6 +254,27 @@ namespace Discord.Audio
#endif #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) private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken)
{ {
//Clean this up when Discord's session patch is live //Clean this up when Discord's session patch is live
@@ -235,7 +288,7 @@ namespace Discord.Audio
{ {
if (ConnectionState == ConnectionState.Connected) 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); await DisconnectInternalAsync(new Exception("Server missed last heartbeat")).ConfigureAwait(false);
return; return;
} }


+ 1
- 2
src/Discord.Net/DiscordClient.cs View File

@@ -35,8 +35,7 @@ namespace Discord
public LoginState LoginState { get; private set; } public LoginState LoginState { get; private set; }


/// <summary> Creates a new REST-only discord client. </summary> /// <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> /// <summary> Creates a new REST-only discord client. </summary>
public DiscordClient(DiscordConfig config) public DiscordClient(DiscordConfig config)
{ {


+ 5
- 3
src/Discord.Net/DiscordSocketClient.cs View File

@@ -35,6 +35,7 @@ namespace Discord
private bool _isReconnecting; private bool _isReconnecting;
private int _unavailableGuilds; private int _unavailableGuilds;
private long _lastGuildAvailableTime; private long _lastGuildAvailableTime;
private int _nextAudioId;


/// <summary> Gets the shard if of this client. </summary> /// <summary> Gets the shard if of this client. </summary>
public int ShardId { get; } public int ShardId { get; }
@@ -74,6 +75,7 @@ namespace Discord
LargeThreshold = config.LargeThreshold; LargeThreshold = config.LargeThreshold;
AudioMode = config.AudioMode; AudioMode = config.AudioMode;
WebSocketProvider = config.WebSocketProvider; WebSocketProvider = config.WebSocketProvider;
_nextAudioId = 1;


_gatewayLogger = LogManager.CreateLogger("Gateway"); _gatewayLogger = LogManager.CreateLogger("Gateway");
#if BENCHMARK #if BENCHMARK
@@ -87,7 +89,7 @@ namespace Discord
e.ErrorContext.Handled = true; 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.ReceivedGatewayEvent += ProcessMessageAsync;
ApiClient.Disconnected += async ex => ApiClient.Disconnected += async ex =>
{ {
@@ -1173,8 +1175,8 @@ namespace Discord
var guild = DataStore.GetGuild(data.GuildId); var guild = DataStore.GetGuild(data.GuildId);
if (guild != null) 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 else
{ {


+ 2
- 2
src/Discord.Net/Entities/WebSocket/CachedGuild.cs View File

@@ -261,7 +261,7 @@ namespace Discord
return null; return null;
} }


public async Task ConnectAudio(string url, string token)
public async Task ConnectAudio(int id, string url, string token)
{ {
AudioClient audioClient; AudioClient audioClient;
await _audioLock.WaitAsync().ConfigureAwait(false); await _audioLock.WaitAsync().ConfigureAwait(false);
@@ -271,7 +271,7 @@ namespace Discord
audioClient = AudioClient; audioClient = AudioClient;
if (audioClient == null) if (audioClient == null)
{ {
audioClient = new AudioClient(this);
audioClient = new AudioClient(this, id);
audioClient.Disconnected += async ex => audioClient.Disconnected += async ex =>
{ {
await _audioLock.WaitAsync().ConfigureAwait(false); await _audioLock.WaitAsync().ConfigureAwait(false);


+ 2
- 0
src/Discord.Net/project.json View File

@@ -26,6 +26,8 @@
"System.IO.Compression": "4.1.0", "System.IO.Compression": "4.1.0",
"System.IO.FileSystem": "4.0.1", "System.IO.FileSystem": "4.0.1",
"System.Net.Http": "4.1.0", "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.Net.WebSockets.Client": "4.0.0",
"System.Reflection.Extensions": "4.0.1", "System.Reflection.Extensions": "4.0.1",
"System.Runtime.InteropServices": "4.1.0", "System.Runtime.InteropServices": "4.1.0",


Loading…
Cancel
Save