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?