Signatur
Beschreibung
RTCIceCandidatePairStats ist ein Wörterbuch (Dictionary). Einen Konstruktor dafür gibt es nicht. Die Objekte liefert RTCPeerConnection.getStats() bzw. RTCRtpSender.getStats()/RTCRtpReceiver.getStats() im zurückgegebenen RTCStatsReport. Man erkennt sie an type === "candidate-pair". Jedes Objekt beschreibt ein Paar aus lokalem und entferntem ICE-Kandidaten, das beim Verbindungsaufbau geprüft wurde oder gerade benutzt wird.
Das Wörterbuch ist das wichtigste Werkzeug, um die tatsächlich genutzte Netzwerkverbindung zu untersuchen: Welcher Weg wurde gewählt (direkt, über STUN oder über ein TURN-Relay)? Wie hoch ist die Umlaufzeit (currentRoundTripTime)? Welche Bandbreite schätzt der Browser (availableOutgoingBitrate)? Wie viele Bytes wurden übertragen? Typische Einsatzfälle sind Verbindungsqualitäts-Anzeigen in Videokonferenzen, Monitoring und Fehlersuche bei NAT- oder Firewall-Problemen.
Wichtige Eigenschaften (zusätzlich zu id, timestamp und type aus RTCStats):
localCandidateId/remoteCandidateId: IDs der zugehörigenRTCIceCandidateStats-Einträge im selben Bericht.transportId: ID des zugehörigenRTCTransportStats-Eintrags.state:"frozen","waiting","in-progress","failed"oder"succeeded".nominated:true, wenn das Paar für die Nutzung nominiert wurde.bytesSent,bytesReceived,packetsSent,packetsReceived: Zähler für die übertragenen Daten.currentRoundTripTime,totalRoundTripTime,responsesReceived: Latenzwerte in Sekunden, ermittelt über STUN-Prüfungen.availableOutgoingBitrate,availableIncomingBitrate: Bandbreitenschätzung in Bit/s.requestsSent,requestsReceived,consentRequestsSent,lastPacketSentTimestamp,lastPacketReceivedTimestamp: Diagnosewerte zu Konnektivitätsprüfungen.
Das aktiv genutzte Paar findet man am zuverlässigsten über RTCTransportStats.selectedCandidatePairId. Wenn man den Transport nicht auswerten will, reicht als Näherung ein Paar mit nominated === true und state === "succeeded".
Rückgabewert
RTCStatsReport (einer Map-ähnlichen Struktur). Man erhält es über await pc.getStats().Beispiele
Alle Kandidatenpaare aus dem Statistikbericht auslesen
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
// ... Signalisierung / Verbindungsaufbau ...
const listPairs = async () => {
const report = await pc.getStats();
report.forEach((stats) => {
if (stats.type === 'candidate-pair') {
console.log(stats.id, stats.state, 'nominiert:', stats.nominated);
}
});
};
await listPairs();
Aktives Paar über den Transport finden und Verbindungstyp bestimmen
const getActivePair = async (pc) => {
const report = await pc.getStats();
let pair = null;
// Bevorzugter Weg: selectedCandidatePairId aus RTCTransportStats
for (const stats of report.values()) {
if (stats.type === 'transport' && stats.selectedCandidatePairId) {
pair = report.get(stats.selectedCandidatePairId);
}
}
// Fallback: nominiertes, erfolgreiches Paar
pair ??= [...report.values()].find(
(s) => s.type === 'candidate-pair' && s.nominated && s.state === 'succeeded'
);
if (!pair) return null;
const local = report.get(pair.localCandidateId);
const remote = report.get(pair.remoteCandidateId);
return {
lokal: `${local.candidateType} ${local.address}:${local.port} (${local.protocol})`,
entfernt: `${remote.candidateType} ${remote.address}:${remote.port}`,
ueberRelay: local.candidateType === 'relay' || remote.candidateType === 'relay',
rttMs: Math.round((pair.currentRoundTripTime ?? 0) * 1000)
};
};
console.log(await getActivePair(pc));
Laufende Qualitätsanzeige: Durchsatz und Bandbreite pro Sekunde
let previous = null;
const monitor = setInterval(async () => {
const report = await pc.getStats();
const pair = [...report.values()].find(
(s) => s.type === 'candidate-pair' && s.nominated && s.state === 'succeeded'
);
if (!pair) return;
if (previous) {
const seconds = (pair.timestamp - previous.timestamp) / 1000;
const kbitIn = ((pair.bytesReceived - previous.bytesReceived) * 8) / seconds / 1000;
const kbitOut = ((pair.bytesSent - previous.bytesSent) * 8) / seconds / 1000;
console.log(
`↓ ${kbitIn.toFixed(0)} kbit/s | ↑ ${kbitOut.toFixed(0)} kbit/s | ` +
`geschätzt verfügbar: ${((pair.availableOutgoingBitrate ?? 0) / 1000).toFixed(0)} kbit/s`
);
}
previous = pair;
}, 1000);
// Beim Beenden aufräumen
pc.addEventListener('connectionstatechange', () => {
if (pc.connectionState === 'closed' || pc.connectionState === 'failed') clearInterval(monitor);
});
// Wichtig · Fallstricke
- Kein Konstruktor:
RTCIceCandidatePairStatsist ein reines Wörterbuch. Man bekommt es nur übergetStats(), einnew-Aufruf ist nicht möglich. - Nominiert ist nicht dasselbe wie aktiv: Mehrere Paare können
nominated: truehaben, etwa nach einem ICE-Neustart. Verlässlich istRTCTransportStats.selectedCandidatePairId. Die veraltete Eigenschaftselectedgab es nur in Firefox, man sollte sich nicht darauf verlassen. - Browser-Unterschiede: Nicht jeder Browser liefert jedes Feld.
availableIncomingBitratefehlt oft, und in Firefox kann auchavailableOutgoingBitratefehlen. Felder deshalb immer mit??oderinabsichern. Veraltete Felder wiepriority,readableoderwritablewurden aus der Spezifikation entfernt. - Zähler sind kumulativ:
bytesSent,totalRoundTripTimeusw. wachsen immer weiter. Raten erhält man nur aus der Differenz zweier Messungen, geteilt durch die Differenz dertimestamp-Werte (in Millisekunden). - Einheiten: Umlaufzeiten werden in Sekunden angegeben, Bitraten in Bit pro Sekunde.
- Datenschutz: Über die verknüpften
RTCIceCandidateStatslassen sich IP-Adressen auslesen. Browser verschleiern lokale Host-Adressen teils über mDNS (*.local). Statistiken, die an ein Monitoring-Backend gehen, sollte man vorher anonymisieren. - Performance:
getStats()ist asynchron und nicht kostenlos. Intervalle unter etwa 500 ms sind selten sinnvoll.