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.
- package/Runtime/ClientSession.cs +173 -40
- package/Runtime/Impl/Networks/Mirror/MirrorModeObserver.cs +1 -1
- package/Runtime/Impl/Outputs/StreamedAudioSourceOutput.cs +1 -1
- package/Samples~/Basic Setup Scripts/UniVoiceMirrorSetupSample.cs +1 -1
- package/Samples~/Group Chat Sample/Scripts/GroupVoiceCallMirrorSample.cs +1 -1
- package/package.json +2 -2
package/Runtime/ClientSession.cs
CHANGED
|
@@ -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
|
|
10
|
-
///
|
|
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
|
-
///
|
|
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
|
-
///
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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 (
|
|
82
|
-
|
|
167
|
+
if (PeerOutputs.ContainsKey(id)) {
|
|
168
|
+
PeerOutputs[id].Dispose();
|
|
169
|
+
PeerOutputs.Remove(id);
|
|
170
|
+
}
|
|
83
171
|
|
|
84
|
-
|
|
85
|
-
|
|
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 (
|
|
93
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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)
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
36
|
+
"com.adrenak.unimic": "3.3.0",
|
|
37
37
|
"com.adrenak.concentus-unity": "1.0.1"
|
|
38
38
|
}
|
|
39
39
|
}
|