Signatur
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.candidateist einRTCIceCandidate. Du schickst ihn über deinen Signalisierungskanal (z. B. WebSocket) an die Gegenstelle, die ihn mitaddIceCandidate()hinzufügt („Trickle ICE“). - Ende einer Generation:
event.candidateist ein Kandidat mit leeremcandidate-String. Das signalisiert, dass für diese Generation keine weiteren Kandidaten folgen. - Sammeln abgeschlossen:
event.candidateistnull. Alle Kandidaten sind gesammelt;iceGatheringStatewechselt 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
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));
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"));
// Wichtig · Fallstricke
- Auf
nullprüfen: Das letzte Ereignis liefertevent.candidate === null. Wer ohne Prüfung aufevent.candidate.candidatezugreift, bekommt einenTypeError. - 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, bissetRemoteDescription()abgeschlossen ist. - Serialisierung: Für den Versand
candidate.toJSON()verwenden. Das enthältcandidate,sdpMid,sdpMLineIndexundusernameFragment. - 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
icegatheringstatechangezuverlässig. - Einen Fehler beim Sammeln (z. B. ein nicht erreichbarer TURN-Server) meldet nicht dieses Ereignis, sondern
icecandidateerror.