Signatur
Beschreibung
Die Eigenschaft signalingState zeigt, an welcher Stelle des SDP-Austauschs (Offer/Answer) die lokale Seite einer RTCPeerConnection gerade steht. Der Wert ändert sich durch Aufrufe von setLocalDescription() und setRemoteDescription(). Bei jeder Änderung wird das Ereignis signalingstatechange ausgelöst.
Mögliche Werte:
"stable": Es läuft kein Austausch. Entweder wurde noch nichts ausgehandelt, oder Offer und Answer sind abgeschlossen."have-local-offer": Ein lokales Offer wurde mitsetLocalDescription()gesetzt."have-remote-offer": Ein entferntes Offer wurde mitsetRemoteDescription()gesetzt."have-local-pranswer": Auf ein entferntes Offer wurde lokal eine vorläufige Antwort (pranswer) gesetzt."have-remote-pranswer": Auf ein lokales Offer ist eine vorläufige Antwort der Gegenseite eingetroffen."closed": Die Verbindung wurde mitclose()geschlossen.
Praktisch ist die Eigenschaft vor allem, um Race Conditions bei der Neuverhandlung zu verhindern, etwa bei „Glare“, wenn beide Seiten gleichzeitig ein Offer senden. Sie ist außerdem die Grundlage des „Perfect Negotiation“-Musters. Mit signalingState prüfen Sie, ob ein neues Offer gerade zulässig ist oder ob ein eingehendes Offer mit einem eigenen kollidiert.
Verwechseln Sie signalingState nicht mit connectionState oder iceConnectionState. Diese beiden beschreiben den Transport- bzw. ICE-Zustand. signalingState betrifft nur die Aushandlung der Sitzungsbeschreibung.
Rückgabewert
RTCSignalingState, die den aktuellen Signalisierungszustand beschreibt.Beispiele
Zustandswechsel beim Offer/Answer-Ablauf beobachten
const pc = new RTCPeerConnection();
pc.addEventListener('signalingstatechange', () => {
console.log('Neuer Zustand:', pc.signalingState);
});
console.log('Start:', pc.signalingState);
const demo = async () => {
pc.createDataChannel('chat'); // damit das Offer Inhalt hat
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
};
demo();
Kompletter lokaler Austausch zwischen zwei Peers
const alice = new RTCPeerConnection();
const bob = new RTCPeerConnection();
alice.createDataChannel('daten');
const log = (name, pc) => console.log(`${name}: ${pc.signalingState}`);
const negotiate = async () => {
const offer = await alice.createOffer();
await alice.setLocalDescription(offer);
log('Alice', alice);
await bob.setRemoteDescription(offer);
log('Bob', bob);
const answer = await bob.createAnswer();
await bob.setLocalDescription(answer);
log('Bob', bob);
await alice.setRemoteDescription(answer);
log('Alice', alice);
alice.close();
log('Alice', alice);
};
negotiate();
Perfect Negotiation: Offer-Kollision erkennen
// signaling ist ein beliebiger Kanal, z. B. ein WebSocket-Wrapper
const setupNegotiation = (pc, signaling, polite) => {
let makingOffer = false;
pc.onnegotiationneeded = async () => {
try {
makingOffer = true;
await pc.setLocalDescription();
signaling.send({ description: pc.localDescription });
} finally {
makingOffer = false;
}
};
signaling.onmessage = async ({ description }) => {
if (!description) return;
const collision = description.type === 'offer' &&
(makingOffer || pc.signalingState !== 'stable');
// Die unhöfliche Seite ignoriert kollidierende Offers
if (!polite && collision) return;
await pc.setRemoteDescription(description); // löst ggf. implizites Rollback aus
if (description.type === 'offer') {
await pc.setLocalDescription();
signaling.send({ description: pc.localDescription });
}
};
};
// Wichtig · Fallstricke
- Schreibgeschützt: Eine Zuweisung an
signalingStatewird ignoriert bzw. wirft im Strict Mode einenTypeError. Den Zustand ändern Sie nur übersetLocalDescription(),setRemoteDescription()oderclose(). - Ungültige Übergänge: Setzen Sie z. B. im Zustand
"have-local-offer"ein weiteres entferntes Offer, schlägtsetRemoteDescription()mit einemInvalidStateErrorfehl. Die Ausnahme sind Browser, die ein implizites Rollback unterstützen. Prüfen Sie den Zustand deshalb vor Neuverhandlungen. - Rollback: Mit
setLocalDescription({ type: 'rollback' })gelangt die Verbindung aus einem Offer-Zustand zurück nach"stable". Ältere Safari-Versionen unterstützten das nur eingeschränkt. - Asynchronität: Der Zustand ändert sich erst, wenn das Promise von
setLocalDescription()bzw.setRemoteDescription()erfüllt ist. Ein direktes Auslesen im selben Tick liefert noch den alten Wert. - pranswer: Die Zustände mit vorläufiger Antwort kommen in der Praxis selten vor und werden nicht von jeder Signalisierungslösung genutzt.
- Nach
close()bleibt der Wert dauerhaft"closed". Die Instanz lässt sich nicht wiederverwenden.