1

I am building an application which requires peer 2 peer communication of generic data, and webRTC seemed like a nice way to do it. However, when attempting to make the p2p connection with the DataChannel, I keep encountering ICE Candidate failure on a particular network. But on this same network, the MediaChannel p2p connection works fine.

Even in the most minimal adaption of the following repo, https://github.com/fireship-io/webrtc-firebase-demo, the datachannel variant does not work.

Suspected that it had to do with TCP as opposed to UDP, but not allowing tcp candidates does not solve the issue.

What is the reason DataChannel does not work, but MediaChannel does? The network is a university public network. We use firebase as a signaling server. Google STUN servers.

HTML consists of callButton, which pushes ICECandidates + offer to signaling server, and generates an ID which can be used to connect from a different instance by answerButton.

The js snippet that handles most of the webRTC connection, where pc is the RTCPeerConnection instance:

function startup() {
  sendChannel = pc.createDataChannel("sendChannel");
  pc.ondatachannel = receiveChannelCallback;
  
  //update HTML depending on open connection
  sendChannel.onopen = handleSendChannelStatusChange; 
  sendChannel.onclose = handleSendChannelStatusChange;
  
  callButton.disabled = false;
  answerButton.disabled = false;
};

// Create an offer
callButton.onclick = async () => {
  // Reference Firestore collections for signaling
  const callDoc = firestore.collection('calls').doc();
  const offerCandidates = callDoc.collection('offerCandidates');
  const answerCandidates = callDoc.collection('answerCandidates');

  callInput.value = callDoc.id;

  // Get candidates for caller, save to Firebase
  pc.onicecandidate = (event) => { 
  // only add ICE that are not tcp
  if (event.candidate && event.candidate.protocol !== 'tcp') {
        offerCandidates.add(event.candidate.toJSON())
      }
  };

  // Create offer
  const offerDescription = await pc.createOffer();
  await pc.setLocalDescription(offerDescription);

  const offer = {
    sdp: offerDescription.sdp,
    type: offerDescription.type,
  };

  await callDoc.set({ offer });

  // Listen for remote answer
  callDoc.onSnapshot((snapshot) => {
    const data = snapshot.data();
    if (!pc.currentRemoteDescription && data?.answer) {
      const answerDescription = new RTCSessionDescription(data.answer);
      pc.setRemoteDescription(answerDescription);
    }
  });

  // When answered, add candidate to peer connection
  answerCandidates.onSnapshot((snapshot) => {
    snapshot.docChanges().forEach((change) => {
      if (change.type === 'added') {
        const candidate = new RTCIceCandidate(change.doc.data());
        pc.addIceCandidate(candidate);
      }
    });
  });
};

// 3. Answer the call with the unique ID
answerButton.onclick = async () => {
  const callId = callInput.value;
  const callDoc = firestore.collection('calls').doc(callId);
  const answerCandidates = callDoc.collection('answerCandidates');
  const offerCandidates = callDoc.collection('offerCandidates');

  pc.onicecandidate = (event) => {
    event.candidate && answerCandidates.add(event.candidate.toJSON());
  };

  const callData = (await callDoc.get()).data();

  const offerDescription = callData.offer;
  await pc.setRemoteDescription(new RTCSessionDescription(offerDescription));

  const answerDescription = await pc.createAnswer();
  await pc.setLocalDescription(answerDescription);

  const answer = {
    type: answerDescription.type,
    sdp: answerDescription.sdp,
  };

  await callDoc.update({ answer });

  offerCandidates.onSnapshot((snapshot) => {
    snapshot.docChanges().forEach((change) => {
      console.log(change);
      if (change.type === 'added') {
        let data = change.doc.data();
        pc.addIceCandidate(new RTCIceCandidate(data));
      }
    });
  });
};

What is the reason DataChannel does not work, but MediaChannel does?

Lourens
  • 104
  • 1
  • 11
  • you need to understand what NAT the network is using, if it is symmetric then you need a TURN server, also you need to be more specific as how, when ICE is failing. Even post some code. did you exchange the ice candidates? – Skin_phil Aug 09 '21 at 14:33
  • @Skin_phil added code for RTC connectivity. As you can, ICE candidates are exchanged via firebase as signaling server. Can't easily figure out the NAT, but seeing that the MediaChannel works without a turn server, it is probably not symmetric. – Lourens Aug 09 '21 at 15:17

0 Answers0