Remove peer connection and signaling calls from UI thread.
- Add separate looper threads for peer connection and websocket signaling classes. - To improve the connection speed start peer connection factory initialization once EGL context is ready in parallel with the room connection. - Add asynchronious http request class and start using it in webscoket signaling and room parameters extractor. - Add helper looper based executor class. - Port some of henrika changes from https://webrtc-codereview.appspot.com/36629004/ to fix sensor crashes on non L devices - will remove the change if CL will be submitted soon. R=jiayl@webrtc.org, wzh@webrtc.org Review URL: https://webrtc-codereview.appspot.com/41369004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@8006 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
@@ -56,6 +56,7 @@ import org.webrtc.VideoRenderer.I420Frame;
|
|||||||
*/
|
*/
|
||||||
public class VideoRendererGui implements GLSurfaceView.Renderer {
|
public class VideoRendererGui implements GLSurfaceView.Renderer {
|
||||||
private static VideoRendererGui instance = null;
|
private static VideoRendererGui instance = null;
|
||||||
|
private static Runnable eglContextReady = null;
|
||||||
private static final String TAG = "VideoRendererGui";
|
private static final String TAG = "VideoRendererGui";
|
||||||
private GLSurfaceView surface;
|
private GLSurfaceView surface;
|
||||||
private static EGLContext eglContext = null;
|
private static EGLContext eglContext = null;
|
||||||
@@ -595,9 +596,11 @@ public class VideoRendererGui implements GLSurfaceView.Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Passes GLSurfaceView to video renderer. */
|
/** Passes GLSurfaceView to video renderer. */
|
||||||
public static void setView(GLSurfaceView surface) {
|
public static void setView(GLSurfaceView surface,
|
||||||
|
Runnable eglContextReadyCallback) {
|
||||||
Log.d(TAG, "VideoRendererGui.setView");
|
Log.d(TAG, "VideoRendererGui.setView");
|
||||||
instance = new VideoRendererGui(surface);
|
instance = new VideoRendererGui(surface);
|
||||||
|
eglContextReady = eglContextReadyCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EGLContext getEGLContext() {
|
public static EGLContext getEGLContext() {
|
||||||
@@ -690,7 +693,7 @@ public class VideoRendererGui implements GLSurfaceView.Renderer {
|
|||||||
@Override
|
@Override
|
||||||
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
|
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
|
||||||
Log.d(TAG, "VideoRendererGui.onSurfaceCreated");
|
Log.d(TAG, "VideoRendererGui.onSurfaceCreated");
|
||||||
// Store render EGL context
|
// Store render EGL context.
|
||||||
if (CURRENT_SDK_VERSION >= EGL14_SDK_VERSION) {
|
if (CURRENT_SDK_VERSION >= EGL14_SDK_VERSION) {
|
||||||
eglContext = EGL14.eglGetCurrentContext();
|
eglContext = EGL14.eglGetCurrentContext();
|
||||||
Log.d(TAG, "VideoRendererGui EGL Context: " + eglContext);
|
Log.d(TAG, "VideoRendererGui EGL Context: " + eglContext);
|
||||||
@@ -711,6 +714,11 @@ public class VideoRendererGui implements GLSurfaceView.Renderer {
|
|||||||
}
|
}
|
||||||
checkNoGLES2Error();
|
checkNoGLES2Error();
|
||||||
GLES20.glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
|
GLES20.glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
|
||||||
|
|
||||||
|
// Fire EGL context ready event.
|
||||||
|
if (eglContextReady != null) {
|
||||||
|
eglContextReady.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="https://apprtc.appspot.com/_ah/channel/jsapi"></script>
|
|
||||||
</head>
|
|
||||||
<!--
|
|
||||||
Helper HTML that redirects Google AppEngine's Channel API to a JS object named
|
|
||||||
|androidMessageHandler|, which is expected to be injected into the WebView
|
|
||||||
rendering this page by an Android app's class such as AppRTCClient.
|
|
||||||
-->
|
|
||||||
<body onbeforeunload="closeSocket()" onload="openSocket()">
|
|
||||||
<script type="text/javascript">
|
|
||||||
var token = androidMessageHandler.getToken();
|
|
||||||
if (!token)
|
|
||||||
throw "Missing/malformed token parameter: [" + token + "]";
|
|
||||||
|
|
||||||
var channel = null;
|
|
||||||
var socket = null;
|
|
||||||
|
|
||||||
function openSocket() {
|
|
||||||
channel = new goog.appengine.Channel(token);
|
|
||||||
socket = channel.open({
|
|
||||||
'onopen': function() { androidMessageHandler.onOpen(); },
|
|
||||||
'onmessage': function(msg) { androidMessageHandler.onMessage(msg.data); },
|
|
||||||
'onclose': function() { androidMessageHandler.onClose(); },
|
|
||||||
'onerror': function(err) { androidMessageHandler.onError(err.code, err.description); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSocket() {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
package org.appspot.apprtc;
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
|
import org.appspot.apprtc.util.AppRTCUtils;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -35,8 +37,6 @@ import android.content.pm.PackageManager;
|
|||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.appspot.apprtc.util.AppRTCUtils;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public interface AppRTCClient {
|
|||||||
* https://apprtc.appspot.com/?r=NNN. Once connection is established
|
* https://apprtc.appspot.com/?r=NNN. Once connection is established
|
||||||
* onConnectedToRoom() callback with room parameters is invoked.
|
* onConnectedToRoom() callback with room parameters is invoked.
|
||||||
*/
|
*/
|
||||||
public void connectToRoom(String url, boolean loopback);
|
public void connectToRoom(final String url, final boolean loopback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send offer SDP to the other participant.
|
* Send offer SDP to the other participant.
|
||||||
@@ -60,9 +60,9 @@ public interface AppRTCClient {
|
|||||||
public void sendLocalIceCandidate(final IceCandidate candidate);
|
public void sendLocalIceCandidate(final IceCandidate candidate);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect from the channel.
|
* Disconnect from room.
|
||||||
*/
|
*/
|
||||||
public void disconnect();
|
public void disconnectFromRoom();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Struct holding the signaling parameters of an AppRTC room.
|
* Struct holding the signaling parameters of an AppRTC room.
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
package org.appspot.apprtc;
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
|
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
@@ -49,9 +51,7 @@ import android.widget.ImageButton;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
|
||||||
import org.webrtc.IceCandidate;
|
import org.webrtc.IceCandidate;
|
||||||
import org.webrtc.PeerConnectionFactory;
|
|
||||||
import org.webrtc.SessionDescription;
|
import org.webrtc.SessionDescription;
|
||||||
import org.webrtc.StatsObserver;
|
import org.webrtc.StatsObserver;
|
||||||
import org.webrtc.StatsReport;
|
import org.webrtc.StatsReport;
|
||||||
@@ -71,7 +71,7 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
implements AppRTCClient.SignalingEvents,
|
implements AppRTCClient.SignalingEvents,
|
||||||
PeerConnectionClient.PeerConnectionEvents {
|
PeerConnectionClient.PeerConnectionEvents {
|
||||||
private static final String TAG = "AppRTCClient";
|
private static final String TAG = "AppRTCClient";
|
||||||
private PeerConnectionClient pc;
|
private PeerConnectionClient pc = null;
|
||||||
private AppRTCClient appRtcClient;
|
private AppRTCClient appRtcClient;
|
||||||
private SignalingParameters signalingParameters;
|
private SignalingParameters signalingParameters;
|
||||||
private AppRTCAudioManager audioManager = null;
|
private AppRTCAudioManager audioManager = null;
|
||||||
@@ -123,7 +123,12 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
roomNameView = (TextView) findViewById(R.id.room_name);
|
roomNameView = (TextView) findViewById(R.id.room_name);
|
||||||
videoView = (GLSurfaceView) findViewById(R.id.glview);
|
videoView = (GLSurfaceView) findViewById(R.id.glview);
|
||||||
|
|
||||||
VideoRendererGui.setView(videoView);
|
VideoRendererGui.setView(videoView, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
createPeerConnectionFactory();
|
||||||
|
}
|
||||||
|
});
|
||||||
scalingType = ScalingType.SCALE_ASPECT_FILL;
|
scalingType = ScalingType.SCALE_ASPECT_FILL;
|
||||||
remoteRender = VideoRendererGui.create(0, 0, 100, 100, scalingType, false);
|
remoteRender = VideoRendererGui.create(0, 0, 100, 100, scalingType, false);
|
||||||
localRender = VideoRendererGui.create(0, 0, 100, 100, scalingType, true);
|
localRender = VideoRendererGui.create(0, 0, 100, 100, scalingType, true);
|
||||||
@@ -201,17 +206,6 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
hudView.setVisibility(View.INVISIBLE);
|
hudView.setVisibility(View.INVISIBLE);
|
||||||
addContentView(hudView, hudLayout);
|
addContentView(hudView, hudLayout);
|
||||||
|
|
||||||
// Create and audio manager that will take care of audio routing,
|
|
||||||
// audio modes, audio device enumeration etc.
|
|
||||||
audioManager = AppRTCAudioManager.create(this, new Runnable() {
|
|
||||||
// This method will be called each time the audio state (number and
|
|
||||||
// type of devices) has been changed.
|
|
||||||
public void run() {
|
|
||||||
onAudioManagerChangedState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
Uri url = intent.getData();
|
Uri url = intent.getData();
|
||||||
roomName = intent.getStringExtra(ConnectActivity.EXTRA_ROOMNAME);
|
roomName = intent.getStringExtra(ConnectActivity.EXTRA_ROOMNAME);
|
||||||
@@ -225,6 +219,7 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
|
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
if (loopback || (roomName != null && !roomName.equals(""))) {
|
if (loopback || (roomName != null && !roomName.equals(""))) {
|
||||||
|
// Start room connection.
|
||||||
logAndToast(getString(R.string.connecting_to, url));
|
logAndToast(getString(R.string.connecting_to, url));
|
||||||
appRtcClient = new WebSocketRTCClient(this);
|
appRtcClient = new WebSocketRTCClient(this);
|
||||||
appRtcClient.connectToRoom(url.toString(), loopback);
|
appRtcClient.connectToRoom(url.toString(), loopback);
|
||||||
@@ -233,6 +228,23 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
} else {
|
} else {
|
||||||
roomNameView.setText(roomName);
|
roomNameView.setText(roomName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and audio manager that will take care of audio routing,
|
||||||
|
// audio modes, audio device enumeration etc.
|
||||||
|
audioManager = AppRTCAudioManager.create(this, new Runnable() {
|
||||||
|
// This method will be called each time the audio state (number and
|
||||||
|
// type of devices) has been changed.
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onAudioManagerChangedState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Store existing audio settings and change audio mode to
|
||||||
|
// MODE_IN_COMMUNICATION for best possible VoIP performance.
|
||||||
|
Log.d(TAG, "Initializing the audio manager...");
|
||||||
|
audioManager.init();
|
||||||
|
|
||||||
// For command line execution run connection for <runTimeMs> and exit.
|
// For command line execution run connection for <runTimeMs> and exit.
|
||||||
if (commandLineRun && runTimeMs > 0) {
|
if (commandLineRun && runTimeMs > 0) {
|
||||||
videoView.postDelayed(new Runnable() {
|
videoView.postDelayed(new Runnable() {
|
||||||
@@ -254,6 +266,21 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create peer connection factory when EGL context is ready.
|
||||||
|
private void createPeerConnectionFactory() {
|
||||||
|
final AppRTCDemoActivity thisCopy = this;
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (pc == null) {
|
||||||
|
pc = new PeerConnectionClient();
|
||||||
|
pc.createPeerConnectionFactory(
|
||||||
|
thisCopy, hwCodec, VideoRendererGui.getEGLContext(), thisCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MenuBar fragment for AppRTC.
|
* MenuBar fragment for AppRTC.
|
||||||
*/
|
*/
|
||||||
@@ -291,6 +318,9 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
disconnect();
|
disconnect();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
if (logToast != null) {
|
||||||
|
logToast.cancel();
|
||||||
|
}
|
||||||
activityRunning = false;
|
activityRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +342,7 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
// Disconnect from remote resources, dispose of local resources, and exit.
|
// Disconnect from remote resources, dispose of local resources, and exit.
|
||||||
private void disconnect() {
|
private void disconnect() {
|
||||||
if (appRtcClient != null) {
|
if (appRtcClient != null) {
|
||||||
appRtcClient.disconnect();
|
appRtcClient.disconnectFromRoom();
|
||||||
appRtcClient = null;
|
appRtcClient = null;
|
||||||
}
|
}
|
||||||
if (pc != null) {
|
if (pc != null) {
|
||||||
@@ -349,13 +379,6 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poor-man's assert(): die with |msg| unless |condition| is true.
|
|
||||||
private static void abortUnless(boolean condition, String msg) {
|
|
||||||
if (!condition) {
|
|
||||||
throw new RuntimeException(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log |msg| and Toast about it.
|
// Log |msg| and Toast about it.
|
||||||
private void logAndToast(String msg) {
|
private void logAndToast(String msg) {
|
||||||
Log.d(TAG, msg);
|
Log.d(TAG, msg);
|
||||||
@@ -460,22 +483,20 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
|
// -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
|
||||||
// All events are called from UI thread.
|
// All callbacks are invoked from websocket signaling looper thread and
|
||||||
@Override
|
// are routed to UI thread.
|
||||||
public void onConnectedToRoom(final SignalingParameters params) {
|
private void onConnectedToRoomInternal(final SignalingParameters params) {
|
||||||
if (audioManager != null) {
|
|
||||||
// Store existing audio settings and change audio mode to
|
|
||||||
// MODE_IN_COMMUNICATION for best possible VoIP performance.
|
|
||||||
Log.d(TAG, "Initializing the audio manager...");
|
|
||||||
audioManager.init();
|
|
||||||
}
|
|
||||||
signalingParameters = params;
|
signalingParameters = params;
|
||||||
abortUnless(PeerConnectionFactory.initializeAndroidGlobals(
|
|
||||||
this, true, true, hwCodec, VideoRendererGui.getEGLContext()),
|
|
||||||
"Failed to initializeAndroidGlobals");
|
|
||||||
logAndToast("Creating peer connection...");
|
logAndToast("Creating peer connection...");
|
||||||
pc = new PeerConnectionClient(localRender, remoteRender,
|
if (pc == null) {
|
||||||
signalingParameters, this, startBitrate);
|
// Create peer connection factory if render EGL context ready event
|
||||||
|
// has not been fired yet.
|
||||||
|
pc = new PeerConnectionClient();
|
||||||
|
pc.createPeerConnectionFactory(
|
||||||
|
this, hwCodec, VideoRendererGui.getEGLContext(), this);
|
||||||
|
}
|
||||||
|
pc.createPeerConnection(
|
||||||
|
localRender, remoteRender, signalingParameters, startBitrate);
|
||||||
if (pc.isHDVideo()) {
|
if (pc.isHDVideo()) {
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||||
} else {
|
} else {
|
||||||
@@ -510,7 +531,7 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
}, null);
|
}, null);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new RuntimeException("getStats() return false!");
|
Log.e(TAG, "getStats() return false!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -524,75 +545,127 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectedToRoom(final SignalingParameters params) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onConnectedToRoomInternal(params);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoteDescription(final SessionDescription sdp) {
|
public void onRemoteDescription(final SessionDescription sdp) {
|
||||||
if (pc == null) {
|
runOnUiThread(new Runnable() {
|
||||||
return;
|
@Override
|
||||||
}
|
public void run() {
|
||||||
logAndToast("Received remote " + sdp.type + " ...");
|
if (pc == null) {
|
||||||
pc.setRemoteDescription(sdp);
|
return;
|
||||||
if (!signalingParameters.initiator) {
|
}
|
||||||
logAndToast("Creating ANSWER...");
|
logAndToast("Received remote " + sdp.type + " ...");
|
||||||
// Create answer. Answer SDP will be sent to offering client in
|
pc.setRemoteDescription(sdp);
|
||||||
// PeerConnectionEvents.onLocalDescription event.
|
if (!signalingParameters.initiator) {
|
||||||
pc.createAnswer();
|
logAndToast("Creating ANSWER...");
|
||||||
}
|
// Create answer. Answer SDP will be sent to offering client in
|
||||||
|
// PeerConnectionEvents.onLocalDescription event.
|
||||||
|
pc.createAnswer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoteIceCandidate(final IceCandidate candidate) {
|
public void onRemoteIceCandidate(final IceCandidate candidate) {
|
||||||
if (pc != null) {
|
runOnUiThread(new Runnable() {
|
||||||
pc.addRemoteIceCandidate(candidate);
|
@Override
|
||||||
}
|
public void run() {
|
||||||
|
if (pc != null) {
|
||||||
|
pc.addRemoteIceCandidate(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChannelClose() {
|
public void onChannelClose() {
|
||||||
logAndToast("Remote end hung up; dropping PeerConnection");
|
runOnUiThread(new Runnable() {
|
||||||
disconnect();
|
@Override
|
||||||
|
public void run() {
|
||||||
|
logAndToast("Remote end hung up; dropping PeerConnection");
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChannelError(final String description) {
|
public void onChannelError(final String description) {
|
||||||
if (!isError) {
|
runOnUiThread(new Runnable() {
|
||||||
isError = true;
|
@Override
|
||||||
disconnectWithErrorMessage(description);
|
public void run() {
|
||||||
}
|
if (!isError) {
|
||||||
|
isError = true;
|
||||||
|
disconnectWithErrorMessage(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
|
// -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
|
||||||
// Send local peer connection SDP and ICE candidates to remote party.
|
// Send local peer connection SDP and ICE candidates to remote party.
|
||||||
// All callbacks are invoked from UI thread.
|
// All callbacks are invoked from peer connection client looper thread and
|
||||||
|
// are routed to UI thread.
|
||||||
@Override
|
@Override
|
||||||
public void onLocalDescription(final SessionDescription sdp) {
|
public void onLocalDescription(final SessionDescription sdp) {
|
||||||
if (appRtcClient != null) {
|
runOnUiThread(new Runnable() {
|
||||||
logAndToast("Sending " + sdp.type + " ...");
|
@Override
|
||||||
if (signalingParameters.initiator) {
|
public void run() {
|
||||||
appRtcClient.sendOfferSdp(sdp);
|
if (appRtcClient != null) {
|
||||||
} else {
|
logAndToast("Sending " + sdp.type + " ...");
|
||||||
appRtcClient.sendAnswerSdp(sdp);
|
if (signalingParameters.initiator) {
|
||||||
|
appRtcClient.sendOfferSdp(sdp);
|
||||||
|
} else {
|
||||||
|
appRtcClient.sendAnswerSdp(sdp);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIceCandidate(final IceCandidate candidate) {
|
public void onIceCandidate(final IceCandidate candidate) {
|
||||||
if (appRtcClient != null) {
|
runOnUiThread(new Runnable() {
|
||||||
appRtcClient.sendLocalIceCandidate(candidate);
|
@Override
|
||||||
}
|
public void run() {
|
||||||
|
if (appRtcClient != null) {
|
||||||
|
appRtcClient.sendLocalIceCandidate(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIceConnected() {
|
public void onIceConnected() {
|
||||||
logAndToast("ICE connected");
|
runOnUiThread(new Runnable() {
|
||||||
iceConnected = true;
|
@Override
|
||||||
updateVideoView();
|
public void run() {
|
||||||
|
logAndToast("ICE connected");
|
||||||
|
iceConnected = true;
|
||||||
|
updateVideoView();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIceDisconnected() {
|
public void onIceDisconnected() {
|
||||||
logAndToast("ICE disconnected");
|
runOnUiThread(new Runnable() {
|
||||||
disconnect();
|
@Override
|
||||||
|
public void run() {
|
||||||
|
logAndToast("ICE disconnected");
|
||||||
|
iceConnected = false;
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -600,10 +673,15 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPeerConnectionError(String description) {
|
public void onPeerConnectionError(final String description) {
|
||||||
if (!isError) {
|
runOnUiThread(new Runnable() {
|
||||||
isError = true;
|
@Override
|
||||||
disconnectWithErrorMessage(description);
|
public void run() {
|
||||||
}
|
if (!isError) {
|
||||||
|
isError = true;
|
||||||
|
disconnectWithErrorMessage(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,15 +27,16 @@
|
|||||||
|
|
||||||
package org.appspot.apprtc;
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
|
import org.appspot.apprtc.util.AppRTCUtils;
|
||||||
|
import org.appspot.apprtc.util.AppRTCUtils.NonThreadSafe;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.Sensor;
|
import android.hardware.Sensor;
|
||||||
import android.hardware.SensorEvent;
|
import android.hardware.SensorEvent;
|
||||||
import android.hardware.SensorEventListener;
|
import android.hardware.SensorEventListener;
|
||||||
import android.hardware.SensorManager;
|
import android.hardware.SensorManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import java.util.List;
|
|
||||||
import org.appspot.apprtc.util.AppRTCUtils;
|
|
||||||
import org.appspot.apprtc.util.AppRTCUtils.NonThreadSafe;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppRTCProximitySensor manages functions related to the proximity sensor in
|
* AppRTCProximitySensor manages functions related to the proximity sensor in
|
||||||
@@ -161,16 +162,27 @@ public class AppRTCProximitySensor implements SensorEventListener {
|
|||||||
if (proximitySensor == null) {
|
if (proximitySensor == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Proximity sensor: " + "name=" + proximitySensor.getName()
|
StringBuilder info = new StringBuilder("Proximity sensor: ");
|
||||||
+ ", vendor: " + proximitySensor.getVendor()
|
info.append("name=" + proximitySensor.getName());
|
||||||
+ ", type: " + proximitySensor.getStringType()
|
info.append(", vendor: " + proximitySensor.getVendor());
|
||||||
+ ", reporting mode: " + proximitySensor.getReportingMode()
|
info.append(", power: " + proximitySensor.getPower());
|
||||||
+ ", power: " + proximitySensor.getPower()
|
info.append(", resolution: " + proximitySensor.getResolution());
|
||||||
+ ", min delay: " + proximitySensor.getMinDelay()
|
info.append(", max range: " + proximitySensor.getMaximumRange());
|
||||||
+ ", max delay: " + proximitySensor.getMaxDelay()
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
|
||||||
+ ", resolution: " + proximitySensor.getResolution()
|
// Added in API level 9.
|
||||||
+ ", max range: " + proximitySensor.getMaximumRange()
|
info.append(", min delay: " + proximitySensor.getMinDelay());
|
||||||
+ ", isWakeUpSensor: " + proximitySensor.isWakeUpSensor());
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
|
||||||
|
// Added in API level 20.
|
||||||
|
info.append(", type: " + proximitySensor.getStringType());
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
// Added in API level 21.
|
||||||
|
info.append(", max delay: " + proximitySensor.getMaxDelay());
|
||||||
|
info.append(", reporting mode: " + proximitySensor.getReportingMode());
|
||||||
|
info.append(", isWakeUpSensor: " + proximitySensor.isWakeUpSensor());
|
||||||
|
}
|
||||||
|
Log.d(TAG, info.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -27,11 +27,13 @@
|
|||||||
|
|
||||||
package org.appspot.apprtc;
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
import android.os.Handler;
|
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
||||||
import android.os.Looper;
|
import org.appspot.apprtc.util.LooperExecutor;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.opengl.EGLContext;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
|
||||||
import org.webrtc.DataChannel;
|
import org.webrtc.DataChannel;
|
||||||
import org.webrtc.IceCandidate;
|
import org.webrtc.IceCandidate;
|
||||||
import org.webrtc.MediaConstraints;
|
import org.webrtc.MediaConstraints;
|
||||||
@@ -54,28 +56,32 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PeerConnection client for AppRTC.
|
* Peer connection client implementation.
|
||||||
|
*
|
||||||
|
* <p>All public methods are routed to local looper thread.
|
||||||
|
* All PeerConnectionEvents callbacks are invoked from the same looper thread.
|
||||||
*/
|
*/
|
||||||
public class PeerConnectionClient {
|
public class PeerConnectionClient {
|
||||||
private static final String TAG = "PCRTCClient";
|
private static final String TAG = "PCRTCClient";
|
||||||
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 Handler uiHandler;
|
private final LooperExecutor executor;
|
||||||
private PeerConnectionFactory factory;
|
private PeerConnectionFactory factory = null;
|
||||||
private PeerConnection pc;
|
private PeerConnection pc = null;
|
||||||
private VideoSource videoSource;
|
private VideoSource videoSource;
|
||||||
private boolean videoSourceStopped;
|
private boolean videoSourceStopped = false;
|
||||||
|
private boolean isError = false;
|
||||||
private final PCObserver pcObserver = new PCObserver();
|
private final PCObserver pcObserver = new PCObserver();
|
||||||
private final SDPObserver sdpObserver = new SDPObserver();
|
private final SDPObserver sdpObserver = new SDPObserver();
|
||||||
private final VideoRenderer.Callbacks localRender;
|
private VideoRenderer.Callbacks localRender;
|
||||||
private final VideoRenderer.Callbacks remoteRender;
|
private VideoRenderer.Callbacks remoteRender;
|
||||||
|
private SignalingParameters signalingParameters;
|
||||||
// Queued remote ICE candidates are consumed only after both local and
|
// Queued remote ICE candidates are consumed only after both local and
|
||||||
// remote descriptions are set. Similarly local ICE candidates are sent to
|
// remote descriptions are set. Similarly local ICE candidates are sent to
|
||||||
// remote peer after both local and remote description are set.
|
// remote peer after both local and remote description are set.
|
||||||
private LinkedList<IceCandidate> queuedRemoteCandidates = null;
|
private LinkedList<IceCandidate> queuedRemoteCandidates = null;
|
||||||
private MediaConstraints sdpMediaConstraints;
|
private MediaConstraints sdpMediaConstraints;
|
||||||
private MediaConstraints videoConstraints;
|
|
||||||
private PeerConnectionEvents events;
|
private PeerConnectionEvents events;
|
||||||
private int startBitrate;
|
private int startBitrate;
|
||||||
private boolean isInitiator;
|
private boolean isInitiator;
|
||||||
@@ -83,184 +89,12 @@ public class PeerConnectionClient {
|
|||||||
private SessionDescription localSdp = null; // either offer or answer SDP
|
private SessionDescription localSdp = null; // either offer or answer SDP
|
||||||
private MediaStream mediaStream = null;
|
private MediaStream mediaStream = null;
|
||||||
|
|
||||||
public PeerConnectionClient(
|
|
||||||
VideoRenderer.Callbacks localRender,
|
|
||||||
VideoRenderer.Callbacks remoteRender,
|
|
||||||
SignalingParameters signalingParameters,
|
|
||||||
PeerConnectionEvents events,
|
|
||||||
int startBitrate) {
|
|
||||||
this.localRender = localRender;
|
|
||||||
this.remoteRender = remoteRender;
|
|
||||||
this.events = events;
|
|
||||||
this.startBitrate = startBitrate;
|
|
||||||
uiHandler = new Handler(Looper.getMainLooper());
|
|
||||||
isInitiator = signalingParameters.initiator;
|
|
||||||
queuedRemoteCandidates = new LinkedList<IceCandidate>();
|
|
||||||
|
|
||||||
sdpMediaConstraints = new MediaConstraints();
|
|
||||||
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
|
||||||
"OfferToReceiveAudio", "true"));
|
|
||||||
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
|
||||||
"OfferToReceiveVideo", "true"));
|
|
||||||
videoConstraints = signalingParameters.videoConstraints;
|
|
||||||
|
|
||||||
factory = new PeerConnectionFactory();
|
|
||||||
MediaConstraints pcConstraints = signalingParameters.pcConstraints;
|
|
||||||
pcConstraints.optional.add(
|
|
||||||
new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
|
|
||||||
pc = factory.createPeerConnection(signalingParameters.iceServers,
|
|
||||||
pcConstraints, pcObserver);
|
|
||||||
isInitiator = false;
|
|
||||||
|
|
||||||
// Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging.
|
|
||||||
// NOTE: this _must_ happen while |factory| is alive!
|
|
||||||
// Logging.enableTracing(
|
|
||||||
// "logcat:",
|
|
||||||
// EnumSet.of(Logging.TraceLevel.TRACE_ALL),
|
|
||||||
// Logging.Severity.LS_SENSITIVE);
|
|
||||||
|
|
||||||
mediaStream = factory.createLocalMediaStream("ARDAMS");
|
|
||||||
if (videoConstraints != null) {
|
|
||||||
mediaStream.addTrack(createVideoTrack(useFrontFacingCamera));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signalingParameters.audioConstraints != null) {
|
|
||||||
mediaStream.addTrack(factory.createAudioTrack(
|
|
||||||
AUDIO_TRACK_ID,
|
|
||||||
factory.createAudioSource(signalingParameters.audioConstraints)));
|
|
||||||
}
|
|
||||||
pc.addStream(mediaStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHDVideo() {
|
|
||||||
if (videoConstraints == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int minWidth = 0;
|
|
||||||
int minHeight = 0;
|
|
||||||
for (KeyValuePair keyValuePair : videoConstraints.mandatory) {
|
|
||||||
if (keyValuePair.getKey().equals("minWidth")) {
|
|
||||||
try {
|
|
||||||
minWidth = Integer.parseInt(keyValuePair.getValue());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Log.e(TAG, "Can not parse video width from video constraints");
|
|
||||||
}
|
|
||||||
} else if (keyValuePair.getKey().equals("minHeight")) {
|
|
||||||
try {
|
|
||||||
minHeight = Integer.parseInt(keyValuePair.getValue());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Log.e(TAG, "Can not parse video height from video constraints");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (minWidth * minHeight >= 1280 * 720) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
|
|
||||||
return pc.getStats(observer, track);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createOffer() {
|
|
||||||
uiHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (pc != null) {
|
|
||||||
isInitiator = true;
|
|
||||||
pc.createOffer(sdpObserver, sdpMediaConstraints);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createAnswer() {
|
|
||||||
uiHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (pc != null) {
|
|
||||||
isInitiator = false;
|
|
||||||
pc.createAnswer(sdpObserver, sdpMediaConstraints);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addRemoteIceCandidate(final IceCandidate candidate) {
|
|
||||||
uiHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (pc != null) {
|
|
||||||
if (queuedRemoteCandidates != null) {
|
|
||||||
queuedRemoteCandidates.add(candidate);
|
|
||||||
} else {
|
|
||||||
pc.addIceCandidate(candidate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRemoteDescription(final SessionDescription sdp) {
|
|
||||||
uiHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (pc != null) {
|
|
||||||
String sdpDescription = preferISAC(sdp.description);
|
|
||||||
if (startBitrate > 0) {
|
|
||||||
sdpDescription = setStartBitrate(sdpDescription, startBitrate);
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Set remote SDP.");
|
|
||||||
SessionDescription sdpRemote = new SessionDescription(
|
|
||||||
sdp.type, sdpDescription);
|
|
||||||
pc.setRemoteDescription(sdpObserver, sdpRemote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopVideoSource() {
|
|
||||||
if (videoSource != null) {
|
|
||||||
Log.d(TAG, "Stop video source.");
|
|
||||||
videoSource.stop();
|
|
||||||
videoSourceStopped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startVideoSource() {
|
|
||||||
if (videoSource != null && videoSourceStopped) {
|
|
||||||
Log.d(TAG, "Restart video source.");
|
|
||||||
videoSource.restart();
|
|
||||||
videoSourceStopped = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
uiHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
Log.d(TAG, "Closing peer connection.");
|
|
||||||
if (pc != null) {
|
|
||||||
pc.dispose();
|
|
||||||
pc = null;
|
|
||||||
}
|
|
||||||
if (videoSource != null) {
|
|
||||||
videoSource.dispose();
|
|
||||||
videoSource = null;
|
|
||||||
}
|
|
||||||
if (factory != null) {
|
|
||||||
factory.dispose();
|
|
||||||
factory = null;
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Closing peer connection done.");
|
|
||||||
events.onPeerConnectionClosed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SDP/ICE ready callbacks.
|
* SDP/ICE ready callbacks.
|
||||||
*/
|
*/
|
||||||
public static interface PeerConnectionEvents {
|
public static interface PeerConnectionEvents {
|
||||||
/**
|
/**
|
||||||
* Callback fired once offer is created and local SDP is set.
|
* Callback fired once local SDP is created and set.
|
||||||
*/
|
*/
|
||||||
public void onLocalDescription(final SessionDescription sdp);
|
public void onLocalDescription(final SessionDescription sdp);
|
||||||
|
|
||||||
@@ -289,15 +123,263 @@ public class PeerConnectionClient {
|
|||||||
/**
|
/**
|
||||||
* Callback fired once peer connection error happened.
|
* Callback fired once peer connection error happened.
|
||||||
*/
|
*/
|
||||||
public void onPeerConnectionError(String description);
|
public void onPeerConnectionError(final String description);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PeerConnectionClient() {
|
||||||
|
executor = new LooperExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createPeerConnectionFactory(
|
||||||
|
final Context context,
|
||||||
|
final boolean vp8HwAcceleration,
|
||||||
|
final EGLContext renderEGLContext,
|
||||||
|
final PeerConnectionEvents events) {
|
||||||
|
this.events = events;
|
||||||
|
executor.requestStart();
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
createPeerConnectionFactoryInternal(
|
||||||
|
context, vp8HwAcceleration, renderEGLContext);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createPeerConnection(
|
||||||
|
final VideoRenderer.Callbacks localRender,
|
||||||
|
final VideoRenderer.Callbacks remoteRender,
|
||||||
|
final SignalingParameters signalingParameters,
|
||||||
|
final int startBitrate) {
|
||||||
|
this.localRender = localRender;
|
||||||
|
this.remoteRender = remoteRender;
|
||||||
|
this.signalingParameters = signalingParameters;
|
||||||
|
this.startBitrate = startBitrate;
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
createPeerConnectionInternal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
closeInternal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
executor.requestStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPeerConnectionFactoryInternal(
|
||||||
|
Context context,
|
||||||
|
boolean vp8HwAcceleration,
|
||||||
|
EGLContext renderEGLContext) {
|
||||||
|
Log.d(TAG, "Create peer connection factory.");
|
||||||
|
isError = false;
|
||||||
|
if (!PeerConnectionFactory.initializeAndroidGlobals(
|
||||||
|
context, true, true, vp8HwAcceleration, renderEGLContext)) {
|
||||||
|
events.onPeerConnectionError("Failed to initializeAndroidGlobals");
|
||||||
|
}
|
||||||
|
factory = new PeerConnectionFactory();
|
||||||
|
Log.d(TAG, "Peer connection factory created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPeerConnectionInternal() {
|
||||||
|
if (factory == null || isError) {
|
||||||
|
Log.e(TAG, "Peerconnection factory is not created");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Create peer connection.");
|
||||||
|
isInitiator = signalingParameters.initiator;
|
||||||
|
queuedRemoteCandidates = new LinkedList<IceCandidate>();
|
||||||
|
|
||||||
|
sdpMediaConstraints = new MediaConstraints();
|
||||||
|
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
||||||
|
"OfferToReceiveAudio", "true"));
|
||||||
|
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
||||||
|
"OfferToReceiveVideo", "true"));
|
||||||
|
|
||||||
|
MediaConstraints pcConstraints = signalingParameters.pcConstraints;
|
||||||
|
pcConstraints.optional.add(
|
||||||
|
new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
|
||||||
|
pc = factory.createPeerConnection(signalingParameters.iceServers,
|
||||||
|
pcConstraints, pcObserver);
|
||||||
|
isInitiator = false;
|
||||||
|
|
||||||
|
// Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging.
|
||||||
|
// NOTE: this _must_ happen while |factory| is alive!
|
||||||
|
// Logging.enableTracing(
|
||||||
|
// "logcat:",
|
||||||
|
// EnumSet.of(Logging.TraceLevel.TRACE_ALL),
|
||||||
|
// Logging.Severity.LS_SENSITIVE);
|
||||||
|
|
||||||
|
mediaStream = factory.createLocalMediaStream("ARDAMS");
|
||||||
|
if (signalingParameters.videoConstraints != null) {
|
||||||
|
mediaStream.addTrack(createVideoTrack(useFrontFacingCamera));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signalingParameters.audioConstraints != null) {
|
||||||
|
mediaStream.addTrack(factory.createAudioTrack(
|
||||||
|
AUDIO_TRACK_ID,
|
||||||
|
factory.createAudioSource(signalingParameters.audioConstraints)));
|
||||||
|
}
|
||||||
|
pc.addStream(mediaStream);
|
||||||
|
Log.d(TAG, "Peer connection created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeInternal() {
|
||||||
|
Log.d(TAG, "Closing peer connection.");
|
||||||
|
if (pc != null) {
|
||||||
|
pc.dispose();
|
||||||
|
pc = null;
|
||||||
|
}
|
||||||
|
if (videoSource != null) {
|
||||||
|
videoSource.dispose();
|
||||||
|
videoSource = null;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Closing peer connection factory.");
|
||||||
|
if (factory != null) {
|
||||||
|
factory.dispose();
|
||||||
|
factory = null;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Closing peer connection done.");
|
||||||
|
events.onPeerConnectionClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHDVideo() {
|
||||||
|
if (signalingParameters.videoConstraints == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int minWidth = 0;
|
||||||
|
int minHeight = 0;
|
||||||
|
for (KeyValuePair keyValuePair :
|
||||||
|
signalingParameters.videoConstraints.mandatory) {
|
||||||
|
if (keyValuePair.getKey().equals("minWidth")) {
|
||||||
|
try {
|
||||||
|
minWidth = Integer.parseInt(keyValuePair.getValue());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "Can not parse video width from video constraints");
|
||||||
|
}
|
||||||
|
} else if (keyValuePair.getKey().equals("minHeight")) {
|
||||||
|
try {
|
||||||
|
minHeight = Integer.parseInt(keyValuePair.getValue());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "Can not parse video height from video constraints");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (minWidth * minHeight >= 1280 * 720) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
|
||||||
|
if (pc != null && !isError) {
|
||||||
|
return pc.getStats(observer, track);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createOffer() {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (pc != null && !isError) {
|
||||||
|
isInitiator = true;
|
||||||
|
pc.createOffer(sdpObserver, sdpMediaConstraints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createAnswer() {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (pc != null && !isError) {
|
||||||
|
isInitiator = false;
|
||||||
|
pc.createAnswer(sdpObserver, sdpMediaConstraints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addRemoteIceCandidate(final IceCandidate candidate) {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (pc != null && !isError) {
|
||||||
|
if (queuedRemoteCandidates != null) {
|
||||||
|
queuedRemoteCandidates.add(candidate);
|
||||||
|
} else {
|
||||||
|
pc.addIceCandidate(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemoteDescription(final SessionDescription sdp) {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (pc == null || isError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String sdpDescription = preferISAC(sdp.description);
|
||||||
|
if (startBitrate > 0) {
|
||||||
|
sdpDescription = setStartBitrate(sdpDescription, startBitrate);
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Set remote SDP.");
|
||||||
|
SessionDescription sdpRemote = new SessionDescription(
|
||||||
|
sdp.type, sdpDescription);
|
||||||
|
pc.setRemoteDescription(sdpObserver, sdpRemote);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopVideoSource() {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (videoSource != null && !videoSourceStopped) {
|
||||||
|
Log.d(TAG, "Stop video source.");
|
||||||
|
videoSource.stop();
|
||||||
|
videoSourceStopped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startVideoSource() {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (videoSource != null && videoSourceStopped) {
|
||||||
|
Log.d(TAG, "Restart video source.");
|
||||||
|
videoSource.restart();
|
||||||
|
videoSourceStopped = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void reportError(final String errorMessage) {
|
private void reportError(final String errorMessage) {
|
||||||
Log.e(TAG, "Peerconnection error: " + errorMessage);
|
Log.e(TAG, "Peerconnection error: " + errorMessage);
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
events.onPeerConnectionError(errorMessage);
|
if (!isError) {
|
||||||
|
events.onPeerConnectionError(errorMessage);
|
||||||
|
isError = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -336,7 +418,8 @@ public class PeerConnectionClient {
|
|||||||
videoSource.dispose();
|
videoSource.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
videoSource = factory.createVideoSource(capturer, videoConstraints);
|
videoSource = factory.createVideoSource(
|
||||||
|
capturer, signalingParameters.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);
|
||||||
@@ -344,13 +427,6 @@ public class PeerConnectionClient {
|
|||||||
return videoTrack;
|
return videoTrack;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poor-man's assert(): die with |msg| unless |condition| is true.
|
|
||||||
private void abortUnless(boolean condition, String msg) {
|
|
||||||
if (!condition) {
|
|
||||||
reportError(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String setStartBitrate(
|
private static String setStartBitrate(
|
||||||
String sdpDescription, int bitrateKbps) {
|
String sdpDescription, int bitrateKbps) {
|
||||||
String[] lines = sdpDescription.split("\r\n");
|
String[] lines = sdpDescription.split("\r\n");
|
||||||
@@ -443,16 +519,16 @@ public class PeerConnectionClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void switchCamera() {
|
private void switchCameraInternal() {
|
||||||
if (videoConstraints == null) {
|
if (signalingParameters.videoConstraints == null) {
|
||||||
return; // No video is sent.
|
return; // No video is sent.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pc.signalingState() != PeerConnection.SignalingState.STABLE) {
|
if (pc.signalingState() != PeerConnection.SignalingState.STABLE) {
|
||||||
Log.e(TAG, "Switching camera during negotiation is not handled.");
|
Log.e(TAG, "Switching camera during negotiation is not handled.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Switch camera");
|
||||||
pc.removeStream(mediaStream);
|
pc.removeStream(mediaStream);
|
||||||
VideoTrack currentTrack = mediaStream.videoTracks.get(0);
|
VideoTrack currentTrack = mediaStream.videoTracks.get(0);
|
||||||
mediaStream.removeTrack(currentTrack);
|
mediaStream.removeTrack(currentTrack);
|
||||||
@@ -485,13 +561,26 @@ public class PeerConnectionClient {
|
|||||||
pc.setRemoteDescription(new SwitchCameraSdbObserver(), remoteDesc);
|
pc.setRemoteDescription(new SwitchCameraSdbObserver(), remoteDesc);
|
||||||
pc.setLocalDescription(new SwitchCameraSdbObserver(), localSdp);
|
pc.setLocalDescription(new SwitchCameraSdbObserver(), localSdp);
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "Switch camera done");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchCamera() {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (pc != null && !isError) {
|
||||||
|
switchCameraInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation detail: observe ICE & stream changes and react accordingly.
|
// Implementation detail: observe ICE & stream changes and react accordingly.
|
||||||
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){
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
events.onIceCandidate(candidate);
|
events.onIceCandidate(candidate);
|
||||||
}
|
}
|
||||||
@@ -506,23 +595,20 @@ public class PeerConnectionClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIceConnectionChange(
|
public void onIceConnectionChange(
|
||||||
PeerConnection.IceConnectionState newState) {
|
final PeerConnection.IceConnectionState newState) {
|
||||||
Log.d(TAG, "IceConnectionState: " + newState);
|
executor.execute(new Runnable() {
|
||||||
if (newState == IceConnectionState.CONNECTED) {
|
@Override
|
||||||
uiHandler.post(new Runnable() {
|
public void run() {
|
||||||
public void run() {
|
Log.d(TAG, "IceConnectionState: " + newState);
|
||||||
|
if (newState == IceConnectionState.CONNECTED) {
|
||||||
events.onIceConnected();
|
events.onIceConnected();
|
||||||
}
|
} else if (newState == IceConnectionState.DISCONNECTED) {
|
||||||
});
|
|
||||||
} else if (newState == IceConnectionState.DISCONNECTED) {
|
|
||||||
uiHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
events.onIceDisconnected();
|
events.onIceDisconnected();
|
||||||
|
} else if (newState == IceConnectionState.FAILED) {
|
||||||
|
reportError("ICE connection failed.");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else if (newState == IceConnectionState.FAILED) {
|
});
|
||||||
reportError("ICE connection failed.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -533,11 +619,16 @@ public class PeerConnectionClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAddStream(final MediaStream stream){
|
public void onAddStream(final MediaStream stream){
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
abortUnless(stream.audioTracks.size() <= 1
|
if (pc == null || isError) {
|
||||||
&& stream.videoTracks.size() <= 1,
|
return;
|
||||||
"Weird-looking stream: " + stream);
|
}
|
||||||
|
if (stream.audioTracks.size() > 1 || stream.videoTracks.size() > 1) {
|
||||||
|
reportError("Weird-looking stream: " + stream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (stream.videoTracks.size() == 1) {
|
if (stream.videoTracks.size() == 1) {
|
||||||
stream.videoTracks.get(0).addRenderer(
|
stream.videoTracks.get(0).addRenderer(
|
||||||
new VideoRenderer(remoteRender));
|
new VideoRenderer(remoteRender));
|
||||||
@@ -548,8 +639,12 @@ public class PeerConnectionClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoveStream(final MediaStream stream){
|
public void onRemoveStream(final MediaStream stream){
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (pc == null || isError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
stream.videoTracks.get(0).dispose();
|
stream.videoTracks.get(0).dispose();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -573,13 +668,17 @@ public class PeerConnectionClient {
|
|||||||
private class SDPObserver implements SdpObserver {
|
private class SDPObserver implements SdpObserver {
|
||||||
@Override
|
@Override
|
||||||
public void onCreateSuccess(final SessionDescription origSdp) {
|
public void onCreateSuccess(final SessionDescription origSdp) {
|
||||||
abortUnless(localSdp == null, "multiple SDP create?!?");
|
if (localSdp != null) {
|
||||||
|
reportError("Multiple SDP create.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
final SessionDescription sdp = new SessionDescription(
|
final SessionDescription sdp = new SessionDescription(
|
||||||
origSdp.type, preferISAC(origSdp.description));
|
origSdp.type, preferISAC(origSdp.description));
|
||||||
localSdp = sdp;
|
localSdp = sdp;
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (pc != null) {
|
if (pc != null && !isError) {
|
||||||
Log.d(TAG, "Set local SDP from " + sdp.type);
|
Log.d(TAG, "Set local SDP from " + sdp.type);
|
||||||
pc.setLocalDescription(sdpObserver, sdp);
|
pc.setLocalDescription(sdpObserver, sdp);
|
||||||
}
|
}
|
||||||
@@ -589,9 +688,10 @@ public class PeerConnectionClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSetSuccess() {
|
public void onSetSuccess() {
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (pc == null) {
|
if (pc == null || isError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isInitiator) {
|
if (isInitiator) {
|
||||||
|
|||||||
@@ -26,10 +26,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.appspot.apprtc;
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
||||||
|
import org.appspot.apprtc.util.AsyncHttpURLConnection;
|
||||||
|
import org.appspot.apprtc.util.AsyncHttpURLConnection.AsyncHttpEvents;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -40,7 +42,6 @@ import org.webrtc.SessionDescription;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -50,12 +51,12 @@ import java.util.Scanner;
|
|||||||
* AsyncTask that converts an AppRTC room URL into the set of signaling
|
* AsyncTask that converts an AppRTC room URL into the set of signaling
|
||||||
* parameters to use with that room.
|
* parameters to use with that room.
|
||||||
*/
|
*/
|
||||||
public class RoomParametersFetcher
|
public class RoomParametersFetcher {
|
||||||
extends AsyncTask<String, Void, SignalingParameters> {
|
|
||||||
private static final String TAG = "RoomRTCClient";
|
private static final String TAG = "RoomRTCClient";
|
||||||
private Exception exception = null;
|
private final RoomParametersFetcherEvents events;
|
||||||
private RoomParametersFetcherEvents events = null;
|
private final boolean loopback;
|
||||||
private boolean loopback;
|
private final String registerUrl;
|
||||||
|
private AsyncHttpURLConnection httpConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Room parameters fetcher callbacks.
|
* Room parameters fetcher callbacks.
|
||||||
@@ -73,146 +74,125 @@ public class RoomParametersFetcher
|
|||||||
public void onSignalingParametersError(final String description);
|
public void onSignalingParametersError(final String description);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoomParametersFetcher(RoomParametersFetcherEvents events,
|
public RoomParametersFetcher(boolean loopback, String registerUrl,
|
||||||
boolean loopback) {
|
final RoomParametersFetcherEvents events) {
|
||||||
super();
|
Log.d(TAG, "Connecting to room: " + registerUrl);
|
||||||
this.events = events;
|
|
||||||
this.loopback = loopback;
|
this.loopback = loopback;
|
||||||
|
this.registerUrl = registerUrl;
|
||||||
|
this.events = events;
|
||||||
|
|
||||||
|
httpConnection = new AsyncHttpURLConnection("POST", registerUrl, null,
|
||||||
|
new AsyncHttpEvents() {
|
||||||
|
@Override
|
||||||
|
public void OnHttpError(String errorMessage) {
|
||||||
|
Log.e(TAG, "Room connection error: " + errorMessage);
|
||||||
|
events.onSignalingParametersError(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void OnHttpComplete(String response) {
|
||||||
|
RoomHttpResponseParse(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
httpConnection.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void RoomHttpResponseParse(String response) {
|
||||||
protected SignalingParameters doInBackground(String... urls) {
|
|
||||||
if (events == null) {
|
|
||||||
exception = new RuntimeException("Room conenction events should be set");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (urls.length != 1) {
|
|
||||||
exception = new RuntimeException("Must be called with a single URL");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
exception = null;
|
|
||||||
return getParametersForRoomUrl(urls[0]);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
exception = e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(SignalingParameters params) {
|
|
||||||
if (exception != null) {
|
|
||||||
Log.e(TAG, "Room connection error: " + exception.toString());
|
|
||||||
events.onSignalingParametersError(exception.getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (params == null) {
|
|
||||||
Log.e(TAG, "Can not extract room parameters");
|
|
||||||
events.onSignalingParametersError("Can not extract room parameters");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
events.onSignalingParametersReady(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetches |url| and fishes the signaling parameters out of the JSON.
|
|
||||||
private SignalingParameters getParametersForRoomUrl(String url)
|
|
||||||
throws IOException, JSONException {
|
|
||||||
Log.d(TAG, "Connecting to room: " + url);
|
|
||||||
HttpURLConnection connection =
|
|
||||||
(HttpURLConnection) new URL(url).openConnection();
|
|
||||||
connection.setDoOutput(true);
|
|
||||||
connection.setRequestMethod("POST");
|
|
||||||
connection.setDoInput(true);
|
|
||||||
|
|
||||||
InputStream responseStream = connection.getInputStream();
|
|
||||||
String response = drainStream(responseStream);
|
|
||||||
responseStream.close();
|
|
||||||
Log.d(TAG, "Room response: " + response);
|
Log.d(TAG, "Room response: " + response);
|
||||||
JSONObject roomJson = new JSONObject(response);
|
try {
|
||||||
LinkedList<IceCandidate> iceCandidates = null;
|
LinkedList<IceCandidate> iceCandidates = null;
|
||||||
SessionDescription offerSdp = null;
|
SessionDescription offerSdp = null;
|
||||||
|
JSONObject roomJson = new JSONObject(response);
|
||||||
|
|
||||||
String result = roomJson.getString("result");
|
String result = roomJson.getString("result");
|
||||||
if (!result.equals("SUCCESS")) {
|
if (!result.equals("SUCCESS")) {
|
||||||
throw new JSONException(result);
|
events.onSignalingParametersError("Room response error: " + result);
|
||||||
}
|
return;
|
||||||
response = roomJson.getString("params");
|
}
|
||||||
roomJson = new JSONObject(response);
|
response = roomJson.getString("params");
|
||||||
String roomId = roomJson.getString("room_id");
|
roomJson = new JSONObject(response);
|
||||||
String clientId = roomJson.getString("client_id");
|
String roomId = roomJson.getString("room_id");
|
||||||
String wssUrl = roomJson.getString("wss_url");
|
String clientId = roomJson.getString("client_id");
|
||||||
String wssPostUrl = roomJson.getString("wss_post_url");
|
String wssUrl = roomJson.getString("wss_url");
|
||||||
boolean initiator = (roomJson.getBoolean("is_initiator"));
|
String wssPostUrl = roomJson.getString("wss_post_url");
|
||||||
String roomUrl = url.substring(0, url.indexOf("/register"));
|
boolean initiator = (roomJson.getBoolean("is_initiator"));
|
||||||
if (!initiator) {
|
String roomUrl =
|
||||||
iceCandidates = new LinkedList<IceCandidate>();
|
registerUrl.substring(0, registerUrl.indexOf("/register"));
|
||||||
String messagesString = roomJson.getString("messages");
|
if (!initiator) {
|
||||||
JSONArray messages = new JSONArray(messagesString);
|
iceCandidates = new LinkedList<IceCandidate>();
|
||||||
for (int i = 0; i < messages.length(); ++i) {
|
String messagesString = roomJson.getString("messages");
|
||||||
String messageString = messages.getString(i);
|
JSONArray messages = new JSONArray(messagesString);
|
||||||
JSONObject message = new JSONObject(messageString);
|
for (int i = 0; i < messages.length(); ++i) {
|
||||||
String messageType = message.getString("type");
|
String messageString = messages.getString(i);
|
||||||
Log.d(TAG, "GAE->C #" + i + " : " + messageString);
|
JSONObject message = new JSONObject(messageString);
|
||||||
if (messageType.equals("offer")) {
|
String messageType = message.getString("type");
|
||||||
offerSdp = new SessionDescription(
|
Log.d(TAG, "GAE->C #" + i + " : " + messageString);
|
||||||
SessionDescription.Type.fromCanonicalForm(messageType),
|
if (messageType.equals("offer")) {
|
||||||
message.getString("sdp"));
|
offerSdp = new SessionDescription(
|
||||||
} else if (messageType.equals("candidate")) {
|
SessionDescription.Type.fromCanonicalForm(messageType),
|
||||||
IceCandidate candidate = new IceCandidate(
|
message.getString("sdp"));
|
||||||
message.getString("id"),
|
} else if (messageType.equals("candidate")) {
|
||||||
message.getInt("label"),
|
IceCandidate candidate = new IceCandidate(
|
||||||
message.getString("candidate"));
|
message.getString("id"),
|
||||||
iceCandidates.add(candidate);
|
message.getInt("label"),
|
||||||
} else {
|
message.getString("candidate"));
|
||||||
Log.e(TAG, "Unknown message: " + messageString);
|
iceCandidates.add(candidate);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unknown message: " + messageString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId);
|
||||||
|
Log.d(TAG, "Initiator: " + initiator);
|
||||||
|
Log.d(TAG, "Room url: " + roomUrl);
|
||||||
|
Log.d(TAG, "WSS url: " + wssUrl);
|
||||||
|
Log.d(TAG, "WSS POST url: " + wssPostUrl);
|
||||||
|
|
||||||
Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId);
|
LinkedList<PeerConnection.IceServer> iceServers =
|
||||||
Log.d(TAG, "Initiator: " + initiator);
|
iceServersFromPCConfigJSON(roomJson.getString("pc_config"));
|
||||||
Log.d(TAG, "Room url: " + roomUrl);
|
boolean isTurnPresent = false;
|
||||||
|
for (PeerConnection.IceServer server : iceServers) {
|
||||||
LinkedList<PeerConnection.IceServer> iceServers =
|
Log.d(TAG, "IceServer: " + server);
|
||||||
iceServersFromPCConfigJSON(roomJson.getString("pc_config"));
|
if (server.uri.startsWith("turn:")) {
|
||||||
boolean isTurnPresent = false;
|
isTurnPresent = true;
|
||||||
for (PeerConnection.IceServer server : iceServers) {
|
break;
|
||||||
Log.d(TAG, "IceServer: " + server);
|
}
|
||||||
if (server.uri.startsWith("turn:")) {
|
|
||||||
isTurnPresent = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
if (!isTurnPresent) {
|
||||||
if (!isTurnPresent) {
|
LinkedList<PeerConnection.IceServer> turnServers =
|
||||||
LinkedList<PeerConnection.IceServer> turnServers =
|
requestTurnServers(roomJson.getString("turn_url"));
|
||||||
requestTurnServers(roomJson.getString("turn_url"));
|
for (PeerConnection.IceServer turnServer : turnServers) {
|
||||||
for (PeerConnection.IceServer turnServer : turnServers) {
|
Log.d(TAG, "TurnServer: " + turnServer);
|
||||||
Log.d(TAG, "TurnServer: " + turnServer);
|
iceServers.add(turnServer);
|
||||||
iceServers.add(turnServer);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediaConstraints pcConstraints = constraintsFromJSON(
|
||||||
|
roomJson.getString("pc_constraints"));
|
||||||
|
addDTLSConstraintIfMissing(pcConstraints, loopback);
|
||||||
|
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,
|
||||||
|
roomUrl, roomId, clientId,
|
||||||
|
wssUrl, wssPostUrl,
|
||||||
|
offerSdp, iceCandidates);
|
||||||
|
events.onSignalingParametersReady(params);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
events.onSignalingParametersError(
|
||||||
|
"Room JSON parsing error: " + e.toString());
|
||||||
|
} catch (IOException e) {
|
||||||
|
events.onSignalingParametersError("Room IO error: " + e.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaConstraints pcConstraints = constraintsFromJSON(
|
|
||||||
roomJson.getString("pc_constraints"));
|
|
||||||
addDTLSConstraintIfMissing(pcConstraints, loopback);
|
|
||||||
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);
|
|
||||||
|
|
||||||
return new SignalingParameters(
|
|
||||||
iceServers, initiator,
|
|
||||||
pcConstraints, videoConstraints, audioConstraints,
|
|
||||||
roomUrl, roomId, clientId,
|
|
||||||
wssUrl, wssPostUrl,
|
|
||||||
offerSdp, iceCandidates);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mimic Chrome and set DtlsSrtpKeyAgreement to true if not set to false by
|
// Mimic Chrome and set DtlsSrtpKeyAgreement to true if not set to false by
|
||||||
@@ -245,7 +225,7 @@ public class RoomParametersFetcher
|
|||||||
private String getAVConstraints (
|
private String getAVConstraints (
|
||||||
String type, String mediaConstraintsString) throws JSONException {
|
String type, String mediaConstraintsString) throws JSONException {
|
||||||
JSONObject json = new JSONObject(mediaConstraintsString);
|
JSONObject json = new JSONObject(mediaConstraintsString);
|
||||||
// Tricksy handling of values that are allowed to be (boolean or
|
// Tricky handling of values that are allowed to be (boolean or
|
||||||
// MediaTrackConstraints) by the getUserMedia() spec. There are three
|
// MediaTrackConstraints) by the getUserMedia() spec. There are three
|
||||||
// cases below.
|
// cases below.
|
||||||
if (!json.has(type) || !json.optBoolean(type, true)) {
|
if (!json.has(type) || !json.optBoolean(type, true)) {
|
||||||
|
|||||||
@@ -26,34 +26,35 @@
|
|||||||
*/
|
*/
|
||||||
package org.appspot.apprtc;
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import de.tavendo.autobahn.WebSocket.WebSocketConnectionObserver;
|
||||||
import de.tavendo.autobahn.WebSocketConnection;
|
import de.tavendo.autobahn.WebSocketConnection;
|
||||||
import de.tavendo.autobahn.WebSocketException;
|
import de.tavendo.autobahn.WebSocketException;
|
||||||
import de.tavendo.autobahn.WebSocket.WebSocketConnectionObserver;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import org.appspot.apprtc.util.AsyncHttpURLConnection;
|
||||||
|
import org.appspot.apprtc.util.AsyncHttpURLConnection.AsyncHttpEvents;
|
||||||
|
import org.appspot.apprtc.util.LooperExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebSocket client implementation.
|
* WebSocket client implementation.
|
||||||
* For proper synchronization all methods should be called from UI thread
|
* All public methods should be called from a looper executor thread
|
||||||
* and all WebSocket events are delivered on UI thread as well.
|
* passed in constructor.
|
||||||
|
* All events are issued on the same thread.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class WebSocketChannelClient {
|
public class WebSocketChannelClient {
|
||||||
private final String TAG = "WSChannelRTCClient";
|
private static final String TAG = "WSChannelRTCClient";
|
||||||
|
private static final int CLOSE_TIMEOUT = 1000;
|
||||||
private final WebSocketChannelEvents events;
|
private final WebSocketChannelEvents events;
|
||||||
private final Handler uiHandler;
|
private final LooperExecutor executor;
|
||||||
private WebSocketConnection ws;
|
private WebSocketConnection ws;
|
||||||
private WebSocketObserver wsObserver;
|
private WebSocketObserver wsObserver;
|
||||||
private String wsServerUrl;
|
private String wsServerUrl;
|
||||||
@@ -61,10 +62,15 @@ public class WebSocketChannelClient {
|
|||||||
private String roomID;
|
private String roomID;
|
||||||
private String clientID;
|
private String clientID;
|
||||||
private WebSocketConnectionState state;
|
private WebSocketConnectionState state;
|
||||||
|
private final Object closeEventLock = new Object();
|
||||||
|
private boolean closeEvent;
|
||||||
// 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocketConnectionState is the names of possible WS connection states.
|
||||||
|
*/
|
||||||
public enum WebSocketConnectionState {
|
public enum WebSocketConnectionState {
|
||||||
NEW, CONNECTED, REGISTERED, CLOSED, ERROR
|
NEW, CONNECTED, REGISTERED, CLOSED, ERROR
|
||||||
};
|
};
|
||||||
@@ -80,9 +86,10 @@ public class WebSocketChannelClient {
|
|||||||
public void onWebSocketError(final String description);
|
public void onWebSocketError(final String description);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebSocketChannelClient(WebSocketChannelEvents events) {
|
public WebSocketChannelClient(LooperExecutor executor,
|
||||||
|
WebSocketChannelEvents events) {
|
||||||
|
this.executor = executor;
|
||||||
this.events = events;
|
this.events = events;
|
||||||
uiHandler = new Handler(Looper.getMainLooper());
|
|
||||||
roomID = null;
|
roomID = null;
|
||||||
clientID = null;
|
clientID = null;
|
||||||
wsSendQueue = new LinkedList<String>();
|
wsSendQueue = new LinkedList<String>();
|
||||||
@@ -93,18 +100,22 @@ public class WebSocketChannelClient {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(String wsUrl, String postUrl) {
|
public void connect(final String wsUrl, final String postUrl,
|
||||||
|
final String roomID, final String clientID) {
|
||||||
if (state != WebSocketConnectionState.NEW) {
|
if (state != WebSocketConnectionState.NEW) {
|
||||||
Log.e(TAG, "WebSocket is already connected.");
|
Log.e(TAG, "WebSocket is already connected.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Connecting WebSocket to: " + wsUrl + ". Post URL: " + postUrl);
|
wsServerUrl = wsUrl;
|
||||||
|
postServerUrl = postUrl;
|
||||||
|
this.roomID = roomID;
|
||||||
|
this.clientID = clientID;
|
||||||
|
closeEvent = false;
|
||||||
|
|
||||||
|
Log.d(TAG, "Connecting WebSocket to: " + wsUrl + ". Post URL: " + postUrl);
|
||||||
ws = new WebSocketConnection();
|
ws = new WebSocketConnection();
|
||||||
wsObserver = new WebSocketObserver();
|
wsObserver = new WebSocketObserver();
|
||||||
try {
|
try {
|
||||||
wsServerUrl = wsUrl;
|
|
||||||
postServerUrl = postUrl;
|
|
||||||
ws.connect(new URI(wsServerUrl), wsObserver);
|
ws.connect(new URI(wsServerUrl), wsObserver);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
reportError("URI error: " + e.getMessage());
|
reportError("URI error: " + e.getMessage());
|
||||||
@@ -113,20 +124,11 @@ public class WebSocketChannelClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClientParameters(String roomID, String clientID) {
|
|
||||||
this.roomID = roomID;
|
|
||||||
this.clientID = clientID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void register() {
|
public void register() {
|
||||||
if (state != WebSocketConnectionState.CONNECTED) {
|
if (state != WebSocketConnectionState.CONNECTED) {
|
||||||
Log.w(TAG, "WebSocket register() in state " + state);
|
Log.w(TAG, "WebSocket register() in state " + state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (roomID == null || clientID == null) {
|
|
||||||
Log.w(TAG, "Call WebSocket register() without setting client ID");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
try {
|
try {
|
||||||
json.put("cmd", "register");
|
json.put("cmd", "register");
|
||||||
@@ -187,7 +189,7 @@ public class WebSocketChannelClient {
|
|||||||
sendWSSMessage("POST", message);
|
sendWSSMessage("POST", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disconnect() {
|
public void disconnect(boolean waitForComplete) {
|
||||||
Log.d(TAG, "Disonnect WebSocket. State: " + state);
|
Log.d(TAG, "Disonnect WebSocket. State: " + state);
|
||||||
if (state == WebSocketConnectionState.REGISTERED) {
|
if (state == WebSocketConnectionState.REGISTERED) {
|
||||||
send("{\"type\": \"bye\"}");
|
send("{\"type\": \"bye\"}");
|
||||||
@@ -202,12 +204,28 @@ public class WebSocketChannelClient {
|
|||||||
sendWSSMessage("DELETE", "");
|
sendWSSMessage("DELETE", "");
|
||||||
|
|
||||||
state = WebSocketConnectionState.CLOSED;
|
state = WebSocketConnectionState.CLOSED;
|
||||||
|
|
||||||
|
// Wait for websocket close event to prevent websocket library from
|
||||||
|
// sending any pending messages to deleted looper thread.
|
||||||
|
if (waitForComplete) {
|
||||||
|
synchronized (closeEventLock) {
|
||||||
|
if (!closeEvent) {
|
||||||
|
try {
|
||||||
|
closeEventLock.wait(CLOSE_TIMEOUT);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Wait error: " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "Disonnecting WebSocket done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reportError(final String errorMessage) {
|
private void reportError(final String errorMessage) {
|
||||||
Log.e(TAG, errorMessage);
|
Log.e(TAG, errorMessage);
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (state != WebSocketConnectionState.ERROR) {
|
if (state != WebSocketConnectionState.ERROR) {
|
||||||
state = WebSocketConnectionState.ERROR;
|
state = WebSocketConnectionState.ERROR;
|
||||||
@@ -217,60 +235,30 @@ public class WebSocketChannelClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WsHttpMessage {
|
|
||||||
WsHttpMessage(String method, String message) {
|
|
||||||
this.method = method;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
public final String method;
|
|
||||||
public final String message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asynchronously send POST/DELETE to WebSocket server.
|
// Asynchronously send POST/DELETE to WebSocket server.
|
||||||
private void sendWSSMessage(String method, String message) {
|
private void sendWSSMessage(final String method, final String message) {
|
||||||
final WsHttpMessage wsHttpMessage = new WsHttpMessage(method, message);
|
String postUrl = postServerUrl + "/" + roomID + "/" + clientID;
|
||||||
Runnable runAsync = new Runnable() {
|
Log.d(TAG, "WS " + method + " : " + postUrl + " : " + message);
|
||||||
public void run() {
|
AsyncHttpURLConnection httpConnection = new AsyncHttpURLConnection(
|
||||||
sendWSSMessageAsync(wsHttpMessage);
|
method, postUrl, message, new AsyncHttpEvents() {
|
||||||
}
|
@Override
|
||||||
};
|
public void OnHttpError(String errorMessage) {
|
||||||
new Thread(runAsync).start();
|
reportError("WS " + method + " error: " + errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendWSSMessageAsync(WsHttpMessage wsHttpMessage) {
|
@Override
|
||||||
if (roomID == null || clientID == null) {
|
public void OnHttpComplete(String response) {
|
||||||
return;
|
}
|
||||||
}
|
});
|
||||||
try {
|
httpConnection.send();
|
||||||
// Send POST or DELETE request.
|
|
||||||
String postUrl = postServerUrl + "/" + roomID + "/" + clientID;
|
|
||||||
Log.d(TAG, "WS " + wsHttpMessage.method + " : " + postUrl + " : "
|
|
||||||
+ wsHttpMessage.message);
|
|
||||||
HttpURLConnection connection =
|
|
||||||
(HttpURLConnection) new URL(postUrl).openConnection();
|
|
||||||
connection.setRequestProperty(
|
|
||||||
"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"));
|
|
||||||
}
|
|
||||||
int responseCode = connection.getResponseCode();
|
|
||||||
if (responseCode != 200) {
|
|
||||||
reportError("Non-200 response to " + wsHttpMessage.method + " : "
|
|
||||||
+ connection.getHeaderField(null));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
reportError("WS POST error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WebSocketObserver implements WebSocketConnectionObserver {
|
private class WebSocketObserver implements WebSocketConnectionObserver {
|
||||||
@Override
|
@Override
|
||||||
public void onOpen() {
|
public void onOpen() {
|
||||||
Log.d(TAG, "WebSocket connection opened to: " + wsServerUrl);
|
Log.d(TAG, "WebSocket connection opened to: " + wsServerUrl);
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
state = WebSocketConnectionState.CONNECTED;
|
state = WebSocketConnectionState.CONNECTED;
|
||||||
events.onWebSocketOpen();
|
events.onWebSocketOpen();
|
||||||
@@ -281,8 +269,13 @@ public class WebSocketChannelClient {
|
|||||||
@Override
|
@Override
|
||||||
public void onClose(WebSocketCloseNotification code, String reason) {
|
public void onClose(WebSocketCloseNotification code, String reason) {
|
||||||
Log.d(TAG, "WebSocket connection closed. Code: " + code
|
Log.d(TAG, "WebSocket connection closed. Code: " + code
|
||||||
+ ". Reason: " + reason);
|
+ ". Reason: " + reason + ". State: " + state);
|
||||||
uiHandler.post(new Runnable() {
|
synchronized (closeEventLock) {
|
||||||
|
closeEvent = true;
|
||||||
|
closeEventLock.notify();
|
||||||
|
}
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (state != WebSocketConnectionState.CLOSED) {
|
if (state != WebSocketConnectionState.CLOSED) {
|
||||||
state = WebSocketConnectionState.CLOSED;
|
state = WebSocketConnectionState.CLOSED;
|
||||||
@@ -296,7 +289,8 @@ public class WebSocketChannelClient {
|
|||||||
public void onTextMessage(String payload) {
|
public void onTextMessage(String payload) {
|
||||||
Log.d(TAG, "WSS->C: " + payload);
|
Log.d(TAG, "WSS->C: " + payload);
|
||||||
final String message = payload;
|
final String message = payload;
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (state == WebSocketConnectionState.CONNECTED
|
if (state == WebSocketConnectionState.CONNECTED
|
||||||
|| state == WebSocketConnectionState.REGISTERED) {
|
|| state == WebSocketConnectionState.REGISTERED) {
|
||||||
|
|||||||
@@ -26,17 +26,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.appspot.apprtc;
|
package org.appspot.apprtc;
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
import org.appspot.apprtc.util.AsyncHttpURLConnection;
|
||||||
import java.io.InputStream;
|
import org.appspot.apprtc.util.AsyncHttpURLConnection.AsyncHttpEvents;
|
||||||
import java.io.OutputStream;
|
import org.appspot.apprtc.util.LooperExecutor;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents;
|
import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents;
|
||||||
import org.appspot.apprtc.WebSocketChannelClient.WebSocketChannelEvents;
|
import org.appspot.apprtc.WebSocketChannelClient.WebSocketChannelEvents;
|
||||||
import org.appspot.apprtc.WebSocketChannelClient.WebSocketConnectionState;
|
import org.appspot.apprtc.WebSocketChannelClient.WebSocketConnectionState;
|
||||||
@@ -56,7 +50,7 @@ import org.webrtc.SessionDescription;
|
|||||||
* be sent after WebSocket connection is established.
|
* be sent after WebSocket connection is established.
|
||||||
*/
|
*/
|
||||||
public class WebSocketRTCClient implements AppRTCClient,
|
public class WebSocketRTCClient implements AppRTCClient,
|
||||||
RoomParametersFetcherEvents, WebSocketChannelEvents {
|
WebSocketChannelEvents {
|
||||||
private static final String TAG = "WSRTCClient";
|
private static final String TAG = "WSRTCClient";
|
||||||
|
|
||||||
private enum ConnectionState {
|
private enum ConnectionState {
|
||||||
@@ -65,7 +59,7 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
private enum MessageType {
|
private enum MessageType {
|
||||||
MESSAGE, BYE
|
MESSAGE, BYE
|
||||||
};
|
};
|
||||||
private final Handler uiHandler;
|
private final LooperExecutor executor;
|
||||||
private boolean loopback;
|
private boolean loopback;
|
||||||
private boolean initiator;
|
private boolean initiator;
|
||||||
private SignalingEvents events;
|
private SignalingEvents events;
|
||||||
@@ -77,14 +71,81 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
|
|
||||||
public WebSocketRTCClient(SignalingEvents events) {
|
public WebSocketRTCClient(SignalingEvents events) {
|
||||||
this.events = events;
|
this.events = events;
|
||||||
uiHandler = new Handler(Looper.getMainLooper());
|
executor = new LooperExecutor();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
// RoomConnectionEvents interface implementation.
|
// AppRTCClient interface implementation.
|
||||||
// All events are called on UI thread.
|
// Asynchronously connect to an AppRTC room URL, e.g.
|
||||||
|
// https://apprtc.appspot.com/register/<room>, retrieve room parameters
|
||||||
|
// and connect to WebSocket server.
|
||||||
@Override
|
@Override
|
||||||
public void onSignalingParametersReady(final SignalingParameters params) {
|
public void connectToRoom(final String url, final boolean loopback) {
|
||||||
|
executor.requestStart();
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
connectToRoomInternal(url, loopback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnectFromRoom() {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
disconnectFromRoomInternal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
executor.requestStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connects to room - function runs on a local looper thread.
|
||||||
|
private void connectToRoomInternal(String url, boolean loopback) {
|
||||||
|
Log.d(TAG, "Connect to room: " + url);
|
||||||
|
this.loopback = loopback;
|
||||||
|
roomState = ConnectionState.NEW;
|
||||||
|
// Create WebSocket client.
|
||||||
|
wsClient = new WebSocketChannelClient(executor, this);
|
||||||
|
// Get room parameters.
|
||||||
|
fetcher = new RoomParametersFetcher(loopback, url,
|
||||||
|
new RoomParametersFetcherEvents() {
|
||||||
|
@Override
|
||||||
|
public void onSignalingParametersReady(
|
||||||
|
final SignalingParameters params) {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
signalingParametersReady(params);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSignalingParametersError(String description) {
|
||||||
|
reportError(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect from room and send bye messages - runs on a local looper thread.
|
||||||
|
private void disconnectFromRoomInternal() {
|
||||||
|
Log.d(TAG, "Disconnect. Room state: " + roomState);
|
||||||
|
if (roomState == ConnectionState.CONNECTED) {
|
||||||
|
Log.d(TAG, "Closing room.");
|
||||||
|
sendPostMessage(MessageType.BYE, byeMessageUrl, "");
|
||||||
|
}
|
||||||
|
roomState = ConnectionState.CLOSED;
|
||||||
|
if (wsClient != null) {
|
||||||
|
wsClient.disconnect(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback issued when room parameters are extracted. Runs on local
|
||||||
|
// looper thread.
|
||||||
|
private void signalingParametersReady(final SignalingParameters params) {
|
||||||
Log.d(TAG, "Room connection completed.");
|
Log.d(TAG, "Room connection completed.");
|
||||||
if (loopback && (!params.initiator || params.offerSdp != null)) {
|
if (loopback && (!params.initiator || params.offerSdp != null)) {
|
||||||
reportError("Loopback room is busy.");
|
reportError("Loopback room is busy.");
|
||||||
@@ -100,15 +161,16 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
+ params.roomId + "/" + params.clientId;
|
+ params.roomId + "/" + params.clientId;
|
||||||
roomState = ConnectionState.CONNECTED;
|
roomState = ConnectionState.CONNECTED;
|
||||||
|
|
||||||
// Connect to WebSocket server.
|
|
||||||
wsClient.connect(params.wssUrl, params.wssPostUrl);
|
|
||||||
wsClient.setClientParameters(params.roomId, params.clientId);
|
|
||||||
|
|
||||||
// Fire connection and signaling parameters events.
|
// Fire connection and signaling parameters events.
|
||||||
events.onConnectedToRoom(params);
|
events.onConnectedToRoom(params);
|
||||||
|
|
||||||
|
// Connect to WebSocket server.
|
||||||
|
wsClient.connect(
|
||||||
|
params.wssUrl, params.wssPostUrl, params.roomId, params.clientId);
|
||||||
|
|
||||||
|
// For call receiver get sdp offer and ice candidates
|
||||||
|
// from room parameters and fire corresponding events.
|
||||||
if (!params.initiator) {
|
if (!params.initiator) {
|
||||||
// For call receiver get sdp offer and ice candidates
|
|
||||||
// from room parameters.
|
|
||||||
if (params.offerSdp != null) {
|
if (params.offerSdp != null) {
|
||||||
events.onRemoteDescription(params.offerSdp);
|
events.onRemoteDescription(params.offerSdp);
|
||||||
}
|
}
|
||||||
@@ -120,14 +182,90 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send local offer SDP to the other participant.
|
||||||
@Override
|
@Override
|
||||||
public void onSignalingParametersError(final String description) {
|
public void sendOfferSdp(final SessionDescription sdp) {
|
||||||
reportError("Room connection error: " + description);
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (roomState != ConnectionState.CONNECTED) {
|
||||||
|
reportError("Sending offer SDP in non connected state.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
jsonPut(json, "sdp", sdp.description);
|
||||||
|
jsonPut(json, "type", "offer");
|
||||||
|
sendPostMessage(MessageType.MESSAGE, postMessageUrl, json.toString());
|
||||||
|
if (loopback) {
|
||||||
|
// In loopback mode rename this offer to answer and route it back.
|
||||||
|
SessionDescription sdpAnswer = new SessionDescription(
|
||||||
|
SessionDescription.Type.fromCanonicalForm("answer"),
|
||||||
|
sdp.description);
|
||||||
|
events.onRemoteDescription(sdpAnswer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send local answer SDP to the other participant.
|
||||||
|
@Override
|
||||||
|
public void sendAnswerSdp(final SessionDescription sdp) {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (loopback) {
|
||||||
|
Log.e(TAG, "Sending answer in loopback mode.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
||||||
|
reportError("Sending answer SDP in non registered state.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
jsonPut(json, "sdp", sdp.description);
|
||||||
|
jsonPut(json, "type", "answer");
|
||||||
|
wsClient.send(json.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send Ice candidate to the other participant.
|
||||||
|
@Override
|
||||||
|
public void sendLocalIceCandidate(final IceCandidate candidate) {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
jsonPut(json, "type", "candidate");
|
||||||
|
jsonPut(json, "label", candidate.sdpMLineIndex);
|
||||||
|
jsonPut(json, "id", candidate.sdpMid);
|
||||||
|
jsonPut(json, "candidate", candidate.sdp);
|
||||||
|
if (initiator) {
|
||||||
|
// Call initiator sends ice candidates to GAE server.
|
||||||
|
if (roomState != ConnectionState.CONNECTED) {
|
||||||
|
reportError("Sending ICE candidate in non connected state.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendPostMessage(MessageType.MESSAGE, postMessageUrl, json.toString());
|
||||||
|
if (loopback) {
|
||||||
|
events.onRemoteIceCandidate(candidate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Call receiver sends ice candidates to websocket server.
|
||||||
|
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
||||||
|
reportError("Sending ICE candidate in non registered state.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wsClient.send(json.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
// WebSocketChannelEvents interface implementation.
|
// WebSocketChannelEvents interface implementation.
|
||||||
// All events are called on UI thread.
|
// All events are called by WebSocketChannelClient on a local looper thread
|
||||||
|
// (passed to WebSocket client constructor).
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketOpen() {
|
public void onWebSocketOpen() {
|
||||||
Log.d(TAG, "Websocket connection completed. Registering...");
|
Log.d(TAG, "Websocket connection completed. Registering...");
|
||||||
@@ -198,108 +336,12 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
reportError("WebSocket error: " + description);
|
reportError("WebSocket error: " + description);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// AppRTCClient interface implementation.
|
|
||||||
// Asynchronously connect to an AppRTC room URL, e.g.
|
|
||||||
// https://apprtc.appspot.com/register/<room>, retrieve room parameters
|
|
||||||
// and connect to WebSocket server.
|
|
||||||
@Override
|
|
||||||
public void connectToRoom(String url, boolean loopback) {
|
|
||||||
this.loopback = loopback;
|
|
||||||
// Create WebSocket client.
|
|
||||||
wsClient = new WebSocketChannelClient(this);
|
|
||||||
// Get room parameters.
|
|
||||||
roomState = ConnectionState.NEW;
|
|
||||||
fetcher = new RoomParametersFetcher(this, loopback);
|
|
||||||
fetcher.execute(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disconnect() {
|
|
||||||
Log.d(TAG, "Disconnect. Room state: " + roomState);
|
|
||||||
if (roomState == ConnectionState.CONNECTED) {
|
|
||||||
Log.d(TAG, "Closing room.");
|
|
||||||
sendPostMessage(MessageType.BYE, byeMessageUrl, "");
|
|
||||||
}
|
|
||||||
roomState = ConnectionState.CLOSED;
|
|
||||||
if (wsClient != null) {
|
|
||||||
wsClient.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send local SDP (offer or answer, depending on role) to the
|
|
||||||
// other participant. Note that it is important to send the output of
|
|
||||||
// create{Offer,Answer} and not merely the current value of
|
|
||||||
// getLocalDescription() because the latter may include ICE candidates that
|
|
||||||
// we might want to filter elsewhere.
|
|
||||||
@Override
|
|
||||||
public void sendOfferSdp(final SessionDescription sdp) {
|
|
||||||
if (roomState != ConnectionState.CONNECTED) {
|
|
||||||
reportError("Sending offer SDP in non connected state.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
jsonPut(json, "sdp", sdp.description);
|
|
||||||
jsonPut(json, "type", "offer");
|
|
||||||
sendPostMessage(MessageType.MESSAGE, postMessageUrl, json.toString());
|
|
||||||
if (loopback) {
|
|
||||||
// In loopback mode rename this offer to answer and route it back.
|
|
||||||
SessionDescription sdpAnswer = new SessionDescription(
|
|
||||||
SessionDescription.Type.fromCanonicalForm("answer"),
|
|
||||||
sdp.description);
|
|
||||||
events.onRemoteDescription(sdpAnswer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendAnswerSdp(final SessionDescription sdp) {
|
|
||||||
if (loopback) {
|
|
||||||
Log.e(TAG, "Sending answer in loopback mode.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
|
||||||
reportError("Sending answer SDP in non registered state.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
jsonPut(json, "sdp", sdp.description);
|
|
||||||
jsonPut(json, "type", "answer");
|
|
||||||
wsClient.send(json.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send Ice candidate to the other participant.
|
|
||||||
@Override
|
|
||||||
public void sendLocalIceCandidate(final IceCandidate candidate) {
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
jsonPut(json, "type", "candidate");
|
|
||||||
jsonPut(json, "label", candidate.sdpMLineIndex);
|
|
||||||
jsonPut(json, "id", candidate.sdpMid);
|
|
||||||
jsonPut(json, "candidate", candidate.sdp);
|
|
||||||
if (initiator) {
|
|
||||||
// Call initiator sends ice candidates to GAE server.
|
|
||||||
if (roomState != ConnectionState.CONNECTED) {
|
|
||||||
reportError("Sending ICE candidate in non connected state.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendPostMessage(MessageType.MESSAGE, postMessageUrl, json.toString());
|
|
||||||
if (loopback) {
|
|
||||||
events.onRemoteIceCandidate(candidate);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Call receiver sends ice candidates to websocket server.
|
|
||||||
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
|
||||||
reportError("Sending ICE candidate in non registered state.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wsClient.send(json.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
// Helper functions.
|
// Helper functions.
|
||||||
private void reportError(final String errorMessage) {
|
private void reportError(final String errorMessage) {
|
||||||
Log.e(TAG, errorMessage);
|
Log.e(TAG, errorMessage);
|
||||||
uiHandler.post(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (roomState != ConnectionState.ERROR) {
|
if (roomState != ConnectionState.ERROR) {
|
||||||
roomState = ConnectionState.ERROR;
|
roomState = ConnectionState.ERROR;
|
||||||
@@ -318,81 +360,36 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PostMessage {
|
// Send SDP or ICE candidate to a room server.
|
||||||
PostMessage(MessageType type, String postUrl, String message) {
|
private void sendPostMessage(
|
||||||
this.messageType = type;
|
final MessageType messageType, final String url, final String message) {
|
||||||
this.postUrl = postUrl;
|
if (messageType == MessageType.BYE) {
|
||||||
this.message = message;
|
Log.d(TAG, "C->GAE: " + url);
|
||||||
}
|
|
||||||
public final MessageType messageType;
|
|
||||||
public final String postUrl;
|
|
||||||
public final String message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue a message for sending to the room and send it if already connected.
|
|
||||||
private synchronized void sendPostMessage(
|
|
||||||
MessageType messageType, String url, String message) {
|
|
||||||
final PostMessage postMessage = new PostMessage(messageType, url, message);
|
|
||||||
Runnable runDrain = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
sendPostMessageAsync(postMessage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
new Thread(runDrain).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send all queued POST messages to app engine server.
|
|
||||||
private void sendPostMessageAsync(PostMessage postMessage) {
|
|
||||||
if (postMessage.messageType == MessageType.BYE) {
|
|
||||||
Log.d(TAG, "C->GAE: " + postMessage.postUrl);
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "C->GAE: " + postMessage.message);
|
Log.d(TAG, "C->GAE: " + message);
|
||||||
}
|
}
|
||||||
try {
|
AsyncHttpURLConnection httpConnection = new AsyncHttpURLConnection(
|
||||||
// Get connection.
|
"POST", url, message, new AsyncHttpEvents() {
|
||||||
HttpURLConnection connection =
|
@Override
|
||||||
(HttpURLConnection) new URL(postMessage.postUrl).openConnection();
|
public void OnHttpError(String errorMessage) {
|
||||||
byte[] postData = postMessage.message.getBytes("UTF-8");
|
reportError("GAE POST error: " + errorMessage);
|
||||||
connection.setUseCaches(false);
|
|
||||||
connection.setDoOutput(true);
|
|
||||||
connection.setDoInput(true);
|
|
||||||
connection.setRequestMethod("POST");
|
|
||||||
connection.setFixedLengthStreamingMode(postData.length);
|
|
||||||
connection.setRequestProperty(
|
|
||||||
"content-type", "text/plain; charset=utf-8");
|
|
||||||
|
|
||||||
// Send POST request.
|
|
||||||
OutputStream outStream = connection.getOutputStream();
|
|
||||||
outStream.write(postData);
|
|
||||||
outStream.close();
|
|
||||||
|
|
||||||
// Get response.
|
|
||||||
int responseCode = connection.getResponseCode();
|
|
||||||
if (responseCode != 200) {
|
|
||||||
reportError("Non-200 response to POST: "
|
|
||||||
+ connection.getHeaderField(null));
|
|
||||||
}
|
|
||||||
InputStream responseStream = connection.getInputStream();
|
|
||||||
String response = drainStream(responseStream);
|
|
||||||
responseStream.close();
|
|
||||||
if (postMessage.messageType == MessageType.MESSAGE) {
|
|
||||||
JSONObject roomJson = new JSONObject(response);
|
|
||||||
String result = roomJson.getString("result");
|
|
||||||
if (!result.equals("SUCCESS")) {
|
|
||||||
reportError("Room POST error: " + result);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
reportError("GAE POST error: " + e.getMessage());
|
|
||||||
} catch (JSONException e) {
|
|
||||||
reportError("GAE POST JSON error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the contents of an InputStream as a String.
|
@Override
|
||||||
private String drainStream(InputStream in) {
|
public void OnHttpComplete(String response) {
|
||||||
Scanner s = new Scanner(in).useDelimiter("\\A");
|
if (messageType == MessageType.MESSAGE) {
|
||||||
return s.hasNext() ? s.next() : "";
|
try {
|
||||||
|
JSONObject roomJson = new JSONObject(response);
|
||||||
|
String result = roomJson.getString("result");
|
||||||
|
if (!result.equals("SUCCESS")) {
|
||||||
|
reportError("GAE POST error: " + result);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
reportError("GAE POST JSON error: " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
httpConnection.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronious http requests implementation.
|
||||||
|
*/
|
||||||
|
public class AsyncHttpURLConnection {
|
||||||
|
private static final int HTTP_TIMEOUT_MS = 5000;
|
||||||
|
private final String method;
|
||||||
|
private final String url;
|
||||||
|
private final String message;
|
||||||
|
private final AsyncHttpEvents events;
|
||||||
|
|
||||||
|
public interface AsyncHttpEvents {
|
||||||
|
public void OnHttpError(String errorMessage);
|
||||||
|
public void OnHttpComplete(String response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncHttpURLConnection(String method, String url, String message,
|
||||||
|
AsyncHttpEvents events) {
|
||||||
|
this.method = method;
|
||||||
|
this.url = url;
|
||||||
|
this.message = message;
|
||||||
|
this.events = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send() {
|
||||||
|
Runnable runHttp = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
sendHttpMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new Thread(runHttp).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendHttpMessage() {
|
||||||
|
try {
|
||||||
|
HttpURLConnection connection =
|
||||||
|
(HttpURLConnection) new URL(url).openConnection();
|
||||||
|
byte[] postData = new byte[0];
|
||||||
|
if (message != null) {
|
||||||
|
postData = message.getBytes("UTF-8");
|
||||||
|
}
|
||||||
|
connection.setRequestMethod(method);
|
||||||
|
connection.setUseCaches(false);
|
||||||
|
connection.setDoInput(true);
|
||||||
|
connection.setConnectTimeout(HTTP_TIMEOUT_MS);
|
||||||
|
connection.setReadTimeout(HTTP_TIMEOUT_MS);
|
||||||
|
boolean doOutput = false;
|
||||||
|
if (method.equals("POST")) {
|
||||||
|
doOutput = true;
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setFixedLengthStreamingMode(postData.length);
|
||||||
|
}
|
||||||
|
connection.setRequestProperty(
|
||||||
|
"content-type", "text/plain; charset=utf-8");
|
||||||
|
|
||||||
|
// Send POST request.
|
||||||
|
if (doOutput && postData.length > 0) {
|
||||||
|
OutputStream outStream = connection.getOutputStream();
|
||||||
|
outStream.write(postData);
|
||||||
|
outStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get response.
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
if (responseCode != 200) {
|
||||||
|
events.OnHttpError("Non-200 response to " + method + " to URL: "
|
||||||
|
+ url + " : " + connection.getHeaderField(null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InputStream responseStream = connection.getInputStream();
|
||||||
|
String response = drainStream(responseStream);
|
||||||
|
responseStream.close();
|
||||||
|
events.OnHttpComplete(response);
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
events.OnHttpError("HTTP " + method + " to " + url + " timeout");
|
||||||
|
} catch (IOException e) {
|
||||||
|
events.OnHttpError("HTTP " + method + " to " + url + " error: "
|
||||||
|
+ e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the contents of an InputStream as a String.
|
||||||
|
private static String drainStream(InputStream in) {
|
||||||
|
Scanner s = new Scanner(in).useDelimiter("\\A");
|
||||||
|
return s.hasNext() ? s.next() : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looper based executor class.
|
||||||
|
*/
|
||||||
|
public class LooperExecutor extends Thread implements Executor {
|
||||||
|
private static final String TAG = "LooperExecutor";
|
||||||
|
// Object used to signal that looper thread has started and Handler instance
|
||||||
|
// associated with looper thread has been allocated.
|
||||||
|
private final Object looperStartedEvent = new Object();
|
||||||
|
private Handler handler = null;
|
||||||
|
private boolean running = false;
|
||||||
|
private long threadId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Looper.prepare();
|
||||||
|
synchronized (looperStartedEvent) {
|
||||||
|
Log.d(TAG, "Looper thread started.");
|
||||||
|
handler = new Handler();
|
||||||
|
threadId = Thread.currentThread().getId();
|
||||||
|
looperStartedEvent.notify();
|
||||||
|
}
|
||||||
|
Looper.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void requestStart() {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
running = true;
|
||||||
|
handler = null;
|
||||||
|
start();
|
||||||
|
// Wait for Hander allocation.
|
||||||
|
synchronized (looperStartedEvent) {
|
||||||
|
while (handler == null) {
|
||||||
|
try {
|
||||||
|
looperStartedEvent.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Can not start looper thread");
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void requestStop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
running = false;
|
||||||
|
handler.post( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Looper.myLooper().quitSafely();
|
||||||
|
Log.d(TAG, "Looper thread finished.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void execute(final Runnable runnable) {
|
||||||
|
if (!running) {
|
||||||
|
Log.w(TAG, "Running looper executor without calling requestStart()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Thread.currentThread().getId() == threadId) {
|
||||||
|
runnable.run();
|
||||||
|
} else {
|
||||||
|
handler.post(runnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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.test;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.appspot.apprtc.util.LooperExecutor;
|
||||||
|
|
||||||
|
import android.test.InstrumentationTestCase;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class LooperExecutorTest extends InstrumentationTestCase {
|
||||||
|
private static final String TAG = "LooperTest";
|
||||||
|
private static final int WAIT_TIMEOUT = 5000;
|
||||||
|
|
||||||
|
public void testLooperExecutor() throws InterruptedException {
|
||||||
|
Log.d(TAG, "testLooperExecutor");
|
||||||
|
final int counter[] = new int[1];
|
||||||
|
final int expectedCounter = 10;
|
||||||
|
final CountDownLatch looperDone = new CountDownLatch(1);
|
||||||
|
|
||||||
|
Runnable counterIncRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
counter[0]++;
|
||||||
|
Log.d(TAG, "Run " + counter[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
LooperExecutor executor = new LooperExecutor();
|
||||||
|
|
||||||
|
// Try to execute a counter increment task before starting an executor.
|
||||||
|
executor.execute(counterIncRunnable);
|
||||||
|
|
||||||
|
// Start the executor and run expected amount of counter increment task.
|
||||||
|
executor.requestStart();
|
||||||
|
for (int i = 0; i < expectedCounter; i++) {
|
||||||
|
executor.execute(counterIncRunnable);
|
||||||
|
}
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
looperDone.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
executor.requestStop();
|
||||||
|
|
||||||
|
// Try to execute a task after stopping the executor.
|
||||||
|
executor.execute(counterIncRunnable);
|
||||||
|
|
||||||
|
// Wait for final looper task and make sure the counter increment task
|
||||||
|
// is executed expected amount of times.
|
||||||
|
looperDone.await(WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
|
assertTrue (looperDone.getCount() == 0);
|
||||||
|
assertTrue (counter[0] == expectedCounter);
|
||||||
|
|
||||||
|
Log.d(TAG, "testLooperExecutor done");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,10 +35,10 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
||||||
import org.appspot.apprtc.PeerConnectionClient;
|
import org.appspot.apprtc.PeerConnectionClient;
|
||||||
import org.appspot.apprtc.PeerConnectionClient.PeerConnectionEvents;
|
import org.appspot.apprtc.PeerConnectionClient.PeerConnectionEvents;
|
||||||
|
import org.appspot.apprtc.util.LooperExecutor;
|
||||||
import org.webrtc.IceCandidate;
|
import org.webrtc.IceCandidate;
|
||||||
import org.webrtc.MediaConstraints;
|
import org.webrtc.MediaConstraints;
|
||||||
import org.webrtc.PeerConnection;
|
import org.webrtc.PeerConnection;
|
||||||
import org.webrtc.PeerConnectionFactory;
|
|
||||||
import org.webrtc.SessionDescription;
|
import org.webrtc.SessionDescription;
|
||||||
import org.webrtc.VideoRenderer;
|
import org.webrtc.VideoRenderer;
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ public class PeerConnectionClientTest extends InstrumentationTestCase
|
|||||||
implements PeerConnectionEvents {
|
implements PeerConnectionEvents {
|
||||||
private static final String TAG = "RTCClientTest";
|
private static final String TAG = "RTCClientTest";
|
||||||
private static final String STUN_SERVER = "stun:stun.l.google.com:19302";
|
private static final String STUN_SERVER = "stun:stun.l.google.com:19302";
|
||||||
private static final int WAIT_TIMEOUT = 3000;
|
private static final int WAIT_TIMEOUT = 5000;
|
||||||
private static final int EXPECTED_VIDEO_FRAMES = 15;
|
private static final int EXPECTED_VIDEO_FRAMES = 15;
|
||||||
|
|
||||||
private volatile PeerConnectionClient pcClient;
|
private volatile PeerConnectionClient pcClient;
|
||||||
@@ -222,9 +222,6 @@ public class PeerConnectionClientTest extends InstrumentationTestCase
|
|||||||
isClosed = false;
|
isClosed = false;
|
||||||
isIceConnected = false;
|
isIceConnected = false;
|
||||||
loopback = false;
|
loopback = false;
|
||||||
Log.d(TAG, "initializeAndroidGlobals");
|
|
||||||
assertTrue(PeerConnectionFactory.initializeAndroidGlobals(
|
|
||||||
getInstrumentation().getContext(), true, true, true, null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInitiatorCreation() throws InterruptedException {
|
public void testInitiatorCreation() throws InterruptedException {
|
||||||
@@ -233,8 +230,11 @@ public class PeerConnectionClientTest extends InstrumentationTestCase
|
|||||||
MockRenderer remoteRender = new MockRenderer(EXPECTED_VIDEO_FRAMES);
|
MockRenderer remoteRender = new MockRenderer(EXPECTED_VIDEO_FRAMES);
|
||||||
SignalingParameters signalingParameters = getTestSignalingParameters();
|
SignalingParameters signalingParameters = getTestSignalingParameters();
|
||||||
|
|
||||||
pcClient = new PeerConnectionClient(
|
pcClient = new PeerConnectionClient();
|
||||||
localRender, remoteRender, signalingParameters, this, 1000);
|
pcClient.createPeerConnectionFactory(
|
||||||
|
getInstrumentation().getContext(), true, null, this);
|
||||||
|
pcClient.createPeerConnection(
|
||||||
|
localRender, remoteRender, signalingParameters, 1000);
|
||||||
pcClient.createOffer();
|
pcClient.createOffer();
|
||||||
|
|
||||||
// Wait for local SDP and ice candidates set events.
|
// Wait for local SDP and ice candidates set events.
|
||||||
@@ -258,8 +258,12 @@ public class PeerConnectionClientTest extends InstrumentationTestCase
|
|||||||
MockRenderer remoteRender = new MockRenderer(EXPECTED_VIDEO_FRAMES);
|
MockRenderer remoteRender = new MockRenderer(EXPECTED_VIDEO_FRAMES);
|
||||||
SignalingParameters signalingParameters = getTestSignalingParameters();
|
SignalingParameters signalingParameters = getTestSignalingParameters();
|
||||||
loopback = true;
|
loopback = true;
|
||||||
pcClient = new PeerConnectionClient(
|
|
||||||
localRender, remoteRender, signalingParameters, this, 1000);
|
pcClient = new PeerConnectionClient();
|
||||||
|
pcClient.createPeerConnectionFactory(
|
||||||
|
getInstrumentation().getContext(), true, null, this);
|
||||||
|
pcClient.createPeerConnection(
|
||||||
|
localRender, remoteRender, signalingParameters, 1000);
|
||||||
pcClient.createOffer();
|
pcClient.createOffer();
|
||||||
|
|
||||||
// Wait for local SDP, rename it to answer and set as remote SDP.
|
// Wait for local SDP, rename it to answer and set as remote SDP.
|
||||||
@@ -284,5 +288,4 @@ public class PeerConnectionClientTest extends InstrumentationTestCase
|
|||||||
assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
|
assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
|
||||||
Log.d(TAG, "testLoopback Done.");
|
Log.d(TAG, "testLoopback Done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,6 @@
|
|||||||
'examples/android/AndroidManifest.xml',
|
'examples/android/AndroidManifest.xml',
|
||||||
'examples/android/README',
|
'examples/android/README',
|
||||||
'examples/android/ant.properties',
|
'examples/android/ant.properties',
|
||||||
'examples/android/assets/channel.html',
|
|
||||||
'examples/android/third_party/autobanh/autobanh.jar',
|
'examples/android/third_party/autobanh/autobanh.jar',
|
||||||
'examples/android/build.xml',
|
'examples/android/build.xml',
|
||||||
'examples/android/jni/Android.mk',
|
'examples/android/jni/Android.mk',
|
||||||
@@ -362,9 +361,11 @@
|
|||||||
'examples/android/src/org/appspot/apprtc/SettingsActivity.java',
|
'examples/android/src/org/appspot/apprtc/SettingsActivity.java',
|
||||||
'examples/android/src/org/appspot/apprtc/SettingsFragment.java',
|
'examples/android/src/org/appspot/apprtc/SettingsFragment.java',
|
||||||
'examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java',
|
'examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java',
|
||||||
'examples/android/src/org/appspot/apprtc/util/AppRTCUtils.java',
|
|
||||||
'examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java',
|
'examples/android/src/org/appspot/apprtc/WebSocketChannelClient.java',
|
||||||
'examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java',
|
'examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java',
|
||||||
|
'examples/android/src/org/appspot/apprtc/util/AppRTCUtils.java',
|
||||||
|
'examples/android/src/org/appspot/apprtc/util/AsyncHttpURLConnection.java',
|
||||||
|
'examples/android/src/org/appspot/apprtc/util/LooperExecutor.java',
|
||||||
],
|
],
|
||||||
'outputs': [
|
'outputs': [
|
||||||
'<(PRODUCT_DIR)/AppRTCDemo-debug.apk',
|
'<(PRODUCT_DIR)/AppRTCDemo-debug.apk',
|
||||||
@@ -412,6 +413,7 @@
|
|||||||
'examples/androidtests/ant.properties',
|
'examples/androidtests/ant.properties',
|
||||||
'examples/androidtests/build.xml',
|
'examples/androidtests/build.xml',
|
||||||
'examples/androidtests/project.properties',
|
'examples/androidtests/project.properties',
|
||||||
|
'examples/androidtests/src/org/appspot/apprtc/test/LooperExecutorTest.java',
|
||||||
'examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java',
|
'examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java',
|
||||||
],
|
],
|
||||||
'outputs': [
|
'outputs': [
|
||||||
|
|||||||
Reference in New Issue
Block a user