fix(ios): keep voice chat on Bluetooth headset
Build and test / test (macos-latest) (push) Waiting to run
Build and test / test (ubuntu-24.04) (push) Waiting to run
Build and test / test (windows-latest) (push) Waiting to run
Build and test / apple-client (push) Waiting to run

This commit is contained in:
2026-09-25 12:27:27 +02:00
parent f3ac779bf4
commit 724f7e912d
3 changed files with 35 additions and 6 deletions
+16 -4
View File
@@ -52,10 +52,14 @@ internal sealed class IosAudioRouter
if (Enum.TryParse(defaults.StringForKey("cat.voice.audio.bluetoothMode"), true, out IosBluetoothMode bluetooth)) BluetoothMode = bluetooth;
if (Enum.TryParse(defaults.StringForKey("cat.voice.audio.micMode"), true, out IosMicMode mic)) MicMode = mic;
ForceSpeaker = defaults.BoolForKey("cat.voice.audio.forceSpeaker");
// Older voice-chat selections saved an automatic speaker override. Drop that
// preference for the named preset so an existing install can follow HFP again.
if (Preset == IosAudioPreset.VoiceChat) ForceSpeaker = false;
VoiceProcessing = defaults.ValueForKey(new NSString("cat.voice.audio.voiceProcessing")) is null || defaults.BoolForKey("cat.voice.audio.voiceProcessing");
AutomaticGainControl = defaults.ValueForKey(new NSString("cat.voice.audio.agc")) is null || defaults.BoolForKey("cat.voice.audio.agc");
CaptureChannels = defaults.IntForKey("cat.voice.audio.captureChannels") == 2 ? 2 : Preset == IosAudioPreset.StereoMicrophone ? 2 : 1;
SelectedInputId = defaults.StringForKey("cat.voice.audio.inputPortId"); SelectedDataSourceId = defaults.StringForKey("cat.voice.audio.dataSourceId");
if (Preset == IosAudioPreset.VoiceChat) { SelectedInputId = null; SelectedDataSourceId = null; }
if (Enum.TryParse(defaults.StringForKey("cat.voice.audio.polarPattern"), true, out AVAudioDataSourcePolarPattern pattern)) SelectedPolarPattern = pattern;
RefreshRoutes();
}
@@ -73,12 +77,14 @@ internal sealed class IosAudioRouter
// Named presets never carry an Advanced capsule selection across transitions.
// Stereo derives its data source below; mono/voice chat must clear a stale stereo one.
SelectedDataSourceId = null; SelectedPolarPattern = AVAudioDataSourcePolarPattern.Unknown;
if (preset == IosAudioPreset.VoiceChat) ForceSpeaker = true;
// HFP needs to remain free to follow a connected headset. A speaker route can
// still be requested explicitly with the Speaker output control.
if (preset == IosAudioPreset.VoiceChat) ForceSpeaker = false;
}
SaveAndReconfigure();
}
internal void SetForceSpeaker(bool value) { ForceSpeaker = value; SaveAndReconfigure(); }
internal void SetForceSpeaker(bool value) { ForceSpeaker = value; Preset = IosAudioPreset.Advanced; SaveAndReconfigure(); }
internal void SetVoiceProcessing(bool value) { VoiceProcessing = value; SaveAndReconfigure(); }
internal void SetAutomaticGainControl(bool value) { AutomaticGainControl = value; SaveAndReconfigure(); }
internal void SetCaptureChannels(int value) { CaptureChannels = value == 2 ? 2 : 1; Preset = IosAudioPreset.Advanced; SaveAndReconfigure(); }
@@ -100,7 +106,10 @@ internal sealed class IosAudioRouter
AVAudioSession session = AVAudioSession.SharedInstance();
Inputs = session.AvailableInputs?.Select(value => new IosAudioPort(value.UID, value.PortName, value.PortType.ToString())).ToArray() ?? [];
Outputs = session.CurrentRoute.Outputs.Select(value => new IosAudioPort(value.UID, value.PortName, value.PortType.ToString())).ToArray();
SelectedInputId ??= session.PreferredInput?.UID; Changed?.Invoke();
// A route reported by iOS is not an explicit user input choice. Capturing it here
// can pin the built-in mic after a speaker fallback and displace a Bluetooth HFP route
// on the next graph rebuild.
Changed?.Invoke();
}
internal void Apply(bool configureInput)
@@ -121,7 +130,10 @@ internal sealed class IosAudioRouter
Console.Error.WriteLine($"VC_ROUTE preset={Preset} requestedCh={CaptureChannels} sessionCh={session.InputNumberOfChannels} " +
$"preferred={session.PreferredInput?.PortName ?? "default"} dataSource={session.InputDataSource?.DataSourceName ?? "default"} " +
$"pattern={session.InputDataSource?.SelectedPolarPattern.ToString() ?? "default"}");
session.OverrideOutputAudioPort(ForceSpeaker && BluetoothMode != IosBluetoothMode.BuiltInMicA2dp ? AVAudioSessionPortOverride.Speaker : AVAudioSessionPortOverride.None, out _); RefreshRoutes();
// The speaker port override forces both input and output to built-in hardware,
// even with a headset connected. DefaultToSpeaker is an explicit speaker choice
// only; the voice-chat preset leaves it off so HFP can follow the headset.
RefreshRoutes();
ResetWatchdog(); EnsureWatchdog();
}
finally { applying = false; }