Connection Statistics
The stats APIs make it easy to get information about your ongoing connections and other WebRTC internals. They closely follow the WebRTC Statistics API RFC. Note that the underlying WebRTC statistics API is not supported consistently from browser to browser. In all cases where our statistics API is wrapping that provided by the browser, if the browser does not implement the given API, then the API returns a null stats object. This is the expected behaviour and the app code should be checking for null stats objects.
Note
About the code examples on this page:
- For .NET MAUI and Unity, use the C# code.
- For macOS, use the iOS code.
getStats
You can call the getStats
function on any active Connection
. For example, one way to do so would be to wire up an event to call getStats
once the Connection
state transitions to Connected
, and then unwire the event when the Connection state transitions to Closed
(or Failed
). Your event could be triggered by some UI element (like a button click), or fired on a Timer, whatever meets your use case.
connection.GetStats().Then((stats) => {
var transport = stats.Streams[0].Transport;
if (transport != null)
{
var localCandidates = transport.LocalCandidates;
var remoteCandidates = transport.RemoteCandidates;
var activeCandidatePair = transport.ActiveCandidatePair;
var activeLocalCandidateId = activeCandidatePair.LocalCandidateId;
var activeRemoteCandidateId = activeCandidatePair.RemoteCandidateId;
for (var i = 0; i < localCandidates.Length; i++)
{
var localCandidate = localCandidates[i];
if (localCandidate.Id == activeLocalCandidateId)
{
// this is the active local candidate
// check the protocol - UDP or TCP
var localCandidateProtocol = localCandidate.Protocol;
if (localCandidate.IsRelayed)
{
// check the relay server IP
var relayServerIPAddress = localCandidate.IPAddress;
}
}
}
for (var i = 0; i < remoteCandidates.Length; i++)
{
var remoteCandidate = remoteCandidates[i];
if (remoteCandidate.Id == activeRemoteCandidateId)
{
// this is the active remote candidate
if (remoteCandidate.IsRelayed)
{
// check the relay server IP
var relayServerIPAddress = remoteCandidate.IPAddress;
}
}
}
}
});
connection.getStats().then((stats) -> {
TransportStats transport = stats.getStreams()[0].getTransport();
CandidateStats[] localCandidates = transport.getLocalCandidates();
CandidateStats[] remoteCandidates = transport.getRemoteCandidates();
CandidatePairStats activeCandidatePair = transport.getActiveCandidatePair();
String activeLocalCandidateId = activeCandidatePair.getLocalCandidateId();
String activeRemoteCandidateId = activeCandidatePair.getRemoteCandidateId();
for (int i = 0; i < localCandidates.length; i++) {
CandidateStats localCandidate = localCandidates[i];
if (localCandidate.getId() == activeLocalCandidateId) {
// this is the active local candidate
if (localCandidate.getIsRelayed()) {
// check the relay server IP
String relayServerIPAddress = localCandidate.getIPAddress();
}
}
}
for (int i = 0; i < remoteCandidates.length; i++) {
CandidateStats remoteCandidate = remoteCandidates[i];
if (remoteCandidate.getId() == activeRemoteCandidateId) {
// this is the active remote candidate
if (remoteCandidate.getIsRelayed()) {
// check the relay server IP
String relayServerIPAddress = remoteCandidate.getIPAddress();
}
}
}
})
connection!.getStats().then(resolveActionBlock: { (stats) in
let statistics = stats as! FMLiveSwitchConnectionStats?
let stream = statistics?.streams()[0] as! FMLiveSwitchStreamStats?
let transport = stream?.transport()
if (transport != nil)
{
let localCandidates = transport?.localCandidates()
let remoteCandidates = transport?.remoteCandidates()
let activeCandidatePair = transport?.activeCandidatePair()
let activeLocalCandidateId = activeCandidatePair?.localCandidateId()
let activeRemoteCandidateId = activeCandidatePair?.remoteCandidateId()
for i in stride(from: 0, to: (localCandidates?.count)!-1, by: 1)
{
let localCandidate = localCandidates?[i] as! FMLiveSwitchCandidateStats?
if (localCandidate?.id() == activeLocalCandidateId)
{
// this is the active local candidate
// check the protocol
let localCandidateProtocol = localCandidate?.protocol()
if (localCandidate?.isRelayed())!
{
// check the relay server IP
let relayServerIPAddress = localCandidate?.ipAddress()
}
}
}
for i in stride(from: 0, to: (remoteCandidates?.count)!-1, by: 1)
{
let remoteCandidate = remoteCandidates?[i] as! FMLiveSwitchCandidateStats?
if (remoteCandidate?.id() == activeRemoteCandidateId)
{
// this is the active remote candidate
if (remoteCandidate?.isRelayed())!
{
// check the relay server IP
let relayServerIPAddress = remoteCandidate?.ipAddress()
}
}
}
}
});
connection.getStats().then(function(stats) {
var transport = stats.getStreams()[0].getTransport();
if (transport) {
var localCandidates = transport.getLocalCandidates();
var remoteCandidates = transport.getRemoteCandidates();
var activeCandidatePair = transport.getActiveCandidatePair();
var activeLocalCandidateId = activeCandidatePair.getLocalCandidateId();
var activeRemoteCandidateId = activeCandidatePair.getRemoteCandidateId();
for (var i = 0; i < localCandidates.length; i++) {
var localCandidate = localCandidates[i];
if (localCandidate.getId() == activeLocalCandidateId) {
// this is the active local candidate
// check the protocol - UDP or TCP
var localCandidateProtocol = localCandidate.getProtocol();
if (localCandidate.getIsRelayed()) {
// check the relay server IP
var relayServerIPAddress = localCandidate.getIPAddress();
}
}
}
for (var i = 0; i < remoteCandidates.length; i++) {
var remoteCandidate = remoteCandidates[i];
if (remoteCandidate.getId() == activeRemoteCandidateId) {
// this is the active remote candidate
if (remoteCandidate.getIsRelayed()) {
// check the relay server IP
var relayServerIPAddress = remoteCandidate.getIPAddress();
}
}
}
}
});
OnStats
The OnStats
event is raised when the current connection's statistics are gathered.
connection.OnStats += stats =>
{
// stats is a snapshot of the current connection stats
};
connection.addOnStats((fm.liveswitch.ConnectionStats stats) -> {
// stats is a snapshot of the current connection stats
});
connection.addOnStats { stats in
// stats is a snapshot of the current connection stats
}
connection.addOnStats((stats) => {
// stats is a snapshot of the current connection stats
});
OnNetworkQuality
The OnNetworkQuality
event is raised when the connection's network quality is estimated. OnNetworkQuality
returns a value that ranges from 0.0 to 1.0, where 0.0 is the lowest quality and 1.0 is the highest quality.
connection.OnNetworkQuality += networkQuality =>
{
// networkQuality is value ranging from 0.0 (lowest quality) to 1.0 (highest quality)
};
connection.addOnNetworkQuality((double networkQuality) -> {
// networkQuality is value ranging from 0.0 (lowest quality) to 1.0 (highest quality)
});
connection.addOnNetworkQuality { networkQuality in
// networkQuality is value ranging from 0.0 (lowest quality) to 1.0 (highest quality)
}
connection.addOnNetworkQuality((networkQuality) => {
// networkQuality is value ranging from 0.0 (lowest quality) to 1.0 (highest quality)
});
The OnMediaQuality
event is raised when the connection's media quality is estimated. OnMediaQuality
returns a value that ranges from 0.0 to 1.0, where 0.0 is the lowest quality and 1.0 is the highest quality.
The OnMediaQuality
and OnNetworkQuality
events are intentionally independent of each other. SFU downstream connections need to be able to distinguish between a poor network connection to the Media Server (a problem with me) and a poor media feed (a problem with you).
connection.OnMediaQuality += mediaQuality =>
{
// mediaQuality is value ranging from 0.0 (lowest quality) to 1.0 (highest quality)
};
connection.addOnMediaQuality((double mediaQuality) -> {
// mediaQuality is value ranging from 0.0 (lowest quality) to 1.0 (highest quality)
});
connection.addOnMediaQuality { mediaQuality in
// mediaQuality is value ranging from 0.0 (lowest quality) to 1.0 (highest quality)
}
connection.OnMediaQuality += mediaQuality =>
{
// mediaQuality is value ranging from 0.0 (lowest quality) to 1.0 (highest quality)
};
Set Statistic Events Frequency
Use StatsEventInterval
to set the interval, in milliseconds, in which statistics events like OnStats
, OnNetworkQuality
, and onMediaQuality
are raised.
connection.StatsEventInterval = 5000; // once every 5 seconds
connection.setStatsEventInterval(5000); // once every 5 seconds
connection.setStatsEventInterval(5000) // once every 5 seconds
connection.setStatsEventInterval(5000); // once every 5 seconds