Signatur
Beschreibung
createOffer() ist der erste Schritt der WebRTC-Aushandlung (Offer/Answer-Modell) auf Seiten des Anrufers. Die Methode erstellt eine SDP-Beschreibung (Session Description Protocol) mit den Medienspuren, Datenkanälen, Codecs, Verschlüsselungsparametern (DTLS-Fingerabdruck) und ICE-Zugangsdaten, die der lokale Peer anbietet.
Das Ergebnis wird nicht automatisch angewendet: Man übergibt es anschließend an setLocalDescription() und schickt es über einen eigenen Signalisierungskanal (z. B. WebSocket) an die Gegenstelle. Diese ruft setRemoteDescription() und danach createAnswer() auf.
Vor dem Aufruf sollten die gewünschten Spuren per addTrack()/addTransceiver() bzw. Datenkanäle per createDataChannel() hinzugefügt sein – sonst enthält das Angebot keine Medienabschnitte. In moderner Praxis wird createOffer() meist innerhalb des negotiationneeded-Events aufgerufen; alternativ erledigt ein parameterloses setLocalDescription() das Erzeugen des Angebots implizit („Perfect Negotiation“).
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $options | RTCOfferOptions | {} | Optionales Objekt. Wichtigste Eigenschaft: iceRestart (boolean, Standard false) – erzeugt neue ICE-Zugangsdaten und erzwingt einen ICE-Neustart, z. B. nach Netzwerkwechsel. Die veralteten Optionen offerToReceiveAudio/offerToReceiveVideo sollten durch addTransceiver(kind, { direction: 'recvonly' }) ersetzt werden. |
Rückgabewert
{ type: 'offer', sdp: '...' } erfüllt wird. Es wird abgelehnt mit InvalidStateError (Verbindung geschlossen), NotReadableError (keine Zertifikate/Ressourcen) oder OperationError.Beispiele
Einfaches Angebot mit Kamera und Mikrofon
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
stream.getTracks().forEach((track) => pc.addTrack(track, stream));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
console.log(offer.type);
console.log(pc.signalingState);
// offer über den Signalisierungskanal an die Gegenstelle senden
// signaling.send(JSON.stringify({ description: pc.localDescription }));
Aushandlung im negotiationneeded-Event per WebSocket
const signaling = new WebSocket('wss://example.com/signal');
const pc = new RTCPeerConnection();
pc.addEventListener('negotiationneeded', async () => {
try {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send(JSON.stringify({ description: pc.localDescription }));
} catch (err) {
console.error('Angebot fehlgeschlagen:', err);
}
});
pc.addEventListener('icecandidate', ({ candidate }) => {
if (candidate) signaling.send(JSON.stringify({ candidate }));
});
signaling.addEventListener('message', async ({ data }) => {
const { description, candidate } = JSON.parse(data);
if (description?.type === 'answer') await pc.setRemoteDescription(description);
if (candidate) await pc.addIceCandidate(candidate);
});
// Löst negotiationneeded aus
pc.createDataChannel('chat');
ICE-Neustart nach Verbindungsabbruch
pc.addEventListener('iceconnectionstatechange', async () => {
if (pc.iceConnectionState === 'failed') {
// Alternativ: pc.restartIce() (modern)
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
signaling.send(JSON.stringify({ description: pc.localDescription }));
}
});
Nur empfangen (ohne eigene Spuren)
const pc = new RTCPeerConnection();
// Moderner Ersatz für offerToReceiveVideo/Audio
pc.addTransceiver('video', { direction: 'recvonly' });
pc.addTransceiver('audio', { direction: 'recvonly' });
const offer = await pc.createOffer();
console.log(offer.sdp.includes('a=recvonly'));
// Wichtig · Fallstricke
- Nicht automatisch angewendet: Ohne
setLocalDescription(offer)startet weder die ICE-Kandidatensuche noch ändert sich dersignalingState. - Glare: Rufen beide Peers gleichzeitig
createOffer()auf, kollidieren die Angebote. Lösung: das „Perfect Negotiation“-Muster mit höflichem/unhöflichem Peer und Rollback. - Leeres Angebot: Ohne Spuren, Transceiver oder Datenkanal enthält das SDP keine
m=-Zeilen – die Verbindung transportiert dann nichts. - Veraltete Signaturen: Die Callback-Variante
createOffer(success, failure, options)und die OptionenofferToReceiveAudio/offerToReceiveVideogelten als veraltet; Promise-Variante undaddTransceiver()nutzen. - SDP-Manipulation („SDP munging“) vor
setLocalDescription()ist fehleranfällig; für Codec-Auswahl besserRTCRtpTransceiver.setCodecPreferences()verwenden. - Sicherheit: Das SDP enthält ICE-Zugangsdaten und ggf. IP-Adressen. Den Signalisierungskanal über TLS (
wss://) absichern; WebRTC ist nur in sicheren Kontexten (HTTPS) mit Mediengeräten nutzbar. - Nach
pc.close()wirft der Aufruf einenInvalidStateError.