WebRTC: simultaneous renegotiation issue - javascript

Use Case: three peers are in video chat with other two in same room, server sends a message and all three change mode to audio,
for now, only chrome supports renegotiation, so for firefox, i just close the connection and create new peer connection, but after I check both sides are chrome and change mode,
If I am changing mode of only one peer at a time, it works smoothly.
but when, message comes from server, both peers try to renegotiate simultaneously and it didn't work, I got something like wrong state : STATE_SENTINITIATE
To handle that problem, I did a workaround where, whenever peerconnection has to renegotiate, it checks if it is the caller
if yes, it goes ahead with renegotiation.
else(if it is answerer), it would change the offered stream and signal the caller to renegotiate.
The above workaround works for few renegotiations, but for some cases, it throws error at setting local description on the answerer's side, claiming wrong state to be either STATE_INPROGRESS or STATE_SENTACCEPT.
how do I resolve this issue?

Since renegotiation is a state-machine, having both sides initiate renegotiation at the same time can collide, and you end up with invalid-state errors. This is called glare.
Your workaround is one way to deal with glare, essentially using signaling to make sure renegotiation is always initiated from the same end (typically the offerer's side).
You say you're still seeing occasional invalid-state errors even with this workaround. Since renegotiation is a round-trip between peers, there's a window of time where if you're also responding to signaling requests for new renegotiation, I suppose you could still get invalid-state errors if you try to renegotiate again too soon.
You can check the pc.signalingState attribute to know what state your peerConnection is in at any time. I would look at that when you receive incoming messages, to see if this is the problem. If it is, I would hold off on renegotiating until your connection is in the "stable" state again. You can use pc.onsignalingstatechange to react to state changes.
Another solution I've heard of (but not tried) to solve glare would be to let peers renegotiate independently, and when they do glare, let the offerer always win. e.g. the answerer would cancel any attempts it was making on receiving an incoming offer (by somehow reverting itself back to its previous stable state), whereas the offerer would ignore any incoming offers during its own attempt.
By the way, renegotiation is supported in Firefox as well now (38+) so you could try it there as well to see if you get the same problems.

Related

WebRTC reconnect after lost connection (reconnection attempt)

I have a working WebRTC JavaScript application. Here is the problem: if during a web call there is a bad network connection, the call is stopped without WebRTC attempting to reconnect.
I would like to improve the code of my application by adding an automatic reconnection attempt system, however, in order to do so I need to understand some theory about WebRTC (and I think this can be very useful for many other developers).
Here are my questions:
Does WebRTC have a native functionality to attempt reconnection if the network is bad or should I listen for some "disconnection tigger" and call "a function" to start a new connection from zero?
If things cannot be done magically, what is/are the right "disconnection tigger/s" and "the function" from which the reconnection attempt process should restart? Is there something that can (or should) be taken from the previous connection?
I have read about an {iceRestart: true} parameter. Should this be used for the first call (then WebRTC will magically handle disconnection by attempting to reconnect) or should I use it ONLY when my code attempts to reconnect (on the 2nd, 3rd times...)?
What is the difference between connectionState "disconnected", "failed" and "closed" and does it have something to do with attempting to reconnect with bad network?
What is the best way to avoid attempting to reconnect in an infinite loop if there is no hope to reconnect (i.e: internet completely down)?
What is the difference between oniceconnectionstatechange and onconnectionstatechange? which is relevant in my case?
Thanks!
Luca
I was able to find the (JavaScript) solution through experimenting...
1) Does WebRTC have a native functionality to attempt reconnection if the network is bad or should I listen for some "disconnection tigger" and call "a function" to start a new connection from zero?
Yes, it does it by default in JavaScript, unless your code handles disconnection by proactively terminating the call through additional lines of instructions.
2) If things cannot be done magically, what is/are the right "disconnection tigger/s" and "the function" from which the reconnection attempt process should restart? Is there something that can (or should) be taken from the previous connection?
Things already happen under the hood (by magic). If the code terminates the call, it is probably because the disconnection trigger (ICE connection state = disconnected OR connection state = disconnected) triggers some additional code from the app you copy/pasted from somewhere.
3) I have read about an {iceRestart: true} parameter. Should this be used for the first call (then WebRTC will magically handle disconnection by attempting to reconnect) or should I use it ONLY when my code attempts to reconnect (on the 2nd, 3rd times...)?
Not useful in this scenario.
4) What is the difference between connectionState "disconnected", "failed" and "closed" and does it have something to do with attempting to reconnect with bad network?
You have to listen for connectionState = disconnected, the other ones don't matter for this purpose.
5) What is the best way to avoid attempting to reconnect in an infinite loop if there is no hope to reconnect (i.e: internet completely down)?
No problem, the reconnection WebRTC handles automatically will not cost anything in terms of signaling, therefore, you can try to reconnect as many times as you want, the user will eventually exit the call on his own if things are too slow.
6) What is the difference between oniceconnectionstatechange and onconnectionstatechange? which is relevant in my case?
No difference in this case, the only difference is that the ice state change is triggered right before the connection state change.
--
I hope this will be helpful to somebody!

Is it possible to convert a WebRTC SDP offer to answer?

I have two peers that want to connect to each other via WebRTC. Typically the first peer would create an offer and send it to the second via a signalling channel/server, the second peer would respond with an answer. This scenario works fine.
However, is it possible to support the case where both peers happen to try to connect to each other simultaneously both sending SDP offers to one another concurrently via the signalling server.
// Both peers do this simultaneously:
const conn = new RTCPeerConnection(null);
const sdpOffer = await conn.createOffer();
await conn.setLocalDescription(sdpOffer);
signalingService.send(peerId, sdpOffer);
// At some point in the future both peers also receive an SDP offer
// (rather than answer) from the other peer whom they sent an offer to
// via the signaling service. If this was an answer we'd call
// RTCPeerConnection.setRemoteDescription, however this doesn't work for an
// offer:
conn.setRemoteDescription(peerSDPOffer);
// In Chrome results in "DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer"
I even tried to "convert" the received peer offers into answers by rewriting the SDP type from offer to answer and setup:actpass to setup:active but that doesn't seem to work, instead I just get a new exception.
So the question is, is this simultaneous connect/offer use case supported in some fashion - or should I close one side/peer RTCPeerConnection & instantiate a new one using RTCPeerConnection.createAnswer this time?
This situation is known as "signaling glare". The WebRTC API does not really define how to handle this (except for something called "rollback" but it is not implemented in any browser yet and nobody has missed it so far) so you have to avoid this situation yourself.
Simply replacing the a=setup won't work since the underlying DTLS mechanism still needs the concept of a client and a server.
The answer for how to avoid glare these days is to use the Perfect Negotiation Pattern: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation
However, what OP described does work with the slight modification of setting setup:active on one peer and setup:passive on the other:
https://codepen.io/evan-brass/pen/mdpgWGG?editors=0010
It might not work for audio / video connections (because those may require negotiating codecs), but I've tested it on Chrome / Firefox / Safari for DataChannel only connections.
You could choose which peer is active and which is passive using whatever system you use to determine 'politeness' in perfect negotiation. One possibility would be to compare the DTLS fingerprints and make whichever one is larger the active peer.

Server-Sent Event connections are not closed despite calling `close()`

I want to programmatically close the Server-Sent-Events once a user logs out. However when the user logs back in, the browser does not execute any https-requests anymore because it has reached its limit of SSE connections.
I'm using EventSource for listening to events.
This is how I close my connections:
var eventSource;
function onChange(accountId:string, callback){
var url = "...";
eventSource = new EventSource(url);
if(eventSource){
eventSource.addEventListener("put", callback);
}
}
function close(){
this.eventSource.close()
}
When I was observing the network connections on the browser, I realized the connection still exists. The output in Timing is: Caution: request is not finished yet!, the following event-streams are stalled due to limited number of connections.
I'm not sure if EventSource is designed to behave like this, but I could not find anything regarding this issue, since many people don't have the same scenario.
Everytime I reload the page in my browser (chrome) all existing connections are closed, but I don't want to reload the page to workaround this issue.
Make sure that this.eventSource is referring to what you think it is. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this If called from an event handler it won't be the global scope.
After calling xxx.close(), set xxx to null. (I doubt that is the problem, but it might help the garbage collection, and also helps find bugs.)
As a 3rd idea of the problem, avoid giving globals the same name as built-in objects. I normally use es for my event source objects.

What is the proper way to reject a WebRTC offer?

In WebRTC, there seems to be a very well-defined order in which things happen.
Locally I use getUserMedia to get my local stream, and save the stream to a variable. I create an RTCPeerConnection object, which I name pc, and I add the local stream to it. I add an onaddstream event handler to pc, so that I can save the remote user's stream to a variable, and eventually set it as the src attribute of an HTML element like audio. I also set onicecandidate event handler on my pc to handle ice candidates.
At this point, there is an RTCPeerConnection, but no remote user "connected yet". This is where the "offer/answer" starts. Let's say I'm using websockets for signaling and I receive an offer, which is a message called 'offer' and containing an SDP object. How do I reject it and how should this be dealt with on both endpoints?
For instance, I could send a message 'reject' that would be relayed to the other user. My RTCPeerConnection still exists, and maybe I want to be able to receive other calls. As is, I don't have to do anything to my RTCPeerConnection, correct? Does the other user, who sent the offer, have to do anything? Does he have to close that particular RTCPeerConnection? I would think not, since all he did was create an SDP object, and then outside of WebRTC, through websockets, sent the object over to the other user. He did add the offer using setLocalDescription though. When the offer is rejected, does he need to do anything about this?
When I create an offer, and send it to the other user, if I never get an answer back, can I just send an offer to a third user and then if he sends an answer I'm connected with him?
I haven't found anything about the lifecycle of an RTCPeerConnection.
Proper (spec) way to reject media
The "proper" way to "reject" offered media in an answer hasn't been implemented in any browser yet:
pc.ontrack = e => e.transceiver.stop();
Basically, the WebRTC 1.0 spec has changed quite significantly in this area. In short, a transceiver is an object combining one sender and one receiver, each sending or receiving a single track.
transceiver.stop() lets you reject a single bidirectional m-line (negotiated media) in the signaled SDP media description. E.g. you can reject parts of an offer in your answer, without rejecting the whole thing.
Today
Today, the only way to reject individual m-lines is through mangling the SDP offer/answer manually.
But it sounds like you're not actually asking about that at all. Instead, it sounds like you're asking how to bail out of incomplete signaling and roll a peer connection back to "stable" state.
Rollback to stable state
The offer/answer negotiation cycle is a state machine. The state is pc.signalingState:
You asked if one side walks away from the negotiation, does either side need to do anything before they can re-purpose their connection object for a new attempt with the same or different peer. Well, it depends.
If you've only called createOffer then no rollback of state is necessary, since createOffer is not in the above diagram.
If you've called setLocalDescription however, then you're now in "have-local-offer" state, which means you do need to somehow get back to "stable" state before you can reuse the connection.
Your options are to either finish the negotiation, delete the connection, or rollback to stable state (currently only supported in Firefox, though it is in the spec):
let pc = new RTCPeerConnection();
pc.onnegotiationneeded = async e => {
try {
await pc.setLocalDescription(await pc.createOffer());
console.log(pc.signalingState); // have-local-offer
await pc.setLocalDescription({type: "rollback"});
console.log(pc.signalingState); // stable
} catch(e) {
console.log(e);
}
}
pc.createDataChannel("dummy");
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
Of course, you should also let the peer know, usually through out-of-band signaling.
Why this is typically not a problem.
In typical cases, you own the JavaScript on both ends, so this doesn't come up. In other words, the desire to have a connection usually precedes one being made.

Firebase Callbacks - what's the underlying trigger?

I understand that in Firebase I can register my page for callbacks with the "on" method.
According to their docs:
on( ) is used to listen for data changes at a particular location.
This is the primary way to read data from Firebase.
firebaseRef.on('value', function(dataSnapshot) {
// code to handle new value.
});
My question is:
How does it work ?
How does it know that something has changed on the serverside ?
(better) How does the server can 'callback' the browser ?
One answer might be that it is "polling". But I have seen no reference about this approach in Firebase documentation or properties to configure polling time ...
Does anybody know ?
Many
Thanks
Firebase uses WebSockets to allow the server to "push" data to the client. Since not all browser versions support WebSockets yet, it also falls back to long polling for those browsers.
The implementation details of how that works on the server are proprietary and sophisticated--enough to write a book about and beyond the scope of a SO question. Logically, works exactly as advertised: The service is designed so that any time a set(), push(), or update() is called (or the REST equivalents), it notifies any listeners of the change.
Regardless of whether the browser uses WebSockets or not, there is no "polling time" as the client is not repeatedly contacting the server. Long polling means waiting for a data change to occur, rather than polling repeatedly to see if a change has occurred. As you can see by trying out the tutorial or any of the real-time examples, data changes are synced to all clients in a matter of milliseconds--nothing to configure.

Categories

Resources