davidliu
Committed by GitHub

Add RTC stats for publisher/subscriber peer connection and tracks (#165)

* Add RTC stats for publisher/subscriber peer connection and tracks

* Documentation
@@ -808,6 +808,14 @@ internal constructor( @@ -808,6 +808,14 @@ internal constructor(
808 808
809 client.sendSyncState(syncState) 809 client.sendSyncState(syncState)
810 } 810 }
  811 +
  812 + fun getPublisherRTCStats(callback: RTCStatsCollectorCallback) {
  813 + _publisher?.peerConnection?.getStats(callback) ?: callback.onStatsDelivered(RTCStatsReport(0, emptyMap()))
  814 + }
  815 +
  816 + fun getSubscriberRTCStats(callback: RTCStatsCollectorCallback) {
  817 + _subscriber?.peerConnection?.getStats(callback) ?: callback.onStatsDelivered(RTCStatsReport(0, emptyMap()))
  818 + }
811 } 819 }
812 820
813 /** 821 /**
@@ -27,6 +27,7 @@ import io.livekit.android.util.FlowObservable @@ -27,6 +27,7 @@ import io.livekit.android.util.FlowObservable
27 import io.livekit.android.util.LKLog 27 import io.livekit.android.util.LKLog
28 import io.livekit.android.util.flowDelegate 28 import io.livekit.android.util.flowDelegate
29 import io.livekit.android.util.invoke 29 import io.livekit.android.util.invoke
  30 +import io.livekit.android.webrtc.getFilteredStats
30 import kotlinx.coroutines.* 31 import kotlinx.coroutines.*
31 import livekit.LivekitModels 32 import livekit.LivekitModels
32 import livekit.LivekitRtc 33 import livekit.LivekitRtc
@@ -874,6 +875,22 @@ constructor( @@ -874,6 +875,22 @@ constructor(
874 } 875 }
875 } 876 }
876 877
  878 + /**
  879 + * Get stats for the publisher peer connection.
  880 + *
  881 + * @see getSubscriberRTCStats
  882 + * @see getFilteredStats
  883 + */
  884 + fun getPublisherRTCStats(callback: RTCStatsCollectorCallback) = engine.getPublisherRTCStats(callback)
  885 +
  886 + /**
  887 + * Get stats for the subscriber peer connection.
  888 + *
  889 + * @see getPublisherRTCStats
  890 + * @see getFilteredStats
  891 + */
  892 + fun getSubscriberRTCStats(callback: RTCStatsCollectorCallback) = engine.getSubscriberRTCStats(callback)
  893 +
877 // Debug options 894 // Debug options
878 895
879 /** 896 /**
  1 +package io.livekit.android.webrtc
  2 +
  3 +import io.livekit.android.util.LKLog
  4 +import org.webrtc.MediaStreamTrack
  5 +import org.webrtc.RTCStats
  6 +import org.webrtc.RTCStatsReport
  7 +
  8 +/**
  9 + * Returns an RTCStatsReport with all the relevant information pertaining to a track.
  10 + *
  11 + * @param trackIdentifier track, sender, or receiver id
  12 + */
  13 +fun RTCStatsReport.getFilteredStats(track: MediaStreamTrack): RTCStatsReport {
  14 + return getFilteredStats(track.id())
  15 +}
  16 +
  17 +/**
  18 + * Returns an RTCStatsReport with all the relevant information pertaining to a track identifier.
  19 + *
  20 + * @param trackIdentifier track, sender, or receiver id
  21 + */
  22 +fun RTCStatsReport.getFilteredStats(trackIdentifier: String): RTCStatsReport {
  23 + val rtcStatsReport = this
  24 + val statsMap = rtcStatsReport.statsMap
  25 + val filteredStats = mutableSetOf<RTCStats>()
  26 +
  27 + // Get track stats
  28 + val trackStats = getTrackStats(trackIdentifier, statsMap)
  29 + if (trackStats == null) {
  30 + LKLog.i { "getStats: couldn't find track stats!" }
  31 + return RTCStatsReport(rtcStatsReport.timestampUs.toLong(), HashMap())
  32 + }
  33 + filteredStats.add(trackStats)
  34 + val trackId = trackStats.id
  35 +
  36 + // Get stream stats
  37 + val streamStats = getStreamStats(trackId, statsMap)
  38 + if (streamStats != null) {
  39 + filteredStats.add(streamStats)
  40 + }
  41 +
  42 + // Get streamType stats and associated information
  43 + val ssrcs: MutableSet<Long?> = HashSet()
  44 + val codecIds: MutableSet<String?> = HashSet()
  45 +
  46 + for (stats in statsMap.values) {
  47 + if ((stats.type == "inbound-rtp" || stats.type == "outbound-rtp") && trackId == stats.members["trackId"]) {
  48 + ssrcs.add(stats.members["ssrc"] as Long?)
  49 + codecIds.add(stats.members["codecId"] as String?)
  50 + filteredStats.add(stats)
  51 + }
  52 + }
  53 +
  54 + // Get nominated candidate information
  55 + var candidatePairStats: RTCStats? = null
  56 + for (stats in statsMap.values) {
  57 + if (stats.type == "candidate-pair" && stats.members["nominated"] == true) {
  58 + candidatePairStats = stats
  59 + break
  60 + }
  61 + }
  62 +
  63 + var localCandidateId: String? = null
  64 + var remoteCandidateId: String? = null
  65 + if (candidatePairStats != null) {
  66 + filteredStats.add(candidatePairStats)
  67 + localCandidateId = candidatePairStats.members["localCandidateId"] as String?
  68 + remoteCandidateId = candidatePairStats.members["remoteCandidateId"] as String?
  69 + }
  70 +
  71 + // Sweep for any remaining stats we want.
  72 + filteredStats.addAll(
  73 + getExtraStats(
  74 + trackIdentifier,
  75 + ssrcs,
  76 + codecIds,
  77 + localCandidateId,
  78 + remoteCandidateId,
  79 + statsMap
  80 + )
  81 + )
  82 + val filteredStatsMap: MutableMap<String, RTCStats> = HashMap()
  83 + for (stats in filteredStats) {
  84 + filteredStatsMap[stats.id] = stats
  85 + }
  86 +
  87 + return RTCStatsReport(rtcStatsReport.timestampUs.toLong(), filteredStatsMap)
  88 +}
  89 +
  90 +// Note: trackIdentifier can differ from the internal stats trackId
  91 +// trackIdentifier refers to the sender or receiver id
  92 +private fun getTrackStats(trackIdentifier: String, statsMap: Map<String, RTCStats>): RTCStats? {
  93 + for (stats in statsMap.values) {
  94 + if (stats.type == "track" && trackIdentifier == stats.members["trackIdentifier"]) {
  95 + return stats
  96 + }
  97 + }
  98 + return null
  99 +}
  100 +
  101 +
  102 +private fun getStreamStats(trackId: String, statsMap: Map<String, RTCStats>): RTCStats? {
  103 + for (stats in statsMap.values) {
  104 + if (stats.type == "stream") {
  105 + val trackIds = (stats.members["trackIds"] as? Array<*>)?.toList() ?: emptyList()
  106 + if (trackIds.contains(trackId)) {
  107 + return stats
  108 + }
  109 + }
  110 + }
  111 + return null
  112 +}
  113 +
  114 +// Note: trackIdentifier can differ from the internal stats trackId
  115 +// trackIdentifier refers to the sender or receiver id
  116 +private fun getExtraStats(
  117 + trackIdentifier: String,
  118 + ssrcs: Set<Long?>,
  119 + codecIds: Set<String?>,
  120 + localCandidateId: String?,
  121 + remoteCandidateId: String?,
  122 + statsMap: Map<String, RTCStats>
  123 +): Set<RTCStats> {
  124 + val extraStats: MutableSet<RTCStats> = HashSet()
  125 + for (stats in statsMap.values) {
  126 + when (stats.type) {
  127 + "certificate", "transport" -> extraStats.add(stats)
  128 + }
  129 + if (stats.id == localCandidateId || stats.id == remoteCandidateId) {
  130 + extraStats.add(stats)
  131 + continue
  132 + }
  133 + if (ssrcs.contains(stats.members["ssrc"])) {
  134 + extraStats.add(stats)
  135 + continue
  136 + }
  137 + if (trackIdentifier == stats.members["trackIdentifier"]) {
  138 + extraStats.add(stats)
  139 + continue
  140 + }
  141 + if (codecIds.contains(stats.id)) {
  142 + extraStats.add(stats)
  143 + }
  144 + }
  145 + return extraStats
  146 +}