Start · Sprachen · JavaScript · Referenz · RTCIceTransport: selectedcandidatepairchange

RTCIceTransport: selectedcandidatepairchange

Ereignis

Wird an einem <code>RTCIceTransport</code> ausgelöst, sobald der ICE-Agent ein neues Kandidatenpaar (lokal + entfernt) als aktiven Verbindungsweg auswählt.

seit JavaScript Chromium-Browser und Safari; in Firefox nicht verf Kategorie: webapi

Signatur

iceTransport.addEventListener("selectedcandidatepairchange", handler)

Beschreibung

Das Ereignis selectedcandidatepairchange wird an ein RTCIceTransport-Objekt gesendet, wenn der ICE-Agent ein neues Kandidatenpaar auswählt. Ein Kandidatenpaar besteht aus einem lokalen und einem entfernten ICE-Kandidaten. Zusammen beschreiben die beiden die Endpunkte, über die die Medien- oder Datenpakete tatsächlich laufen. Das Ereignis tritt zum ersten Mal auf, wenn eine funktionierende Verbindung zustande kommt. Danach kommt es immer dann, wenn der Agent den Weg wechselt, etwa nach einem ICE-Neustart, beim Wechsel zwischen WLAN und Mobilfunk oder beim Umstieg von einem TURN-Relais auf eine direkte Verbindung.

Das Ereignis ist ein einfaches Event ohne zusätzliche Eigenschaften. Es steigt nicht auf (bubbles) und lässt sich nicht abbrechen. Das neue Paar liest man im Handler mit RTCIceTransport.getSelectedCandidatePair() aus. Die Methode gibt ein Objekt mit den Eigenschaften local und remote zurück, beide vom Typ RTCIceCandidate.

An den RTCIceTransport kommt man über den DTLS-Transport eines Senders oder Empfängers (sender.transport.iceTransport) oder über den SCTP-Transport eines Datenkanals (pc.sctp.transport.iceTransport). Diese Objekte gibt es erst, nachdem die Beschreibungen per setLocalDescription() bzw. setRemoteDescription() gesetzt wurden.

Typische Einsatzzwecke sind Diagnose und Überwachung: Man erkennt damit, ob eine Verbindung direkt (host/srflx) oder über ein TURN-Relais (relay) läuft. Man kann Netzwerkwechsel protokollieren oder dem Nutzer die Verbindungsqualität anzeigen. Den Handler registriert man mit addEventListener() oder über die Eigenschaft onselectedcandidatepairchange.

Parameter

Name Typ Default Beschreibung
$type Pflicht string Der Ereignisname "selectedcandidatepairchange".
$handler Pflicht (event: Event) => void Callback, der beim Wechsel des Kandidatenpaars aufgerufen wird. event.target ist der betroffene RTCIceTransport.
$options boolean | AddEventListenerOptions false Optionale Listener-Optionen wie once oder signal, zum Beispiel um den Listener über einen AbortController zu entfernen.

Rückgabewert

Typ
void
Beschreibung
addEventListener() gibt nichts zurück. Die Informationen zum neuen Paar liefert getSelectedCandidatePair() im Handler.

Beispiele

Gewähltes Kandidatenpaar per addEventListener protokollieren

const pc = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});

const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const sender = pc.addTrack(stream.getAudioTracks()[0], stream);

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// ... Offer per Signalisierung senden, Answer empfangen und setzen ...

const iceTransport = sender.transport?.iceTransport;

iceTransport?.addEventListener('selectedcandidatepairchange', (event) => {
  const pair = event.target.getSelectedCandidatePair();
  if (!pair) return;
  const { local, remote } = pair;
  console.log(`Lokal:    ${local.type} ${local.address}:${local.port} (${local.protocol})`);
  console.log(`Entfernt: ${remote.type} ${remote.address}:${remote.port} (${remote.protocol})`);
});
Lokal: srflx 203.0.113.5:54012 (udp) Entfernt: host 192.168.1.20:61234 (udp)

TURN-Relais erkennen über onselectedcandidatepairchange

const pc = new RTCPeerConnection();
const channel = pc.createDataChannel('chat');

const showRelayHint = (isRelay) => {
  document.querySelector('#verbindung').textContent = isRelay
    ? 'Verbindung läuft über TURN-Relais (höhere Latenz möglich)'
    : 'Direkte Verbindung aktiv';
};

channel.addEventListener('open', () => {
  const iceTransport = pc.sctp?.transport.iceTransport;
  if (!iceTransport) return;

  iceTransport.onselectedcandidatepairchange = () => {
    const pair = iceTransport.getSelectedCandidatePair();
    const isRelay = pair?.local.type === 'relay' || pair?.remote.type === 'relay';
    showRelayHint(isRelay);
  };
});

Listener mit AbortController wieder entfernen

const controller = new AbortController();
const changes = [];

const watchPairChanges = (iceTransport) => {
  iceTransport.addEventListener('selectedcandidatepairchange', () => {
    const pair = iceTransport.getSelectedCandidatePair();
    changes.push({ zeit: Date.now(), lokal: pair?.local.type, entfernt: pair?.remote.type });
    console.log(`Paarwechsel Nr. ${changes.length}`);
  }, { signal: controller.signal });
};

// Beim Beenden des Anrufs aufräumen
const hangUp = (pc) => {
  controller.abort(); // entfernt den Listener
  pc.close();
  console.log('Protokoll:', changes);
};
Paarwechsel Nr. 1 Protokoll: [{ zeit: 1718000000000, lokal: 'host', entfernt: 'host' }]

Browserübergreifender Ersatz über getStats()

const logSelectedPairViaStats = async (pc) => {
  const stats = await pc.getStats();
  for (const report of stats.values()) {
    if (report.type === 'transport' && report.selectedCandidatePairId) {
      const pair = stats.get(report.selectedCandidatePairId);
      const local = stats.get(pair.localCandidateId);
      const remote = stats.get(pair.remoteCandidateId);
      console.log(`Aktives Paar: ${local.candidateType} ↔ ${remote.candidateType}`);
    }
  }
};

// Fallback für Browser ohne RTCIceTransport-Ereignis (z. B. Firefox)
const pc = new RTCPeerConnection();
pc.addEventListener('iceconnectionstatechange', () => {
  if (pc.iceConnectionState === 'connected') logSelectedPairViaStats(pc);
});
Aktives Paar: host ↔ srflx