Simple-peer: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: kStable

Created on 11 Jul 2020  Β·  15Comments  Β·  Source: feross/simple-peer

I'm trying to figure out what's wrong with what I did, but for some reason, completely random, some users won't establish a connection. I use Xirsys to get dynamic ICE Servers on the server side. This happens in the socket connected method, and it's async so before i emit the video_users event, i await on receiving the dynamic ICE Servers. But those are not the problem.

    socket.on("connect", () => {
      this.setState({ id: socket.id });
    });

    socket.open();

This function generates the peer:

  generatePeer(id: string, initiator: boolean = false, iceServers: any[] = null) {
    if (!iceServers) return null;
    let peer = new Peer({
      initiator: initiator,
      trickle: false,
      config: {
        iceServers: iceServers
      },
      stream: this.state.stream
    });

    peer.on("signal", (data: any) => {
      if (this.state.id !== id) {
        socket.emit("video_signal", {
          signal: data,
          to: id,
          name: this.state.name,
          host: this.state.host,
          iceServers: iceServers
        });
      }
    });

    peer.on("connect", () => {
      console.log("CONNECTED PEER " + id);
    });

    peer.on("stream", (stream: any) => {
      let partners = this.state.partners;
      if (partners[id] && partners[id].video) {
        if ("srcObject" in partners[id].video) {
          partners[id].video.srcObject = stream;
        } else {
          partners[id].video.src = window.URL.createObjectURL(stream);
        }
        this.setState({ partners });
      }
    });

    peer.on("error", (err: any) => {
      console.error(err);
    });

    peer.on("close", () => {
      console.log("CLOSED PEER " + id);
      delete this.peers[id];
      delete partners[id];
      this.setState({ partners });
    });

    return peer;
  }

This event calls it (video_users is an event called every time someone enters the room)

    this.io.on("video_users", (peers: any) => {
      peers.map((peer: any) => {
        if (!this.peers[peer.id] && peer.id !== this.state.id) {
            this.peers[peer.id] = this.generatePeer(peer.id, true, data.iceServers);
          }
      });
    });

And this event is called when a signal has been sent:

    this.io.on("video_new_signal", async (data: any) => {
      let id = data.from;
      if (id != this.state.id) {
        if (!this.peers[id])
          this.peers[id] = this.generatePeer(id, false, data.iceServers);

        this.peers[id].signal(data.signal);
        let partners = this.state.partners;
        if (!partners[id]) {
          partners[id] = { name: data.name, full: false, host: data.host };
          this.setState({ partners });
        }
      }
    });

This is a multi-video room chat, so full mesh. But the problem happens also when it's one-on-one video chat.
What am I doing wrong? Is there a way to simulate a problem? Because right now it's completely random and very hard to debug.

question

Most helpful comment

Ah, that is the "negotiate" signal that's being incorrectly sent. See: https://github.com/feross/simple-peer/issues/670

We're working on a fix. In the meantime, you can disable renegotiation by dropping the signal.

peer.on('signal', (data) => {
  if (data. renegotiate || data.transceiverRequest) return
})

This may or may not solve your issues.

All 15 comments

I managed to debug the error a bit. It seems some users get Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: kStable error. This happens as far as a I can see only on Chrome. Anyway, the problem is not the ICE Servers. I checked.

It is still randomly happening. Even on one-on-one video chat.

You're trying to process an SDP answer when you haven't generated an offer or have already processed an answer. Basically, your signalling messages are going to the wrong peers or are arriving in the wrong order.

I recommend logging the signalling messages that each peer is receiving and checking the "type" field is "offer" or "answer" as you would expect.

But the code looks fine to me, order-wise.
I removed let iceServers = await this.getICEServers(); from the client-side and added it on the server-side. So the peer gets his ICE-Servers before being generated inside the video_users event. Now this error is gone.

Nevertheless for many clients I get those kind of errors:

Connection failed:
https://sentry.io/share/issue/f0e1f00ef79140aaa2fd6056e93d5777/
Transport Channel closed:
https://sentry.io/share/issue/355fb0bb7d974a529e5b9395b3376144/

You're trying to process an SDP answer when you haven't generated an offer or have already processed an answer. Basically, your signalling messages are going to the wrong peers or are arriving in the wrong order.

I recommend logging the signalling messages that each peer is receiving and checking the "type" field is "offer" or "answer" as you would expect.

Ok, I did that. SIGNAL PEER is when peer.on("signal") fires. Those are the logs for the initiator peer:

16:59:04.365 Creating peer ZcXrozU80lfh4FljAAAG my id: aa6lWK0kFzXmvrS-AAAF
16:59:05.868 SIGNAL PEER ZcXrozU80lfh4FljAAAG an offer MY ID: aa6lWK0kFzXmvrS-AAAF (x2 times)
16:59:06.861 CONNECTED PEER ZcXrozU80lfh4FljAAAG MY ID: aa6lWK0kFzXmvrS-AAAF

And this is on the other side:

16:59:05.901 Creating peer aa6lWK0kFzXmvrS-AAAF my id: ZcXrozU80lfh4FljAAAG
16:59:06.049 SIGNAL PEER aa6lWK0kFzXmvrS-AAAF an undefined MY ID: ZcXrozU80lfh4FljAAAG
16:59:06.560 SIGNAL PEER aa6lWK0kFzXmvrS-AAAF an answer MY ID: ZcXrozU80lfh4FljAAAG
16:59:06.794 CONNECTED PEER aa6lWK0kFzXmvrS-AAAF MY ID: ZcXrozU80lfh4FljAAAG
16:59:07.127 SIGNAL PEER aa6lWK0kFzXmvrS-AAAF an answer MY ID: ZcXrozU80lfh4FljAAAG

What is this "undefined" type? is this normal? Connection has been made nonetheless.

The Connection Failed error is completely different from the Failed to set remote answer sdp error. It likely has a different cause.

I'm not sure what that undefined signal type is. If you do peer._debug = console.log, you can get simple-peer's debug logs.

If you're trying to do signalling with socket.io, I recommend simple-signal. It solves most of these issues for you.

I'm not sure what that undefined signal type is. If you do peer._debug = console.log, you can get simple-peer's debug logs.

Here is the log for initiator:

17:32:03.109 Creating initiator peer VeSvNVpiX8qfBdW5AAAN my id: qvmAT9xEQR1mw_gxAAAM Video.tsx:209
17:32:03.360 starting batched negotiation index.js:450
17:32:03.361 start negotiation index.js:466
17:32:03.414 signalingStateChange have-local-offer index.js:1047
17:32:03.416 createOffer success index.js:707
17:32:03.449 iceStateChange (connection: new) (gathering: gathering) index.js:793
17:32:03.452 started iceComplete timeout index.js:668
17:32:06.226 iceStateChange (connection: new) (gathering: complete) index.js:793
17:32:06.239 signal index.js:698
17:32:08.620 signal() index.js:220
17:32:08.621 got request to renegotiate index.js:223
17:32:08.622 _needsNegotiation index.js:442
17:32:08.667 starting batched negotiation index.js:450
17:32:08.668 already negotiating, queueing index.js:464
17:32:13.305 signal() index.js:220
17:32:13.437 flushing sender queue 
Array []
index.js:1021
17:32:13.438 flushing negotiation queue index.js:1032
17:32:13.439 _needsNegotiation index.js:442
17:32:13.440 negotiate index.js:1040
17:32:13.440 signalingStateChange stable index.js:1047
17:32:13.441 starting batched negotiation index.js:450
17:32:13.441 start negotiation index.js:466
17:32:13.443 on track 2 index.js:1120
17:32:13.737 iceStateChange (connection: checking) (gathering: complete) index.js:793
17:32:13.740 iceStateChange (connection: connected) (gathering: complete) index.js:793
17:32:13.741 maybeReady pc true channel false index.js:869
17:32:13.743 signalingStateChange have-local-offer index.js:1047
17:32:13.745 createOffer success index.js:707
17:32:13.745 signal index.js:698
17:32:14.237 iceStateChange (connection: connected) (gathering: gathering) index.js:793
17:32:14.237 maybeReady pc true channel false index.js:869
17:32:14.799 signal() index.js:220
17:32:14.804 flushing sender queue 
Array []
index.js:1021
17:32:14.805 negotiate index.js:1040
17:32:14.806 signalingStateChange stable index.js:1047
17:32:14.830 iceStateChange (connection: connected) (gathering: complete) index.js:793
17:32:14.831 maybeReady pc true channel false index.js:869
17:32:15.102 on channel open index.js:1098
17:32:15.103 maybeReady pc true channel true index.js:869
17:32:15.124 connect local: 2a02:8109:8500:136f:e5f2:6cb5:5a8c:45c6:64719 remote: 2a02:8109:8500:136f:e5f2:6cb5:5a8c:45c6:56229 index.js:945
17:32:15.126 connect index.js:994

And for the other peer:

17:32:06.642 Creating peer qvmAT9xEQR1mw_gxAAAM my id: VeSvNVpiX8qfBdW5AAAN Video.tsx:246
17:32:07.061 signal() index.js:220
17:32:07.114 starting batched negotiation index.js:450
17:32:07.117 already negotiating, queueing index.js:477
17:32:07.144 signalingStateChange have-remote-offer index.js:1047
17:32:07.146 on track 2 index.js:1120
17:32:07.838 flushing sender queue 
Array []
index.js:1021
17:32:07.839 flushing negotiation queue index.js:1032
17:32:07.840 _needsNegotiation index.js:442
17:32:07.843 negotiate index.js:1040
17:32:07.843 signalingStateChange stable index.js:1047
17:32:07.844 starting batched negotiation index.js:450
17:32:07.844 requesting negotiation from initiator index.js:479
17:32:08.276 iceStateChange (connection: new) (gathering: gathering) index.js:793
17:32:08.279 started iceComplete timeout index.js:668
17:32:08.281 iceStateChange (connection: checking) (gathering: gathering) index.js:793
17:32:13.287 iceComplete timeout completed index.js:674
17:32:13.290 signal index.js:753
17:32:13.515 iceStateChange (connection: connected) (gathering: gathering) index.js:793
17:32:13.516 maybeReady pc true channel false index.js:869
17:32:13.873 signal() index.js:220
17:32:13.877 signalingStateChange have-remote-offer index.js:1047
17:32:13.897 flushing sender queue 
Array []
index.js:1021
17:32:13.899 negotiate index.js:1040
17:32:13.900 signalingStateChange stable index.js:1047
17:32:13.901 signal index.js:753
17:32:15.221 on channel open index.js:1098
17:32:15.225 maybeReady pc true channel true index.js:869
17:32:15.250 connect local: 2a02:8109:8500:136f:e5f2:6cb5:5a8c:45c6:56229 remote: 2a02:8109:8500:136f:e5f2:6cb5:5a8c:45c6:64719 index.js:945
17:32:15.251 connect index.js:994
17:32:19.908 iceStateChange (connection: connected) (gathering: complete) index.js:793
17:32:19.909 maybeReady pc true channel true index.js:869

Do you see any problems here? Because the setRemoteDescription error is gone, but many users get disconnections.

If you're trying to do signalling with socket.io, I recommend simple-signal. It solves most of these issues for you.

I do use socket.io, but simple-signal is a bit too simple for my need. As I mentioned, I'm getting my ICE servers dynamically and the string lives for 120 seconds only. So it needs to be created upon request of joining the room and to be passed from the initiator to it's peer once he signals. Moreover I have some credit system in place to bill people, some limitations of room size, and many other events that are happening with socket.io.

This is the implementation server-side of the relevant events:

      socket.on("video_join", async (data) => {
        let token = data.token;
        let room = rooms[token];
        let user = {
          id: socket.id,
          host: data.host,
          name: data.name,
          time: moment(),
        };

        if (room) {
          if (room.users.length === 4) {
            io.to(socket.id).emit("video_full");
            return;
          }

          if (user.host) {
            let foundHost = room.users.find((u) => u.host === true);
            if (foundHost) {
              io.to(socket.id).emit("video_host_taken");
              return;
            }
          }
        }

        let iceServers = await getICEServers();

        if (!iceServers) {
          io.to(socket.id).emit("video_error");
          return;
        }

        if (rooms[token]) {
          rooms[token].users.push(user);
        } else {
          rooms[token] = {
            creditsUsed: 0,
            users: [user],
          };
        }

        socketToRoom[socket.id] = token;

        rooms[token].users.map((user: any) => {
          if (user.id !== socket.id)
            io.to(user.id).emit("video_users", {
              peers: rooms[token].users,
              iceServers,
            });
        });
      });

and

      socket.on("video_signal", (data) => {
        io.to(data.to).emit("video_new_signal", {
          signal: data.signal,
          from: socket.id,
          name: data.name,
          host: data.host,
          iceServers: data.iceServers,
        });
      });

Ah, that is the "negotiate" signal that's being incorrectly sent. See: https://github.com/feross/simple-peer/issues/670

We're working on a fix. In the meantime, you can disable renegotiation by dropping the signal.

peer.on('signal', (data) => {
  if (data. renegotiate || data.transceiverRequest) return
})

This may or may not solve your issues.

Disabling it won’t cause problems or unexpected behavior?

On Fri 7. Aug 2020 at 21:25, Thomas Mullen notifications@github.com wrote:

Ah, that is the "negotiate" signal that's being incorrectly sent. See:

670 https://github.com/feross/simple-peer/issues/670

We're working on a fix. In the meantime, you can disable renegotiation by
dropping the signal.

peer.on('signal', (data) => {
if (data. renegotiate || data.transceiverRequest) return})

β€”
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/feross/simple-peer/issues/703#issuecomment-670675483,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AFA3ZI4VETP3UZU7VDJBOODR7RIJVANCNFSM4OXGVNAQ
.

No. It isn't a necessary negotiation.

Note this is only because your application doesn't use features that require renegotiation. If you do use addTrack or addStream you'll need to change your code to properly handle these signal events.

Note this is only because your application doesn't use features that require renegotiation. If you do use addTrack or addStream you'll need to change your code to properly handle these signal events.

I do use it, i just omitted it from the code above. This is what I'm doing:

toggleScreenShare() {
    const mediaDevices = navigator.mediaDevices as any;
    const oldStream = this.state.stream;

      mediaDevices
        .getDisplayMedia({
          video: true,
          audio: false
        })
        .then((stream: any) => {
          stream.addTrack(oldStream.getAudioTracks()[0]); //Taking the audio from the microphone
          this.setState(
            { stream },
            () => {
              if (this.userVideo) this.userVideo.srcObject = stream;
              Object.keys(this.peers).map((id: string) => {
                this.peers[id].removeStream(oldStream); //Removing old camera stream
                this.peers[id].addStream(this.state.stream); //Adding new screen share stream + microphone audio
              });
            }
          );
        })
}

How can I handle it now when the negotiation is off? Can I somehow know when removeStream/addStream/addTrack happened?

Weirdly enough, I just tested it, and it seems to work even without handling the addStream. I just added the code you sent.

Now after adding your code, it seems that it works. Also with addStream. But some users are now getting this error:
Description type incompatible with current signaling state.

If you use addStream, then filtering those signals will cause your peers to end up in a bad state. You'll need to handle multiple signals between peers correctly.

Closing since this doesn't seem to be an issue with simple-peer, but feel free to continue discussion.

@t-mullen @BoazKG93 I'm also getting this same error while I do peer.addStream(stream). Anyone can give an example on how to handle it or any fix. I also tried to update to 9.8.0. For me, it is working when I do something like

const peer = new Peer({initiator: true, trickle:false, stream: stream});

Only not working when I add the stream later.

I'm also getting an object {renegotiate: true}. And I don't know what is it
Please help me.

logs:

Browser 1

Peer1: offer
Peer2: answer
Finally Connected πŸ˜€πŸŽ‰πŸŽŠπŸ₯³
Peer1: offer
{renegotiate: true}
Peer2: answer
Peer2: answer
Got Stream
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: stable

Brower 2

Peer1: offer
Peer2: answer
Finally Connected πŸ˜€πŸŽ‰πŸŽŠπŸ₯³
Peer1: offer
Peer2: answer
Got Stream

Note this is only because your application doesn't use features that require renegotiation. If you do use addTrack or addStream you'll need to change your code to properly handle these signal events.

Any example on how to handle it?

Was this page helpful?
0 / 5 - 0 ratings