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 |
---|---|
ssrc
|
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. |
VideoBwe
|
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 ). |
googComponent
|
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 . |
googCandidatePair
|
Represents an ICE candidate pair, which is a pairing of a local and a remote candidate. The standard version is candidate-pair . |
googCertificate
|
Represents a certificate used by the DTLS transport. The standard version is certificate . |
googLibjingleSession
|
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:
|
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
googCertificate |
Standard correspondence
certificate |
---|---|
.googFingerprint
|
.fingerprint
|
.googFingerprintAlgorithm
|
.fingerprintAlgorithm
|
.googDerBase64
|
.base64Certificate
|
Legacy metric
googComponent |
Standard correspondence
transport |
---|---|
.localCertificateId
|
.localCertificateId
|
.remoteCertificateId
|
.remoteCertificateId
|
.selectedCandidatePairId
|
.selectedCandidatePairId
|
.dtlsCipher
|
.dtlsCipher
|
.srtpCipher
|
.srtpCipher
|
Legacy metric
localcandidate |
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
remotecandidate |
Standard correspondence
remote-candidate |
---|---|
Same as localcandidate above. |
Same as local-candidate above. |
Legacy metric
googCandidatePair |
Standard correspondence
candidate-pair |
---|---|
.responsesSent
|
candidate-pair.responsesSent
|
.requestsReceived
|
candidate-pair.requestsReceived
|
.googRemoteCandidateType
|
remote-candidate.candidateType (lookup remote-candidate via candidate-pair.remoteCandidateId ) |
.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 candidate-pair.localCandidateId ) |
.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
ssrc |
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]. |
.audioOutputLevel
|
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 MediaStreamTrack s and inbound-rtp.trackIdentifier for remote MediaStreamTrack s |
.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
|
.googFrameRateSent
|
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
|
.googFrameRateDecoded
|
The rate of change of inbound-rtp.framesDecoded |
.googFrameRateOutput
|
The rate of change of inbound-rtp.framesDecoded - inbound-rtp.framesDropped |
.hugeFramesSent
|
outbound-rtp.hugeFramesSent
|
.qpSum
|
|
.framesEncoded
|
outbound-rtp.framesEncoded
|
.googAvgEncodeMs
|
|
.codecImplementationName
|
|
.googCpuLimitedResolution
|
True if outbound-rtp.qualityLimitationReason == "cpu" |
.googBandwidthLimitedResolution
|
True if outbound-rtp.qualityLimitationReason == "bandwidth" |
.googAdaptationChanges
|
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
|
.googSecondaryDecodedRate
|
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
|
.googExpandRate
|
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 |
.googPreemptiveExpandRate
|
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
|
.googTimingFrameInfo
|
The only remaining goog-metric. inbound-rtp.googTimingFrameInfo |
.framesDecoded
|
inbound-rtp.framesDecoded
|
Legacy metric
VideoBwe |
Standard correspondence
outbound-rtp and candidate-pair |
---|---|
.googTargetEncBitrate
|
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.