Lifetime of a WebRTC session - Web APIs

Lifetime of a WebRTC session
============================

WebRTC lets you build peer-to-peer communication of arbitrary data, audio, or video—or any combination thereof—into a browser application. In this article, we'll look at the lifetime of a WebRTC session, from establishing the connection all the way through closing the connection when it's no longer needed.

This article doesn't get into details of the actual APIs involved in establishing and handling a WebRTC connection; it reviews the process in general with some information about why each step is required. See Signaling and video calling for an actual example with a step-by-step explanation of what the code does.

**Note:** This page is currently under construction, and some of the content will move to other pages as the WebRTC guide material is built out. Pardon our dust!

Establishing the connection
---------------------------

The internet is big. Really big. It's so big that years ago, smart people saw how big it was, how fast it was growing, and the limitations of the 32-bit IP addressing system, and realized that something had to be done before we ran out of addresses to use, so they started working on designing a new 64-bit addressing system. But they realized that it would take longer to complete the transition than 32-bit addresses would last, so other smart people came up with a way to let multiple computers share the same 32-bit IP address. Network Address Translation (NAT) is a standard which supports this address sharing by handling routing of data inbound and outbound to and from devices on a LAN, all of which are sharing a single WAN (global) IP address.

The problem for users is that each individual computer on the internet no longer necessarily has a unique IP address, and, in fact, each device's IP address may change not only if they move from one network to another, but if their network's address is changed by NAT and/or DHCP. For developers trying to do peer-to-peer networking, this introduces a conundrum: without a unique identifier for every user device, it's not possible to instantly and automatically know how to connect to a specific device on the internet. Even though you know who you want to talk to, you don't necessarily know how to reach them or even what their address is.

This is like trying to mail a package to your friend Michelle by labeling it "Michelle" and dropping it in a mailbox when you don't know her address. You need to look up her address and include it on the package, or she'll wind up wondering why you forgot her birthday again.

This is where signaling comes in.

### Signaling

Signaling is the process of sending control information between two devices to determine the communication protocols, channels, media codecs and formats, and method of data transfer, as well as any required routing information. The most important thing to know about the signaling process for WebRTC: **it is not defined in the specification**.

Why, you may wonder, is something fundamental to the process of establishing a WebRTC connection left out of the specification? The answer is simple: since the two devices have no way to directly contact each other, and the specification can't predict every possible use case for WebRTC, it makes more sense to let the developer select an appropriate networking technology and messaging protocol.

In particular, if a developer already has a method in place for connecting two devices, it doesn't make sense for them to have to use another one, defined by the specification, just for WebRTC. Since WebRTC doesn't live in a vacuum, there is likely other connectivity in play, so it makes sense to avoid having to add additional connection channels for signaling if an existing one can be used.

In order to exchange signaling information, you can choose to send JSON objects back and forth over a WebSocket connection, or you could use XMPP or SIP over an appropriate channel, or you could use `fetch()` over HTTPS with polling, or any other combination of technologies you can come up with. You could even use email as the signaling channel.

It's also worth noting that the channel for performing signaling doesn't even need to be over the network. One peer can output a data object that can be printed out, physically carried (on foot or by carrier pigeon) to another device, entered into that device, and a response then output by that device to be returned on foot, and so forth, until the WebRTC peer connection is open. It'd be very high latency but it could be done.

#### Information exchanged during signaling

There are three basic types of information that need to be exchanged during signaling:

* Control messages used to set up, open, and close the communication channel, and to handle errors.
* Information needed in order to set up the connection: the IP addressing and port information needed for the peers to be able to talk to one another.
* Media capability negotiation: what codecs and media data formats can the peers understand? These need to be agreed upon before the WebRTC session can begin.

Only once signaling has been successfully completed can the true process of opening the WebRTC peer connection begin.

It's worth noting that the signaling server does not actually need to understand or do anything with the data being exchanged through it by the two peers during signaling. The signaling server is, in essence, a relay: a common point which both sides connect to knowing that their signaling data can be transferred through it. The server doesn't need to react to this information in any way.

#### The signaling process

There's a sequence of things that have to happen in order to make it possible to begin a WebRTC session:

1. Each peer creates an `RTCPeerConnection` object representing their end of the WebRTC session.
2. Each peer establishes a handler for `icecandidate` events, which handles sending those candidates to the other peer over the signaling channel.
3. Each peer establishes a handler for `track` event, which is received when the remote peer adds a track to the stream. This code should connect the tracks to its consumer, such as a `<video>` element.
4. The caller creates and shares with the receiving peer a unique identifier or token of some kind so that the call between them can be identified by the code on the signaling server. The exact contents and form of this identifier is up to you.
5. Each peer connects to an agreed-upon signaling server, such as a WebSocket server they both know how to exchange messages with.
6. Each peer tells the signaling server that they want to join the same WebRTC session (identified by the token established in step 4).
7. ***descriptions, candidates, etc. — more coming up***
ICE restart
-----------

Sometimes, during the lifetime of a WebRTC session, network conditions change. One of the users might transition from a cellular to a Wi-Fi network, or the network might become congested, for example. When this happens, the ICE agent may choose to perform **ICE restart**. This is a process by which the network connection is renegotiated, exactly the same way the initial ICE negotiation is performed, with one exception: media continues to flow across the original network connection until the new one is up and running. Then media shifts to the new network connection and the old one is closed.

**Note:** Different browsers support ICE restart under different sets of conditions. Not all browsers will perform ICE restart due to network congestion, for example.

If you need to change the configuration of the connection in some way (such as changing to a different set of ICE servers), you can do so before restarting ICE by calling `RTCPeerConnection.setConfiguration()` with an updated configuration object before restarting ICE.

To explicitly trigger ICE restart, start the renegotiation process by calling `RTCPeerConnection.createOffer()`, specifying the `iceRestart` option with a value of `true`. Then handle the connection process from then on just like you normally would. This generates new values for the ICE username fragment (ufrag) and password, which will be used by the renegotiation process and the resulting connection.

The answerer side of the connection will automatically begin ICE restart when new values are detected for the ICE ufrag and ICE password.

Transmission
------------

Reception
---------


Signaling and video calling - Web APIs

Signaling and video calling
===========================

WebRTC allows real-time, peer-to-peer, media exchange between two devices. A connection is established through a discovery and negotiation process called **signaling**. This tutorial will guide you through building a two-way video-call.

WebRTC is a fully peer-to-peer technology for the real-time exchange of audio, video, and data, with one central caveat. A form of discovery and media format negotiation must take place, as discussed elsewhere, in order for two devices on different networks to locate one another. This process is called **signaling** and involves both devices connecting to a third, mutually agreed-upon server. Through this third server, the two devices can locate one another, and exchange negotiation messages.

In this article, we will further enhance the WebSocket chat first created as part of our WebSocket documentation (this article link is forthcoming; it isn't actually online yet) to support opening a two-way video call between users. You can try out this example on Glitch, and you can remix the example to experiment with it as well. You can also look at the full project on GitHub.

**Note:** If you try out the example on Glitch, please note that any changes made to the code will immediately reset any connections. In addition, there is a short timeout period; the Glitch instance is for quick experiments and testing only.

The signaling server
--------------------

Establishing a WebRTC connection between two devices requires the use of a **signaling server** to resolve how to connect them over the internet. A signaling server's job is to serve as an intermediary to let two peers find and establish a connection while minimizing exposure of potentially private information as much as possible. How do we create this server and how does the signaling process actually work?

First we need the signaling server itself. WebRTC doesn't specify a transport mechanism for the signaling information. You can use anything you like, from WebSocket to `fetch()` to carrier pigeons to exchange the signaling information between the two peers.

It's important to note that the server doesn't need to understand or interpret the signaling data content. Although it's SDP, even this doesn't matter so much: the content of the message going through the signaling server is, in effect, a black box. What does matter is when the ICE subsystem instructs you to send signaling data to the other peer, you do so, and the other peer knows how to receive this information and deliver it to its own ICE subsystem. All you have to do is channel the information back and forth. The contents don't matter at all to the signaling server.

### Readying the chat server for signaling

Our chat server uses the WebSocket API to send information as JSON strings between each client and the server. The server supports several message types to handle tasks, such as registering new users, setting usernames, and sending public chat messages.

To allow the server to support signaling and ICE negotiation, we need to update the code. We'll have to allow directing messages to one specific user instead of broadcasting to all connected users, and ensure unrecognized message types are passed through and delivered, without the server needing to know what they are. This lets us send signaling messages using this same server, instead of needing a separate server.

Let's take a look at changes we need to make to the chat server to support WebRTC signaling. This is in the file `chatserver.js`.

First up is the addition of the function `sendToOneUser()`. As the name suggests, this sends a stringified JSON message to a particular username.

```js
function sendToOneUser(target, msgString) {
  connectionArray.find((conn) => conn.username === target).send(msgString);
}
```

This function iterates over the list of connected users until it finds one matching the specified username, then sends the message to that user. The parameter `msgString` is a stringified JSON object. We could have made it receive our original message object, but in this example it's more efficient this way. Since the message has already been stringified, we can send it with no further processing. Each entry in `connectionArray` is a `WebSocket` object, so we can just call its `send()` method directly.

Our original chat demo didn't support sending messages to a specific user. The next task is to update the main WebSocket message handler to support doing so. This involves a change near the end of the `"connection"` message handler:

```js
if (sendToClients) {
  const msgString = JSON.stringify(msg);

  if (msg.target && msg.target.length !== 0) {
    sendToOneUser(msg.target, msgString);
  } else {
    for (const connection of connectionArray) {
      connection.send(msgString);
    }
  }
}
```

This code now looks at the pending message to see if it has a `target` property. If that property is present, it specifies the username of the client to which the message is to be sent, and we call `sendToOneUser()` to send the message to them. Otherwise, the message is broadcast to all users by iterating over the connection list, sending the message to each user.

As the existing code allows the sending of arbitrary message types, no additional changes are required. Our clients can now send messages of unknown types to any specific user, letting them send signaling messages back and forth as desired.

That's all we need to change on the server side of the equation. Now let's consider the signaling protocol we will implement.

### Designing the signaling protocol

Now that we've built a mechanism for exchanging messages, we need a protocol defining how those messages will look. This can be done in a number of ways; what's demonstrated here is just one possible way to structure signaling messages.

This example's server uses stringified JSON objects to communicate with its clients. This means our signaling messages will be in JSON format, with contents which specify what kind of messages they are as well as any additional information needed in order to handle the messages properly.

#### Exchanging session descriptions

When starting the signaling process, an **offer** is created by the user initiating the call. This offer includes a session description, in SDP format, and needs to be delivered to the receiving user, which we'll call the **callee**. The callee responds to the offer with an **answer** message, also containing an SDP description. Our signaling server will use WebSocket to transmit offer messages with the type `"video-offer"`, and answer messages with the type `"video-answer"`. These messages have the following fields:

`type`

The message type; either `"video-offer"` or `"video-answer"`.

`name`

The sender's username.

`target`

The username of the person to receive the description (if the caller is sending the message, this specifies the callee, and vice versa).

`sdp`

The SDP (Session Description Protocol) string describing the local end of the connection from the perspective of the sender (or the remote end of the connection from the receiver's point of view).

At this point, the two participants know which codecs and codec parameters are to be used for this call. They still don't know how to transmit the media data itself though. This is where Interactive Connectivity Establishment (ICE) comes in.

### Exchanging ICE candidates

Two peers need to exchange ICE candidates to negotiate the actual connection between them. Every ICE candidate describes a method that the sending peer is able to use to communicate. Each peer sends candidates in the order they're discovered, and keeps sending candidates until it runs out of suggestions, even if media has already started streaming.

An `icecandidate` event is sent to the `RTCPeerConnection` to complete the process of adding a local description using `pc.setLocalDescription(offer)`.

Once the two peers agree upon a mutually-compatible candidate, that candidate's SDP is used by each peer to construct and open a connection, through which media then begins to flow. If they later agree on a better (usually higher-performance) candidate, the stream may change formats as needed.

Though not currently supported, a candidate received after media is already flowing could theoretically also be used to downgrade to a lower-bandwidth connection if needed.

Each ICE candidate is sent to the other peer by sending a JSON message of type `"new-ice-candidate"` over the signaling server to the remote peer. Each candidate message include these fields:

`type`

The message type: `"new-ice-candidate"`.

`target`

The username of the person with whom negotiation is underway; the server will direct the message to this user only.

`candidate`

The SDP candidate string, describing the proposed connection method. You typically don't need to look at the contents of this string. All your code needs to do is route it through to the remote peer using the signaling server.

Each ICE message suggests a communication protocol (TCP or UDP), IP address, port number, connection type (for example, whether the specified IP is the peer itself or a relay server), along with other information needed to link the two computers together. This includes NAT or other networking complexity.

**Note:** The important thing to note is this: the only thing your code is responsible for during ICE negotiation is accepting outgoing candidates from the ICE layer and sending them across the signaling connection to the other peer when your `onicecandidate` handler is executed, and receiving ICE candidate messages from the signaling server (when the `"new-ice-candidate"` message is received) and delivering them to your ICE layer by calling `RTCPeerConnection.addIceCandidate()`. That's it.

The contents of the SDP are irrelevant to you in essentially all cases. Avoid the temptation to try to make it more complicated than that until you really know what you're doing. That way lies madness.

All your signaling server now needs to do is send the messages it's asked to. Your workflow may also demand login/authentication functionality, but such details will vary.

**Note:** The `onicecandidate` Event and `createAnswer()` Promise are both async calls which are handled separately. Be sure that your signaling does not change order! For example `addIceCandidate()` with the server's ice candidates must be called after setting the answer with `setRemoteDescription()`.

### Signaling transaction flow

The signaling process involves this exchange of messages between two peers using an intermediary, the signaling server. The exact process will vary, of course, but in general there are a few key points at which signaling messages get handled:

The signaling process involves this exchange of messages among a number of points:

* Each user's client running within a web browser
* Each user's web browser
* The signaling server
* The web server hosting the chat service

Imagine that Naomi and Priya are engaged in a discussion using the chat software, and Naomi decides to open a video call between the two. Here's the expected sequence of events:

![Diagram of the signaling process](/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling/webrtc_-_signaling_diagram.svg)

We'll see this detailed more over the course of this article.

### ICE candidate exchange process

When each peer's ICE layer begins to send candidates, it enters into an exchange among the various points in the chain that looks like this:

![Diagram of ICE candidate exchange process](/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling/webrtc_-_ice_candidate_exchange.svg)

Each side sends candidates to the other as it receives them from their local ICE layer; there is no taking turns or batching of candidates. As soon as the two peers agree upon one candidate that they can both use to exchange the media, media begins to flow. Each peer continues to send candidates until it runs out of options, even after the media has already begun to flow. This is done in hopes of identifying even better options than the one initially selected.

If conditions change (for example, the network connection deteriorates), one or both peers might suggest switching to a lower-bandwidth media resolution, or to an alternative codec. That triggers a new exchange of candidates, after which another media format and/or codec change may take place. In the guide Codecs used by WebRTC you can learn more about the codecs which WebRTC requires browsers to support, which additional codecs are supported by which browsers, and how to choose the best codecs to use.

Optionally, see RFC 8445: Interactive Connectivity Establishment, section 2.3 ("Negotiating Candidate Pairs and Concluding ICE") if you want greater understanding of how this process is completed inside the ICE layer. You should note that candidates are exchanged and media starts to flow as soon as the ICE layer is satisfied. This is all taken care of behind the scenes. Our role is to send the candidates, back and forth, through the signaling server.

The client application
----------------------

The core to any signaling process is its message handling. It's not necessary to use WebSockets for signaling, but it is a common solution. You should, of course, select a mechanism for exchanging signaling information that is appropriate for your application.

Let's update the chat client to support video calling.

### Updating the HTML

The HTML for our client needs a location for video to be presented. This requires video elements, and a button to hang up the call:

```html
<div class="flexChild" id="camera-container">
  <div class="camera-box">
    <video id="received\_video" autoplay></video>
    <video id="local\_video" autoplay muted></video>
    <button id="hangup-button" onclick="hangUpCall();" disabled>Hang Up</button>
  </div>
</div>
```

The page structure defined here is using `<div>` elements, giving us full control over the page layout by enabling the use of CSS. We'll skip layout detail in this guide, but take a look at the CSS on GitHub to see how we handled it. Take note of the two `<video>` elements, one for your self-view, one for the connection, and the `<button>` element.

The `<video>` element with the `id` "`received_video`" will present video received from the connected user. We specify the `autoplay` attribute, ensuring once the video starts arriving, it immediately plays. This removes any need to explicitly handle playback in our code. The "`local_video`" `<video>` element presents a preview of the user's camera; specifying the `muted` attribute, as we don't need to hear local audio in this preview panel.

Finally, the "`hangup-button`" `<button>`, to disconnect from a call, is defined and configured to start disabled (setting this as our default for when no call is connected) and apply the function `hangUpCall()` on click. This function's role is to close the call, and send a signalling server notification to the other peer, requesting it also close.

### The JavaScript code

We'll divide this code into functional areas to more easily describe how it works. The main body of this code is found in the `connect()` function: it opens up a `WebSocket` server on port 6503, and establishes a handler to receive messages in JSON object format. This code generally handles text chat messages as it did previously.

#### Sending messages to the signaling server

Throughout our code, we call `sendToServer()` in order to send messages to the signaling server. This function uses the WebSocket connection to do its work:

```js
function sendToServer(msg) {
  const msgJSON = JSON.stringify(msg);

  connection.send(msgJSON);
}
```

The message object passed into this function is converted into a JSON string by calling `JSON.stringify()`, then we call the WebSocket connection's `send()` function to transmit the message to the server.

#### UI to start a call

The code which handles the `"userlist"` message calls `handleUserlistMsg()`. Here we set up the handler for each connected user in the user list displayed to the left of the chat panel. This function receives a message object whose `users` property is an array of strings specifying the user names of every connected user.

```js
function handleUserlistMsg(msg) {
  const listElem = document.querySelector(".userlistbox");

  while (listElem.firstChild) {
    listElem.removeChild(listElem.firstChild);
  }

  msg.users.forEach((username) => {
    const item = document.createElement("li");
    item.appendChild(document.createTextNode(username));
    item.addEventListener("click", invite, false);

    listElem.appendChild(item);
  });
}
```

After getting a reference to the `<ul>` which contains the list of user names into the variable `listElem`, we empty the list by removing each of its child elements.

**Note:** Obviously, it would be more efficient to update the list by adding and removing individual users instead of rebuilding the whole list every time it changes, but this is good enough for the purposes of this example.

Then we iterate over the array of user names using `forEach()`. For each name, we create a new `<li>` element, then create a new text node containing the user name using `createTextNode()`. That text node is added as a child of the `<li>` element. Next, we set a handler for the `click` event on the list item, that clicking on a user name calls our `invite()` method, which we'll look at in the next section.

Finally, we append the new item to the `<ul>` that contains all of the user names.

#### Starting a call

When the user clicks on a username they want to call, the `invite()` function is invoked as the event handler for that `click` event:

```js
const mediaConstraints = {
  audio: true, // We want an audio track
  video: true, // And we want a video track
};

function invite(evt) {
  if (myPeerConnection) {
    alert("You can't start a call because you already have one open!");
  } else {
    const clickedUsername = evt.target.textContent;

    if (clickedUsername === myUsername) {
      alert(
        "I'm afraid I can't let you talk to yourself. That would be weird.",
      );
      return;
    }

    targetUsername = clickedUsername;
    createPeerConnection();

    navigator.mediaDevices
      .getUserMedia(mediaConstraints)
      .then((localStream) => {
        document.getElementById("local\_video").srcObject = localStream;
        localStream
          .getTracks()
          .forEach((track) => myPeerConnection.addTrack(track, localStream));
      })
      .catch(handleGetUserMediaError);
  }
}
```

This begins with a basic sanity check: is the user already connected? If there's already a `RTCPeerConnection`, they obviously can't make a call. Then the name of the user that was clicked upon is obtained from the event target's `textContent` property, and we check to be sure that it's not the same user that's trying to start the call.

Then we copy the name of the user we're calling into the variable `targetUsername` and call `createPeerConnection()`, a function which will create and do basic configuration of the `RTCPeerConnection`.

Once the `RTCPeerConnection` has been created, we request access to the user's camera and microphone by calling `MediaDevices.getUserMedia()`, which is exposed to us through the `MediaDevices.getUserMedia` property. When this succeeds, fulfilling the returned promise, our `then` handler is executed. It receives, as input, a `MediaStream` object representing the stream with audio from the user's microphone and video from their webcam.

**Note:** We could restrict the set of permitted media inputs to a specific device or set of devices by calling `navigator.mediaDevices.enumerateDevices()` to get a list of devices, filtering the resulting list based on our desired criteria, then using the selected devices' `deviceId` values in the `deviceId` field of the `mediaConstraints` object passed into `getUserMedia()`. In practice, this is rarely if ever necessary, since most of that work is done for you by `getUserMedia()`.

We attach the incoming stream to the local preview `<video>` element by setting the element's `srcObject` property. Since the element is configured to automatically play incoming video, the stream begins playing in our local preview box.

We then iterate over the tracks in the stream, calling `addTrack()` to add each track to the `RTCPeerConnection`. Even though the connection is not fully established yet, you can begin sending data when you feel it's appropriate to do so. Media received before the ICE negotiation is completed may be used to help ICE decide upon the best connectivity approach to take, thus aiding in the negotiation process.

Note that for native apps, such as a phone application, you should not begin sending until the connection has been accepted at both ends, at a minimum, to avoid inadvertently sending video and/or audio data when the user isn't prepared for it.

As soon as media is attached to the `RTCPeerConnection`, a `negotiationneeded` event is triggered at the connection, so that ICE negotiation can be started.

If an error occurs while trying to get the local media stream, our catch clause calls `handleGetUserMediaError()`, which displays an appropriate error to the user as required.

#### Handling getUserMedia() errors

If the promise returned by `getUserMedia()` concludes in a failure, our `handleGetUserMediaError()` function performs.

```js
function handleGetUserMediaError(e) {
  switch (e.name) {
    case "NotFoundError":
      alert(
        "Unable to open your call because no camera and/or microphone" +
          "were found.",
      );
      break;
    case "SecurityError":
    case "PermissionDeniedError":
      // Do nothing; this is the same as the user canceling the call.
      break;
    default:
      alert(`Error opening your camera and/or microphone: ${e.message}`);
      break;
  }

  closeVideoCall();
}
```

An error message is displayed in all cases but one. In this example, we ignore `"SecurityError"` and `"PermissionDeniedError"` results, treating refusal to grant permission to use the media hardware the same as the user canceling the call.

Regardless of why an attempt to get the stream fails, we call our `closeVideoCall()` function to shut down the `RTCPeerConnection`, and release any resources already allocated by the process of attempting the call. This code is designed to safely handle partially-started calls.

#### Creating the peer connection

The `createPeerConnection()` function is used by both the caller and the callee to construct their `RTCPeerConnection` objects, their respective ends of the WebRTC connection. It's invoked by `invite()` when the caller tries to start a call, and by `handleVideoOfferMsg()` when the callee receives an offer message from the caller.

```js
function createPeerConnection() {
  myPeerConnection = new RTCPeerConnection({
    iceServers: [
      // Information about ICE servers - Use your own!
      {
        urls: "stun:stun.stunprotocol.org",
      },
    ],
  });

  myPeerConnection.onicecandidate = handleICECandidateEvent;
  myPeerConnection.ontrack = handleTrackEvent;
  myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
  myPeerConnection.onremovetrack = handleRemoveTrackEvent;
  myPeerConnection.oniceconnectionstatechange =
    handleICEConnectionStateChangeEvent;
  myPeerConnection.onicegatheringstatechange =
    handleICEGatheringStateChangeEvent;
  myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
}
```

When using the `RTCPeerConnection()` constructor, we will specify an object providing configuration parameters for the connection. We use only one of these in this example: `iceServers`. This is an array of objects describing STUN and/or TURN servers for the ICE layer to use when attempting to establish a route between the caller and the callee. These servers are used to determine the best route and protocols to use when communicating between the peers, even if they're behind a firewall or using NAT.

**Note:** You should always use STUN/TURN servers which you own, or which you have specific authorization to use. This example is using a known public STUN server but abusing these is bad form.

Each object in `iceServers` contains at least a `urls` field providing URLs at which the specified server can be reached. It may also provide `username` and `credential` values to allow authentication to take place, if needed.

After creating the `RTCPeerConnection`, we set up handlers for the events that matter to us.

The first three of these event handlers are required; you have to handle them to do anything involving streamed media with WebRTC. The rest aren't strictly required but can be useful, and we'll explore them. There are a few other events available that we're not using in this example, as well. Here's a summary of each of the event handlers we will be implementing:

`onicecandidate`

The local ICE layer calls your `icecandidate` event handler, when it needs you to transmit an ICE candidate to the other peer, through your signaling server. See Sending ICE candidates for more information and to see the code for this example.

`ontrack`

This handler for the `track` event is called by the local WebRTC layer when a track is added to the connection. This lets you connect the incoming media to an element to display it, for example. See Receiving new streams for details.

`onnegotiationneeded`

This function is called whenever the WebRTC infrastructure needs you to start the session negotiation process anew. Its job is to create and send an offer, to the callee, asking it to connect with us. See Starting negotiation to see how we handle this.

`onremovetrack`

This counterpart to `ontrack` is called to handle the `removetrack` event; it's sent to the `RTCPeerConnection` when the remote peer removes a track from the media being sent. See Handling the removal of tracks.

`oniceconnectionstatechange`

The `iceconnectionstatechange` event is sent by the ICE layer to let you know about changes to the state of the ICE connection. This can help you know when the connection has failed, or been lost. We'll look at the code for this example in ICE connection state below.

`onicegatheringstatechange`

The ICE layer sends you the `icegatheringstatechange` event, when the ICE agent's process of collecting candidates shifts, from one state to another (such as starting to gather candidates or completing negotiation). See ICE gathering state below.

`onsignalingstatechange`

The WebRTC infrastructure sends you the `signalingstatechange` message when the state of the signaling process changes (or if the connection to the signaling server changes). See Signaling state to see our code.

#### Starting negotiation

Once the caller has created its `RTCPeerConnection`, created a media stream, and added its tracks to the connection as shown in Starting a call, the browser will deliver a `negotiationneeded` event to the `RTCPeerConnection` to indicate that it's ready to begin negotiation with the other peer. Here's our code for handling the `negotiationneeded` event:

```js
function handleNegotiationNeededEvent() {
  myPeerConnection
    .createOffer()
    .then((offer) => myPeerConnection.setLocalDescription(offer))
    .then(() => {
      sendToServer({
        name: myUsername,
        target: targetUsername,
        type: "video-offer",
        sdp: myPeerConnection.localDescription,
      });
    })
    .catch(reportError);
}
```

To start the negotiation process, we need to create and send an SDP offer to the peer we want to connect to. This offer includes a list of supported configurations for the connection, including information about the media stream we've added to the connection locally (that is, the video we want to send to the other end of the call), and any ICE candidates gathered by the ICE layer already. We create this offer by calling `myPeerConnection.createOffer()`.

When `createOffer()` succeeds (fulfilling the promise), we pass the created offer information into `myPeerConnection.setLocalDescription()`, which configures the connection and media configuration state for the caller's end of the connection.

**Note:** Technically speaking, the string returned by `createOffer()` is an RFC 3264 offer.

We know the description is valid, and has been set, when the promise returned by `setLocalDescription()` is fulfilled. This is when we send our offer to the other peer by creating a new `"video-offer"` message containing the local description (now the same as the offer), then sending it through our signaling server to the callee. The offer has the following members:

`type`

The message type: `"video-offer"`.

`name`

The caller's username.

`target`

The name of the user we wish to call.

`sdp`

The SDP string describing the offer.

If an error occurs, either in the initial `createOffer()` or in any of the fulfillment handlers that follow, an error is reported by invoking our `reportError()` function.

Once `setLocalDescription()`'s fulfillment handler has run, the ICE agent begins sending `icecandidate` events to the `RTCPeerConnection`, one for each potential configuration it discovers. Our handler for the `icecandidate` event is responsible for transmitting the candidates to the other peer.

#### Session negotiation

Now that we've started negotiation with the other peer and have transmitted an offer, let's look at what happens on the callee's side of the connection for a while. The callee receives the offer and calls `handleVideoOfferMsg()` function to process it. Let's see how the callee handles the `"video-offer"` message.

##### Handling the invitation

When the offer arrives, the callee's `handleVideoOfferMsg()` function is called with the `"video-offer"` message that was received. This function needs to do two things. First, it needs to create its own `RTCPeerConnection` and add the tracks containing the audio and video from its microphone and webcam to that. Second, it needs to process the received offer, constructing and sending its answer.

```js
function handleVideoOfferMsg(msg) {
  let localStream = null;

  targetUsername = msg.name;
  createPeerConnection();

  const desc = new RTCSessionDescription(msg.sdp);

  myPeerConnection
    .setRemoteDescription(desc)
    .then(() => navigator.mediaDevices.getUserMedia(mediaConstraints))
    .then((stream) => {
      localStream = stream;
      document.getElementById("local\_video").srcObject = localStream;

      localStream
        .getTracks()
        .forEach((track) => myPeerConnection.addTrack(track, localStream));
    })
    .then(() => myPeerConnection.createAnswer())
    .then((answer) => myPeerConnection.setLocalDescription(answer))
    .then(() => {
      const msg = {
        name: myUsername,
        target: targetUsername,
        type: "video-answer",
        sdp: myPeerConnection.localDescription,
      };

      sendToServer(msg);
    })
    .catch(handleGetUserMediaError);
}
```

This code is very similar to what we did in the `invite()` function back in Starting a call. It starts by creating and configuring an `RTCPeerConnection` using our `createPeerConnection()` function. Then it takes the SDP offer from the received `"video-offer"` message and uses it to create a new `RTCSessionDescription` object representing the caller's session description.

That session description is then passed into `myPeerConnection.setRemoteDescription()`. This establishes the received offer as the description of the remote (caller's) end of the connection. If this is successful, the promise fulfillment handler (in the `then()` clause) starts the process of getting access to the callee's camera and microphone using `getUserMedia()`, adding the tracks to the connection, and so forth, as we saw previously in `invite()`.

Once the answer has been created using `myPeerConnection.createAnswer()`, the description of the local end of the connection is set to the answer's SDP by calling `myPeerConnection.setLocalDescription()`, then the answer is transmitted through the signaling server to the caller to let them know what the answer is.

Any errors are caught and passed to `handleGetUserMediaError()`, described in Handling getUserMedia() errors.

**Note:** As is the case with the caller, once the `setLocalDescription()` fulfillment handler has run, the browser begins firing `icecandidate` events that the callee must handle, one for each candidate that needs to be transmitted to the remote peer.

 Finally, the caller handles the answer message it received by creating a new `RTCSessionDescription` object representing the callee's session description and passing it into
 `myPeerConnection.setRemoteDescription()`.

```js
function handleVideoAnswerMsg(msg) {
  const desc = new RTCSessionDescription(msg.sdp);
  myPeerConnection.setRemoteDescription(desc).catch(reportError);
}
```

##### Sending ICE candidates

The ICE negotiation process involves each peer sending candidates to the other, repeatedly, until it runs out of potential ways it can support the `RTCPeerConnection`'s media transport needs. Since ICE doesn't know about your signaling server, your code handles transmission of each candidate in your handler for the `icecandidate` event.

Your `onicecandidate` handler receives an event whose `candidate` property is the SDP describing the candidate (or is `null` to indicate that the ICE layer has run out of potential configurations to suggest). The contents of `candidate` are what you need to transmit using your signaling server. Here's our example's implementation:

```js
function handleICECandidateEvent(event) {
  if (event.candidate) {
    sendToServer({
      type: "new-ice-candidate",
      target: targetUsername,
      candidate: event.candidate,
    });
  }
}
```

This builds an object containing the candidate, then sends it to the other peer using the `sendToServer()` function previously described in Sending messages to the signaling server. The message's properties are:

`type`

The message type: `"new-ice-candidate"`.

`target`

The username the ICE candidate needs to be delivered to. This lets the signaling server route the message.

`candidate`

The SDP representing the candidate the ICE layer wants to transmit to the other peer.

The format of this message (as is the case with everything you do when handling signaling) is entirely up to you, depending on your needs; you can provide other information as required.

**Note:** It's important to keep in mind that the `icecandidate` event is **not** sent when ICE candidates arrive from the other end of the call. Instead, they're sent by your own end of the call so that you can take on the job of transmitting the data over whatever channel you choose. This can be confusing when you're new to WebRTC.

##### Receiving ICE candidates

The signaling server delivers each ICE candidate to the destination peer using whatever method it chooses; in our example this is as JSON objects, with a `type` property containing the string `"new-ice-candidate"`. Our `handleNewICECandidateMsg()` function is called by our main WebSocket incoming message code to handle these messages:

```js
function handleNewICECandidateMsg(msg) {
  const candidate = new RTCIceCandidate(msg.candidate);

  myPeerConnection.addIceCandidate(candidate).catch(reportError);
}
```

This function constructs an `RTCIceCandidate` object by passing the received SDP into its constructor, then delivers the candidate to the ICE layer by passing it into `myPeerConnection.addIceCandidate()`. This hands the fresh ICE candidate to the local ICE layer, and finally, our role in the process of handling this candidate is complete.

Each peer sends to the other peer a candidate for each possible transport configuration that it believes might be viable for the media being exchanged. At some point, the two peers agree that a given candidate is a good choice and they open the connection and begin to share media. It's important to note, however, that ICE negotiation does *not* stop once media is flowing. Instead, candidates may still keep being exchanged after the conversation has begun, either while trying to find a better connection method, or because they were already in transport when the peers successfully established their connection.

In addition, if something happens to cause a change in the streaming scenario, negotiation will begin again, with the `negotiationneeded` event being sent to the `RTCPeerConnection`, and the entire process starts again as described before. This can happen in a variety of situations, including:

* Changes in the network status, such as a bandwidth change, transitioning from Wi-Fi to cellular connectivity, or the like.
* Switching between the front and rear cameras on a phone.
* A change to the configuration of the stream, such as its resolution or frame rate.

##### Receiving new streams

When new tracks are added to the `RTCPeerConnection`— either by calling its `addTrack()` method or because of renegotiation of the stream's format—a `track` event is set to the `RTCPeerConnection` for each track added to the connection. Making use of newly added media requires implementing a handler for the `track` event. A common need is to attach the incoming media to an appropriate HTML element. In our example, we add the track's stream to the `<video>` element that displays the incoming video:

```js
function handleTrackEvent(event) {
  document.getElementById("received\_video").srcObject = event.streams[0];
  document.getElementById("hangup-button").disabled = false;
}
```

The incoming stream is attached to the `"received_video"` `<video>` element, and the "Hang Up" `<button>` element is enabled so the user can hang up the call.

Once this code has completed, finally the video being sent by the other peer is displayed in the local browser window!

##### Handling the removal of tracks

Your code receives a `removetrack` event when the remote peer removes a track from the connection by calling `RTCPeerConnection.removeTrack()`. Our handler for `"removetrack"` is:

```js
function handleRemoveTrackEvent(event) {
  const stream = document.getElementById("received\_video").srcObject;
  const trackList = stream.getTracks();

  if (trackList.length === 0) {
    closeVideoCall();
  }
}
```

This code fetches the incoming video `MediaStream` from the `"received_video"` `<video>` element's `srcObject` attribute, then calls the stream's `getTracks()` method to get an array of the stream's tracks.

If the array's length is zero, meaning there are no tracks left in the stream, we end the call by calling `closeVideoCall()`. This cleanly restores our app to a state in which it's ready to start or receive another call. See Ending the call to learn how `closeVideoCall()` works.

#### Ending the call

There are many reasons why calls may end. A call might have completed, with one or both sides having hung up. Perhaps a network failure has occurred, or one user might have quit their browser, or had a system crash. In any case, all good things must come to an end.

##### Hanging up

When the user clicks the "Hang Up" button to end the call, the `hangUpCall()` function is called:

```js
function hangUpCall() {
  closeVideoCall();
  sendToServer({
    name: myUsername,
    target: targetUsername,
    type: "hang-up",
  });
}
```

`hangUpCall()` executes `closeVideoCall()` to shut down and reset the connection and release resources. It then builds a `"hang-up"` message and sends it to the other end of the call to tell the other peer to neatly shut itself down.

##### Ending the call

The `closeVideoCall()` function, shown below, is responsible for stopping the streams, cleaning up, and disposing of the `RTCPeerConnection` object:

```js
function closeVideoCall() {
  const remoteVideo = document.getElementById("received\_video");
  const localVideo = document.getElementById("local\_video");

  if (myPeerConnection) {
    myPeerConnection.ontrack = null;
    myPeerConnection.onremovetrack = null;
    myPeerConnection.onremovestream = null;
    myPeerConnection.onicecandidate = null;
    myPeerConnection.oniceconnectionstatechange = null;
    myPeerConnection.onsignalingstatechange = null;
    myPeerConnection.onicegatheringstatechange = null;
    myPeerConnection.onnegotiationneeded = null;

    if (remoteVideo.srcObject) {
      remoteVideo.srcObject.getTracks().forEach((track) => track.stop());
    }

    if (localVideo.srcObject) {
      localVideo.srcObject.getTracks().forEach((track) => track.stop());
    }

    myPeerConnection.close();
    myPeerConnection = null;
  }

  remoteVideo.removeAttribute("src");
  remoteVideo.removeAttribute("srcObject");
  localVideo.removeAttribute("src");
  localVideo.removeAttribute("srcObject");

  document.getElementById("hangup-button").disabled = true;
  targetUsername = null;
}
```

After pulling references to the two `<video>` elements, we check if a WebRTC connection exists; if it does, we proceed to disconnect and close the call:

1. All of the event handlers are removed. This prevents stray event handlers from being triggered while the connection is in the process of closing, potentially causing errors.
2. For both remote and local video streams, we iterate over each track, calling the `MediaStreamTrack.stop()` method to close each one.
3. Close the `RTCPeerConnection` by calling `myPeerConnection.close()`.
4. Set `myPeerConnection` to `null`, ensuring our code learns there's no ongoing call; this is useful when the user clicks a name in the user list.

Then for both the incoming and outgoing `<video>` elements, we remove their `src` and `srcObject` attributes using their `removeAttribute()` methods. This completes the disassociation of the streams from the video elements.

Finally, we set the `disabled` property to `true` on the "Hang Up" button, making it unclickable while there is no call underway; then we set `targetUsername` to `null` since we're no longer talking to anyone. This allows the user to call another user, or to receive an incoming call.

#### Dealing with state changes

There are a number of additional events for which you can set listeners to notify your code of a variety of state changes. We use three of them: `iceconnectionstatechange`, `icegatheringstatechange`, and `signalingstatechange`.

##### ICE connection state

`iceconnectionstatechange` events are sent to the `RTCPeerConnection` by the ICE layer when the connection state changes (such as when the call is terminated from the other end).

```js
function handleICEConnectionStateChangeEvent(event) {
  switch (myPeerConnection.iceConnectionState) {
    case "closed":
    case "failed":
      closeVideoCall();
      break;
  }
}
```

Here, we apply our `closeVideoCall()` function when the ICE connection state changes to `"closed"` or `"failed"`. This handles shutting down our end of the connection so that we're ready start or accept a call once again.

**Note:** We don't watch the `disconnected` signaling state here as it can indicate temporary issues and may go back to a `connected` state after some time. Watching it would close the video call on any temporary network issue.

##### ICE signaling state

Similarly, we watch for `signalingstatechange` events. If the signaling state changes to `closed`, we likewise close the call out.

```js
function handleSignalingStateChangeEvent(event) {
  switch (myPeerConnection.signalingState) {
    case "closed":
      closeVideoCall();
      break;
  }
}
```

**Note:** The `closed` signaling state has been deprecated in favor of the `closed` `iceConnectionState`. We are watching for it here to add a bit of backward compatibility.

##### ICE gathering state

`icegatheringstatechange` events are used to let you know when the ICE candidate gathering process state changes. Our example doesn't use this for anything, but it can be useful to watch these events for debugging purposes, as well as to detect when candidate collection has finished.

```js
function handleICEGatheringStateChangeEvent(event) {
  // Our sample just logs information to console here,
  // but you can do whatever you need.
}
```

Next steps
----------

You can now try out this example on Glitch to see it in action. Open the Web console on both devices and look at the logged output—although you don't see it in the code as shown above, the code on the server (and on GitHub) has a lot of console output so you can see the signaling and connection processes at work.

Another obvious improvement would be to add a "ringing" feature, so that instead of just asking the user for permission to use the camera and microphone, a "User X is calling. Would you like to answer?" prompt appears first.

See also
--------

* WebRTC API
* Web media technologies
* Guide to media types and formats on the web
* Media Capture and Streams API
* Media Capabilities API
* MediaStream Recording API
* The Perfect Negotiation pattern


Building an Internet-Connected Phone with PeerJS - Web APIs

Building an Internet-Connected Phone with PeerJS
================================================

* Next

One of WebRTC's main issues is that it is pretty complicated to use and develop with — handling the signalling service and knowing when to call the right endpoint can get confusing. But there is some good news; PeerJS is a WebRTC framework that abstracts away all of the ice and signalling logic so that you can focus on the functionality of your application. There are two parts to PeerJS, the client-side framework and the server.

In this series of articles we will create a simple phone application using PeerJS. We'll be using both the server and the client-side framework, but most of our work will be involved with handling the client-side code.

### Prerequisites

This is an intermediate level tutorial; before attempting it you should already be comfortable with:

* Vanilla JavaScript
* Node
* Express
* HTML

Before you get started, you'll want to make sure you've installed node and Yarn (the instructions in later articles assume Yarn, but you can feel free to use npm or another manager if you'd prefer).

**Note:** If you learn better by following step-by-step code, we've also provided this tutorial in code, which you can use instead.

### Table of Contents

1. Setup
2. Connect Peers
    1. Get Microphone Permission
    2. Showing and hiding HTML
    3. Create a Peer Connection
    4. Creating a Call
    5. Answer a Call
    6. End a Call
3. Deployment and Further Reading
* Next


Establishing a connection: The WebRTC perfect negotiation pattern - Web APIs

Establishing a connection: The WebRTC perfect negotiation pattern
=================================================================

This article introduces WebRTC **perfect negotiation**, describing how it works and why it's the recommended way to negotiate a WebRTC connection between peers, and provides sample code to demonstrate the technique.

Because WebRTC doesn't mandate a specific transport mechanism for signaling during the negotiation of a new peer connection, it's highly flexible. However, despite that flexibility in transport and communication of signaling messages, there's still a recommended design pattern you should follow when possible, known as perfect negotiation.

After the first deployments of WebRTC-capable browsers, it was realized that parts of the negotiation process were more complicated than they needed to be for typical use cases. This was due to a small number of issues with the API and some potential race conditions that needed to be prevented. These issues have since been addressed, letting us simplify our WebRTC negotiation significantly. The perfect negotiation pattern is an example of the ways in which negotiation have improved since the early days of WebRTC.

Perfect negotiation concepts
----------------------------

Perfect negotiation makes it possible to seamlessly and completely separate the negotiation process from the rest of your application's logic. Negotiation is an inherently asymmetric operation: one side needs to serve as the "caller" while the other peer is the "callee." The perfect negotiation pattern smooths this difference away by separating that difference out into independent negotiation logic, so that your application doesn't need to care which end of the connection it is. As far as your application is concerned, it makes no difference whether you're calling out or receiving a call.

The best thing about perfect negotiation is that the same code is used for both the caller and the callee, so there's no repetition or otherwise added levels of negotiation code to write.

Perfect negotiation works by assigning each of the two peers a role to play in the negotiation process that's entirely separate from the WebRTC connection state:

* A **polite** peer, which uses ICE rollback to prevent collisions with incoming offers. A polite peer, essentially, is one which may send out offers, but then responds if an offer arrives from the other peer with "Okay, never mind, drop my offer and I'll consider yours instead."
* An **impolite** peer, which always ignores incoming offers that collide with its own offers. It never apologizes or gives up anything to the polite peer. Any time a collision occurs, the impolite peer wins.

This way, both peers know exactly what should happen if there are collisions between offers that have been sent. Responses to error conditions become far more predictable.

How you determine which peer is polite and which is impolite is generally up to you. It could be as simple as assigning the polite role to the first peer to connect to the signaling server, or you could do something more elaborate like having the peers exchange random numbers and assigning the polite role to the winner. However you make the determination, once these roles are assigned to the two peers, they can then work together to manage signaling in a way that doesn't deadlock and doesn't require a lot of extra code to manage.

An important thing to keep in mind is this: the roles of caller and callee can switch during perfect negotiation. If the polite peer is the caller and it sends an offer but there's a collision with the impolite peer, the polite peer drops its offer and instead replies to the offer it has received from the impolite peer. By doing so, the polite peer has switched from being the caller to the callee!

Implementing perfect negotiation
--------------------------------

Let's take a look at an example that implements the perfect negotiation pattern. The code assumes that there's a `SignalingChannel` class defined that is used to communicate with the signaling server. Your own code, of course, can use any signaling technique you like.

Note that this code is identical for both peers involved in the connection.

### Create the signaling and peer connections

First, the signaling channel needs to be opened and the `RTCPeerConnection` needs to be created. The STUN server listed here is obviously not a real one; you'll need to replace `stun.myserver.tld` with the address of a real STUN server.

```js
const config = {
  iceServers: [{ urls: "stun:stun.mystunserver.tld" }],
};

const signaler = new SignalingChannel();
const pc = new RTCPeerConnection(config);
```

This code also gets the `<video>` elements using the classes "selfview" and "remoteview"; these will contain, respectively, the local user's self-view and the view of the incoming stream from the remote peer.

### Connecting to a remote peer

```js
const constraints = { audio: true, video: true };
const selfVideo = document.querySelector("video.selfview");
const remoteVideo = document.querySelector("video.remoteview");

async function start() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia(constraints);

    for (const track of stream.getTracks()) {
      pc.addTrack(track, stream);
    }
    selfVideo.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}
```

The `start()` function shown above can be called by either of the two end-points that want to talk to one another. It doesn't matter who does it first; the negotiation will just work.

This isn't appreciably different from older WebRTC connection establishment code. The user's camera and microphone are obtained by calling `getUserMedia()`. The resulting media tracks are then added to the `RTCPeerConnection` by passing them into `addTrack()`. Then, finally, the media source for the self-view `<video>` element indicated by the `selfVideo` constant is set to the camera and microphone stream, allowing the local user to see what the other peer sees.

### Handling incoming tracks

We next need to set up a handler for `track` events to handle inbound video and audio tracks that have been negotiated to be received by this peer connection. To do this, we implement the `RTCPeerConnection`'s `ontrack` event handler.

```js
pc.ontrack = ({ track, streams }) => {
  track.onunmute = () => {
    if (remoteVideo.srcObject) {
      return;
    }
    remoteVideo.srcObject = streams[0];
  };
};
```

When the `track` event occurs, this handler executes. Using destructuring, the `RTCTrackEvent`'s `track` and `streams` properties are extracted. The former is either the video track or the audio track being received. The latter is an array of `MediaStream` objects, each representing a stream containing this track (a track may in rare cases belong to multiple streams at once). In our case, this will always contain one stream, at index 0, because we passed one stream into `addTrack()` earlier.

We add an unmute event handler to the track, because the track will become unmuted once it starts receiving packets. We put the remainder of our reception code in there.

If we already have video coming in from the remote peer (which we can see if the remote view's `<video>` element's `srcObject` property already has a value), we do nothing. Otherwise, we set `srcObject` to the stream at index 0 in the `streams` array.

### The perfect negotiation logic

Now we get into the true perfect negotiation logic, which functions entirely independently from the rest of the application.

#### Handling the negotiationneeded event

First, we implement the `RTCPeerConnection` event handler `onnegotiationneeded` to get a local description and send it using the signaling channel to the remote peer.

```js
let makingOffer = false;

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

Note that `setLocalDescription()` without arguments automatically creates and sets the appropriate description based on the current `signalingState`. The set description is either an answer to the most recent offer from the remote peer *or* a freshly-created offer if there's no negotiation underway. Here, it will always be an `offer`, because the negotiationneeded event is only fired in `stable` state.

We set a Boolean variable, `makingOffer` to `true` to mark that we're preparing an offer. To avoid races, we'll use this value later instead of the signaling state to determine whether or not an offer is being processed because the value of `signalingState` changes asynchronously, introducing a glare opportunity.

Once the offer has been created, set and sent (or an error occurs), `makingOffer` gets set back to `false`.

#### Handling incoming ICE candidates

Next, we need to handle the `RTCPeerConnection` event `icecandidate`, which is how the local ICE layer passes candidates to us for delivery to the remote peer over the signaling channel.

```js
pc.onicecandidate = ({ candidate }) => signaler.send({ candidate });
```

This takes the `candidate` member of this ICE event and passes it through to the signaling channel's `send()` method to be sent over the signaling server to the remote peer.

#### Handling incoming messages on the signaling channel

The last piece of the puzzle is code to handle incoming messages from the signaling server. That's implemented here as an `onmessage` event handler on the signaling channel object. This method is invoked each time a message arrives from the signaling server.

```js
let ignoreOffer = false;

signaler.onmessage = async ({ data: { description, candidate } }) => {
  try {
    if (description) {
      const offerCollision =
        description.type === "offer" &&
        (makingOffer || pc.signalingState !== "stable");

      ignoreOffer = !polite && offerCollision;
      if (ignoreOffer) {
        return;
      }

      await pc.setRemoteDescription(description);
      if (description.type === "offer") {
        await pc.setLocalDescription();
        signaler.send({ description: pc.localDescription });
      }
    } else if (candidate) {
      try {
        await pc.addIceCandidate(candidate);
      } catch (err) {
        if (!ignoreOffer) {
          throw err;
        }
      }
    }
  } catch (err) {
    console.error(err);
  }
};
```

Upon receiving an incoming message from the `SignalingChannel` through its `onmessage` event handler, the received JSON object is destructured to obtain the `description` or `candidate` found within. If the incoming message has a `description`, it's either an offer or an answer sent by the other peer.

If, on the other hand, the message has a `candidate`, it's an ICE candidate received from the remote peer as part of trickle ICE. The candidate is destined to be delivered to the local ICE layer by passing it into `addIceCandidate()`.

##### On receiving a description

If we received a `description`, we prepare to respond to the incoming offer or answer. First, we check to make sure we're in a state in which we can accept an offer. If the connection's signaling state isn't `stable` or if our end of the connection has started the process of making its own offer, then we need to look out for offer collision.

If we're the impolite peer, and we're receiving a colliding offer, we return without setting the description, and instead set `ignoreOffer` to `true` to ensure we also ignore all candidates the other side may be sending us on the signaling channel belonging to this offer. Doing so avoids error noise since we never informed our side about this offer.

If we're the polite peer, and we're receiving a colliding offer, we don't need to do anything special, because our existing offer will automatically be rolled back in the next step.

Having ensured that we want to accept the offer, we set the remote description to the incoming offer by calling `setRemoteDescription()`. This lets WebRTC know what the proposed configuration of the other peer is. If we're the polite peer, we will drop our offer and accept the new one.

If the newly-set remote description is an offer, we ask WebRTC to select an appropriate local configuration by calling the `RTCPeerConnection` method `setLocalDescription()` without parameters. This causes `setLocalDescription()` to automatically generate an appropriate answer in response to the received offer. Then we send the answer through the signaling channel back to the first peer.

##### On receiving an ICE candidate

On the other hand, if the received message contains an ICE candidate, we deliver it to the local ICE layer by calling the `RTCPeerConnection` method `addIceCandidate()`. If an error occurs and we've ignored the most recent offer, we also ignore any error that may occur when trying to add the candidate.

Making negotiation perfect
--------------------------

If you're curious what makes perfect negotiation *so perfect*, this section is for you. Here, we'll look at each change made to the WebRTC API and to best practice recommendations to make perfect negotiation possible.

### Glare-free setLocalDescription()

In the past, the `negotiationneeded` event was easily handled in a way that was susceptible to glare—that is, it was prone to collisions, where both peers could wind up attempting to make an offer at the same time, leading to one or the other peers getting an error and aborting the connection attempt.

#### The old way

Consider this `onnegotiationneeded` event handler:

```js
pc.onnegotiationneeded = async () => {
  try {
    await pc.setLocalDescription(await pc.createOffer());
    signaler.send({ description: pc.localDescription });
  } catch (err) {
    console.error(err);
  }
};
```

Because the `createOffer()` method is asynchronous and takes some time to complete, there's time in which the remote peer might attempt to send an offer of its own, causing us to leave the `stable` state and enter the `have-remote-offer` state, which means we are now waiting for a response to the offer. But once it receives the offer we just sent, so is the remote peer. This leaves both peers in a state in which the connection attempt cannot be completed.

#### Perfect negotiation with the updated API

As shown in the section Implementing perfect negotiation, we can eliminate this problem by introducing a variable (here called `makingOffer`) which we use to indicate that we are in the process of sending an offer, and making use of the updated `setLocalDescription()` method:

```js
let makingOffer = false;

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

We set `makingOffer` immediately before calling `setLocalDescription()` in order to lock against interfering with sending this offer, and we don't clear it back to `false` until the offer has been sent to the signaling server (or an error has occurred, preventing the offer from being made). This way, we avoid the risk of offers colliding.

### Automatic rollback in setRemoteDescription()

A key component to perfect negotiation is the concept of the polite peer, which always rolls itself back if it receives an offer while itself waiting for an answer to an offer. Previously, triggering rollback involved manually checking for rollback conditions and triggering the rollback manually, by setting the local description to one with the type `rollback`, like this:

```js
await pc.setLocalDescription({ type: "rollback" });
```

Doing so returns the local peer to the `stable` `signalingState` from whichever state it had previously been in. Since a peer can only accept offers when in the `stable` state, the peer has thus rescinded its offer and is ready to receive the offer from the remote (impolite) peer. As we'll see in a moment, there are problems with this approach, however.

#### Perfect negotiation with the old API

Using the previous API to implement incoming negotiation messages during perfect negotiation would look something like this:

```js
signaler.onmessage = async ({ data: { description, candidate } }) => {
  try {
    if (description) {
      if (description.type === "offer" && pc.signalingState !== "stable") {
        if (!polite) {
          return;
        }

        await Promise.all([
          pc.setLocalDescription({ type: "rollback" }),
          pc.setRemoteDescription(description),
        ]);
      } else {
        await pc.setRemoteDescription(description);
      }

      if (description.type === "offer") {
        await pc.setLocalDescription(await pc.createAnswer());
        signaler.send({ description: pc.localDescription });
      }
    } else if (candidate) {
      try {
        await pc.addIceCandidate(candidate);
      } catch (err) {
        if (!ignoreOffer) {
          throw err;
        }
      }
    }
  } catch (err) {
    console.error(err);
  }
};
```

Since rollback works by postponing changes until the next negotiation (which will begin immediately after the current one is finished), the polite peer needs to know when it needs to throw away a received offer if it's currently waiting for a reply to an offer it's already sent.

The code checks to see if the message is an offer, and if so, if the local signaling state isn't `stable`. If it's not stable, *and* the local peer is the polite one, we need to trigger rollback so we can replace the outgoing offer with the new incoming one. And these must both be completed before we can proceed with handling the received offer.

Since there isn't a single "roll back and use this offer instead", performing this change on the polite peer requires two steps, executed in the context of `Promise.all()`, which is used to ensure that both statements execute completely before continuing to handle the received offer. The first statement triggers rollback and the second sets the remote description to the received one, thus completing the process of replacing the previously *sent* offer with the newly *received* offer. The impolite peer has now become the callee instead of the caller.

All other descriptions received from the impolite peer are processed as normal, by passing them into `setRemoteDescription()`.

Finally, we process a received offer by calling `setLocalDescription()` to set our local description to the one returned by `createAnswer()`. Then that gets sent to the polite peer using the signaling channel.

If the incoming message is an ICE candidate rather than an SDP description, it's delivered to the ICE layer by passing it into the `RTCPeerConnection` method `addIceCandidate()`. If an error occurs here and we didn't just discard an offer due to being the impolite peer during a collision, we `throw` the error so the caller can handle it. Otherwise, we drop the error, ignoring it, since it doesn't matter in this context.

#### Perfect negotiation with the updated API

The updated code takes advantage of the fact that you can now call `setLocalDescription()` with no parameters so it just does the right thing for you, as well as the fact that `setRemoteDescription()` automatically rolls back if necessary. This lets us get rid of the need to use a `Promise` to keep the timing in order, since the rollback becomes an essentially atomic part of the `setRemoteDescription()` call.

```js
let ignoreOffer = false;

signaler.onmessage = async ({ data: { description, candidate } }) => {
  try {
    if (description) {
      const offerCollision =
        description.type === "offer" &&
        (makingOffer || pc.signalingState !== "stable");

      ignoreOffer = !polite && offerCollision;
      if (ignoreOffer) {
        return;
      }

      await pc.setRemoteDescription(description);
      if (description.type === "offer") {
        await pc.setLocalDescription();
        signaler.send({ description: pc.localDescription });
      }
    } else if (candidate) {
      try {
        await pc.addIceCandidate(candidate);
      } catch (err) {
        if (!ignoreOffer) {
          throw err;
        }
      }
    }
  } catch (err) {
    console.error(err);
  }
};
```

While the difference in code size is minor, and the complexity isn't reduced much either, the code is much, much more reliable. Let's take a dive into the code to see how it works now.

##### On receiving a description

In the revised code, if the received message is an SDP `description`, we check to see if it arrived while we're attempting to transmit an offer. If the received message is an `offer` *and* the local peer is the impolite peer, *and* a collision is occurring, we ignore the offer because we want to continue to try to use the offer that's already in the process of being sent. That's the impolite peer in action.

In any other case, we'll try instead to handle the incoming message. This begins by setting the remote description to the received `description` by passing it into `setRemoteDescription()`. This works regardless of whether we're handling an offer or an answer since rollback will be performed automatically as needed.

At that point, if the received message is an `offer`, we use `setLocalDescription()` to create and set an appropriate local description, then we send it to the remote peer over the signaling server.

##### On receiving an ICE candidate

On the other hand, if the received message is an ICE candidate—indicated by the JSON object containing a `candidate` member—we deliver it to the local ICE layer by calling the `RTCPeerConnection` method `addIceCandidate()`. Errors are, as before, ignored if we have just discarded an offer.

### Explicit restartIce() method added

The techniques previously used to trigger an ICE restart while handling the event `negotiationneeded` have significant flaws. These flaws have made it difficult to safely and reliably trigger a restart during negotiation. The perfect negotiation improvements have fixed this by adding a new `restartIce()` method to `RTCPeerConnection`.

#### The old way

In the past, if you encountered an ICE error and needed to restart negotiation, you might have done something like this:

```js
pc.onnegotiationneeded = async (options) => {
  await pc.setLocalDescription(await pc.createOffer(options));
  signaler.send({ description: pc.localDescription });
};
pc.oniceconnectionstatechange = () => {
  if (pc.iceConnectionState === "failed") {
    pc.onnegotiationneeded({ iceRestart: true });
  }
};
```

This has a number of reliability issues and outright bugs (such as failing if the `iceconnectionstatechange` event fires when the signaling state isn't `stable`), but there was no way you could actually request an ICE restart other than by creating and sending an offer with the `iceRestart` option set to `true`. Sending the restart request thus required directly invoking the `negotiationneeded` event's handler. Getting it right was tricky at best, and was so easy to get wrong that bugs are common.

#### Using restartIce()

Now, you can use `restartIce()` to do this much more cleanly:

```js
let makingOffer = false;

pc.onnegotiationneeded = async () => {
  try {
    makingOffer = true;
    await pc.setLocalDescription();
    signaler.send({ description: pc.localDescription });
  } catch (err) {
    console.error(err);
  } finally {
    makingOffer = false;
  }
};
pc.oniceconnectionstatechange = () => {
  if (pc.iceConnectionState === "failed") {
    pc.restartIce();
  }
};
```

With this improved technique, instead of directly calling `onnegotiationneeded` with options to trigger ICE restart, the `failed` ICE connection state calls `restartIce()`. `restartIce()` tells the ICE layer to automatically add the `iceRestart` flag to the next ICE message sent. Problem solved!

### Rollback no longer supported in the pranswer state

The last of the API changes that stand out is that you can no longer roll back when in either of the `have-remote-pranswer` or the `have-local-pranswer` states. Fortunately, when using perfect negotiation there's no need to do this anyway, since the situations that cause this are caught and prevented before rolling these back ever becomes necessary.

Thus, attempting to trigger rollback while in one of the two `pranswer` states will now throw an `InvalidStateError`.

See also
--------

* WebRTC API
* Lifetime of a WebRTC session


A simple RTCDataChannel sample - Web APIs

A simple RTCDataChannel sample
==============================

The `RTCDataChannel` interface is a feature of the WebRTC API which lets you open a channel between two peers over which you may send and receive arbitrary data. The API is intentionally similar to the WebSocket API, so that the same programming model can be used for each.

In this example, we will open an `RTCDataChannel` connection linking two elements on the same page. While that's obviously a contrived scenario, it's useful for demonstrating the flow of connecting two peers. We'll cover the mechanics of accomplishing the connection and transmitting and receiving data, but we will save the bits about locating and linking to a remote computer for another example.

The HTML
--------

First, let's take a quick look at the HTML that's needed. There's nothing incredibly complicated here. First, we have a couple of buttons for establishing and closing the connection:

```html
<button id="connectButton" name="connectButton" class="buttonleft">
  Connect
</button>
<button
 id="disconnectButton"
 name="disconnectButton"
 class="buttonright"
 disabled>
  Disconnect
</button>
```

Then there's a box which contains the text input box into which the user can type a message to transmit, with a button to send the entered text. This `<div>` will be the first peer in the channel.

```html
<div class="messagebox">
  <label for="message"
 >Enter a message:
    <input
 type="text"
 name="message"
 id="message"
 placeholder="Message text"
 inputmode="latin"
 size="60"
 maxlength="120"
 disabled />
  </label>
  <button id="sendButton" name="sendButton" class="buttonright" disabled>
    Send
  </button>
</div>
```

Finally, there's the little box into which we'll insert the messages. This `<div>` block will be the second peer.

```html
<div class="messagebox" id="receivebox">
  <p>Messages received:</p>
</div>
```

The JavaScript code
-------------------

While you can just look at the code itself on GitHub, below we'll review the parts of the code that do the heavy lifting.

### Starting up

When the script is run, we set up a `load` event listener, so that once the page is fully loaded, our `startup()` function is called.

```js
let connectButton = null;
let disconnectButton = null;
let sendButton = null;
let messageInputBox = null;
let receiveBox = null;

let localConnection = null; // RTCPeerConnection for our "local" connection
let remoteConnection = null; // RTCPeerConnection for the "remote"

let sendChannel = null; // RTCDataChannel for the local (sender)
let receiveChannel = null; // RTCDataChannel for the remote (receiver)

function startup() {
  connectButton = document.getElementById("connectButton");
  disconnectButton = document.getElementById("disconnectButton");
  sendButton = document.getElementById("sendButton");
  messageInputBox = document.getElementById("message");
  receiveBox = document.getElementById("receivebox");

  // Set event listeners for user interface widgets

  connectButton.addEventListener("click", connectPeers, false);
  disconnectButton.addEventListener("click", disconnectPeers, false);
  sendButton.addEventListener("click", sendMessage, false);
}
```

This is quite straightforward. We declare variables and grab references to all the page elements we'll need to access, then set `event listeners` on the three buttons.

### Establishing a connection

When the user clicks the "Connect" button, the `connectPeers()` method is called. We're going to break this up and look at it a bit at a time, for clarity.

**Note:** Even though both ends of our connection will be on the same page, we're going to refer to the one that starts the connection as the "local" one, and to the other as the "remote" end.

#### Set up the local peer

```js
localConnection = new RTCPeerConnection();

sendChannel = localConnection.createDataChannel("sendChannel");
sendChannel.onopen = handleSendChannelStatusChange;
sendChannel.onclose = handleSendChannelStatusChange;
```

The first step is to create the "local" end of the connection. This is the peer that will send out the connection request. The next step is to create the `RTCDataChannel` by calling `RTCPeerConnection.createDataChannel()` and set up event listeners to monitor the channel so that we know when it's opened and closed (that is, when the channel is connected or disconnected within that peer connection).

It's important to keep in mind that each end of the channel has its own `RTCDataChannel` object.

#### Set up the remote peer

```js
remoteConnection = new RTCPeerConnection();
remoteConnection.ondatachannel = receiveChannelCallback;
```

The remote end is set up similarly, except that we don't need to explicitly create an `RTCDataChannel` ourselves, since we're going to be connected through the channel established above. Instead, we set up a `datachannel` event handler; this will be called when the data channel is opened; this handler will receive an `RTCDataChannel` object; you'll see this below.

#### Set up the ICE candidates

The next step is to set up each connection with ICE candidate listeners; these will be called when there's a new ICE candidate to tell the other side about.

**Note:** In a real-world scenario in which the two peers aren't running in the same context, the process is a bit more involved; each side provides, one at a time, a suggested way to connect (for example, UDP, UDP with a relay, TCP, etc.) by calling `RTCPeerConnection.addIceCandidate()`, and they go back and forth until agreement is reached. But here, we just accept the first offer on each side, since there's no actual networking involved.

```js
localConnection.onicecandidate = (e) =>
  !e.candidate ||
  remoteConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);

remoteConnection.onicecandidate = (e) =>
  !e.candidate ||
  localConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
```

We configure each `RTCPeerConnection` to have an event handler for the `icecandidate` event.

#### Start the connection attempt

The last thing we need to do in order to begin connecting our peers is to create a connection offer.

```js
localConnection
  .createOffer()
  .then((offer) => localConnection.setLocalDescription(offer))
  .then(() =>
    remoteConnection.setRemoteDescription(localConnection.localDescription),
  )
  .then(() => remoteConnection.createAnswer())
  .then((answer) => remoteConnection.setLocalDescription(answer))
  .then(() =>
    localConnection.setRemoteDescription(remoteConnection.localDescription),
  )
  .catch(handleCreateDescriptionError);
```

Let's go through this line by line and decipher what it means.

1. First, we call `RTCPeerConnection.createOffer()` method to create an SDP (Session Description Protocol) blob describing the connection we want to make. This method accepts, optionally, an object with constraints to be met for the connection to meet your needs, such as whether the connection should support audio, video, or both. In our simple example, we don't have any constraints.
2. If the offer is created successfully, we pass the blob along to the local connection's `RTCPeerConnection.setLocalDescription()` method. This configures the local end of the connection.
3. The next step is to connect the local peer to the remote by telling the remote peer about it. This is done by calling `remoteConnection.``RTCPeerConnection.setRemoteDescription()`. Now the `remoteConnection` knows about the connection that's being built. In a real application, this would require a signaling server to exchange the description object.
4. That means it's time for the remote peer to reply. It does so by calling its `createAnswer()` method. This generates a blob of SDP which describes the connection the remote peer is willing and able to establish. This configuration lies somewhere in the union of options that both peers can support.
5. Once the answer has been created, it's passed into the remoteConnection by calling `RTCPeerConnection.setLocalDescription()`. That establishes the remote's end of the connection (which, to the remote peer, is its local end. This stuff can be confusing, but you get used to it). Again, this would normally be exchanged through a signalling server.
6. Finally, the local connection's remote description is set to refer to the remote peer by calling localConnection's `RTCPeerConnection.setRemoteDescription()`.
7. The `catch()` calls a routine that handles any errors that occur.

**Note:** Once again, this process is not a real-world implementation; in normal usage, there's two chunks of code running on two machines, interacting and negotiating the connection. A side channel, commonly called a "signalling server," is usually used to exchange the description (which is in **application/sdp** form) between the two peers.

#### Handling successful peer connection

As each side of the peer-to-peer connection is successfully linked up, the corresponding `RTCPeerConnection`'s `icecandidate` event is fired. These handlers can do whatever's needed, but in this example, all we need to do is update the user interface:

```js
function handleCreateDescriptionError(error) {
  console.log(`Unable to create an offer: ${error.toString()}`);
}

function handleLocalAddCandidateSuccess() {
  connectButton.disabled = true;
}

function handleRemoteAddCandidateSuccess() {
  disconnectButton.disabled = false;
}

function handleAddCandidateError() {
  console.log("Oh noes! addICECandidate failed!");
}
```

The only thing we do here is disable the "Connect" button when the local peer is connected and enable the "Disconnect" button when the remote peer connects.

#### Connecting the data channel

Once the `RTCPeerConnection` is open, the `datachannel` event is sent to the remote to complete the process of opening the data channel; this invokes our `receiveChannelCallback()` method, which looks like this:

```js
function receiveChannelCallback(event) {
  receiveChannel = event.channel;
  receiveChannel.onmessage = handleReceiveMessage;
  receiveChannel.onopen = handleReceiveChannelStatusChange;
  receiveChannel.onclose = handleReceiveChannelStatusChange;
}
```

The `datachannel` event includes, in its channel property, a reference to a `RTCDataChannel` representing the remote peer's end of the channel. This is saved, and we set up, on the channel, event listeners for the events we want to handle. Once this is done, our `handleReceiveMessage()` method will be called each time data is received by the remote peer, and the `handleReceiveChannelStatusChange()` method will be called any time the channel's connection state changes, so we can react when the channel is fully opened and when it's closed.

### Handling channel status changes

Both our local and remote peers use a single method to handle events indicating a change in the status of the channel's connection.

When the local peer experiences an open or close event, the `handleSendChannelStatusChange()` method is called:

```js
function handleSendChannelStatusChange(event) {
  if (sendChannel) {
    const state = sendChannel.readyState;

    if (state === "open") {
      messageInputBox.disabled = false;
      messageInputBox.focus();
      sendButton.disabled = false;
      disconnectButton.disabled = false;
      connectButton.disabled = true;
    } else {
      messageInputBox.disabled = true;
      sendButton.disabled = true;
      connectButton.disabled = false;
      disconnectButton.disabled = true;
    }
  }
}
```

If the channel's state has changed to "open", that indicates that we have finished establishing the link between the two peers. The user interface is updated correspondingly by enabling the text input box for the message to send, focusing the input box so that the user can immediately begin to type, enabling the "Send" and "Disconnect" buttons, now that they're usable, and disabling the "Connect" button, since it is not needed when the connection is open.

If the state has changed to "closed", the opposite set of actions occurs: the input box and "Send" button are disabled, the "Connect" button is enabled so that the user can open a new connection if they wish to do so, and the "Disconnect" button is disabled, since it's not useful when no connection exists.

Our example's remote peer, on the other hand, ignores the status change events, except for logging the event to the console:

```js
function handleReceiveChannelStatusChange(event) {
  if (receiveChannel) {
    console.log(
      `Receive channel's status has changed to ${receiveChannel.readyState}`,
    );
  }
}
```

The `handleReceiveChannelStatusChange()` method receives as an input parameter the event which occurred; this will be an `RTCDataChannelEvent`.

### Sending messages

When the user presses the "Send" button, the sendMessage() method we've established as the handler for the button's `click` event is called. That method is simple enough:

```js
function sendMessage() {
  const message = messageInputBox.value;
  sendChannel.send(message);

  messageInputBox.value = "";
  messageInputBox.focus();
}
```

First, the text of the message is fetched from the input box's `value` attribute. This is then sent to the remote peer by calling `sendChannel.send()`. That's all there is to it! The rest of this method is just some user experience sugar — the input box is emptied and re-focused so the user can immediately begin typing another message.

### Receiving messages

When a "message" event occurs on the remote channel, our `handleReceiveMessage()` method is called as the event handler.

```js
function handleReceiveMessage(event) {
  const el = document.createElement("p");
  const txtNode = document.createTextNode(event.data);

  el.appendChild(txtNode);
  receiveBox.appendChild(el);
}
```

This method performs some basic DOM injection; it creates a new `<p>` (paragraph) element, then creates a new `Text` node containing the message text, which is received in the event's `data` property. This text node is appended as a child of the new element, which is then inserted into the `receiveBox` block, thereby causing it to draw in the browser window.

### Disconnecting the peers

When the user clicks the "Disconnect" button, the `disconnectPeers()` method previously set as that button's handler is called.

```js
function disconnectPeers() {
  // Close the RTCDataChannels if they're open.

  sendChannel.close();
  receiveChannel.close();

  // Close the RTCPeerConnections

  localConnection.close();
  remoteConnection.close();

  sendChannel = null;
  receiveChannel = null;
  localConnection = null;
  remoteConnection = null;

  // Update user interface elements

  connectButton.disabled = false;
  disconnectButton.disabled = true;
  sendButton.disabled = true;

  messageInputBox.value = "";
  messageInputBox.disabled = true;
}
```

This starts by closing each peer's `RTCDataChannel`, then, similarly, each `RTCPeerConnection`. Then all the saved references to these objects are set to `null` to avoid accidental reuse, and the user interface is updated to reflect the fact that the connection has been closed.

Next steps
----------

Take a look at the webrtc-simple-datachannel source code, available on GitHub.

See also
--------

* Signaling and Video Calling.
* The Perfect Negotiation pattern.