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