diff --git a/talk/examples/android/res/layout/activity_call.xml b/talk/examples/android/res/layout/activity_call.xml
index 0aca7ad70b..a18f758568 100644
--- a/talk/examples/android/res/layout/activity_call.xml
+++ b/talk/examples/android/res/layout/activity_call.xml
@@ -4,8 +4,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
+ android:layout_height="match_parent">
+
diff --git a/talk/examples/android/res/layout/fragment_call.xml b/talk/examples/android/res/layout/fragment_call.xml
index f0d5d3c58c..70a9a28197 100644
--- a/talk/examples/android/res/layout/fragment_call.xml
+++ b/talk/examples/android/res/layout/fragment_call.xml
@@ -4,18 +4,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
-
+ android:layout_height="match_parent">
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/talk/examples/android/src/org/appspot/apprtc/CallActivity.java b/talk/examples/android/src/org/appspot/apprtc/CallActivity.java
index 815081183a..a4255b5f27 100644
--- a/talk/examples/android/src/org/appspot/apprtc/CallActivity.java
+++ b/talk/examples/android/src/org/appspot/apprtc/CallActivity.java
@@ -43,7 +43,7 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
-import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
import android.widget.Toast;
import org.webrtc.IceCandidate;
@@ -132,6 +132,7 @@ public class CallActivity extends Activity
// Controls
private GLSurfaceView videoView;
CallFragment callFragment;
+ HudFragment hudFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -142,8 +143,12 @@ public class CallActivity extends Activity
// Set window styles for fullscreen-window size. Needs to be done before
// adding content.
requestWindowFeature(Window.FEATURE_NO_TITLE);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ getWindow().addFlags(
+ LayoutParams.FLAG_FULLSCREEN
+ | LayoutParams.FLAG_KEEP_SCREEN_ON
+ | LayoutParams.FLAG_DISMISS_KEYGUARD
+ | LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | LayoutParams.FLAG_TURN_SCREEN_ON);
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
@@ -157,6 +162,7 @@ public class CallActivity extends Activity
// Create UI controls.
videoView = (GLSurfaceView) findViewById(R.id.glview_call);
callFragment = new CallFragment();
+ hudFragment = new HudFragment();
// Create video renderers.
VideoRendererGui.setView(videoView, new Runnable() {
@@ -219,11 +225,14 @@ public class CallActivity extends Activity
roomConnectionParameters = new RoomConnectionParameters(
roomUri.toString(), roomId, loopback);
- // Send intent arguments to fragment.
+ // Send intent arguments to fragments.
callFragment.setArguments(intent.getExtras());
- // Activate call fragment and start the call.
- getFragmentManager().beginTransaction()
- .add(R.id.call_fragment_container, callFragment).commit();
+ hudFragment.setArguments(intent.getExtras());
+ // Activate call and HUD fragments and start the call.
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.add(R.id.call_fragment_container, callFragment);
+ ft.add(R.id.hud_fragment_container, hudFragment);
+ ft.commit();
startCall();
// For command line execution run connection for and exit.
@@ -296,8 +305,10 @@ public class CallActivity extends Activity
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (callControlFragmentVisible) {
ft.show(callFragment);
+ ft.show(hudFragment);
} else {
ft.hide(callFragment);
+ ft.hide(hudFragment);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
@@ -387,6 +398,7 @@ public class CallActivity extends Activity
// Disconnect from remote resources, dispose of local resources, and exit.
private void disconnect() {
+ activityRunning = false;
if (appRtcClient != null) {
appRtcClient.disconnectFromRoom();
appRtcClient = null;
@@ -436,6 +448,18 @@ public class CallActivity extends Activity
logToast.show();
}
+ private void reportError(final String description) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isError) {
+ isError = true;
+ disconnectWithErrorMessage(description);
+ }
+ }
+ });
+ }
+
// -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
// All callbacks are invoked from websocket signaling looper thread and
// are routed to UI thread.
@@ -533,15 +557,7 @@ public class CallActivity extends Activity
@Override
public void onChannelError(final String description) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (!isError) {
- isError = true;
- disconnectWithErrorMessage(description);
- }
- }
- });
+ reportError(description);
}
// -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
@@ -613,7 +629,7 @@ public class CallActivity extends Activity
@Override
public void run() {
if (!isError && iceConnected) {
- callFragment.updateEncoderStatistics(reports);
+ hudFragment.updateEncoderStatistics(reports);
}
}
});
@@ -621,14 +637,6 @@ public class CallActivity extends Activity
@Override
public void onPeerConnectionError(final String description) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (!isError) {
- isError = true;
- disconnectWithErrorMessage(description);
- }
- }
- });
+ reportError(description);
}
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/CallFragment.java b/talk/examples/android/src/org/appspot/apprtc/CallFragment.java
index dc7695c4ef..d0b670da73 100644
--- a/talk/examples/android/src/org/appspot/apprtc/CallFragment.java
+++ b/talk/examples/android/src/org/appspot/apprtc/CallFragment.java
@@ -30,36 +30,26 @@ package org.appspot.apprtc;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
-import org.webrtc.StatsReport;
import org.webrtc.VideoRendererGui.ScalingType;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Fragment for call control.
*/
public class CallFragment extends Fragment {
private View controlView;
- private TextView encoderStatView;
- private TextView roomIdView;
+ private TextView contactView;
private ImageButton disconnectButton;
private ImageButton cameraSwitchButton;
private ImageButton videoScalingButton;
- private ImageButton toggleDebugButton;
private OnCallEvents callEvents;
private ScalingType scalingType;
- private boolean displayHud;
- private volatile boolean isRunning;
- private TextView hudView;
- private final CpuMonitor cpuMonitor = new CpuMonitor();
+ private boolean videoCallEnabled = true;
/**
* Call control interface for container activity.
@@ -77,20 +67,14 @@ public class CallFragment extends Fragment {
inflater.inflate(R.layout.fragment_call, container, false);
// Create UI controls.
- encoderStatView =
- (TextView) controlView.findViewById(R.id.encoder_stat_call);
- roomIdView =
+ contactView =
(TextView) controlView.findViewById(R.id.contact_name_call);
- hudView =
- (TextView) controlView.findViewById(R.id.hud_stat_call);
disconnectButton =
(ImageButton) controlView.findViewById(R.id.button_call_disconnect);
cameraSwitchButton =
(ImageButton) controlView.findViewById(R.id.button_call_switch_camera);
videoScalingButton =
(ImageButton) controlView.findViewById(R.id.button_call_scaling_mode);
- toggleDebugButton =
- (ImageButton) controlView.findViewById(R.id.button_toggle_debug);
// Add buttons click events.
disconnectButton.setOnClickListener(new View.OnClickListener() {
@@ -124,17 +108,6 @@ public class CallFragment extends Fragment {
});
scalingType = ScalingType.SCALE_ASPECT_FILL;
- toggleDebugButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (displayHud) {
- int visibility = (hudView.getVisibility() == View.VISIBLE)
- ? View.INVISIBLE : View.VISIBLE;
- hudView.setVisibility(visibility);
- }
- }
- });
-
return controlView;
}
@@ -144,22 +117,13 @@ public class CallFragment extends Fragment {
Bundle args = getArguments();
if (args != null) {
- String roomId = args.getString(CallActivity.EXTRA_ROOMID);
- roomIdView.setText(roomId);
- displayHud = args.getBoolean(CallActivity.EXTRA_DISPLAY_HUD, false);
+ String contactName = args.getString(CallActivity.EXTRA_ROOMID);
+ contactView.setText(contactName);
+ videoCallEnabled = args.getBoolean(CallActivity.EXTRA_VIDEO_CALL, true);
+ }
+ if (!videoCallEnabled) {
+ cameraSwitchButton.setVisibility(View.INVISIBLE);
}
- int visibility = displayHud ? View.VISIBLE : View.INVISIBLE;
- encoderStatView.setVisibility(visibility);
- toggleDebugButton.setVisibility(visibility);
- hudView.setVisibility(View.INVISIBLE);
- hudView.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
- isRunning = true;
- }
-
- @Override
- public void onStop() {
- isRunning = false;
- super.onStop();
}
@Override
@@ -168,73 +132,4 @@ public class CallFragment extends Fragment {
callEvents = (OnCallEvents) activity;
}
- private Map getReportMap(StatsReport report) {
- Map reportMap = new HashMap();
- for (StatsReport.Value value : report.values) {
- reportMap.put(value.name, value.value);
- }
- return reportMap;
- }
-
- public void updateEncoderStatistics(final StatsReport[] reports) {
- if (!isRunning || !displayHud) {
- return;
- }
- String fps = null;
- String targetBitrate = null;
- String actualBitrate = null;
- StringBuilder bweBuilder = new StringBuilder();
- for (StatsReport report : reports) {
- if (report.type.equals("ssrc") && report.id.contains("ssrc")
- && report.id.contains("send")) {
- Map reportMap = getReportMap(report);
- String trackId = reportMap.get("googTrackId");
- if (trackId != null
- && trackId.contains(PeerConnectionClient.VIDEO_TRACK_ID)) {
- fps = reportMap.get("googFrameRateSent");
- }
- } else if (report.id.equals("bweforvideo")) {
- Map reportMap = getReportMap(report);
- targetBitrate = reportMap.get("googTargetEncBitrate");
- actualBitrate = reportMap.get("googActualEncBitrate");
-
- for (StatsReport.Value value : report.values) {
- String name = value.name.replace("goog", "")
- .replace("Available", "").replace("Bandwidth", "")
- .replace("Bitrate", "").replace("Enc", "");
- bweBuilder.append(name).append("=").append(value.value)
- .append(" ");
- }
- bweBuilder.append("\n");
- }
- }
-
- StringBuilder stat = new StringBuilder(128);
- if (fps != null) {
- stat.append("Fps: ")
- .append(fps)
- .append("\n");
- }
- if (targetBitrate != null) {
- stat.append("Target BR: ")
- .append(targetBitrate)
- .append("\n");
- }
- if (actualBitrate != null) {
- stat.append("Actual BR: ")
- .append(actualBitrate)
- .append("\n");
- }
-
- if (cpuMonitor.sampleCpuUtilization()) {
- stat.append("CPU%: ")
- .append(cpuMonitor.getCpuCurrent())
- .append("/")
- .append(cpuMonitor.getCpuAvg3())
- .append("/")
- .append(cpuMonitor.getCpuAvgAll());
- }
- encoderStatView.setText(stat.toString());
- hudView.setText(bweBuilder.toString() + hudView.getText());
- }
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/HudFragment.java b/talk/examples/android/src/org/appspot/apprtc/HudFragment.java
new file mode 100644
index 0000000000..c1f8fa2b85
--- /dev/null
+++ b/talk/examples/android/src/org/appspot/apprtc/HudFragment.java
@@ -0,0 +1,217 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.appspot.apprtc;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import org.webrtc.StatsReport;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Fragment for HUD statistics display.
+ */
+public class HudFragment extends Fragment {
+ private View controlView;
+ private TextView encoderStatView;
+ private TextView hudViewBwe;
+ private TextView hudViewConnection;
+ private TextView hudViewVideoSend;
+ private TextView hudViewVideoRecv;
+ private ImageButton toggleDebugButton;
+ private boolean videoCallEnabled;
+ private boolean displayHud;
+ private volatile boolean isRunning;
+ private final CpuMonitor cpuMonitor = new CpuMonitor();
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ controlView = inflater.inflate(R.layout.fragment_hud, container, false);
+
+ // Create UI controls.
+ encoderStatView = (TextView) controlView.findViewById(R.id.encoder_stat_call);
+ hudViewBwe = (TextView) controlView.findViewById(R.id.hud_stat_bwe);
+ hudViewConnection = (TextView) controlView.findViewById(R.id.hud_stat_connection);
+ hudViewVideoSend = (TextView) controlView.findViewById(R.id.hud_stat_video_send);
+ hudViewVideoRecv = (TextView) controlView.findViewById(R.id.hud_stat_video_recv);
+ toggleDebugButton = (ImageButton) controlView.findViewById(R.id.button_toggle_debug);
+
+ toggleDebugButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (displayHud) {
+ int visibility = (hudViewBwe.getVisibility() == View.VISIBLE)
+ ? View.INVISIBLE : View.VISIBLE;
+ hudViewsSetProperties(visibility);
+ }
+ }
+ });
+
+ return controlView;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ Bundle args = getArguments();
+ if (args != null) {
+ videoCallEnabled = args.getBoolean(CallActivity.EXTRA_VIDEO_CALL, true);
+ displayHud = args.getBoolean(CallActivity.EXTRA_DISPLAY_HUD, false);
+ }
+ int visibility = displayHud ? View.VISIBLE : View.INVISIBLE;
+ encoderStatView.setVisibility(visibility);
+ toggleDebugButton.setVisibility(visibility);
+ hudViewsSetProperties(View.INVISIBLE);
+ isRunning = true;
+ }
+
+ @Override
+ public void onStop() {
+ isRunning = false;
+ super.onStop();
+ }
+
+ private void hudViewsSetProperties(int visibility) {
+ hudViewBwe.setVisibility(visibility);
+ hudViewConnection.setVisibility(visibility);
+ hudViewVideoSend.setVisibility(visibility);
+ hudViewVideoRecv.setVisibility(visibility);
+ hudViewBwe.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
+ hudViewConnection.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
+ hudViewVideoSend.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
+ hudViewVideoRecv.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
+ }
+
+ private Map getReportMap(StatsReport report) {
+ Map reportMap = new HashMap();
+ for (StatsReport.Value value : report.values) {
+ reportMap.put(value.name, value.value);
+ }
+ return reportMap;
+ }
+
+ public void updateEncoderStatistics(final StatsReport[] reports) {
+ if (!isRunning || !displayHud) {
+ return;
+ }
+ StringBuilder encoderStat = new StringBuilder(128);
+ StringBuilder bweStat = new StringBuilder();
+ StringBuilder connectionStat = new StringBuilder();
+ StringBuilder videoSendStat = new StringBuilder();
+ StringBuilder videoRecvStat = new StringBuilder();
+ String fps = null;
+ String targetBitrate = null;
+ String actualBitrate = null;
+
+ for (StatsReport report : reports) {
+ if (report.type.equals("ssrc") && report.id.contains("ssrc")
+ && report.id.contains("send")) {
+ // Send video statistics.
+ Map reportMap = getReportMap(report);
+ String trackId = reportMap.get("googTrackId");
+ if (trackId != null && trackId.contains(PeerConnectionClient.VIDEO_TRACK_ID)) {
+ fps = reportMap.get("googFrameRateSent");
+ videoSendStat.append(report.id).append("\n");
+ for (StatsReport.Value value : report.values) {
+ String name = value.name.replace("goog", "");
+ videoSendStat.append(name).append("=").append(value.value).append("\n");
+ }
+ }
+ } else if (report.type.equals("ssrc") && report.id.contains("ssrc")
+ && report.id.contains("recv")) {
+ // Receive video statistics.
+ Map reportMap = getReportMap(report);
+ // Check if this stat is for video track.
+ String frameWidth = reportMap.get("googFrameWidthReceived");
+ if (frameWidth != null) {
+ videoRecvStat.append(report.id).append("\n");
+ for (StatsReport.Value value : report.values) {
+ String name = value.name.replace("goog", "");
+ videoRecvStat.append(name).append("=").append(value.value).append("\n");
+ }
+ }
+ } else if (report.id.equals("bweforvideo")) {
+ // BWE statistics.
+ Map reportMap = getReportMap(report);
+ targetBitrate = reportMap.get("googTargetEncBitrate");
+ actualBitrate = reportMap.get("googActualEncBitrate");
+
+ bweStat.append(report.id).append("\n");
+ for (StatsReport.Value value : report.values) {
+ String name = value.name.replace("goog", "").replace("Available", "");
+ bweStat.append(name).append("=").append(value.value).append("\n");
+ }
+ } else if (report.type.equals("googCandidatePair")) {
+ // Connection statistics.
+ Map reportMap = getReportMap(report);
+ String activeConnection = reportMap.get("googActiveConnection");
+ if (activeConnection != null && activeConnection.equals("true")) {
+ connectionStat.append(report.id).append("\n");
+ for (StatsReport.Value value : report.values) {
+ String name = value.name.replace("goog", "");
+ connectionStat.append(name).append("=").append(value.value).append("\n");
+ }
+ }
+ }
+ }
+ hudViewBwe.setText(bweStat.toString());
+ hudViewConnection.setText(connectionStat.toString());
+ hudViewVideoSend.setText(videoSendStat.toString());
+ hudViewVideoRecv.setText(videoRecvStat.toString());
+
+ if (videoCallEnabled) {
+ if (fps != null) {
+ encoderStat.append("Fps: ").append(fps).append("\n");
+ }
+ if (targetBitrate != null) {
+ encoderStat.append("Target BR: ").append(targetBitrate).append("\n");
+ }
+ if (actualBitrate != null) {
+ encoderStat.append("Actual BR: ").append(actualBitrate).append("\n");
+ }
+ }
+
+ if (cpuMonitor.sampleCpuUtilization()) {
+ encoderStat.append("CPU%: ")
+ .append(cpuMonitor.getCpuCurrent()).append("/")
+ .append(cpuMonitor.getCpuAvg3()).append("/")
+ .append(cpuMonitor.getCpuAvgAll());
+ }
+ encoderStatView.setText(encoderStat.toString());
+ }
+}
diff --git a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
index 04517c0d9d..67290bef51 100644
--- a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
@@ -82,6 +82,7 @@ public class PeerConnectionClient {
private static final String MIN_VIDEO_HEIGHT_CONSTRAINT = "minHeight";
private static final String MAX_VIDEO_FPS_CONSTRAINT = "maxFrameRate";
private static final String MIN_VIDEO_FPS_CONSTRAINT = "minFrameRate";
+ private static final String DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT = "DtlsSrtpKeyAgreement";
private static final int HD_VIDEO_WIDTH = 1280;
private static final int HD_VIDEO_HEIGHT = 720;
private static final int MAX_VIDEO_WIDTH = 1280;
@@ -103,13 +104,15 @@ public class PeerConnectionClient {
private VideoRenderer.Callbacks localRender;
private VideoRenderer.Callbacks remoteRender;
private SignalingParameters signalingParameters;
+ private MediaConstraints pcConstraints;
private MediaConstraints videoConstraints;
+ private MediaConstraints audioConstraints;
+ private MediaConstraints sdpMediaConstraints;
private PeerConnectionParameters peerConnectionParameters;
// Queued remote ICE candidates are consumed only after both local and
// remote descriptions are set. Similarly local ICE candidates are sent to
// remote peer after both local and remote description are set.
private LinkedList queuedRemoteCandidates = null;
- private MediaConstraints sdpMediaConstraints;
private PeerConnectionEvents events;
private boolean isInitiator;
private SessionDescription localSdp = null; // either offer or answer SDP
@@ -210,6 +213,7 @@ public class PeerConnectionClient {
final PeerConnectionEvents events) {
this.peerConnectionParameters = peerConnectionParameters;
this.events = events;
+ videoCallEnabled = peerConnectionParameters.videoCallEnabled;
executor.requestStart();
executor.execute(new Runnable() {
@Override
@@ -230,59 +234,10 @@ public class PeerConnectionClient {
this.localRender = localRender;
this.remoteRender = remoteRender;
this.signalingParameters = signalingParameters;
- // Merge video constraints from signaling parameters and peer connection
- // parameters.
- videoConstraints = signalingParameters.videoConstraints;
- if (signalingParameters.videoConstraints == null) {
- videoCallEnabled = false;
- }
- // Check if there is a camera on device and disable video call if not.
- numberOfCameras = VideoCapturerAndroid.getDeviceCount();
- if (numberOfCameras == 0) {
- Log.w(TAG, "No camera on device. Switch to audio only call.");
- videoCallEnabled = false;
- }
- if (videoCallEnabled) {
- int videoWidth = peerConnectionParameters.videoWidth;
- int videoHeight = peerConnectionParameters.videoHeight;
-
- // If HW video encoder is supported and video resolution is not
- // specified force it to HD.
- if ((videoWidth == 0 || videoHeight == 0)
- && peerConnectionParameters.videoCodecHwAcceleration
- && MediaCodecVideoEncoder.isVp8HwSupported()) {
- videoWidth = HD_VIDEO_WIDTH;
- videoHeight = HD_VIDEO_HEIGHT;
- }
-
- // Add video resolution constraints.
- if (videoWidth > 0 && videoHeight > 0) {
- videoWidth = Math.min(videoWidth, MAX_VIDEO_WIDTH);
- videoHeight = Math.min(videoHeight, MAX_VIDEO_HEIGHT);
- videoConstraints.mandatory.add(new KeyValuePair(
- MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
- videoConstraints.mandatory.add(new KeyValuePair(
- MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
- videoConstraints.mandatory.add(new KeyValuePair(
- MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
- videoConstraints.mandatory.add(new KeyValuePair(
- MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
- }
-
- // Add fps constraints.
- int videoFps = peerConnectionParameters.videoFps;
- if (videoFps > 0) {
- videoFps = Math.min(videoFps, MAX_VIDEO_FPS);
- videoConstraints.mandatory.add(new KeyValuePair(
- MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
- videoConstraints.mandatory.add(new KeyValuePair(
- MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
- }
- }
-
executor.execute(new Runnable() {
@Override
public void run() {
+ createMediaConstraintsInternal();
createPeerConnectionInternal();
}
});
@@ -303,7 +258,6 @@ public class PeerConnectionClient {
Log.d(TAG, "Create peer connection factory with EGLContext "
+ renderEGLContext + ". Use video: "
+ peerConnectionParameters.videoCallEnabled);
- videoCallEnabled = peerConnectionParameters.videoCallEnabled;
isError = false;
// Check if VP9 is used by default.
if (videoCallEnabled && peerConnectionParameters.videoCodec != null
@@ -340,18 +294,68 @@ public class PeerConnectionClient {
protected void configureFactory(PeerConnectionFactory factory) {
}
- private void createPeerConnectionInternal() {
- if (factory == null || isError) {
- Log.e(TAG, "Peerconnection factory is not created");
- return;
+ private void createMediaConstraintsInternal() {
+ // Create peer connection constraints.
+ pcConstraints = new MediaConstraints();
+ // Enable DTLS for normal calls and disable for loopback calls.
+ if (peerConnectionParameters.loopback) {
+ pcConstraints.optional.add(
+ new MediaConstraints.KeyValuePair(DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT, "false"));
+ } else {
+ pcConstraints.optional.add(
+ new MediaConstraints.KeyValuePair(DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT, "true"));
}
- Log.d(TAG, "Create peer connection");
- if (videoConstraints != null) {
- Log.d(TAG, "VideoConstraints: " + videoConstraints.toString());
- }
- isInitiator = signalingParameters.initiator;
- queuedRemoteCandidates = new LinkedList();
+ // Check if there is a camera on device and disable video call if not.
+ numberOfCameras = VideoCapturerAndroid.getDeviceCount();
+ if (numberOfCameras == 0) {
+ Log.w(TAG, "No camera on device. Switch to audio only call.");
+ videoCallEnabled = false;
+ }
+ // Create video constraints if video call is enabled.
+ if (videoCallEnabled) {
+ videoConstraints = new MediaConstraints();
+ int videoWidth = peerConnectionParameters.videoWidth;
+ int videoHeight = peerConnectionParameters.videoHeight;
+
+ // If VP8 HW video encoder is supported and video resolution is not
+ // specified force it to HD.
+ if ((videoWidth == 0 || videoHeight == 0)
+ && peerConnectionParameters.videoCodecHwAcceleration
+ && MediaCodecVideoEncoder.isVp8HwSupported()) {
+ videoWidth = HD_VIDEO_WIDTH;
+ videoHeight = HD_VIDEO_HEIGHT;
+ }
+
+ // Add video resolution constraints.
+ if (videoWidth > 0 && videoHeight > 0) {
+ videoWidth = Math.min(videoWidth, MAX_VIDEO_WIDTH);
+ videoHeight = Math.min(videoHeight, MAX_VIDEO_HEIGHT);
+ videoConstraints.mandatory.add(new KeyValuePair(
+ MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
+ videoConstraints.mandatory.add(new KeyValuePair(
+ MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
+ videoConstraints.mandatory.add(new KeyValuePair(
+ MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
+ videoConstraints.mandatory.add(new KeyValuePair(
+ MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
+ }
+
+ // Add fps constraints.
+ int videoFps = peerConnectionParameters.videoFps;
+ if (videoFps > 0) {
+ videoFps = Math.min(videoFps, MAX_VIDEO_FPS);
+ videoConstraints.mandatory.add(new KeyValuePair(
+ MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
+ videoConstraints.mandatory.add(new KeyValuePair(
+ MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
+ }
+ }
+
+ // Create audio constraints.
+ audioConstraints = new MediaConstraints();
+
+ // Create SDP constraints.
sdpMediaConstraints = new MediaConstraints();
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
"OfferToReceiveAudio", "true"));
@@ -362,10 +366,20 @@ public class PeerConnectionClient {
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
"OfferToReceiveVideo", "false"));
}
+ }
+
+ private void createPeerConnectionInternal() {
+ if (factory == null || isError) {
+ Log.e(TAG, "Peerconnection factory is not created");
+ return;
+ }
+ Log.d(TAG, "Create peer connection");
+ Log.d(TAG, "PCConstraints: " + pcConstraints.toString());
+ if (videoConstraints != null) {
+ Log.d(TAG, "VideoConstraints: " + videoConstraints.toString());
+ }
+ queuedRemoteCandidates = new LinkedList();
- MediaConstraints pcConstraints = signalingParameters.pcConstraints;
- pcConstraints.optional.add(
- new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
peerConnection = factory.createPeerConnection(
signalingParameters.iceServers, pcConstraints, pcObserver);
isInitiator = false;
@@ -390,11 +404,9 @@ public class PeerConnectionClient {
mediaStream.addTrack(createVideoTrack(videoCapturer));
}
- if (signalingParameters.audioConstraints != null) {
- mediaStream.addTrack(factory.createAudioTrack(
- AUDIO_TRACK_ID,
- factory.createAudioSource(signalingParameters.audioConstraints)));
- }
+ mediaStream.addTrack(factory.createAudioTrack(
+ AUDIO_TRACK_ID,
+ factory.createAudioSource(audioConstraints)));
peerConnection.addStream(mediaStream);
Log.d(TAG, "Peer connection created.");
@@ -611,11 +623,9 @@ public class PeerConnectionClient {
}
private VideoTrack createVideoTrack(VideoCapturerAndroid capturer) {
- videoSource = factory.createVideoSource(
- capturer, signalingParameters.videoConstraints);
+ videoSource = factory.createVideoSource(capturer, videoConstraints);
- localVideoTrack =
- factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
+ localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
localVideoTrack.setEnabled(renderVideo);
localVideoTrack.addRenderer(new VideoRenderer(localRender));
return localVideoTrack;
@@ -700,8 +710,8 @@ public class PeerConnectionClient {
if (isAudio) {
mediaDescription = "m=audio ";
}
- for (int i = 0; (i < lines.length) &&
- (mLineIndex == -1 || codecRtpMap == null); i++) {
+ for (int i = 0; (i < lines.length)
+ && (mLineIndex == -1 || codecRtpMap == null); i++) {
if (lines[i].startsWith(mediaDescription)) {
mLineIndex = i;
continue;
@@ -720,23 +730,27 @@ public class PeerConnectionClient {
Log.w(TAG, "No rtpmap for " + codec);
return sdpDescription;
}
- Log.d(TAG, "Found " + codec + " rtpmap " + codecRtpMap + ", prefer at " +
- lines[mLineIndex]);
+ Log.d(TAG, "Found " + codec + " rtpmap " + codecRtpMap + ", prefer at "
+ + lines[mLineIndex]);
String[] origMLineParts = lines[mLineIndex].split(" ");
- StringBuilder newMLine = new StringBuilder();
- int origPartIndex = 0;
- // Format is: m= ...
- newMLine.append(origMLineParts[origPartIndex++]).append(" ");
- newMLine.append(origMLineParts[origPartIndex++]).append(" ");
- newMLine.append(origMLineParts[origPartIndex++]).append(" ");
- newMLine.append(codecRtpMap);
- for (; origPartIndex < origMLineParts.length; origPartIndex++) {
- if (!origMLineParts[origPartIndex].equals(codecRtpMap)) {
- newMLine.append(" ").append(origMLineParts[origPartIndex]);
+ if (origMLineParts.length > 3) {
+ StringBuilder newMLine = new StringBuilder();
+ int origPartIndex = 0;
+ // Format is: m= ...
+ newMLine.append(origMLineParts[origPartIndex++]).append(" ");
+ newMLine.append(origMLineParts[origPartIndex++]).append(" ");
+ newMLine.append(origMLineParts[origPartIndex++]).append(" ");
+ newMLine.append(codecRtpMap);
+ for (; origPartIndex < origMLineParts.length; origPartIndex++) {
+ if (!origMLineParts[origPartIndex].equals(codecRtpMap)) {
+ newMLine.append(" ").append(origMLineParts[origPartIndex]);
+ }
}
+ lines[mLineIndex] = newMLine.toString();
+ Log.d(TAG, "Change media description: " + lines[mLineIndex]);
+ } else {
+ Log.e(TAG, "Wrong SDP media description format: " + lines[mLineIndex]);
}
- lines[mLineIndex] = newMLine.toString();
- Log.d(TAG, "Change media description: " + lines[mLineIndex]);
StringBuilder newSdpDescription = new StringBuilder();
for (String line : lines) {
newSdpDescription.append(line).append("\r\n");
diff --git a/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java b/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
index 2bb3cee98f..92bfbd73f3 100644
--- a/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
+++ b/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
@@ -43,8 +43,8 @@ import org.webrtc.SessionDescription;
import java.io.IOException;
import java.io.InputStream;
+import java.net.HttpURLConnection;
import java.net.URL;
-import java.net.URLConnection;
import java.util.LinkedList;
import java.util.Scanner;
@@ -54,10 +54,10 @@ import java.util.Scanner;
*/
public class RoomParametersFetcher {
private static final String TAG = "RoomRTCClient";
+ private static final int TURN_HTTP_TIMEOUT_MS = 5000;
private final RoomParametersFetcherEvents events;
- private final boolean loopback;
- private final String registerUrl;
- private final String registerMessage;
+ private final String roomUrl;
+ private final String roomMessage;
private AsyncHttpURLConnection httpConnection;
/**
@@ -76,18 +76,17 @@ public class RoomParametersFetcher {
public void onSignalingParametersError(final String description);
}
- public RoomParametersFetcher(boolean loopback, String registerUrl,
- String registerMessage, final RoomParametersFetcherEvents events) {
- this.loopback = loopback;
- this.registerUrl = registerUrl;
- this.registerMessage = registerMessage;
+ public RoomParametersFetcher(String roomUrl, String roomMessage,
+ final RoomParametersFetcherEvents events) {
+ this.roomUrl = roomUrl;
+ this.roomMessage = roomMessage;
this.events = events;
}
public void makeRequest() {
- Log.d(TAG, "Connecting to room: " + registerUrl);
+ Log.d(TAG, "Connecting to room: " + roomUrl);
httpConnection = new AsyncHttpURLConnection(
- "POST", registerUrl, registerMessage,
+ "POST", roomUrl, roomMessage,
new AsyncHttpEvents() {
@Override
public void onHttpError(String errorMessage) {
@@ -161,6 +160,7 @@ public class RoomParametersFetcher {
break;
}
}
+ // Request TURN servers.
if (!isTurnPresent) {
LinkedList turnServers =
requestTurnServers(roomJson.getString("turn_url"));
@@ -170,17 +170,13 @@ public class RoomParametersFetcher {
}
}
- MediaConstraints pcConstraints = constraintsFromJSON(
- roomJson.getString("pc_constraints"));
- addDTLSConstraintIfMissing(pcConstraints, loopback);
+ MediaConstraints pcConstraints = constraintsFromJSON(roomJson.getString("pc_constraints"));
Log.d(TAG, "pcConstraints: " + pcConstraints);
MediaConstraints videoConstraints = constraintsFromJSON(
- getAVConstraints("video",
- roomJson.getString("media_constraints")));
+ getAVConstraints("video", roomJson.getString("media_constraints")));
Log.d(TAG, "videoConstraints: " + videoConstraints);
MediaConstraints audioConstraints = constraintsFromJSON(
- getAVConstraints("audio",
- roomJson.getString("media_constraints")));
+ getAVConstraints("audio", roomJson.getString("media_constraints")));
Log.d(TAG, "audioConstraints: " + audioConstraints);
SignalingParameters params = new SignalingParameters(
@@ -197,35 +193,10 @@ public class RoomParametersFetcher {
}
}
- // Mimic Chrome and set DtlsSrtpKeyAgreement to true if not set to false by
- // the web-app.
- private void addDTLSConstraintIfMissing(
- MediaConstraints pcConstraints, boolean loopback) {
- for (MediaConstraints.KeyValuePair pair : pcConstraints.mandatory) {
- if (pair.getKey().equals("DtlsSrtpKeyAgreement")) {
- return;
- }
- }
- for (MediaConstraints.KeyValuePair pair : pcConstraints.optional) {
- if (pair.getKey().equals("DtlsSrtpKeyAgreement")) {
- return;
- }
- }
- // DTLS isn't being specified (e.g. for debug=loopback calls), so enable
- // it for normal calls and disable for loopback calls.
- if (loopback) {
- pcConstraints.optional.add(
- new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "false"));
- } else {
- pcConstraints.optional.add(
- new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
- }
- }
-
// Return the constraints specified for |type| of "audio" or "video" in
// |mediaConstraintsString|.
private String getAVConstraints (
- String type, String mediaConstraintsString) throws JSONException {
+ String type, String mediaConstraintsString) throws JSONException {
JSONObject json = new JSONObject(mediaConstraintsString);
// Tricky handling of values that are allowed to be (boolean or
// MediaTrackConstraints) by the getUserMedia() spec. There are three
@@ -282,10 +253,17 @@ public class RoomParametersFetcher {
LinkedList turnServers =
new LinkedList();
Log.d(TAG, "Request TURN from: " + url);
- URLConnection connection = (new URL(url)).openConnection();
- connection.addRequestProperty("user-agent", "Mozilla/5.0");
- connection.addRequestProperty("origin", "https://apprtc.appspot.com");
- String response = drainStream(connection.getInputStream());
+ HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+ connection.setConnectTimeout(TURN_HTTP_TIMEOUT_MS);
+ connection.setReadTimeout(TURN_HTTP_TIMEOUT_MS);
+ int responseCode = connection.getResponseCode();
+ if (responseCode != 200) {
+ throw new IOException("Non-200 response when requesting TURN server from "
+ + url + " : " + connection.getHeaderField(null));
+ }
+ InputStream responseStream = connection.getInputStream();
+ String response = drainStream(responseStream);
+ connection.disconnect();
Log.d(TAG, "TURN response: " + response);
JSONObject responseJSON = new JSONObject(response);
String username = responseJSON.getString("username");
@@ -317,7 +295,7 @@ public class RoomParametersFetcher {
}
// Return the contents of an InputStream as a String.
- private String drainStream(InputStream in) {
+ private static String drainStream(InputStream in) {
Scanner s = new Scanner(in).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java b/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
index 494b8b6722..c783816e52 100644
--- a/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
@@ -82,14 +82,12 @@ public class WebSocketChannelClient {
* All events are dispatched from a looper executor thread.
*/
public interface WebSocketChannelEvents {
- public void onWebSocketOpen();
public void onWebSocketMessage(final String message);
public void onWebSocketClose();
public void onWebSocketError(final String description);
}
- public WebSocketChannelClient(LooperExecutor executor,
- WebSocketChannelEvents events) {
+ public WebSocketChannelClient(LooperExecutor executor, WebSocketChannelEvents events) {
this.executor = executor;
this.events = events;
roomID = null;
@@ -102,8 +100,7 @@ public class WebSocketChannelClient {
return state;
}
- public void connect(final String wsUrl, final String postUrl,
- final String roomID, final String clientID) {
+ public void connect(final String wsUrl, final String postUrl) {
checkIfCalledOnValidThread();
if (state != WebSocketConnectionState.NEW) {
Log.e(TAG, "WebSocket is already connected.");
@@ -111,8 +108,6 @@ public class WebSocketChannelClient {
}
wsServerUrl = wsUrl;
postServerUrl = postUrl;
- this.roomID = roomID;
- this.clientID = clientID;
closeEvent = false;
Log.d(TAG, "Connecting WebSocket to: " + wsUrl + ". Post URL: " + postUrl);
@@ -127,12 +122,15 @@ public class WebSocketChannelClient {
}
}
- public void register() {
+ public void register(final String roomID, final String clientID) {
checkIfCalledOnValidThread();
+ this.roomID = roomID;
+ this.clientID = clientID;
if (state != WebSocketConnectionState.CONNECTED) {
- Log.w(TAG, "WebSocket register() in state " + state);
+ Log.d(TAG, "WebSocket register() in state " + state);
return;
}
+ Log.d(TAG, "Registering WebSocket for room " + roomID + ". CLientID: " + clientID);
JSONObject json = new JSONObject();
try {
json.put("cmd", "register");
@@ -271,7 +269,10 @@ public class WebSocketChannelClient {
@Override
public void run() {
state = WebSocketConnectionState.CONNECTED;
- events.onWebSocketOpen();
+ // Check if we have pending register request.
+ if (roomID != null && clientID != null) {
+ register(roomID, clientID);
+ }
}
});
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java
index c055435502..060477f135 100644
--- a/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java
@@ -77,6 +77,7 @@ public class WebSocketRTCClient implements AppRTCClient,
this.events = events;
this.executor = executor;
roomState = ConnectionState.NEW;
+ executor.requestStart();
}
// --------------------------------------------------------------------
@@ -86,7 +87,6 @@ public class WebSocketRTCClient implements AppRTCClient,
@Override
public void connectToRoom(RoomConnectionParameters connectionParameters) {
this.connectionParameters = connectionParameters;
- executor.requestStart();
executor.execute(new Runnable() {
@Override
public void run() {
@@ -131,8 +131,7 @@ public class WebSocketRTCClient implements AppRTCClient,
}
};
- new RoomParametersFetcher(connectionParameters.loopback, connectionUrl,
- null, callbacks).makeRequest();
+ new RoomParametersFetcher(connectionUrl, null, callbacks).makeRequest();
}
// Disconnect from room and send bye messages - runs on a local looper thread.
@@ -193,9 +192,9 @@ public class WebSocketRTCClient implements AppRTCClient,
// Fire connection and signaling parameters events.
events.onConnectedToRoom(signalingParameters);
- // Connect to WebSocket server.
- wsClient.connect(signalingParameters.wssUrl, signalingParameters.wssPostUrl,
- connectionParameters.roomId, signalingParameters.clientId);
+ // Connect and register WebSocket client.
+ wsClient.connect(signalingParameters.wssUrl, signalingParameters.wssPostUrl);
+ wsClient.register(connectionParameters.roomId, signalingParameters.clientId);
}
// Send local offer SDP to the other participant.
@@ -274,12 +273,6 @@ public class WebSocketRTCClient implements AppRTCClient,
// WebSocketChannelEvents interface implementation.
// All events are called by WebSocketChannelClient on a local looper thread
// (passed to WebSocket client constructor).
- @Override
- public void onWebSocketOpen() {
- Log.d(TAG, "Websocket connection completed. Registering...");
- wsClient.register();
- }
-
@Override
public void onWebSocketMessage(final String msg) {
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
diff --git a/talk/examples/android/src/org/appspot/apprtc/util/AsyncHttpURLConnection.java b/talk/examples/android/src/org/appspot/apprtc/util/AsyncHttpURLConnection.java
index 888434f881..d1fddb4f5b 100644
--- a/talk/examples/android/src/org/appspot/apprtc/util/AsyncHttpURLConnection.java
+++ b/talk/examples/android/src/org/appspot/apprtc/util/AsyncHttpURLConnection.java
@@ -40,6 +40,7 @@ import java.util.Scanner;
*/
public class AsyncHttpURLConnection {
private static final int HTTP_TIMEOUT_MS = 8000;
+ private static final String HTTP_ORIGIN = "https://apprtc.appspot.com";
private final String method;
private final String url;
private final String message;
@@ -83,6 +84,8 @@ public class AsyncHttpURLConnection {
connection.setDoInput(true);
connection.setConnectTimeout(HTTP_TIMEOUT_MS);
connection.setReadTimeout(HTTP_TIMEOUT_MS);
+ // TODO(glaznev) - query request origin from pref_room_server_url_key preferences.
+ connection.addRequestProperty("origin", HTTP_ORIGIN);
boolean doOutput = false;
if (method.equals("POST")) {
doOutput = true;
@@ -102,6 +105,7 @@ public class AsyncHttpURLConnection {
// Get response.
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
+ connection.disconnect();
events.onHttpError("Non-200 response to " + method + " to URL: "
+ url + " : " + connection.getHeaderField(null));
return;
@@ -109,6 +113,7 @@ public class AsyncHttpURLConnection {
InputStream responseStream = connection.getInputStream();
String response = drainStream(responseStream);
responseStream.close();
+ connection.disconnect();
events.onHttpComplete(response);
} catch (SocketTimeoutException e) {
events.onHttpError("HTTP " + method + " to " + url + " timeout");
diff --git a/talk/examples/android/src/org/appspot/apprtc/util/LooperExecutor.java b/talk/examples/android/src/org/appspot/apprtc/util/LooperExecutor.java
index 0aa4c39bf9..a57030351f 100644
--- a/talk/examples/android/src/org/appspot/apprtc/util/LooperExecutor.java
+++ b/talk/examples/android/src/org/appspot/apprtc/util/LooperExecutor.java
@@ -85,7 +85,7 @@ public class LooperExecutor extends Thread implements Executor {
handler.post(new Runnable() {
@Override
public void run() {
- Looper.myLooper().quitSafely();
+ Looper.myLooper().quit();
Log.d(TAG, "Looper thread finished.");
}
});