From 94cbe1a2c567d52eaa922a5a4c6ab7a46b0825f5 Mon Sep 17 00:00:00 2001 From: Andrew nuark G Date: Thu, 30 Apr 2026 15:25:06 +0700 Subject: [PATCH] feat: (for now android only!) method to get current call stats --- .../LiblinphoneFlutterPlugin.kt | 4 + .../liblinphone_flutter/LinphoneBridge.kt | 128 ++++++++++ lib/liblinphone_flutter.dart | 4 + lib/liblinphone_flutter_method_channel.dart | 13 + ...iblinphone_flutter_platform_interface.dart | 5 + lib/models/call_stats.dart | 228 ++++++++++++++++++ 6 files changed, 382 insertions(+) create mode 100644 lib/models/call_stats.dart 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 9e68b06..24260a3 100644 --- a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt +++ b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt @@ -259,6 +259,10 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler result.success(linphoneBridge.getDscp().toMap()) } + "getCurrentCallStats" -> { + result.success(linphoneBridge.getCurrentCallStats()?.toMap()) + } + 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 f3be837..963afbc 100644 --- a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneBridge.kt +++ b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneBridge.kt @@ -9,6 +9,7 @@ import androidx.core.content.ContextCompat import org.linphone.core.Account import org.linphone.core.Call import org.linphone.core.Core +import org.linphone.core.StreamType import org.linphone.core.CoreListenerStub import org.linphone.core.Factory import org.linphone.core.MediaDirection @@ -413,4 +414,131 @@ class LinphoneBridge( fun getDscp(): DscpValues { return DscpValues(core.sipDscp, core.audioDscp, core.videoDscp) } + + data class CallStats( + val remoteAddress: String, + val downloadBandwidth: Float, + val estimatedDownloadBandwidth: Float, + val fecCumulativeLostPacketsNumber: Int, + val fecDownloadBandwidth: Float, + val fecRepairedPacketsNumber: Int, + val fecUploadBandwidth: Float, + val iceState: String, + val jitterBufferSizeMs: Float, + val latePacketsCumulativeNumber: Int, + val localLateRate: Float, + val localLossRate: Float, + val receiverInterarrivalJitter: Float, + val receiverLossRate: Float, + val roundTripDelay: Float, + val rtcpDownloadBandwidth: Float, + val rtcpUploadBandwidth: Float, + val rtpCumPacketLoss: Int, + val rtpDiscarded: Int, + val rtpHwRecv: Int, + val rtpPacketRecv: Int, + val rtpPacketSent: Int, + val rtpRecv: Int, + val rtpSent: Int, + val senderInterarrivalJitter: Float, + val senderLossRate: Float, + val srtpSource: String, + val srtpSuite: String, + val uploadBandwidth: Float, + val upnpState: String, + val zrtpAuthTagAlgo: String, + val zrtpCipherAlgo: String, + val zrtpHashAlgo: String, + val zrtpKeyAgreementAlgo: String, + val zrtpSasAlgo: String, + val isZrtpKeyAgreementAlgoPostQuantum: Boolean, + ) { + fun toMap(): Map { + return mapOf( + "remoteAddress" to remoteAddress, + "downloadBandwidth" to downloadBandwidth, + "estimatedDownloadBandwidth" to estimatedDownloadBandwidth, + "fecCumulativeLostPacketsNumber" to fecCumulativeLostPacketsNumber, + "fecDownloadBandwidth" to fecDownloadBandwidth, + "fecRepairedPacketsNumber" to fecRepairedPacketsNumber, + "fecUploadBandwidth" to fecUploadBandwidth, + "iceState" to iceState, + "jitterBufferSizeMs" to jitterBufferSizeMs, + "latePacketsCumulativeNumber" to latePacketsCumulativeNumber, + "localLateRate" to localLateRate, + "localLossRate" to localLossRate, + "receiverInterarrivalJitter" to receiverInterarrivalJitter, + "receiverLossRate" to receiverLossRate, + "roundTripDelay" to roundTripDelay, + "rtcpDownloadBandwidth" to rtcpDownloadBandwidth, + "rtcpUploadBandwidth" to rtcpUploadBandwidth, + "rtpCumPacketLoss" to rtpCumPacketLoss, + "rtpDiscarded" to rtpDiscarded, + "rtpHwRecv" to rtpHwRecv, + "rtpPacketRecv" to rtpPacketRecv, + "rtpPacketSent" to rtpPacketSent, + "rtpRecv" to rtpRecv, + "rtpSent" to rtpSent, + "senderInterarrivalJitter" to senderInterarrivalJitter, + "senderLossRate" to senderLossRate, + "srtpSource" to srtpSource, + "srtpSuite" to srtpSuite, + "uploadBandwidth" to uploadBandwidth, + "upnpState" to upnpState, + "zrtpAuthTagAlgo" to zrtpAuthTagAlgo, + "zrtpCipherAlgo" to zrtpCipherAlgo, + "zrtpHashAlgo" to zrtpHashAlgo, + "zrtpKeyAgreementAlgo" to zrtpKeyAgreementAlgo, + "zrtpSasAlgo" to zrtpSasAlgo, + "isZrtpKeyAgreementAlgoPostQuantum" to isZrtpKeyAgreementAlgoPostQuantum, + ); + } + } + + fun getCurrentCallStats(): CallStats? { + val currentCall = core.currentCall + if (currentCall == null) return null + + val stats = currentCall.getStats(StreamType.Audio) + if (stats == null) return null + + return CallStats( + remoteAddress = currentCall.remoteAddress.asString(), + downloadBandwidth = stats.downloadBandwidth, + estimatedDownloadBandwidth = stats.estimatedDownloadBandwidth, + fecCumulativeLostPacketsNumber = stats.fecCumulativeLostPacketsNumber, + fecDownloadBandwidth = stats.fecDownloadBandwidth, + fecRepairedPacketsNumber = stats.fecRepairedPacketsNumber, + fecUploadBandwidth = stats.fecUploadBandwidth, + iceState = stats.iceState.name, + jitterBufferSizeMs = stats.jitterBufferSizeMs, + latePacketsCumulativeNumber = stats.latePacketsCumulativeNumber, + localLateRate = stats.localLateRate, + localLossRate = stats.localLossRate, + receiverInterarrivalJitter = stats.receiverInterarrivalJitter, + receiverLossRate = stats.receiverLossRate, + roundTripDelay = stats.roundTripDelay, + rtcpDownloadBandwidth = stats.rtcpDownloadBandwidth, + rtcpUploadBandwidth = stats.rtcpUploadBandwidth, + rtpCumPacketLoss = stats.rtpCumPacketLoss, + rtpDiscarded = stats.rtpDiscarded, + rtpHwRecv = stats.rtpHwRecv, + rtpPacketRecv = stats.rtpPacketRecv, + rtpPacketSent = stats.rtpPacketSent, + rtpRecv = stats.rtpRecv, + rtpSent = stats.rtpSent, + senderInterarrivalJitter = stats.senderInterarrivalJitter, + senderLossRate = stats.senderLossRate, + srtpSource = stats.srtpSource.name, + srtpSuite = stats.srtpSuite.name, + uploadBandwidth = stats.uploadBandwidth, + upnpState = stats.upnpState.name, + zrtpAuthTagAlgo = stats.zrtpAuthTagAlgo, + zrtpCipherAlgo = stats.zrtpCipherAlgo, + zrtpHashAlgo = stats.zrtpHashAlgo, + zrtpKeyAgreementAlgo = stats.zrtpKeyAgreementAlgo, + zrtpSasAlgo = stats.zrtpSasAlgo, + isZrtpKeyAgreementAlgoPostQuantum = stats.isZrtpKeyAgreementAlgoPostQuantum(), + ) + } } diff --git a/lib/liblinphone_flutter.dart b/lib/liblinphone_flutter.dart index 97cb465..dcb6b6a 100644 --- a/lib/liblinphone_flutter.dart +++ b/lib/liblinphone_flutter.dart @@ -1,6 +1,7 @@ import 'package:flutter/services.dart' show EventChannel; import 'liblinphone_flutter_platform_interface.dart'; +import 'models/call_stats.dart'; import 'models/call_type.dart'; import 'models/dscp_values.dart'; import 'models/registration_state.dart'; @@ -113,4 +114,7 @@ class LiblinphoneFlutter { Future getDscp() async => LiblinphoneFlutterPlatform.instance.getDscp(); + + Future getCurrentCallStats() async => + LiblinphoneFlutterPlatform.instance.getCurrentCallStats(); } diff --git a/lib/liblinphone_flutter_method_channel.dart b/lib/liblinphone_flutter_method_channel.dart index 093bb25..1e3a70a 100644 --- a/lib/liblinphone_flutter_method_channel.dart +++ b/lib/liblinphone_flutter_method_channel.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'liblinphone_flutter_platform_interface.dart'; +import 'models/call_stats.dart'; import 'models/call_type.dart'; import 'models/dscp_values.dart'; @@ -162,4 +163,16 @@ class MethodChannelLiblinphoneFlutter extends LiblinphoneFlutterPlatform { final values = DscpValues.fromJson(data); return values; } + + @override + Future getCurrentCallStats() async { + final data = (await methodChannel.invokeMethod>( + 'getCurrentCallStats', + )); + + if (data == null) return null; + + final values = CallStats.fromJson(data); + return values; + } } diff --git a/lib/liblinphone_flutter_platform_interface.dart b/lib/liblinphone_flutter_platform_interface.dart index b447f94..a23daaa 100644 --- a/lib/liblinphone_flutter_platform_interface.dart +++ b/lib/liblinphone_flutter_platform_interface.dart @@ -2,6 +2,7 @@ import 'package:liblinphone_flutter/models/call_type.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'liblinphone_flutter_method_channel.dart'; +import 'models/call_stats.dart'; import 'models/dscp_values.dart'; abstract class LiblinphoneFlutterPlatform extends PlatformInterface { @@ -134,4 +135,8 @@ abstract class LiblinphoneFlutterPlatform extends PlatformInterface { Future getDscp() async { throw UnimplementedError('getDscp() has not been implemented.'); } + + Future getCurrentCallStats() async { + throw UnimplementedError('getCurrentCallStats() has not been implemented.'); + } } diff --git a/lib/models/call_stats.dart b/lib/models/call_stats.dart new file mode 100644 index 0000000..d8a713d --- /dev/null +++ b/lib/models/call_stats.dart @@ -0,0 +1,228 @@ +class CallStats { + /// Remote adress, duh + final String remoteAddress; + + /// Bandwidth measurement of the received stream, expressed in kbit/s, including IP/UDP/RTP headers + final double downloadBandwidth; + + /// Estimated bandwidth measurement of the received stream, expressed in kbit/s, including IP/UDP/RTP headers + final double estimatedDownloadBandwidth; + + /// If the FEC is enabled, gets the cumulative number of lost source packets of the RTP session that have not been repaired by the current FEC stream + final int fecCumulativeLostPacketsNumber; + + /// Bandwidth measurement of the part of the received stream dedicated to FEC, expressed in kbit/s, including IP/UDP/RTP headers + final double fecDownloadBandwidth; + + /// If the FEC is enabled, gets the cumulative number of source packets of the RTP session that have been repaired by the current FEC stream + final int fecRepairedPacketsNumber; + + /// Bandwidth measurement of the part of the sent stream dedicated to FEC, expressed in kbit/s, including IP/UDP/RTP headers + final double fecUploadBandwidth; + + /// State of ICE processing + final String iceState; + + /// Jitter buffer size in ms + final double jitterBufferSizeMs; + + /// Cumulative number of late packets + final int latePacketsCumulativeNumber; + + /// Local late rate since last report, expressed as a percentage + final double localLateRate; + + /// Local loss rate since last report, expressed as a percentage + final double localLossRate; + + /// Remote reported interarrival jitter, expressed in seconds + final double receiverInterarrivalJitter; + + /// Remote reported loss rate since last report, expressed as a percentage + final double receiverLossRate; + + /// Round trip delay in s + final double roundTripDelay; + + /// Bandwidth measurement of the received RTCP, expressed in kbit/s, including IP/UDP/RTP headers + final double rtcpDownloadBandwidth; + + /// Bandwidth measurement of the sent RTCP, expressed in kbit/s, including IP/UDP/RTP headers + final double rtcpUploadBandwidth; + + /// RTP cumulative number of incoming packet lost + final int rtpCumPacketLoss; + + /// RTP incoming packets discarded because the queue exceeds its max size + final int rtpDiscarded; + + /// Number of received bytes excluding IPv4/IPv6/UDP headers and including late and duplicate packets + final int rtpHwRecv; + + /// Number of RTP received packets + final int rtpPacketRecv; + + /// Number of RTP outgoing packets + final int rtpPacketSent; + + /// RTP incoming recv_bytes of payload and delivered in time to the application + final int rtpRecv; + + /// RTP outgoing sent_bytes (excluding IP header) + final int rtpSent; + + /// Local interarrival jitter, expressed in seconds + final double senderInterarrivalJitter; + + /// Local loss rate since last report, expressed as a percentage + final double senderLossRate; + + /// Method used for SRTP key exchange + final String srtpSource; + + /// SRTP Cryto suite in use + final String srtpSuite; + + /// Bandwidth measurement of the sent stream, expressed in kbit/s, including IP/UDP/RTP headers + final double uploadBandwidth; + + /// State of uPnP processing + final String upnpState; + + /// ZRTP algorithm statistics details (authentication method) + final String zrtpAuthTagAlgo; + + /// ZRTP algorithm statistics details (cipher) + final String zrtpCipherAlgo; + + /// ZRTP algorithm statistics details (hash function) + final String zrtpHashAlgo; + + /// ZRTP algorithm statistics details (key agreeement) + final String zrtpKeyAgreementAlgo; + + /// ZRTP algorithm statistics details (SAS display) + final String zrtpSasAlgo; + + /// Did ZRTP used a Post Quantum algorithm to perform a key exchange + final bool isZrtpKeyAgreementAlgoPostQuantum; + + const CallStats( + this.remoteAddress, + this.downloadBandwidth, + this.estimatedDownloadBandwidth, + this.fecCumulativeLostPacketsNumber, + this.fecDownloadBandwidth, + this.fecRepairedPacketsNumber, + this.fecUploadBandwidth, + this.iceState, + this.jitterBufferSizeMs, + this.latePacketsCumulativeNumber, + this.localLateRate, + this.localLossRate, + this.receiverInterarrivalJitter, + this.receiverLossRate, + this.roundTripDelay, + this.rtcpDownloadBandwidth, + this.rtcpUploadBandwidth, + this.rtpCumPacketLoss, + this.rtpDiscarded, + this.rtpHwRecv, + this.rtpPacketRecv, + this.rtpPacketSent, + this.rtpRecv, + this.rtpSent, + this.senderInterarrivalJitter, + this.senderLossRate, + this.srtpSource, + this.srtpSuite, + this.uploadBandwidth, + this.upnpState, + this.zrtpAuthTagAlgo, + this.zrtpCipherAlgo, + this.zrtpHashAlgo, + this.zrtpKeyAgreementAlgo, + this.zrtpSasAlgo, + this.isZrtpKeyAgreementAlgoPostQuantum, + ); + + factory CallStats.fromJson(Map json) => CallStats( + json["remoteAddress"], + json["downloadBandwidth"], + json["estimatedDownloadBandwidth"], + json["fecCumulativeLostPacketsNumber"], + json["fecDownloadBandwidth"], + json["fecRepairedPacketsNumber"], + json["fecUploadBandwidth"], + json["iceState"], + json["jitterBufferSizeMs"], + json["latePacketsCumulativeNumber"], + json["localLateRate"], + json["localLossRate"], + json["receiverInterarrivalJitter"], + json["receiverLossRate"], + json["roundTripDelay"], + json["rtcpDownloadBandwidth"], + json["rtcpUploadBandwidth"], + json["rtpCumPacketLoss"], + json["rtpDiscarded"], + json["rtpHwRecv"], + json["rtpPacketRecv"], + json["rtpPacketSent"], + json["rtpRecv"], + json["rtpSent"], + json["senderInterarrivalJitter"], + json["senderLossRate"], + json["srtpSource"], + json["srtpSuite"], + json["uploadBandwidth"], + json["upnpState"], + json["zrtpAuthTagAlgo"], + json["zrtpCipherAlgo"], + json["zrtpHashAlgo"], + json["zrtpKeyAgreementAlgo"], + json["zrtpSasAlgo"], + json["isZrtpKeyAgreementAlgoPostQuantum"], + ); + + Map toJson() { + return { + "remoteAddress": remoteAddress, + "downloadBandwidth": downloadBandwidth, + "estimatedDownloadBandwidth": estimatedDownloadBandwidth, + "fecCumulativeLostPacketsNumber": fecCumulativeLostPacketsNumber, + "fecDownloadBandwidth": fecDownloadBandwidth, + "fecRepairedPacketsNumber": fecRepairedPacketsNumber, + "fecUploadBandwidth": fecUploadBandwidth, + "iceState": iceState, + "jitterBufferSizeMs": jitterBufferSizeMs, + "latePacketsCumulativeNumber": latePacketsCumulativeNumber, + "localLateRate": localLateRate, + "localLossRate": localLossRate, + "receiverInterarrivalJitter": receiverInterarrivalJitter, + "receiverLossRate": receiverLossRate, + "roundTripDelay": roundTripDelay, + "rtcpDownloadBandwidth": rtcpDownloadBandwidth, + "rtcpUploadBandwidth": rtcpUploadBandwidth, + "rtpCumPacketLoss": rtpCumPacketLoss, + "rtpDiscarded": rtpDiscarded, + "rtpHwRecv": rtpHwRecv, + "rtpPacketRecv": rtpPacketRecv, + "rtpPacketSent": rtpPacketSent, + "rtpRecv": rtpRecv, + "rtpSent": rtpSent, + "senderInterarrivalJitter": senderInterarrivalJitter, + "senderLossRate": senderLossRate, + "srtpSource": srtpSource, + "srtpSuite": srtpSuite, + "uploadBandwidth": uploadBandwidth, + "upnpState": upnpState, + "zrtpAuthTagAlgo": zrtpAuthTagAlgo, + "zrtpCipherAlgo": zrtpCipherAlgo, + "zrtpHashAlgo": zrtpHashAlgo, + "zrtpKeyAgreementAlgo": zrtpKeyAgreementAlgo, + "zrtpSasAlgo": zrtpSasAlgo, + "isZrtpKeyAgreementAlgoPostQuantum": isZrtpKeyAgreementAlgoPostQuantum, + }; + } +}