diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java index 20f8a4a19..30451bf86 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 interface AppRTCClient { public static class SignalingParameters { public final List 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 interface AppRTCClient { public SignalingParameters( List 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 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 a4255b5f2..e4382d18a 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 @@ public class CallActivity extends Activity 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 327d47e4d..89d21d4e2 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 @@ class CpuMonitor { 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 @@ class CpuMonitor { 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 @@ class CpuMonitor { 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 67290bef5..c5beec42e 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.AppRTCClient.SignalingParameters; 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.VideoRenderer; 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 @@ import java.util.regex.Pattern; * *

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 @@ public class PeerConnectionClient { 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 @@ public class PeerConnectionClient { // 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 LinkedList 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 class PeerConnectionClient { 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 @@ public class PeerConnectionClient { 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 @@ public class PeerConnectionClient { closeInternal(); } }); - executor.requestStop(); + } + + public boolean isVideoCallEnabled() { + return videoCallEnabled; } private void createPeerConnectionFactoryInternal( @@ -284,16 +320,13 @@ public class PeerConnectionClient { 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 @@ public class PeerConnectionClient { 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 @@ public class PeerConnectionClient { } 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 @@ public class PeerConnectionClient { peerConnection.dispose(); peerConnection = null; } + Log.d(TAG, "Closing video source."); if (videoSource != null) { videoSource.dispose(); videoSource = null; @@ -428,6 +466,7 @@ public class PeerConnectionClient { factory.dispose(); factory = null; } + options = null; Log.d(TAG, "Closing peer connection done."); events.onPeerConnectionClosed(); } @@ -477,17 +516,21 @@ public class PeerConnectionClient { 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 @@ public class PeerConnectionClient { } 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 @@ public class PeerConnectionClient { 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 92bfbd73f..b14d2d4f2 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.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; @@ -170,18 +169,8 @@ public class RoomParametersFetcher { } } - 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 @@ public class RoomParametersFetcher { } } - // 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 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 c783816e5..7ba257586 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 @@ public class WebSocketChannelClient { 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 @@ public class WebSocketChannelClient { 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 d1fddb4f5..9cb0196bb 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 @@ public class AsyncHttpURLConnection { private final String url; private final String message; private final AsyncHttpEvents events; + private String contentType; /** * Http requests callbacks. @@ -62,6 +63,10 @@ public class AsyncHttpURLConnection { 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 @@ public class AsyncHttpURLConnection { 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 @@ 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)); + 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 7354fb876..480649131 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 @@ public class PeerConnectionClientTest extends InstrumentationTestCase 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 @@ public class PeerConnectionClientTest extends InstrumentationTestCase } } - // 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 @@ public class PeerConnectionClientTest extends InstrumentationTestCase } } - private SignalingParameters getTestSignalingParameters() { - List iceServers = - new LinkedList(); - 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 iceServers = + new LinkedList(); + 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);