6 package com.google.appinventor.components.runtime.util;
8 import android.content.Context;
10 import android.util.Log;
16 import java.io.BufferedReader;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.UnsupportedEncodingException;
21 import java.nio.ByteBuffer;
22 import java.nio.charset.CharacterCodingException;
23 import java.nio.charset.Charset;
24 import java.nio.charset.CharsetDecoder;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Random;
29 import java.util.Timer;
30 import java.util.TimerTask;
31 import java.util.TreeSet;
33 import org.apache.http.HttpResponse;
34 import org.apache.http.client.HttpClient;
35 import org.apache.http.client.methods.HttpGet;
36 import org.apache.http.client.methods.HttpPost;
38 import org.apache.http.entity.StringEntity;
39 import org.apache.http.entity.StringEntity;
41 import org.apache.http.impl.client.DefaultHttpClient;
43 import org.json.JSONArray;
44 import org.json.JSONException;
45 import org.json.JSONObject;
47 import org.webrtc.DataChannel.Buffer;
48 import org.webrtc.DataChannel;
49 import org.webrtc.IceCandidate;
50 import org.webrtc.MediaConstraints;
51 import org.webrtc.MediaStream;
52 import org.webrtc.PeerConnection.ContinualGatheringPolicy;
53 import org.webrtc.PeerConnection.IceConnectionState;
54 import org.webrtc.PeerConnection.IceGatheringState;
55 import org.webrtc.PeerConnection.Observer;
56 import org.webrtc.PeerConnection.RTCConfiguration;
57 import org.webrtc.PeerConnection.SignalingState;
58 import org.webrtc.PeerConnection;
59 import org.webrtc.PeerConnectionFactory;
60 import org.webrtc.RtpReceiver;
61 import org.webrtc.SdpObserver;
62 import org.webrtc.SessionDescription;
67 private static final boolean DEBUG =
true;
69 private static final String LOG_TAG =
"AppInvWebRTC";
70 private static final CharsetDecoder utf8Decoder = Charset.forName(
"UTF-8").newDecoder();
74 private PeerConnection peerConnection;
77 private TreeSet<String> seenNonces =
new TreeSet();
78 private boolean haveOffer =
false;
80 private volatile boolean keepPolling =
true;
81 private volatile boolean haveLocalDescription =
false;
82 private boolean first =
true;
83 private Random random =
new Random();
84 private DataChannel dataChannel =
null;
85 private String rendezvousServer =
null;
86 private String rendezvousServer2 =
null;
87 private List<PeerConnection.IceServer> iceServers =
new ArrayList();
89 Timer timer =
new Timer();
92 SdpObserver sdpObserver =
new SdpObserver() {
94 public void onCreateFailure(String str) {
96 Log.d(LOG_TAG,
"onCreateFailure: " + str);
100 public void onCreateSuccess(SessionDescription sessionDescription) {
103 Log.d(LOG_TAG,
"sdp.type = " + sessionDescription.type.canonicalForm());
104 Log.d(LOG_TAG,
"sdp.description = " + sessionDescription.description);
106 DataChannel.Init init =
new DataChannel.Init();
107 if (sessionDescription.type == SessionDescription.Type.OFFER) {
109 Log.d(LOG_TAG,
"Got offer, about to set remote description (again?)");
111 peerConnection.setRemoteDescription(sdpObserver, sessionDescription);
112 }
else if (sessionDescription.type == SessionDescription.Type.ANSWER) {
114 Log.d(LOG_TAG,
"onCreateSuccess: type = ANSWER");
116 peerConnection.setLocalDescription(sdpObserver, sessionDescription);
117 haveLocalDescription =
true;
119 JSONObject offer =
new JSONObject();
120 offer.put(
"type",
"answer");
121 offer.put(
"sdp", sessionDescription.description);
122 JSONObject response =
new JSONObject();
123 response.put(
"offer", offer);
124 sendRendezvous(response);
126 }
catch (Exception e) {
127 Log.e(LOG_TAG,
"Exception during onCreateSuccess", e);
131 public void onSetFailure(String str) {
134 public void onSetSuccess() {
139 Observer observer =
new Observer() {
140 public void onAddStream(MediaStream mediaStream) {
143 public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreamArr) {
146 public void onDataChannel(DataChannel dataChannel) {
148 Log.d(LOG_TAG,
"Have Data Channel!");
149 Log.d(LOG_TAG,
"v5");
152 dataChannel.registerObserver(dataObserver);
156 Log.d(LOG_TAG,
"Poller() Canceled");
161 public void onIceCandidate(IceCandidate iceCandidate) {
164 Log.d(LOG_TAG,
"IceCandidate = " + iceCandidate.toString());
165 if (iceCandidate.sdp ==
null) {
166 Log.d(LOG_TAG,
"IceCandidate is null");
168 Log.d(LOG_TAG,
"IceCandidateSDP = " + iceCandidate.sdp);
172 JSONObject response =
new JSONObject();
173 response.put(
"nonce", random.nextInt(100000));
174 JSONObject jsonCandidate =
new JSONObject();
175 jsonCandidate.put(
"candidate", iceCandidate.sdp);
176 jsonCandidate.put(
"sdpMLineIndex", iceCandidate.sdpMLineIndex);
177 jsonCandidate.put(
"sdpMid", iceCandidate.sdpMid);
178 response.put(
"candidate", jsonCandidate);
179 sendRendezvous(response);
180 }
catch (Exception e) {
181 Log.e(LOG_TAG,
"Exception during onIceCandidate", e);
185 public void onIceCandidatesRemoved(IceCandidate[] iceCandidateArr) {
188 public void onIceConnectionChange(IceConnectionState iceConnectionState) {
191 public void onIceConnectionReceivingChange(
boolean z) {
194 public void onIceGatheringChange(IceGatheringState iceGatheringState) {
196 Log.d(LOG_TAG,
"onIceGatheringChange: iceGatheringState = " + iceGatheringState);
200 public void onRemoveStream(MediaStream mediaStream) {
203 public void onRenegotiationNeeded() {
206 public void onSignalingChange(SignalingState signalingState) {
208 Log.d(LOG_TAG,
"onSignalingChange: signalingState = " + signalingState);
214 DataChannel.Observer dataObserver =
new DataChannel.Observer() {
215 public void onBufferedAmountChange(
long j) {
218 public void onMessage(Buffer buffer) {
221 input = utf8Decoder.decode(buffer.data).toString();
222 }
catch (CharacterCodingException e) {
223 Log.e(LOG_TAG,
"onMessage decoder error", e);
227 Log.d(LOG_TAG,
"onMessage: received: " + input);
232 public void onStateChange() {
237 this.rendezvousServer = rendezvousServer;
238 if (rendezvousResult.isEmpty() || rendezvousResult.startsWith(
"OK")) {
241 "\"iceservers\" : " +
242 "[{ \"server\" : \"stun:stun.l.google.com:19302\" }," +
243 "{ \"server\" : \"turn:turn.appinventor.mit.edu:3478\"," +
244 "\"username\" : \"oh\"," +
245 "\"password\" : \"boy\"}]}";
248 JSONObject resultJson =
new JSONObject(rendezvousResult);
249 this.rendezvousServer2 = resultJson.getString(
"rendezvous2");
250 JSONArray iceServerArray = resultJson.getJSONArray(
"iceservers");
251 this.iceServers =
new ArrayList(iceServerArray.length());
252 for (
int i = 0; i < iceServerArray.length(); i++) {
253 JSONObject jsonServer = iceServerArray.getJSONObject(i);
254 PeerConnection.IceServer.Builder builder = PeerConnection.IceServer.builder(jsonServer.getString(
"server"));
256 Log.d(LOG_TAG,
"Adding iceServer = " + jsonServer.getString(
"server"));
258 if (jsonServer.has(
"username")) {
259 builder.setUsername(jsonServer.getString(
"username"));
261 if (jsonServer.has(
"password")) {
262 builder.setPassword(jsonServer.getString(
"password"));
264 this.iceServers.add(builder.createIceServer());
266 }
catch (JSONException e) {
267 Log.e(LOG_TAG,
"parsing iceServers:", e);
276 PeerConnectionFactory.initializeAndroidGlobals(context,
false);
278 PeerConnectionFactory.Options options =
new PeerConnectionFactory.Options();
280 PeerConnectionFactory factory =
new PeerConnectionFactory(options);
282 RTCConfiguration rtcConfig =
new RTCConfiguration(iceServers);
283 rtcConfig.continualGatheringPolicy = ContinualGatheringPolicy.GATHER_CONTINUALLY;
284 peerConnection = factory.createPeerConnection(rtcConfig,
new MediaConstraints(),
286 timer.schedule(
new TimerTask() {
299 private void Poller() {
306 Log.d(LOG_TAG,
"Poller() Called");
307 Log.d(LOG_TAG,
"Poller: rendezvousServer2 = " + rendezvousServer2);
309 HttpClient client =
new DefaultHttpClient();
310 HttpGet request =
new HttpGet(
"http://" + rendezvousServer2 +
"/rendezvous2/" + rCode +
"-s");
311 HttpResponse response = client.execute(request);
312 StringBuilder sb =
new StringBuilder();
314 BufferedReader rd =
null;
316 rd =
new BufferedReader
317 (
new InputStreamReader(
318 response.getEntity().getContent()));
320 while ((line = rd.readLine()) !=
null) {
331 Log.d(LOG_TAG,
"keepPolling is false, we're done!");
336 String responseText = sb.toString();
339 Log.d(LOG_TAG,
"response = " + responseText);
342 if (responseText.equals(
"")) {
344 Log.d(LOG_TAG,
"Received an empty response");
350 JSONArray jsonArray =
new JSONArray(responseText);
352 Log.d(LOG_TAG,
"jsonArray.length() = " + jsonArray.length());
355 while (i < jsonArray.length()) {
357 Log.d(LOG_TAG,
"i = " + i);
358 Log.d(LOG_TAG,
"element = " + jsonArray.optString(i));
360 JSONObject element = (JSONObject) jsonArray.get(i);
362 if (!element.has(
"offer")) {
366 JSONObject offer = (JSONObject) element.get(
"offer");
367 String sdp = offer.optString(
"sdp");
368 String type = offer.optString(
"type");
371 Log.d(LOG_TAG,
"sdb = " + sdp);
372 Log.d(LOG_TAG,
"type = " + type);
373 Log.d(LOG_TAG,
"About to set remote offer");
376 Log.d(LOG_TAG,
"Got offer, about to set remote description (maincode)");
378 peerConnection.setRemoteDescription(sdpObserver,
379 new SessionDescription(SessionDescription.Type.OFFER, sdp));
380 peerConnection.createAnswer(sdpObserver,
new MediaConstraints());
382 Log.d(LOG_TAG,
"createAnswer returned");
385 }
else if (element.has(
"nonce")) {
386 if (!haveLocalDescription) {
388 Log.d(LOG_TAG,
"Incoming candidate before local description set, punting");
392 if (element.has(
"offer")) {
395 Log.d(LOG_TAG,
"skipping offer, already processed");
399 if (element.isNull(
"candidate")) {
404 String nonce = element.optString(
"nonce");
405 JSONObject candidate = (JSONObject) element.get(
"candidate");
406 String sdpcandidate = candidate.optString(
"candidate");
407 String sdpMid = candidate.optString(
"sdpMid");
408 int sdpMLineIndex = candidate.optInt(
"sdpMLineIndex");
409 if (!seenNonces.contains(nonce)) {
410 seenNonces.add(nonce);
412 Log.d(LOG_TAG,
"new nonce, about to add candidate!");
413 Log.d(LOG_TAG,
"candidate = " + sdpcandidate);
415 IceCandidate iceCandidate =
new IceCandidate(sdpMid, sdpMLineIndex, sdpcandidate);
416 peerConnection.addIceCandidate(iceCandidate);
418 Log.d(LOG_TAG,
"addIceCandidate returned");
425 Log.d(LOG_TAG,
"exited loop");
427 }
catch (IOException e) {
428 Log.e(LOG_TAG,
"Caught IOException: " + e.toString(), e);
429 }
catch (JSONException e) {
430 Log.e(LOG_TAG,
"Caught JSONException: " + e.toString(), e);
431 }
catch (Exception e) {
432 Log.e(LOG_TAG,
"Caught Exception: " + e.toString(), e);
436 private void sendRendezvous(
final JSONObject data) {
437 AsynchUtil.runAsynchronously(
new Runnable() {
441 data.put(
"first", first);
442 data.put(
"webrtc",
true);
443 data.put(
"key", rCode +
"-r");
446 data.put(
"apiversion", SdkLevel.getLevel());
448 HttpClient client =
new DefaultHttpClient();
449 HttpPost post =
new HttpPost(
"http://" + rendezvousServer2 +
"/rendezvous2/");
452 Log.d(LOG_TAG,
"About to send = " + data.toString());
454 post.setEntity(
new StringEntity(data.toString()));
455 client.execute(post);
456 }
catch (IOException e) {
457 Log.e(LOG_TAG,
"sendRedezvous IOException", e);
459 }
catch (Exception e) {
460 Log.e(LOG_TAG,
"Exception in sendRendezvous", e);
466 public void send(String output) {
468 if (dataChannel ==
null) {
469 Log.w(LOG_TAG,
"No Data Channel in Send");
472 ByteBuffer bbuffer = ByteBuffer.wrap(output.getBytes(
"UTF-8"));
473 Buffer buffer =
new Buffer(bbuffer,
false);
474 dataChannel.send(buffer);
475 }
catch (UnsupportedEncodingException e) {
476 Log.e(LOG_TAG,
"While encoding data to send to companion", e);