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
}
}