262 lines
9.6 KiB
Swift
262 lines
9.6 KiB
Swift
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":
|
|
result(linphoneBridge.checkPermissions())
|
|
|
|
case "initialize":
|
|
linphoneBridge = LinphoneBridge(
|
|
remoteViewAcquisitor: { [weak self] in self?.acquireRemoteView() },
|
|
localViewAcquisitor: { [weak self] in self?.acquireLocalView() },
|
|
onRegistrationStateChanged: { [weak self] state in
|
|
self?.registrationEventsChannel?(state)
|
|
},
|
|
onCallStateChanged: { [weak self] state in
|
|
self?.callEventsChannel?(state)
|
|
}
|
|
)
|
|
linphoneBridge.initializeLinphone()
|
|
result(true)
|
|
|
|
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
|
|
}
|
|
linphoneBridge.register(username: username, password: password, serverIp: serverIp, serverPort: serverPort)
|
|
result(true)
|
|
|
|
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 {
|
|
result(FlutterError(code: "ERROR", message: error.localizedDescription, details: nil))
|
|
}
|
|
|
|
case "answerCall":
|
|
result(linphoneBridge.answerCall())
|
|
|
|
case "hangupCall":
|
|
result(linphoneBridge.hangupCall())
|
|
|
|
case "inCall":
|
|
result(linphoneBridge.inCall())
|
|
|
|
case "callType":
|
|
switch linphoneBridge.callType() {
|
|
case .audio: result(0)
|
|
case .video: result(1)
|
|
case .unknown: result(2)
|
|
}
|
|
|
|
case "toggleVideo":
|
|
result(linphoneBridge.toggleVideo())
|
|
|
|
case "toggleMicrophone":
|
|
result(linphoneBridge.toggleMicrophone())
|
|
|
|
case "stop":
|
|
linphoneBridge.stop()
|
|
result(true)
|
|
|
|
case "syncCurrentState":
|
|
linphoneBridge.syncCurrentState()
|
|
result(true)
|
|
|
|
case "sendDtmf":
|
|
guard let args = call.arguments as? [String: Any],
|
|
let tone = args["tone"] as? String else {
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing required arguments", details: nil))
|
|
return
|
|
}
|
|
result(linphoneBridge.sendDtmf(tone: tone))
|
|
|
|
case "stopCallService":
|
|
// Android-only; no-op on iOS
|
|
result(true)
|
|
|
|
case "setMicGain":
|
|
guard let args = call.arguments as? [String: Any],
|
|
let levelStr = args["level"] as? String,
|
|
let level = Float(levelStr) else {
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid 'level'", details: nil))
|
|
return
|
|
}
|
|
linphoneBridge.setMicGain(level: level)
|
|
result(true)
|
|
|
|
case "getMicGain":
|
|
result(String(linphoneBridge.getMicGain()))
|
|
|
|
case "setPlaybackGain":
|
|
guard let args = call.arguments as? [String: Any],
|
|
let levelStr = args["level"] as? String,
|
|
let level = Float(levelStr) else {
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid 'level'", details: nil))
|
|
return
|
|
}
|
|
linphoneBridge.setPlaybackGain(level: level)
|
|
result(true)
|
|
|
|
case "getPlaybackGain":
|
|
result(linphoneBridge.getPlaybackGain())
|
|
|
|
case "setDscp":
|
|
guard let args = call.arguments as? [String: Any] else {
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing arguments", details: nil))
|
|
return
|
|
}
|
|
// All three are optional — only present keys are applied
|
|
let sipDscp = args["sipDscp"] as? Int
|
|
let audioDscp = args["audioDscp"] as? Int
|
|
let videoDscp = args["videoDscp"] as? Int
|
|
linphoneBridge.setDscp(sipDscp: sipDscp, audioDscp: audioDscp, videoDscp: videoDscp)
|
|
result(true)
|
|
|
|
case "getDscp":
|
|
result(linphoneBridge.getDscp())
|
|
|
|
case "getCurrentCallStats":
|
|
result(linphoneBridge.getCurrentCallStats())
|
|
|
|
case "getAvailableAudioCodecs":
|
|
result(linphoneBridge.getAvailableAudioCodecs())
|
|
|
|
case "setAudioCodec":
|
|
guard let args = call.arguments as? [String: Any],
|
|
let mime = args["mime"] as? String,
|
|
let clockRate = args["clockRate"] as? Int else {
|
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing required arguments", details: nil))
|
|
return
|
|
}
|
|
result(linphoneBridge.setAudioCodec(mime: mime, clockRate: clockRate))
|
|
|
|
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
|
|
}
|
|
}
|