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":continueprint("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":continueinbound = stat.inbound_rtp.inboundif inbound.jitter_buffer_emitted_count == 0:continueavg_delay_ms = 1000 * inbound.jitter_buffer_delay / inbound.jitter_buffer_emitted_countprint(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":continueinbound = stat.inbound_rtp.inboundprint(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.