Hi. I switched from native WebRTC to Simple Peer, hoping a library would already control webRTC's pretty fragile state machines.
I am developing an app for online conferencing and need to heavily connect, disconnect and reconnect various peers.
connection works flawlessly on first attempt. After closing the connection and trying to reconnect, I get the error
Cannot set remote answer in state stable
in latest Firefox, or
DOMException: Failed to set remote answer sdp: Called in wrong state: kStable
in latest chrome (chromium)
That behavior is quite annoying. I am looking for a way to control peers. Disconnecting should really disconnect peers from each other without any leftovers.
It looks like the ICE-Servers are still expecting the peers to have a stable connection still?
When a connection is closed I call peer.destroy() and null my peer-object. I hoped that is enough to completely clean up, but WebRTC (or rather the ICE magic) is not really behaving deterministic.
here are some code snippets:
//connects to a client
//target: ID in OpenSlides
//offer: bool. should be true if initial
function connectToClient(target, offer, video) {
var peer = registerClient(target, offer, video);
var doSendVideo = false;
if (video) {
doSendVideo = true;
}
peer.on('signal', function (data) {
// this is sending the given JSON to target over a python server
Notify.notify("signalling", {
id: operator.user.id,
signal: data,
video: doSendVideo,
}, [target]);
});
return peer;
}
//registers a new client
//returns a client if already exists
function registerClient(id, initiator, video) {
console.log('registerClient: ', clients);
var peerConfig = {
initiator: initiator,
stream: video,
tickle: true,
reconnectTimer: 2000,
iceTransportPolicy: 'relay',
};
if (clients[id] === undefined) {
clients[id] = {};
}
if (clients[id].history === undefined) {
clients[id].history = [];
}
if (clients[id].peer === undefined) {
clients[id].peer = new SimplePeer(peerConfig);
setupPeer(clients[id].peer); //this is just setting up the peer.on('xxx')-stuff
}
return clients[id].peer;
}
this will be executed on the other peer. Ignore video for now
var signallingCallback = Notify.registerCallback("signalling" , function (notify) {
if (!notify.sendBySelf) {
var clientInfo = notify.params;
if (clientInfo.video) {
getMediaClient(clientInfo.id, false).then( function (peer) {
console.log('getMedieClient, peer, video: ' , peer);
peer.signal(clientInfo.signal);
});
} else {
var peer = connectToClient(clientInfo.id, false, false);
peer.signal(clientInfo.signal);
}
}
});
I hope somebody can help me or suggest something.
After closing the connection and trying to reconnect
Are you trying to reconnect using the same simple-peer instance? You'll need to create a separate instance for this to happen.
Are you trying to reconnect using the same simple-peer instance? You'll need to create a separate instance for this to happen.
Hi. Everytime I try to reconnect I:
As far as I understand it I am creating a new instace. The relevant code for that is shown above. I can paste more if needed.
I don't see peer.destroy() in snippet above. I only see clients[id].peer === undefined, which indicates that you're trying to use the same connection again even after it was destroyed.
In one of my projects simple-peer opens and closes a lot of connections (hundreds to thousands) without issues.
Make sure you don't reuse instance after destruction and it will work fine.
I did try to avoid that since it is a little bit more complex, but there you go. Peers destroy and setting undefined it pretty reliable.
Although, peer.on(signal) is called quite often. I create exactly one SimplePeer-Object on every session and do not like overwriting. Might that be the issue?
function setupPeer(peer) {
peer.on('connect', function () {...});
peer.on('close', function (msg) {
console.log('connection closed by peer: ' , peer);
peer.destroy( function (err) {
console.log('error during destruction of peer in client ' , id , '\n error: ' , err);
});
removePeerFromClient(peer);
});
peer.on('stream', function (stream) {...});
peer.on('error', function (error) {...});
peer.on('data', newDataChannelMessage);
}
function removePeerFromClient(peer) {
var peerId = getIdFromPeer(peer);
if (peerId > 0 ) {
disconnectFromClient(peerId);
}
}
function getIdFromPeer(peer) {
var peerId = 0;
angular.forEach(clients, function(client, id) {
if (peer == client.peer) {
peerId = id;
}
});
return peerId;
}
function disconnectFromClient(id) {
clients[id].peer.destroy( function (err) {
console.log('error during destruction of peer in client ' , id , '\n error: ' , err);
});
if (clients[id].peer) {
clients[id].peer = undefined;
}
}
In signallingCallback you need to check if connection was already destroyed, this probably why you're getting error.
Also you don't need to call peer.destroy() in close event handler, as it is called automatically for you.
Closing for now, since the issue is not in simple-peer and it heavily depends on your code base.
If you still believe there is an issue in simple-peer, please try to get reduced demo with steps to reproduce the issue.
you need to check if connection was already destroyed
this would only prevent the peer.signal function to trigger. What is the purpose of that?
Even tho, how to check for that? connectToClient() will always return a SimplePeer-object and there is nothing like apeer.isDestroyedproperty (why should there be). If something did (ever) close the connection,connectToClientwill always return a fresh SimplePeer-Object.
It kind of works on it's own again after some time of doing nothing. Not even reloading or anything. Looks like it's something with the ICE Servers?
please try to get reduced demo with steps to reproduce the issue.
I would but I don't know any way of getting signalling outside my own servers.
Looking more at source code I see that check for destroying in .signal() call is not actually needed, it is already checked internally.
When code reaches setRemoteDescription() call, input passes basic checks, so it should be valid. Make sure signaling messages are sent in correct order and each one exactly once.
You can always make 2 nodes on the same page and send signaling directly like in readme: https://github.com/feross/simple-peer#data-channels
You don't really need a server for simple reduced example, having 2 nodes on the same page also rules out potential signaling transport implementation issues.
Also would be nice to know which browser you're testing it with.
Also would be nice to know which browser you're testing it with.
I am on Chromium 65.0.3325.162.
Make sure signaling messages are sent in correct order and each one exactly once.
exactly once might help out. I guess. peer.on.signal() is called multiple times in my code. Usually 4 times or so.
As far as I understand the order-process, only the caller has the initiator: true-Option.
I am working on a jsfidle now.
.signal() might be called multiple times, it is correct, I meant code shouldn't accidentally mix the order of signal messages and shouldn't call with the same message twice. Lots of things might go wrong, demo will definitely help.
You can always make 2 nodes on the same page and send signaling directly like in readme:
It seems not to work. To me, it looks like there is really a server and multiple clients needed to get anything running at all.
https://jsfiddle.net/h1y7khk6/1/
That fiddle works fine for me. Can you try this modified one and post the console log?
This fiddle is equivalent to my code.
https://jsfiddle.net/h1y7khk6/47/
Somehow it works in Firefox without any problems.
Connecting, Disconnecting, Reconnecting, everything is flawless.
My own project is not working in Firefox at all.
The peer.signal is called 30 to 40 times, I think my firefox simply does not want so send that many requests to a server and therefore throws ICE issues. This fiddle also does not work in Chrome.
Maybe somebody can spot an issue there?
You call connectToClient once at the beginning and then each time you receive signal event. In connectToClient however you subscribe to signal event. Hence, for each signal event dispatched by simple-peer you'll get multiple notify->peer.signal calls, which in turn causes issues.
Separate instance creation from getting existing instance and it should be fine.
Indeed. That does the trick. Thank you!
@nazar-pc You're so patient and persistent in this issue to help figure out @tsiegleauq's issue. Thanks for being awesome.
Most helpful comment
That fiddle works fine for me. Can you try this modified one and post the console log?
https://jsfiddle.net/h1y7khk6/2/