feat: add sendDtmf method to send DTMF tones during a call

This commit is contained in:
Andrew 2026-03-30 09:28:40 +07:00
parent b95baa6799
commit f64284f963
9 changed files with 84 additions and 1 deletions

View file

@ -136,6 +136,11 @@ await liblinphone.toggleMicrophone();
// Hangup // Hangup
await liblinphone.hangupCall(); 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 // Unregister and cleanup
await liblinphone.unregister(); await liblinphone.unregister();
await liblinphone.stop(); await liblinphone.stop();
@ -185,6 +190,7 @@ Column(
| `Future<CallType> callType()` | Returns the type of the current call (audio/video) | | `Future<CallType> callType()` | Returns the type of the current call (audio/video) |
| `Future<bool> toggleVideo()` | Toggles video enabled state during a call | | `Future<bool> toggleVideo()` | Toggles video enabled state during a call |
| `Future<bool> toggleMicrophone()` | Toggles microphone muted state | | `Future<bool> toggleMicrophone()` | Toggles microphone muted state |
| `Future<bool> sendDtmf(tone)` | Sends a DTMF tone during a call (0-9, *, #, A-D) |
| `Future<bool> stop()` | Stops the Linphone core | | `Future<bool> stop()` | Stops the Linphone core |
| `Future<void> syncCurrentState()` | Forces synchronization of current state | | `Future<void> syncCurrentState()` | Forces synchronization of current state |

View file

@ -185,6 +185,17 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler
result.success(true) result.success(true)
} }
"sendDtmf" -> {
try {
val tone = call.argument<String>("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 -> { else -> {
result.notImplemented() result.notImplemented()
} }

View file

@ -364,4 +364,18 @@ class LinphoneBridge(
} }
return CallType.Unknown 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
}
} }

View file

@ -210,6 +210,20 @@ public class LiblinphoneFlutterPlugin: NSObject, FlutterPlugin {
linphoneBridge.syncCurrentState() linphoneBridge.syncCurrentState()
result(true) 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: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }

View file

@ -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() { func syncCurrentState() {
onRegistrationStateChanged(registrationState.rawValue) onRegistrationStateChanged(registrationState.rawValue)
onCallStateChanged(callState.rawValue) onCallStateChanged(callState.rawValue)

View file

@ -70,4 +70,7 @@ class LiblinphoneFlutter {
Future<void> syncCurrentState() async => Future<void> syncCurrentState() async =>
LiblinphoneFlutterPlatform.instance.syncCurrentState(); LiblinphoneFlutterPlatform.instance.syncCurrentState();
Future<bool> sendDtmf(String tone) async =>
LiblinphoneFlutterPlatform.instance.sendDtmf(tone);
} }

View file

@ -89,4 +89,11 @@ class MethodChannelLiblinphoneFlutter extends LiblinphoneFlutterPlatform {
Future<bool> syncCurrentState() async { Future<bool> syncCurrentState() async {
return (await methodChannel.invokeMethod<bool>('syncCurrentState'))!; return (await methodChannel.invokeMethod<bool>('syncCurrentState'))!;
} }
@override
Future<bool> sendDtmf(String tone) async {
return (await methodChannel.invokeMethod<bool>('sendDtmf', <String, dynamic>{
'tone': tone,
}))!;
}
} }

View file

@ -81,4 +81,8 @@ abstract class LiblinphoneFlutterPlatform extends PlatformInterface {
Future<bool> syncCurrentState() { Future<bool> syncCurrentState() {
throw UnimplementedError('syncCurrentState() has not been implemented.'); throw UnimplementedError('syncCurrentState() has not been implemented.');
} }
Future<bool> sendDtmf(String tone) {
throw UnimplementedError('sendDtmf() has not been implemented.');
}
} }

View file

@ -77,6 +77,11 @@ class MockLiblinphoneFlutterPlatform
Future<bool> unregister() { Future<bool> unregister() {
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<bool> sendDtmf(String tone) {
throw UnimplementedError();
}
} }
void main() { void main() {