Improve WebRTC Stability

Improve the stability of the WebRTC IceCandidate negotiation when the
round trip time to the Rendezvous server is large (> 400ms) which can be
the case when the Rendezvous server is on the other side of the planet!

Change-Id: I7a8883d30696ada2ea13393fde82f1d8f49bbc0c
parent 0eef5a9e
......@@ -1446,6 +1446,8 @@ Blockly.Msg.en.switch_language_to_english = {
Blockly.Msg.REPL_YOUR_CODE_IS = "Your code is";
Blockly.Msg.REPL_DO_YOU_REALLY_Q = "Do You Really?";
Blockly.Msg.REPL_FACTORY_RESET = 'This will attempt to reset your Emulator to its "factory" state. If you had previously updated the Companion installed in the Emulator, you will likely have to do this again.';
Blockly.Msg.REPL_WEBRTC_CONNECTION_ERROR = "Companion Connection Error";
// Messages from Blockly.js
Blockly.Msg.WARNING_DELETE_X_BLOCKS = "Are you sure you want to delete all %1 of these blocks?";
......
......@@ -334,6 +334,24 @@ Blockly.ReplMgr.putYail = (function() {
var offer;
var key = rs.replcode;
var haveoffer = false;
var connectionstate = "none";
var webrtcerror = function(doalert, msg) {
webrtcdata = null;
webrtcstarting = false;
webrtcrunning = false;
webrtcforcestop = true;
top.BlocklyPanel_indicateDisconnect();
top.ConnectProgressBar_hide();
if (!webrtcpeer) {
webrtcpeer.close();
}
if (doalert) {
var dialog = new Blockly.Util.Dialog(Blockly.Msg.REPL_NETWORK_ERROR, msg, Blockly.Msg.REPL_OK, false, null, 0,
function() {
dialog.hide();
});
}
};
webrtcisopen = false;
webrtcforcestop = false;
top.ConnectProgressBar_setProgress(20, Blockly.Msg.DIALOG_SECURE_ESTABLISHING);
......@@ -348,11 +366,17 @@ Blockly.ReplMgr.putYail = (function() {
var hunk = json[i];
var candidate = hunk['candidate'];
offer = hunk['offer'];
if (candidate && haveoffer) {
if (candidate && haveoffer && connectionstate != "none") {
var nonce = hunk['nonce'];
if (!seennonce[nonce]) {
seennonce[nonce] = true;
webrtcpeer.addIceCandidate(candidate);
console.log("addIceCandidate: signalingState = " + webrtcpeer.signalingState +
" iceConnectionState = " + webrtcpeer.iceConnectionState);
console.log("addIceCandidate: candidate = " + JSON.stringify(candidate));
webrtcpeer.addIceCandidate(candidate)["catch"](function(e) {
console.error(e);
webrtcerror(true, Bockly.Msg.REPL_WEBRTC_CONNECTION_ERROR + "\n" + e);
});
} else {
console.log("Seen nonce " + nonce);
}
......@@ -368,24 +392,24 @@ Blockly.ReplMgr.putYail = (function() {
if (!webrtcisopen && !webrtcforcestop) {
setTimeout(poll, 1000); // Try again in one second
}
} else if (this.readyState == 4) { // Done, but didn't get a 200 back
webrtcerror(true, Blockly.Msg.REPL_WEBRTC_CONNECTION_ERROR + "\n" + "Rendezvous Fail: " + this.status);
}
};
xhr.send();
};
webrtcpeer = new RTCPeerConnection(top.ReplState.iceservers);
webrtcpeer.oniceconnectionstatechange = function(evt) {
console.log("oniceconnectionstatechange: evt.type = " + evt.type);
if (this.iceConnectionState == "disconnected" ||
this.iceConnectionState == "failed") {
webrtcdata = null;
webrtcstarting = false;
webrtcrunning = false;
top.BlocklyPanel_indicateDisconnect();
webrtcpeer.close();
console.log("oniceconnectionstatechange: evt.type = " + evt.type + " connection state = " + this.iceConnectionState);
connectionstate = this.iceConnectionState;
if (connectionstate == "disconnected" ||
connectionstate == "failed") {
webrtcerror(true, Blockly.Msg.REPL_WEBRTC_CONNECTION_ERROR + "\n" + "state = " + connectionstate);
}
};
webrtcpeer.onsignalingstatechange = function(evt) {
console.log("onsignalingstatechange: evt.type = " + evt.type);
console.log("onsignalingstatechange: signalingstate = " + this.signalingState);
};
webrtcpeer.onicecandidate = function(evt) {
if (evt.type == 'icecandidate') {
......
......@@ -11,6 +11,7 @@ import android.util.Log;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.ReplForm;
import com.google.appinventor.components.runtime.util.AsynchUtil;
import java.io.BufferedReader;
import java.io.IOException;
......@@ -75,6 +76,7 @@ public class WebRTCNativeMgr {
private boolean haveOffer = false;
private String rCode;
private volatile boolean keepPolling = true;
private volatile boolean haveLocalDescription = false;
private boolean first = true; // This is used for logging in the Rendezvous server
private Random random = new Random();
private DataChannel dataChannel = null;
......@@ -86,7 +88,11 @@ public class WebRTCNativeMgr {
/* Callback that handles sdp offer/answers */
SdpObserver sdpObserver = new SdpObserver() {
public void onCreateFailure(String str) {
if (DEBUG) {
Log.d(LOG_TAG, "onCreateFailure: " + str);
}
}
public void onCreateSuccess(SessionDescription sessionDescription) {
......@@ -97,9 +103,16 @@ public class WebRTCNativeMgr {
}
DataChannel.Init init = new DataChannel.Init();
if (sessionDescription.type == SessionDescription.Type.OFFER) {
if (DEBUG) {
Log.d(LOG_TAG, "Got offer, about to set remote description (again?)");
}
peerConnection.setRemoteDescription(sdpObserver, sessionDescription);
} else if (sessionDescription.type == SessionDescription.Type.ANSWER) {
if (DEBUG) {
Log.d(LOG_TAG, "onCreateSuccess: type = ANSWER");
}
peerConnection.setLocalDescription(sdpObserver, sessionDescription);
haveLocalDescription = true;
/* Send to peer */
JSONObject offer = new JSONObject();
offer.put("type", "answer");
......@@ -254,7 +267,7 @@ public class WebRTCNativeMgr {
/* Create the peer connection using the iceServers we received in the constructor */
peerConnection = factory.createPeerConnection(iceServers, new MediaConstraints(),
observer);
timer.scheduleAtFixedRate(new TimerTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
Poller();
......@@ -343,6 +356,9 @@ public class WebRTCNativeMgr {
Log.d(LOG_TAG, "type = " + type);
Log.d(LOG_TAG, "About to set remote offer");
}
if (DEBUG) {
Log.d(LOG_TAG, "Got offer, about to set remote description (maincode)");
}
peerConnection.setRemoteDescription(sdpObserver,
new SessionDescription(SessionDescription.Type.OFFER, sdp));
peerConnection.createAnswer(sdpObserver, new MediaConstraints());
......@@ -351,11 +367,29 @@ public class WebRTCNativeMgr {
}
i = -1;
} else if (element.has("nonce")) {
if (element.isNull("candidate")) {
if (!haveLocalDescription) {
if (DEBUG) {
Log.d(LOG_TAG, "Received a null candidate, skipping...");
Log.d(LOG_TAG, "Incoming candidate before local description set, punting");
}
return;
}
if (element.has("offer")) { // Only take in the offer once!
i++;
if (DEBUG) {
Log.d(LOG_TAG, "skipping offer, already processed");
}
continue;
}
if (element.isNull("candidate")) {
i++;
String nonce = element.optString("nonce");
if (!seenNonces.contains(nonce)) {
if (DEBUG) {
Log.d(LOG_TAG, "Received a null candidate, done?");
}
peerConnection.addIceCandidate(new IceCandidate("", 0, ""));
seenNonces.add(nonce);
}
continue;
}
String nonce = element.optString("nonce");
......@@ -363,13 +397,11 @@ public class WebRTCNativeMgr {
String sdpcandidate = candidate.optString("candidate");
String sdpMid = candidate.optString("sdpMid");
int sdpMLineIndex = candidate.optInt("sdpMLineIndex");
if (DEBUG) {
Log.d(LOG_TAG, "candidate = " + sdpcandidate);
}
if (!seenNonces.contains(nonce)) {
seenNonces.add(nonce);
if (DEBUG) {
Log.d(LOG_TAG, "new nonce, about to add candidate!");
Log.d(LOG_TAG, "candidate = " + sdpcandidate);
}
IceCandidate iceCandidate = new IceCandidate(sdpMid, sdpMLineIndex, sdpcandidate);
peerConnection.addIceCandidate(iceCandidate);
......@@ -392,29 +424,34 @@ public class WebRTCNativeMgr {
}
}
private void sendRendezvous(JSONObject data) {
try {
data.put("first", first);
data.put("webrtc", true);
data.put("key", rCode + "-r");
if (first) {
first = false;
data.put("apiversion", SdkLevel.getLevel());
}
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost("http://" + rendezvousServer2 + "/rendezvous2/");
try {
if (DEBUG) {
Log.d(LOG_TAG, "About to send = " + data.toString());
private void sendRendezvous(final JSONObject data) {
AsynchUtil.runAsynchronously(new Runnable() {
@Override
public void run() {
try {
data.put("first", first);
data.put("webrtc", true);
data.put("key", rCode + "-r");
if (first) {
first = false;
data.put("apiversion", SdkLevel.getLevel());
}
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost("http://" + rendezvousServer2 + "/rendezvous2/");
try {
if (DEBUG) {
Log.d(LOG_TAG, "About to send = " + data.toString());
}
post.setEntity(new StringEntity(data.toString()));
client.execute(post);
} catch (IOException e) {
Log.e(LOG_TAG, "sendRedezvous IOException", e);
}
} catch (Exception e) {
Log.e(LOG_TAG, "Exception in sendRendezvous", e);
}
}
post.setEntity(new StringEntity(data.toString()));
client.execute(post);
} catch (IOException e) {
Log.e(LOG_TAG, "sendRedezvous IOException", e);
}
} catch (Exception e) {
Log.e(LOG_TAG, "Exception in sendRendezvous", e);
}
});
}
public void send(String output) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment