com.adrenak.univoice 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,92 @@
1
1
  using System;
2
2
  using System.Collections.Generic;
3
+ using System.Linq;
3
4
 
4
5
  using UnityEngine;
5
6
 
6
7
  namespace Adrenak.UniVoice {
7
8
  /// <summary>
8
9
  /// Handles a client session.
9
- /// Requires an implementation of <see cref="IAudioClient{T}"/>, <see cref="IAudioInput"/> and <see cref="IAudioOutputFactory"/> each.
10
- /// Allows adding input and output filters and handles their execution.
10
+ /// Requires implementations of <see cref="IAudioClient{T}"/>, <see cref="IAudioInput"/> and <see cref="IAudioOutput"/>.
11
+ /// Handles input, output along with filters over the entire client lifecycle.
12
+ /// Adjusts to changes in configuration at runtime.
11
13
  /// </summary>
12
14
  /// <typeparam name="T"></typeparam>
13
15
  public class ClientSession<T> : IDisposable {
16
+ /// <summary>
17
+ /// Represents a filter registered in the session.
18
+ /// Currently used only for output filters.
19
+ /// </summary>
20
+ class FilterFactoryEntry {
21
+ public Type FilterType { get; }
22
+ public Func<IAudioFilter> Factory { get; }
23
+
24
+ public FilterFactoryEntry(Type filterType, Func<IAudioFilter> factory) {
25
+ FilterType = filterType;
26
+ Factory = factory;
27
+ }
28
+ }
29
+
30
+ #region AUDIO OUTPUT
31
+
32
+ List<FilterFactoryEntry> outputFilterFactories = new List<FilterFactoryEntry>();
33
+ Dictionary<T, List<IAudioFilter>> peerOutputFilters = new Dictionary<T, List<IAudioFilter>>();
34
+
35
+ /// <summary>
36
+ /// Whether any incoming audio from peers would be processed. If set to false, all incoming peer audio is ignored, and would
37
+ /// neither be processed by the <see cref="OutputFilters"/> nor output to the <see cref="IAudioOutput"/> of any peer.
38
+ /// This can be used to easily mute all the peers on the network.
39
+ /// Note that this doesn't stop the audio data from arriving and would consume bandwidth. To stop reception completely
40
+ /// by telling the server to not send audio, use <see cref="IAudioClient{T}.YourVoiceSettings"/>
41
+ /// </summary>
42
+ public bool OutputsEnabled { get; set; } = true;
43
+
14
44
  /// <summary>
15
45
  /// The <see cref="IAudioOutput"/> instances of each peer in the session
16
46
  /// </summary>
17
47
  public Dictionary<T, IAudioOutput> PeerOutputs { get; private set; } = new Dictionary<T, IAudioOutput>();
18
48
 
49
+ /// <summary>
50
+ /// Adds an filter to the output audio. Note: it is possible to register the same
51
+ /// filter type more than once, this can be used to create some effects but can also cause
52
+ /// errors.
53
+ /// </summary>
54
+ /// <typeparam name="TFilter">The type of the filter to be added</typeparam>
55
+ /// <param name="filterFactory">A lambda method that returns an instance of the filter type</param>
56
+ public void AddOutputFilter<TFilter>(Func<IAudioFilter> filterFactory)
57
+ where TFilter : IAudioFilter {
58
+ outputFilterFactories.Add(new FilterFactoryEntry(typeof(TFilter), filterFactory));
59
+
60
+ foreach (var peerFilters in peerOutputFilters.Values)
61
+ peerFilters.Add(filterFactory());
62
+ }
63
+
64
+ /// <summary>
65
+ /// Checks if an output audio filter of a specific type has been registered.
66
+ /// </summary>
67
+ /// <typeparam name="TFilter">The type of the filter to check</typeparam>
68
+ /// <returns>True if the filter is registered, false otherwise</returns>
69
+ public bool HasOutputFilter<TFilter>()
70
+ where TFilter : IAudioFilter {
71
+ return outputFilterFactories.Any(entry => entry.FilterType == typeof(TFilter));
72
+ }
73
+
74
+ /// <summary>
75
+ /// Removes a previously registered output audio filter
76
+ /// </summary>
77
+ /// <typeparam name="TFilter">The type of the filter to be removed</typeparam>
78
+ public void RemoveOutputFilter<TFilter>()
79
+ where TFilter : IAudioFilter {
80
+ outputFilterFactories.RemoveAll(entry => entry.FilterType == typeof(TFilter));
81
+
82
+ foreach (var peerFilters in peerOutputFilters.Values)
83
+ peerFilters.RemoveAll(f => f.GetType() == typeof(TFilter));
84
+ }
85
+
86
+ #endregion
87
+
88
+ #region AUDIO INPUT
89
+
19
90
  /// <summary>
20
91
  /// Whether input audio will be processed. If set to false, any input audio captured by
21
92
  /// <see cref="Input"/> would be ignored and would neither be processed by the <see cref="InputFilters"/> nor send via the <see cref="Client"/>
@@ -24,25 +95,28 @@ namespace Adrenak.UniVoice {
24
95
  public bool InputEnabled { get; set; } = true;
25
96
 
26
97
  /// <summary>
27
- /// Whether any incoming audio from peers would be processed. If set to false, all incoming peer audio is ignored, and would
28
- /// neither be processed by the <see cref="OutputFilters"/> nor outputted to the <see cref="IAudioOutput"/> of any peer.
29
- /// This can be used to easily mute all the peers on the network.
30
- /// Note that this doesn't stop the audio data from arriving and would consume bandwidth. Do stop reception completely
31
- /// use <see cref="IAudioClient{T}.YourVoiceSettings"/>
32
- /// </summary>
33
- public bool OutputsEnabled { get; set; } = true;
34
-
35
- /// <summary>
36
- /// The input <see cref="IAudioFilter"/> that will be applied to the outgoing audio for all the peers.
98
+ /// The <see cref="IAudioFilter"/> that will be applied to the outgoing audio for all the peers.
37
99
  /// Note that filters are executed in the order they are present in this list
38
100
  /// </summary>
39
101
  public List<IAudioFilter> InputFilters { get; set; } = new List<IAudioFilter>();
40
102
 
41
103
  /// <summary>
42
- /// The output <see cref="IAudioFilter"/> that will be applied to the incoming audio for all the peers.
43
- /// Note that filters are executed in the order they are present in this list.
104
+ /// Checks if an input audio filter of a specific type has been registered.
44
105
  /// </summary>
45
- public List<IAudioFilter> OutputFilters { get; set; } = new List<IAudioFilter>();
106
+ /// <typeparam name="TFilter">The type of the filter to check</typeparam>
107
+ /// <returns>True if the filter is registered, false otherwise</returns>
108
+ public bool HasInputFilter<TFilter>()
109
+ where TFilter : IAudioFilter {
110
+ return InputFilters.Any(filter => filter.GetType() == typeof(TFilter));
111
+ }
112
+
113
+ #endregion
114
+
115
+ public ClientSession(IAudioClient<T> client, IAudioInput input, Func<IAudioOutput> outputProvider) {
116
+ Client = client;
117
+ Input = input;
118
+ OutputProvider = outputProvider;
119
+ }
46
120
 
47
121
  public ClientSession(IAudioClient<T> client, IAudioInput input, IAudioOutputFactory outputFactory) {
48
122
  Client = client;
@@ -57,7 +131,7 @@ namespace Adrenak.UniVoice {
57
131
  public IAudioClient<T> Client {
58
132
  get => client;
59
133
  set {
60
- if(client != null)
134
+ if (client != null)
61
135
  client.Dispose();
62
136
  client = value;
63
137
 
@@ -65,12 +139,24 @@ namespace Adrenak.UniVoice {
65
139
  foreach (var output in PeerOutputs)
66
140
  output.Value.Dispose();
67
141
  PeerOutputs.Clear();
142
+ peerOutputFilters.Clear();
68
143
  };
69
144
 
70
145
  Client.OnPeerJoined += id => {
71
146
  try {
72
- var output = outputFactory.Create();
73
- PeerOutputs.Add(id, output);
147
+ if (OutputProvider != null) {
148
+ var output = OutputProvider();
149
+ PeerOutputs.Add(id, output);
150
+ }
151
+ else if (OutputFactory != null) {
152
+ var output = OutputFactory.Create();
153
+ PeerOutputs.Add(id, output);
154
+ }
155
+
156
+ var filters = outputFilterFactories
157
+ .Select(entry => entry.Factory())
158
+ .ToList();
159
+ peerOutputFilters.Add(id, filters);
74
160
  }
75
161
  catch (Exception e) {
76
162
  Debug.LogException(e);
@@ -78,26 +164,27 @@ namespace Adrenak.UniVoice {
78
164
  };
79
165
 
80
166
  Client.OnPeerLeft += id => {
81
- if (!PeerOutputs.ContainsKey(id))
82
- return;
167
+ if (PeerOutputs.ContainsKey(id)) {
168
+ PeerOutputs[id].Dispose();
169
+ PeerOutputs.Remove(id);
170
+ }
83
171
 
84
- PeerOutputs[id].Dispose();
85
- PeerOutputs.Remove(id);
172
+ if (peerOutputFilters.ContainsKey(id)) {
173
+ peerOutputFilters.Remove(id);
174
+ }
86
175
  };
87
176
 
88
177
  Client.OnReceivedPeerAudioFrame += (id, audioFrame) => {
89
- if (!OutputsEnabled)
178
+ if (!OutputsEnabled || !PeerOutputs.ContainsKey(id))
90
179
  return;
91
180
 
92
- if (!PeerOutputs.ContainsKey(id))
93
- return;
94
-
95
- if (OutputFilters != null) {
96
- foreach (var filter in OutputFilters)
181
+ if (peerOutputFilters.TryGetValue(id, out var filters)) {
182
+ foreach (var filter in filters)
97
183
  audioFrame = filter.Run(audioFrame);
98
184
  }
99
- if(audioFrame.samples.Length > 0)
100
- PeerOutputs[id].Feed(audioFrame);
185
+
186
+ if (audioFrame.samples.Length > 0)
187
+ PeerOutputs[id]?.Feed(audioFrame);
101
188
  };
102
189
  }
103
190
  }
@@ -109,11 +196,11 @@ namespace Adrenak.UniVoice {
109
196
  public IAudioInput Input {
110
197
  get => input;
111
198
  set {
112
- if(input != null)
199
+ if (input != null)
113
200
  input.Dispose();
114
201
  input = value;
115
202
  input.OnFrameReady += frame => {
116
- if (!InputEnabled)
203
+ if (!InputEnabled)
117
204
  return;
118
205
 
119
206
  if (InputFilters != null) {
@@ -121,12 +208,44 @@ namespace Adrenak.UniVoice {
121
208
  frame = filter.Run(frame);
122
209
  }
123
210
 
124
- if(frame.samples.Length > 0)
211
+ if (frame.samples.Length > 0)
125
212
  Client.SendAudioFrame(frame);
126
213
  };
127
214
  }
128
215
  }
129
216
 
217
+ Func<IAudioOutput> outputProvider;
218
+ /// <summary>
219
+ /// The provider of IAudioOutput objects for peers.
220
+ /// If this value is being set while peers already exist,
221
+ /// the old outputs would be cleared and new onces will
222
+ /// be created.
223
+ /// </summary>
224
+ public Func<IAudioOutput> OutputProvider {
225
+ get => outputProvider;
226
+ set {
227
+ outputProvider = value;
228
+ outputFactory = null;
229
+
230
+ foreach (var output in PeerOutputs)
231
+ output.Value.Dispose();
232
+ PeerOutputs.Clear();
233
+
234
+ if(outputProvider != null) {
235
+ foreach (var id in Client.PeerIDs) {
236
+ try {
237
+ var output = outputProvider();
238
+ PeerOutputs.Add(id, output);
239
+ }
240
+ catch (Exception e) {
241
+ Debug.LogException(e);
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+
130
249
  IAudioOutputFactory outputFactory;
131
250
  /// <summary>
132
251
  /// The <see cref="IAudioOutputFactory"/> that creates the <see cref="IAudioOutput"/> of peers
@@ -135,18 +254,21 @@ namespace Adrenak.UniVoice {
135
254
  get => outputFactory;
136
255
  set {
137
256
  outputFactory = value;
257
+ outputProvider = null;
138
258
 
139
259
  foreach (var output in PeerOutputs)
140
260
  output.Value.Dispose();
141
261
  PeerOutputs.Clear();
142
262
 
143
- foreach (var id in Client.PeerIDs) {
144
- try {
145
- var output = outputFactory.Create();
146
- PeerOutputs.Add(id, output);
147
- }
148
- catch (Exception e) {
149
- Debug.LogException(e);
263
+ if (outputFactory != null) {
264
+ foreach (var id in Client.PeerIDs) {
265
+ try {
266
+ var output = outputFactory.Create();
267
+ PeerOutputs.Add(id, output);
268
+ }
269
+ catch (Exception e) {
270
+ Debug.LogException(e);
271
+ }
150
272
  }
151
273
  }
152
274
  }
@@ -156,5 +278,16 @@ namespace Adrenak.UniVoice {
156
278
  Client.Dispose();
157
279
  Input.Dispose();
158
280
  }
281
+
282
+ #region OBSOLETE
283
+
284
+ /// <summary>
285
+ /// The output <see cref="IAudioFilter"/> that will be applied to the incoming audio for all the peers.
286
+ /// Note that filters are executed in the order they are present in this list.
287
+ /// </summary>
288
+ [Obsolete("OutputFilters has been removed. Use AddOutputFilter and RemoveOutputFilter instead.", true)]
289
+ public List<IAudioFilter> OutputFilters { get; set; } = new List<IAudioFilter>();
290
+
291
+ #endregion
159
292
  }
160
293
  }
@@ -40,7 +40,7 @@ namespace Adrenak.UniVoice.Networks {
40
40
  ModeChanged?.Invoke(lastMode, newMode);
41
41
  }
42
42
  catch (Exception e) {
43
- Debug.Log(LogType.Error, TAG, "Exception while handling Mirror Mode change: " + e);
43
+ Debug.unityLogger.Log(LogType.Error, TAG, "Exception while handling Mirror Mode change: " + e);
44
44
  }
45
45
  lastMode = newMode;
46
46
  }
@@ -33,7 +33,7 @@ namespace Adrenak.UniVoice.Outputs {
33
33
  /// </summary>
34
34
  /// <param name="frame"></param>
35
35
  public void Feed(AudioFrame frame) {
36
- Stream.Feed(frame.frequency, frame.channelCount, Utils.Bytes.BytesToFloats(frame.samples), true);
36
+ Stream.Feed(frame.frequency, frame.channelCount, Utils.Bytes.BytesToFloats(frame.samples));
37
37
  }
38
38
 
39
39
  /// <summary>
@@ -185,7 +185,7 @@ namespace Adrenak.UniVoice.Samples {
185
185
  Debug.unityLogger.Log(LogType.Log, TAG, "Registered ConcentusEncodeFilter as an input filter");
186
186
 
187
187
  // For incoming audio register the ConcentusDecodeFilter to decode the encoded audio received from other clients
188
- ClientSession.OutputFilters.Add(new ConcentusDecodeFilter());
188
+ ClientSession.AddOutputFilter<ConcentusDecodeFilter>(() => new ConcentusDecodeFilter());
189
189
  Debug.unityLogger.Log(LogType.Log, TAG, "Registered ConcentusDecodeFilter as an output filter");
190
190
  }
191
191
 
@@ -89,7 +89,7 @@ namespace Adrenak.UniVoice.Samples {
89
89
 
90
90
  // Next, for incoming audio we register the Concentus decode filter as the audio we'd
91
91
  // receive from other clients would be encoded and not readily playable
92
- session.OutputFilters.Add(new ConcentusDecodeFilter());
92
+ session.AddOutputFilter<ConcentusDecodeFilter>(() => new ConcentusDecodeFilter());
93
93
 
94
94
  // Subscribe to some server events
95
95
  server.OnServerStart += () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.adrenak.univoice",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "displayName": "Adrenak.UniVoice",
5
5
  "description": "Voice chat/VoIP framework for Unity.",
6
6
  "unity": "2021.2",
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "com.adrenak.brw": "1.0.1",
36
- "com.adrenak.unimic": "3.2.4",
36
+ "com.adrenak.unimic": "3.3.0",
37
37
  "com.adrenak.concentus-unity": "1.0.1"
38
38
  }
39
39
  }