From 23639b227ac5574299c08a8253ce534312f6947b Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 30 Dec 2015 05:12:54 -0400 Subject: [PATCH] Readded basic voice support --- src/Discord.Net.Audio/AudioService.cs | 11 +- src/Discord.Net.Audio/DiscordAudioClient.cs | 111 +++++++++++------- .../Net/WebSockets/VoiceWebSocket.cs | 21 +--- .../GatewaySocket/Commands/UpdateVoice.cs | 2 +- src/Discord.Net/DiscordClient.cs | 25 ++-- .../Net/WebSockets/GatewaySocket.cs | 7 +- src/Discord.Net/Net/WebSockets/WebSocket.cs | 4 +- 7 files changed, 95 insertions(+), 86 deletions(-) diff --git a/src/Discord.Net.Audio/AudioService.cs b/src/Discord.Net.Audio/AudioService.cs index c42db17ef..1f41fb3e3 100644 --- a/src/Discord.Net.Audio/AudioService.cs +++ b/src/Discord.Net.Audio/AudioService.cs @@ -49,7 +49,7 @@ namespace Discord.Audio private DiscordAudioClient _defaultClient; private ConcurrentDictionary _voiceClients; private ConcurrentDictionary _talkingUsers; - private int _nextClientId; + //private int _nextClientId; internal DiscordClient Client => _client; private DiscordClient _client; @@ -143,13 +143,14 @@ namespace Discord.Audio _defaultClient.SetServerId(server.Id); return Task.FromResult(_defaultClient); } + else + throw new InvalidOperationException("Multiserver voice is not currently supported"); - var client = _voiceClients.GetOrAdd(server.Id, _ => + /*var client = _voiceClients.GetOrAdd(server.Id, _ => { int id = unchecked(++_nextClientId); var logger = Client.Log.CreateLogger($"Voice #{id}"); - GatewaySocket gatewaySocket = null; - var voiceClient = new DiscordAudioClient(this, id, logger, gatewaySocket); + var voiceClient = new DiscordAudioClient(this, id, logger, Client.GatewaySocket); voiceClient.SetServerId(server.Id); voiceClient.VoiceSocket.OnPacket += (s, e) => @@ -165,7 +166,7 @@ namespace Discord.Audio return voiceClient; }); //await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false); - return Task.FromResult(client); + return Task.FromResult(client);*/ } public async Task Join(Channel channel) diff --git a/src/Discord.Net.Audio/DiscordAudioClient.cs b/src/Discord.Net.Audio/DiscordAudioClient.cs index 56449433b..2665dc087 100644 --- a/src/Discord.Net.Audio/DiscordAudioClient.cs +++ b/src/Discord.Net.Audio/DiscordAudioClient.cs @@ -1,18 +1,20 @@ -using Discord.API; -using Discord.API.Client.GatewaySocket; +using Discord.API.Client.GatewaySocket; using Discord.Logging; using Discord.Net.WebSockets; using Newtonsoft.Json; using System; +using System.Threading; using System.Threading.Tasks; namespace Discord.Audio { public partial class DiscordAudioClient - { - private JsonSerializer _serializer; + { + private readonly Semaphore _connectionLock; + private readonly JsonSerializer _serializer; + private CancellationTokenSource _cancelTokenSource; - internal AudioService Service { get; } + internal AudioService Service { get; } internal Logger Logger { get; } public int Id { get; } public GatewaySocket GatewaySocket { get; } @@ -26,6 +28,9 @@ namespace Discord.Audio Service = service; Id = id; Logger = logger; + GatewaySocket = gatewaySocket; + + _connectionLock = new Semaphore(1, 1); _serializer = new JsonSerializer(); _serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; @@ -35,7 +40,6 @@ namespace Discord.Audio Logger.Error("Serialization Failed", e.ErrorContext.Error); }; - GatewaySocket = gatewaySocket; VoiceSocket = new VoiceWebSocket(service.Client, this, _serializer, logger); /*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); @@ -72,33 +76,7 @@ namespace Discord.Audio _voiceSocket.ParentCancelToken = _cancelToken; };*/ - GatewaySocket.ReceivedDispatch += async (s, e) => - { - try - { - switch (e.Type) - { - case "VOICE_SERVER_UPDATE": - { - var data = e.Payload.ToObject(_serializer); - var serverId = data.GuildId; - - if (serverId == ServerId) - { - var client = Service.Client; - VoiceSocket.Token = data.Token; - VoiceSocket.Host = "wss://" + e.Payload.Value("endpoint").Split(':')[0]; - await VoiceSocket.Connect().ConfigureAwait(false); - } - } - break; - } - } - catch (Exception ex) - { - Logger.Error($"Error handling {e.Type} event", ex); - } - }; + GatewaySocket.ReceivedDispatch += OnReceivedDispatch; } @@ -106,22 +84,71 @@ namespace Discord.Audio { VoiceSocket.ServerId = serverId; } - public async Task Join(Channel channel) + public Task Join(Channel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); ulong? serverId = channel.Server?.Id; if (serverId != ServerId) throw new InvalidOperationException("Cannot join a channel on a different server than this voice client."); - //CheckReady(checkVoice: true); + //CheckReady(checkVoice: true); - await VoiceSocket.Disconnect().ConfigureAwait(false); - VoiceSocket.ChannelId = channel.Id; - GatewaySocket.SendUpdateVoice(channel.Server.Id, channel.Id, - (Service.Config.Mode | AudioMode.Outgoing) == 0, - (Service.Config.Mode | AudioMode.Incoming) == 0); - await VoiceSocket.WaitForConnection(Service.Config.ConnectionTimeout).ConfigureAwait(false); + return Task.Run(async () => + { + _connectionLock.WaitOne(); + try + { + await VoiceSocket.Disconnect().ConfigureAwait(false); + + _cancelTokenSource = new CancellationTokenSource(); + var cancelToken = _cancelTokenSource.Token; + VoiceSocket.ParentCancelToken = cancelToken; + + VoiceSocket.ChannelId = channel.Id; + GatewaySocket.SendUpdateVoice(channel.Server.Id, channel.Id, + (Service.Config.Mode | AudioMode.Outgoing) == 0, + (Service.Config.Mode | AudioMode.Incoming) == 0); + + VoiceSocket.WaitForConnection(cancelToken); + } + finally + { + _connectionLock.Release(); + } + }); + } + public Task Disconnect() + { + GatewaySocket.ReceivedDispatch -= OnReceivedDispatch; + return VoiceSocket.Disconnect(); + } + + private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) + { + try + { + switch (e.Type) + { + case "VOICE_SERVER_UPDATE": + { + var data = e.Payload.ToObject(_serializer); + var serverId = data.GuildId; + + if (serverId == ServerId) + { + var client = Service.Client; + VoiceSocket.Token = data.Token; + VoiceSocket.Host = "wss://" + e.Payload.Value("endpoint").Split(':')[0]; + await VoiceSocket.Connect().ConfigureAwait(false); + } + } + break; + } + } + catch (Exception ex) + { + Logger.Error($"Error handling {e.Type} event", ex); + } } - public Task Disconnect() => VoiceSocket.Disconnect(); /// Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. /// PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. diff --git a/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs index 84d990300..e4f40f7ff 100644 --- a/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs @@ -63,10 +63,8 @@ namespace Discord.Net.WebSockets _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); } - public async Task Connect() - { - await BeginConnect().ConfigureAwait(false); - } + public Task Connect() + => BeginConnect(); public async Task Reconnect() { try @@ -473,21 +471,6 @@ namespace Discord.Net.WebSockets { _sendBuffer.Wait(CancelToken); } - public Task WaitForConnection(int timeout) - { - return Task.Run(() => - { - try - { - if (!_connectedEvent.Wait(timeout, CancelToken)) - throw new TimeoutException(); - } - catch (OperationCanceledException) - { - _taskManager.ThrowException(); - } - }); - } public override void SendHeartbeat() => QueueMessage(new HeartbeatCommand()); diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs index b86609d0f..378afbed3 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs @@ -6,7 +6,7 @@ namespace Discord.API.Client.GatewaySocket [JsonObject(MemberSerialization.OptIn)] public sealed class UpdateVoiceCommand : IWebSocketMessage { - int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate; + int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate; object IWebSocketMessage.Payload => this; bool IWebSocketMessage.IsPrivate => false; diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 21f7913d5..526a0216d 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -126,11 +126,6 @@ namespace Discord if (Config.UseMessageQueue) MessageQueue = new MessageQueue(this, Log.CreateLogger("MessageQueue")); - Connected += async (s, e) => - { - ClientAPI.CancelToken = CancelToken; - await SendStatus().ConfigureAwait(false); - }; //Extensibility Services = new ServiceManager(this); @@ -172,6 +167,10 @@ namespace Discord State = ConnectionState.Connecting; _disconnectedEvent.Reset(); + _cancelTokenSource = new CancellationTokenSource(); + CancelToken = _cancelTokenSource.Token; + GatewaySocket.ParentCancelToken = CancelToken; + await Login(email, password, token).ConfigureAwait(false); await GatewaySocket.Connect().ConfigureAwait(false); @@ -196,10 +195,6 @@ namespace Discord } private async Task Login(string email, string password, string token) { - _cancelTokenSource = new CancellationTokenSource(); - CancelToken = _cancelTokenSource.Token; - GatewaySocket.ParentCancelToken = CancelToken; - bool useCache = Config.CacheToken; while (true) { @@ -261,6 +256,9 @@ namespace Discord { State = ConnectionState.Connected; _connectedEvent.Set(); + + ClientAPI.CancelToken = CancelToken; + SendStatus(); OnConnected(); } @@ -293,21 +291,19 @@ namespace Discord _disconnectedEvent.Set(); } - public Task SetStatus(UserStatus status) + public void SetStatus(UserStatus status) { if (status == null) throw new ArgumentNullException(nameof(status)); if (status != UserStatus.Online && status != UserStatus.Idle) throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}", nameof(status)); Status = status; - return SendStatus(); } - public Task SetGame(string game) + public void SetGame(string game) { CurrentGame = game; - return SendStatus(); } - private Task SendStatus() + private void SendStatus() { PrivateUser.Status = Status; PrivateUser.CurrentGame = CurrentGame; @@ -321,7 +317,6 @@ namespace Discord } } GatewaySocket.SendUpdateStatus(Status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, CurrentGame); - return TaskHelper.CompletedTask; } #region Channels diff --git a/src/Discord.Net/Net/WebSockets/GatewaySocket.cs b/src/Discord.Net/Net/WebSockets/GatewaySocket.cs index ff9fb52e2..86495a516 100644 --- a/src/Discord.Net/Net/WebSockets/GatewaySocket.cs +++ b/src/Discord.Net/Net/WebSockets/GatewaySocket.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Discord.Net.WebSockets @@ -176,5 +177,9 @@ namespace Discord.Net.WebSockets => QueueMessage(new UpdateVoiceCommand { GuildId = serverId, ChannelId = channelId, IsSelfMuted = isSelfMuted, IsSelfDeafened = isSelfDeafened }); public void SendRequestMembers(ulong serverId, string query, int limit) => QueueMessage(new RequestMembersCommand { GuildId = serverId, Query = query, Limit = limit }); - } + + //Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached + public override void WaitForConnection(CancellationToken cancelToken) + => base.WaitForConnection(CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token); + } } diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index 69e86753c..7ff32651e 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -173,12 +173,10 @@ namespace Discord.Net.WebSockets } public abstract void SendHeartbeat(); - public void WaitForConnection(CancellationToken cancelToken) + public virtual void WaitForConnection(CancellationToken cancelToken) { try { - //Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached - cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token; if (!_connectedEvent.Wait(_client.Config.ConnectionTimeout, cancelToken)) throw new TimeoutException(); }