diff --git a/README.md b/README.md index dd33b52..f7d7705 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,11 @@ await liblinphone.toggleMicrophone(); // Hangup await liblinphone.hangupCall(); +// Send DTMF tone during a call +await liblinphone.sendDtmf('5'); // Send tone '5' +await liblinphone.sendDtmf('#'); // Send tone '#' +await liblinphone.sendDtmf('*'); // Send tone '*' + // Unregister and cleanup await liblinphone.unregister(); await liblinphone.stop(); @@ -185,6 +190,7 @@ Column( | `Future callType()` | Returns the type of the current call (audio/video) | | `Future toggleVideo()` | Toggles video enabled state during a call | | `Future toggleMicrophone()` | Toggles microphone muted state | +| `Future sendDtmf(tone)` | Sends a DTMF tone during a call (0-9, *, #, A-D) | | `Future stop()` | Stops the Linphone core | | `Future syncCurrentState()` | Forces synchronization of current state | diff --git a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt index 9e92d25..73560e1 100644 --- a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt +++ b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt @@ -185,6 +185,17 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler result.success(true) } + "sendDtmf" -> { + try { + val tone = call.argument("tone")!! + val res = linphoneBridge.sendDtmf(tone) + result.success(res) + } catch (e: Exception) { + Log.e(TAG, "sendDtmf: ${e.message}") + result.error("error", e.message, e) + } + } + else -> { result.notImplemented() } diff --git a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneBridge.kt b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneBridge.kt index 4e9583f..24046d8 100644 --- a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneBridge.kt +++ b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneBridge.kt @@ -364,4 +364,18 @@ class LinphoneBridge( } return CallType.Unknown } + + fun sendDtmf(tone: String): Boolean { + if (currentCall == null) { + return false + } + + if (tone.isEmpty()) { + return false + } + + val dtmfChar = tone[0] + currentCall?.sendDtmf(dtmfChar) + return true + } } diff --git a/ios/Classes/LiblinphoneFlutterPlugin.swift b/ios/Classes/LiblinphoneFlutterPlugin.swift index 9f30fd5..401756b 100644 --- a/ios/Classes/LiblinphoneFlutterPlugin.swift +++ b/ios/Classes/LiblinphoneFlutterPlugin.swift @@ -209,7 +209,21 @@ public class LiblinphoneFlutterPlugin: NSObject, FlutterPlugin { 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 + } + let success = linphoneBridge.sendDtmf(tone: tone) + result(success) + default: result(FlutterMethodNotImplemented) } diff --git a/ios/Classes/LinphoneBridge.swift b/ios/Classes/LinphoneBridge.swift index 28ccd7a..07eb4b4 100644 --- a/ios/Classes/LinphoneBridge.swift +++ b/ios/Classes/LinphoneBridge.swift @@ -241,7 +241,7 @@ class LinphoneBridge { guard let params = currentCall?.currentParams else { return .unknown } - + if params.videoEnabled { return .video } else { @@ -249,6 +249,25 @@ class LinphoneBridge { } } + func sendDtmf(tone: String) -> Bool { + guard let call = currentCall else { + return false + } + + guard !tone.isEmpty else { + return false + } + + let dtmfChar = tone.first! + do { + try call.sendDtmf(dtmf: CChar(dtmfChar.asciiValue!)) + return true + } catch { + print("Error sending DTMF: \(error)") + return false + } + } + func syncCurrentState() { onRegistrationStateChanged(registrationState.rawValue) onCallStateChanged(callState.rawValue) diff --git a/lib/liblinphone_flutter.dart b/lib/liblinphone_flutter.dart index b8f47f7..da21d2d 100644 --- a/lib/liblinphone_flutter.dart +++ b/lib/liblinphone_flutter.dart @@ -70,4 +70,7 @@ class LiblinphoneFlutter { Future syncCurrentState() async => LiblinphoneFlutterPlatform.instance.syncCurrentState(); + + Future sendDtmf(String tone) async => + LiblinphoneFlutterPlatform.instance.sendDtmf(tone); } diff --git a/lib/liblinphone_flutter_method_channel.dart b/lib/liblinphone_flutter_method_channel.dart index d9abad1..d6d4044 100644 --- a/lib/liblinphone_flutter_method_channel.dart +++ b/lib/liblinphone_flutter_method_channel.dart @@ -89,4 +89,11 @@ class MethodChannelLiblinphoneFlutter extends LiblinphoneFlutterPlatform { Future syncCurrentState() async { return (await methodChannel.invokeMethod('syncCurrentState'))!; } + + @override + Future sendDtmf(String tone) async { + return (await methodChannel.invokeMethod('sendDtmf', { + 'tone': tone, + }))!; + } } diff --git a/lib/liblinphone_flutter_platform_interface.dart b/lib/liblinphone_flutter_platform_interface.dart index d339afc..9c41f91 100644 --- a/lib/liblinphone_flutter_platform_interface.dart +++ b/lib/liblinphone_flutter_platform_interface.dart @@ -81,4 +81,8 @@ abstract class LiblinphoneFlutterPlatform extends PlatformInterface { Future syncCurrentState() { throw UnimplementedError('syncCurrentState() has not been implemented.'); } + + Future sendDtmf(String tone) { + throw UnimplementedError('sendDtmf() has not been implemented.'); + } } diff --git a/test/liblinphone_flutter_test.dart b/test/liblinphone_flutter_test.dart index 43fda77..15c5fa6 100644 --- a/test/liblinphone_flutter_test.dart +++ b/test/liblinphone_flutter_test.dart @@ -77,6 +77,11 @@ class MockLiblinphoneFlutterPlatform Future unregister() { throw UnimplementedError(); } + + @override + Future sendDtmf(String tone) { + throw UnimplementedError(); + } } void main() {