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:
EdisonJwa
2026-05-16 20:53:00 +08:00
parent 0c1fd1c2e3
commit f0016155aa
4 changed files with 345 additions and 72 deletions
+12 -4
View File
@@ -33,14 +33,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.1"
audio_router:
audio_session:
dependency: "direct main"
description:
name: audio_router
sha256: "4dd8b65870f915ef1e7a08ca97898b9ac12c44d3e236ac272bb8f1f68065f48f"
name: audio_session
sha256: "7217b229db57cc4dc577a8abb56b7429a5a212b978517a5be578704bfe5e568b"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "0.2.3"
boolean_selector:
dependency: transitive
description:
@@ -581,6 +581,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shelf:
dependency: transitive
description: