Fix AppRTCDemo closing error for KK and JB Android devices.

- Do not allow connection output when sending http delete
request to ws server - this causes IOException for KK and JB devices.
- Avoid creating dialog box with error message when activity
has been already closed / paused -
this causes resource leak error message for KK devices.
- Plus some code clean up to support async http messages in
websocket channel wrapper and use Handler for running
peerconnection client funcitons on UI thread.

R=jiayl@webrtc.org, tkchin@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7836 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
glaznev@webrtc.org 2014-12-09 01:29:17 +00:00
parent 86b6d65ef1
commit eef85387ec
3 changed files with 62 additions and 97 deletions

View File

@ -90,6 +90,7 @@ public class AppRTCDemoActivity extends Activity
private ImageButton videoScalingButton; private ImageButton videoScalingButton;
private String roomName; private String roomName;
private boolean commandLineRun; private boolean commandLineRun;
private boolean activityRunning;
private int runTimeMs; private int runTimeMs;
private int startBitrate; private int startBitrate;
private boolean hwCodec; private boolean hwCodec;
@ -231,8 +232,8 @@ public class AppRTCDemoActivity extends Activity
} else { } else {
roomNameView.setText(roomName); roomNameView.setText(roomName);
} }
// For command line execution run connection for <runTimeMs> and exit.
if (commandLineRun && runTimeMs > 0) { if (commandLineRun && runTimeMs > 0) {
// For command line execution run connection for <runTimeMs> and exit.
videoView.postDelayed(new Runnable() { videoView.postDelayed(new Runnable() {
public void run() { public void run() {
disconnect(); disconnect();
@ -266,6 +267,7 @@ public class AppRTCDemoActivity extends Activity
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
videoView.onPause(); videoView.onPause();
activityRunning = false;
if (pc != null) { if (pc != null) {
pc.stopVideoSource(); pc.stopVideoSource();
} }
@ -275,6 +277,7 @@ public class AppRTCDemoActivity extends Activity
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
videoView.onResume(); videoView.onResume();
activityRunning = true;
if (pc != null) { if (pc != null) {
pc.startVideoSource(); pc.startVideoSource();
} }
@ -284,6 +287,7 @@ public class AppRTCDemoActivity extends Activity
protected void onDestroy() { protected void onDestroy() {
disconnect(); disconnect();
super.onDestroy(); super.onDestroy();
activityRunning = false;
} }
private void updateVideoView() { private void updateVideoView() {
@ -319,7 +323,7 @@ public class AppRTCDemoActivity extends Activity
} }
private void disconnectWithErrorMessage(final String errorMessage) { private void disconnectWithErrorMessage(final String errorMessage) {
if (commandLineRun) { if (commandLineRun || !activityRunning) {
Log.e(TAG, "Critical error: " + errorMessage); Log.e(TAG, "Critical error: " + errorMessage);
disconnect(); disconnect();
} else { } else {
@ -453,7 +457,7 @@ public class AppRTCDemoActivity extends Activity
if (audioManager != null) { if (audioManager != null) {
// Store existing audio settings and change audio mode to // Store existing audio settings and change audio mode to
// MODE_IN_COMMUNICATION for best possible VoIP performance. // MODE_IN_COMMUNICATION for best possible VoIP performance.
logAndToast("Initializing the audio manager..."); Log.d(TAG, "Initializing the audio manager...");
audioManager.init(); audioManager.init();
} }
signalingParameters = params; signalingParameters = params;
@ -461,7 +465,7 @@ public class AppRTCDemoActivity extends Activity
this, true, true, hwCodec, VideoRendererGui.getEGLContext()), this, true, true, hwCodec, VideoRendererGui.getEGLContext()),
"Failed to initializeAndroidGlobals"); "Failed to initializeAndroidGlobals");
logAndToast("Creating peer connection..."); logAndToast("Creating peer connection...");
pc = new PeerConnectionClient( this, localRender, remoteRender, pc = new PeerConnectionClient(localRender, remoteRender,
signalingParameters, this, startBitrate); signalingParameters, this, startBitrate);
if (pc.isHDVideo()) { if (pc.isHDVideo()) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

View File

@ -27,7 +27,8 @@
package org.appspot.apprtc; package org.appspot.apprtc;
import android.app.Activity; import android.os.Handler;
import android.os.Looper;
import android.util.Log; import android.util.Log;
import org.appspot.apprtc.AppRTCClient.SignalingParameters; import org.appspot.apprtc.AppRTCClient.SignalingParameters;
@ -57,7 +58,7 @@ public class PeerConnectionClient {
public static final String VIDEO_TRACK_ID = "ARDAMSv0"; public static final String VIDEO_TRACK_ID = "ARDAMSv0";
public static final String AUDIO_TRACK_ID = "ARDAMSa0"; public static final String AUDIO_TRACK_ID = "ARDAMSa0";
private final Activity activity; private final Handler uiHandler;
private PeerConnectionFactory factory; private PeerConnectionFactory factory;
private PeerConnection pc; private PeerConnection pc;
private VideoSource videoSource; private VideoSource videoSource;
@ -80,17 +81,16 @@ public class PeerConnectionClient {
private MediaStream mediaStream = null; private MediaStream mediaStream = null;
public PeerConnectionClient( public PeerConnectionClient(
Activity activity,
VideoRenderer.Callbacks localRender, VideoRenderer.Callbacks localRender,
VideoRenderer.Callbacks remoteRender, VideoRenderer.Callbacks remoteRender,
SignalingParameters signalingParameters, SignalingParameters signalingParameters,
PeerConnectionEvents events, PeerConnectionEvents events,
int startBitrate) { int startBitrate) {
this.activity = activity;
this.localRender = localRender; this.localRender = localRender;
this.remoteRender = remoteRender; this.remoteRender = remoteRender;
this.events = events; this.events = events;
this.startBitrate = startBitrate; this.startBitrate = startBitrate;
uiHandler = new Handler(Looper.getMainLooper());
isInitiator = signalingParameters.initiator; isInitiator = signalingParameters.initiator;
queuedRemoteCandidates = new LinkedList<IceCandidate>(); queuedRemoteCandidates = new LinkedList<IceCandidate>();
@ -162,7 +162,7 @@ public class PeerConnectionClient {
} }
public void createOffer() { public void createOffer() {
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
if (pc != null) { if (pc != null) {
isInitiator = true; isInitiator = true;
@ -173,7 +173,7 @@ public class PeerConnectionClient {
} }
public void createAnswer() { public void createAnswer() {
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
if (pc != null) { if (pc != null) {
isInitiator = false; isInitiator = false;
@ -184,7 +184,7 @@ public class PeerConnectionClient {
} }
public void addRemoteIceCandidate(final IceCandidate candidate) { public void addRemoteIceCandidate(final IceCandidate candidate) {
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
if (pc != null) { if (pc != null) {
if (queuedRemoteCandidates != null) { if (queuedRemoteCandidates != null) {
@ -198,7 +198,7 @@ public class PeerConnectionClient {
} }
public void setRemoteDescription(final SessionDescription sdp) { public void setRemoteDescription(final SessionDescription sdp) {
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
if (pc != null) { if (pc != null) {
String sdpDescription = preferISAC(sdp.description); String sdpDescription = preferISAC(sdp.description);
@ -231,7 +231,7 @@ public class PeerConnectionClient {
} }
public void close() { public void close() {
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
Log.d(TAG, "Closing peer connection."); Log.d(TAG, "Closing peer connection.");
if (pc != null) { if (pc != null) {
@ -284,7 +284,7 @@ public class PeerConnectionClient {
private void reportError(final String errorMessage) { private void reportError(final String errorMessage) {
Log.e(TAG, "Peerconnection error: " + errorMessage); Log.e(TAG, "Peerconnection error: " + errorMessage);
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
events.onPeerConnectionError(errorMessage); events.onPeerConnectionError(errorMessage);
} }
@ -325,8 +325,7 @@ public class PeerConnectionClient {
videoSource.dispose(); videoSource.dispose();
} }
videoSource = factory.createVideoSource( videoSource = factory.createVideoSource(capturer, videoConstraints);
capturer, videoConstraints);
String trackExtension = frontFacing ? "frontFacing" : "backFacing"; String trackExtension = frontFacing ? "frontFacing" : "backFacing";
VideoTrack videoTrack = VideoTrack videoTrack =
factory.createVideoTrack(VIDEO_TRACK_ID + trackExtension, videoSource); factory.createVideoTrack(VIDEO_TRACK_ID + trackExtension, videoSource);
@ -480,7 +479,7 @@ public class PeerConnectionClient {
private class PCObserver implements PeerConnection.Observer { private class PCObserver implements PeerConnection.Observer {
@Override @Override
public void onIceCandidate(final IceCandidate candidate){ public void onIceCandidate(final IceCandidate candidate){
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
events.onIceCandidate(candidate); events.onIceCandidate(candidate);
} }
@ -498,13 +497,13 @@ public class PeerConnectionClient {
PeerConnection.IceConnectionState newState) { PeerConnection.IceConnectionState newState) {
Log.d(TAG, "IceConnectionState: " + newState); Log.d(TAG, "IceConnectionState: " + newState);
if (newState == IceConnectionState.CONNECTED) { if (newState == IceConnectionState.CONNECTED) {
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
events.onIceConnected(); events.onIceConnected();
} }
}); });
} else if (newState == IceConnectionState.DISCONNECTED) { } else if (newState == IceConnectionState.DISCONNECTED) {
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
events.onIceDisconnected(); events.onIceDisconnected();
} }
@ -522,7 +521,7 @@ public class PeerConnectionClient {
@Override @Override
public void onAddStream(final MediaStream stream){ public void onAddStream(final MediaStream stream){
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
abortUnless(stream.audioTracks.size() <= 1 && abortUnless(stream.audioTracks.size() <= 1 &&
stream.videoTracks.size() <= 1, stream.videoTracks.size() <= 1,
@ -537,7 +536,7 @@ public class PeerConnectionClient {
@Override @Override
public void onRemoveStream(final MediaStream stream){ public void onRemoveStream(final MediaStream stream){
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
stream.videoTracks.get(0).dispose(); stream.videoTracks.get(0).dispose();
} }
@ -566,7 +565,7 @@ public class PeerConnectionClient {
final SessionDescription sdp = new SessionDescription( final SessionDescription sdp = new SessionDescription(
origSdp.type, preferISAC(origSdp.description)); origSdp.type, preferISAC(origSdp.description));
localSdp = sdp; localSdp = sdp;
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
if (pc != null) { if (pc != null) {
Log.d(TAG, "Set local SDP from " + sdp.type); Log.d(TAG, "Set local SDP from " + sdp.type);
@ -578,7 +577,7 @@ public class PeerConnectionClient {
@Override @Override
public void onSetSuccess() { public void onSetSuccess() {
activity.runOnUiThread(new Runnable() { uiHandler.post(new Runnable() {
public void run() { public void run() {
if (pc == null) { if (pc == null) {
return; return;

View File

@ -36,12 +36,10 @@ import de.tavendo.autobahn.WebSocketException;
import de.tavendo.autobahn.WebSocket.WebSocketConnectionObserver; import de.tavendo.autobahn.WebSocket.WebSocketConnectionObserver;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder;
import java.util.LinkedList; import java.util.LinkedList;
import org.json.JSONException; import org.json.JSONException;
@ -64,10 +62,6 @@ public class WebSocketChannelClient {
private String roomID; private String roomID;
private String clientID; private String clientID;
private WebSocketConnectionState state; private WebSocketConnectionState state;
// Http post/delete message queue. Messages are added from UI thread in
// post() and disconnect() calls. Messages are consumed by AsyncTask's
// background thread.
private LinkedList<WsHttpMessage> wsHttpQueue;
// WebSocket send queue. Messages are added to the queue when WebSocket // WebSocket send queue. Messages are added to the queue when WebSocket
// client is not registered and are consumed in register() call. // client is not registered and are consumed in register() call.
private LinkedList<String> wsSendQueue; private LinkedList<String> wsSendQueue;
@ -92,7 +86,6 @@ public class WebSocketChannelClient {
uiHandler = new Handler(Looper.getMainLooper()); uiHandler = new Handler(Looper.getMainLooper());
roomID = null; roomID = null;
clientID = null; clientID = null;
wsHttpQueue = new LinkedList<WsHttpMessage>();
wsSendQueue = new LinkedList<String>(); wsSendQueue = new LinkedList<String>();
state = WebSocketConnectionState.NEW; state = WebSocketConnectionState.NEW;
} }
@ -192,10 +185,7 @@ public class WebSocketChannelClient {
// send through websocket before SDP answer sent through http post will be // send through websocket before SDP answer sent through http post will be
// resolved. // resolved.
public void post(String message) { public void post(String message) {
synchronized (wsHttpQueue) { sendWSSMessage("POST", message);
wsHttpQueue.add(new WsHttpMessage("POST", message));
}
requestHttpQueueDrainInBackground();
} }
public void disconnect() { public void disconnect() {
@ -210,11 +200,7 @@ public class WebSocketChannelClient {
ws.disconnect(); ws.disconnect();
// Send DELETE to http WebSocket server. // Send DELETE to http WebSocket server.
synchronized (wsHttpQueue) { sendWSSMessage("DELETE", "");
wsHttpQueue.clear();
wsHttpQueue.add(new WsHttpMessage("DELETE", ""));
}
requestHttpQueueDrainInBackground();
state = WebSocketConnectionState.CLOSED; state = WebSocketConnectionState.CLOSED;
} }
@ -241,67 +227,43 @@ public class WebSocketChannelClient {
public final String message; public final String message;
} }
// TODO(glaznev): This is not good implementation due to discrepancy // Asynchronously send POST/DELETE to WebSocket server.
// between JS encodeURIComponent() and Java URLEncoder.encode(). private void sendWSSMessage(String method, String message) {
// Remove this once WebSocket server will switch to a different encoding. final WsHttpMessage wsHttpMessage = new WsHttpMessage(method, message);
private String encodeURIComponent(String s) { Runnable runAsync = new Runnable() {
String result = null; public void run() {
try { sendWSSMessageAsync(wsHttpMessage);
result = URLEncoder.encode(s, "UTF-8") }
.replaceAll("\\+", "%20") };
.replaceAll("\\%21", "!") new Thread(runAsync).start();
.replaceAll("\\%27", "'") }
.replaceAll("\\%28", "(")
.replaceAll("\\%29", ")") private void sendWSSMessageAsync(WsHttpMessage wsHttpMessage) {
.replaceAll("\\%7E", "~"); if (roomID == null || clientID == null) {
} catch (UnsupportedEncodingException e) { return;
result = s;
} }
return result; try {
} // Send POST or DELETE request.
String postUrl = postServerUrl + "/" + roomID + "/" + clientID;
// Request an attempt to drain the send queue, on a background thread. Log.d(TAG, "WS " + wsHttpMessage.method + " : " + postUrl + " : " +
private void requestHttpQueueDrainInBackground() { wsHttpMessage.message);
(new AsyncTask<Void, Void, Void>() { HttpURLConnection connection =
public Void doInBackground(Void... unused) { (HttpURLConnection) new URL(postUrl).openConnection();
maybeDrainWsHttpQueue(); connection.setRequestProperty(
return null; "content-type", "text/plain; charset=utf-8");
connection.setRequestMethod(wsHttpMessage.method);
if (wsHttpMessage.method.equals("POST")) {
connection.setDoOutput(true);
String message = wsHttpMessage.message;
connection.getOutputStream().write(message.getBytes("UTF-8"));
} }
}).execute(); int responseCode = connection.getResponseCode();
} if (responseCode != 200) {
reportError("Non-200 response to " + wsHttpMessage.method + " : " +
// Send all queued websocket messages. connection.getHeaderField(null));
private void maybeDrainWsHttpQueue() {
synchronized (wsHttpQueue) {
if (roomID == null || clientID == null) {
return;
} }
try { } catch (IOException e) {
for (WsHttpMessage wsHttpMessage : wsHttpQueue) { reportError("WS POST error: " + e.getMessage());
// Send POST request.
String postUrl = postServerUrl + "/" + roomID + "/" + clientID;
Log.d(TAG, "WS " + wsHttpMessage.method + " : " + postUrl + " : " +
wsHttpMessage.message);
HttpURLConnection connection =
(HttpURLConnection) new URL(postUrl).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty(
"Content-type", "application/x-www-form-urlencoded");
connection.setRequestMethod(wsHttpMessage.method);
if (wsHttpMessage.message.length() > 0) {
String message = "msg=" + encodeURIComponent(wsHttpMessage.message);
connection.getOutputStream().write(message.getBytes("UTF-8"));
}
String replyHeader = connection.getHeaderField(null);
if (!replyHeader.startsWith("HTTP/1.1 200 ")) {
reportError("Non-200 response to " + wsHttpMessage.method + " : " +
connection.getHeaderField(null));
}
}
} catch (IOException e) {
reportError("WS POST error: " + e.getMessage());
}
wsHttpQueue.clear();
} }
} }