Support new WebSocket signaling format.

- Support new GAE message format and new signaling
sequence, which allows connection to 3-dot-apprtc server.
- Add UI setting to switch between GAE / WebSockets signaling.
- Some clean ups to better support command line application
execution.

BUG=3937,3995,4041
R=jiayl@webrtc.org, tkchin@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7813 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
glaznev@webrtc.org 2014-12-04 17:28:52 +00:00
parent 0b38478885
commit 369746bcb8
11 changed files with 413 additions and 260 deletions

View File

@ -57,6 +57,11 @@
<string name="pref_hwcodec_dlg">Use VP8 VP8 hardware accelerated codec (if available).</string> <string name="pref_hwcodec_dlg">Use VP8 VP8 hardware accelerated codec (if available).</string>
<string name="pref_hwcodec_default">true</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_enabled">Enabled</string>
<string name="pref_value_disabled">Disabled</string> <string name="pref_value_disabled">Disabled</string>

View File

@ -43,5 +43,10 @@
android:dialogTitle="@string/pref_cpu_usage_detection_dlg" android:dialogTitle="@string/pref_cpu_usage_detection_dlg"
android:defaultValue="@string/pref_cpu_usage_detection_default" /> 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> </PreferenceScreen>

View File

@ -75,14 +75,18 @@ public interface AppRTCClient {
public final String roomId; public final String roomId;
public final String clientId; public final String clientId;
public final String channelToken; public final String channelToken;
public final String offerSdp; public final String wssUrl;
public final String wssPostUrl;
public final SessionDescription offerSdp;
public final List<IceCandidate> iceCandidates;
public SignalingParameters( public SignalingParameters(
List<PeerConnection.IceServer> iceServers, List<PeerConnection.IceServer> iceServers,
boolean initiator, MediaConstraints pcConstraints, boolean initiator, MediaConstraints pcConstraints,
MediaConstraints videoConstraints, MediaConstraints audioConstraints, MediaConstraints videoConstraints, MediaConstraints audioConstraints,
String roomUrl, String roomId, String clientId, String roomUrl, String roomId, String clientId,
String channelToken, String offerSdp ) { String wssUrl, String wssPostUrl, String channelToken,
SessionDescription offerSdp, List<IceCandidate> iceCandidates) {
this.iceServers = iceServers; this.iceServers = iceServers;
this.initiator = initiator; this.initiator = initiator;
this.pcConstraints = pcConstraints; this.pcConstraints = pcConstraints;
@ -91,8 +95,11 @@ public interface AppRTCClient {
this.roomUrl = roomUrl; this.roomUrl = roomUrl;
this.roomId = roomId; this.roomId = roomId;
this.clientId = clientId; this.clientId = clientId;
this.wssUrl = wssUrl;
this.wssPostUrl = wssPostUrl;
this.channelToken = channelToken; this.channelToken = channelToken;
this.offerSdp = offerSdp; this.offerSdp = offerSdp;
this.iceCandidates = iceCandidates;
if (channelToken == null || channelToken.length() == 0) { if (channelToken == null || channelToken.length() == 0) {
this.websocketSignaling = true; this.websocketSignaling = true;
} else { } else {

View File

@ -71,7 +71,6 @@ public class AppRTCDemoActivity extends Activity
implements AppRTCClient.SignalingEvents, implements AppRTCClient.SignalingEvents,
PeerConnectionClient.PeerConnectionEvents { PeerConnectionClient.PeerConnectionEvents {
private static final String TAG = "AppRTCClient"; private static final String TAG = "AppRTCClient";
private final boolean USE_WEBSOCKETS = false;
private PeerConnectionClient pc; private PeerConnectionClient pc;
private AppRTCClient appRtcClient; private AppRTCClient appRtcClient;
private SignalingParameters signalingParameters; private SignalingParameters signalingParameters;
@ -87,8 +86,9 @@ public class AppRTCDemoActivity extends Activity
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
private TextView hudView; private TextView hudView;
private TextView encoderStatView; private TextView encoderStatView;
private TextView roomName; private TextView roomNameView;
private ImageButton videoScalingButton; private ImageButton videoScalingButton;
private String roomName;
private boolean commandLineRun; private boolean commandLineRun;
private int runTimeMs; private int runTimeMs;
private int startBitrate; private int startBitrate;
@ -119,7 +119,7 @@ public class AppRTCDemoActivity extends Activity
rootView = findViewById(android.R.id.content); rootView = findViewById(android.R.id.content);
encoderStatView = (TextView)findViewById(R.id.encoder_stat); encoderStatView = (TextView)findViewById(R.id.encoder_stat);
menuBar = findViewById(R.id.menubar_fragment); menuBar = findViewById(R.id.menubar_fragment);
roomName = (TextView) findViewById(R.id.room_name); roomNameView = (TextView) findViewById(R.id.room_name);
videoView = (GLSurfaceView) findViewById(R.id.glview); videoView = (GLSurfaceView) findViewById(R.id.glview);
VideoRendererGui.setView(videoView); VideoRendererGui.setView(videoView);
@ -135,11 +135,11 @@ public class AppRTCDemoActivity extends Activity
? View.INVISIBLE : View.VISIBLE; ? View.INVISIBLE : View.VISIBLE;
encoderStatView.setVisibility(visibility); encoderStatView.setVisibility(visibility);
menuBar.setVisibility(visibility); menuBar.setVisibility(visibility);
roomName.setVisibility(visibility); roomNameView.setVisibility(visibility);
if (visibility == View.VISIBLE) { if (visibility == View.VISIBLE) {
encoderStatView.bringToFront(); encoderStatView.bringToFront();
menuBar.bringToFront(); menuBar.bringToFront();
roomName.bringToFront(); roomNameView.bringToFront();
rootView.invalidate(); rootView.invalidate();
} }
} }
@ -206,6 +206,7 @@ public class AppRTCDemoActivity extends Activity
final Intent intent = getIntent(); final Intent intent = getIntent();
Uri url = intent.getData(); Uri url = intent.getData();
roomName = intent.getStringExtra(ConnectActivity.EXTRA_ROOMNAME);
boolean loopback = intent.getBooleanExtra( boolean loopback = intent.getBooleanExtra(
ConnectActivity.EXTRA_LOOPBACK, false); ConnectActivity.EXTRA_LOOPBACK, false);
commandLineRun = intent.getBooleanExtra( commandLineRun = intent.getBooleanExtra(
@ -213,21 +214,22 @@ public class AppRTCDemoActivity extends Activity
runTimeMs = intent.getIntExtra(ConnectActivity.EXTRA_RUNTIME, 0); runTimeMs = intent.getIntExtra(ConnectActivity.EXTRA_RUNTIME, 0);
startBitrate = intent.getIntExtra(ConnectActivity.EXTRA_BITRATE, 0); startBitrate = intent.getIntExtra(ConnectActivity.EXTRA_BITRATE, 0);
hwCodec = intent.getBooleanExtra(ConnectActivity.EXTRA_HWCODEC, true); hwCodec = intent.getBooleanExtra(ConnectActivity.EXTRA_HWCODEC, true);
boolean useWebsocket = intent.getBooleanExtra(
ConnectActivity.EXTRA_WEBSOCKET, false);
if (url != null) { if (url != null) {
String room = url.getQueryParameter("r"); if (loopback || (roomName != null && !roomName.equals(""))) {
if (loopback || (room != null && !room.equals(""))) {
logAndToast(getString(R.string.connecting_to, url)); logAndToast(getString(R.string.connecting_to, url));
if (USE_WEBSOCKETS) { if (useWebsocket) {
appRtcClient = new WebSocketRTCClient(this); appRtcClient = new WebSocketRTCClient(this);
} else { } else {
appRtcClient = new GAERTCClient(this, this); appRtcClient = new GAERTCClient(this, this);
} }
appRtcClient.connectToRoom(url.toString(), loopback); appRtcClient.connectToRoom(url.toString(), loopback);
if (loopback) { if (loopback) {
roomName.setText("loopback"); roomNameView.setText("loopback");
} else { } else {
roomName.setText(room); roomNameView.setText(roomName);
} }
if (commandLineRun && runTimeMs > 0) { if (commandLineRun && runTimeMs > 0) {
// For command line execution run connection for <runTimeMs> and exit. // For command line execution run connection for <runTimeMs> and exit.

View File

@ -51,6 +51,7 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Random;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -62,16 +63,18 @@ import org.webrtc.MediaCodecVideoEncoder;
*/ */
public class ConnectActivity extends Activity { public class ConnectActivity extends Activity {
public static final String EXTRA_ROOMNAME = "org.appspot.apprtc.ROOMNAME";
public static final String EXTRA_LOOPBACK = "org.appspot.apprtc.LOOPBACK"; public static final String EXTRA_LOOPBACK = "org.appspot.apprtc.LOOPBACK";
public static final String EXTRA_CMDLINE = "org.appspot.apprtc.CMDLINE"; public static final String EXTRA_CMDLINE = "org.appspot.apprtc.CMDLINE";
public static final String EXTRA_RUNTIME = "org.appspot.apprtc.RUNTIME"; public static final String EXTRA_RUNTIME = "org.appspot.apprtc.RUNTIME";
public static final String EXTRA_BITRATE = "org.appspot.apprtc.BITRATE"; public static final String EXTRA_BITRATE = "org.appspot.apprtc.BITRATE";
public static final String EXTRA_HWCODEC = "org.appspot.apprtc.HWCODEC"; public static final String EXTRA_HWCODEC = "org.appspot.apprtc.HWCODEC";
private static final String TAG = "ConnectActivity"; public static final String EXTRA_WEBSOCKET = "org.appspot.apprtc.WEBSOCKET";
private final boolean USE_WEBSOCKETS = false; private static final String TAG = "ConnectRTCClient";
private final String APPRTC_SERVER = "https://apprtc.appspot.com"; private final String APPRTC_SERVER = "https://apprtc.appspot.com";
private final String APPRTC_WS_SERVER = "https://8-dot-apprtc.appspot.com"; private final String APPRTC_WS_SERVER = "https://3-dot-apprtc.appspot.com";
private final int CONNECTION_REQUEST = 1; private final int CONNECTION_REQUEST = 1;
private static boolean commandLineRun = false;
private ImageButton addRoomButton; private ImageButton addRoomButton;
private ImageButton removeRoomButton; private ImageButton removeRoomButton;
@ -86,12 +89,11 @@ public class ConnectActivity extends Activity {
private String keyprefBitrateValue; private String keyprefBitrateValue;
private String keyprefHwCodec; private String keyprefHwCodec;
private String keyprefCpuUsageDetection; private String keyprefCpuUsageDetection;
private String keyprefWebsocketSignaling;
private String keyprefRoom; private String keyprefRoom;
private String keyprefRoomList; private String keyprefRoomList;
private ArrayList<String> roomList; private ArrayList<String> roomList;
private ArrayAdapter<String> adapter; private ArrayAdapter<String> adapter;
private boolean commandLineRun;
private int runTimeMs;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -106,6 +108,7 @@ public class ConnectActivity extends Activity {
keyprefBitrateValue = getString(R.string.pref_startbitratevalue_key); keyprefBitrateValue = getString(R.string.pref_startbitratevalue_key);
keyprefHwCodec = getString(R.string.pref_hwcodec_key); keyprefHwCodec = getString(R.string.pref_hwcodec_key);
keyprefCpuUsageDetection = getString(R.string.pref_cpu_usage_detection_key); keyprefCpuUsageDetection = getString(R.string.pref_cpu_usage_detection_key);
keyprefWebsocketSignaling = getString(R.string.pref_signaling_key);
keyprefRoom = getString(R.string.pref_room_key); keyprefRoom = getString(R.string.pref_room_key);
keyprefRoomList = getString(R.string.pref_room_list_key); keyprefRoomList = getString(R.string.pref_room_list_key);
@ -140,17 +143,15 @@ public class ConnectActivity extends Activity {
connectLoopbackButton.setOnClickListener(connectListener); connectLoopbackButton.setOnClickListener(connectListener);
// If an implicit VIEW intent is launching the app, go directly to that URL. // If an implicit VIEW intent is launching the app, go directly to that URL.
commandLineRun = false;
final Intent intent = getIntent(); final Intent intent = getIntent();
if ("android.intent.action.VIEW".equals(intent.getAction())) { if ("android.intent.action.VIEW".equals(intent.getAction()) &&
!commandLineRun) {
commandLineRun = true; commandLineRun = true;
boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false); boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false);
runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0); int runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
String url = intent.getData().toString(); String room = sharedPref.getString(keyprefRoom, "");
if (loopback && !url.contains("debug=loopback")) { roomEditText.setText(room);
url += "/?debug=loopback"; connectToRoom(loopback, runTimeMs);
}
connectToRoom(url, loopback, 0, true);
return; return;
} }
} }
@ -227,24 +228,54 @@ public class ConnectActivity extends Activity {
if (view.getId() == R.id.connect_loopback_button) { if (view.getId() == R.id.connect_loopback_button) {
loopback = true; loopback = true;
} }
String url; commandLineRun = false;
if (USE_WEBSOCKETS) { connectToRoom(loopback, 0);
url = APPRTC_WS_SERVER;
} else {
url = APPRTC_SERVER;
} }
if (loopback) { };
url += "/?debug=loopback";
private String appendQueryParameter(String url, String parameter) {
String newUrl = url;
if (newUrl.contains("?")) {
newUrl += "&" + parameter;
} else { } else {
String roomName = getSelectedItem(); newUrl += "?" + parameter;
}
return newUrl;
}
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) {
roomName = Integer.toString((new Random()).nextInt(100000000));
} else {
roomName = getSelectedItem();
if (roomName == null) { if (roomName == null) {
roomName = roomEditText.getText().toString(); roomName = roomEditText.getText().toString();
} }
url += "/?r=" + roomName;
} }
// 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");
}
}
// Check HW codec flag. // Check HW codec flag.
boolean hwCodec = sharedPref.getBoolean(keyprefHwCodec, boolean hwCodec = sharedPref.getBoolean(keyprefHwCodec,
Boolean.valueOf(getString(R.string.pref_hwcodec_default))); Boolean.valueOf(getString(R.string.pref_hwcodec_default)));
// Add video resolution constraints. // Add video resolution constraints.
String parametersResolution = null; String parametersResolution = null;
String parametersFps = null; String parametersFps = null;
@ -263,6 +294,7 @@ public class ConnectActivity extends Activity {
Log.e(TAG, "Wrong video resolution setting: " + resolution); Log.e(TAG, "Wrong video resolution setting: " + resolution);
} }
} }
// Add camera fps constraints. // Add camera fps constraints.
String fps = sharedPref.getString(keyprefFps, String fps = sharedPref.getString(keyprefFps,
getString(R.string.pref_fps_default)); getString(R.string.pref_fps_default));
@ -278,23 +310,26 @@ public class ConnectActivity extends Activity {
Log.e(TAG, "Wrong camera fps setting: " + fps); Log.e(TAG, "Wrong camera fps setting: " + fps);
} }
} }
// Modify connection URL. // Modify connection URL.
if (parametersResolution != null || parametersFps != null) { if (parametersResolution != null || parametersFps != null) {
url += "&video="; String urlVideoParameters = "video=";
if (parametersResolution != null) { if (parametersResolution != null) {
url += parametersResolution; urlVideoParameters += parametersResolution;
if (parametersFps != null) { if (parametersFps != null) {
url += ","; urlVideoParameters += ",";
} }
} }
if (parametersFps != null) { if (parametersFps != null) {
url += parametersFps; urlVideoParameters += parametersFps;
} }
url = appendQueryParameter(url, urlVideoParameters);
} else { } else {
if (hwCodec && MediaCodecVideoEncoder.isPlatformSupported()) { if (hwCodec && MediaCodecVideoEncoder.isPlatformSupported()) {
url += "&hd=true"; url = appendQueryParameter(url, "hd=true");
} }
} }
// Get start bitrate. // Get start bitrate.
int startBitrate = 0; int startBitrate = 0;
String bitrateTypeDefault = getString(R.string.pref_startbitrate_default); String bitrateTypeDefault = getString(R.string.pref_startbitrate_default);
@ -305,30 +340,29 @@ public class ConnectActivity extends Activity {
getString(R.string.pref_startbitratevalue_default)); getString(R.string.pref_startbitratevalue_default));
startBitrate = Integer.parseInt(bitrateValue); startBitrate = Integer.parseInt(bitrateValue);
} }
// Test if CpuOveruseDetection should be disabled. By default is on. // Test if CpuOveruseDetection should be disabled. By default is on.
boolean cpuOveruseDetection = sharedPref.getBoolean( boolean cpuOveruseDetection = sharedPref.getBoolean(
keyprefCpuUsageDetection, keyprefCpuUsageDetection,
Boolean.valueOf( Boolean.valueOf(
getString(R.string.pref_cpu_usage_detection_default))); getString(R.string.pref_cpu_usage_detection_default)));
if (!cpuOveruseDetection) { if (!cpuOveruseDetection) {
url += "&googCpuOveruseDetection=false"; url = appendQueryParameter(url, "googCpuOveruseDetection=false");
} }
// TODO(kjellander): Add support for custom parameters to the URL.
connectToRoom(url, loopback, startBitrate, hwCodec);
}
};
private void connectToRoom( // Start AppRTCDemo activity.
String roomUrl, boolean loopback, int startBitrate, boolean hwCodec) { Log.d(TAG, "Connecting to room " + roomName + " at URL " + url);
if (validateUrl(roomUrl)) { if (validateUrl(url)) {
Uri url = Uri.parse(roomUrl); Uri uri = Uri.parse(url);
Intent intent = new Intent(this, AppRTCDemoActivity.class); Intent intent = new Intent(this, AppRTCDemoActivity.class);
intent.setData(url); intent.setData(uri);
intent.putExtra(EXTRA_ROOMNAME, roomName);
intent.putExtra(EXTRA_LOOPBACK, loopback); intent.putExtra(EXTRA_LOOPBACK, loopback);
intent.putExtra(EXTRA_CMDLINE, commandLineRun); intent.putExtra(EXTRA_CMDLINE, commandLineRun);
intent.putExtra(EXTRA_RUNTIME, runTimeMs); intent.putExtra(EXTRA_RUNTIME, runTimeMs);
intent.putExtra(EXTRA_BITRATE, startBitrate); intent.putExtra(EXTRA_BITRATE, startBitrate);
intent.putExtra(EXTRA_HWCODEC, hwCodec); intent.putExtra(EXTRA_HWCODEC, hwCodec);
intent.putExtra(EXTRA_WEBSOCKET, useWebsocket);
startActivityForResult(intent, CONNECTION_REQUEST); startActivityForResult(intent, CONNECTION_REQUEST);
} }
} }

View File

@ -76,7 +76,7 @@ public class GAERTCClient implements AppRTCClient, RoomParametersFetcherEvents {
*/ */
@Override @Override
public void connectToRoom(String url, boolean loopback) { public void connectToRoom(String url, boolean loopback) {
fetcher = new RoomParametersFetcher(this, loopback); fetcher = new RoomParametersFetcher(this, false, loopback);
fetcher.execute(url); fetcher.execute(url);
} }
@ -246,12 +246,12 @@ public class GAERTCClient implements AppRTCClient, RoomParametersFetcherEvents {
} }
try { try {
JSONObject json = new JSONObject(msg); JSONObject json = new JSONObject(msg);
String type = (String) json.get("type"); String type = json.getString("type");
if (type.equals("candidate")) { if (type.equals("candidate")) {
IceCandidate candidate = new IceCandidate( IceCandidate candidate = new IceCandidate(
(String) json.get("id"), json.getString("id"),
json.getInt("label"), json.getInt("label"),
(String) json.get("candidate")); json.getString("candidate"));
events.onRemoteIceCandidate(candidate); events.onRemoteIceCandidate(candidate);
} else if (type.equals("answer") || type.equals("offer")) { } else if (type.equals("answer") || type.equals("offer")) {
SessionDescription sdp = new SessionDescription( SessionDescription sdp = new SessionDescription(

View File

@ -283,6 +283,7 @@ public class PeerConnectionClient {
} }
private void reportError(final String errorMessage) { private void reportError(final String errorMessage) {
Log.e(TAG, "Peerconnection error: " + errorMessage);
activity.runOnUiThread(new Runnable() { activity.runOnUiThread(new Runnable() {
public void run() { public void run() {
events.onPeerConnectionError(errorMessage); events.onPeerConnectionError(errorMessage);

View File

@ -33,12 +33,14 @@ import org.appspot.apprtc.AppRTCClient.SignalingParameters;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints; import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection; import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.LinkedList; import java.util.LinkedList;
@ -51,6 +53,7 @@ public class RoomParametersFetcher
private static final String TAG = "RoomRTCClient"; private static final String TAG = "RoomRTCClient";
private Exception exception = null; private Exception exception = null;
private RoomParametersFetcherEvents events = null; private RoomParametersFetcherEvents events = null;
private boolean useNewSignaling;
private boolean loopback; private boolean loopback;
/** /**
@ -69,10 +72,11 @@ public class RoomParametersFetcher
public void onSignalingParametersError(final String description); public void onSignalingParametersError(final String description);
} }
public RoomParametersFetcher( public RoomParametersFetcher(RoomParametersFetcherEvents events,
RoomParametersFetcherEvents events, boolean loopback) { boolean useNewSignaling, boolean loopback) {
super(); super();
this.events = events; this.events = events;
this.useNewSignaling = useNewSignaling;
this.loopback = loopback; this.loopback = loopback;
} }
@ -115,45 +119,92 @@ public class RoomParametersFetcher
// Fetches |url| and fishes the signaling parameters out of the JSON. // Fetches |url| and fishes the signaling parameters out of the JSON.
private SignalingParameters getParametersForRoomUrl(String url) private SignalingParameters getParametersForRoomUrl(String url)
throws IOException, JSONException { throws IOException, JSONException {
url = url + "&t=json"; if (!useNewSignaling) {
if (url.contains("?")) {
url += "&t=json";
} else {
url += "?t=json";
}
}
Log.d(TAG, "Connecting to room: " + url); Log.d(TAG, "Connecting to room: " + url);
InputStream responseStream = new BufferedInputStream( HttpURLConnection connection =
(new URL(url)).openConnection().getInputStream()); (HttpURLConnection) new URL(url).openConnection();
if (useNewSignaling) {
connection.setDoOutput(true);
connection.setRequestMethod("POST");
} else {
connection.setRequestMethod("GET");
}
connection.setDoInput(true);
InputStream responseStream = connection.getInputStream();
String response = drainStream(responseStream); String response = drainStream(responseStream);
responseStream.close();
Log.d(TAG, "Room response: " + response); Log.d(TAG, "Room response: " + response);
JSONObject roomJson = new JSONObject(response); JSONObject roomJson = new JSONObject(response);
String roomId;
String clientId;
String roomUrl;
String channelToken = "";
String wssUrl = "";
String wssPostUrl = "";
boolean initiator;
LinkedList<IceCandidate> iceCandidates = null;
SessionDescription offerSdp = null;
if (useNewSignaling) {
String result = roomJson.getString("result");
if (!result.equals("SUCCESS")) {
throw new JSONException(result);
}
response = roomJson.getString("params");
roomJson = new JSONObject(response);
roomId = roomJson.getString("room_id");
clientId = roomJson.getString("client_id");
wssUrl = roomJson.getString("wss_url");
wssPostUrl = roomJson.getString("wss_post_url");
initiator = (roomJson.getBoolean("is_initiator"));
roomUrl = url.substring(0, url.indexOf("/register"));
if (!initiator) {
iceCandidates = new LinkedList<IceCandidate>();
String messagesString = roomJson.getString("messages");
JSONArray messages = new JSONArray(messagesString);
for (int i = 0; i < messages.length(); ++i) {
String messageString = messages.getString(i);
JSONObject message = new JSONObject(messageString);
String messageType = message.getString("type");
Log.d(TAG, "GAE->C #" + i + " : " + messageString);
if (messageType.equals("offer")) {
offerSdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(messageType),
message.getString("sdp"));
} else if (messageType.equals("candidate")) {
IceCandidate candidate = new IceCandidate(
message.getString("id"),
message.getInt("label"),
message.getString("candidate"));
iceCandidates.add(candidate);
} else {
Log.e(TAG, "Unknown message: " + messageString);
}
}
}
} else {
if (roomJson.has("error")) { if (roomJson.has("error")) {
JSONArray errors = roomJson.getJSONArray("error_messages"); JSONArray errors = roomJson.getJSONArray("error_messages");
throw new IOException(errors.toString()); throw new IOException(errors.toString());
} }
roomId = roomJson.getString("room_key");
clientId = roomJson.getString("me");
channelToken = roomJson.optString("token");
initiator = (roomJson.getInt("initiator") == 1);
roomUrl = url.substring(0, url.indexOf('?'));
}
String roomId = roomJson.getString("room_key");
String clientId = roomJson.getString("me");
Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId); Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId);
String channelToken = roomJson.optString("token");
String offerSdp = roomJson.optString("offer");
if (offerSdp != null && offerSdp.length() > 0) {
JSONObject offerJson = new JSONObject(offerSdp);
offerSdp = offerJson.getString("sdp");
Log.d(TAG, "SDP type: " + offerJson.getString("type"));
} else {
offerSdp = null;
}
String roomUrl = url.substring(0, url.indexOf('?'));
Log.d(TAG, "Room url: " + roomUrl);
boolean initiator;
if (loopback) {
// In loopback mode caller should always be call initiator.
// TODO(glaznev): remove this once 8-dot-apprtc server will set initiator
// flag to true for loopback calls.
initiator = true;
} else {
initiator = roomJson.getInt("initiator") == 1;
}
Log.d(TAG, "Initiator: " + initiator); Log.d(TAG, "Initiator: " + initiator);
Log.d(TAG, "Room url: " + roomUrl);
LinkedList<PeerConnection.IceServer> iceServers = LinkedList<PeerConnection.IceServer> iceServers =
iceServersFromPCConfigJSON(roomJson.getString("pc_config")); iceServersFromPCConfigJSON(roomJson.getString("pc_config"));
@ -191,7 +242,8 @@ public class RoomParametersFetcher
iceServers, initiator, iceServers, initiator,
pcConstraints, videoConstraints, audioConstraints, pcConstraints, videoConstraints, audioConstraints,
roomUrl, roomId, clientId, roomUrl, roomId, clientId,
channelToken, offerSdp); wssUrl, wssPostUrl, channelToken,
offerSdp, iceCandidates);
} }
// Mimic Chrome and set DtlsSrtpKeyAgreement to true if not set to false by // Mimic Chrome and set DtlsSrtpKeyAgreement to true if not set to false by

View File

@ -42,6 +42,7 @@ public class SettingsActivity extends Activity
private String keyprefStartBitrateValue; private String keyprefStartBitrateValue;
private String keyprefHwCodec; private String keyprefHwCodec;
private String keyprefCpuUsageDetection; private String keyprefCpuUsageDetection;
private String keyprefSignaling;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -52,6 +53,7 @@ public class SettingsActivity extends Activity
keyprefStartBitrateValue = getString(R.string.pref_startbitratevalue_key); keyprefStartBitrateValue = getString(R.string.pref_startbitratevalue_key);
keyprefHwCodec = getString(R.string.pref_hwcodec_key); keyprefHwCodec = getString(R.string.pref_hwcodec_key);
keyprefCpuUsageDetection = getString(R.string.pref_cpu_usage_detection_key); keyprefCpuUsageDetection = getString(R.string.pref_cpu_usage_detection_key);
keyprefSignaling = getString(R.string.pref_signaling_key);
// Display the fragment as the main content. // Display the fragment as the main content.
settingsFragment = new SettingsFragment(); settingsFragment = new SettingsFragment();
@ -74,6 +76,7 @@ public class SettingsActivity extends Activity
setBitrateEnable(sharedPreferences); setBitrateEnable(sharedPreferences);
updateSummaryB(sharedPreferences, keyprefHwCodec); updateSummaryB(sharedPreferences, keyprefHwCodec);
updateSummaryB(sharedPreferences, keyprefCpuUsageDetection); updateSummaryB(sharedPreferences, keyprefCpuUsageDetection);
updateSummaryB(sharedPreferences, keyprefSignaling);
} }
@Override @Override
@ -93,7 +96,7 @@ public class SettingsActivity extends Activity
} else if (key.equals(keyprefStartBitrateValue)) { } else if (key.equals(keyprefStartBitrateValue)) {
updateSummaryBitrate(sharedPreferences, key); updateSummaryBitrate(sharedPreferences, key);
} else if (key.equals(keyprefCpuUsageDetection) || } else if (key.equals(keyprefCpuUsageDetection) ||
key.equals(keyprefHwCodec)) { key.equals(keyprefHwCodec) || key.equals(keyprefSignaling)) {
updateSummaryB(sharedPreferences, key); updateSummaryB(sharedPreferences, key);
} }
if (key.equals(keyprefStartBitrateType)) { if (key.equals(keyprefStartBitrateType)) {

View File

@ -140,7 +140,7 @@ public class WebSocketChannelClient {
json.put("cmd", "register"); json.put("cmd", "register");
json.put("roomid", roomID); json.put("roomid", roomID);
json.put("clientid", clientID); json.put("clientid", clientID);
Log.d(TAG, "WS SEND: " + json.toString()); Log.d(TAG, "C->WSS: " + json.toString());
ws.sendTextMessage(json.toString()); ws.sendTextMessage(json.toString());
state = WebSocketConnectionState.REGISTERED; state = WebSocketConnectionState.REGISTERED;
// Send any previously accumulated messages. // Send any previously accumulated messages.
@ -176,7 +176,7 @@ public class WebSocketChannelClient {
json.put("cmd", "send"); json.put("cmd", "send");
json.put("msg", message); json.put("msg", message);
message = json.toString(); message = json.toString();
Log.d(TAG, "WS SEND: " + message); Log.d(TAG, "C->WSS: " + message);
ws.sendTextMessage(message); ws.sendTextMessage(message);
} catch (JSONException e) { } catch (JSONException e) {
reportError("WebSocket send JSON error: " + e.getMessage()); reportError("WebSocket send JSON error: " + e.getMessage());
@ -279,9 +279,9 @@ public class WebSocketChannelClient {
try { try {
for (WsHttpMessage wsHttpMessage : wsHttpQueue) { for (WsHttpMessage wsHttpMessage : wsHttpQueue) {
// Send POST request. // Send POST request.
Log.d(TAG, "WS " + wsHttpMessage.method + " : " + String postUrl = postServerUrl + "/" + roomID + "/" + clientID;
Log.d(TAG, "WS " + wsHttpMessage.method + " : " + postUrl + " : " +
wsHttpMessage.message); wsHttpMessage.message);
String postUrl = postServerUrl + roomID + "/" + clientID;
HttpURLConnection connection = HttpURLConnection connection =
(HttpURLConnection) new URL(postUrl).openConnection(); (HttpURLConnection) new URL(postUrl).openConnection();
connection.setDoOutput(true); connection.setDoOutput(true);
@ -333,7 +333,7 @@ public class WebSocketChannelClient {
@Override @Override
public void onTextMessage(String payload) { public void onTextMessage(String payload) {
Log.d(TAG, "WS GET: " + payload); Log.d(TAG, "WSS->C: " + payload);
final String message = payload; final String message = payload;
uiHandler.post(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {

View File

@ -32,9 +32,12 @@ import android.os.Looper;
import android.util.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Scanner;
import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents; import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents;
import org.appspot.apprtc.WebSocketChannelClient.WebSocketChannelEvents; import org.appspot.apprtc.WebSocketChannelClient.WebSocketChannelEvents;
@ -57,31 +60,25 @@ import org.webrtc.SessionDescription;
public class WebSocketRTCClient implements AppRTCClient, public class WebSocketRTCClient implements AppRTCClient,
RoomParametersFetcherEvents, WebSocketChannelEvents { RoomParametersFetcherEvents, WebSocketChannelEvents {
private static final String TAG = "WSRTCClient"; private static final String TAG = "WSRTCClient";
private static final String WSS_SERVER =
"wss://apprtc-ws.webrtc.org:8089/ws";
// TODO(glaznev): remove this hard-coded URL and instead get WebSocket http
// server URL from room response once it will be supported by 8-dot-apprtc.
private static final String WSS_POST_URL =
"https://apprtc-ws.webrtc.org:8089/";
private enum ConnectionState { private enum ConnectionState {
NEW, CONNECTED, CLOSED, ERROR NEW, CONNECTED, CLOSED, ERROR
}; };
private final Handler uiHandler; private final Handler uiHandler;
private boolean loopback; private boolean loopback;
private boolean initiator;
private SignalingEvents events; private SignalingEvents events;
private SignalingParameters signalingParameters;
private WebSocketChannelClient wsClient; private WebSocketChannelClient wsClient;
private RoomParametersFetcher fetcher; private RoomParametersFetcher fetcher;
private ConnectionState roomState; private ConnectionState roomState;
private LinkedList<GAEMessage> gaePostQueue; private LinkedList<PostMessage> postQueue;
private String postMessageUrl; private String postMessageUrl;
private String byeMessageUrl; private String byeMessageUrl;
public WebSocketRTCClient(SignalingEvents events) { public WebSocketRTCClient(SignalingEvents events) {
this.events = events; this.events = events;
uiHandler = new Handler(Looper.getMainLooper()); uiHandler = new Handler(Looper.getMainLooper());
gaePostQueue = new LinkedList<GAEMessage>(); postQueue = new LinkedList<PostMessage>();
} }
// -------------------------------------------------------------------- // --------------------------------------------------------------------
@ -90,31 +87,38 @@ public class WebSocketRTCClient implements AppRTCClient,
@Override @Override
public void onSignalingParametersReady(final SignalingParameters params) { public void onSignalingParametersReady(final SignalingParameters params) {
Log.d(TAG, "Room connection completed."); Log.d(TAG, "Room connection completed.");
if (!loopback && !params.initiator && params.offerSdp == null) { if (loopback && (!params.initiator || params.offerSdp != null)) {
reportError("Offer SDP is not available.");
return;
}
if (loopback && params.offerSdp != null) {
reportError("Loopback room is busy."); reportError("Loopback room is busy.");
return; return;
} }
signalingParameters = params; if (!loopback && !params.initiator && params.offerSdp == null) {
postMessageUrl = params.roomUrl + "message?r=" + Log.w(TAG, "No offer SDP in room response.");
params.roomId + "&u=" + params.clientId; }
byeMessageUrl = params.roomUrl + "bye/" + initiator = params.initiator;
postMessageUrl = params.roomUrl + "/message/" +
params.roomId + "/" + params.clientId;
byeMessageUrl = params.roomUrl + "/bye/" +
params.roomId + "/" + params.clientId; params.roomId + "/" + params.clientId;
roomState = ConnectionState.CONNECTED; roomState = ConnectionState.CONNECTED;
wsClient.setClientParameters(
signalingParameters.roomId, signalingParameters.clientId); // Connect to WebSocket server.
wsClient.register(); wsClient.connect(params.wssUrl, params.wssPostUrl);
events.onConnectedToRoom(signalingParameters); wsClient.setClientParameters(params.roomId, params.clientId);
// Fire connection and signaling parameters events.
events.onConnectedToRoom(params);
events.onChannelOpen(); events.onChannelOpen();
if (!signalingParameters.initiator) { if (!params.initiator) {
// For call receiver get sdp offer from room parameters. // For call receiver get sdp offer and ice candidates
SessionDescription sdp = new SessionDescription( // from room parameters.
SessionDescription.Type.fromCanonicalForm("offer"), if (params.offerSdp != null) {
signalingParameters.offerSdp); events.onRemoteDescription(params.offerSdp);
events.onRemoteDescription(sdp); }
if (params.iceCandidates != null) {
for (IceCandidate iceCandidate : params.iceCandidates) {
events.onRemoteIceCandidate(iceCandidate);
}
}
} }
} }
@ -128,11 +132,9 @@ public class WebSocketRTCClient implements AppRTCClient,
// All events are called on UI thread. // All events are called on UI thread.
@Override @Override
public void onWebSocketOpen() { public void onWebSocketOpen() {
Log.d(TAG, "Websocket connection completed."); Log.d(TAG, "Websocket connection completed. Registering...");
if (roomState == ConnectionState.CONNECTED) {
wsClient.register(); wsClient.register();
} }
}
@Override @Override
public void onWebSocketMessage(final String msg) { public void onWebSocketMessage(final String msg) {
@ -149,15 +151,28 @@ public class WebSocketRTCClient implements AppRTCClient,
String type = json.optString("type"); String type = json.optString("type");
if (type.equals("candidate")) { if (type.equals("candidate")) {
IceCandidate candidate = new IceCandidate( IceCandidate candidate = new IceCandidate(
(String) json.get("id"), json.getString("id"),
json.getInt("label"), json.getInt("label"),
(String) json.get("candidate")); json.getString("candidate"));
events.onRemoteIceCandidate(candidate); events.onRemoteIceCandidate(candidate);
} else if (type.equals("answer")) { } else if (type.equals("answer")) {
if (initiator) {
SessionDescription sdp = new SessionDescription( SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(type), SessionDescription.Type.fromCanonicalForm(type),
(String)json.get("sdp")); json.getString("sdp"));
events.onRemoteDescription(sdp); events.onRemoteDescription(sdp);
} else {
reportError("Received answer for call initiator: " + msg);
}
} else if (type.equals("offer")) {
if (!initiator) {
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(type),
json.getString("sdp"));
events.onRemoteDescription(sdp);
} else {
reportError("Received offer for call receiver: " + msg);
}
} else if (type.equals("bye")) { } else if (type.equals("bye")) {
events.onChannelClose(); events.onChannelClose();
} else { } else {
@ -189,32 +204,28 @@ public class WebSocketRTCClient implements AppRTCClient,
// -------------------------------------------------------------------- // --------------------------------------------------------------------
// AppRTCClient interface implementation. // AppRTCClient interface implementation.
// Asynchronously connect to an AppRTC room URL, e.g. // Asynchronously connect to an AppRTC room URL, e.g.
// https://apprtc.appspot.com/?r=NNN, retrieve room parameters // https://apprtc.appspot.com/register/<room>, retrieve room parameters
// and connect to WebSocket server. // and connect to WebSocket server.
@Override @Override
public void connectToRoom(String url, boolean loopback) { public void connectToRoom(String url, boolean loopback) {
this.loopback = loopback; this.loopback = loopback;
// Create WebSocket client.
wsClient = new WebSocketChannelClient(this);
// Get room parameters. // Get room parameters.
roomState = ConnectionState.NEW; roomState = ConnectionState.NEW;
fetcher = new RoomParametersFetcher(this, loopback); fetcher = new RoomParametersFetcher(this, true, loopback);
fetcher.execute(url); fetcher.execute(url);
// Connect to WebSocket server.
wsClient = new WebSocketChannelClient(this);
if (!loopback) {
wsClient.connect(WSS_SERVER, WSS_POST_URL);
}
} }
@Override @Override
public void disconnect() { public void disconnect() {
Log.d(TAG, "Disconnect. Room state: " + roomState); Log.d(TAG, "Disconnect. Room state: " + roomState);
wsClient.disconnect();
if (roomState == ConnectionState.CONNECTED) { if (roomState == ConnectionState.CONNECTED) {
Log.d(TAG, "Closing room."); Log.d(TAG, "Closing room.");
// TODO(glaznev): Remove json bye message sending once new bye will sendGAEMessage(byeMessageUrl, "");
// be supported on 8-dot. }
//sendGAEMessage(byeMessageUrl, ""); if (wsClient != null) {
sendGAEMessage(postMessageUrl, "{\"type\": \"bye\"}"); wsClient.disconnect();
} }
} }
@ -225,17 +236,16 @@ public class WebSocketRTCClient implements AppRTCClient,
// we might want to filter elsewhere. // we might want to filter elsewhere.
@Override @Override
public void sendOfferSdp(final SessionDescription sdp) { public void sendOfferSdp(final SessionDescription sdp) {
if (loopback) {
// In loopback mode rename this offer to answer and send it back.
SessionDescription sdpAnswer = new SessionDescription(
SessionDescription.Type.fromCanonicalForm("answer"),
sdp.description);
events.onRemoteDescription(sdpAnswer);
} else {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
jsonPut(json, "sdp", sdp.description); jsonPut(json, "sdp", sdp.description);
jsonPut(json, "type", "offer"); jsonPut(json, "type", "offer");
sendGAEMessage(postMessageUrl, json.toString()); sendGAEMessage(postMessageUrl, json.toString());
if (loopback) {
// In loopback mode rename this offer to answer and route it back.
SessionDescription sdpAnswer = new SessionDescription(
SessionDescription.Type.fromCanonicalForm("answer"),
sdp.description);
events.onRemoteDescription(sdpAnswer);
} }
} }
@ -258,18 +268,27 @@ public class WebSocketRTCClient implements AppRTCClient,
// Send Ice candidate to the other participant. // Send Ice candidate to the other participant.
@Override @Override
public void sendLocalIceCandidate(final IceCandidate candidate) { public void sendLocalIceCandidate(final IceCandidate candidate) {
if (loopback) {
events.onRemoteIceCandidate(candidate);
} else {
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
reportError("Sending ICE candidate in non registered state.");
return;
}
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
jsonPut(json, "type", "candidate"); jsonPut(json, "type", "candidate");
jsonPut(json, "label", candidate.sdpMLineIndex); jsonPut(json, "label", candidate.sdpMLineIndex);
jsonPut(json, "id", candidate.sdpMid); jsonPut(json, "id", candidate.sdpMid);
jsonPut(json, "candidate", candidate.sdp); jsonPut(json, "candidate", candidate.sdp);
if (initiator) {
// Call initiator sends ice candidates to GAE server.
if (roomState != ConnectionState.CONNECTED) {
reportError("Sending ICE candidate in non connected state.");
return;
}
sendGAEMessage(postMessageUrl, json.toString());
if (loopback) {
events.onRemoteIceCandidate(candidate);
}
} else {
// Call receiver sends ice candidates to websocket server.
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
reportError("Sending ICE candidate in non registered state.");
return;
}
wsClient.send(json.toString()); wsClient.send(json.toString());
} }
} }
@ -297,8 +316,8 @@ public class WebSocketRTCClient implements AppRTCClient,
} }
} }
private class GAEMessage { private class PostMessage {
GAEMessage(String postUrl, String message) { PostMessage(String postUrl, String message) {
this.postUrl = postUrl; this.postUrl = postUrl;
this.message = message; this.message = message;
} }
@ -308,8 +327,8 @@ public class WebSocketRTCClient implements AppRTCClient,
// Queue a message for sending to the room and send it if already connected. // Queue a message for sending to the room and send it if already connected.
private synchronized void sendGAEMessage(String url, String message) { private synchronized void sendGAEMessage(String url, String message) {
synchronized (gaePostQueue) { synchronized (postQueue) {
gaePostQueue.add(new GAEMessage(url, message)); postQueue.add(new PostMessage(url, message));
} }
(new AsyncTask<Void, Void, Void>() { (new AsyncTask<Void, Void, Void>() {
public Void doInBackground(Void... unused) { public Void doInBackground(Void... unused) {
@ -321,47 +340,72 @@ public class WebSocketRTCClient implements AppRTCClient,
// Send all queued messages if connected to the room. // Send all queued messages if connected to the room.
private void maybeDrainGAEPostQueue() { private void maybeDrainGAEPostQueue() {
synchronized (gaePostQueue) {
if (roomState != ConnectionState.CONNECTED) { if (roomState != ConnectionState.CONNECTED) {
return; return;
} }
try { PostMessage postMessage = null;
for (GAEMessage gaeMessage : gaePostQueue) { while (true) {
Log.d(TAG, "ROOM SEND to " + gaeMessage.postUrl + synchronized (postQueue) {
". Message: " + gaeMessage.message); postMessage = postQueue.poll();
// Check if this is 'bye' message and update room connection state.
// TODO(glaznev): Uncomment this check and remove check below
// once new bye message will be supported by 8-dot.
//if (gaeMessage.postUrl.contains("bye")) {
// roomState = ConnectionState.CLOSED;
//}
JSONObject json = new JSONObject(gaeMessage.message);
String type = json.optString("type");
if (type != null && type.equals("bye")) {
roomState = ConnectionState.CLOSED;
} }
// Send POST request. if (postMessage == null) {
break;
}
try {
// Check if this is 'bye' message and update room connection state.
if (postMessage.postUrl.contains("bye")) {
roomState = ConnectionState.CLOSED;
Log.d(TAG, "C->GAE: " + postMessage.postUrl);
} else {
Log.d(TAG, "C->GAE: " + postMessage.message);
}
// Get connection.
HttpURLConnection connection = HttpURLConnection connection =
(HttpURLConnection) new URL(gaeMessage.postUrl).openConnection(); (HttpURLConnection) new URL(postMessage.postUrl).openConnection();
byte[] postData = postMessage.message.getBytes("UTF-8");
connection.setUseCaches(false);
connection.setDoOutput(true); connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST");
connection.setFixedLengthStreamingMode(postData.length);
connection.setRequestProperty( connection.setRequestProperty(
"content-type", "text/plain; charset=utf-8"); "content-type", "text/plain; charset=utf-8");
connection.getOutputStream().write(
gaeMessage.message.getBytes("UTF-8")); // Send POST request.
String replyHeader = connection.getHeaderField(null); OutputStream outStream = connection.getOutputStream();
if (!replyHeader.startsWith("HTTP/1.1 200 ")) { outStream.write(postData);
outStream.close();
// Get response.
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
reportError("Non-200 response to POST: " + reportError("Non-200 response to POST: " +
connection.getHeaderField(null)); connection.getHeaderField(null));
} }
InputStream responseStream = connection.getInputStream();
String response = drainStream(responseStream);
responseStream.close();
if (roomState != ConnectionState.CLOSED) {
JSONObject roomJson = new JSONObject(response);
String result = roomJson.getString("result");
if (!result.equals("SUCCESS")) {
reportError("Room POST error: " + result);
}
} }
} catch (IOException e) { } catch (IOException e) {
reportError("GAE POST error: " + e.getMessage()); reportError("GAE POST error: " + e.getMessage());
} catch (JSONException e) { } catch (JSONException e) {
reportError("GAE POST JSON error: " + e.getMessage()); reportError("GAE POST JSON error: " + e.getMessage());
} }
gaePostQueue.clear();
} }
} }
// Return the contents of an InputStream as a String.
private String drainStream(InputStream in) {
Scanner s = new Scanner(in).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
} }