javascriptwebrtc

WebRTC stuck in connecting state


I have successfully communicated the offer, answer and ice candidates for a WebRTC connection from A to B. At this point, the connection is stuck in the "connecting" state. The initiator (A) seems to timeout or something after a while and switch to the "failed" state, whereas its remote (B) is staying in the "connecting" state permanently.

Any help would be very appreciated.

Creation of peer (A and B):

let peer = new RTCPeerConnection({
    iceServers: [
        {
            urls: [
                "stun:stun1.l.google.com:19302",
                "stun:stun2.l.google.com:19302",
            ],
        },
        {
            urls: [
                "stun:global.stun.twilio.com:3478?transport=udp",
            ],
        },
    ],
    iceCandidatePoolSize: 10,
});

Creating offer (A):

peer.onnegotiationneeded = async () => {
    offer = await peer.createOffer();
    await peer.setLocalDescription(offer);
};

Collecting ice candidates (A):

peer.onicecandidate = (evt) => {
    if (evt.candidate) {
        iceCandidates.push(evt.candidate);
    } else {
        // send offer and iceCandidates to B through signaling server
        // this part is working perfectly
    }
};

Creating answer and populating ice candidates (B):

await peer.setRemoteDescription(offer);

let answer = await this._peer.createAnswer();
await peer.setLocalDescription(answer);

// send answer back to A through signaling server

for (let candidate of sigData.iceCandidates) {
    await peer.addIceCandidate(candidate);
}

On answer from B through signaling server (A):

await peer.setRemoteDescription(answer);

Detect connection state change (A and B):

peer.onconnectionstatechange = () => {
    console.log("state changed")
    console.log(peer.connectionState);
}

Also note that there were two occasions where it connected successfully, but I am yet to see it work again.

EDIT: I forgot to mention I am also creating a data channel (the onicecandidate event doesn't seem to call without this). This is called immediately after the RTCPeerConnection is constructed and any event handlers have been attached.

let channel = peer.createDataChannel("...", {
    id: ...,
    ordered: true,
});

EDIT 2: As @jib suggested, I am now also gathering ice candidates in B and sending them back to A to add. However, the exact same problem persists.

EDIT 3: It seems to connect the first time I hard reload the webpage for A and the webpage for B. Connection stops working again until I do another hard reload. Does anyone have any ideas why this is the case? At least I should be able to continue development for the time being until I can figure out this issue.

EDIT 4: I removed the iceServers I was using and left the RTCPeerConnection constructor blank. Somehow it is much more reliable now. But I am yet to get a successful connection on iOS Safari!


Solution

  • Finally! After a few weeks I have figured out the issue, which wasn't apparent in the code I included in my question, but may still be useful for anyone who is having similar problems.

    I assumed that the ice gathering was being completed after the onnegotiationneeded event fired and the offer/answer was created.

    Because of this incorrect assumption, I was signaling the offer/answer along with the ice candidates at this stage, but very frequently (always in iOS Safari from my experience) the offer/answer was not yet created at this point.

    I solved this by creating two promises for a) the completion of ice candidate gathering, and b) the creation of the offer/answer. I used Promise.all on the two promises, and when they both completed, I sent the ice candidates and offer/answer down through the signaling server all at once.

    This works, but of course in the future I should "trickle" this information, by sending bits and pieces as they come, instead of waiting for everything to fully complete. But I'll worry about that in the future, since at the moment I am using HTTP requests, and it is too much hassle.

    EDIT: My connection is still always stuck when iceServers are included, so I've created a new question. But local connections when no iceServers are included are now 100% fully reliable :)