-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathVoiceChatPlugin.cs
More file actions
315 lines (269 loc) · 15.4 KB
/
Copy pathVoiceChatPlugin.cs
File metadata and controls
315 lines (269 loc) · 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
using Arch.Core;
using Arch.SystemGroups;
using Cysharp.Threading.Tasks;
using DCL.AssetsProvision;
using DCL.Audio;
using DCL.ChatArea;
using DCL.Communities.CommunitiesDataProvider;
using DCL.DebugUtilities;
using DCL.Diagnostics;
using DCL.Friends.UserBlocking;
using DCL.Multiplayer.Connections.RoomHubs;
using DCL.Multiplayer.Profiles.Tables;
using DCL.SceneRestrictionBusController.SceneRestrictionBus;
using DCL.UI.Profiles.Helpers;
using ECS.SceneLifeCycle;
using DCL.VoiceChat;
using DCL.VoiceChat.CommunityVoiceChat;
using DCL.VoiceChat.Nearby;
using DCL.VoiceChat.Nearby.Audio;
using DCL.VoiceChat.Nearby.Systems;
using LiveKit.Rooms;
using System;
using System.Threading;
using DCL.UI;
using DCL.Utilities;
using DCL.Utilities.Extensions;
using UnityEngine;
using UnityEngine.AddressableAssets;
using DCL.FeatureFlags;
using DCL.Prefs;
using DCL.RealmNavigation;
using DCL.SceneBannedUsers;
using DCL.VoiceChat.UI;
using Utility;
using Utility.Multithreading;
using AudioSettings = UnityEngine.AudioSettings;
using RustAudio;
namespace DCL.PluginSystem.Global
{
public class VoiceChatPlugin : IDCLGlobalPlugin<VoiceChatPlugin.Settings>
{
private readonly IAssetsProvisioner assetsProvisioner;
private readonly IDebugContainerBuilder debugContainer;
private readonly IRoomHub roomHub;
private readonly VoiceChatPanelView voiceChatPanelView;
private readonly ProfileRepositoryWrapper profileDataProvider;
private readonly CommunitiesDataProvider communityDataProvider;
private readonly ImageControllerProvider imageControllerProvider;
private readonly IReadOnlyEntityParticipantTable entityParticipantTable;
private readonly Arch.Core.World world;
private readonly Entity playerEntity;
private readonly VoiceChatOrchestrator voiceChatOrchestrator;
private readonly ChatSharedAreaEventBus chatSharedAreaEventBus;
private readonly EventSubscriptionScope pluginScope = new ();
private readonly NearbyMuteService? nearbyMuteService;
private readonly NearbyVoiceChatStateModel? nearbyStateModel;
private readonly IUserBlockingCache userBlockingCache;
private readonly NearbyVoiceChatButtonView nearbyVoiceChatButtonView;
private readonly NearbyVoiceWidgetView nearbyVoiceWidgetView;
private readonly NearbyVoiceTipView nearbyVoiceTipView;
private readonly ILoadingStatus loadingStatus;
private readonly IScenesCache scenesCache;
private readonly ISceneRestrictionBusController sceneRestrictionBusController;
private readonly VolumeBus volumeBus;
private ProvidedAsset<VoiceChatPluginSettings> voiceChatPluginSettingsAsset;
private VoiceChatMicrophoneHandler? voiceChatHandler;
private MicrophoneTrackPublisher? microphonePublisher;
private RemoteTrackListener? remoteListener;
private VoiceChatRoomManager? roomManager;
private VoiceChatNametagsHandler? nametagsHandler;
private VoiceChatMicrophoneStateManager? microphoneStateManager;
private MicrophoneAudioToggleHandler? microphoneAudioToggleHandler;
private VoiceChatPanelPresenter? voiceChatPanelPresenter;
private VoiceChatDebugContainer? voiceChatDebugContainer;
private NearbyAudioStreamsRegistry? nearbyAudioStreamRegistry;
private NearbyAudioSourceFactory? nearbyAudioSourceFactory;
private NearbyVoiceChatSuppressor? nearbyVoiceChatSuppressor;
private NearbyMicrophoneHandler? nearbyMicrophoneHandler;
private NearbyMicrophoneAudioToggleHandler? nearbyMicrophoneAudioToggleHandler;
private NearbyVoiceChatButtonController? nearbyButtonController;
private NearbyVoiceWidgetController? nearbyWidgetController;
private CancellationTokenSource? nearbyTipCts;
private VoiceChatConfiguration voiceChatConfiguration;
public VoiceChatPlugin(
IRoomHub roomHub,
VoiceChatPanelView voiceChatPanelView,
VoiceChatContainer voiceChatContainer,
ProfileRepositoryWrapper profileDataProvider,
IReadOnlyEntityParticipantTable entityParticipantTable,
Arch.Core.World world,
Entity playerEntity,
CommunitiesDataProvider communityDataProvider,
ImageControllerProvider imageControllerProvider,
IAssetsProvisioner assetsProvisioner,
ChatSharedAreaEventBus chatSharedAreaEventBus,
IDebugContainerBuilder debugContainer,
ILoadingStatus loadingStatus,
IScenesCache scenesCache,
ISceneRestrictionBusController sceneRestrictionBusController,
NearbyVoiceChatButtonView nearbyVoiceChatButtonView,
NearbyVoiceWidgetView nearbyVoiceWidgetView,
NearbyVoiceTipView nearbyVoiceTipView,
VolumeBus volumeBus,
IUserBlockingCache userBlockingCache,
NearbyMuteService? nearbyMuteService = null,
NearbyVoiceChatStateModel? nearbyStateModel = null)
{
this.nearbyMuteService = nearbyMuteService;
this.nearbyStateModel = nearbyStateModel;
this.userBlockingCache = userBlockingCache;
this.roomHub = roomHub;
this.voiceChatPanelView = voiceChatPanelView;
this.profileDataProvider = profileDataProvider;
this.entityParticipantTable = entityParticipantTable;
this.world = world;
this.playerEntity = playerEntity;
this.communityDataProvider = communityDataProvider;
this.imageControllerProvider = imageControllerProvider;
this.assetsProvisioner = assetsProvisioner;
this.chatSharedAreaEventBus = chatSharedAreaEventBus;
this.debugContainer = debugContainer;
this.loadingStatus = loadingStatus;
this.scenesCache = scenesCache;
this.sceneRestrictionBusController = sceneRestrictionBusController;
this.nearbyVoiceChatButtonView = nearbyVoiceChatButtonView;
this.nearbyVoiceWidgetView = nearbyVoiceWidgetView;
this.nearbyVoiceTipView = nearbyVoiceTipView;
this.volumeBus = volumeBus;
voiceChatOrchestrator = voiceChatContainer.VoiceChatOrchestrator;
}
public void Dispose()
{
nearbyTipCts.SafeCancelAndDispose();
pluginScope.Dispose();
if (voiceChatPluginSettingsAsset.Value != null)
voiceChatPluginSettingsAsset.Dispose();
RustAudioClient.DeInit();
}
public void InjectToWorld(ref ArchSystemsWorldBuilder<Arch.Core.World> builder, in GlobalPluginArguments arguments)
{
if (FeaturesRegistry.Instance.IsEnabled(FeatureId.NEARBY_VOICE_CHAT))
{
var listenerState = new NearbyListenerState();
NearbyLivekitBridgeSystem.InjectToWorld(ref builder, nearbyAudioStreamRegistry!);
NearbyAudibleRangeSystem.InjectToWorld(ref builder, voiceChatConfiguration, listenerState);
NearbyAudioBindingSystem.InjectToWorld(ref builder, nearbyAudioStreamRegistry!, userBlockingCache, nearbyStateModel!, nearbyAudioSourceFactory!, RoomMetadataCurrentScene.Instance);
NearbyAudioPositionSystem.InjectToWorld(ref builder, nearbyMuteService!, listenerState);
NearbyAudioCleanupSystem.InjectToWorld(ref builder, nearbyAudioStreamRegistry!, userBlockingCache, nearbyStateModel!, nearbyAudioSourceFactory!, RoomMetadataCurrentScene.Instance);
NearbyVoiceChatNametagSystem.InjectToWorld(ref builder, playerEntity, nearbyAudioStreamRegistry!, nearbyStateModel!, nearbyMuteService!);
NearbyVoiceChatDebugSystem.InjectToWorld(ref builder, voiceChatConfiguration, debugContainer, roomHub.IslandRoom(), nearbyStateModel!, nearbyAudioStreamRegistry!, entityParticipantTable);
}
}
public async UniTask InitializeAsync(Settings settings, CancellationToken ct)
{
ReportHub.LogWarning(ReportCategory.VOICE_CHAT, "VOICE CHAT!");
AudioConfiguration audioConfig = AudioSettings.GetConfiguration();
audioConfig.sampleRate = VoiceChatConstants.LIVEKIT_SAMPLE_RATE;
AudioSettings.Reset(audioConfig);
voiceChatPluginSettingsAsset = await assetsProvisioner.ProvideMainAssetAsync(settings.VoiceChatConfigurations, ct: ct);
VoiceChatPluginSettings pluginSettings = voiceChatPluginSettingsAsset.Value;
voiceChatConfiguration = pluginSettings.VoiceChatConfiguration;
voiceChatHandler = new VoiceChatMicrophoneHandler(voiceChatConfiguration, voiceChatOrchestrator);
pluginScope.Add(voiceChatHandler);
microphoneStateManager = new VoiceChatMicrophoneStateManager(voiceChatHandler, voiceChatOrchestrator);
pluginScope.Add(microphoneStateManager);
microphonePublisher = new MicrophoneTrackPublisher(roomHub.VoiceChatRoom().Room(), voiceChatConfiguration, VoiceChatType.COMMUNITY);
var callPlaybackSourcesHub = new PlaybackSourcesHub("Call", voiceChatConfiguration.ChatAudioMixerGroup.EnsureNotNull());
remoteListener = new RemoteTrackListener(
roomHub.VoiceChatRoom().Room(),
voiceChatConfiguration,
callPlaybackSourcesHub);
roomManager = new VoiceChatRoomManager(microphonePublisher, remoteListener, roomHub, roomHub.VoiceChatRoom().Room(), voiceChatOrchestrator, voiceChatConfiguration, microphoneStateManager, voiceChatHandler);
pluginScope.Add(roomManager);
nametagsHandler = new VoiceChatNametagsHandler(
roomHub.VoiceChatRoom().Room(),
voiceChatOrchestrator,
entityParticipantTable,
world,
playerEntity);
pluginScope.Add(nametagsHandler);
VoiceChatParticipantEntryView playerEntry = pluginSettings.PlayerEntryView;
AudioClipConfig muteMicrophoneAudio = pluginSettings.MuteMicrophoneAudio;
AudioClipConfig unmuteMicrophoneAudio = pluginSettings.UnmuteMicrophoneAudio;
microphoneAudioToggleHandler = new MicrophoneAudioToggleHandler(voiceChatHandler.IsMicrophoneEnabled, muteMicrophoneAudio, unmuteMicrophoneAudio);
pluginScope.Add(microphoneAudioToggleHandler);
voiceChatPanelPresenter = new VoiceChatPanelPresenter(voiceChatPanelView, profileDataProvider, communityDataProvider, imageControllerProvider, voiceChatOrchestrator, voiceChatHandler, roomManager, roomHub, playerEntry, chatSharedAreaEventBus);
pluginScope.Add(voiceChatPanelPresenter);
voiceChatDebugContainer = new VoiceChatDebugContainer(debugContainer, microphonePublisher, roomHub.VoiceChatRoom().Room(), callPlaybackSourcesHub);
pluginScope.Add(voiceChatDebugContainer);
if (FeaturesRegistry.Instance.IsEnabled(FeatureId.NEARBY_VOICE_CHAT))
{
IRoom islandRoom = roomHub.IslandRoom();
nearbyAudioStreamRegistry = new NearbyAudioStreamsRegistry(islandRoom);
pluginScope.Add(nearbyAudioStreamRegistry);
nearbyAudioSourceFactory = new NearbyAudioSourceFactory(voiceChatConfiguration);
// State model is created in DynamicWorldContainer so analytics can subscribe to it.
NearbyVoiceChatStateModel stateModel = nearbyStateModel!;
pluginScope.Add(stateModel);
// Persist the user's on/off preference of the nearby chat.
stateModel.State.Subscribe(newState =>
{
if (newState is NearbyVoiceChatState.DISABLED or NearbyVoiceChatState.IDLE)
DCLPlayerPrefs.SetBool(DCLPrefKeys.NEARBY_VOICE_CHAT_DISABLED, newState == NearbyVoiceChatState.DISABLED);
});
var sceneRestrictionWatcher = new NearbyVoiceSceneRestrictionWatcher(scenesCache, sceneRestrictionBusController, stateModel);
pluginScope.Add(sceneRestrictionWatcher);
var bannedPlayerWatcher = new NearbyVoiceBannedPlayerWatcher(roomHub, sceneRestrictionBusController, stateModel);
pluginScope.Add(bannedPlayerWatcher);
nearbyMuteService!.LoadAsync(ct).Forget();
nearbyVoiceChatSuppressor = new NearbyVoiceChatSuppressor(stateModel, voiceChatOrchestrator.CurrentCallStatus, loadingStatus);
pluginScope.Add(nearbyVoiceChatSuppressor);
nearbyMicrophoneHandler = new NearbyMicrophoneHandler(stateModel, islandRoom, voiceChatConfiguration);
pluginScope.Add(nearbyMicrophoneHandler);
nearbyMicrophoneAudioToggleHandler = new NearbyMicrophoneAudioToggleHandler(stateModel, voiceChatConfiguration, muteMicrophoneAudio, unmuteMicrophoneAudio);
pluginScope.Add(nearbyMicrophoneAudioToggleHandler);
// UI
nearbyButtonController = new NearbyVoiceChatButtonController(nearbyVoiceChatButtonView, stateModel);
pluginScope.Add(nearbyButtonController);
nearbyWidgetController = new NearbyVoiceWidgetController(nearbyVoiceWidgetView, stateModel, voiceChatConfiguration.ChatAudioMixerGroup, volumeBus);
pluginScope.Add(nearbyWidgetController);
// Intro FLUX
nearbyTipCts = new CancellationTokenSource();
RunNearbyVoiceTipAsync(nearbyVoiceTipView, loadingStatus, nearbyVoiceChatButtonView, nearbyTipCts.Token).Forget();
}
}
private static async UniTaskVoid RunNearbyVoiceTipAsync(NearbyVoiceTipView view, ILoadingStatus loadingStatus,
NearbyVoiceChatButtonView buttonView, CancellationToken ct)
{
if (await NearbyVoiceTipFlow.WaitAndShowAsync(view, loadingStatus, ct))
buttonView.Button.onClick.Invoke();
}
[Serializable]
public class Settings : IDCLPluginSettings
{
[field: SerializeField] public VoiceChatConfigurationsReference VoiceChatConfigurations { get; private set; } = null!;
[Serializable]
public class VoiceChatConfigurationsReference : AssetReferenceT<VoiceChatPluginSettings>
{
public VoiceChatConfigurationsReference(string guid) : base(guid) { }
}
}
private static class NearbyVoiceTipFlow
{
public static async UniTask<bool> WaitAndShowAsync(NearbyVoiceTipView view, ILoadingStatus loadingStatus, CancellationToken ct)
{
view.Hide();
if (DCLPlayerPrefs.GetBool(DCLPrefKeys.NEARBY_VOICE_TIP_DISMISSED))
return false;
try
{
await UniTask.WaitUntil(
() => loadingStatus.CurrentStage.Value == LoadingStatus.LoadingStage.Completed,
cancellationToken: ct);
view.Show();
int winner = await UniTask.WhenAny(
view.CloseButton.OnClickAsync(ct),
view.TryItNowButton.OnClickAsync(ct));
DCLPlayerPrefs.SetBool(DCLPrefKeys.NEARBY_VOICE_TIP_DISMISSED, true, save: true);
view.Hide();
return winner == 1;
}
catch (OperationCanceledException)
{
return false;
}
}
}
}
}