Start · Sprachen · JavaScript · Referenz · RTCPeerConnection: icecandidate

RTCPeerConnection: icecandidate

Ereignis

Wird an eine <code>RTCPeerConnection</code> gesendet, sobald der lokale ICE-Agent einen neuen Kandidaten ermittelt hat, der per Signalisierung an die Gegenstelle übertragen werden muss.

seit JavaScript Baseline: alle modernen Browser (seit ca. 2017) Kategorie: webapi

Signatur

peerConnection.addEventListener("icecandidate", handler)

Beschreibung

Das Ereignis icecandidate tritt auf, wenn die RTCPeerConnection einen neuen ICE-Kandidaten gefunden hat, also eine mögliche Netzwerkadresse (Host, Server-reflexiv über STUN oder Relay über TURN), über die die Gegenstelle die Verbindung erreichen kann. Der Kandidat liegt in der Eigenschaft event.candidate des übergebenen RTCPeerConnectionIceEvent.

Es wird in drei Situationen ausgelöst:

  • Neuer Kandidat gefunden: event.candidate ist ein RTCIceCandidate. Du schickst ihn über deinen Signalisierungskanal (z. B. WebSocket) an die Gegenstelle, die ihn mit addIceCandidate() hinzufügt („Trickle ICE“).
  • Ende einer Generation: event.candidate ist ein Kandidat mit leerem candidate-String. Das signalisiert, dass für diese Generation keine weiteren Kandidaten folgen.
  • Sammeln abgeschlossen: event.candidate ist null. Alle Kandidaten sind gesammelt; iceGatheringState wechselt auf "complete".

Das Ereignis beginnt in der Regel, nachdem setLocalDescription() aufgerufen wurde. Den Handler registrierst du entweder mit addEventListener("icecandidate", …) oder über die Eigenschaft onicecandidate. Das Ereignis ist nicht abbrechbar und steigt nicht auf (kein Bubbling).

Parameter

Name Typ Default Beschreibung
$type Pflicht string Der Ereignisname, hier immer "icecandidate".
$handler Pflicht (event: RTCPeerConnectionIceEvent) => void Callback-Funktion. Sie bekommt ein RTCPeerConnectionIceEvent, dessen Eigenschaft candidate ein RTCIceCandidate oder null ist.
$options boolean|AddEventListenerOptions false Optionale Listener-Optionen wie once oder signal (zum Entfernen per AbortController).

Rückgabewert

Typ
void
Beschreibung
addEventListener() liefert keinen Wert. Der Handler bekommt ein RTCPeerConnectionIceEvent mit candidate: RTCIceCandidate | null.

Beispiele

Trickle ICE: Kandidaten per WebSocket an die Gegenstelle senden

const signaling = new WebSocket("wss://signal.example.com");
const pc = new RTCPeerConnection({
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});

pc.addEventListener("icecandidate", (event) => {
  if (event.candidate) {
    // Kandidat serialisieren und übertragen
    signaling.send(JSON.stringify({ type: "candidate", candidate: event.candidate.toJSON() }));
  } else {
    // null = Sammeln abgeschlossen
    signaling.send(JSON.stringify({ type: "end-of-candidates" }));
  }
});

// Empfangsseite: Kandidaten der Gegenstelle hinzufügen
signaling.addEventListener("message", async ({ data }) => {
  const msg = JSON.parse(data);
  if (msg.type === "candidate") {
    await pc.addIceCandidate(msg.candidate);
  }
});

const start = async () => {
  pc.createDataChannel("chat");
  await pc.setLocalDescription(await pc.createOffer());
  signaling.send(JSON.stringify({ type: "offer", sdp: pc.localDescription.sdp }));
};
signaling.addEventListener("open", start);

Über die Eigenschaft onicecandidate: Kandidaten protokollieren

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

pc.onicecandidate = ({ candidate }) => {
  if (!candidate) {
    console.log("Sammeln abgeschlossen");
    return;
  }
  if (candidate.candidate === "") return; // Ende einer Generation
  console.log(candidate.type, candidate.protocol, candidate.address);
};

pc.createDataChannel("test");
pc.createOffer().then((offer) => pc.setLocalDescription(offer));
host udp 3f2a1c9e-....local srflx udp 203.0.113.42 Sammeln abgeschlossen

Warten, bis alle Kandidaten gesammelt sind (Non-Trickle), und Listener entfernen

const waitForGatheringComplete = (pc) =>
  new Promise((resolve) => {
    const onCandidate = (event) => {
      if (event.candidate === null) {
        pc.removeEventListener("icecandidate", onCandidate);
        resolve(pc.localDescription);
      }
    };
    pc.addEventListener("icecandidate", onCandidate);
  });

const pc = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.l.google.com:19302" }] });
pc.createDataChannel("data");
await pc.setLocalDescription(await pc.createOffer());
const fullOffer = await waitForGatheringComplete(pc);
console.log(fullOffer.sdp.includes("a=candidate"));
true

// Wichtig · Fallstricke

  • Auf null prüfen: Das letzte Ereignis liefert event.candidate === null. Wer ohne Prüfung auf event.candidate.candidate zugreift, bekommt einen TypeError.
  • Handler früh registrieren: Den Listener vor setLocalDescription() anmelden, sonst können die ersten Kandidaten verloren gehen.
  • Reihenfolge beim Empfänger: addIceCandidate() schlägt fehl, solange keine Remote-Beschreibung gesetzt ist. Früh eintreffende Kandidaten zwischenspeichern, bis setRemoteDescription() abgeschlossen ist.
  • Serialisierung: Für den Versand candidate.toJSON() verwenden. Das enthält candidate, sdpMid, sdpMLineIndex und usernameFragment.
  • Datenschutz: Moderne Browser verschleiern lokale IP-Adressen bei Host-Kandidaten mit mDNS-Namen (*.local). Server-reflexive Kandidaten verraten aber die öffentliche IP. Den Signalisierungskanal deshalb absichern (WSS, Authentifizierung).
  • Kompatibilität: Wird in allen aktuellen Browsern unterstützt. Ältere Implementierungen senden unter Umständen keinen Kandidaten mit leerem String, der eine Generation abschließt. Für das Ende des Sammelns ist zusätzlich icegatheringstatechange zuverlässig.
  • Einen Fehler beim Sammeln (z. B. ein nicht erreichbarer TURN-Server) meldet nicht dieses Ereignis, sondern icecandidateerror.