import UIKit import Flutter import AVFoundation @main @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // Configure the iOS AVAudioSession for voice chat BEFORE Flutter // starts the audio engine. The category + mode combination tells // iOS to: // * route via the receiver/speaker like a phone call // (`.playAndRecord` + `.voiceChat`) // * engage hardware AEC / NS where the device supports it // * default the speaker output (so the user doesn't have to // hold the phone to their ear) // * permit Bluetooth headsets (so AirPods et al. just work) // // SRS-197 covers the iOS audio routing contract; this is the // matching iOS-side implementation. Failures are logged but do // not block app launch — the audio engine will still come up, // just at the iOS default playback route. do { let session = AVAudioSession.sharedInstance() try session.setCategory( .playAndRecord, mode: .voiceChat, options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP] ) try session.setActive(true, options: []) NSLog("chanora_flutter: AVAudioSession configured (playAndRecord/voiceChat)") } catch { NSLog("chanora_flutter: AVAudioSession setup failed: \(error)") } // Request microphone access on first launch rather than waiting // for the user's first voice-channel join. The latter is // surprising: the user has only tapped "connect to server" and // suddenly iOS pops the permission prompt because joining a // text channel happens to trigger audio engine startup. Asking // up-front matches user expectations for a voice-chat client. // // The request is asynchronous and non-blocking. If the user // denies, voice_join will surface a clearer error later when // the audio engine fails to open the input device. The // permission state is cached by iOS so subsequent launches // skip the prompt. AVAudioSession.sharedInstance().requestRecordPermission { granted in NSLog("chanora_flutter: microphone permission granted=\(granted)") } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) } }