diff --git a/talk/examples/android/res/values/strings.xml b/talk/examples/android/res/values/strings.xml index 08ff54190..c29050521 100644 --- a/talk/examples/android/res/values/strings.xml +++ b/talk/examples/android/res/values/strings.xml @@ -57,6 +57,11 @@ Use VP8 VP8 hardware accelerated codec (if available). true + signaling_preference + Use WebSocket signaling. + Use WebSocket signaling. + true + Enabled Disabled diff --git a/talk/examples/android/res/xml/preferences.xml b/talk/examples/android/res/xml/preferences.xml index 94894bc22..57c0315d8 100644 --- a/talk/examples/android/res/xml/preferences.xml +++ b/talk/examples/android/res/xml/preferences.xml @@ -43,5 +43,10 @@ android:dialogTitle="@string/pref_cpu_usage_detection_dlg" android:defaultValue="@string/pref_cpu_usage_detection_default" /> + diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java index 96816a77c..9fbdb3b19 100644 --- a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java +++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java @@ -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 iceCandidates; public SignalingParameters( List 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 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 { diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java index 85e81e7d1..a83e6696e 100644 --- a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java +++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java @@ -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 and exit. diff --git a/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java b/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java index 9fe6f5191..b8e0e6a73 100644 --- a/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java +++ b/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java @@ -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 roomList; private ArrayAdapter 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); } } diff --git a/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java b/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java index fb0f9f07d..c1d7a5184 100644 --- a/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java +++ b/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java @@ -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( diff --git a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java index 9d53049c0..da94cdbb1 100644 --- a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java +++ b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java @@ -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); diff --git a/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java b/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java index f49a87386..bdeb9fbcd 100644 --- a/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java +++ b/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java @@ -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 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(); + 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 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 diff --git a/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java b/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java index 4ca81ef00..0bed40313 100644 --- a/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java +++ b/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java @@ -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)) { diff --git a/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java b/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java index 170c80700..33aa63b80 100644 --- a/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java +++ b/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java @@ -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() { diff --git a/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java index ef6ef288c..4bfb418d4 100644 --- a/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java +++ b/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java @@ -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 gaePostQueue; + private LinkedList postQueue; private String postMessageUrl; private String byeMessageUrl; public WebSocketRTCClient(SignalingEvents events) { this.events = events; uiHandler = new Handler(Looper.getMainLooper()); - gaePostQueue = new LinkedList(); + postQueue = new LinkedList(); } // -------------------------------------------------------------------- @@ -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/, 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() { 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() : ""; + } + }