WebRTC getStats: Monitor Connection Health in JS
WebRTC statistics provide detailed, real-time telemetry on the state,
performance, and media quality of peer-to-peer connections. By utilizing
the browser’s built-in
RTCPeerConnection.prototype.getStats() method in
JavaScript, developers can programmatically inspect critical network
metrics such as packet loss, round-trip time (RTT), jitter, and bitrate.
This guide explores the architecture of WebRTC statistics, identifies
the key metrics to track, and demonstrates how to implement automated
connection health monitoring in your web applications.
What Are WebRTC Statistics?
WebRTC statistics represent an extensive collection of metrics gathered at the browser level across various layers of the real-time communication pipeline. These metrics encompass network transport information, ICE (Interactive Connectivity Establishment) candidate negotiation, RTP (Real-time Transport Protocol) audio and video streams, and device-level hardware performance (such as encoding and decoding pipelines).
When queried, the browser returns an RTCStatsReport
object. This report is a map-like collection of individual
RTCStats dictionaries, each categorized by a specific
type (such as inbound-rtp,
outbound-rtp, candidate-pair, or
media-source) and identified by a unique
id.
The getStats() Method
The getStats() API is an asynchronous method available
on the RTCPeerConnection interface. It queries the
underlying WebRTC engine and resolves a Promise containing the
RTCStatsReport.
peerConnection.getStats(selector)
.then(statsReport => {
statsReport.forEach(report => {
// Process individual metric objects
});
})
.catch(error => {
console.error('Error fetching WebRTC stats:', error);
});The optional selector parameter allows you to pass a
specific MediaStreamTrack to filter the output down to
metrics associated with that track. Passing no argument returns the
complete set of statistics for the entire peer connection.
Key Health Metrics to Monitor
To diagnose network degradation and maintain high call quality, focus on the following primary metric types and fields:
1. Network Latency (Round-Trip Time)
- Report Type:
candidate-pair - Key Field:
currentRoundTripTime(seconds) - Description: Represents the time it takes for data
to travel from the local peer to the remote peer and back over the
currently active network path (where
state === 'succeeded'andnominated === true). RTT values above 300ms typically introduce noticeable conversation delays.
2. Packet Loss
- Report Type:
inbound-rtp/outbound-rtp - Key Fields:
packetsLost,packetsReceived,packetsSent - Description: Continuous increases in
packetsLostrelative to total packets indicate network congestion or poor Wi-Fi/cellular signal strength. A packet loss rate exceeding 5% often degrades audio and video intelligibility.
3. Jitter
- Report Type:
inbound-rtp - Key Field:
jitter(seconds) - Description: Measures the variance in packet arrival times. High jitter requires the WebRTC jitter buffer to delay playback to reorder packets, resulting in audio stutter or increased latency.
4. Throughput and Bitrate
- Report Type:
inbound-rtp/outbound-rtp - Key Fields:
bytesReceived,bytesSent,timestamp - Description: Cumulative counters of transmitted data. Calculating the difference (delta) between consecutive samples divided by the elapsed time yields the current bitrate (bits per second).
5. Video Performance
- Report Type:
inbound-rtp - Key Fields:
framesPerSecond,framesDropped,freezeCount,totalFreezesDuration - Description: Tracks rendering smoothness and identifies UI freezes caused by CPU bottlenecks or network stalls.
Implementing Health Monitoring in JavaScript
Because getStats() returns cumulative values (totals
since the connection started), monitoring requires polling at periodic
intervals (e.g., every 1 to 2 seconds) and calculating the delta between
readings.
class WebRTCConnectionMonitor {
constructor(peerConnection, intervalMs = 2000) {
this.pc = peerConnection;
this.intervalMs = intervalMs;
this.timerId = null;
this.previousStats = new Map();
}
start() {
this.timerId = setInterval(() => this.collectMetrics(), this.intervalMs);
}
stop() {
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = null;
}
}
async collectMetrics() {
try {
const statsReport = await this.pc.getStats();
statsReport.forEach(report => {
if (report.type === 'candidate-pair' && report.state === 'succeeded' && report.nominated) {
const rtt = report.currentRoundTripTime ? (report.currentRoundTripTime * 1000).toFixed(2) : 'N/A';
console.log(`[Network] Current RTT: ${rtt} ms`);
}
if (report.type === 'inbound-rtp' && report.kind === 'video') {
const prev = this.previousStats.get(report.id);
if (prev) {
const timeDiff = (report.timestamp - prev.timestamp) / 1000;
const bytesDiff = report.bytesReceived - prev.bytesReceived;
const bitrateKbps = ((bytesDiff * 8) / (timeDiff * 1000)).toFixed(2);
const lostDiff = report.packetsLost - prev.packetsLost;
const totalPacketsDiff = (report.packetsReceived - prev.packetsReceived) + lostDiff;
const lossRate = totalPacketsDiff > 0 ? ((lostDiff / totalPacketsDiff) * 100).toFixed(2) : 0;
console.log(`[Video Inbound] Bitrate: ${bitrateKbps} kbps | Packet Loss: ${lossRate}% | Jitter: ${(report.jitter * 1000).toFixed(2)} ms`);
}
this.previousStats.set(report.id, report);
}
});
} catch (err) {
console.error('Failed to collect WebRTC metrics:', err);
}
}
}
// Usage:
// const monitor = new WebRTCConnectionMonitor(peerConnection);
// monitor.start();Best Practices for Connection Health Monitoring
- Maintain State for Delta Calculations: Never rely on absolute cumulative values for instantaneous metrics like bitrate or packet loss; always calculate rates using time differentials.
- Control Polling Frequency: Poll every 1 to 5 seconds. Polling more frequently than once per second can waste CPU cycles with minimal analytical benefit.
- Correlate Audio and Video: Monitor audio
(
kind === 'audio') and video (kind === 'video') streams separately, as WebRTC applies different forward error correction (FEC) and bandwidth allocations to each. - Send Aggregated Telemetry: Rather than sending raw stats to your analytics server on every interval, aggregate metrics locally and transmit summary statistics (averages, percentiles, and degradation events) periodically or at the end of the session.