// AudioRoutingPlugin.swift // Force la sortie audio sur les lunettes Ray-Ban Meta via AVAudioSession. // Pattern extrait de OpenGlasses (straff2002) WakeWordService.swift + GeminiLiveAudioManager.swift. import Flutter import AVFoundation @objc class AudioRoutingPlugin: NSObject, FlutterPlugin { static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel( name: "be.unov.mymuseum/audio_routing", binaryMessenger: registrar.messenger() ) let instance = AudioRoutingPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "enableBluetoothOutput": enableBluetoothOutput() result(nil) case "restoreDefaultOutput": restoreDefaultOutput() result(nil) default: result(FlutterMethodNotImplemented) } } private func enableBluetoothOutput() { do { let session = AVAudioSession.sharedInstance() // Mode voiceChat + A2DP + HFP : pattern validé dans OpenGlasses // pour jouer audio sur lunettes tout en gardant le micro disponible try session.setCategory( .playAndRecord, mode: .voiceChat, options: [.allowBluetoothHFP, .allowBluetoothA2DP, .defaultToSpeaker] ) try session.setActive(true, options: .notifyOthersOnDeactivation) } catch { print("[AudioRoutingPlugin] enableBluetoothOutput error: \(error)") } } private func restoreDefaultOutput() { do { let session = AVAudioSession.sharedInstance() try session.setCategory(.playback, options: []) try session.setActive(true) } catch { print("[AudioRoutingPlugin] restoreDefaultOutput error: \(error)") } } }