User report: 'speaker change not work' \u2014 selecting Speaker or
iPhone receiver in the audio output picker had no audible effect.
Root cause:
AVAudioSession was configured with category options
[.defaultToSpeaker, .allowBluetoothHFP, .allowBluetoothA2DP] +
mode .voiceChat. The .defaultToSpeaker flag tells iOS 'this app's
baseline output route is the speakerphone, even though .voiceChat
mode would normally route to the receiver.'
When the user picked Speaker:
overrideOutputAudioPort(.speaker) <- already at speaker baseline; no-op
When the user picked iPhone receiver:
overrideOutputAudioPort(.none) <- removes speaker OVERRIDE,
restores baseline = .defaultToSpeaker
= speakerphone. Receiver
row silently mapped to speaker.
So both rows produced the same audible state. The picker UI changed
the selected radio but the route didn't actually move.
Fix:
1. AppDelegate.swift: drop .defaultToSpeaker from options. With
pure .voiceChat mode (no .defaultToSpeaker), the baseline is
the receiver/earpiece. overrideOutputAudioPort then works as
documented:
Default = receiver
overrideOutputAudioPort(.speaker) -> speakerphone
overrideOutputAudioPort(.none) -> back to receiver
BT/AirPods connected -> automatic
Wired headphones plugged in -> automatic
2. voice_compact.dart: replace 'catch (_) {/* ignore */}' silent
swallow with debugPrint logging of (a) the actual exception
and (b) which route was selected. So if iOS rejects an
override (e.g. wired headphones plugged in), we can see WHY
in the device log instead of a silent picker no-op.
3. _selectSpeaker also now sets preferredInput to the built-in
mic so input + output stay consistent. Previously the
speakerphone override could leave the mic still routed to a
previously-selected BT input \u2014 user hears self through
speaker but server hears nothing.
flutter build ios --release --no-codesign: 21.9 s, Runner.app
30.4 MB.