I'm implementing WebRTC peer-to-peer call, it is working well in all browser except if chrome is the call initializer (who invoke invite function).
if chrome is the call initializer, the ICE connection state change to connected and the video appear in the other peer,then an error "DOMException: "Cannot create answer in state stable"' happen and the video stop transfer to the other peer.
if the receiver is also chrome, the error says "Failed to execute 'createAnswer' on 'RTCPeerConnection': PeerConnection cannot create an answer in a state other than have-remote-offer or have-local-pranswer."
these are the handleVideoOfferMsg function and handleGetUserMediaError function (which trigger the error) code:
function handleVideoOfferMsg(msg) {
// debugger;
// $("#ring")[0].play();
var localStream = null;
targetUsername = msg.from;
// Call createPeerConnection() to create the RTCPeerConnection.
log("Starting to accept invitation from " + targetUsername);
createPeerConnection();
// We need to set the remote description to the received SDP offer
// so that our local WebRTC layer knows how to talk to the caller.
var desc = new RTCSessionDescription(msg.sdp);
myPeerConnection.setRemoteDescription(desc).then(function () {
log("Setting up the local media stream...");
return navigator.mediaDevices.getUserMedia(responserConstraints);
})
.then(function(stream) {
$("#video-container").removeClass("hidden");
log("-- Local video stream obtained");
localStream = stream;
if(responserConstraints.hasOwnProperty('video'))
{
$("#localVideo").removeClass('hidden')[0].srcObject = localStream;
}
log("-- Adding outgoing tracks to the RTCPeerConnection");
localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream));
})
.then(function() {
log("------> Creating answer");
// Now that we've successfully set the remote description, we need to
// start our stream up locally then create an SDP answer. This SDP
// data describes the local end of our call, including the codec
// information, options agreed upon, and so forth.
return myPeerConnection.createAnswer();
})
.then(function(answer) {
log("------> Setting local description after creating answer");
// We now have our answer, so establish that as the local description.
// This actually configures our end of the call to match the settings
// specified in the SDP.
return myPeerConnection.setLocalDescription(answer);
})
.then(function() {
var msg = {
name: myUsername,
target: targetUsername,
type: "video-answer",
sdp: myPeerConnection.localDescription
};
// We've configured our end of the call now. Time to send our
// answer back to the caller so they know that we want to talk
// and how to talk to us.
log("Sending answer packet back to other peer");
sendToServer(msg);
})
.catch(
handleGetUserMediaError);
}
function handleGetUserMediaError(e) {
log(e);
switch(e.name) {
case "NotFoundError":
alert("Unable to open your call because no camera and/or microphone" +
"were found.");
break;
case "SecurityError":
case "PermissionDeniedError":
// Do nothing; this is the same as the user canceling the call.
break;
default:
//the error occur here
alert("Error opening your camera and/or microphone: " + e.message);
console.log("----------");
console.log(e);
break;
}
// Make sure we shut down our end of the RTCPeerConnection so we're
// ready to try again.
closeVideoCall();
}
the invite function:
function invite() {
log("Starting to prepare an invitation");
if (myPeerConnection) {
alert("You can't start a call because you already have one open!");
} else {
var clickedUsername = $("input[name=teacherId]").val();
console.log('user id is '+ clickedUsername);
// Don't allow users to call themselves, because weird.
if (clickedUsername === myUsername) {
alert("I'm afraid I can't let you talk to yourself. That would be weird.");
return;
}
// Record the username being called for future reference
targetUsername = clickedUsername;
log("Inviting user " + targetUsername);
// Call createPeerConnection() to create the RTCPeerConnection.
log("Setting up connection to invite user: " + targetUsername);
createPeerConnection();
// Now configure and create the local stream, attach it to the
// "preview" box (id "video"), and add it to the
// RTCPeerConnection.
log("Requesting webcam access...");
isInviter = true;
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(function(localStream) {
$("#video-container").removeClass("hidden");
console.log(localStream);
log("-- Local video stream obtained");
if(responserConstraints.hasOwnProperty('video'))
{
var localVideo = $('#localVideo');
localVideo.removeClass('hidden')[0].srcObject = localStream;
localVideo[0].mute;
}
else
{
var videoEl = document.getElementById("video");
videoEl.muted = true;
videoEl.srcObject = localStream;
}
log("-- Adding incoming tracks to the RTCPeerConnection");
localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream));
})
.catch(handleGetUserMediaError);
}
}
the console response is the following:
[3:28:31 PM] *** ICE gathering state changed to: gathering
opensocket.js:66 [3:28:31 PM] Outgoing ICE candidate: candidate:202810205 1 udp 2122260223 192.168.1.21 39265 typ host generation 0 ufrag 76SW network-id 1 network-cost 10
opensocket.js:66 [3:28:31 PM] Sending 'new-ice-candidate' message: {"type":"new-ice-candidate","candidate":{"candidate":"candidate:202810205 1 udp 2122260223 192.168.1.21 39265 typ host generation 0 ufrag 76SW network-id 1 network-cost 10","sdpMid":"audio","sdpMLineIndex":0,"usernameFragment":"76SW"},"hash":"m0wk8Kaz8JFejuJGte7kAmNtYDiWPZNBGl7fKA5b"}
opensocket.js:66 [3:28:31 PM] -- Local video stream obtained
opensocket.js:66 [3:28:31 PM] -- Adding outgoing tracks to the RTCPeerConnection
opensocket.js:66 [3:28:31 PM] ------> Creating answer
opensocket.js:363 stable
opensocket.js:66 [3:28:31 PM] *** Negotiation needed
opensocket.js:66 [3:28:31 PM] ---> Creating offer
opensocket.js:363 stable
opensocket.js:66 [3:28:31 PM] *** Negotiation needed
opensocket.js:66 [3:28:31 PM] ---> Creating offer
opensocket.js:66 [3:28:31 PM] InvalidStateError: Failed to execute 'createAnswer' on 'RTCPeerConnection': PeerConnection cannot create an answer in a state other than have-remote-offer or have-local-pranswer.
I can't find a way to solve it !, the error only occur in chrome !
Any help, please ?
Edit: This is my handleNegotiationNeededEvent function:
function handleNegotiationNeededEvent() {
console.log(myPeerConnection.signalingState);
log("*** Negotiation needed");
log("---> Creating offer");
myPeerConnection.createOffer().then(function(offer) {
log("---> Creating new description object to send to remote peer");
return myPeerConnection.setLocalDescription(offer);
})
.then(function() {
log("---> Sending offer to remote peer");
sendToServer({
name: myUsername,
target: targetUsername,
type: "video-offer",
sdp: myPeerConnection.localDescription
});
})
.catch(reportError);
}
Ok I found a solution and it was really strange !
It look like chrome is firing onnegotiationneeded
twice !
I don't know why chrome is doing such weird behavior !
so createOffer
is fired twice because of that, as the log show:
opensocket.js:66 [3:28:31 PM] *** Negotiation needed
opensocket.js:66 [3:28:31 PM] ---> Creating offer
opensocket.js:363 stable
opensocket.js:66 [3:28:31 PM] *** Negotiation needed
opensocket.js:66 [3:28:31 PM] ---> Creating offer
so my solution was to change the handleNegotiationNeededEvent
as the following:
var Negotiation = 0;
function handleNegotiationNeededEvent() {
if(Negotiation === 0 )
{
Negotiation++;
}
else
{
return;
}
// if (myPeerConnection.signalingState === "stable") return;
console.log(myPeerConnection.signalingState);
log("*** Negotiation needed");
log("---> Creating offer");
myPeerConnection.createOffer().then(function(offer) {
log("---> Creating new description object to send to remote peer");
return myPeerConnection.setLocalDescription(offer);
})
.then(function() {
log("---> Sending offer to remote peer");
sendToServer({
name: myUsername,
target: targetUsername,
type: "video-offer",
sdp: myPeerConnection.localDescription
});
})
.catch(reportError);
}
it look like strange to me, but at least it is working now.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With