如何在Flutter生成的iOS Swift项目中使用liblinphone 4.x?
刚好我前阵子帮朋友搞定了liblinphone新版本(不是3.x)和Flutter iOS的集成,完全用平台通道实现你要的注册、通话功能和状态监听,给你一套现成的方案,直接就能用:
适配liblinphone新版本的Flutter iOS平台通道实现
一、先搞定Swift端的Linphone核心初始化
我习惯用单例类LinphoneManager来管理Linphone的核心实例,避免重复初始化,还能统一处理所有回调:
import linphonesw class LinphoneManager: NSObject { static let shared = LinphoneManager() private var core: Core? private var coreDelegate: CoreDelegate? private override init() { super.init() setupLinphoneCore() } private func setupLinphoneCore() { let config = Config() // 可选:开启日志调试,方便排查问题 config.setInt(forKey: "debug", inSection: "log", value: 3) do { // 初始化Core,systemContext传nil就行 core = try Core(config: config, systemContext: nil) setupCoreCallbacks() // 启动Core core?.start() } catch { print("Linphone核心初始化失败: \(error.localizedDescription)") } } private func setupCoreCallbacks() { // 用CoreDelegateStub来实现需要的回调,不用写全所有方法 coreDelegate = CoreDelegateStub( // 注册状态变化回调 onRegistrationStateChanged: { [weak self] core, cfg, state, message in let statusData = ["state": state.rawValue, "message": message] self?.sendEventToFlutter(event: "registrationStateChanged", data: statusData) }, // 通话状态变化回调 onCallStateChanged: { [weak self] core, call, state, message in let callData = ["callId": call.callId, "state": state.rawValue, "message": message] self?.sendEventToFlutter(event: "callStateChanged", data: callData) }, // 来电回调 onIncomingReceived: { [weak self] core, call in let incomingData = [ "callId": call.callId, "remoteAddress": call.remoteAddress?.asString() ?? "未知号码" ] self?.sendEventToFlutter(event: "incomingCall", data: incomingData) } ) core?.addDelegate(delegate: coreDelegate!) } // 通用方法:把事件发送给Flutter private func sendEventToFlutter(event: String, data: [String: Any]) { guard let flutterVC = UIApplication.shared.delegate?.window??.rootViewController as? FlutterViewController else { return } let channel = FlutterMethodChannel(name: "com.your.app/linphone", binaryMessenger: flutterVC) channel.invokeMethod(event, arguments: data) } }
二、实现核心功能方法(Swift端)
在LinphoneManager里添加注册、发起通话、接听、挂断的具体实现:
// 注册SIP账号 func registerSIPAccount(username: String, password: String, domain: String) { guard let core = core else { return } do { // 创建认证信息 let authInfo = try AuthInfo( username: username, userid: nil, password: password, ha1: nil, realm: domain, domain: domain ) core.addAuthInfo(info: authInfo) // 创建账号参数 let accountParams = try core.createAccountParams() let identityAddr = try Address(addr: "sip:\(username)@\(domain)") accountParams.setIdentityaddress(newValue: identityAddr) // 设置服务器地址 let serverAddr = try Address(addr: "sip:\(domain)") accountParams.setServeraddress(newValue: serverAddr) accountParams.registerEnabled = true // 添加账号到Core并设为默认 let account = try core.createAccount(params: accountParams) core.addAccount(account: account) core.defaultAccount = account } catch { print("注册失败: \(error.localizedDescription)") } } // 发起通话 func startCall(to address: String) { guard let core = core else { return } do { let remoteAddr = try Address(addr: address) let callParams = try core.createCallParams(call: nil) try core.inviteAddress(address: remoteAddr, params: callParams) } catch { print("发起通话失败: \(error.localizedDescription)") } } // 接听来电 func acceptIncomingCall(callId: String) { guard let core = core else { return } // 通过callId找到对应的通话实例 if let call = core.calls.first(where: { $0.callId == callId }) { do { try call.accept() } catch { print("接听失败: \(error.localizedDescription)") } } } // 挂断通话 func endCurrentCall(callId: String) { guard let core = core else { return } if let call = core.calls.first(where: { $0.callId == callId }) { do { try call.terminate() } catch { print("挂断失败: \(error.localizedDescription)") } } }
三、Flutter端的平台通道配置
在Flutter里创建一个服务类,用来调用Swift方法并监听回调事件:
import 'package:flutter/services.dart'; class LinphoneService { // 通道名称要和Swift端保持一致 static const MethodChannel _linphoneChannel = MethodChannel('com.your.app/linphone'); // 设置事件监听器,处理Swift发来的回调 static void setupEventListeners() { _linphoneChannel.setMethodCallHandler((call) async { switch (call.method) { case 'registrationStateChanged': // 处理注册状态变化,比如更新UI提示用户 final state = call.arguments['state']; final message = call.arguments['message']; print("注册状态更新: $state - $message"); // 这里可以添加你的业务逻辑,比如通知页面更新 break; case 'callStateChanged': // 处理通话状态变化,比如显示通话中、已挂断等状态 final callId = call.arguments['callId']; final state = call.arguments['state']; print("通话[$callId]状态更新: $state"); break; case 'incomingCall': // 处理来电,比如弹出接听/挂断弹窗 final callId = call.arguments['callId']; final remoteAddress = call.arguments['remoteAddress']; print("收到来电: $remoteAddress (CallID: $callId)"); break; default: throw MissingPluginException(); } }); } // 调用注册方法 static Future<void> registerAccount(String username, String password, String domain) async { await _linphoneChannel.invokeMethod('registerSIPAccount', { 'username': username, 'password': password, 'domain': domain, }); } // 发起通话 static Future<void> makeCall(String address) async { await _linphoneChannel.invokeMethod('startCall', {'address': address}); } // 接听通话 static Future<void> acceptCall(String callId) async { await _linphoneChannel.invokeMethod('acceptIncomingCall', {'callId': callId}); } // 挂断通话 static Future<void> endCall(String callId) async { await _linphoneChannel.invokeMethod('endCurrentCall', {'callId': callId}); } }
四、Swift端绑定平台通道方法
最后在AppDelegate里注册平台通道,处理Flutter发来的方法调用:
import UIKit import Flutter import linphonesw @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { guard let flutterVC = window?.rootViewController as? FlutterViewController else { return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // 注册平台通道 let linphoneChannel = FlutterMethodChannel(name: "com.your.app/linphone", binaryMessenger: flutterVC) linphoneChannel.setMethodCallHandler { [weak self] call, result in switch call.method { case "registerSIPAccount": guard let args = call.arguments as? [String: String], let username = args["username"], let password = args["password"], let domain = args["domain"] else { result.error("INVALID_ARGS", "参数格式错误", nil) return } LinphoneManager.shared.registerSIPAccount(username: username, password: password, domain: domain) result.success(nil) case "startCall": guard let args = call.arguments as? [String: String], let address = args["address"] else { result.error("INVALID_ARGS", "缺少通话地址", nil) return } LinphoneManager.shared.startCall(to: address) result.success(nil) case "acceptIncomingCall": guard let args = call.arguments as? [String: String], let callId = args["callId"] else { result.error("INVALID_ARGS", "缺少CallID", nil) return } LinphoneManager.shared.acceptIncomingCall(callId: callId) result.success(nil) case "endCurrentCall": guard let args = call.arguments as? [String: String], let callId = args["callId"] else { result.error("INVALID_ARGS", "缺少CallID", nil) return } LinphoneManager.shared.endCurrentCall(callId: callId) result.success(nil) default: result.notImplemented() } } GeneratedPluginRegistrant.register(with: self) // 初始化Linphone单例 _ = LinphoneManager.shared return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
几个关键注意事项
- 一定要在iOS项目的
Info.plist里添加麦克风权限:NSMicrophoneUsageDescription,否则通话会没有声音,用户也会被拒绝权限 - liblinphone新版本用的是
import linphonesw,和3.x的import Linphone完全不兼容,所以别再用旧版本的代码了 - 测试前确保你的SIP服务器是正常运行的,账号可以正常注册(可以用linphone官方客户端先测一遍)
- 如果遇到Core初始化失败,检查pod集成是否正确,比如
pod install有没有跑完,liblinphone的版本是不是最新的
内容的提问来源于stack exchange,提问作者slance
相关产品推荐
相关产品推荐

