import Foundation import Capacitor import AVFoundation // Earpiece <-> speaker toggle for calls. Registered by cap sync as a normal Capacitor plugin package, so it // appears as window.Capacitor.Plugins.AudioRoute (unlike an app-embedded class, which gets stripped/not // discovered in release builds). setSpeaker({on}) overrides the AVAudioSession output port. @objc(AudioRoutePlugin) public class AudioRoutePlugin: CAPPlugin, CAPBridgedPlugin { public let identifier = "AudioRoutePlugin" public let jsName = "AudioRoute" public let pluginMethods: [CAPPluginMethod] = [ CAPPluginMethod(name: "setSpeaker", returnType: CAPPluginReturnPromise) ] override public func load() { // Default calls to the loudspeaker when the plugin initialises (iOS uses the quiet earpiece otherwise). let session = AVAudioSession.sharedInstance() try? session.setCategory(.playAndRecord, mode: .voiceChat, options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP]) try? session.setActive(true) } @objc func setSpeaker(_ call: CAPPluginCall) { let on = call.getBool("on") ?? true let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .allowBluetoothA2DP]) try session.setActive(true) try session.overrideOutputAudioPort(on ? .speaker : .none) call.resolve(["route": on ? "speaker" : "earpiece"]) } catch { call.reject(error.localizedDescription) } } }