Skip to content

Commit bcae1e8

Browse files
committed
audio and microphone range overrides added
1 parent de02ceb commit bcae1e8

20 files changed

Lines changed: 336 additions & 7 deletions

File tree

Basis Server/BasisNetworkCore/BasisConfigXmlDocs.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ private static void RegisterServerConfig()
186186
t.Fields.Add(new FieldDoc("AdditionalAvatarDataLock", " Strip AdditionalAvatarDatas (blendshapes, custom-behaviour params) from inbound avatar sync before relaying; muscle/position/rotation still sync. true|false. "));
187187
t.Fields.Add(new FieldDoc("CameraMetadataDisallowMask", " Bitmask of camera photo-metadata categories disallowed for all clients (set bit = disallowed). 0 = everything allowed. byte, range 0-255; the category-to-bit mapping is defined client-side. "));
188188
t.Fields.Add(new FieldDoc("CrashReportingEnabled", " Allow clients to send a one-shot report of each error/exception they hit to the server, stored under CrashReports/<uuid>.jsonl with their UUID and display name. true|false; default true. Set false to globally disable reporting (clients are told to stop sending). ", " ===== Diagnostics ===== "));
189+
t.Fields.Add(new FieldDoc("MaxMicrophoneRangeMeters", " Maximum microphone (voice transmit) range, in metres, a client may set. Clients clamp their Microphone Range slider and effective range to this ceiling; can also be changed live from the admin panel. float; default 25. ", " ===== Audio / voice range ===== "));
190+
t.Fields.Add(new FieldDoc("MaxHearingRangeMeters", " Maximum hearing (audio receive) range, in metres, a client may set. Clients clamp their Hearing Range slider and effective range to this ceiling. float; default 25. "));
189191
_docs[typeof(global::Configuration)] = t;
190192
}
191193

Basis Server/BasisNetworkCore/BasisServerConfiguration.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class Configuration
1919
/// doc comments). Newly-added settings are healed automatically regardless: on load a
2020
/// config missing any current field is re-saved with the new settings added.
2121
/// </summary>
22-
public const int CurrentConfigVersion = 1;
22+
public const int CurrentConfigVersion = 2;
2323
/// <summary>Schema version stamped into config.xml; 0 = a pre-versioning file that is upgraded on load.</summary>
2424
public int ConfigVersion = 0;
2525

@@ -102,6 +102,8 @@ public class Configuration
102102
/// </summary>
103103
public byte CameraMetadataDisallowMask = 0;
104104
public bool CrashReportingEnabled = true;
105+
public float MaxMicrophoneRangeMeters = 25f;
106+
public float MaxHearingRangeMeters = 25f;
105107
/// <summary>
106108
/// Read config from file. If no file is found create a default config file at filePath.
107109
/// Also loads per-transport config sidecars from <c>{configDir}/transports/{stackId}.xml</c>.

Basis Server/BasisNetworkCore/Serializable/AdminRequest.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ public enum AdminRequestMode : byte
106106

107107
GlobalGetCrashReportState, // server→client: whether client error/exception reporting is enabled
108108
SetGlobalCrashReporting, // admin: enable/disable client error/exception reporting (persisted). Payload: [bool]
109+
110+
GlobalGetAudioRangeLimits, // server→client: max microphone + hearing range in metres. Payload: [float micMeters][float hearingMeters]
111+
SetGlobalAudioRangeLimits, // admin: set max microphone + hearing range in metres (persisted). Payload: [float micMeters][float hearingMeters]
109112
}
110113
}
111114
}

Basis Server/BasisNetworkServer/BasisServerHandleEvents.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ public static void OnNetworkAccepted(NetPeer newPeer, ReadyMessage ReadyMessage,
359359
BasisNetworkServer.Security.BasisOpusFrameDurationStateManager.SendStateToPeer(newPeer);
360360
BasisNetworkServer.Security.BasisUserOpusBitrateStateManager.SendStateToPeer(newPeer);
361361
BasisNetworkServer.Security.BasisCrashReportStateManager.SendStateToPeer(newPeer);
362+
BasisNetworkServer.Security.BasisAudioRangeLimitManager.SendStateToPeer(newPeer);
362363
SendShoutStateToPeer(newPeer);
363364
}
364365
else

Basis Server/BasisNetworkServer/NetworkServer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public static void StartServer(Configuration configuration)
7474
BasisHeadlessConnectionPolicyManager.InitializeFromConfig(configuration.DisallowHeadless);
7575
BasisNetworkServer.Security.BasisGlobalLockManager.InitializeFromConfig(configuration);
7676
BasisNetworkServer.Security.BasisCrashReportStateManager.InitializeFromConfig(configuration);
77+
BasisNetworkServer.Security.BasisAudioRangeLimitManager.InitializeFromConfig(configuration);
7778
SetupServer(configuration);
7879
SubscribeEvents(Configuration);
7980

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Basis.Network.Core;
2+
using System.Threading;
3+
using static BasisNetworkCore.Serializable.SerializableBasis;
4+
5+
namespace BasisNetworkServer.Security
6+
{
7+
/// <summary>
8+
/// Server-defined ceilings (in metres) for how far clients may set their microphone (voice
9+
/// transmit) and hearing (audio receive) range. Seeded from Configuration at boot and pushed to
10+
/// clients via GlobalGetAudioRangeLimits so they clamp their sliders and effective range to it.
11+
/// Admins can change it live; the new values are persisted to config.xml and broadcast.
12+
/// </summary>
13+
public static class BasisAudioRangeLimitManager
14+
{
15+
private const float DefaultMeters = 25f;
16+
17+
private static float _maxMicrophoneRangeMeters = DefaultMeters;
18+
private static float _maxHearingRangeMeters = DefaultMeters;
19+
20+
public static float MaxMicrophoneRangeMeters => Interlocked.CompareExchange(ref _maxMicrophoneRangeMeters, 0f, 0f);
21+
public static float MaxHearingRangeMeters => Interlocked.CompareExchange(ref _maxHearingRangeMeters, 0f, 0f);
22+
23+
public static void InitializeFromConfig(Configuration config)
24+
{
25+
Interlocked.Exchange(ref _maxMicrophoneRangeMeters, Sanitize(config.MaxMicrophoneRangeMeters));
26+
Interlocked.Exchange(ref _maxHearingRangeMeters, Sanitize(config.MaxHearingRangeMeters));
27+
}
28+
29+
/// <summary>Clamp, set, and report whether either value actually changed.</summary>
30+
public static bool SetLimits(float microphoneMeters, float hearingMeters)
31+
{
32+
float mic = Sanitize(microphoneMeters);
33+
float hearing = Sanitize(hearingMeters);
34+
float prevMic = Interlocked.Exchange(ref _maxMicrophoneRangeMeters, mic);
35+
float prevHearing = Interlocked.Exchange(ref _maxHearingRangeMeters, hearing);
36+
return prevMic != mic || prevHearing != hearing;
37+
}
38+
39+
public static void SendStateToPeer(NetPeer peer)
40+
{
41+
NetDataWriter writer = NetworkServer.RentWriter();
42+
try
43+
{
44+
new AdminRequest().Serialize(writer, AdminRequestMode.GlobalGetAudioRangeLimits);
45+
writer.Put(MaxMicrophoneRangeMeters);
46+
writer.Put(MaxHearingRangeMeters);
47+
NetworkServer.TrySend(peer, writer, BasisNetworkCommons.AdminChannel, DeliveryMethod.ReliableOrdered);
48+
}
49+
finally
50+
{
51+
NetworkServer.ReturnWriter(writer);
52+
}
53+
}
54+
55+
public static void BroadcastState()
56+
{
57+
NetDataWriter writer = NetworkServer.RentWriter();
58+
try
59+
{
60+
new AdminRequest().Serialize(writer, AdminRequestMode.GlobalGetAudioRangeLimits);
61+
writer.Put(MaxMicrophoneRangeMeters);
62+
writer.Put(MaxHearingRangeMeters);
63+
NetworkServer.BroadcastMessageToClients(
64+
writer,
65+
BasisNetworkCommons.AdminChannel,
66+
NetworkServer.PeerSnapshot,
67+
DeliveryMethod.ReliableOrdered);
68+
}
69+
finally
70+
{
71+
NetworkServer.ReturnWriter(writer);
72+
}
73+
}
74+
75+
private static float Sanitize(float meters)
76+
{
77+
return meters <= 0f ? DefaultMeters : meters;
78+
}
79+
}
80+
}

Basis Server/BasisNetworkServer/Security/BasisPlayerModeration.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,11 @@ public static void OnAdminMessage(NetPeer peer, NetPacketReader reader)
364364
HandleCrashReportingSet(peer, reader));
365365
break;
366366

367+
case AdminRequestMode.SetGlobalAudioRangeLimits:
368+
Require(peer, PermNodes.ModerationGlobalLock, () =>
369+
HandleAudioRangeLimitsSet(peer, reader));
370+
break;
371+
367372
case AdminRequestMode.SetGlobalHeadlessDisallow:
368373
Require(peer, PermNodes.ModerationGlobalLock, () =>
369374
HandleHeadlessDisallowSet(peer, reader));
@@ -684,6 +689,18 @@ private static void HandleCrashReportingSet(NetPeer peer, NetPacketReader reader
684689
SendBackMessage(peer, $"Crash reporting {(enabled ? "ENABLED" : "DISABLED")}.");
685690
}
686691

692+
private static void HandleAudioRangeLimitsSet(NetPeer peer, NetPacketReader reader)
693+
{
694+
float microphoneMeters = reader.GetFloat();
695+
float hearingMeters = reader.GetFloat();
696+
BasisAudioRangeLimitManager.SetLimits(microphoneMeters, hearingMeters);
697+
NetworkServer.Configuration.MaxMicrophoneRangeMeters = BasisAudioRangeLimitManager.MaxMicrophoneRangeMeters;
698+
NetworkServer.Configuration.MaxHearingRangeMeters = BasisAudioRangeLimitManager.MaxHearingRangeMeters;
699+
SaveConfig();
700+
BasisAudioRangeLimitManager.BroadcastState();
701+
SendBackMessage(peer, $"Audio range limits set: microphone {NetworkServer.Configuration.MaxMicrophoneRangeMeters} m, hearing {NetworkServer.Configuration.MaxHearingRangeMeters} m.");
702+
}
703+
687704
private static void HandleGlobalToggle(NetPeer peer, string contentType, bool nowLocked)
688705
{
689706
string state = nowLocked ? "DISABLED" : "ENABLED";

Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5026,6 +5026,14 @@
50265026
"key": "settings.admin.opusFecLoss",
50275027
"value": "Opus FEC loss %"
50285028
},
5029+
{
5030+
"key": "settings.admin.maxMicrophoneRange",
5031+
"value": "Max Microphone Range"
5032+
},
5033+
{
5034+
"key": "settings.admin.maxHearingRange",
5035+
"value": "Max Hearing Range"
5036+
},
50295037
{
50305038
"key": "settings.main.title.searchMenus",
50315039
"value": "Search Menus"

Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,7 @@ void MicrophoneSelectionChanged(string name)
771771
// Microphone broadcast range (relocated from General).
772772
PanelSlider sliderMicrophoneRange = PanelSlider.CreateEntryAndBind(
773773
microphoneGroup,
774-
PanelSlider.SliderSettings.Distance(BasisLocalization.Get("settings.general.microphoneRange"), 25),
774+
PanelSlider.SliderSettings.Distance(BasisLocalization.Get("settings.general.microphoneRange"), BasisNetworkModeration.ServerMaxMicrophoneRangeMeters),
775775
BasisSettingsDefaults.MicrophoneRange);
776776

777777
PanelToggle toggleMicrophoneDenoiser = PanelToggle.CreateNewEntry(microphoneGroup);

Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderAdminTab.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ public static PanelTabPage AdminTab(PanelTabGroup tabGroup)
9898
opusPacketLossSlider.SetValueWithoutNotify(BasisNetworkModeration.GlobalOpusPacketLossPercent);
9999
opusPacketLossSlider.OnValueChanged += value => BasisNetworkModeration.SetGlobalOpusPacketLoss(Mathf.RoundToInt(value));
100100

101+
PanelSlider maxMicrophoneRangeSlider = PanelSlider.CreateNew(PanelSlider.SliderStyles.Entry, lockGroup.ContentParent);
102+
maxMicrophoneRangeSlider.SetSliderSettings(PanelSlider.SliderSettings.Advanced(BasisLocalization.Get("settings.admin.maxMicrophoneRange"), 1f, 200f, true, 0, ValueDisplayMode.Meters));
103+
maxMicrophoneRangeSlider.Descriptor.SetDescription("Maximum microphone (voice transmit) range in metres any client may set. Each client's Microphone Range slider and effective range is clamped to this.");
104+
maxMicrophoneRangeSlider.SetValueWithoutNotify(BasisNetworkModeration.ServerMaxMicrophoneRangeMeters);
105+
106+
PanelSlider maxHearingRangeSlider = PanelSlider.CreateNew(PanelSlider.SliderStyles.Entry, lockGroup.ContentParent);
107+
maxHearingRangeSlider.SetSliderSettings(PanelSlider.SliderSettings.Advanced(BasisLocalization.Get("settings.admin.maxHearingRange"), 1f, 200f, true, 0, ValueDisplayMode.Meters));
108+
maxHearingRangeSlider.Descriptor.SetDescription("Maximum hearing (audio receive) range in metres any client may set. Each client's Hearing Range slider and effective range is clamped to this.");
109+
maxHearingRangeSlider.SetValueWithoutNotify(BasisNetworkModeration.ServerMaxHearingRangeMeters);
110+
101111
controller.AvatarLockToggle = avatarLock;
102112
controller.PropLockToggle = propLock;
103113
controller.WorldLockToggle = worldLock;
@@ -107,6 +117,20 @@ public static PanelTabPage AdminTab(PanelTabGroup tabGroup)
107117
controller.HeadlessAudioToggle = headlessAudioToggle;
108118
controller.HeadlessDisallowToggle = disallowHeadlessToggle;
109119
controller.OpusPacketLossSlider = opusPacketLossSlider;
120+
controller.MaxMicrophoneRangeSlider = maxMicrophoneRangeSlider;
121+
controller.MaxHearingRangeSlider = maxHearingRangeSlider;
122+
controller.MaxMicrophoneRangeMeters = BasisNetworkModeration.ServerMaxMicrophoneRangeMeters;
123+
controller.MaxHearingRangeMeters = BasisNetworkModeration.ServerMaxHearingRangeMeters;
124+
maxMicrophoneRangeSlider.OnValueChanged += value =>
125+
{
126+
controller.MaxMicrophoneRangeMeters = value;
127+
BasisNetworkModeration.SetGlobalAudioRangeLimits(controller.MaxMicrophoneRangeMeters, controller.MaxHearingRangeMeters);
128+
};
129+
maxHearingRangeSlider.OnValueChanged += value =>
130+
{
131+
controller.MaxHearingRangeMeters = value;
132+
BasisNetworkModeration.SetGlobalAudioRangeLimits(controller.MaxMicrophoneRangeMeters, controller.MaxHearingRangeMeters);
133+
};
110134

111135
// --- Camera photo metadata policy (per-category disallow; default allowed) ---
112136
PanelElementDescriptor cameraPolicyGroup =
@@ -425,6 +449,10 @@ private sealed class AdminTabController : MonoBehaviour
425449
public PanelToggle HeadlessAudioToggle;
426450
public PanelToggle HeadlessDisallowToggle;
427451
public PanelSlider OpusPacketLossSlider;
452+
public PanelSlider MaxMicrophoneRangeSlider;
453+
public PanelSlider MaxHearingRangeSlider;
454+
public float MaxMicrophoneRangeMeters;
455+
public float MaxHearingRangeMeters;
428456
public System.Action<byte> ApplyCameraMask;
429457

430458
private void OnEnable()
@@ -443,6 +471,8 @@ private void OnEnable()
443471
BasisNetworkModeration.OnGlobalHeadlessDisallowStateChanged += OnGlobalHeadlessDisallowStateChanged;
444472
BasisNetworkModeration.OnGlobalOpusPacketLossChanged -= OnGlobalOpusPacketLossChanged;
445473
BasisNetworkModeration.OnGlobalOpusPacketLossChanged += OnGlobalOpusPacketLossChanged;
474+
BasisNetworkModeration.OnAudioRangeLimitsChanged -= OnAudioRangeLimitsChanged;
475+
BasisNetworkModeration.OnAudioRangeLimitsChanged += OnAudioRangeLimitsChanged;
446476
BasisNetworkModeration.OnGlobalCameraPolicyChanged -= OnGlobalCameraPolicyChanged;
447477
BasisNetworkModeration.OnGlobalCameraPolicyChanged += OnGlobalCameraPolicyChanged;
448478
}
@@ -457,6 +487,7 @@ private void OnDisable()
457487
BasisNetworkModeration.OnGlobalHeadlessAudioStateChanged -= OnGlobalHeadlessAudioStateChanged;
458488
BasisNetworkModeration.OnGlobalHeadlessDisallowStateChanged -= OnGlobalHeadlessDisallowStateChanged;
459489
BasisNetworkModeration.OnGlobalOpusPacketLossChanged -= OnGlobalOpusPacketLossChanged;
490+
BasisNetworkModeration.OnAudioRangeLimitsChanged -= OnAudioRangeLimitsChanged;
460491
BasisNetworkModeration.OnGlobalCameraPolicyChanged -= OnGlobalCameraPolicyChanged;
461492
}
462493

@@ -468,6 +499,7 @@ private void OnDestroy()
468499
BasisNetworkModeration.OnGlobalHeadlessAudioStateChanged -= OnGlobalHeadlessAudioStateChanged;
469500
BasisNetworkModeration.OnGlobalHeadlessDisallowStateChanged -= OnGlobalHeadlessDisallowStateChanged;
470501
BasisNetworkModeration.OnGlobalOpusPacketLossChanged -= OnGlobalOpusPacketLossChanged;
502+
BasisNetworkModeration.OnAudioRangeLimitsChanged -= OnAudioRangeLimitsChanged;
471503
BasisNetworkModeration.OnGlobalCameraPolicyChanged -= OnGlobalCameraPolicyChanged;
472504
}
473505

@@ -504,6 +536,14 @@ private void OnGlobalOpusPacketLossChanged(int percent)
504536
if (OpusPacketLossSlider != null) OpusPacketLossSlider.SetValueWithoutNotify(percent);
505537
}
506538

539+
private void OnAudioRangeLimitsChanged(float microphoneMeters, float hearingMeters)
540+
{
541+
MaxMicrophoneRangeMeters = microphoneMeters;
542+
MaxHearingRangeMeters = hearingMeters;
543+
if (MaxMicrophoneRangeSlider != null) MaxMicrophoneRangeSlider.SetValueWithoutNotify(microphoneMeters);
544+
if (MaxHearingRangeSlider != null) MaxHearingRangeSlider.SetValueWithoutNotify(hearingMeters);
545+
}
546+
507547
private void OnGlobalCameraPolicyChanged(byte mask)
508548
{
509549
ApplyCameraMask?.Invoke(mask);

0 commit comments

Comments
 (0)