What are the design limitations leading to separate send/receive transports on the client and can we lift them?

I’m new to mediasoup and getting something working is unexpectedly challenging.

One particular point I’d like to discuss here is the requirement to have separate transport for sending and receiving on the client.
It is really annoying, especially for data channels. Just to get simple duplex data channel with predefined label one needs to write hundreds of lines of boilerplate and have a bunch of signaling messages. There should be an easier way.

I’ve seen Can I create a sendTransport and recvTransport from 1 server-side Transport?, but looks like things have changed since and current documentation says that sever-side transport is not limited to just sending or receiving, good.
Now there is even older Reasons for having separate transports for producing and consuming media which addresses client-side specifically, but again, maybe things have changed since?

Is there any chance to get sending and receiving using the same transport today?
If not, what are the key blockers and maybe there is a way to help in removing them?

P.S. It is so challenging I’m currently considering using https://github.com/node-webrtc/node-webrtc for data channels and manage that myself instead.

In server side there has never been such a limitation.

No. This is still true. As I said in that topic:

By having separate Peerconnectios to send and receive we avoid the incredibly painful bidirectionality of Unified-Plan and so much hell in SDP O/A. And we can safely decide which codecs to use for each producer and consumer.

This is part of the SDP pain we want to avoid. In mediasoup we allow (via mediasoup-client) to decide which codec to use for each Producer. You can send a webcam track using VP8, another video using H264 with simulcast, and screen sharing using VP9 with SVC. Or send 200 different video tracks. And all them over the same sending transport.

If we used bidirectional transports, that would mean having SDP with bidirectional m= sections, or maybe some m= sections with a=sendonly and others with a=recvonly. And then, when trying to reuse a m= section it would be the most absolute pain (it’s already a pain with just single direction PeerConnections).

In send transports, mediasoup-client is the SDP offerer. Always. Otherwise things like simulcast would not be possible.

In recv transports, mediasoup-client is always the SDP answerer. This means that the codec payload types and RTP header extension ids are decided by the “offerer”, which is mediasoup (server). Otherwise, mediasoup (server) should rewrite codec payload types and header extension ids for each clients (because each browser may want to use different values in their offer, and the SDP answerer cannot change them).

These are just a few examples. There are more, but I hope you feel the pain. This is not an improvised design decision, but a very thoughtful decision.

Regarding DataChannels, we keep the same design. Note that in mediasoup we don’t assume that DataChannels are bidirectional. That’s just an addition of WebRTC 1.0 spec. But in fact, a WebRTC DataChannel is just a SCTP stream pair in different directions and with same SCTP id. Just that. It’s an artificial constrain that does not exist in SCTP spec.

2 Likes

My initial idea was to have a single connection for everything with data channel opened first and everything else gradually upgraded from there. But looks like now I’ll establish data channel connection with node-webrtc and then separately 2 more for sending and receiving multiple tracks each.
Looks like reasonable balance of complexity and efficiency. Fascinating how far WebRTC has come and how tricky it still is.

Thanks for writing it down, it really helps to understand why things are the way they are (not that I have doubts it is not done for good, but having insides into decisions and understanding technology better makes me more comfortable).

Just a note:

If for instance, you are just interested in DataChannel, you could perfectly write your own JS from scratch by using a single PeerConnection and wrap DataProducers and DataConsumers into it. However you would not get a “pure” DataChannel (this is, a SCTP stream pair with same sctp id for sending and receiving).