WebRTC stuck in connecting state

会有一股神秘感。 提交于 2020-06-28 04:03:31

问题


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!


回答1:


Open this in two browser windows and hit the Connect button in one of them. This is the code:

const pc = new RTCPeerConnection();

call.onclick = async () => {
  const stream = await navigator.mediaDevices.getUserMedia({video:true,audio:true})
  video.srcObject = stream;
  for (const track of stream.getTracks()) {
    pc.addTrack(track, stream);
  }
};

pc.ontrack = ({streams}) => video.srcObject = streams[0];
pc.oniceconnectionstatechange = () => console.log(pc.iceConnectionState);
pc.onicecandidate = ({candidate}) => sc.send({candidate});
pc.onnegotiationneeded = async () => {
  await pc.setLocalDescription(await pc.createOffer());
  sc.send({sdp: pc.localDescription});
}

const sc = new localSocket(); // localStorage signaling hack
sc.onmessage = async ({data: {sdp, candidate}}) => {
  if (sdp) {
    await pc.setRemoteDescription(sdp);
    if (sdp.type == "offer") {
      await pc.setLocalDescription(await pc.createAnswer());
      sc.send({sdp: pc.localDescription});
    }
  } else if (candidate) await pc.addIceCandidate(candidate);
}

This is the same source for A and B. Replace the localSocket hack with your preferred signaling channel (e.g. websocket).

Don't cache ICE candidates since that defeats the purpose of Trickle ICE. It may appear fast locally, but in real networks ICE may take time.

In fact, sending candidates is meaningless if you delay sending the offer/answer until all local candidates have been gathered, since candidates are already embedded in the offer/answer (pc.localDescription) at that point.




回答2:


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.



来源:https://stackoverflow.com/questions/62020695/webrtc-stuck-in-connecting-state

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!