Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How are data channels negotiated between two peers with WebRTC?

The WebRTC RTCPeerConnection interface has a createDataChannel method and an ondatachannel event handler. How do these interact? How do I create a single data channel that can be used to send/receive data between two peers?

Also, the RTCDataChannelInit constructor has a negotiated field, which by default is set to false and says it results in the channel being announced in-band. What happens if it's set to true?

like image 792
Taylor Brandstetter Avatar asked May 04 '17 17:05

Taylor Brandstetter


1 Answers

Firstly: to create any data channel, the peers need to exchange an SDP offer/answer that negotiates the properties of the SCTP connection used by all data channels. This doesn't happen by default; you must call createDataChannel before calling createOffer for the offer to contain this SCTP information (an "m=application" section in the SDP).

If you don't do this, the data channel state will be stuck forever at connecting.

With that out of the way, there are two ways to negotiate a data channel between two peers:

In-band negotiation

This is what occurs by default, if the negotiated field is not set to true. One peer calls createDataChannel, and the other connects to the ondatachannel EventHandler. How this works:

  1. Peer A calls createDataChannel.
  2. Normal offer/answer exchange occurs.
  3. Once the SCTP connection is up, a message is sent in-band from Peer A to Peer B to tell it about the data channel's existence.
  4. On Peer B, the ondatachannel EventHandler is invoked with a new data channel, created from the in-band message. It has the same properties as the data channel created by Peer A, and now these data channels can be used to send data bidirectionally.

The advantage of this approach is that data channels can be created dynamically at any time, without the application needing to do additional signaling.

Out-of-band negotiation

Data channels can also be negotiated out-of-band. With this approach, instead of calling createDataChannel on one side and listening for ondatachannel on the other side, the application just calls createDataChannel on both sides.

  1. Peer A calls createDataChannel({negotiated: true, id: 0})
  2. Peer B also calls createDataChannel({negotiated: true, id: 0}).
  3. Normal offer/answer exchange occurs.
  4. Once the SCTP connection is up, the channels will instantly be usable (readyState will change to open). They're matched up by the ID, which is the underlying SCTP stream ID.

The advantage of this approach is that, since no message needs to be sent in-band to create the data channel on Peer B, the channel is usable sooner. This also makes the application code simpler, since you don't even need to bother with ondatachannel.

So, for applications that only use a fixed number of data channels, this approach is recommended.

Note that the ID you choose is not just an arbitrary value. It represents an underlying 0-based SCTP stream ID. And these IDs can only go as high as the number of SCTP streams negotiated by the WebRTC implementations. So, if you use an ID that's too high, your data channel won't work.

What about native applications?

If you're using the native webrtc library instead of the JS API, it works the same way; things just have different names.

C++:

  • PeerConnectionObserver::OnDataChannel
  • DataChannelInit::negotiated
  • DataChannelInit::id

Java:

  • PeerConnection.Observer.onDataChannel
  • DataChannel.Init.negotiated
  • DataChannel.Init.id

Obj-C:

  • RTCPeerConnectionDelegate::didOpenDataChannel
  • RTCDataChannelConfiguration::isNegotiated
  • RTCDataChannelConfiguration::channelId
like image 177
Taylor Brandstetter Avatar answered Oct 24 '22 05:10

Taylor Brandstetter