fix(voice): clamp transmit when speakers muted
This commit is contained in:
@@ -718,8 +718,14 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
final next = !_outputMuted;
|
||||
try {
|
||||
await rust.setOutputMuted(muted: next);
|
||||
await rust.setHardMute(
|
||||
muted: next || _inputMuted || _hardMuteByPermission,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() => _outputMuted = next);
|
||||
setState(() {
|
||||
_outputMuted = next;
|
||||
_hardMute = next || _inputMuted || _hardMuteByPermission;
|
||||
});
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _error = e.toString());
|
||||
@@ -887,6 +893,7 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_inputMuted = next;
|
||||
_hardMute = next;
|
||||
_hardMuteByPermission = false;
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -300,6 +300,13 @@ struct ConnectedState {
|
||||
/// for that channel. Kept in memory only; durable secret storage is
|
||||
/// a separate storage-schema decision.
|
||||
channel_passwords: HashMap<u64, String>,
|
||||
/// Local microphone mute state used to resolve the single
|
||||
/// transmit hard-mute clamp from independent UI mute buttons.
|
||||
local_input_muted: bool,
|
||||
/// Local speaker mute state. Product rule for P0: speaker muted
|
||||
/// also means mic disabled, so this participates in the local
|
||||
/// transmit hard-mute clamp without sending server-side deafen.
|
||||
local_output_muted: bool,
|
||||
}
|
||||
|
||||
fn normalize_channel_password(password: Option<String>) -> Option<String> {
|
||||
@@ -610,6 +617,8 @@ impl ChanoraSession {
|
||||
sup_inner,
|
||||
join_state,
|
||||
channel_passwords: HashMap::new(),
|
||||
local_input_muted: false,
|
||||
local_output_muted: false,
|
||||
});
|
||||
|
||||
// TS3 servers auto-place a newly-connected client into the
|
||||
@@ -996,15 +1005,23 @@ impl ChanoraSession {
|
||||
input: Option<bool>,
|
||||
output: Option<bool>,
|
||||
) -> Result<(), CoreError> {
|
||||
let guard = self.inner.lock().await;
|
||||
let state = guard.as_ref().ok_or(CoreError::NotConnected)?;
|
||||
let mut guard = self.inner.lock().await;
|
||||
let state = guard.as_mut().ok_or(CoreError::NotConnected)?;
|
||||
// Server-side output mute/deafen makes TeamSpeak/tsclientlib
|
||||
// consider the client unable to send audio. That is correct
|
||||
// for a server-visible "deafened" state, but our P0 speaker
|
||||
// button is a local playback mute. Keep output mute local-only
|
||||
// so Android can still transmit while the user silences remote
|
||||
// playback on this device.
|
||||
state.protocol.set_muted(input, None).await?;
|
||||
// button is a local playback mute. Keep output mute off the
|
||||
// server-side output/deafen flag, but fold it into the mic
|
||||
// disabled state below because P0 product semantics are:
|
||||
// speaker disabled also means microphone disabled.
|
||||
if let Some(muted) = input {
|
||||
state.local_input_muted = muted;
|
||||
}
|
||||
if let Some(muted) = output {
|
||||
state.local_output_muted = muted;
|
||||
}
|
||||
let mic_disabled = state.local_input_muted || state.local_output_muted;
|
||||
state.protocol.set_muted(Some(mic_disabled), None).await?;
|
||||
if let Some(muted) = output {
|
||||
if let Some(audio) = state.audio.as_ref() {
|
||||
audio.set_output_muted(muted);
|
||||
@@ -1019,9 +1036,8 @@ impl ChanoraSession {
|
||||
// flooded the log to 200 MB on the Korean test host.
|
||||
// Clamp the transmit-mode selector's hard_mute input so
|
||||
// the gate goes false too.
|
||||
if let Some(muted) = input {
|
||||
self.voice_selector.set_hard_mute(muted);
|
||||
}
|
||||
self.voice_selector
|
||||
.set_hard_mute(mic_disabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user