Signatur
Beschreibung
getSelectedCandidatePair() zeigt, über welchen Netzwerkpfad eine WebRTC-Verbindung tatsächlich läuft. Beim ICE-Verfahren (Interactive Connectivity Establishment) sammeln beide Seiten Kandidaten, also mögliche Adressen und Ports, und prüfen, welche Kombinationen funktionieren. Das Paar, das der ICE-Agent am Ende auswählt, gibt diese Methode als RTCIceCandidatePair mit den Eigenschaften local und remote zurück. Beide sind RTCIceCandidate-Objekte.
Typische Einsatzfälle sind Diagnose und Überwachung. Man kann zum Beispiel feststellen, ob eine Verbindung direkt läuft (host), über NAT-Traversal (srflx/prflx) oder über einen TURN-Server (relay). Relay-Verbindungen kosten Serverbandbreite und erhöhen meist die Latenz. Deshalb ist diese Information für Qualitätsanzeigen, Logs und Kostenauswertungen nützlich.
Ein RTCIceTransport wird nicht direkt erzeugt. Man erreicht ihn über den DTLS-Transport eines Senders oder Empfängers (sender.transport.iceTransport) oder über pc.sctp.transport.iceTransport bei Datenkanälen. Das ausgewählte Paar kann sich während einer Sitzung ändern, etwa bei einem Netzwechsel oder einem ICE-Restart. Dann wird das Ereignis selectedcandidatepairchange ausgelöst.
Rückgabewert
local und remote (jeweils RTCIceCandidate), das das aktuell ausgewählte Kandidatenpaar beschreibt. Ist noch kein Paar gewählt, zum Beispiel weil die ICE-Prüfungen noch laufen, lautet der Rückgabewert null.Beispiele
Verbindungsart nach dem Aufbau ermitteln
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
pc.addEventListener('connectionstatechange', () => {
if (pc.connectionState !== 'connected') return;
const sender = pc.getSenders().find((s) => s.transport);
const iceTransport = sender?.transport?.iceTransport;
const pair = iceTransport?.getSelectedCandidatePair?.();
if (!pair) {
console.log('Kein ausgewähltes Kandidatenpaar verfügbar');
return;
}
const { local, remote } = pair;
console.log(`Lokal: ${local.type} ${local.protocol} ${local.address}:${local.port}`);
console.log(`Remote: ${remote.type} ${remote.protocol} ${remote.address}:${remote.port}`);
console.log(local.type === 'relay' || remote.type === 'relay'
? 'Verbindung läuft über TURN-Relay'
: 'Direkte bzw. NAT-traversierte Verbindung');
});
Auf Wechsel des Kandidatenpaars reagieren
const watchCandidatePair = (pc) => {
const iceTransport = pc.sctp?.transport?.iceTransport
?? pc.getSenders()[0]?.transport?.iceTransport;
if (!iceTransport) return;
const logPair = () => {
const pair = iceTransport.getSelectedCandidatePair();
if (pair) {
console.log('Neues Paar:', pair.local.type, '↔', pair.remote.type);
}
};
// addEventListener-Variante
iceTransport.addEventListener('selectedcandidatepairchange', logPair);
// alternativ: iceTransport.onselectedcandidatepairchange = logPair;
return () => iceTransport.removeEventListener('selectedcandidatepairchange', logPair);
};
// Aufruf, nachdem Tracks oder Datenkanal ausgehandelt wurden:
// const stopWatching = watchCandidatePair(pc);
Browserübergreifender Ersatz über getStats()
const getSelectedPairFromStats = async (pc) => {
const stats = await pc.getStats();
let pairId;
stats.forEach((report) => {
if (report.type === 'transport' && report.selectedCandidatePairId) {
pairId = report.selectedCandidatePairId;
}
});
// Firefox: Fallback über das nominierte Paar
if (!pairId) {
stats.forEach((r) => {
if (r.type === 'candidate-pair' && r.nominated && r.state === 'succeeded') pairId = r.id;
});
}
const pair = stats.get(pairId);
if (!pair) return null;
return {
local: stats.get(pair.localCandidateId),
remote: stats.get(pair.remoteCandidateId),
rtt: pair.currentRoundTripTime,
};
};
const info = await getSelectedPairFromStats(pc);
console.log(info?.local.candidateType, info?.remote.candidateType, info?.rtt);
// Wichtig · Fallstricke
- Browser-Kompatibilität: In Chromium-basierten Browsern (Chrome, Edge, Opera) und in Safari verfügbar. Firefox stellt
RTCIceTransportsamt dieser Methode nicht bereit. Deshalb vorher die Unterstützung prüfen (typeof iceTransport?.getSelectedCandidatePair === 'function') oder die Werte übergetStats()ermitteln (siehe Beispiel). - Kann
nullliefern: Vor Abschluss der ICE-Prüfungen gibt es kein ausgewähltes Paar. Deshalb erst beiconnectionState === 'connected'oder im Ereignisselectedcandidatepairchangeabfragen. - Zugriff auf den Transport:
sender.transportistnull, bis die SDP-Aushandlung abgeschlossen ist. Bei Bundling (Standard) teilen sich alle Sender einen gemeinsamen Transport. - Momentaufnahme: Das zurückgegebene Objekt aktualisiert sich nicht. Nach einem Netzwechsel oder
restartIce()muss man die Methode erneut aufrufen. - Datenschutz: Wegen mDNS-Verschleierung erscheinen lokale Host-Adressen oft als
*.local-Hostname statt als echte IP. IP-Adressen gelten als personenbezogen und sollten nicht ungefiltert protokolliert werden.