| @@ -45,6 +45,9 @@ | |||||
| <Reference Include="System" /> | <Reference Include="System" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <Compile Include="..\Discord.Net.Audio\AudioClient.cs"> | |||||
| <Link>AudioClient.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net.Audio\AudioExtensions.cs"> | <Compile Include="..\Discord.Net.Audio\AudioExtensions.cs"> | ||||
| <Link>AudioExtensions.cs</Link> | <Link>AudioExtensions.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -54,8 +57,8 @@ | |||||
| <Compile Include="..\Discord.Net.Audio\AudioServiceConfig.cs"> | <Compile Include="..\Discord.Net.Audio\AudioServiceConfig.cs"> | ||||
| <Link>AudioServiceConfig.cs</Link> | <Link>AudioServiceConfig.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\DiscordAudioClient.cs"> | |||||
| <Link>DiscordAudioClient.cs</Link> | |||||
| <Compile Include="..\Discord.Net.Audio\IAudioClient.cs"> | |||||
| <Link>IAudioClient.cs</Link> | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\Net\WebSockets\VoiceWebSocket.cs"> | <Compile Include="..\Discord.Net.Audio\Net\WebSockets\VoiceWebSocket.cs"> | ||||
| <Link>Net\WebSockets\VoiceWebSocket.cs</Link> | <Link>Net\WebSockets\VoiceWebSocket.cs</Link> | ||||
| @@ -72,6 +75,9 @@ | |||||
| <Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs"> | <Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs"> | ||||
| <Link>Opus\OpusEncoder.cs</Link> | <Link>Opus\OpusEncoder.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\SimpleAudioClient.cs"> | |||||
| <Link>SimpleAudioClient.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net.Audio\Sodium.cs"> | <Compile Include="..\Discord.Net.Audio\Sodium.cs"> | ||||
| <Link>Sodium.cs</Link> | <Link>Sodium.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -8,7 +8,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public partial class DiscordAudioClient | |||||
| internal class AudioClient : IAudioClient | |||||
| { | { | ||||
| private readonly Semaphore _connectionLock; | private readonly Semaphore _connectionLock; | ||||
| private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
| @@ -20,19 +20,19 @@ namespace Discord.Audio | |||||
| public GatewaySocket GatewaySocket { get; } | public GatewaySocket GatewaySocket { get; } | ||||
| public VoiceWebSocket VoiceSocket { get; } | public VoiceWebSocket VoiceSocket { get; } | ||||
| public ulong? ServerId => VoiceSocket.ServerId; | |||||
| public ulong? ChannelId => VoiceSocket.ChannelId; | |||||
| public ConnectionState State => VoiceSocket.State; | public ConnectionState State => VoiceSocket.State; | ||||
| public Server Server => VoiceSocket.Server; | |||||
| public Channel Channel => VoiceSocket.Channel; | |||||
| public DiscordAudioClient(AudioService service, int id, Logger logger, GatewaySocket gatewaySocket) | |||||
| public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, Logger logger) | |||||
| { | { | ||||
| Service = service; | Service = service; | ||||
| Id = id; | |||||
| Logger = logger; | |||||
| Id = clientId; | |||||
| GatewaySocket = gatewaySocket; | GatewaySocket = gatewaySocket; | ||||
| _connectionLock = new Semaphore(1, 1); | |||||
| Logger = logger; | |||||
| _connectionLock = new Semaphore(1, 1); | |||||
| _serializer = new JsonSerializer(); | _serializer = new JsonSerializer(); | ||||
| _serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; | _serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; | ||||
| _serializer.Error += (s, e) => | _serializer.Error += (s, e) => | ||||
| @@ -41,7 +41,10 @@ namespace Discord.Audio | |||||
| Logger.Error("Serialization Failed", e.ErrorContext.Error); | Logger.Error("Serialization Failed", e.ErrorContext.Error); | ||||
| }; | }; | ||||
| VoiceSocket = new VoiceWebSocket(service.Client, this, _serializer, logger); | |||||
| GatewaySocket.ReceivedDispatch += OnReceivedDispatch; | |||||
| VoiceSocket = new VoiceWebSocket(service.Client, this, _serializer, logger); | |||||
| VoiceSocket.Server = server; | |||||
| /*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); | /*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); | ||||
| _voiceSocket.Disconnected += async (s, e) => | _voiceSocket.Disconnected += async (s, e) => | ||||
| @@ -76,58 +79,54 @@ namespace Discord.Audio | |||||
| { | { | ||||
| _voiceSocket.ParentCancelToken = _cancelToken; | _voiceSocket.ParentCancelToken = _cancelToken; | ||||
| };*/ | };*/ | ||||
| } | } | ||||
| internal async Task SetServer(ulong serverId) | |||||
| { | |||||
| if (serverId != VoiceSocket.ServerId) | |||||
| { | |||||
| await Disconnect().ConfigureAwait(false); | |||||
| VoiceSocket.ServerId = serverId; | |||||
| VoiceSocket.ChannelId = null; | |||||
| SendVoiceUpdate(); | |||||
| } | |||||
| } | |||||
| public Task JoinChannel(Channel channel) | |||||
| public async Task Join(Channel channel) | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| var serverId = channel.Server?.Id; | |||||
| var channelId = channel.Id; | |||||
| if (serverId != ServerId) | |||||
| throw new InvalidOperationException("Cannot join a channel on a different server than this voice client."); | |||||
| if (channelId == VoiceSocket.ChannelId) | |||||
| return TaskHelper.CompletedTask; | |||||
| //CheckReady(checkVoice: true); | |||||
| return Task.Run(async () => | |||||
| { | |||||
| _connectionLock.WaitOne(); | |||||
| GatewaySocket.ReceivedDispatch += OnReceivedDispatch; | |||||
| try | |||||
| { | |||||
| if (State != ConnectionState.Disconnected) | |||||
| await Disconnect().ConfigureAwait(false); | |||||
| if (channel.Type != ChannelType.Voice) | |||||
| throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); | |||||
| if (channel.Server != VoiceSocket.Server) | |||||
| throw new ArgumentException("This is channel is not part of the current server.", nameof(channel)); | |||||
| if (channel == VoiceSocket.Channel) return; | |||||
| if (VoiceSocket.Server == null) | |||||
| throw new InvalidOperationException("This client has been closed."); | |||||
| _cancelTokenSource = new CancellationTokenSource(); | |||||
| var cancelToken = _cancelTokenSource.Token; | |||||
| VoiceSocket.ParentCancelToken = cancelToken; | |||||
| _connectionLock.WaitOne(); | |||||
| try | |||||
| { | |||||
| _cancelTokenSource = new CancellationTokenSource(); | |||||
| var cancelToken = _cancelTokenSource.Token; | |||||
| VoiceSocket.ParentCancelToken = cancelToken; | |||||
| VoiceSocket.Channel = channel; | |||||
| VoiceSocket.ChannelId = channelId; | |||||
| await Task.Run(() => | |||||
| { | |||||
| SendVoiceUpdate(); | SendVoiceUpdate(); | ||||
| VoiceSocket.WaitForConnection(cancelToken); | VoiceSocket.WaitForConnection(cancelToken); | ||||
| } | |||||
| finally | |||||
| { | |||||
| GatewaySocket.ReceivedDispatch -= OnReceivedDispatch; | |||||
| _connectionLock.Release(); | |||||
| } | |||||
| }); | |||||
| }); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _connectionLock.Release(); | |||||
| } | |||||
| } | |||||
| public async Task Disconnect() | |||||
| { | |||||
| _connectionLock.WaitOne(); | |||||
| try | |||||
| { | |||||
| Service.RemoveClient(VoiceSocket.Server, this); | |||||
| VoiceSocket.Channel = null; | |||||
| SendVoiceUpdate(); | |||||
| await VoiceSocket.Disconnect(); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _connectionLock.Release(); | |||||
| } | |||||
| } | } | ||||
| public Task Disconnect() | |||||
| => VoiceSocket.Disconnect(); | |||||
| private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) | private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) | ||||
| { | { | ||||
| @@ -135,12 +134,31 @@ namespace Discord.Audio | |||||
| { | { | ||||
| switch (e.Type) | switch (e.Type) | ||||
| { | { | ||||
| case "VOICE_STATE_UPDATE": | |||||
| { | |||||
| var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer); | |||||
| if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) | |||||
| { | |||||
| if (data.ChannelId == null) | |||||
| await Disconnect(); | |||||
| else | |||||
| { | |||||
| var channel = Service.Client.GetChannel(data.ChannelId.Value); | |||||
| if (channel != null) | |||||
| VoiceSocket.Channel = channel; | |||||
| else | |||||
| { | |||||
| Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); | |||||
| await Disconnect(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| break; | |||||
| case "VOICE_SERVER_UPDATE": | case "VOICE_SERVER_UPDATE": | ||||
| { | { | ||||
| var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer); | var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer); | ||||
| var serverId = data.GuildId; | |||||
| if (serverId == ServerId) | |||||
| if (data.GuildId == VoiceSocket.Server?.Id) | |||||
| { | { | ||||
| var client = Service.Client; | var client = Service.Client; | ||||
| VoiceSocket.Token = data.Token; | VoiceSocket.Token = data.Token; | ||||
| @@ -164,34 +182,32 @@ namespace Discord.Audio | |||||
| { | { | ||||
| if (data == null) throw new ArgumentException(nameof(data)); | if (data == null) throw new ArgumentException(nameof(data)); | ||||
| if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); | if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); | ||||
| //CheckReady(checkVoice: true); | |||||
| if (VoiceSocket.Server == null) return; //Has been closed | |||||
| if (count != 0) | |||||
| if (count != 0) | |||||
| VoiceSocket.SendPCMFrames(data, count); | VoiceSocket.SendPCMFrames(data, count); | ||||
| } | } | ||||
| /// <summary> Clears the PCM buffer. </summary> | |||||
| public void Clear() | |||||
| { | |||||
| //CheckReady(checkVoice: true); | |||||
| VoiceSocket.ClearPCMFrames(); | |||||
| } | |||||
| /// <summary> Clears the PCM buffer. </summary> | |||||
| public void Clear() | |||||
| { | |||||
| if (VoiceSocket.Server == null) return; //Has been closed | |||||
| VoiceSocket.ClearPCMFrames(); | |||||
| } | |||||
| /// <summary> Returns a task that completes once the voice output buffer is empty. </summary> | /// <summary> Returns a task that completes once the voice output buffer is empty. </summary> | ||||
| public void Wait() | public void Wait() | ||||
| { | |||||
| //CheckReady(checkVoice: true); | |||||
| VoiceSocket.WaitForQueue(); | |||||
| { | |||||
| if (VoiceSocket.Server == null) return; //Has been closed | |||||
| VoiceSocket.WaitForQueue(); | |||||
| } | } | ||||
| private void SendVoiceUpdate() | private void SendVoiceUpdate() | ||||
| { | { | ||||
| var serverId = VoiceSocket.ServerId; | |||||
| var serverId = VoiceSocket.Server?.Id; | |||||
| if (serverId != null) | if (serverId != null) | ||||
| { | { | ||||
| GatewaySocket.SendUpdateVoice(serverId, VoiceSocket.ChannelId, | |||||
| GatewaySocket.SendUpdateVoice(serverId, VoiceSocket.Channel?.Id, | |||||
| (Service.Config.Mode | AudioMode.Outgoing) == 0, | (Service.Config.Mode | AudioMode.Outgoing) == 0, | ||||
| (Service.Config.Mode | AudioMode.Incoming) == 0); | (Service.Config.Mode | AudioMode.Incoming) == 0); | ||||
| } | } | ||||
| @@ -46,8 +46,8 @@ namespace Discord.Audio | |||||
| public class AudioService : IService | public class AudioService : IService | ||||
| { | { | ||||
| private DiscordAudioClient _defaultClient; | |||||
| private ConcurrentDictionary<ulong, DiscordAudioClient> _voiceClients; | |||||
| private AudioClient _defaultClient; | |||||
| private ConcurrentDictionary<ulong, IAudioClient> _voiceClients; | |||||
| private ConcurrentDictionary<User, bool> _talkingUsers; | private ConcurrentDictionary<User, bool> _talkingUsers; | ||||
| //private int _nextClientId; | //private int _nextClientId; | ||||
| @@ -91,11 +91,11 @@ namespace Discord.Audio | |||||
| { | { | ||||
| _client = client; | _client = client; | ||||
| if (Config.EnableMultiserver) | if (Config.EnableMultiserver) | ||||
| _voiceClients = new ConcurrentDictionary<ulong, DiscordAudioClient>(); | |||||
| _voiceClients = new ConcurrentDictionary<ulong, IAudioClient>(); | |||||
| else | else | ||||
| { | { | ||||
| var logger = Client.Log.CreateLogger("Voice"); | var logger = Client.Log.CreateLogger("Voice"); | ||||
| _defaultClient = new DiscordAudioClient(this, 0, logger, _client.GatewaySocket); | |||||
| _defaultClient = new SimpleAudioClient(this, 0, logger); | |||||
| } | } | ||||
| _talkingUsers = new ConcurrentDictionary<User, bool>(); | _talkingUsers = new ConcurrentDictionary<User, bool>(); | ||||
| @@ -118,34 +118,29 @@ namespace Discord.Audio | |||||
| }; | }; | ||||
| } | } | ||||
| public DiscordAudioClient GetClient(Server server) | |||||
| public IAudioClient GetClient(Server server) | |||||
| { | { | ||||
| if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
| if (!Config.EnableMultiserver) | |||||
| { | |||||
| if (server.Id == _defaultClient.ServerId) | |||||
| return _defaultClient; | |||||
| else | |||||
| return null; | |||||
| } | |||||
| DiscordAudioClient client; | |||||
| if (_voiceClients.TryGetValue(server.Id, out client)) | |||||
| return client; | |||||
| else | |||||
| return null; | |||||
| if (!Config.EnableMultiserver) | |||||
| { | |||||
| if (server == _defaultClient.Server) | |||||
| return (_defaultClient as SimpleAudioClient).CurrentClient; | |||||
| else | |||||
| return null; | |||||
| } | |||||
| else | |||||
| { | |||||
| IAudioClient client; | |||||
| if (_voiceClients.TryGetValue(server.Id, out client)) | |||||
| return client; | |||||
| else | |||||
| return null; | |||||
| } | |||||
| } | } | ||||
| private async Task<DiscordAudioClient> CreateClient(Server server) | |||||
| private Task<IAudioClient> CreateClient(Server server) | |||||
| { | { | ||||
| if (!Config.EnableMultiserver) | |||||
| { | |||||
| await _defaultClient.SetServer(server.Id); | |||||
| return _defaultClient; | |||||
| } | |||||
| else | |||||
| throw new InvalidOperationException("Multiserver voice is not currently supported"); | |||||
| throw new NotImplementedException(); | |||||
| /*var client = _voiceClients.GetOrAdd(server.Id, _ => | /*var client = _voiceClients.GetOrAdd(server.Id, _ => | ||||
| { | { | ||||
| int id = unchecked(++_nextClientId); | int id = unchecked(++_nextClientId); | ||||
| @@ -169,30 +164,44 @@ namespace Discord.Audio | |||||
| return Task.FromResult(client);*/ | return Task.FromResult(client);*/ | ||||
| } | } | ||||
| public async Task<DiscordAudioClient> Join(Channel channel) | |||||
| //TODO: This isn't threadsafe | |||||
| internal void RemoveClient(Server server, IAudioClient client) | |||||
| { | |||||
| if (Config.EnableMultiserver && server != null) | |||||
| _voiceClients.TryRemove(server.Id, out client); | |||||
| } | |||||
| public async Task<IAudioClient> Join(Channel channel) | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| //CheckReady(true); | |||||
| var client = await CreateClient(channel.Server).ConfigureAwait(false); | |||||
| await client.JoinChannel(channel).ConfigureAwait(false); | |||||
| IAudioClient client; | |||||
| if (!Config.EnableMultiserver) | |||||
| client = await (_defaultClient as SimpleAudioClient).Connect(channel).ConfigureAwait(false); | |||||
| else | |||||
| { | |||||
| client = await CreateClient(channel.Server).ConfigureAwait(false); | |||||
| await client.Join(channel).ConfigureAwait(false); | |||||
| } | |||||
| return client; | return client; | ||||
| } | } | ||||
| public async Task Leave(Server server) | public async Task Leave(Server server) | ||||
| { | { | ||||
| if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
| //CheckReady(true); | |||||
| if (Config.EnableMultiserver) | |||||
| { | |||||
| //client.CheckReady(); | |||||
| DiscordAudioClient client; | |||||
| if (_voiceClients.TryRemove(server.Id, out client)) | |||||
| await client.Disconnect().ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| await _defaultClient.Disconnect().ConfigureAwait(false); | |||||
| if (Config.EnableMultiserver) | |||||
| { | |||||
| IAudioClient client; | |||||
| if (_voiceClients.TryRemove(server.Id, out client)) | |||||
| await client.Disconnect().ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| { | |||||
| IAudioClient client = GetClient(server); | |||||
| if (client != null) | |||||
| await (_defaultClient as SimpleAudioClient).Leave(client as SimpleAudioClient.VirtualClient).ConfigureAwait(false); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,23 @@ | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Audio | |||||
| { | |||||
| public interface IAudioClient | |||||
| { | |||||
| ConnectionState State { get; } | |||||
| Channel Channel { get; } | |||||
| Server Server { get; } | |||||
| Task Join(Channel channel); | |||||
| Task Disconnect(); | |||||
| /// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary> | |||||
| /// <param name="data">PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. </param> | |||||
| /// <param name="count">Number of bytes in this frame. </param> | |||||
| void Send(byte[] data, int count); | |||||
| /// <summary> Clears the PCM buffer. </summary> | |||||
| void Clear(); | |||||
| /// <summary> Blocks until the voice output buffer is empty. </summary> | |||||
| void Wait(); | |||||
| } | |||||
| } | |||||
| @@ -28,7 +28,7 @@ namespace Discord.Net.WebSockets | |||||
| private readonly int _targetAudioBufferLength; | private readonly int _targetAudioBufferLength; | ||||
| private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders; | private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders; | ||||
| private readonly DiscordAudioClient _audioClient; | |||||
| private readonly AudioClient _audioClient; | |||||
| private readonly AudioServiceConfig _config; | private readonly AudioServiceConfig _config; | ||||
| private Thread _sendThread, _receiveThread; | private Thread _sendThread, _receiveThread; | ||||
| private VoiceBuffer _sendBuffer; | private VoiceBuffer _sendBuffer; | ||||
| @@ -44,13 +44,13 @@ namespace Discord.Net.WebSockets | |||||
| private int _ping; | private int _ping; | ||||
| public string Token { get; internal set; } | public string Token { get; internal set; } | ||||
| public ulong? ServerId { get; internal set; } | |||||
| public ulong? ChannelId { get; internal set; } | |||||
| public Server Server { get; internal set; } | |||||
| public Channel Channel { get; internal set; } | |||||
| public int Ping => _ping; | public int Ping => _ping; | ||||
| internal VoiceBuffer OutputBuffer => _sendBuffer; | internal VoiceBuffer OutputBuffer => _sendBuffer; | ||||
| public VoiceWebSocket(DiscordClient client, DiscordAudioClient audioClient, JsonSerializer serializer, Logger logger) | |||||
| internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, JsonSerializer serializer, Logger logger) | |||||
| : base(client, serializer, logger) | : base(client, serializer, logger) | ||||
| { | { | ||||
| _audioClient = audioClient; | _audioClient = audioClient; | ||||
| @@ -65,7 +65,7 @@ namespace Discord.Net.WebSockets | |||||
| public Task Connect() | public Task Connect() | ||||
| => BeginConnect(); | => BeginConnect(); | ||||
| public async Task Reconnect() | |||||
| private async Task Reconnect() | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -234,7 +234,7 @@ namespace Discord.Net.WebSockets | |||||
| ulong userId; | ulong userId; | ||||
| if (_ssrcMapping.TryGetValue(ssrc, out userId)) | if (_ssrcMapping.TryGetValue(ssrc, out userId)) | ||||
| RaiseOnPacket(userId, ChannelId.Value, result, resultOffset, resultLength); | |||||
| RaiseOnPacket(userId, Channel.Id, result, resultOffset, resultLength); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -477,7 +477,7 @@ namespace Discord.Net.WebSockets | |||||
| public override void SendHeartbeat() | public override void SendHeartbeat() | ||||
| => QueueMessage(new HeartbeatCommand()); | => QueueMessage(new HeartbeatCommand()); | ||||
| public void SendIdentify() | public void SendIdentify() | ||||
| => QueueMessage(new IdentifyCommand { GuildId = ServerId.Value, UserId = _client.CurrentUser.Id, | |||||
| => QueueMessage(new IdentifyCommand { GuildId = Server.Id, UserId = _client.CurrentUser.Id, | |||||
| SessionId = _client.SessionId, Token = Token }); | SessionId = _client.SessionId, Token = Token }); | ||||
| public void SendSelectProtocol(string externalAddress, int externalPort) | public void SendSelectProtocol(string externalAddress, int externalPort) | ||||
| => QueueMessage(new SelectProtocolCommand { Protocol = "udp", ExternalAddress = externalAddress, | => QueueMessage(new SelectProtocolCommand { Protocol = "udp", ExternalAddress = externalAddress, | ||||
| @@ -0,0 +1,79 @@ | |||||
| using Discord.Logging; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Audio | |||||
| { | |||||
| internal class SimpleAudioClient : AudioClient | |||||
| { | |||||
| internal class VirtualClient : IAudioClient | |||||
| { | |||||
| private readonly SimpleAudioClient _client; | |||||
| ConnectionState IAudioClient.State => _client.VoiceSocket.State; | |||||
| Server IAudioClient.Server => _client.VoiceSocket.Server; | |||||
| Channel IAudioClient.Channel => _client.VoiceSocket.Channel; | |||||
| public VirtualClient(SimpleAudioClient client) | |||||
| { | |||||
| _client = client; | |||||
| } | |||||
| Task IAudioClient.Disconnect() => _client.Leave(this); | |||||
| Task IAudioClient.Join(Channel channel) => _client.Join(channel); | |||||
| void IAudioClient.Send(byte[] data, int count) => _client.Send(data, count); | |||||
| void IAudioClient.Clear() => _client.Clear(); | |||||
| void IAudioClient.Wait() => _client.Wait(); | |||||
| } | |||||
| private readonly Semaphore _connectionLock; | |||||
| internal VirtualClient CurrentClient { get; private set; } | |||||
| public SimpleAudioClient(AudioService service, int id, Logger logger) | |||||
| : base(service, id, null, service.Client.GatewaySocket, logger) | |||||
| { | |||||
| _connectionLock = new Semaphore(1, 1); | |||||
| } | |||||
| //Only disconnects if is current a member of this server | |||||
| public async Task Leave(VirtualClient client) | |||||
| { | |||||
| _connectionLock.WaitOne(); | |||||
| try | |||||
| { | |||||
| if (CurrentClient == client) | |||||
| { | |||||
| CurrentClient = null; | |||||
| await Disconnect(); | |||||
| } | |||||
| } | |||||
| finally | |||||
| { | |||||
| _connectionLock.Release(); | |||||
| } | |||||
| } | |||||
| internal async Task<IAudioClient> Connect(Channel channel) | |||||
| { | |||||
| _connectionLock.WaitOne(); | |||||
| try | |||||
| { | |||||
| bool changeServer = channel.Server != VoiceSocket.Server; | |||||
| if (changeServer || CurrentClient == null) | |||||
| { | |||||
| await Disconnect().ConfigureAwait(false); | |||||
| CurrentClient = new VirtualClient(this); | |||||
| VoiceSocket.Server = channel.Server; | |||||
| } | |||||
| await Join(channel); | |||||
| return CurrentClient; | |||||
| } | |||||
| finally | |||||
| { | |||||
| _connectionLock.Release(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -3,7 +3,7 @@ using System.Security; | |||||
| namespace Discord.Audio.Sodium | namespace Discord.Audio.Sodium | ||||
| { | { | ||||
| public unsafe static class SecretBox | |||||
| internal unsafe static class SecretBox | |||||
| { | { | ||||
| #if NET45 | #if NET45 | ||||
| [SuppressUnmanagedCodeSecurity] | [SuppressUnmanagedCodeSecurity] | ||||