import Flutter import UIKit public class LiblinphoneFlutterPlugin: NSObject, FlutterPlugin { private var channel: FlutterMethodChannel! private var registrationEventsChannel: FlutterEventSink? private var callEventsChannel: FlutterEventSink? private var remoteViewCache: [Int: UIView] = [:] private var localViewCache: [Int: UIView] = [:] private var linphoneBridge: LinphoneBridge! private static let TAG = "LiblinphoneFlutterPlugin" public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel( name: "liblinphone_flutter", binaryMessenger: registrar.messenger() ) let instance = LiblinphoneFlutterPlugin() instance.channel = channel registrar.addMethodCallDelegate(instance, channel: channel) // Register platform views let remoteViewFactory = RemoteViewFactory( messenger: registrar.messenger(), cacher: { view in print("[\(TAG)] Caching RemoteView") instance.remoteViewCache[0] = view } ) registrar.register( remoteViewFactory, withId: "liblinphone_flutter.nuark.xyz/remote_view" ) let localViewFactory = LocalViewFactory( messenger: registrar.messenger(), cacher: { view in print("[\(TAG)] Caching LocalView") instance.localViewCache[0] = view } ) registrar.register( localViewFactory, withId: "liblinphone_flutter.nuark.xyz/local_view" ) // Setup event channels let registrationEventChannel = FlutterEventChannel( name: "liblinphone_flutter.nuark.xyz/registration_events", binaryMessenger: registrar.messenger() ) let registrationStreamHandler = EventStreamHandler( onListen: { sink in instance.registrationEventsChannel = sink }, onCancel: { instance.registrationEventsChannel = nil } ) registrationEventChannel.setStreamHandler(registrationStreamHandler) let callEventChannel = FlutterEventChannel( name: "liblinphone_flutter.nuark.xyz/call_events", binaryMessenger: registrar.messenger() ) let callStreamHandler = EventStreamHandler( onListen: { sink in instance.callEventsChannel = sink }, onCancel: { instance.callEventsChannel = nil } ) callEventChannel.setStreamHandler(callStreamHandler) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "checkPermissions": let hasPermissions = linphoneBridge.checkPermissions() result(hasPermissions) case "initialize": do { linphoneBridge = LinphoneBridge( remoteViewAcquisitor: { [weak self] in return self?.acquireRemoteView() }, localViewAcquisitor: { [weak self] in return self?.acquireLocalView() }, onRegistrationStateChanged: { [weak self] state in self?.registrationEventsChannel?(state) }, onCallStateChanged: { [weak self] state in self?.callEventsChannel?(state) } ) linphoneBridge.initializeLinphone() result(true) } catch { print("[\(LiblinphoneFlutterPlugin.TAG)] initialize error: \(error.localizedDescription)") result(FlutterError( code: "ERROR", message: error.localizedDescription, details: nil )) } case "register": guard let args = call.arguments as? [String: Any], let username = args["username"] as? String, let password = args["password"] as? String, let serverIp = args["serverIp"] as? String, let serverPort = args["serverPort"] as? Int else { result(FlutterError( code: "INVALID_ARGUMENTS", message: "Missing required arguments", details: nil )) return } do { linphoneBridge.register( username: username, password: password, serverIp: serverIp, serverPort: serverPort ) result(true) } catch { print("[\(LiblinphoneFlutterPlugin.TAG)] register error: \(error.localizedDescription)") result(FlutterError( code: "ERROR", message: error.localizedDescription, details: nil )) } case "unregister": linphoneBridge.unregister() result(true) case "makeCall": guard let args = call.arguments as? [String: Any], let callTo = args["callTo"] as? String, let isVideoEnabled = args["isVideoEnabled"] as? Bool else { result(FlutterError( code: "INVALID_ARGUMENTS", message: "Missing required arguments", details: nil )) return } do { try linphoneBridge.makeCall(callTo: callTo, isVideoEnabled: isVideoEnabled) result(true) } catch { print("[\(LiblinphoneFlutterPlugin.TAG)] makeCall error: \(error.localizedDescription)") result(FlutterError( code: "ERROR", message: error.localizedDescription, details: nil )) } case "answerCall": let success = linphoneBridge.answerCall() result(success) case "hangupCall": let success = linphoneBridge.hangupCall() result(success) case "inCall": let inCall = linphoneBridge.inCall() result(inCall) case "callType": let callType = linphoneBridge.callType() let ordinal: Int switch callType { case .audio: ordinal = 0 case .video: ordinal = 1 case .unknown: ordinal = 2 } result(ordinal) case "toggleVideo": let enabled = linphoneBridge.toggleVideo() result(enabled) case "toggleMicrophone": let enabled = linphoneBridge.toggleMicrophone() result(enabled) case "stop": linphoneBridge.stop() result(true) default: result(FlutterMethodNotImplemented) } } internal func acquireLocalView() -> UIView? { print("[\(LiblinphoneFlutterPlugin.TAG)] acquireLocalView: \(localViewCache.count)") return localViewCache[0] } internal func acquireRemoteView() -> UIView? { print("[\(LiblinphoneFlutterPlugin.TAG)] acquireRemoteView: \(remoteViewCache.count)") return remoteViewCache[0] } } // MARK: - Event Stream Handler class EventStreamHandler: NSObject, FlutterStreamHandler { private let onListenCallback: (@escaping FlutterEventSink) -> Void private let onCancelCallback: () -> Void init(onListen: @escaping (@escaping FlutterEventSink) -> Void, onCancel: @escaping () -> Void) { self.onListenCallback = onListen self.onCancelCallback = onCancel } func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { onListenCallback(events) return nil } func onCancel(withArguments arguments: Any?) -> FlutterError? { onCancelCallback() return nil } }