Revert "diag(ios,audio): log AVAudioSession state + route changes around picker overrides"

This reverts commit da631a2bef.
This commit is contained in:
EdisonJwa
2026-05-17 00:17:10 +08:00
parent da631a2bef
commit 2f6cdd3ad4
2 changed files with 28 additions and 113 deletions
@@ -766,78 +766,65 @@ class _AudioOutputPickerSheetState extends State<_AudioOutputPickerSheet> {
}
}
/// Diagnostic helper: log the current route's first input and
/// output ports with a tag so we can correlate user actions with
/// what iOS thinks the route is. Called immediately before an
/// override, immediately after, and again 250 ms later to detect
/// silent revert.
Future<void> _logRoute(String tag) async {
try {
final route = await AVAudioSession().currentRoute;
final outs = route.outputs
.map((o) => '${o.portType.name}/${o.portName}')
.join(',');
final ins = route.inputs
.map((i) => '${i.portType.name}/${i.portName}')
.join(',');
debugPrint('chanora.route[$tag] out=[$outs] in=[$ins]');
} catch (e) {
debugPrint('chanora.route[$tag] FAILED: $e');
}
}
Future<void> _selectSpeaker() async {
await _logRoute('speaker.before');
try {
// Apple-documented quirk: in .voiceChat mode, calling
// setPreferredInput(builtInMic) AFTER overrideOutputAudioPort(.speaker)
// causes iOS to recalculate the route. Built-in mic naturally
// pairs with the receiver (not the speaker), so the system
// SILENTLY REVERTS the speaker override and routes audio
// back through the earpiece. Net: the await chain returns
// successfully ('no exception'), but the user hears no
// change.
//
// Fix: do NOT call setPreferredInput when forcing speaker.
// The speaker override is sufficient on its own \u2014 input
// remains on whatever the system was already using (built-in
// mic by default, or BT/wired if connected and selected
// elsewhere). For consistency, only switch input when the
// user explicitly picks a non-speaker input row.
await AVAudioSession()
.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker);
debugPrint('chanora: override(.speaker) returned OK');
debugPrint('chanora: audio output -> speakerphone (override applied)');
} catch (e, st) {
debugPrint('chanora: _selectSpeaker FAILED: $e\n$st');
}
await _logRoute('speaker.after');
// Schedule a delayed re-check — if cpal's RemoteIO unit reverts
// the route on routeChangeNotification, this will surface the
// revert (out=builtInReceiver instead of builtInSpeaker).
Future.delayed(const Duration(milliseconds: 250), () {
_logRoute('speaker.after+250ms');
});
if (!mounted) return;
Navigator.of(context).pop();
}
Future<void> _selectReceiver() async {
await _logRoute('receiver.before');
try {
// Same quirk in reverse: removing the speaker override
// (.none) is enough to restore the .voiceChat default route,
// which is the built-in receiver. Calling setPreferredInput
// explicitly here is redundant and risks the same recalc
// race that broke _selectSpeaker before.
await AVAudioSession()
.overrideOutputAudioPort(AVAudioSessionPortOverride.none);
debugPrint('chanora: override(.none) returned OK');
debugPrint('chanora: audio output -> receiver (override cleared)');
} catch (e, st) {
debugPrint('chanora: _selectReceiver FAILED: $e\n$st');
}
await _logRoute('receiver.after');
Future.delayed(const Duration(milliseconds: 250), () {
_logRoute('receiver.after+250ms');
});
if (!mounted) return;
Navigator.of(context).pop();
}
Future<void> _selectInput(AVAudioSessionPortDescription port) async {
await _logRoute('input.${port.portType.name}.before');
try {
// Drop any speakerphone override so the route follows the
// selected input. BT / wired headset / USB inputs pair their
// OWN output (the user hears audio through the same device
// they speak into), so .none + setPreferredInput is the
// correct combo here.
await AVAudioSession()
.overrideOutputAudioPort(AVAudioSessionPortOverride.none);
await AVAudioSession().setPreferredInput(port);
debugPrint(
'chanora: setPreferredInput(${port.portName}/${port.portType.name}) OK');
'chanora: audio output -> ${port.portName} (${port.portType})');
} catch (e, st) {
debugPrint('chanora: _selectInput FAILED: $e\n$st');
}
await _logRoute('input.${port.portType.name}.after');
Future.delayed(const Duration(milliseconds: 250), () {
_logRoute('input.${port.portType.name}.after+250ms');
});
if (!mounted) return;
Navigator.of(context).pop();
}