Port some fixes in AppRTCDemo.
- Make PeerConnectionClient a singleton.
- Fix crash in CpuMonitor.
- Remove reading constraints from room response.
- Catch and report camera errors.
R=wzh@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/43059004
Cr-Commit-Position: refs/heads/master@{#8930}
diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
index 20f8a4a..30451bf 100644
--- a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
@@ -28,7 +28,6 @@
package org.appspot.apprtc;
import org.webrtc.IceCandidate;
-import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;
@@ -87,9 +86,6 @@
public static class SignalingParameters {
public final List<PeerConnection.IceServer> iceServers;
public final boolean initiator;
- public final MediaConstraints pcConstraints;
- public final MediaConstraints videoConstraints;
- public final MediaConstraints audioConstraints;
public final String clientId;
public final String wssUrl;
public final String wssPostUrl;
@@ -98,15 +94,11 @@
public SignalingParameters(
List<PeerConnection.IceServer> iceServers,
- boolean initiator, MediaConstraints pcConstraints,
- MediaConstraints videoConstraints, MediaConstraints audioConstraints,
- String clientId, String wssUrl, String wssPostUrl,
+ boolean initiator, String clientId,
+ String wssUrl, String wssPostUrl,
SessionDescription offerSdp, List<IceCandidate> iceCandidates) {
this.iceServers = iceServers;
this.initiator = initiator;
- this.pcConstraints = pcConstraints;
- this.videoConstraints = videoConstraints;
- this.audioConstraints = audioConstraints;
this.clientId = clientId;
this.wssUrl = wssUrl;
this.wssPostUrl = wssPostUrl;
diff --git a/talk/examples/android/src/org/appspot/apprtc/CallActivity.java b/talk/examples/android/src/org/appspot/apprtc/CallActivity.java
index a4255b5..e4382d1 100644
--- a/talk/examples/android/src/org/appspot/apprtc/CallActivity.java
+++ b/talk/examples/android/src/org/appspot/apprtc/CallActivity.java
@@ -383,7 +383,7 @@
if (peerConnectionClient == null) {
final long delta = System.currentTimeMillis() - callStartedTimeMs;
Log.d(TAG, "Creating peer connection factory, delay=" + delta + "ms");
- peerConnectionClient = new PeerConnectionClient();
+ peerConnectionClient = PeerConnectionClient.getInstance();
peerConnectionClient.createPeerConnectionFactory(CallActivity.this,
VideoRendererGui.getEGLContext(), peerConnectionParameters,
CallActivity.this);
diff --git a/talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java b/talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java
index 327d47e..89d21d4 100644
--- a/talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java
+++ b/talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java
@@ -113,7 +113,8 @@
Scanner scanner = new Scanner(rdr).useDelimiter("[-\n]");
scanner.nextInt(); // Skip leading number 0.
cpusPresent = 1 + scanner.nextInt();
- } catch (InputMismatchException e) {
+ scanner.close();
+ } catch (Exception e) {
Log.e(TAG, "Cannot do CPU stats due to /sys/devices/system/cpu/present parsing problem");
} finally {
fin.close();
@@ -264,7 +265,8 @@
BufferedReader rdr = new BufferedReader(fin);
Scanner scannerC = new Scanner(rdr);
number = scannerC.nextLong();
- } catch (InputMismatchException e) {
+ scannerC.close();
+ } catch (Exception e) {
// CPU presumably got offline just after we opened file.
} finally {
fin.close();
@@ -295,7 +297,8 @@
long sys = scanner.nextLong();
runTime = user + nice + sys;
idleTime = scanner.nextLong();
- } catch (InputMismatchException e) {
+ scanner.close();
+ } catch (Exception e) {
Log.e(TAG, "Problems parsing /proc/stat");
return null;
} finally {
diff --git a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
index 67290be..c5beec4 100644
--- a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
@@ -35,6 +35,7 @@
import org.appspot.apprtc.util.LooperExecutor;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
+import org.webrtc.Logging;
import org.webrtc.MediaCodecVideoEncoder;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaConstraints.KeyValuePair;
@@ -51,6 +52,7 @@
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
+import java.util.EnumSet;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
@@ -62,6 +64,7 @@
*
* <p>All public methods are routed to local looper thread.
* All PeerConnectionEvents callbacks are invoked from the same looper thread.
+ * This class is a singleton.
*/
public class PeerConnectionClient {
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
@@ -89,18 +92,21 @@
private static final int MAX_VIDEO_HEIGHT = 1280;
private static final int MAX_VIDEO_FPS = 30;
- private final LooperExecutor executor;
- private PeerConnectionFactory factory = null;
- private PeerConnection peerConnection = null;
- private VideoSource videoSource;
- private boolean videoCallEnabled = true;
- private boolean preferIsac = false;
- private boolean preferH264 = false;
- private boolean videoSourceStopped = false;
- private boolean isError = false;
- private final Timer statsTimer = new Timer();
+ private static final PeerConnectionClient instance = new PeerConnectionClient();
private final PCObserver pcObserver = new PCObserver();
private final SDPObserver sdpObserver = new SDPObserver();
+ private final LooperExecutor executor;
+
+ private PeerConnectionFactory factory;
+ private PeerConnection peerConnection;
+ PeerConnectionFactory.Options options = null;
+ private VideoSource videoSource;
+ private boolean videoCallEnabled;
+ private boolean preferIsac;
+ private boolean preferH264;
+ private boolean videoSourceStopped;
+ private boolean isError;
+ private Timer statsTimer;
private VideoRenderer.Callbacks localRender;
private VideoRenderer.Callbacks remoteRender;
private SignalingParameters signalingParameters;
@@ -112,17 +118,17 @@
// 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<IceCandidate> queuedRemoteCandidates = null;
+ private LinkedList<IceCandidate> queuedRemoteCandidates;
private PeerConnectionEvents events;
private boolean isInitiator;
- private SessionDescription localSdp = null; // either offer or answer SDP
- private MediaStream mediaStream = null;
+ private SessionDescription localSdp; // either offer or answer SDP
+ private MediaStream mediaStream;
private int numberOfCameras;
- private VideoCapturerAndroid videoCapturer = null;
+ private VideoCapturerAndroid videoCapturer;
// enableVideo is set to true if video should be rendered and sent.
- private boolean renderVideo = true;
- private VideoTrack localVideoTrack = null;
- private VideoTrack remoteVideoTrack = null;
+ private boolean renderVideo;
+ private VideoTrack localVideoTrack;
+ private VideoTrack remoteVideoTrack;
/**
* Peer connection parameters.
@@ -202,8 +208,20 @@
public void onPeerConnectionError(final String description);
}
- public PeerConnectionClient() {
+ private PeerConnectionClient() {
executor = new LooperExecutor();
+ // Looper thread is started once in private ctor and is used for all
+ // peer connection API calls to ensure new peer connection factory is
+ // created on the same thread as previously destroyed factory.
+ executor.requestStart();
+ }
+
+ public static PeerConnectionClient getInstance() {
+ return instance;
+ }
+
+ public void setPeerConnectionFactoryOptions(PeerConnectionFactory.Options options) {
+ this.options = options;
}
public void createPeerConnectionFactory(
@@ -214,7 +232,22 @@
this.peerConnectionParameters = peerConnectionParameters;
this.events = events;
videoCallEnabled = peerConnectionParameters.videoCallEnabled;
- executor.requestStart();
+ // Reset variables to initial states.
+ factory = null;
+ peerConnection = null;
+ preferIsac = false;
+ preferH264 = false;
+ videoSourceStopped = false;
+ isError = false;
+ queuedRemoteCandidates = null;
+ localSdp = null; // either offer or answer SDP
+ mediaStream = null;
+ videoCapturer = null;
+ renderVideo = true;
+ localVideoTrack = null;
+ remoteVideoTrack = null;
+ statsTimer = new Timer();
+
executor.execute(new Runnable() {
@Override
public void run() {
@@ -250,7 +283,10 @@
closeInternal();
}
});
- executor.requestStop();
+ }
+
+ public boolean isVideoCallEnabled() {
+ return videoCallEnabled;
}
private void createPeerConnectionFactoryInternal(
@@ -284,16 +320,13 @@
events.onPeerConnectionError("Failed to initializeAndroidGlobals");
}
factory = new PeerConnectionFactory();
- configureFactory(factory);
+ if (options != null) {
+ Log.d(TAG, "Factory networkIgnoreMask option: " + options.networkIgnoreMask);
+ factory.setOptions(options);
+ }
Log.d(TAG, "Peer connection factory created.");
}
- /**
- * Hook where tests can provide additional configuration for the factory.
- */
- protected void configureFactory(PeerConnectionFactory factory) {
- }
-
private void createMediaConstraintsInternal() {
// Create peer connection constraints.
pcConstraints = new MediaConstraints();
@@ -384,12 +417,12 @@
signalingParameters.iceServers, pcConstraints, pcObserver);
isInitiator = false;
- // Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging.
+ // Set default WebRTC tracing and INFO libjingle logging.
// NOTE: this _must_ happen while |factory| is alive!
- // Logging.enableTracing(
- // "logcat:",
- // EnumSet.of(Logging.TraceLevel.TRACE_ALL),
- // Logging.Severity.LS_SENSITIVE);
+ Logging.enableTracing(
+ "logcat:",
+ EnumSet.of(Logging.TraceLevel.TRACE_DEFAULT),
+ Logging.Severity.LS_INFO);
mediaStream = factory.createLocalMediaStream("ARDAMS");
if (videoCallEnabled) {
@@ -401,6 +434,10 @@
}
Log.d(TAG, "Opening camera: " + cameraDeviceName);
videoCapturer = VideoCapturerAndroid.create(cameraDeviceName);
+ if (videoCapturer == null) {
+ reportError("Failed to open camera");
+ return;
+ }
mediaStream.addTrack(createVideoTrack(videoCapturer));
}
@@ -419,6 +456,7 @@
peerConnection.dispose();
peerConnection = null;
}
+ Log.d(TAG, "Closing video source.");
if (videoSource != null) {
videoSource.dispose();
videoSource = null;
@@ -428,6 +466,7 @@
factory.dispose();
factory = null;
}
+ options = null;
Log.d(TAG, "Closing peer connection done.");
events.onPeerConnectionClosed();
}
@@ -477,17 +516,21 @@
public void enableStatsEvents(boolean enable, int periodMs) {
if (enable) {
- statsTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- getStats();
- }
- });
- }
- }, 0, periodMs);
+ try {
+ statsTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ getStats();
+ }
+ });
+ }
+ }, 0, periodMs);
+ } catch (Exception e) {
+ Log.e(TAG, "Can not schedule statistics timer", e);
+ }
} else {
statsTimer.cancel();
}
@@ -769,8 +812,10 @@
}
private void switchCameraInternal() {
- if (!videoCallEnabled || numberOfCameras < 2) {
- return; // No video is sent or only one camera is available.
+ if (!videoCallEnabled || numberOfCameras < 2 || isError || videoCapturer == null) {
+ Log.e(TAG, "Failed to switch camera. Video: " + videoCallEnabled + ". Error : "
+ + isError + ". Number of cameras: " + numberOfCameras);
+ return; // No video is sent or only one camera is available or error happened.
}
Log.d(TAG, "Switch camera");
videoCapturer.switchCamera();
@@ -780,9 +825,7 @@
executor.execute(new Runnable() {
@Override
public void run() {
- if (peerConnection != null && !isError) {
- switchCameraInternal();
- }
+ switchCameraInternal();
}
});
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java b/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
index 92bfbd7..b14d2d4 100644
--- a/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
+++ b/talk/examples/android/src/org/appspot/apprtc/RoomParametersFetcher.java
@@ -37,7 +37,6 @@
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
-import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;
@@ -170,18 +169,8 @@
}
}
- MediaConstraints pcConstraints = constraintsFromJSON(roomJson.getString("pc_constraints"));
- Log.d(TAG, "pcConstraints: " + pcConstraints);
- MediaConstraints videoConstraints = constraintsFromJSON(
- getAVConstraints("video", roomJson.getString("media_constraints")));
- Log.d(TAG, "videoConstraints: " + videoConstraints);
- MediaConstraints audioConstraints = constraintsFromJSON(
- getAVConstraints("audio", roomJson.getString("media_constraints")));
- Log.d(TAG, "audioConstraints: " + audioConstraints);
-
SignalingParameters params = new SignalingParameters(
iceServers, initiator,
- pcConstraints, videoConstraints, audioConstraints,
clientId, wssUrl, wssPostUrl,
offerSdp, iceCandidates);
events.onSignalingParametersReady(params);
@@ -193,59 +182,6 @@
}
}
- // Return the constraints specified for |type| of "audio" or "video" in
- // |mediaConstraintsString|.
- private String getAVConstraints (
- 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
- // cases below.
- if (!json.has(type) || !json.optBoolean(type, true)) {
- // Case 1: "audio"/"video" is not present, or is an explicit "false"
- // boolean.
- return null;
- }
- if (json.optBoolean(type, false)) {
- // Case 2: "audio"/"video" is an explicit "true" boolean.
- return "{\"mandatory\": {}, \"optional\": []}";
- }
- // Case 3: "audio"/"video" is an object.
- return json.getJSONObject(type).toString();
- }
-
- private MediaConstraints constraintsFromJSON(String jsonString)
- throws JSONException {
- if (jsonString == null) {
- return null;
- }
- MediaConstraints constraints = new MediaConstraints();
- JSONObject json = new JSONObject(jsonString);
- JSONObject mandatoryJSON = json.optJSONObject("mandatory");
- if (mandatoryJSON != null) {
- JSONArray mandatoryKeys = mandatoryJSON.names();
- if (mandatoryKeys != null) {
- for (int i = 0; i < mandatoryKeys.length(); ++i) {
- String key = mandatoryKeys.getString(i);
- String value = mandatoryJSON.getString(key);
- constraints.mandatory.add(
- new MediaConstraints.KeyValuePair(key, value));
- }
- }
- }
- JSONArray optionalJSON = json.optJSONArray("optional");
- if (optionalJSON != null) {
- for (int i = 0; i < optionalJSON.length(); ++i) {
- JSONObject keyValueDict = optionalJSON.getJSONObject(i);
- String key = keyValueDict.names().getString(0);
- String value = keyValueDict.getString(key);
- constraints.optional.add(
- new MediaConstraints.KeyValuePair(key, value));
- }
- }
- return constraints;
- }
-
// Requests & returns a TURN ICE Server based on a request URL. Must be run
// off the main thread!
private LinkedList<PeerConnection.IceServer> requestTurnServers(String url)
diff --git a/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java b/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
index c783816..7ba2575 100644
--- a/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java
@@ -127,7 +127,7 @@
this.roomID = roomID;
this.clientID = clientID;
if (state != WebSocketConnectionState.CONNECTED) {
- Log.d(TAG, "WebSocket register() in state " + state);
+ Log.w(TAG, "WebSocket register() in state " + state);
return;
}
Log.d(TAG, "Registering WebSocket for room " + roomID + ". CLientID: " + clientID);
@@ -190,17 +190,16 @@
checkIfCalledOnValidThread();
Log.d(TAG, "Disonnect WebSocket. State: " + state);
if (state == WebSocketConnectionState.REGISTERED) {
+ // Send "bye" to WebSocket server.
send("{\"type\": \"bye\"}");
state = WebSocketConnectionState.CONNECTED;
+ // Send http DELETE to http WebSocket server.
+ sendWSSMessage("DELETE", "");
}
// Close WebSocket in CONNECTED or ERROR states only.
if (state == WebSocketConnectionState.CONNECTED
|| state == WebSocketConnectionState.ERROR) {
ws.disconnect();
-
- // Send DELETE to http WebSocket server.
- sendWSSMessage("DELETE", "");
-
state = WebSocketConnectionState.CLOSED;
// Wait for websocket close event to prevent websocket library from
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 d1fddb4..9cb0196 100644
--- a/talk/examples/android/src/org/appspot/apprtc/util/AsyncHttpURLConnection.java
+++ b/talk/examples/android/src/org/appspot/apprtc/util/AsyncHttpURLConnection.java
@@ -45,6 +45,7 @@
private final String url;
private final String message;
private final AsyncHttpEvents events;
+ private String contentType;
/**
* Http requests callbacks.
@@ -62,6 +63,10 @@
this.events = events;
}
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
public void send() {
Runnable runHttp = new Runnable() {
public void run() {
@@ -92,8 +97,11 @@
connection.setDoOutput(true);
connection.setFixedLengthStreamingMode(postData.length);
}
- connection.setRequestProperty(
- "content-type", "text/plain; charset=utf-8");
+ if (contentType == null) {
+ connection.setRequestProperty("Content-Type", "text/plain; charset=utf-8");
+ } else {
+ connection.setRequestProperty("Content-Type", contentType);
+ }
// Send POST request.
if (doOutput && postData.length > 0) {
@@ -105,9 +113,9 @@
// Get response.
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
- connection.disconnect();
events.onHttpError("Non-200 response to " + method + " to URL: "
+ url + " : " + connection.getHeaderField(null));
+ connection.disconnect();
return;
}
InputStream responseStream = connection.getInputStream();
diff --git a/talk/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java b/talk/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java
index 7354fb8..4806491 100644
--- a/talk/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java
+++ b/talk/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java
@@ -61,7 +61,6 @@
private static final String VIDEO_CODEC_VP9 = "VP9";
private static final String VIDEO_CODEC_H264 = "H264";
private static final int AUDIO_RUN_TIMEOUT = 1000;
- private static final String DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT = "DtlsSrtpKeyAgreement";
private static final String LOCAL_RENDERER_NAME = "Local renderer";
private static final String REMOTE_RENDERER_NAME = "Remote renderer";
@@ -130,17 +129,6 @@
}
}
- // Test instance of the PeerConnectionClient class that overrides the options
- // for the factory so we can run the test without an Internet connection.
- class TestPeerConnectionClient extends PeerConnectionClient {
- protected void configureFactory(PeerConnectionFactory factory) {
- PeerConnectionFactory.Options options =
- new PeerConnectionFactory.Options();
- options.networkIgnoreMask = 0;
- factory.setOptions(options);
- }
- }
-
// Peer connection events implementation.
@Override
public void onLocalDescription(SessionDescription sdp) {
@@ -251,33 +239,25 @@
}
}
- private SignalingParameters getTestSignalingParameters() {
- List<PeerConnection.IceServer> iceServers =
- new LinkedList<PeerConnection.IceServer>();
- MediaConstraints pcConstraints = new MediaConstraints();
- pcConstraints.optional.add(
- new MediaConstraints.KeyValuePair(DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT, "false"));
- MediaConstraints videoConstraints = new MediaConstraints();
- MediaConstraints audioConstraints = new MediaConstraints();
- SignalingParameters signalingParameters = new SignalingParameters(
- iceServers, true,
- pcConstraints, videoConstraints, audioConstraints,
- null, null, null,
- null, null);
- return signalingParameters;
- }
-
PeerConnectionClient createPeerConnectionClient(
MockRenderer localRenderer, MockRenderer remoteRenderer,
boolean enableVideo, String videoCodec) {
- SignalingParameters signalingParameters = getTestSignalingParameters();
+ List<PeerConnection.IceServer> iceServers =
+ new LinkedList<PeerConnection.IceServer>();
+ SignalingParameters signalingParameters = new SignalingParameters(
+ iceServers, true, // iceServers, initiator.
+ null, null, null, // clientId, wssUrl, wssPostUrl.
+ null, null); // offerSdp, iceCandidates.
PeerConnectionParameters peerConnectionParameters =
new PeerConnectionParameters(
enableVideo, true, // videoCallEnabled, loopback.
0, 0, 0, 0, videoCodec, true, // video codec parameters.
0, "OPUS", true); // audio codec parameters.
- PeerConnectionClient client = new TestPeerConnectionClient();
+ PeerConnectionClient client = PeerConnectionClient.getInstance();
+ PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
+ options.networkIgnoreMask = 0;
+ client.setPeerConnectionFactoryOptions(options);
client.createPeerConnectionFactory(
getInstrumentation().getContext(), null,
peerConnectionParameters, this);