Skip to main content

Transport stats

Read WebRTC transport stats to monitor connection health and diagnose performance issues.

Overview

Access statistics from the underlying WebRTC transport to observe connection health and understand performance issues. Read statistics at the room level for an overall view of the connection, or per media track to inspect a single stream.

Room level statistics

Call the room's stats method to read transport statistics for the whole connection:

let stats = room.get_stats().await?;
stats = await room.get_rtc_stats()
auto stats = room.getStats().get();

Room level stats aren't currently supported for JavaScript, access per-track stats instead.

Per media track statistics

Call a track's stats method to read statistics for a single published or subscribed stream:

let stats = track.get_stats().await?;
stats = await track.get_stats()
auto stats = track->getStats().get();
const report = await track.getRTCStatsReport();

Examples

The following examples read stats from a subscribed video track to monitor common performance signals.

Log packet loss

Log inbound RTP packet loss for a subscribed video track:

let stats = track.get_stats().await?;
for stat in &stats {
let RtcStats::InboundRtp(inbound) = stat else { continue };
println!("Packets lost: {}", inbound.received.packets_lost);
}
stats = await track.get_stats()
for stat in stats:
if stat.WhichOneof("stats") != "inbound_rtp":
continue
print("Packets lost: ", stat.inbound_rtp.received.packets_lost)
std::vector<RtcStats> stats = track->getStats().get();
for (const RtcStats& stat : stats) {
const auto* inbound = std::get_if<RtcInboundRtpStats>(&stat.stats);
if (!inbound) continue;
std::cout << "Packets lost: " << inbound->received.packets_lost << "\n";
}
const stats = await track.getReceiverStats();
console.log('Packets lost: ', stats?.packetsLost);

The count includes all packets lost since the subscription started. To measure one interval, compare two readings. If the count increases, the network drops data between the server and this subscriber.

Measure buffering delay

Measure the average time each video frame waits in the receive buffer before display. This is the delay that playout delay hints and zero jitter buffer mode control, so use it to verify your settings take effect:

let stats = track.get_stats().await?;
for stat in &stats {
let RtcStats::InboundRtp(inbound) = stat else { continue };
if inbound.inbound.jitter_buffer_emitted_count == 0 { continue };
let avg_delay_ms = 1000.0 * inbound.inbound.jitter_buffer_delay
/ inbound.inbound.jitter_buffer_emitted_count as f64;
println!("Average buffering delay: {avg_delay_ms:.1} ms");
}
stats = await track.get_stats()
for stat in stats:
if stat.WhichOneof("stats") != "inbound_rtp":
continue
inbound = stat.inbound_rtp.inbound
if inbound.jitter_buffer_emitted_count == 0:
continue
avg_delay_ms = 1000 * inbound.jitter_buffer_delay / inbound.jitter_buffer_emitted_count
print(f"Average buffering delay: {avg_delay_ms:.1f} ms")
std::vector<RtcStats> stats = track->getStats().get();
for (const RtcStats& stat : stats) {
const auto* inbound = std::get_if<RtcInboundRtpStats>(&stat.stats);
if (!inbound || inbound->inbound.jitter_buffer_emitted_count == 0) continue;
double avg_delay_ms = 1000.0 * inbound->inbound.jitter_buffer_delay /
inbound->inbound.jitter_buffer_emitted_count;
std::cout << "Average buffering delay: " << avg_delay_ms << " ms\n";
}
const report = await track.getRTCStatsReport();
report?.forEach((stat) => {
if (stat.type !== 'inbound-rtp' || !stat.jitterBufferEmittedCount) {
return;
}
const avgDelayMs = (1000 * stat.jitterBufferDelay) / stat.jitterBufferEmittedCount;
console.log(`Average buffering delay: ${avgDelayMs.toFixed(1)} ms`);
});

The delay counter is the total time all frames spent in the buffer, in seconds. Divide it by the emitted frame count to get the average delay per frame since the subscription started. On a good network, expect a value near your minimum playout delay. Both values are cumulative. To measure one interval, subtract the previous reading from each counter before you divide.

Log video freezes

Log how often video rendering stalls. A freeze is a gap in rendering long enough for a viewer to notice, so this stat measures the smoothness cost of a low playout delay:

let stats = track.get_stats().await?;
for stat in &stats {
let RtcStats::InboundRtp(inbound) = stat else { continue };
println!(
"Freezes: {} ({:.1} s total)",
inbound.inbound.freeze_count,
inbound.inbound.total_freeze_duration,
);
}
stats = await track.get_stats()
for stat in stats:
if stat.WhichOneof("stats") != "inbound_rtp":
continue
inbound = stat.inbound_rtp.inbound
print(f"Freezes: {inbound.freeze_count} ({inbound.total_freeze_duration:.1f} s total)")
std::vector<RtcStats> stats = track->getStats().get();
for (const RtcStats& stat : stats) {
const auto* inbound = std::get_if<RtcInboundRtpStats>(&stat.stats);
if (!inbound) continue;
std::cout << "Freezes: " << inbound->inbound.freeze_count << " ("
<< inbound->inbound.total_freeze_duration << " s total)\n";
}
const report = await track.getRTCStatsReport();
report?.forEach((stat) => {
if (stat.type !== 'inbound-rtp') {
return;
}
console.log(`Freezes: ${stat.freezeCount} (${stat.totalFreezesDuration} s total)`);
});

Both values are cumulative since the subscription started. In a low-latency room, a rising freeze count usually means the network needs more buffer than your maximum playout delay allows. If freezes are frequent, consider increasing the maximum playout delay to favor smoother playback over lower latency.

Available stats

The previous examples demonstrate a small subset of the available stats. For a full list, see the W3C WebRTC statistics specification , documented on MDN . Not all stats are available in all SDKs, and field names follow the conventions of each language: jitterBufferDelay in JavaScript is jitter_buffer_delay in the native SDKs.