using Nito.AsyncEx; using System; using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; namespace Discord.Audio { public class AudioService : IService { private readonly AsyncLock _asyncLock; private AudioClient _defaultClient; //Only used for single server private VirtualClient _currentClient; //Only used for single server private ConcurrentDictionary _voiceClients; private ConcurrentDictionary _talkingUsers; private int _nextClientId; internal DiscordClient Client { get; private set; } public AudioServiceConfig Config { get; } public event EventHandler Connected = delegate { }; public event EventHandler Disconnected = delegate { }; public event EventHandler UserIsSpeakingUpdated = delegate { }; private void OnConnected() => Connected(this, EventArgs.Empty); private void OnDisconnected(ulong serverId, bool wasUnexpected, Exception ex) => Disconnected(this, new VoiceDisconnectedEventArgs(serverId, wasUnexpected, ex)); private void OnUserIsSpeakingUpdated(User user, bool isSpeaking) => UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking)); public AudioService(AudioServiceConfig config) { Config = config; _asyncLock = new AsyncLock(); } void IService.Install(DiscordClient client) { Client = client; Config.Lock(); if (Config.EnableMultiserver) _voiceClients = new ConcurrentDictionary(); else { var logger = Client.Log.CreateLogger("Voice"); _defaultClient = new AudioClient(Client, null, 0); } _talkingUsers = new ConcurrentDictionary(); client.Disconnected += async (s, e) => { if (Config.EnableMultiserver) { var tasks = _voiceClients .Select(x => { var val = x.Value; if (val != null) return x.Value.Disconnect(); else return TaskHelper.CompletedTask; }) .ToArray(); await Task.WhenAll(tasks).ConfigureAwait(false); _voiceClients.Clear(); } foreach (var member in _talkingUsers) { bool ignored; if (_talkingUsers.TryRemove(member.Key, out ignored)) OnUserIsSpeakingUpdated(member.Key, false); } }; } public IAudioClient GetClient(Server server) { if (server == null) throw new ArgumentNullException(nameof(server)); if (Config.EnableMultiserver) { AudioClient client; if (_voiceClients.TryGetValue(server.Id, out client)) return client; else return null; } else { if (server == _currentClient.Server) return _currentClient; else return null; } } //Called from AudioClient.Disconnect internal async Task RemoveClient(Server server, AudioClient client) { using (await _asyncLock.LockAsync().ConfigureAwait(false)) { if (_voiceClients.TryUpdate(server.Id, null, client)) _voiceClients.TryRemove(server.Id, out client); } } public async Task Join(Channel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); var server = channel.Server; using (await _asyncLock.LockAsync().ConfigureAwait(false)) { if (Config.EnableMultiserver) { AudioClient client; if (!_voiceClients.TryGetValue(server.Id, out client)) { client = new AudioClient(Client, server, unchecked(++_nextClientId)); _voiceClients[server.Id] = client; await client.Connect().ConfigureAwait(false); /*voiceClient.VoiceSocket.FrameReceived += (s, e) => { OnFrameReceieved(e); }; voiceClient.VoiceSocket.UserIsSpeaking += (s, e) => { var user = server.GetUser(e.UserId); OnUserIsSpeakingUpdated(user, e.IsSpeaking); };*/ } await client.Join(channel).ConfigureAwait(false); return client; } else { if (_defaultClient.Server != server) { await _defaultClient.Disconnect(); _defaultClient.VoiceSocket.Server = server; await _defaultClient.Connect().ConfigureAwait(false); } var client = new VirtualClient(_defaultClient, server); _currentClient = client; await client.Join(channel).ConfigureAwait(false); return client; } } } public async Task Leave(Server server) { if (server == null) throw new ArgumentNullException(nameof(server)); if (Config.EnableMultiserver) { AudioClient client; if (_voiceClients.TryRemove(server.Id, out client)) await client.Disconnect().ConfigureAwait(false); } else { using (await _asyncLock.LockAsync().ConfigureAwait(false)) { var client = GetClient(server) as VirtualClient; if (client != null) await _defaultClient.Disconnect().ConfigureAwait(false); } } } } }