Scenario
We are processing the signaling of WebRTC. We employ MQTT as the signaling protocol. Here is the pseudo code:
mqttClient.on('message', messageHandler)
async messageHandler(peerConnection, topic, message) {
switch (message.type) {
case 'offer':
const stream = await getUserMedia({ video: true })
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream))
await peerConnection.setRemoteDescription(message.offer)
const answer = await peerConnection.createAnswer()
peerConnection.setLocalDescription(answer)
mqttClient.publish(destinationTopic, answer)
break
case 'candidate':
peerConnection.addIceCandidate(message.candidate);
}
}
We see that it is a standard WebRTC callee flow:
- Receive offer.
- Add stream.
- Set remote description.
- Create answer.
- Set local description.
- Send answer.
- Receive candidate.
It seems good.
Analysis
But it will fail. The WebRTC API complains with something like this: setRemoteDescription needs to called before addIceCandidate
.
The culprit is this line: const stream = await getUserMedia({ video: true })
.
Here is the flow:
- Offer Received.
- getUserMedia() is invoked. It is async and will take a relatively long time.
- The whole function
messageHandler
yields. messageHandler
is invoked again, this time, it processcandidate
.
When we set the call back to register the message handler, mqttClient.on('message', messageHandler)
, it assumes the handler is a SYNC function. If we set an ASYNC callback, it wouldn't wait for the completion of the callback. So leads to the bug. When the procedure of processing offer
does not complete (still getting media), the processing of candidate starts.
Questions
- Why does MQTT.js (and other javascript standard runtime APIs such as event emitter, foreach/map/reduce, etc.) not support async callback (wait for the completion of the previous async callback)?
- Why does setRemoteDescription need to called before addIceCandidate?
Destination
- How to fix the bug under the current circumstance?
- What is the final solution in the future?