Clean up the Channel code in AppRTCDemo and use GAE prod server for new signaling mode.

BUG=
R=tkchin@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/31299004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7955 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
jiayl@webrtc.org 2014-12-18 20:12:03 +00:00
parent f5847d7746
commit 16a05dddb8
9 changed files with 5 additions and 484 deletions

View File

@ -14,12 +14,12 @@ Prerequisites:
Example of building & using the app:
cd <path/to/libjingle>/trunk
cd <path/to/libjingle>/src
ninja -C out/Debug AppRTCDemo
adb install -r out/Debug/AppRTCDemo-debug.apk
In desktop chrome, navigate to https://3-dot-apprtc.appspot.com and note the r=<NNN> room
this redirects to or navigate directly to https://3-dot-apprtc.appspot.com/room/<NNN> with
In desktop chrome, navigate to https://apprtc.appspot.com and note the r=<NNN> room
this redirects to or navigate directly to https://apprtc.appspot.com/r/<NNN> with
your own room number. Launch AppRTC on the device and add same <NNN> into the room name list.
You can also run application from a command line to connect to the first room in a list:

View File

@ -57,11 +57,6 @@
<string name="pref_hwcodec_dlg">Use VP8 VP8 hardware accelerated codec (if available).</string>
<string name="pref_hwcodec_default">true</string>
<string name="pref_signaling_key">signaling_preference</string>
<string name="pref_signaling_title">Use WebSocket signaling.</string>
<string name="pref_signaling_dlg">Use WebSocket signaling.</string>
<string name="pref_signaling_default">true</string>
<string name="pref_value_enabled">Enabled</string>
<string name="pref_value_disabled">Disabled</string>

View File

@ -43,10 +43,4 @@
android:dialogTitle="@string/pref_cpu_usage_detection_dlg"
android:defaultValue="@string/pref_cpu_usage_detection_default" />
<CheckBoxPreference
android:key="@string/pref_signaling_key"
android:title="@string/pref_signaling_title"
android:dialogTitle="@string/pref_signaling_dlg"
android:defaultValue="@string/pref_signaling_default" />
</PreferenceScreen>

View File

@ -215,17 +215,11 @@ public class AppRTCDemoActivity extends Activity
runTimeMs = intent.getIntExtra(ConnectActivity.EXTRA_RUNTIME, 0);
startBitrate = intent.getIntExtra(ConnectActivity.EXTRA_BITRATE, 0);
hwCodec = intent.getBooleanExtra(ConnectActivity.EXTRA_HWCODEC, true);
boolean useWebsocket = intent.getBooleanExtra(
ConnectActivity.EXTRA_WEBSOCKET, false);
if (url != null) {
if (loopback || (roomName != null && !roomName.equals(""))) {
logAndToast(getString(R.string.connecting_to, url));
if (useWebsocket) {
appRtcClient = new WebSocketRTCClient(this);
} else {
appRtcClient = new GAERTCClient(this, this);
}
appRtcClient = new WebSocketRTCClient(this);
appRtcClient.connectToRoom(url.toString(), loopback);
if (loopback) {
roomNameView.setText("loopback");

View File

@ -72,7 +72,6 @@ public class ConnectActivity extends Activity {
public static final String EXTRA_WEBSOCKET = "org.appspot.apprtc.WEBSOCKET";
private static final String TAG = "ConnectRTCClient";
private final String APPRTC_SERVER = "https://apprtc.appspot.com";
private final String APPRTC_WS_SERVER = "https://3-dot-apprtc.appspot.com";
private final int CONNECTION_REQUEST = 1;
private static boolean commandLineRun = false;
@ -89,7 +88,6 @@ public class ConnectActivity extends Activity {
private String keyprefBitrateValue;
private String keyprefHwCodec;
private String keyprefCpuUsageDetection;
private String keyprefWebsocketSignaling;
private String keyprefRoom;
private String keyprefRoomList;
private ArrayList<String> roomList;
@ -108,7 +106,6 @@ public class ConnectActivity extends Activity {
keyprefBitrateValue = getString(R.string.pref_startbitratevalue_key);
keyprefHwCodec = getString(R.string.pref_hwcodec_key);
keyprefCpuUsageDetection = getString(R.string.pref_cpu_usage_detection_key);
keyprefWebsocketSignaling = getString(R.string.pref_signaling_key);
keyprefRoom = getString(R.string.pref_room_key);
keyprefRoomList = getString(R.string.pref_room_list_key);
@ -244,10 +241,6 @@ public class ConnectActivity extends Activity {
}
private void connectToRoom(boolean loopback, int runTimeMs) {
// Check webSocket signaling flag.
boolean useWebsocket = sharedPref.getBoolean(keyprefWebsocketSignaling,
Boolean.valueOf(getString(R.string.pref_signaling_default)));
// Get room name (random for loopback).
String roomName;
if (loopback) {
@ -259,18 +252,8 @@ public class ConnectActivity extends Activity {
}
}
// Build room URL.
String url;
if (useWebsocket) {
url = APPRTC_WS_SERVER;
url += "/register/" + roomName;
} else {
url = APPRTC_SERVER;
url = appendQueryParameter(url, "r=" + roomName);
if (loopback) {
url = appendQueryParameter(url, "debug=loopback");
}
}
url = APPRTC_SERVER + "/register/" + roomName;
// Check HW codec flag.
boolean hwCodec = sharedPref.getBoolean(keyprefHwCodec,
@ -362,7 +345,6 @@ public class ConnectActivity extends Activity {
intent.putExtra(EXTRA_RUNTIME, runTimeMs);
intent.putExtra(EXTRA_BITRATE, startBitrate);
intent.putExtra(EXTRA_HWCODEC, hwCodec);
intent.putExtra(EXTRA_WEBSOCKET, useWebsocket);
startActivityForResult(intent, CONNECTION_REQUEST);
}
}

View File

@ -1,151 +0,0 @@
/*
* libjingle
* Copyright 2013, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.appspot.apprtc;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.util.Log;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* Java-land version of Google AppEngine's JavaScript Channel API:
* https://developers.google.com/appengine/docs/python/channel/javascript
*
* Requires a hosted HTML page that opens the desired channel and dispatches JS
* on{Open,Message,Close,Error}() events to a global object named
* "androidMessageHandler".
*/
public class GAEChannelClient {
private static final String TAG = "GAERTCClient";
private WebView webView;
private final ProxyingMessageHandler proxyingMessageHandler;
/**
* Callback interface for messages delivered on the Google AppEngine channel.
*/
public interface GAEMessageHandler {
public void onOpen();
public void onMessage(final String data);
public void onClose();
public void onError(final int code, final String description);
}
/** Asynchronously open an AppEngine channel. */
@SuppressLint("SetJavaScriptEnabled")
public GAEChannelClient(
Activity activity, String token, GAEMessageHandler handler) {
webView = new WebView(activity);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient() { // Purely for debugging.
public boolean onConsoleMessage (ConsoleMessage msg) {
Log.d(TAG, "console: " + msg.message() + " at " +
msg.sourceId() + ":" + msg.lineNumber());
return false;
}
});
webView.setWebViewClient(new WebViewClient() { // Purely for debugging.
public void onReceivedError(
WebView view, int errorCode, String description,
String failingUrl) {
Log.e(TAG, "JS error: " + errorCode + " in " + failingUrl +
", desc: " + description);
}
});
proxyingMessageHandler = new ProxyingMessageHandler(handler, token);
webView.addJavascriptInterface(
proxyingMessageHandler, "androidMessageHandler");
webView.loadUrl("file:///android_asset/channel.html");
}
/** Close the connection to the AppEngine channel. */
public void close() {
if (webView == null) {
return;
}
proxyingMessageHandler.disconnect();
webView.removeJavascriptInterface("androidMessageHandler");
webView.loadUrl("about:blank");
webView = null;
}
// Helper class for proxying callbacks from the Java<->JS interaction
// (private, background) thread.
private static class ProxyingMessageHandler {
private final GAEMessageHandler handler;
private boolean disconnected = false;
private final String token;
public ProxyingMessageHandler(GAEMessageHandler handler, String token) {
this.handler = handler;
this.token = token;
}
public void disconnect() {
disconnected = true;
}
@JavascriptInterface
public String getToken() {
return token;
}
@JavascriptInterface
public void onOpen() {
if (!disconnected) {
handler.onOpen();
}
}
@JavascriptInterface
public void onMessage(final String data) {
if (!disconnected) {
handler.onMessage(data);
}
}
@JavascriptInterface
public void onClose() {
if (!disconnected) {
handler.onClose();
}
}
@JavascriptInterface
public void onError(final int code, final String description) {
Log.e(TAG, "Channel error. Code: " + code +
". Description: " + description);
if (!disconnected) {
handler.onError(code, description);
}
}
}
}

View File

@ -1,290 +0,0 @@
/*
* libjingle
* Copyright 2013, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.appspot.apprtc;
import android.app.Activity;
import android.os.AsyncTask;
import android.util.Log;
import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.SessionDescription;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedList;
/**
* Negotiates signaling for chatting with apprtc.appspot.com "rooms".
* Uses the client<->server specifics of the apprtc AppEngine webapp.
*
* To use: create an instance of this object (registering a message handler) and
* call connectToRoom(). Once room connection is established
* onConnectedToRoom() callback with room parameters is invoked.
* Messages to other party (with local Ice candidates and SDP) can
* be sent after GAE channel is opened and onChannelOpen() callback is invoked.
*/
public class GAERTCClient implements AppRTCClient, RoomParametersFetcherEvents {
private static final String TAG = "GAERTCClient";
private GAEChannelClient channelClient;
private final Activity activity;
private SignalingEvents events;
private GAEChannelClient.GAEMessageHandler gaeHandler;
private SignalingParameters signalingParameters;
private RoomParametersFetcher fetcher;
private LinkedList<String> sendQueue = new LinkedList<String>();
private String postMessageUrl;
public GAERTCClient(Activity activity, SignalingEvents events) {
this.activity = activity;
this.events = events;
}
// --------------------------------------------------------------------
// AppRTCClient interface implementation.
/**
* Asynchronously connect to an AppRTC room URL, e.g.
* https://apprtc.appspot.com/?r=NNN and register message-handling callbacks
* on its GAE Channel.
*/
@Override
public void connectToRoom(String url, boolean loopback) {
fetcher = new RoomParametersFetcher(this, false, loopback);
fetcher.execute(url);
}
/**
* Disconnect from the GAE Channel.
*/
@Override
public void disconnect() {
if (channelClient != null) {
Log.d(TAG, "Closing GAE Channel.");
sendMessage("{\"type\": \"bye\"}");
channelClient.close();
channelClient = null;
gaeHandler = null;
}
}
/**
* Send local SDP (offer or answer, depending on role) to the
* other participant. Note that it is important to send the output of
* create{Offer,Answer} and not merely the current value of
* getLocalDescription() because the latter may include ICE candidates that
* we might want to filter elsewhere.
*/
@Override
public void sendOfferSdp(final SessionDescription sdp) {
JSONObject json = new JSONObject();
jsonPut(json, "type", "offer");
jsonPut(json, "sdp", sdp.description);
sendMessage(json.toString());
}
@Override
public void sendAnswerSdp(final SessionDescription sdp) {
JSONObject json = new JSONObject();
jsonPut(json, "type", "answer");
jsonPut(json, "sdp", sdp.description);
sendMessage(json.toString());
}
/**
* Send Ice candidate to the other participant.
*/
@Override
public void sendLocalIceCandidate(final IceCandidate candidate) {
JSONObject json = new JSONObject();
jsonPut(json, "type", "candidate");
jsonPut(json, "label", candidate.sdpMLineIndex);
jsonPut(json, "id", candidate.sdpMid);
jsonPut(json, "candidate", candidate.sdp);
sendMessage(json.toString());
}
// Queue a message for sending to the room's channel and send it if already
// connected (other wise queued messages are drained when the channel is
// eventually established).
private synchronized void sendMessage(String msg) {
synchronized (sendQueue) {
sendQueue.add(msg);
}
requestQueueDrainInBackground();
}
// Put a |key|->|value| mapping in |json|.
private static void jsonPut(JSONObject json, String key, Object value) {
try {
json.put(key, value);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
// Request an attempt to drain the send queue, on a background thread.
private void requestQueueDrainInBackground() {
(new AsyncTask<Void, Void, Void>() {
public Void doInBackground(Void... unused) {
maybeDrainQueue();
return null;
}
}).execute();
}
// Send all queued messages if connected to the room.
private void maybeDrainQueue() {
synchronized (sendQueue) {
if (signalingParameters == null) {
return;
}
try {
for (String msg : sendQueue) {
Log.d(TAG, "SEND: " + msg);
URLConnection connection = new URL(postMessageUrl).openConnection();
connection.setDoOutput(true);
connection.getOutputStream().write(msg.getBytes("UTF-8"));
if (!connection.getHeaderField(null).startsWith("HTTP/1.1 200 ")) {
String errorMessage = "Non-200 response to POST: " +
connection.getHeaderField(null) + " for msg: " + msg;
reportChannelError(errorMessage);
}
}
} catch (IOException e) {
reportChannelError("GAE Post error: " + e.getMessage());
}
sendQueue.clear();
}
}
private void reportChannelError(final String errorMessage) {
Log.e(TAG, errorMessage);
activity.runOnUiThread(new Runnable() {
public void run() {
events.onChannelError(errorMessage);
}
});
}
// --------------------------------------------------------------------
// RoomConnectionEvents interface implementation.
// All events are called on UI thread.
@Override
public void onSignalingParametersReady(final SignalingParameters params) {
Log.d(TAG, "Room signaling parameters ready.");
if (params.websocketSignaling) {
reportChannelError("Room does not support GAE channel signaling.");
return;
}
postMessageUrl = params.roomUrl + "/message?r=" +
params.roomId + "&u=" + params.clientId;
gaeHandler = new GAEHandler();
channelClient =
new GAEChannelClient(activity, params.channelToken, gaeHandler);
synchronized (sendQueue) {
signalingParameters = params;
}
requestQueueDrainInBackground();
events.onConnectedToRoom(signalingParameters);
}
@Override
public void onSignalingParametersError(final String description) {
reportChannelError("Room connection error: " + description);
}
// --------------------------------------------------------------------
// GAEMessageHandler interface implementation.
// Implementation detail: handler for receiving GAE messages and dispatching
// them appropriately. All dispatched messages are called from UI thread.
private class GAEHandler implements GAEChannelClient.GAEMessageHandler {
private boolean channelOpen = false;
public void onOpen() {
activity.runOnUiThread(new Runnable() {
public void run() {
events.onChannelOpen();
channelOpen = true;
}
});
}
public void onMessage(final String msg) {
Log.d(TAG, "RECEIVE: " + msg);
activity.runOnUiThread(new Runnable() {
public void run() {
if (!channelOpen) {
return;
}
try {
JSONObject json = new JSONObject(msg);
String type = json.getString("type");
if (type.equals("candidate")) {
IceCandidate candidate = new IceCandidate(
json.getString("id"),
json.getInt("label"),
json.getString("candidate"));
events.onRemoteIceCandidate(candidate);
} else if (type.equals("answer") || type.equals("offer")) {
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(type),
(String)json.get("sdp"));
events.onRemoteDescription(sdp);
} else if (type.equals("bye")) {
events.onChannelClose();
} else {
reportChannelError("Unexpected channel message: " + msg);
}
} catch (JSONException e) {
reportChannelError("Channel message JSON parsing error: " +
e.toString());
}
}
});
}
public void onClose() {
activity.runOnUiThread(new Runnable() {
public void run() {
events.onChannelClose();
channelOpen = false;
}
});
}
public void onError(final int code, final String description) {
channelOpen = false;
reportChannelError("GAE Handler error. Code: " + code +
". " + description);
}
}
}

View File

@ -53,7 +53,6 @@ public class SettingsActivity extends Activity
keyprefStartBitrateValue = getString(R.string.pref_startbitratevalue_key);
keyprefHwCodec = getString(R.string.pref_hwcodec_key);
keyprefCpuUsageDetection = getString(R.string.pref_cpu_usage_detection_key);
keyprefSignaling = getString(R.string.pref_signaling_key);
// Display the fragment as the main content.
settingsFragment = new SettingsFragment();

View File

@ -341,8 +341,6 @@
'examples/android/src/org/appspot/apprtc/AppRTCClient.java',
'examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java',
'examples/android/src/org/appspot/apprtc/ConnectActivity.java',
'examples/android/src/org/appspot/apprtc/GAEChannelClient.java',
'examples/android/src/org/appspot/apprtc/GAERTCClient.java',
'examples/android/src/org/appspot/apprtc/PeerConnectionClient.java',
'examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java',
'examples/android/src/org/appspot/apprtc/SettingsActivity.java',