feat: (for now android only!) method to get current call stats

This commit is contained in:
Andrew 2026-04-30 15:25:06 +07:00
parent 82c1a3f96c
commit 03361a06c3
6 changed files with 341 additions and 0 deletions

View file

@ -259,6 +259,10 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler
result.success(linphoneBridge.getDscp().toMap()) result.success(linphoneBridge.getDscp().toMap())
} }
"getCurrentCallStats" -> {
result.success(linphoneBridge.getCurrentCallStats().toMap())
}
else -> { else -> {
result.notImplemented() result.notImplemented()
} }

View file

@ -9,6 +9,7 @@ import androidx.core.content.ContextCompat
import org.linphone.core.Account import org.linphone.core.Account
import org.linphone.core.Call import org.linphone.core.Call
import org.linphone.core.Core import org.linphone.core.Core
import org.linphone.core.StreamType
import org.linphone.core.CoreListenerStub import org.linphone.core.CoreListenerStub
import org.linphone.core.Factory import org.linphone.core.Factory
import org.linphone.core.MediaDirection import org.linphone.core.MediaDirection
@ -413,4 +414,131 @@ class LinphoneBridge(
fun getDscp(): DscpValues { fun getDscp(): DscpValues {
return DscpValues(core.sipDscp, core.audioDscp, core.videoDscp) 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<String, Any?> {
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 = call.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.zrtpKeyAgreementAlgoPostQuantum,
)
}
} }

View file

@ -1,6 +1,7 @@
import 'package:flutter/services.dart' show EventChannel; import 'package:flutter/services.dart' show EventChannel;
import 'liblinphone_flutter_platform_interface.dart'; import 'liblinphone_flutter_platform_interface.dart';
import 'models/call_stats.dart';
import 'models/call_type.dart'; import 'models/call_type.dart';
import 'models/dscp_values.dart'; import 'models/dscp_values.dart';
import 'models/registration_state.dart'; import 'models/registration_state.dart';
@ -113,4 +114,7 @@ class LiblinphoneFlutter {
Future<DscpValues> getDscp() async => Future<DscpValues> getDscp() async =>
LiblinphoneFlutterPlatform.instance.getDscp(); LiblinphoneFlutterPlatform.instance.getDscp();
Future<CallStats?> getCurrentCallStats() async =>
LiblinphoneFlutterPlatform.instance.getCurrentCallStats();
} }

View file

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'liblinphone_flutter_platform_interface.dart'; import 'liblinphone_flutter_platform_interface.dart';
import 'models/call_stats.dart';
import 'models/call_type.dart'; import 'models/call_type.dart';
import 'models/dscp_values.dart'; import 'models/dscp_values.dart';
@ -162,4 +163,16 @@ class MethodChannelLiblinphoneFlutter extends LiblinphoneFlutterPlatform {
final values = DscpValues.fromJson(data); final values = DscpValues.fromJson(data);
return values; return values;
} }
@override
Future<CallStats?> getCurrentCallStats() async {
final data = (await methodChannel.invokeMethod<Map<dynamic, dynamic>>(
'getCurrentCallStats',
));
if (data == null) return null;
final values = CallStats.fromJson(data);
return values;
}
} }

View file

@ -2,6 +2,7 @@ import 'package:liblinphone_flutter/models/call_type.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'liblinphone_flutter_method_channel.dart'; import 'liblinphone_flutter_method_channel.dart';
import 'models/call_stats.dart';
import 'models/dscp_values.dart'; import 'models/dscp_values.dart';
abstract class LiblinphoneFlutterPlatform extends PlatformInterface { abstract class LiblinphoneFlutterPlatform extends PlatformInterface {
@ -134,4 +135,8 @@ abstract class LiblinphoneFlutterPlatform extends PlatformInterface {
Future<DscpValues> getDscp() async { Future<DscpValues> getDscp() async {
throw UnimplementedError('getDscp() has not been implemented.'); throw UnimplementedError('getDscp() has not been implemented.');
} }
Future<CallStats?> getCurrentCallStats() async {
throw UnimplementedError('getCurrentCallStats() has not been implemented.');
}
} }

187
lib/models/call_stats.dart Normal file
View file

@ -0,0 +1,187 @@
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<dynamic, dynamic> 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"],
);
}