WebRTC Perfect Negotiation: Simplifying Peer Connection

Establishing a peer-to-peer connection in WebRTC has historically required complex, error-prone signaling logic to handle state synchronization and race conditions. The WebRTC Perfect Negotiation pattern solves these issues by decoupling negotiation logic from application state and assigning predictable roles to each peer. This article explains how the Perfect Negotiation pattern works, why it eliminates the notorious “glare” problem, and how it drastically simplifies modern WebRTC implementations.

The Problem: Glare and State Desynchronization

In traditional WebRTC setups, both peers can attempt to modify the connection simultaneously—such as adding a video track at the exact same moment. This creates a collision known as “glare.”

When glare occurs, both peers generate and send a Session Description Protocol (SDP) offer. Because the WebRTC state machine is highly state-sensitive, receiving an offer while you have an outstanding local offer causes a conflict. Without a strict resolution strategy, the connection fails, requiring developers to write complex, fragile state machines to catch errors, roll back descriptions, and renegotiate.

How Perfect Negotiation Solves It

Perfect Negotiation introduces a simple, asymmetrical framework to resolve glare automatically. Instead of treating peers identically, one peer is designated as polite and the other as impolite.

By establishing this simple asymmetry, one peer’s offer is guaranteed to win in any collision scenario. The entire negotiation state is resolved deterministically without the application having to manually track who started the call or tear down the connection.

The Perfect Negotiation Implementation Loop

The beauty of this pattern lies in its implementation. Using modern WebRTC APIs, the logic can be consolidated into a single, reusable event-driven template. The pattern relies on three primary components:

1. Handling the negotiationneeded Event

Whenever a change occurs that requires negotiation (like adding a track), the browser fires a negotiationneeded event. The application simply responds by creating and sending an offer:

peerConnection.onnegotiationneeded = async () => {
  try {
    makingOffer = true;
    await peerConnection.setLocalDescription();
    sendSignalingMessage({ description: peerConnection.localDescription });
  } catch (err) {
    console.error(err);
  } finally {
    makingOffer = false;
  }
};

Note: Calling setLocalDescription() without arguments automatically creates the appropriate offer or answer based on the current state.

2. Processing Incoming Signaling Messages

When a peer receives an incoming SDP description, it processes it using the polite/impolite logic:

async function handleSignalingMessage({ description, candidate }) {
  try {
    if (description) {
      const offerCollision = (description.type === "offer") && 
                             (makingOffer || peerConnection.signalingState !== "stable");

      // Ignore the offer if we are impolite and a collision occurs
      ignoreOffer = !isPolite && offerCollision;
      if (ignoreOffer) {
        return;
      }

      // If we are polite and there is a collision, rollback our own offer
      if (offerCollision) {
        await Promise.all([
          peerConnection.setLocalDescription({ type: "rollback" }),
          peerConnection.setRemoteDescription(description)
        ]);
      } else {
        await peerConnection.setRemoteDescription(description);
      }

      // If it was an offer, send back an answer
      if (description.type === "offer") {
        await peerConnection.setLocalDescription();
        sendSignalingMessage({ description: peerConnection.localDescription });
      }
    } else if (candidate) {
      try {
        await peerConnection.addIceCandidate(candidate);
      } catch (err) {
        if (!ignoreOffer) throw err; // Ignore candidates if we ignored the offer
      }
    }
  } catch (err) {
    console.error(err);
  }
}

Key Benefits of Perfect Negotiation

By adopting this pattern, developers gain several immediate advantages: