WebRTC: Legacy getStats() migration guide

Henrik Boström
Henrik Boström

The legacy getStats() WebRTC API will be removed in Chrome 117, therefore apps using it will need to migrate to the standard API. This article explains how to migrate your code, and what to do if you need more time to make this change.

Historically there have been two competing versions of the WebRTC getStats() API. The legacy getStats() API, that pre-dates the standardization process and takes a callback argument, and the standardized and widely supported API which returns a promise.

The standard API is more feature rich and has well-defined metrics publicly documented in the W3C specification Identifiers for WebRTC's Statistics API. The specification includes descriptions of each metric listed in this guide and many more.

From Chrome 117 the legacy getStats() API will throw an exception in the Stable release channel (the exception throwing will be gradually rolled out). Follow this guide to ease your transition to the standard API.

Legacy versus standard stats types

The complete list of standard stats types can be found by looking at the RTCStatsType enum in the specification. This includes which stats dictionary definition describes the metrics collected for each type.

The stats objects all have an id attribute which uniquely identifies the underlying object across multiple getStats() calls. The same object will have the same ID every time the method is called. This is useful for calculating the rate of change of metrics (there's an example in the next section). The IDs also form relationships of references. For example the outbound-rtp stats object references the associated media-source stats object via the outbound-rtp.mediaSourceId attribute. If you draw all ...Id relationships you get a graph.

The legacy API has the following stats types, corresponding to the standard types as follows:

Legacy type

Standard type
Represents an RTP stream and metrics about the associated MediaStreamTrack.

The standard types for this are inbound-rtp (for receive RTP streams and its associated remote MediaStreamTrack), outbound-rtp (for send RTP streams) and media-source (for local MediaStreamTrack metrics associated with a send RTP stream). RTP stream metrics also contain information about the encoder or decoder used by the RTP stream.
Bandwidth estimation metrics, target bitrate, encoder bitrate and actual bitrate. These types of metrics are part of the RTP metrics (outbound-rtp and inbound-rtp) and ICE candidate pair metrics (candidate-pair).
Represents the transport (ICE and DTLS). The standard version is transport.
localcandidate and remotecandidate
Represents an ICE candidate. The standard version is local-candidate and remote-candidate.
Represents an ICE candidate pair, which is a pairing of a local and a remote candidate. The standard version is candidate-pair.
Represents a certificate used by the DTLS transport. The standard version is certificate.
Represents the RTCPeerConnection. While its contents does not map to anything in the standard, the standard does have a type associated with the RTCPeerConnection: peer-connection.

Missing from legacy API

These stats types have been added to the standard API that don't have any corresponding legacy type:
  • codec: A codec that is currently being used by an RTP stream, either for encoding or decoding. This is a subset of the codecs that have been negotiated in the SDP.
  • remote-inbound-rtp: A remote endpoint's inbound RTP stream corresponding to an outbound RTP stream that this endpoint is sending (outbound-rtp). It is measured at the remote endpoint and reported in an RTCP Receiver Report (RR) or RTCP Extended Report (XR).
  • remote-outbound-rtp: A remote endpoint's outbound RTP stream corresponding to an inbound RTP stream that this endpoint is receiving (inbound-rtp). It is measured at the remote endpoint and reported in an RTCP Sender Report (SR).
  • media-playout: Metrics about the playout of a remote MediaStreamTrack associated with an inbound RTP stream (inbound-rtp).
  • data-channel: Represents an RTCDataChannel.

Legacy to standard metrics mapping

This mapping is aimed to help developers find which legacy metric corresponds to which standard metric, but note that the corresponding metric may use different units or be expressed as a total counter rather than an instantaneous value. Refer to the specification for metric definitions.
The standard API prefers exposing total counters rather than rates. This means that to get the corresponding rate (for example, bitrate) as in the legacy API, the app must calculate the average rate by taking the delta between two getStats() calls. For example:

// Periodically (e.g. every second or every 10 seconds)...
const currReport = await pc.getStats();
// Calculate bitrate since the last getStats() call.
// Handling of undefined is omitted for clarity.
const currOutboundRtp = currReport.values().find(s => s.type == 'outbound-rtp');
const prevOutboundRtp = prevReport.get(currOutboundRtp.id);
const deltaBits = (currOutboundRtp.bytesSent - prevOutboundRtp.bytesSent) * 8;
const deltaSeconds = (currOutboundRtp.timestamp - prevOutboundRtp.timestamp) / 1000;
logBitrateMeasurement(deltaBits / deltaSeconds);
// Remember the report for next time.
prevReport = currReport;

Having to calculate rates and averages yourself like this may seem like a cumbersome additional step but it has the upside of allowing you to get averages over any desired time interval. Calling the standard API less often than you may otherwise have had to do with the legacy API has some performance benefits.

Legacy metric
Standard correspondence
.googFingerprint .fingerprint
.googFingerprintAlgorithm .fingerprintAlgorithm
.googDerBase64 .base64Certificate
Legacy metric
Standard correspondence
.localCertificateId .localCertificateId
.remoteCertificateId .remoteCertificateId
.selectedCandidatePairId .selectedCandidatePairId
.dtlsCipher .dtlsCipher
.srtpCipher .srtpCipher
Legacy metric
Standard correspondence
local-candidate or candidate-pair
.stunKeepaliveRequestsSent candidate-pair.requestsSent (reverse lookup candidate-pair via candidate-pair.localCandidateId)
.portNumber local-candidate.port
.networkType local-candidate.networkType
.ipAddress local-candidate.address
.stunKeepaliveResponsesReceived candidate-pair.responsesReceived
.stunKeepaliveRttTotal candidate-pair.totalRoundTripTime
.transport local-candidate.protocol
.candidateType local-candidate.candidateType
.priority local-candidate.priority
Legacy metric
Standard correspondence
Same as localcandidate above. Same as local-candidate above.
Legacy metric
Standard correspondence
.responsesSent candidate-pair.responsesSent
.requestsReceived candidate-pair.requestsReceived
.googRemoteCandidateType remote-candidate.candidateType
(lookup remote-candidate via
.googReadable googReadable is a boolean reflecting whether or not we've recently incremented candidate-pair.requestsReceived or candidate-pair.responsesReceived
.googLocalAddress local-candidate.address
(lookup local-candidate via
.consentRequestsSent candidate-pair.consentRequestsSent
.googTransportType Same as local-candidate.protocol and remote-candidate.protocol.
.googChannelId candidate-pair.transportId
.googLocalCandidateType local-candidate.candidateType
.googWritable googWritable is a boolean reflecting whether or not we've recently incremented candidate-pair.responsesReceived
.googRemoteAddress remote-candidate.address
.googRtt candidate-pair.currentRoundTripTime
.googActiveConnection The active connection refers to the candidate pair that is currently selected by the transport, such as where candidate-pair.id == transport.selectedCandidatePairId
.packetsDiscardedOnSend candidate-pair.packetsDiscardedOnSend
.bytesReceived candidate-pair.bytesReceived
.responsesReceived candidate-pair.responsesReceived
.remoteCandidateId candidate-pair.remoteCandidateId
.localCandidateId candidate-pair.localCandidateId
.bytesSent candidate-pair.bytesSent
.packetsSent candidate-pair.packetsSent
.bytesReceived candidate-pair.bytesReceived
.bytesReceived candidate-pair.bytesReceived
Legacy metric
Standard correspondence
inbound-rtp, outbound-rtp, media-source
.audioInputLevel media-source.audioLevel. The legacy metric is in range [0..32768] but the standard metrc is in range [0..1].
inbound-rtp.audioLevel. The legacy metric is in range [0..32768] but the standard metrc is in range [0..1].
.packetsLost inbound-rtp.packetsLost
.googTrackId media-source.trackIdentifier for local MediaStreamTracks and inbound-rtp.trackIdentifier for remote MediaStreamTracks
.googRtt remote-inbound-rtp.roundTripTime (see outbound-rtp.remoteId)
.googEchoCancellationReturnLossEnhancement inbound-rtp.echoReturnLossEnhancement
.googCodecName The codec name is the subtype of the "type/subtype" mime type, codec.mimeType (see inbound-rtp.codecId and outbound-rtp.codecId)
.transportId inbound-rtp.transportId and outbound-rtp.transportId
.mediaType inbound-rtp.kind and outbound-rtp.kind or media-source.kind
.googEchoCancellationReturnLoss inbound-rtp.echoReturnLoss
.totalAudioEnergy inbound-rtp.totalAudioEnergy and media-source.totalAudioEnergy
ssrc.totalSamplesDuration inbound-rtp.totalSamplesDuration and media-source.totalSamplesDuration
.ssrc inbound-rtp.ssrc and outbound-rtp.ssrc
.googJitterReceived inbound-rtp.jitter
.packetsSent outbound-rtp.packetsSent
.bytesSent outbound-rtp.bytesSent
.googContentType inbound-rtp.contentType and outbound-rtp.contentType
.googFrameWidthInput media-source.width
.googFrameHeightInput media-source.height
.googFrameRateInput media-source.framesPerSecond
.googFrameWidthSent outbound-rtp.frameWidth
.googFrameHeightSent outbound-rtp.frameHeight
While the send FPS is the rate of change of outbound-rtp.framesSent, this is actually implemented as outbound-rtp.framesPerSecond which is encoding FPS.
.googFrameWidthReceived inbound-rtp.frameWidth
.googFrameHeightReceived inbound-rtp.frameHeight
The rate of change of inbound-rtp.framesDecoded
The rate of change of inbound-rtp.framesDecoded - inbound-rtp.framesDropped
.hugeFramesSent outbound-rtp.hugeFramesSent

inbound-rtp.qpSum and outbound-rtp.qpSum

.framesEncoded outbound-rtp.framesEncoded

outbound-rtp.totalEncodeTime / outbound-rtp.framesEncoded


inbound-rtp.decoderImplementation and outbound-rtp.encoderImplementation

True if outbound-rtp.qualityLimitationReason == "cpu"
True if outbound-rtp.qualityLimitationReason == "bandwidth"
The legacy metric counts the number of times resolution or frame rate changed for qualityLimitationReason related reasons. This could be deduced from other metrics (e.g. send resolution or frame rate being different from source resolution or frame rate), but the duration that we have been limited, outbound-rtp.qualityLimitationDurations, may be more useful than how frequently resolution or frame rate changed was reconfigured.
.googNacksReceived inbound-rtp.nackCount
.googNacksSent inbound-rtp.nackCount
.googPlisReceived inbound-rtp.pliCount
.googPlisSent inbound-rtp.pliCount
.googFirsReceived inbound-rtp.firCount
.googFirsSent inbound-rtp.firCount
The recent ratio of packets containing error correction: inbound-rtp.fecPacketsReceived - inbound-rtp.fecPacketsDiscarded
.packetsReceived inbound-rtp.packetsReceived
.googJitterBufferMs inbound-rtp.jitterBufferDelay / inbound-rtp.jitterBufferEmittedCount
.googTargetDelayMs (video) inbound-rtp.jitterBufferTargetDelay / inbound-rtp.jitterBufferEmittedCount
.googPreferredJitterBufferMs (audio) inbound-rtp.jitterBufferTargetDelay / inbound-rtp.jitterBufferEmittedCount
The recent ratio of concealed samples: inbound-rtp.concealedSamples / inbound-rtp.totalSamplesReceived
.googSpeechExpandRate The recent ratio of concealed samples while the stream was not silent: of (inbound-rtp.concealedSamples - inbound-rtp.silentConcealedSamples) / inbound-rtp.concealedSamples
.googAccelerateRate The recent ratio of samples that were discarded in order to accelerate playout speed: inbound-rtp.removedSamplesForAcceleration / inbound-rtp.totalSamplesReceived
The recent ratio of samples that were synthesized in order to decelerate playout speed: inbound-rtp.insertedSamplesForDeceleration / inbound-rtp.totalSamplesReceived
.googSecondaryDiscardedRate inbound-rtp.fecPacketsDiscarded
.bytesReceived inbound-rtp.bytesReceived
s.googCurrentDelayMs inbound-rtp.jitterBufferDelay + media-playout.totalPlayoutDelay
.googDecodeMs inbound-rtp.totalDecodeTime / inbound-rtp.framesDecoded
The only remaining goog-metric. inbound-rtp.googTimingFrameInfo
.framesDecoded inbound-rtp.framesDecoded
Legacy metric
Standard correspondence
outbound-rtp and candidate-pair
outbound-rtp.targetBitrate as an instantaneous value or outbound-rtp.totalEncodedBytesTarget / outbound-rtp.framesEncoded as an average
.googActualEncBitrate The bytes produced by the encoder are the payload bytes, excluding retransmissions: the rate of change of outbound-rtp.bytesSent - outbound-rtp.retransmittedBytesSent
.googBucketDelay outbound-rtp.totalPacketSendDelay / outbound-rtp.packetsSent
.googTransmitBitrate The rate of change of outbound-rtp.headerBytesSent + outbound-rtp.bytesSent for per-RTP stream bitrate, candidate-pair.bytesSent for per-ICE candidate bitrate or transport.bytesSent for per-transport bitrate
.googRetransmitBitrate The range of change of outbound-rtp.retransmittedBytesSent
.googAvailableSendBandwidth candidate-pair.availableOutgoingBitrate
.googAvailableReceiveBandwidth candidate-pair.availableIncomingBitrate

The standard API is simulcast-aware

If you use simulcast you may have noticed that the legacy API only reports a single SSRC even when you're using simulcast to send (for example) three RTP streams over three separate SSRCs.

The standard API does not share this limitation and will return three outbound-rtp stats objects, one for each of the SSRCs. This means that you can analyze each RTP stream individually, but it also means that to obtain the total bitrate of all RTP send streams you'll need to aggregate them yourself.

SVC streams or RTP streams with multiple spatial layers configured via the scalabilityMode API on the other hand still show up as a single outbound-rtp because these are sent over a single SSRC.

If you need more time for migration

When the legacy API is removed in Chrome 117, using it will generate an exception. If you are unable to migrate your code in time, the origin trial for RTCPeerConnection callback-based getStats() API gives registered websites more time to migrate. With an origin trial token, the legacy getStats() API may continue to be used until Chrome 121.