WebRTC with perfect negotiation - Rollback on mobile Safari does not work

Viewed 1441

I am trying to implement perfect WebRTC negotiation for my small video conferencing application by considering the examples from the following page:

https://blog.mozilla.org/webrtc/perfect-negotiation-in-webrtc/

Unortunately I did not manage to make it fully work, especially mobile safari seems to handle rollback behavior its own way, here is the code that handles the rollback behavior:

      if (description) {
        const offerCollision = description.type == 'offer' && (makingOffer || pc.signalingState != 'stable');
        this.ignoreOffer = !this.polite && offerCollision;
        if (this.ignoreOffer) {
          return;
        }
        if (offerCollision) {
          await Promise.all([pc.setRemoteDescription({ type: 'rollback' }), pc.setRemoteDescription(description)]);

So when on mobile safari an offer collision (offerCollision === true) is detected and pc.setRemoteDescription({ type: 'rollback' }) is called as it's implemented in my code, it throws an error of type InvalidStateError. Taking a closer look at the documentation about this type of error in MDN (https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setRemoteDescription#Browser_compatibility) shows:

"The RTCPeerConnection is closed, or it's in a state which isn't compatible with the specified description's type. For example, if the type is rollback and the signaling state is one of stable, have-local-pranswer, or have-remote-pranswer, this exception is thrown, because you can't roll back a connection that's either fully established or is in the final stage of becoming connected."

Checking the peer connections signaling state just before rolling back shows that it is in the state "have-local-offer" which should be ok since MDN says that rollback is not possible (throws InvalidStateError) in the states stable, have-local-pranswer, or have-remote-pranswer.

For the other case when my Desktop Chrome browser runs in an offer collision everything just works as expected with the same signaling state just before rollback is initiated.

Does someone here have an idea what's potentially wrong or to be handled differrently for mobile Safari.

3 Answers

As mentioned in the comments, Safari (both iOS/mobile and macOS) has a known bug with { type: 'rollback' }. It also does not yet support optional descriptions in setLocalDescription/setRemoteDescription, the latest perfect negotiation recommendation in the spec.

This can be fixed by discarding the colliding peer connections and retrying (as suggested). To do this, handle the error when setting a remote description with the following steps:

  1. Reset any state variables (e.g. makingOffer, isSettingRemoteAnswerPending)
  2. Close the peer connection by calling peerConnection.close()
  3. Tear down any peer connection event listeners (e.g. negotiationneeded, icecandidate, track)
  4. Signal to the other peer to do the same e.g. via websocket messaging
  5. On receiving the signal to restart, the other can go through the same steps, and finally create a new offer to kick off the negotiation process again.

An example of this flow can be seen in this demo.

Good news: Safari Tech Preview (15.4) supports implicit rollback in setRemoteDescription directly¹, which appears to also have fixed the explicit {type: 'rollback'} method you were using in the more backwards-compatible polyfill (though I've asked for clarification in the bug).

Bad news: At the time of writing, it's still not available in regular Safari (at 15.3 now) or Safari for iOS. But that should be only a matter of time.

Implicit rollback was designed specifically for perfect negotiation, letting you use the standard pattern directly. Here's a fiddle using it.


1. I've submitted a PR to update MDN to reflect this.

In case anyone finds themselves back here, I found that I could make perfect negotiation work with Safari if I make 2 changes:

  1. in onnegotiationneeded: if we are on safari, don't do the createOffer() or setLocalDescription() if the instance is polite.

  2. on incoming messages, in addition to the existing 'ignore offer' functionality, ignore type=='offer' messages if the instance is NOT polite and we are running on safari.

My assumption is that Safari webkit doesn't like being presented with offers if it's made an offer to the peer. I don't have anything to back that up with beyond the fact it seems to work - the errors disappear and I get video.

Related