Signatur
Beschreibung
RTCStatsReport ist das Ergebnis der Methoden RTCPeerConnection.getStats(), RTCRtpSender.getStats() und RTCRtpReceiver.getStats(). Es handelt sich um ein Map-ähnliches, schreibgeschütztes Objekt: Die Schlüssel sind eindeutige Statistik-IDs (Strings), die Werte sind Statistik-Wörterbücher (RTCStats-Objekte) mit mindestens den Feldern id, type und timestamp.
Über das Feld type lässt sich unterscheiden, welche Art von Statistik vorliegt, z. B. "inbound-rtp", "outbound-rtp", "remote-inbound-rtp", "candidate-pair", "local-candidate", "remote-candidate", "transport", "codec" oder "media-source". Einträge verweisen über Felder wie transportId, codecId oder localCandidateId aufeinander – diese IDs können direkt mit report.get(id) aufgelöst werden.
Typische Anwendungsfälle sind Qualitätsanzeigen in Videokonferenz-Apps (Bitrate, Paketverlust, Jitter, Round-Trip-Time), Fehlerdiagnose bei Verbindungsproblemen (welches ICE-Kandidatenpaar ist aktiv? Relay über TURN?) sowie Telemetrie. Da die meisten Zähler kumulativ sind, berechnet man Raten, indem man zwei Berichte in zeitlichem Abstand vergleicht und die Differenz durch die Differenz der timestamp-Werte (in Millisekunden) teilt.
Das Interface stellt die üblichen schreibgeschützten Map-Methoden bereit: size, get(), has(), keys(), values(), entries(), forEach() sowie [Symbol.iterator]. Methoden wie set(), delete() oder clear() gibt es nicht.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $size | number | Schreibgeschützt: Anzahl der enthaltenen Statistik-Einträge. | |
| $get(id) | (id: string) => RTCStats | undefined | Liefert den Statistik-Eintrag zur angegebenen ID, z. B. um codecId oder transportId aufzulösen. |
|
| $has(id) | (id: string) => boolean | Prüft, ob ein Eintrag mit dieser ID existiert. | |
| $forEach(callback[, thisArg]) | (callback: (stats: RTCStats, id: string, report: RTCStatsReport) => void, thisArg?: any) => void | Ruft callback für jeden Eintrag in Einfügereihenfolge auf. |
|
| $entries() / keys() / values() | () => Iterator | Iteratoren über [id, stats]-Paare, IDs bzw. Statistik-Objekte. Der Bericht selbst ist mit for...of iterierbar. |
Rückgabewert
Promise von getStats() geliefert; enthält eine Momentaufnahme aller Statistiken zum Abfragezeitpunkt.Beispiele
Bericht abrufen und alle Einträge nach Typ auflisten
const pc = new RTCPeerConnection();
pc.createDataChannel('test');
await pc.setLocalDescription(await pc.createOffer());
const report = await pc.getStats();
console.log('Anzahl Einträge:', report.size);
for (const [id, stats] of report) {
console.log(stats.type, id);
}
Eingehende Video-Bitrate aus zwei Berichten berechnen
// pc ist eine bestehende, verbundene RTCPeerConnection
const getInboundVideo = (report) =>
[...report.values()].find(
(s) => s.type === 'inbound-rtp' && s.kind === 'video'
);
let last = null;
setInterval(async () => {
const report = await pc.getStats();
const now = getInboundVideo(report);
if (!now) return;
if (last) {
const bytes = now.bytesReceived - last.bytesReceived;
const ms = now.timestamp - last.timestamp;
const kbps = Math.round((bytes * 8) / ms); // Bit pro ms = kbit/s
const lossRate = now.packetsLost / (now.packetsReceived + now.packetsLost);
console.log(`Bitrate: ${kbps} kbit/s, Verlust: ${(lossRate * 100).toFixed(1)} %, Jitter: ${now.jitter} s`);
}
last = now;
}, 1000);
Aktives ICE-Kandidatenpaar und Codec über IDs auflösen
const report = await pc.getStats();
report.forEach((stats) => {
if (stats.type === 'transport' && stats.selectedCandidatePairId) {
const pair = report.get(stats.selectedCandidatePairId);
const local = report.get(pair.localCandidateId);
const remote = report.get(pair.remoteCandidateId);
console.log('RTT:', pair.currentRoundTripTime, 's');
console.log('Lokal:', local.candidateType, local.protocol);
console.log('Remote:', remote.candidateType);
if (local.candidateType === 'relay') {
console.log('Verbindung läuft über TURN-Server');
}
}
if (stats.type === 'outbound-rtp' && stats.codecId) {
const codec = report.get(stats.codecId);
console.log(`${stats.kind}-Codec:`, codec.mimeType);
}
});
Statistiken nur für einen bestimmten Sender
const [sender] = pc.getSenders().filter((s) => s.track?.kind === 'audio');
const report = await sender.getStats();
const outbound = [...report.values()].find((s) => s.type === 'outbound-rtp');
console.log('Gesendete Pakete:', outbound?.packetsSent);
// Wichtig · Fallstricke
- Kein Array, keine echte Map:
RTCStatsReportist nur Map-ähnlich und schreibgeschützt. Fürfilter()/find()erst mit[...report.values()]oderArray.from(report.values())umwandeln. - IDs nicht fest verdrahten: Die Schlüssel sind browserspezifisch (z. B.
"OT01V123"in Chrome, UUID-artig in Firefox) und können sich zwischen Aufrufen ändern. Immer übertypeund Felder wiekindoderssrcfiltern. - Kumulative Zähler: Werte wie
bytesReceivedoderpacketsLostsind Summen seit Beginn. Raten nur über die Differenz zweier Berichte ermitteln;timestampist in Millisekunden angegeben. - Uneinheitliche Unterstützung: Nicht jeder Browser liefert jeden Statistik-Typ oder jedes Feld (z. B. fehlten in Safari lange einige
candidate-pair-Felder). Felder daher defensiv mit?.abfragen. Das veraltete, callback-basiertegetStats()aus frühen Chrome-Versionen liefert ein anderes Format und sollte nicht mehr genutzt werden. - Performance:
getStats()ist nicht kostenlos. Abfrageintervalle von etwa 1 s sind üblich; sehr häufiges Pollen kann bei vielen Verbindungen spürbar CPU kosten. - Datenschutz: Kandidaten-Statistiken können IP-Adressen enthalten (
address). Beim Versand an Telemetrie-Server diese Felder entfernen oder anonymisieren.