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_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,5 +43,10 @@
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

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

View File

@ -71,7 +71,6 @@ public class AppRTCDemoActivity extends Activity
implements AppRTCClient.SignalingEvents,
PeerConnectionClient.PeerConnectionEvents {
private static final String TAG = "AppRTCClient";
private final boolean USE_WEBSOCKETS = false;
private PeerConnectionClient pc;
private AppRTCClient appRtcClient;
private SignalingParameters signalingParameters;
@ -87,8 +86,9 @@ public class AppRTCDemoActivity extends Activity
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
private TextView hudView;
private TextView encoderStatView;
private TextView roomName;
private TextView roomNameView;
private ImageButton videoScalingButton;
private String roomName;
private boolean commandLineRun;
private int runTimeMs;
private int startBitrate;
@ -119,7 +119,7 @@ public class AppRTCDemoActivity extends Activity
rootView = findViewById(android.R.id.content);
encoderStatView = (TextView)findViewById(R.id.encoder_stat);
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);
VideoRendererGui.setView(videoView);
@ -135,11 +135,11 @@ public class AppRTCDemoActivity extends Activity
? View.INVISIBLE : View.VISIBLE;
encoderStatView.setVisibility(visibility);
menuBar.setVisibility(visibility);
roomName.setVisibility(visibility);
roomNameView.setVisibility(visibility);
if (visibility == View.VISIBLE) {
encoderStatView.bringToFront();
menuBar.bringToFront();
roomName.bringToFront();
roomNameView.bringToFront();
rootView.invalidate();
}
}
@ -206,6 +206,7 @@ public class AppRTCDemoActivity extends Activity
final Intent intent = getIntent();
Uri url = intent.getData();
roomName = intent.getStringExtra(ConnectActivity.EXTRA_ROOMNAME);
boolean loopback = intent.getBooleanExtra(
ConnectActivity.EXTRA_LOOPBACK, false);
commandLineRun = intent.getBooleanExtra(
@ -213,21 +214,22 @@ 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) {
String room = url.getQueryParameter("r");
if (loopback || (room != null && !room.equals(""))) {
if (loopback || (roomName != null && !roomName.equals(""))) {
logAndToast(getString(R.string.connecting_to, url));
if (USE_WEBSOCKETS) {
if (useWebsocket) {
appRtcClient = new WebSocketRTCClient(this);
} else {
appRtcClient = new GAERTCClient(this, this);
}
appRtcClient.connectToRoom(url.toString(), loopback);
if (loopback) {
roomName.setText("loopback");
roomNameView.setText("loopback");
} else {
roomName.setText(room);
roomNameView.setText(roomName);
}
if (commandLineRun && runTimeMs > 0) {
// 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 java.util.ArrayList;
import java.util.Random;
import org.json.JSONArray;
import org.json.JSONException;
@ -62,16 +63,18 @@ import org.webrtc.MediaCodecVideoEncoder;
*/
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_CMDLINE = "org.appspot.apprtc.CMDLINE";
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_HWCODEC = "org.appspot.apprtc.HWCODEC";
private static final String TAG = "ConnectActivity";
private final boolean USE_WEBSOCKETS = false;
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://8-dot-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;
private ImageButton addRoomButton;
private ImageButton removeRoomButton;
@ -86,12 +89,11 @@ 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;
private ArrayAdapter<String> adapter;
private boolean commandLineRun;
private int runTimeMs;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -106,6 +108,7 @@ 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);
@ -140,17 +143,15 @@ public class ConnectActivity extends Activity {
connectLoopbackButton.setOnClickListener(connectListener);
// If an implicit VIEW intent is launching the app, go directly to that URL.
commandLineRun = false;
final Intent intent = getIntent();
if ("android.intent.action.VIEW".equals(intent.getAction())) {
if ("android.intent.action.VIEW".equals(intent.getAction()) &&
!commandLineRun) {
commandLineRun = true;
boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false);
runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
String url = intent.getData().toString();
if (loopback && !url.contains("debug=loopback")) {
url += "/?debug=loopback";
}
connectToRoom(url, loopback, 0, true);
int runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
String room = sharedPref.getString(keyprefRoom, "");
roomEditText.setText(room);
connectToRoom(loopback, runTimeMs);
return;
}
}
@ -227,108 +228,141 @@ public class ConnectActivity extends Activity {
if (view.getId() == R.id.connect_loopback_button) {
loopback = true;
}
String url;
if (USE_WEBSOCKETS) {
url = APPRTC_WS_SERVER;
} else {
url = APPRTC_SERVER;
}
if (loopback) {
url += "/?debug=loopback";
} else {
String roomName = getSelectedItem();
if (roomName == null) {
roomName = roomEditText.getText().toString();
}
url += "/?r=" + roomName;
}
// Check HW codec flag.
boolean hwCodec = sharedPref.getBoolean(keyprefHwCodec,
Boolean.valueOf(getString(R.string.pref_hwcodec_default)));
// Add video resolution constraints.
String parametersResolution = null;
String parametersFps = null;
String resolution = sharedPref.getString(keyprefResolution,
getString(R.string.pref_resolution_default));
String[] dimensions = resolution.split("[ x]+");
if (dimensions.length == 2) {
try {
int maxWidth = Integer.parseInt(dimensions[0]);
int maxHeight = Integer.parseInt(dimensions[1]);
if (maxWidth > 0 && maxHeight > 0) {
parametersResolution = "minHeight=" + maxHeight + ",maxHeight=" +
maxHeight + ",minWidth=" + maxWidth + ",maxWidth=" + maxWidth;
}
} catch (NumberFormatException e) {
Log.e(TAG, "Wrong video resolution setting: " + resolution);
}
}
// Add camera fps constraints.
String fps = sharedPref.getString(keyprefFps,
getString(R.string.pref_fps_default));
String[] fpsValues = fps.split("[ x]+");
if (fpsValues.length == 2) {
try {
int cameraFps = Integer.parseInt(fpsValues[0]);
if (cameraFps > 0) {
parametersFps = "minFrameRate=" + cameraFps +
",maxFrameRate=" + cameraFps;
}
} catch (NumberFormatException e) {
Log.e(TAG, "Wrong camera fps setting: " + fps);
}
}
// Modify connection URL.
if (parametersResolution != null || parametersFps != null) {
url += "&video=";
if (parametersResolution != null) {
url += parametersResolution;
if (parametersFps != null) {
url += ",";
}
}
if (parametersFps != null) {
url += parametersFps;
}
} else {
if (hwCodec && MediaCodecVideoEncoder.isPlatformSupported()) {
url += "&hd=true";
}
}
// Get start bitrate.
int startBitrate = 0;
String bitrateTypeDefault = getString(R.string.pref_startbitrate_default);
String bitrateType = sharedPref.getString(
keyprefBitrateType, bitrateTypeDefault);
if (!bitrateType.equals(bitrateTypeDefault)) {
String bitrateValue = sharedPref.getString(keyprefBitrateValue,
getString(R.string.pref_startbitratevalue_default));
startBitrate = Integer.parseInt(bitrateValue);
}
// Test if CpuOveruseDetection should be disabled. By default is on.
boolean cpuOveruseDetection = sharedPref.getBoolean(
keyprefCpuUsageDetection,
Boolean.valueOf(
getString(R.string.pref_cpu_usage_detection_default)));
if (!cpuOveruseDetection) {
url += "&googCpuOveruseDetection=false";
}
// TODO(kjellander): Add support for custom parameters to the URL.
connectToRoom(url, loopback, startBitrate, hwCodec);
commandLineRun = false;
connectToRoom(loopback, 0);
}
};
private void connectToRoom(
String roomUrl, boolean loopback, int startBitrate, boolean hwCodec) {
if (validateUrl(roomUrl)) {
Uri url = Uri.parse(roomUrl);
private String appendQueryParameter(String url, String parameter) {
String newUrl = url;
if (newUrl.contains("?")) {
newUrl += "&" + parameter;
} else {
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) {
roomName = roomEditText.getText().toString();
}
}
// 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.
boolean hwCodec = sharedPref.getBoolean(keyprefHwCodec,
Boolean.valueOf(getString(R.string.pref_hwcodec_default)));
// Add video resolution constraints.
String parametersResolution = null;
String parametersFps = null;
String resolution = sharedPref.getString(keyprefResolution,
getString(R.string.pref_resolution_default));
String[] dimensions = resolution.split("[ x]+");
if (dimensions.length == 2) {
try {
int maxWidth = Integer.parseInt(dimensions[0]);
int maxHeight = Integer.parseInt(dimensions[1]);
if (maxWidth > 0 && maxHeight > 0) {
parametersResolution = "minHeight=" + maxHeight + ",maxHeight=" +
maxHeight + ",minWidth=" + maxWidth + ",maxWidth=" + maxWidth;
}
} catch (NumberFormatException e) {
Log.e(TAG, "Wrong video resolution setting: " + resolution);
}
}
// Add camera fps constraints.
String fps = sharedPref.getString(keyprefFps,
getString(R.string.pref_fps_default));
String[] fpsValues = fps.split("[ x]+");
if (fpsValues.length == 2) {
try {
int cameraFps = Integer.parseInt(fpsValues[0]);
if (cameraFps > 0) {
parametersFps = "minFrameRate=" + cameraFps +
",maxFrameRate=" + cameraFps;
}
} catch (NumberFormatException e) {
Log.e(TAG, "Wrong camera fps setting: " + fps);
}
}
// Modify connection URL.
if (parametersResolution != null || parametersFps != null) {
String urlVideoParameters = "video=";
if (parametersResolution != null) {
urlVideoParameters += parametersResolution;
if (parametersFps != null) {
urlVideoParameters += ",";
}
}
if (parametersFps != null) {
urlVideoParameters += parametersFps;
}
url = appendQueryParameter(url, urlVideoParameters);
} else {
if (hwCodec && MediaCodecVideoEncoder.isPlatformSupported()) {
url = appendQueryParameter(url, "hd=true");
}
}
// Get start bitrate.
int startBitrate = 0;
String bitrateTypeDefault = getString(R.string.pref_startbitrate_default);
String bitrateType = sharedPref.getString(
keyprefBitrateType, bitrateTypeDefault);
if (!bitrateType.equals(bitrateTypeDefault)) {
String bitrateValue = sharedPref.getString(keyprefBitrateValue,
getString(R.string.pref_startbitratevalue_default));
startBitrate = Integer.parseInt(bitrateValue);
}
// Test if CpuOveruseDetection should be disabled. By default is on.
boolean cpuOveruseDetection = sharedPref.getBoolean(
keyprefCpuUsageDetection,
Boolean.valueOf(
getString(R.string.pref_cpu_usage_detection_default)));
if (!cpuOveruseDetection) {
url = appendQueryParameter(url, "googCpuOveruseDetection=false");
}
// Start AppRTCDemo activity.
Log.d(TAG, "Connecting to room " + roomName + " at URL " + url);
if (validateUrl(url)) {
Uri uri = Uri.parse(url);
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_CMDLINE, commandLineRun);
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

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

View File

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

View File

@ -33,12 +33,14 @@ import org.appspot.apprtc.AppRTCClient.SignalingParameters;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedList;
@ -51,6 +53,7 @@ public class RoomParametersFetcher
private static final String TAG = "RoomRTCClient";
private Exception exception = null;
private RoomParametersFetcherEvents events = null;
private boolean useNewSignaling;
private boolean loopback;
/**
@ -69,10 +72,11 @@ public class RoomParametersFetcher
public void onSignalingParametersError(final String description);
}
public RoomParametersFetcher(
RoomParametersFetcherEvents events, boolean loopback) {
public RoomParametersFetcher(RoomParametersFetcherEvents events,
boolean useNewSignaling, boolean loopback) {
super();
this.events = events;
this.useNewSignaling = useNewSignaling;
this.loopback = loopback;
}
@ -115,45 +119,92 @@ public class RoomParametersFetcher
// Fetches |url| and fishes the signaling parameters out of the JSON.
private SignalingParameters getParametersForRoomUrl(String url)
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);
InputStream responseStream = new BufferedInputStream(
(new URL(url)).openConnection().getInputStream());
HttpURLConnection connection =
(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);
responseStream.close();
Log.d(TAG, "Room response: " + response);
JSONObject roomJson = new JSONObject(response);
if (roomJson.has("error")) {
JSONArray errors = roomJson.getJSONArray("error_messages");
throw new IOException(errors.toString());
}
String roomId = roomJson.getString("room_key");
String clientId = roomJson.getString("me");
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);
String roomId;
String clientId;
String roomUrl;
String channelToken = "";
String wssUrl = "";
String wssPostUrl = "";
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;
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 {
initiator = roomJson.getInt("initiator") == 1;
if (roomJson.has("error")) {
JSONArray errors = roomJson.getJSONArray("error_messages");
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('?'));
}
Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId);
Log.d(TAG, "Initiator: " + initiator);
Log.d(TAG, "Room url: " + roomUrl);
LinkedList<PeerConnection.IceServer> iceServers =
iceServersFromPCConfigJSON(roomJson.getString("pc_config"));
@ -191,7 +242,8 @@ public class RoomParametersFetcher
iceServers, initiator,
pcConstraints, videoConstraints, audioConstraints,
roomUrl, roomId, clientId,
channelToken, offerSdp);
wssUrl, wssPostUrl, channelToken,
offerSdp, iceCandidates);
}
// 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 keyprefHwCodec;
private String keyprefCpuUsageDetection;
private String keyprefSignaling;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -52,6 +53,7 @@ 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();
@ -74,6 +76,7 @@ public class SettingsActivity extends Activity
setBitrateEnable(sharedPreferences);
updateSummaryB(sharedPreferences, keyprefHwCodec);
updateSummaryB(sharedPreferences, keyprefCpuUsageDetection);
updateSummaryB(sharedPreferences, keyprefSignaling);
}
@Override
@ -93,7 +96,7 @@ public class SettingsActivity extends Activity
} else if (key.equals(keyprefStartBitrateValue)) {
updateSummaryBitrate(sharedPreferences, key);
} else if (key.equals(keyprefCpuUsageDetection) ||
key.equals(keyprefHwCodec)) {
key.equals(keyprefHwCodec) || key.equals(keyprefSignaling)) {
updateSummaryB(sharedPreferences, key);
}
if (key.equals(keyprefStartBitrateType)) {

View File

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

View File

@ -32,9 +32,12 @@ import android.os.Looper;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedList;
import java.util.Scanner;
import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents;
import org.appspot.apprtc.WebSocketChannelClient.WebSocketChannelEvents;
@ -57,31 +60,25 @@ import org.webrtc.SessionDescription;
public class WebSocketRTCClient implements AppRTCClient,
RoomParametersFetcherEvents, WebSocketChannelEvents {
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 {
NEW, CONNECTED, CLOSED, ERROR
};
private final Handler uiHandler;
private boolean loopback;
private boolean initiator;
private SignalingEvents events;
private SignalingParameters signalingParameters;
private WebSocketChannelClient wsClient;
private RoomParametersFetcher fetcher;
private ConnectionState roomState;
private LinkedList<GAEMessage> gaePostQueue;
private LinkedList<PostMessage> postQueue;
private String postMessageUrl;
private String byeMessageUrl;
public WebSocketRTCClient(SignalingEvents events) {
this.events = events;
uiHandler = new Handler(Looper.getMainLooper());
gaePostQueue = new LinkedList<GAEMessage>();
postQueue = new LinkedList<PostMessage>();
}
// --------------------------------------------------------------------
@ -90,31 +87,38 @@ public class WebSocketRTCClient implements AppRTCClient,
@Override
public void onSignalingParametersReady(final SignalingParameters params) {
Log.d(TAG, "Room connection completed.");
if (!loopback && !params.initiator && params.offerSdp == null) {
reportError("Offer SDP is not available.");
return;
}
if (loopback && params.offerSdp != null) {
if (loopback && (!params.initiator || params.offerSdp != null)) {
reportError("Loopback room is busy.");
return;
}
signalingParameters = params;
postMessageUrl = params.roomUrl + "message?r=" +
params.roomId + "&u=" + params.clientId;
byeMessageUrl = params.roomUrl + "bye/" +
if (!loopback && !params.initiator && params.offerSdp == null) {
Log.w(TAG, "No offer SDP in room response.");
}
initiator = params.initiator;
postMessageUrl = params.roomUrl + "/message/" +
params.roomId + "/" + params.clientId;
byeMessageUrl = params.roomUrl + "/bye/" +
params.roomId + "/" + params.clientId;
roomState = ConnectionState.CONNECTED;
wsClient.setClientParameters(
signalingParameters.roomId, signalingParameters.clientId);
wsClient.register();
events.onConnectedToRoom(signalingParameters);
// Connect to WebSocket server.
wsClient.connect(params.wssUrl, params.wssPostUrl);
wsClient.setClientParameters(params.roomId, params.clientId);
// Fire connection and signaling parameters events.
events.onConnectedToRoom(params);
events.onChannelOpen();
if (!signalingParameters.initiator) {
// For call receiver get sdp offer from room parameters.
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm("offer"),
signalingParameters.offerSdp);
events.onRemoteDescription(sdp);
if (!params.initiator) {
// For call receiver get sdp offer and ice candidates
// from room parameters.
if (params.offerSdp != null) {
events.onRemoteDescription(params.offerSdp);
}
if (params.iceCandidates != null) {
for (IceCandidate iceCandidate : params.iceCandidates) {
events.onRemoteIceCandidate(iceCandidate);
}
}
}
}
@ -128,10 +132,8 @@ public class WebSocketRTCClient implements AppRTCClient,
// All events are called on UI thread.
@Override
public void onWebSocketOpen() {
Log.d(TAG, "Websocket connection completed.");
if (roomState == ConnectionState.CONNECTED) {
wsClient.register();
}
Log.d(TAG, "Websocket connection completed. Registering...");
wsClient.register();
}
@Override
@ -149,15 +151,28 @@ public class WebSocketRTCClient implements AppRTCClient,
String type = json.optString("type");
if (type.equals("candidate")) {
IceCandidate candidate = new IceCandidate(
(String) json.get("id"),
json.getString("id"),
json.getInt("label"),
(String) json.get("candidate"));
json.getString("candidate"));
events.onRemoteIceCandidate(candidate);
} else if (type.equals("answer")) {
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(type),
(String)json.get("sdp"));
events.onRemoteDescription(sdp);
if (initiator) {
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(type),
json.getString("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")) {
events.onChannelClose();
} else {
@ -189,32 +204,28 @@ public class WebSocketRTCClient implements AppRTCClient,
// --------------------------------------------------------------------
// AppRTCClient interface implementation.
// 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.
@Override
public void connectToRoom(String url, boolean loopback) {
this.loopback = loopback;
// Create WebSocket client.
wsClient = new WebSocketChannelClient(this);
// Get room parameters.
roomState = ConnectionState.NEW;
fetcher = new RoomParametersFetcher(this, loopback);
fetcher = new RoomParametersFetcher(this, true, loopback);
fetcher.execute(url);
// Connect to WebSocket server.
wsClient = new WebSocketChannelClient(this);
if (!loopback) {
wsClient.connect(WSS_SERVER, WSS_POST_URL);
}
}
@Override
public void disconnect() {
Log.d(TAG, "Disconnect. Room state: " + roomState);
wsClient.disconnect();
if (roomState == ConnectionState.CONNECTED) {
Log.d(TAG, "Closing room.");
// TODO(glaznev): Remove json bye message sending once new bye will
// be supported on 8-dot.
//sendGAEMessage(byeMessageUrl, "");
sendGAEMessage(postMessageUrl, "{\"type\": \"bye\"}");
sendGAEMessage(byeMessageUrl, "");
}
if (wsClient != null) {
wsClient.disconnect();
}
}
@ -225,17 +236,16 @@ public class WebSocketRTCClient implements AppRTCClient,
// we might want to filter elsewhere.
@Override
public void sendOfferSdp(final SessionDescription sdp) {
JSONObject json = new JSONObject();
jsonPut(json, "sdp", sdp.description);
jsonPut(json, "type", "offer");
sendGAEMessage(postMessageUrl, json.toString());
if (loopback) {
// In loopback mode rename this offer to answer and send it back.
// 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);
} else {
JSONObject json = new JSONObject();
jsonPut(json, "sdp", sdp.description);
jsonPut(json, "type", "offer");
sendGAEMessage(postMessageUrl, json.toString());
}
}
@ -258,18 +268,27 @@ public class WebSocketRTCClient implements AppRTCClient,
// Send Ice candidate to the other participant.
@Override
public void sendLocalIceCandidate(final IceCandidate candidate) {
if (loopback) {
events.onRemoteIceCandidate(candidate);
JSONObject json = new JSONObject();
jsonPut(json, "type", "candidate");
jsonPut(json, "label", candidate.sdpMLineIndex);
jsonPut(json, "id", candidate.sdpMid);
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;
}
JSONObject json = new JSONObject();
jsonPut(json, "type", "candidate");
jsonPut(json, "label", candidate.sdpMLineIndex);
jsonPut(json, "id", candidate.sdpMid);
jsonPut(json, "candidate", candidate.sdp);
wsClient.send(json.toString());
}
}
@ -297,8 +316,8 @@ public class WebSocketRTCClient implements AppRTCClient,
}
}
private class GAEMessage {
GAEMessage(String postUrl, String message) {
private class PostMessage {
PostMessage(String postUrl, String message) {
this.postUrl = postUrl;
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.
private synchronized void sendGAEMessage(String url, String message) {
synchronized (gaePostQueue) {
gaePostQueue.add(new GAEMessage(url, message));
synchronized (postQueue) {
postQueue.add(new PostMessage(url, message));
}
(new AsyncTask<Void, Void, Void>() {
public Void doInBackground(Void... unused) {
@ -321,37 +340,58 @@ public class WebSocketRTCClient implements AppRTCClient,
// Send all queued messages if connected to the room.
private void maybeDrainGAEPostQueue() {
synchronized (gaePostQueue) {
if (roomState != ConnectionState.CONNECTED) {
return;
if (roomState != ConnectionState.CONNECTED) {
return;
}
PostMessage postMessage = null;
while (true) {
synchronized (postQueue) {
postMessage = postQueue.poll();
}
if (postMessage == null) {
break;
}
try {
for (GAEMessage gaeMessage : gaePostQueue) {
Log.d(TAG, "ROOM SEND to " + gaeMessage.postUrl +
". Message: " + gaeMessage.message);
// 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.
HttpURLConnection connection =
(HttpURLConnection) new URL(gaeMessage.postUrl).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty(
"content-type", "text/plain; charset=utf-8");
connection.getOutputStream().write(
gaeMessage.message.getBytes("UTF-8"));
String replyHeader = connection.getHeaderField(null);
if (!replyHeader.startsWith("HTTP/1.1 200 ")) {
reportError("Non-200 response to POST: " +
connection.getHeaderField(null));
// 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) new URL(postMessage.postUrl).openConnection();
byte[] postData = postMessage.message.getBytes("UTF-8");
connection.setUseCaches(false);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST");
connection.setFixedLengthStreamingMode(postData.length);
connection.setRequestProperty(
"content-type", "text/plain; charset=utf-8");
// Send POST request.
OutputStream outStream = connection.getOutputStream();
outStream.write(postData);
outStream.close();
// Get response.
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
reportError("Non-200 response to POST: " +
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) {
@ -359,9 +399,13 @@ public class WebSocketRTCClient implements AppRTCClient,
} catch (JSONException e) {
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() : "";
}
}