feat(ui,ios): replace audio_router with audio_session + custom VoIP-style picker
User reported: the 'output device' picker only listed AirPlay
destinations (other iPhones / AirPlay speakers / AppleTV) and not
the speaker / iPhone receiver / AirPods / wired headset choices.
Root cause: audio_router 1.1.1's iOS path uses AVRoutePickerView,
which is Apple's **AirPlay** picker UI \u2014 by design it only lists
AirPlay-eligible output destinations, NOT the input/output route
choices we need (speaker vs receiver vs Bluetooth HFP vs wired).
AVRoutePickerView is the right UI for 'cast audio elsewhere'; for
'pick how I hear / talk' (VoIP) the right primitive is direct
AVAudioSession calls.
Fix: replace audio_router with audio_session 0.2.3 (Ryan Heise,
verified publisher, 865k downloads, MIT). audio_session exposes:
* AVAudioSession.availableInputs \u2014 enumerate every real input
port: builtInMic, bluetoothHfp, bluetoothA2dp, headsetMic
(wired), usbAudio, carAudio, airPlay.
* AVAudioSession.currentRoute \u2014 .inputs + .outputs of the
active route.
* AVAudioSession.setPreferredInput(port) \u2014 switch the input
(HFP / wired / USB / car audio also move output to themselves).
* AVAudioSession.overrideOutputAudioPort(.speaker | .none) \u2014
toggle built-in speakerphone vs receiver/earpiece.
* AVAudioSession.routeChangeStream \u2014 live notifications when
the user plugs / unplugs / connects a device while the picker
is open.
This is exactly the same primitive Discord, WhatsApp, FaceTime
use for their VoIP audio chooser. No native UI plugin needed.
New widgets in voice_compact.dart:
* _AudioOutputTile: shows the active output port name (Speaker /
iPhone / AirPods / 'Phil's Wired Headset' / etc.) with the
matching icon. Subscribes to routeChangeStream for live
updates. Tap opens _AudioOutputPickerSheet.
* _AudioOutputPickerSheet: bottom sheet with 'Choose audio' title
and a Discord-style list:
- Speaker (volume_up)
- iPhone (phone_in_talk; the receiver/earpiece)
- <BT name> (bluetooth_audio)
- <Wired headset> (headset)
- <USB / Car> (usb / directions_car)
Selected row is highlighted + has a check mark. Tap routes:
- Speaker -> overrideOutputAudioPort(.speaker)
- iPhone -> overrideOutputAudioPort(.none) + setPreferredInput(builtInMic)
- External -> overrideOutputAudioPort(.none) + setPreferredInput(port)
* _PickerRow: shared row widget with selected/check styling.
AppDelegate.swift is unchanged: the manual AVAudioSession
.setCategory(playAndRecord / .voiceChat) we already do at launch
(0466000 / 4ee2b38) is fully compatible with audio_session \u2014 the
plugin only adds Dart-side accessors over the same underlying
AVAudioSession singleton.
Removed l10n keys not used anymore (audioRouteUsb was already gone).
Kept audioRouteSpeaker / Receiver / Bluetooth / WiredHeadset /
CarAudio / Airplay / Unknown \u2014 all still used by the new picker.
flutter analyze: 6 pre-existing Radio deprecation infos (unchanged).
flutter build ios --release --no-codesign: 54.9 s, Runner.app
30.4 MB (+200 KB vs audio_router build).
This commit is contained in:
@@ -41,20 +41,28 @@ dependencies:
|
||||
freezed_annotation: ^3.1.0
|
||||
connectivity_plus: ^6.1.0
|
||||
path_provider: ^2.1.4
|
||||
# audio_router 1.1.1 (MIT, supports Android + iOS) drives the
|
||||
# "Audio output" device picker on mobile. iOS displays the native
|
||||
# AVRoutePickerView (AirPlay/Bluetooth/Speaker/Receiver). Android
|
||||
# gets a Material 3 dialog backed by AudioManager.setCommunicationDevice.
|
||||
# No-op on desktop \u2014 we only import the symbols and gate use behind
|
||||
# Platform.isIOS / Platform.isAndroid at the call site.
|
||||
# audio_session 0.2.3 (MIT, Ryan Heise, 865k downloads) gives us
|
||||
# programmatic access to AVAudioSession on iOS + AudioManager on
|
||||
# Android. We use:
|
||||
#
|
||||
# Prerequisite the plugin documents: the audio session must be
|
||||
# configured *before* the picker is shown. We already set
|
||||
# AVAudioSession to .playAndRecord/.voiceChat in AppDelegate.swift
|
||||
# (iOS 0466000), so the plugin's iOS path is satisfied. Android
|
||||
# has no audio session yet \u2014 will be addressed when we wire up
|
||||
# the Android target post-rc.8.
|
||||
audio_router: ^1.1.1
|
||||
# * AVAudioSession.availableInputs \u2014 enumerate real input ports
|
||||
# (Built-in Mic, Bluetooth HFP, wired headset, USB).
|
||||
# * AVAudioSession.currentRoute \u2014 inputs + outputs of the
|
||||
# active route.
|
||||
# * AVAudioSession.setPreferredInput(port) \u2014 switch input
|
||||
# (also drives matching output for HFP devices).
|
||||
# * AVAudioSession.overrideOutputAudioPort(speaker|none) \u2014
|
||||
# toggle the built-in receiver/earpiece vs speakerphone.
|
||||
# * AVAudioSession.routeChangeStream \u2014 live notifications when
|
||||
# the user plugs / unplugs / connects a device.
|
||||
#
|
||||
# This is the right primitive for a VoIP-style 'pick speaker /
|
||||
# receiver / AirPods / wired' picker. Discord, WhatsApp, FaceTime
|
||||
# all do exactly this. We previously tried audio_router 1.1.1
|
||||
# whose iOS path is AVRoutePickerView (the AirPlay button) \u2014 the
|
||||
# wrong UI: only lists AirPlay output destinations, not the
|
||||
# speaker/receiver/Bluetooth choices we actually want.
|
||||
audio_session: ^0.2.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user