diff --git a/src/Discord.Net/API/DiscordAPIClient.cs b/src/Discord.Net/API/DiscordAPIClient.cs index 55f0cc009..a8644f97b 100644 --- a/src/Discord.Net/API/DiscordAPIClient.cs +++ b/src/Discord.Net/API/DiscordAPIClient.cs @@ -27,8 +27,8 @@ namespace Discord.API public event Func SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } private readonly AsyncEvent> _sentRequestEvent = new AsyncEvent>(); - public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } - private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); + public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } + private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); public event Func ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } private readonly AsyncEvent> _receivedGatewayEvent = new AsyncEvent>(); @@ -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 diff --git a/src/Discord.Net/API/DiscordVoiceAPIClient.cs b/src/Discord.Net/API/DiscordVoiceAPIClient.cs index ddb8f2b6b..ecbd47778 100644 --- a/src/Discord.Net/API/DiscordVoiceAPIClient.cs +++ b/src/Discord.Net/API/DiscordVoiceAPIClient.cs @@ -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 SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } private readonly AsyncEvent> _sentRequestEvent = new AsyncEvent>(); - public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } - private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); + public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } + private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); + public event Func SentDiscovery { add { _sentDiscoveryEvent.Add(value); } remove { _sentDiscoveryEvent.Remove(value); } } + private readonly AsyncEvent> _sentDiscoveryEvent = new AsyncEvent>(); public event Func ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } private readonly AsyncEvent> _receivedEvent = new AsyncEvent>(); + public event Func ReceivedPacket { add { _receivedPacketEvent.Add(value); } remove { _receivedPacketEvent.Remove(value); } } + private readonly AsyncEvent> _receivedPacketEvent = new AsyncEvent>(); public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); 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(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) diff --git a/src/Discord.Net/API/Voice/ReadyEvent.cs b/src/Discord.Net/API/Voice/ReadyEvent.cs new file mode 100644 index 000000000..7914fd825 --- /dev/null +++ b/src/Discord.Net/API/Voice/ReadyEvent.cs @@ -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; } + } +} diff --git a/src/Discord.Net/API/Voice/SelectProtocolParams.cs b/src/Discord.Net/API/Voice/SelectProtocolParams.cs new file mode 100644 index 000000000..b7992ba5a --- /dev/null +++ b/src/Discord.Net/API/Voice/SelectProtocolParams.cs @@ -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; } + } +} diff --git a/src/Discord.Net/API/Voice/SessionDescriptionEvent.cs b/src/Discord.Net/API/Voice/SessionDescriptionEvent.cs new file mode 100644 index 000000000..6a2846f8f --- /dev/null +++ b/src/Discord.Net/API/Voice/SessionDescriptionEvent.cs @@ -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; } + } +} diff --git a/src/Discord.Net/API/Voice/SpeakingParams.cs b/src/Discord.Net/API/Voice/SpeakingParams.cs new file mode 100644 index 000000000..01fd001b0 --- /dev/null +++ b/src/Discord.Net/API/Voice/SpeakingParams.cs @@ -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; } + } +} diff --git a/src/Discord.Net/API/Voice/UdpProtocolInfo.cs b/src/Discord.Net/API/Voice/UdpProtocolInfo.cs new file mode 100644 index 000000000..d31bbd8db --- /dev/null +++ b/src/Discord.Net/API/Voice/UdpProtocolInfo.cs @@ -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; } + } +} diff --git a/src/Discord.Net/Audio/AudioClient.cs b/src/Discord.Net/Audio/AudioClient.cs index b59f3d267..210cce347 100644 --- a/src/Discord.Net/Audio/AudioClient.cs +++ b/src/Discord.Net/Audio/AudioClient.cs @@ -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> _latencyUpdatedEvent = new AsyncEvent>(); - 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; /// Creates a new REST/WebSocket discord client. - 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(); _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(_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(_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; } diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 4cf53d2a3..7231db649 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -35,8 +35,7 @@ namespace Discord public LoginState LoginState { get; private set; } /// Creates a new REST-only discord client. - public DiscordClient() - : this(new DiscordConfig()) { } + public DiscordClient() : this(new DiscordConfig()) { } /// Creates a new REST-only discord client. public DiscordClient(DiscordConfig config) { diff --git a/src/Discord.Net/DiscordSocketClient.cs b/src/Discord.Net/DiscordSocketClient.cs index 7bb2e93a6..c1abfb6d3 100644 --- a/src/Discord.Net/DiscordSocketClient.cs +++ b/src/Discord.Net/DiscordSocketClient.cs @@ -35,6 +35,7 @@ namespace Discord private bool _isReconnecting; private int _unavailableGuilds; private long _lastGuildAvailableTime; + private int _nextAudioId; /// Gets the shard if of this client. 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 { diff --git a/src/Discord.Net/Entities/WebSocket/CachedGuild.cs b/src/Discord.Net/Entities/WebSocket/CachedGuild.cs index d75ba4bab..d4c5153b4 100644 --- a/src/Discord.Net/Entities/WebSocket/CachedGuild.cs +++ b/src/Discord.Net/Entities/WebSocket/CachedGuild.cs @@ -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); diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 76133d6d7..ceac7be0a 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -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",