using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Adrenak.UniVoice { /// /// Handles a client session. /// Requires implementations of , and . /// Handles input, output along with filters over the entire client lifecycle. /// Adjusts to changes in configuration at runtime. /// /// public class ClientSession : IDisposable { /// /// Represents a filter registered in the session. /// Currently used only for output filters. /// class FilterFactoryEntry { public Type FilterType { get; } public Func Factory { get; } public FilterFactoryEntry(Type filterType, Func factory) { FilterType = filterType; Factory = factory; } } #region AUDIO OUTPUT List outputFilterFactories = new List(); Dictionary> peerOutputFilters = new Dictionary>(); /// /// Whether any incoming audio from peers would be processed. If set to false, all incoming peer audio is ignored, and would /// neither be processed by the nor output to the of any peer. /// This can be used to easily mute all the peers on the network. /// Note that this doesn't stop the audio data from arriving and would consume bandwidth. To stop reception completely /// by telling the server to not send audio, use /// public bool OutputsEnabled { get; set; } = true; /// /// The instances of each peer in the session /// public Dictionary PeerOutputs { get; private set; } = new Dictionary(); /// /// Adds an filter to the output audio. Note: it is possible to register the same /// filter type more than once, this can be used to create some effects but can also cause /// errors. /// /// The type of the filter to be added /// A lambda method that returns an instance of the filter type public void AddOutputFilter(Func filterFactory) where TFilter : IAudioFilter { outputFilterFactories.Add(new FilterFactoryEntry(typeof(TFilter), filterFactory)); foreach (var peerFilters in peerOutputFilters.Values) peerFilters.Add(filterFactory()); } /// /// Checks if an output audio filter of a specific type has been registered. /// /// The type of the filter to check /// True if the filter is registered, false otherwise public bool HasOutputFilter() where TFilter : IAudioFilter { return outputFilterFactories.Any(entry => entry.FilterType == typeof(TFilter)); } /// /// Removes a previously registered output audio filter /// /// The type of the filter to be removed public void RemoveOutputFilter() where TFilter : IAudioFilter { outputFilterFactories.RemoveAll(entry => entry.FilterType == typeof(TFilter)); foreach (var peerFilters in peerOutputFilters.Values) peerFilters.RemoveAll(f => f.GetType() == typeof(TFilter)); } #endregion #region AUDIO INPUT /// /// Whether input audio will be processed. If set to false, any input audio captured by /// would be ignored and would neither be processed by the nor send via the /// This can be used to create "Push to talk" style features without having to use /// public bool InputEnabled { get; set; } = true; /// /// The that will be applied to the outgoing audio for all the peers. /// Note that filters are executed in the order they are present in this list /// public List InputFilters { get; set; } = new List(); /// /// Checks if an input audio filter of a specific type has been registered. /// /// The type of the filter to check /// True if the filter is registered, false otherwise public bool HasInputFilter() where TFilter : IAudioFilter { return InputFilters.Any(filter => filter.GetType() == typeof(TFilter)); } #endregion public ClientSession(IAudioClient client, IAudioInput input, Func outputProvider) { Client = client; Input = input; OutputProvider = outputProvider; } public ClientSession(IAudioClient client, IAudioInput input, IAudioOutputFactory outputFactory) { Client = client; Input = input; OutputFactory = outputFactory; } /// /// The that's used for networking /// IAudioClient client; public IAudioClient Client { get => client; set { if (client != null) client.Dispose(); client = value; Client.OnLeft += () => { foreach (var output in PeerOutputs) output.Value.Dispose(); PeerOutputs.Clear(); peerOutputFilters.Clear(); }; Client.OnPeerJoined += id => { try { if (OutputProvider != null) { var output = OutputProvider(); PeerOutputs.Add(id, output); } else if (OutputFactory != null) { var output = OutputFactory.Create(); PeerOutputs.Add(id, output); } var filters = outputFilterFactories .Select(entry => entry.Factory()) .ToList(); peerOutputFilters.Add(id, filters); } catch (Exception e) { Debug.LogException(e); } }; Client.OnPeerLeft += id => { if (PeerOutputs.ContainsKey(id)) { PeerOutputs[id].Dispose(); PeerOutputs.Remove(id); } if (peerOutputFilters.ContainsKey(id)) { peerOutputFilters.Remove(id); } }; Client.OnReceivedPeerAudioFrame += (id, audioFrame) => { if (!OutputsEnabled || !PeerOutputs.ContainsKey(id)) return; if (peerOutputFilters.TryGetValue(id, out var filters)) { foreach (var filter in filters) audioFrame = filter.Run(audioFrame); } if (audioFrame.samples.Length > 0) PeerOutputs[id]?.Feed(audioFrame); }; } } IAudioInput input; /// /// The that's used for sourcing outgoing audio /// public IAudioInput Input { get => input; set { if (input != null) input.Dispose(); input = value; input.OnFrameReady += frame => { if (!InputEnabled) return; if (InputFilters != null) { foreach (var filter in InputFilters) { frame = filter.Run(frame); if (frame.samples == null) break; } } if (frame.samples != null && frame.samples.Length > 0) Client.SendAudioFrame(frame); }; } } Func outputProvider; /// /// The provider of IAudioOutput objects for peers. /// If this value is being set while peers already exist, /// the old outputs would be cleared and new onces will /// be created. /// public Func OutputProvider { get => outputProvider; set { outputProvider = value; outputFactory = null; foreach (var output in PeerOutputs) output.Value.Dispose(); PeerOutputs.Clear(); if(outputProvider != null) { foreach (var id in Client.PeerIDs) { try { var output = outputProvider(); PeerOutputs.Add(id, output); } catch (Exception e) { Debug.LogException(e); } } } } } IAudioOutputFactory outputFactory; /// /// The that creates the of peers /// public IAudioOutputFactory OutputFactory { get => outputFactory; set { outputFactory = value; outputProvider = null; foreach (var output in PeerOutputs) output.Value.Dispose(); PeerOutputs.Clear(); if (outputFactory != null) { foreach (var id in Client.PeerIDs) { try { var output = outputFactory.Create(); PeerOutputs.Add(id, output); } catch (Exception e) { Debug.LogException(e); } } } } } public void Dispose() { Client.Dispose(); Input.Dispose(); } #region OBSOLETE /// /// The output that will be applied to the incoming audio for all the peers. /// Note that filters are executed in the order they are present in this list. /// [Obsolete("OutputFilters has been removed. Use AddOutputFilter and RemoveOutputFilter instead.", true)] public List OutputFilters { get; set; } = new List(); #endregion } }