Support loopback mode and command line execution
for Android AppRTCDemo when using WebSocket signaling. - Add loopback support for new signaling. In loopback mode only room connection is established, WebSocket connection is not opened and all candidate/sdp messages are automatically routed back. - Fix command line support both for channek and new signaling. Exit from application when room connection is closed and add an option to run application for certain time period and exit. - Plus some fixes for WebSocket signaling - support POST (not used for now) and DELETE request to WebSocket server and making sure that all available TURN server are used by peer connection client. BUG=3995,3937 R=jiayl@webrtc.org Review URL: https://webrtc-codereview.appspot.com/24339004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7725 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
@@ -39,7 +39,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);
|
public void connectToRoom(String url, boolean loopback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send offer SDP to the other participant.
|
* Send offer SDP to the other participant.
|
||||||
@@ -71,7 +71,7 @@ public interface AppRTCClient {
|
|||||||
public final MediaConstraints pcConstraints;
|
public final MediaConstraints pcConstraints;
|
||||||
public final MediaConstraints videoConstraints;
|
public final MediaConstraints videoConstraints;
|
||||||
public final MediaConstraints audioConstraints;
|
public final MediaConstraints audioConstraints;
|
||||||
public final String postMessageUrl;
|
public final String roomUrl;
|
||||||
public final String roomId;
|
public final String roomId;
|
||||||
public final String clientId;
|
public final String clientId;
|
||||||
public final String channelToken;
|
public final String channelToken;
|
||||||
@@ -81,14 +81,14 @@ public interface AppRTCClient {
|
|||||||
List<PeerConnection.IceServer> iceServers,
|
List<PeerConnection.IceServer> iceServers,
|
||||||
boolean initiator, MediaConstraints pcConstraints,
|
boolean initiator, MediaConstraints pcConstraints,
|
||||||
MediaConstraints videoConstraints, MediaConstraints audioConstraints,
|
MediaConstraints videoConstraints, MediaConstraints audioConstraints,
|
||||||
String postMessageUrl, String roomId, String clientId,
|
String roomUrl, String roomId, String clientId,
|
||||||
String channelToken, String offerSdp ) {
|
String channelToken, String offerSdp ) {
|
||||||
this.iceServers = iceServers;
|
this.iceServers = iceServers;
|
||||||
this.initiator = initiator;
|
this.initiator = initiator;
|
||||||
this.pcConstraints = pcConstraints;
|
this.pcConstraints = pcConstraints;
|
||||||
this.videoConstraints = videoConstraints;
|
this.videoConstraints = videoConstraints;
|
||||||
this.audioConstraints = audioConstraints;
|
this.audioConstraints = audioConstraints;
|
||||||
this.postMessageUrl = postMessageUrl;
|
this.roomUrl = roomUrl;
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.channelToken = channelToken;
|
this.channelToken = channelToken;
|
||||||
|
|||||||
@@ -85,7 +85,10 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
private TextView hudView;
|
private TextView hudView;
|
||||||
private TextView roomName;
|
private TextView roomName;
|
||||||
private ImageButton videoScalingButton;
|
private ImageButton videoScalingButton;
|
||||||
|
private boolean commandLineRun;
|
||||||
|
private int runTimeMs;
|
||||||
private boolean iceConnected;
|
private boolean iceConnected;
|
||||||
|
private boolean isError;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -194,30 +197,44 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
Uri url = intent.getData();
|
Uri url = intent.getData();
|
||||||
|
boolean loopback = intent.getBooleanExtra(
|
||||||
|
ConnectActivity.EXTRA_LOOPBACK, false);
|
||||||
|
commandLineRun = intent.getBooleanExtra(
|
||||||
|
ConnectActivity.EXTRA_CMDLINE, false);
|
||||||
|
runTimeMs = intent.getIntExtra(
|
||||||
|
ConnectActivity.EXTRA_RUNTIME, 0);
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
String room = url.getQueryParameter("r");
|
String room = url.getQueryParameter("r");
|
||||||
String loopback = url.getQueryParameter("debug");
|
if (loopback || (room != null && !room.equals(""))) {
|
||||||
if ((room != null && !room.equals("")) ||
|
|
||||||
(loopback != null && loopback.equals("loopback"))) {
|
|
||||||
logAndToast(getString(R.string.connecting_to, url));
|
logAndToast(getString(R.string.connecting_to, url));
|
||||||
if (USE_WEBSOCKETS) {
|
if (USE_WEBSOCKETS) {
|
||||||
appRtcClient = new WebSocketRTCClient(this);
|
appRtcClient = new WebSocketRTCClient(this);
|
||||||
} else {
|
} else {
|
||||||
appRtcClient = new GAERTCClient(this, this);
|
appRtcClient = new GAERTCClient(this, this);
|
||||||
}
|
}
|
||||||
appRtcClient.connectToRoom(url.toString());
|
appRtcClient.connectToRoom(url.toString(), loopback);
|
||||||
if (room != null && !room.equals("")) {
|
if (loopback) {
|
||||||
roomName.setText(room);
|
|
||||||
} else {
|
|
||||||
roomName.setText("loopback");
|
roomName.setText("loopback");
|
||||||
|
} else {
|
||||||
|
roomName.setText(room);
|
||||||
|
}
|
||||||
|
if (commandLineRun && runTimeMs > 0) {
|
||||||
|
// For command line execution run connection for <runTimeMs> and exit.
|
||||||
|
videoView.postDelayed(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}, runTimeMs);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logAndToast("Empty or missing room name!");
|
logAndToast("Empty or missing room name!");
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logAndToast(getString(R.string.missing_url));
|
logAndToast(getString(R.string.missing_url));
|
||||||
Log.wtf(TAG, "Didn't get any URL in intent!");
|
Log.e(TAG, "Didn't get any URL in intent!");
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,10 +270,6 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
disconnect();
|
disconnect();
|
||||||
if (audioManager != null) {
|
|
||||||
audioManager.close();
|
|
||||||
audioManager = null;
|
|
||||||
}
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,10 +340,23 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
pc.close();
|
pc.close();
|
||||||
pc = null;
|
pc = null;
|
||||||
}
|
}
|
||||||
|
if (audioManager != null) {
|
||||||
|
audioManager.close();
|
||||||
|
audioManager = null;
|
||||||
|
}
|
||||||
|
if (iceConnected && !isError) {
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
} else {
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disconnectWithMessage(final String errorMessage) {
|
private void disconnectWithErrorMessage(final String errorMessage) {
|
||||||
|
if (commandLineRun) {
|
||||||
|
Log.e(TAG, "Critical error: " + errorMessage);
|
||||||
|
disconnect();
|
||||||
|
} else {
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(getText(R.string.channel_error_title))
|
.setTitle(getText(R.string.channel_error_title))
|
||||||
.setMessage(errorMessage)
|
.setMessage(errorMessage)
|
||||||
@@ -342,6 +368,7 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
}).create().show();
|
}).create().show();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Poor-man's assert(): die with |msg| unless |condition| is true.
|
// Poor-man's assert(): die with |msg| unless |condition| is true.
|
||||||
private static void abortUnless(boolean condition, String msg) {
|
private static void abortUnless(boolean condition, String msg) {
|
||||||
@@ -461,7 +488,10 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChannelError(final String description) {
|
public void onChannelError(final String description) {
|
||||||
disconnectWithMessage(description);
|
if (!isError) {
|
||||||
|
isError = true;
|
||||||
|
disconnectWithErrorMessage(description);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
|
// -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
|
||||||
@@ -501,7 +531,10 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPeerConnectionError(String description) {
|
public void onPeerConnectionError(String description) {
|
||||||
disconnectWithMessage(description);
|
if (!isError) {
|
||||||
|
isError = true;
|
||||||
|
disconnectWithErrorMessage(description);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,10 +62,14 @@ import org.webrtc.MediaCodecVideoEncoder;
|
|||||||
*/
|
*/
|
||||||
public class ConnectActivity extends Activity {
|
public class ConnectActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String EXTRA_LOOPBACK = "org.appspot.apprtc.LOOPBACK";
|
||||||
|
public static final String EXTRA_CMDLINE = "org.appspot.apprtc.CMDLINE";
|
||||||
|
public static final String EXTRA_RUNTIME = "org.appspot.apprtc.RUNTIME";
|
||||||
private static final String TAG = "ConnectActivity";
|
private static final String TAG = "ConnectActivity";
|
||||||
private final boolean USE_WEBSOCKETS = false;
|
private final boolean USE_WEBSOCKETS = false;
|
||||||
private final String APPRTC_SERVER = "https://apprtc.appspot.com";
|
private final String APPRTC_SERVER = "https://apprtc.appspot.com";
|
||||||
private final String APPRTC_WS_SERVER = "https://8-dot-apprtc.appspot.com";
|
private final String APPRTC_WS_SERVER = "https://8-dot-apprtc.appspot.com";
|
||||||
|
private final int CONNECTION_REQUEST = 1;
|
||||||
|
|
||||||
private ImageButton addRoomButton;
|
private ImageButton addRoomButton;
|
||||||
private ImageButton removeRoomButton;
|
private ImageButton removeRoomButton;
|
||||||
@@ -81,6 +85,8 @@ public class ConnectActivity extends Activity {
|
|||||||
private String keyprefRoomList;
|
private String keyprefRoomList;
|
||||||
private ArrayList<String> roomList;
|
private ArrayList<String> roomList;
|
||||||
private ArrayAdapter<String> adapter;
|
private ArrayAdapter<String> adapter;
|
||||||
|
private boolean commandLineRun;
|
||||||
|
private int runTimeMs;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -95,13 +101,6 @@ public class ConnectActivity extends Activity {
|
|||||||
keyprefRoom = getString(R.string.pref_room_key);
|
keyprefRoom = getString(R.string.pref_room_key);
|
||||||
keyprefRoomList = getString(R.string.pref_room_list_key);
|
keyprefRoomList = getString(R.string.pref_room_list_key);
|
||||||
|
|
||||||
// If an implicit VIEW intent is launching the app, go directly to that URL.
|
|
||||||
final Intent intent = getIntent();
|
|
||||||
if ("android.intent.action.VIEW".equals(intent.getAction())) {
|
|
||||||
connectToRoom(intent.getData().toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_connect);
|
setContentView(R.layout.activity_connect);
|
||||||
|
|
||||||
roomEditText = (EditText) findViewById(R.id.room_edittext);
|
roomEditText = (EditText) findViewById(R.id.room_edittext);
|
||||||
@@ -131,6 +130,21 @@ public class ConnectActivity extends Activity {
|
|||||||
connectLoopbackButton =
|
connectLoopbackButton =
|
||||||
(ImageButton) findViewById(R.id.connect_loopback_button);
|
(ImageButton) findViewById(R.id.connect_loopback_button);
|
||||||
connectLoopbackButton.setOnClickListener(connectListener);
|
connectLoopbackButton.setOnClickListener(connectListener);
|
||||||
|
|
||||||
|
// If an implicit VIEW intent is launching the app, go directly to that URL.
|
||||||
|
commandLineRun = false;
|
||||||
|
final Intent intent = getIntent();
|
||||||
|
if ("android.intent.action.VIEW".equals(intent.getAction())) {
|
||||||
|
commandLineRun = true;
|
||||||
|
boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false);
|
||||||
|
runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
|
||||||
|
String url = intent.getData().toString();
|
||||||
|
if (loopback && !url.contains("debug=loopback")) {
|
||||||
|
url += "/?debug=loopback";
|
||||||
|
}
|
||||||
|
connectToRoom(url, loopback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -188,6 +202,16 @@ public class ConnectActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(
|
||||||
|
int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == CONNECTION_REQUEST && commandLineRun) {
|
||||||
|
Log.d(TAG, "Return: " + resultCode);
|
||||||
|
setResult(resultCode);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final OnClickListener connectListener = new OnClickListener() {
|
private final OnClickListener connectListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
@@ -195,11 +219,13 @@ public class ConnectActivity extends Activity {
|
|||||||
if (view.getId() == R.id.connect_loopback_button) {
|
if (view.getId() == R.id.connect_loopback_button) {
|
||||||
loopback = true;
|
loopback = true;
|
||||||
}
|
}
|
||||||
String url = APPRTC_SERVER;
|
String url;
|
||||||
if (USE_WEBSOCKETS) {
|
if (USE_WEBSOCKETS) {
|
||||||
url = APPRTC_WS_SERVER;
|
url = APPRTC_WS_SERVER;
|
||||||
|
} else {
|
||||||
|
url = APPRTC_SERVER;
|
||||||
}
|
}
|
||||||
if (loopback && !USE_WEBSOCKETS) {
|
if (loopback) {
|
||||||
url += "/?debug=loopback";
|
url += "/?debug=loopback";
|
||||||
} else {
|
} else {
|
||||||
String roomName = getSelectedItem();
|
String roomName = getSelectedItem();
|
||||||
@@ -267,16 +293,19 @@ public class ConnectActivity extends Activity {
|
|||||||
url += "&googCpuOveruseDetection=false";
|
url += "&googCpuOveruseDetection=false";
|
||||||
}
|
}
|
||||||
// TODO(kjellander): Add support for custom parameters to the URL.
|
// TODO(kjellander): Add support for custom parameters to the URL.
|
||||||
connectToRoom(url);
|
connectToRoom(url, loopback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private void connectToRoom(String roomUrl) {
|
private void connectToRoom(String roomUrl, boolean loopback) {
|
||||||
if (validateUrl(roomUrl)) {
|
if (validateUrl(roomUrl)) {
|
||||||
Uri url = Uri.parse(roomUrl);
|
Uri url = Uri.parse(roomUrl);
|
||||||
Intent intent = new Intent(this, AppRTCDemoActivity.class);
|
Intent intent = new Intent(this, AppRTCDemoActivity.class);
|
||||||
intent.setData(url);
|
intent.setData(url);
|
||||||
startActivity(intent);
|
intent.putExtra(EXTRA_LOOPBACK, loopback);
|
||||||
|
intent.putExtra(EXTRA_CMDLINE, commandLineRun);
|
||||||
|
intent.putExtra(EXTRA_RUNTIME, runTimeMs);
|
||||||
|
startActivityForResult(intent, CONNECTION_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public class GAERTCClient implements AppRTCClient, RoomParametersFetcherEvents {
|
|||||||
private SignalingParameters signalingParameters;
|
private SignalingParameters signalingParameters;
|
||||||
private RoomParametersFetcher fetcher;
|
private RoomParametersFetcher fetcher;
|
||||||
private LinkedList<String> sendQueue = new LinkedList<String>();
|
private LinkedList<String> sendQueue = new LinkedList<String>();
|
||||||
|
private String postMessageUrl;
|
||||||
|
|
||||||
public GAERTCClient(Activity activity, SignalingEvents events) {
|
public GAERTCClient(Activity activity, SignalingEvents events) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
@@ -74,8 +75,8 @@ public class GAERTCClient implements AppRTCClient, RoomParametersFetcherEvents {
|
|||||||
* on its GAE Channel.
|
* on its GAE Channel.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void connectToRoom(String url) {
|
public void connectToRoom(String url, boolean loopback) {
|
||||||
fetcher = new RoomParametersFetcher(this);
|
fetcher = new RoomParametersFetcher(this, loopback);
|
||||||
fetcher.execute(url);
|
fetcher.execute(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +168,7 @@ public class GAERTCClient implements AppRTCClient, RoomParametersFetcherEvents {
|
|||||||
try {
|
try {
|
||||||
for (String msg : sendQueue) {
|
for (String msg : sendQueue) {
|
||||||
Log.d(TAG, "SEND: " + msg);
|
Log.d(TAG, "SEND: " + msg);
|
||||||
URLConnection connection = new URL(
|
URLConnection connection = new URL(postMessageUrl).openConnection();
|
||||||
signalingParameters.postMessageUrl).openConnection();
|
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
connection.getOutputStream().write(msg.getBytes("UTF-8"));
|
connection.getOutputStream().write(msg.getBytes("UTF-8"));
|
||||||
if (!connection.getHeaderField(null).startsWith("HTTP/1.1 200 ")) {
|
if (!connection.getHeaderField(null).startsWith("HTTP/1.1 200 ")) {
|
||||||
@@ -203,6 +203,8 @@ public class GAERTCClient implements AppRTCClient, RoomParametersFetcherEvents {
|
|||||||
reportChannelError("Room does not support GAE channel signaling.");
|
reportChannelError("Room does not support GAE channel signaling.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
postMessageUrl = params.roomUrl + "/message?r=" +
|
||||||
|
params.roomId + "&u=" + params.clientId;
|
||||||
gaeHandler = new GAEHandler();
|
gaeHandler = new GAEHandler();
|
||||||
channelClient =
|
channelClient =
|
||||||
new GAEChannelClient(activity, params.channelToken, gaeHandler);
|
new GAEChannelClient(activity, params.channelToken, gaeHandler);
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ public class PeerConnectionClient {
|
|||||||
// 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 LinkedList<IceCandidate> queuedLocalCandidates = null;
|
|
||||||
private MediaConstraints sdpMediaConstraints;
|
private MediaConstraints sdpMediaConstraints;
|
||||||
private MediaConstraints videoConstraints;
|
private MediaConstraints videoConstraints;
|
||||||
private PeerConnectionEvents events;
|
private PeerConnectionEvents events;
|
||||||
@@ -88,7 +87,6 @@ public class PeerConnectionClient {
|
|||||||
this.events = events;
|
this.events = events;
|
||||||
isInitiator = signalingParameters.initiator;
|
isInitiator = signalingParameters.initiator;
|
||||||
queuedRemoteCandidates = new LinkedList<IceCandidate>();
|
queuedRemoteCandidates = new LinkedList<IceCandidate>();
|
||||||
queuedLocalCandidates = new LinkedList<IceCandidate>();
|
|
||||||
|
|
||||||
sdpMediaConstraints = new MediaConstraints();
|
sdpMediaConstraints = new MediaConstraints();
|
||||||
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
||||||
@@ -384,13 +382,6 @@ public class PeerConnectionClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void drainCandidates() {
|
private void drainCandidates() {
|
||||||
if (queuedLocalCandidates != null) {
|
|
||||||
Log.d(TAG, "Send " + queuedLocalCandidates.size() + " local candidates");
|
|
||||||
for (IceCandidate candidate : queuedLocalCandidates) {
|
|
||||||
events.onIceCandidate(candidate);
|
|
||||||
}
|
|
||||||
queuedLocalCandidates = null;
|
|
||||||
}
|
|
||||||
if (queuedRemoteCandidates != null) {
|
if (queuedRemoteCandidates != null) {
|
||||||
Log.d(TAG, "Add " + queuedRemoteCandidates.size() + " remote candidates");
|
Log.d(TAG, "Add " + queuedRemoteCandidates.size() + " remote candidates");
|
||||||
for (IceCandidate candidate : queuedRemoteCandidates) {
|
for (IceCandidate candidate : queuedRemoteCandidates) {
|
||||||
@@ -449,12 +440,8 @@ public class PeerConnectionClient {
|
|||||||
public void onIceCandidate(final IceCandidate candidate){
|
public void onIceCandidate(final IceCandidate candidate){
|
||||||
activity.runOnUiThread(new Runnable() {
|
activity.runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if (queuedLocalCandidates != null) {
|
|
||||||
queuedLocalCandidates.add(candidate);
|
|
||||||
} else {
|
|
||||||
events.onIceCandidate(candidate);
|
events.onIceCandidate(candidate);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class RoomParametersFetcher
|
|||||||
private static final String TAG = "RoomRTCClient";
|
private static final String TAG = "RoomRTCClient";
|
||||||
private Exception exception = null;
|
private Exception exception = null;
|
||||||
private RoomParametersFetcherEvents events = null;
|
private RoomParametersFetcherEvents events = null;
|
||||||
|
private boolean loopback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Room parameters fetcher callbacks.
|
* Room parameters fetcher callbacks.
|
||||||
@@ -68,9 +69,11 @@ public class RoomParametersFetcher
|
|||||||
public void onSignalingParametersError(final String description);
|
public void onSignalingParametersError(final String description);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoomParametersFetcher(RoomParametersFetcherEvents events) {
|
public RoomParametersFetcher(
|
||||||
|
RoomParametersFetcherEvents events, boolean loopback) {
|
||||||
super();
|
super();
|
||||||
this.events = events;
|
this.events = events;
|
||||||
|
this.loopback = loopback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -138,11 +141,18 @@ public class RoomParametersFetcher
|
|||||||
offerSdp = null;
|
offerSdp = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String postMessageUrl = url.substring(0, url.indexOf('?'));
|
String roomUrl = url.substring(0, url.indexOf('?'));
|
||||||
postMessageUrl += "/message?r=" + roomId + "&u=" + clientId;
|
Log.d(TAG, "Room url: " + roomUrl);
|
||||||
Log.d(TAG, "Post url: " + postMessageUrl);
|
|
||||||
|
|
||||||
boolean initiator = roomJson.getInt("initiator") == 1;
|
boolean initiator;
|
||||||
|
if (loopback) {
|
||||||
|
// In loopback mode caller should always be call initiator.
|
||||||
|
// TODO(glaznev): remove this once 8-dot-apprtc server will set initiator
|
||||||
|
// flag to true for loopback calls.
|
||||||
|
initiator = true;
|
||||||
|
} else {
|
||||||
|
initiator = roomJson.getInt("initiator") == 1;
|
||||||
|
}
|
||||||
Log.d(TAG, "Initiator: " + initiator);
|
Log.d(TAG, "Initiator: " + initiator);
|
||||||
|
|
||||||
LinkedList<PeerConnection.IceServer> iceServers =
|
LinkedList<PeerConnection.IceServer> iceServers =
|
||||||
@@ -156,15 +166,17 @@ public class RoomParametersFetcher
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isTurnPresent) {
|
if (!isTurnPresent) {
|
||||||
PeerConnection.IceServer server =
|
LinkedList<PeerConnection.IceServer> turnServers =
|
||||||
requestTurnServer(roomJson.getString("turn_url"));
|
requestTurnServers(roomJson.getString("turn_url"));
|
||||||
Log.d(TAG, "TurnServer: " + server);
|
for (PeerConnection.IceServer turnServer : turnServers) {
|
||||||
iceServers.add(server);
|
Log.d(TAG, "TurnServer: " + turnServer);
|
||||||
|
iceServers.add(turnServer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaConstraints pcConstraints = constraintsFromJSON(
|
MediaConstraints pcConstraints = constraintsFromJSON(
|
||||||
roomJson.getString("pc_constraints"));
|
roomJson.getString("pc_constraints"));
|
||||||
addDTLSConstraintIfMissing(pcConstraints);
|
addDTLSConstraintIfMissing(pcConstraints, loopback);
|
||||||
Log.d(TAG, "pcConstraints: " + pcConstraints);
|
Log.d(TAG, "pcConstraints: " + pcConstraints);
|
||||||
MediaConstraints videoConstraints = constraintsFromJSON(
|
MediaConstraints videoConstraints = constraintsFromJSON(
|
||||||
getAVConstraints("video",
|
getAVConstraints("video",
|
||||||
@@ -178,13 +190,14 @@ public class RoomParametersFetcher
|
|||||||
return new SignalingParameters(
|
return new SignalingParameters(
|
||||||
iceServers, initiator,
|
iceServers, initiator,
|
||||||
pcConstraints, videoConstraints, audioConstraints,
|
pcConstraints, videoConstraints, audioConstraints,
|
||||||
postMessageUrl, roomId, clientId,
|
roomUrl, roomId, clientId,
|
||||||
channelToken, offerSdp);
|
channelToken, offerSdp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
// the web-app.
|
// the web-app.
|
||||||
private void addDTLSConstraintIfMissing(MediaConstraints pcConstraints) {
|
private void addDTLSConstraintIfMissing(
|
||||||
|
MediaConstraints pcConstraints, boolean loopback) {
|
||||||
for (MediaConstraints.KeyValuePair pair : pcConstraints.mandatory) {
|
for (MediaConstraints.KeyValuePair pair : pcConstraints.mandatory) {
|
||||||
if (pair.getKey().equals("DtlsSrtpKeyAgreement")) {
|
if (pair.getKey().equals("DtlsSrtpKeyAgreement")) {
|
||||||
return;
|
return;
|
||||||
@@ -195,11 +208,16 @@ public class RoomParametersFetcher
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// DTLS isn't being suppressed (e.g. for debug=loopback calls), so enable
|
// DTLS isn't being specified (e.g. for debug=loopback calls), so enable
|
||||||
// it by default.
|
// it for normal calls and disable for loopback calls.
|
||||||
|
if (loopback) {
|
||||||
|
pcConstraints.optional.add(
|
||||||
|
new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "false"));
|
||||||
|
} else {
|
||||||
pcConstraints.optional.add(
|
pcConstraints.optional.add(
|
||||||
new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return the constraints specified for |type| of "audio" or "video" in
|
// Return the constraints specified for |type| of "audio" or "video" in
|
||||||
// |mediaConstraintsString|.
|
// |mediaConstraintsString|.
|
||||||
@@ -256,17 +274,25 @@ public class RoomParametersFetcher
|
|||||||
|
|
||||||
// Requests & returns a TURN ICE Server based on a request URL. Must be run
|
// Requests & returns a TURN ICE Server based on a request URL. Must be run
|
||||||
// off the main thread!
|
// off the main thread!
|
||||||
private PeerConnection.IceServer requestTurnServer(String url)
|
private LinkedList<PeerConnection.IceServer> requestTurnServers(String url)
|
||||||
throws IOException, JSONException {
|
throws IOException, JSONException {
|
||||||
|
LinkedList<PeerConnection.IceServer> turnServers =
|
||||||
|
new LinkedList<PeerConnection.IceServer>();
|
||||||
|
Log.d(TAG, "Request TURN from: " + url);
|
||||||
URLConnection connection = (new URL(url)).openConnection();
|
URLConnection connection = (new URL(url)).openConnection();
|
||||||
connection.addRequestProperty("user-agent", "Mozilla/5.0");
|
connection.addRequestProperty("user-agent", "Mozilla/5.0");
|
||||||
connection.addRequestProperty("origin", "https://apprtc.appspot.com");
|
connection.addRequestProperty("origin", "https://apprtc.appspot.com");
|
||||||
String response = drainStream(connection.getInputStream());
|
String response = drainStream(connection.getInputStream());
|
||||||
|
Log.d(TAG, "TURN response: " + response);
|
||||||
JSONObject responseJSON = new JSONObject(response);
|
JSONObject responseJSON = new JSONObject(response);
|
||||||
String uri = responseJSON.getJSONArray("uris").getString(0);
|
|
||||||
String username = responseJSON.getString("username");
|
String username = responseJSON.getString("username");
|
||||||
String password = responseJSON.getString("password");
|
String password = responseJSON.getString("password");
|
||||||
return new PeerConnection.IceServer(uri, username, password);
|
JSONArray turnUris = responseJSON.getJSONArray("uris");
|
||||||
|
for (int i = 0; i < turnUris.length(); i++) {
|
||||||
|
String uri = turnUris.getString(i);
|
||||||
|
turnServers.add(new PeerConnection.IceServer(uri, username, password));
|
||||||
|
}
|
||||||
|
return turnServers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the list of ICE servers described by a WebRTCPeerConnection
|
// Return the list of ICE servers described by a WebRTCPeerConnection
|
||||||
|
|||||||
@@ -35,8 +35,14 @@ import de.tavendo.autobahn.WebSocketConnection;
|
|||||||
import de.tavendo.autobahn.WebSocketException;
|
import de.tavendo.autobahn.WebSocketException;
|
||||||
import de.tavendo.autobahn.WebSocket.WebSocketConnectionObserver;
|
import de.tavendo.autobahn.WebSocket.WebSocketConnectionObserver;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -53,8 +59,18 @@ public class WebSocketChannelClient {
|
|||||||
private final Handler uiHandler;
|
private final Handler uiHandler;
|
||||||
private WebSocketConnection ws;
|
private WebSocketConnection ws;
|
||||||
private WebSocketObserver wsObserver;
|
private WebSocketObserver wsObserver;
|
||||||
private URI serverURI;
|
private String wsServerUrl;
|
||||||
|
private String postServerUrl;
|
||||||
|
private String roomID;
|
||||||
|
private String clientID;
|
||||||
private WebSocketConnectionState state;
|
private WebSocketConnectionState state;
|
||||||
|
// Http post/delete message queue. Messages are added from UI thread in
|
||||||
|
// post() and disconnect() calls. Messages are consumed by AsyncTask's
|
||||||
|
// background thread.
|
||||||
|
private LinkedList<WsHttpMessage> wsHttpQueue;
|
||||||
|
// WebSocket send queue. Messages are added to the queue when WebSocket
|
||||||
|
// client is not registered and are consumed in register() call.
|
||||||
|
private LinkedList<String> wsSendQueue;
|
||||||
|
|
||||||
public enum WebSocketConnectionState {
|
public enum WebSocketConnectionState {
|
||||||
NEW, CONNECTED, REGISTERED, CLOSED, ERROR
|
NEW, CONNECTED, REGISTERED, CLOSED, ERROR
|
||||||
@@ -74,6 +90,10 @@ public class WebSocketChannelClient {
|
|||||||
public WebSocketChannelClient(WebSocketChannelEvents events) {
|
public WebSocketChannelClient(WebSocketChannelEvents events) {
|
||||||
this.events = events;
|
this.events = events;
|
||||||
uiHandler = new Handler(Looper.getMainLooper());
|
uiHandler = new Handler(Looper.getMainLooper());
|
||||||
|
roomID = null;
|
||||||
|
clientID = null;
|
||||||
|
wsHttpQueue = new LinkedList<WsHttpMessage>();
|
||||||
|
wsSendQueue = new LinkedList<String>();
|
||||||
state = WebSocketConnectionState.NEW;
|
state = WebSocketConnectionState.NEW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,18 +101,19 @@ public class WebSocketChannelClient {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(String url) {
|
public void connect(String wsUrl, String postUrl) {
|
||||||
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: " + url);
|
Log.d(TAG, "Connecting WebSocket to: " + wsUrl + ". Post URL: " + postUrl);
|
||||||
|
|
||||||
ws = new WebSocketConnection();
|
ws = new WebSocketConnection();
|
||||||
wsObserver = new WebSocketObserver();
|
wsObserver = new WebSocketObserver();
|
||||||
try {
|
try {
|
||||||
serverURI = new URI(url);
|
wsServerUrl = wsUrl;
|
||||||
ws.connect(serverURI, wsObserver);
|
postServerUrl = postUrl;
|
||||||
|
ws.connect(new URI(wsServerUrl), wsObserver);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
reportError("URI error: " + e.getMessage());
|
reportError("URI error: " + e.getMessage());
|
||||||
} catch (WebSocketException e) {
|
} catch (WebSocketException e) {
|
||||||
@@ -100,29 +121,56 @@ public class WebSocketChannelClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register(String roomId, String clientId) {
|
public void setClientParameters(String roomID, String clientID) {
|
||||||
|
this.roomID = roomID;
|
||||||
|
this.clientID = clientID;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
json.put("roomid", roomId);
|
json.put("roomid", roomID);
|
||||||
json.put("clientid", clientId);
|
json.put("clientid", clientID);
|
||||||
Log.d(TAG, "WS SEND: " + json.toString());
|
Log.d(TAG, "WS SEND: " + json.toString());
|
||||||
ws.sendTextMessage(json.toString());
|
ws.sendTextMessage(json.toString());
|
||||||
state = WebSocketConnectionState.REGISTERED;
|
state = WebSocketConnectionState.REGISTERED;
|
||||||
|
// Send any previously accumulated messages.
|
||||||
|
synchronized(wsSendQueue) {
|
||||||
|
for (String sendMessage : wsSendQueue) {
|
||||||
|
send(sendMessage);
|
||||||
|
}
|
||||||
|
wsSendQueue.clear();
|
||||||
|
}
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
reportError("WebSocket register JSON error: " + e.getMessage());
|
reportError("WebSocket register JSON error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(String message) {
|
public void send(String message) {
|
||||||
if (state != WebSocketConnectionState.REGISTERED) {
|
switch (state) {
|
||||||
Log.e(TAG, "WebSocket send() in non registered state : " + message);
|
case NEW:
|
||||||
|
case CONNECTED:
|
||||||
|
// Store outgoing messages and send them after websocket client
|
||||||
|
// is registered.
|
||||||
|
Log.d(TAG, "WS ACC: " + message);
|
||||||
|
synchronized(wsSendQueue) {
|
||||||
|
wsSendQueue.add(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case ERROR:
|
||||||
|
case CLOSED:
|
||||||
|
Log.e(TAG, "WebSocket send() in error or closed state : " + message);
|
||||||
|
return;
|
||||||
|
case REGISTERED:
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
try {
|
try {
|
||||||
json.put("cmd", "send");
|
json.put("cmd", "send");
|
||||||
@@ -133,6 +181,21 @@ public class WebSocketChannelClient {
|
|||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
reportError("WebSocket send JSON error: " + e.getMessage());
|
reportError("WebSocket send JSON error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This call can be used to send WebSocket messages before WebSocket
|
||||||
|
// connection is opened. However for now this way of sending messages
|
||||||
|
// is not used until possible race condition of arriving ice candidates
|
||||||
|
// send through websocket before SDP answer sent through http post will be
|
||||||
|
// resolved.
|
||||||
|
public void post(String message) {
|
||||||
|
synchronized (wsHttpQueue) {
|
||||||
|
wsHttpQueue.add(new WsHttpMessage("POST", message));
|
||||||
|
}
|
||||||
|
requestHttpQueueDrainInBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
@@ -141,14 +204,19 @@ public class WebSocketChannelClient {
|
|||||||
send("{\"type\": \"bye\"}");
|
send("{\"type\": \"bye\"}");
|
||||||
state = WebSocketConnectionState.CONNECTED;
|
state = WebSocketConnectionState.CONNECTED;
|
||||||
}
|
}
|
||||||
// TODO(glaznev): send DELETE to http WebSocket server once send()
|
|
||||||
// will switch to http POST.
|
|
||||||
|
|
||||||
// Close WebSocket in CONNECTED or ERROR states only.
|
// Close WebSocket in CONNECTED or ERROR states only.
|
||||||
if (state == WebSocketConnectionState.CONNECTED ||
|
if (state == WebSocketConnectionState.CONNECTED ||
|
||||||
state == WebSocketConnectionState.ERROR) {
|
state == WebSocketConnectionState.ERROR) {
|
||||||
state = WebSocketConnectionState.CLOSED;
|
|
||||||
ws.disconnect();
|
ws.disconnect();
|
||||||
|
|
||||||
|
// Send DELETE to http WebSocket server.
|
||||||
|
synchronized (wsHttpQueue) {
|
||||||
|
wsHttpQueue.clear();
|
||||||
|
wsHttpQueue.add(new WsHttpMessage("DELETE", ""));
|
||||||
|
}
|
||||||
|
requestHttpQueueDrainInBackground();
|
||||||
|
|
||||||
|
state = WebSocketConnectionState.CLOSED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,10 +232,83 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(glaznev): This is not good implementation due to discrepancy
|
||||||
|
// between JS encodeURIComponent() and Java URLEncoder.encode().
|
||||||
|
// Remove this once WebSocket server will switch to a different encoding.
|
||||||
|
private String encodeURIComponent(String s) {
|
||||||
|
String result = null;
|
||||||
|
try {
|
||||||
|
result = URLEncoder.encode(s, "UTF-8")
|
||||||
|
.replaceAll("\\+", "%20")
|
||||||
|
.replaceAll("\\%21", "!")
|
||||||
|
.replaceAll("\\%27", "'")
|
||||||
|
.replaceAll("\\%28", "(")
|
||||||
|
.replaceAll("\\%29", ")")
|
||||||
|
.replaceAll("\\%7E", "~");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
result = s;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request an attempt to drain the send queue, on a background thread.
|
||||||
|
private void requestHttpQueueDrainInBackground() {
|
||||||
|
(new AsyncTask<Void, Void, Void>() {
|
||||||
|
public Void doInBackground(Void... unused) {
|
||||||
|
maybeDrainWsHttpQueue();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all queued websocket messages.
|
||||||
|
private void maybeDrainWsHttpQueue() {
|
||||||
|
synchronized (wsHttpQueue) {
|
||||||
|
if (roomID == null || clientID == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (WsHttpMessage wsHttpMessage : wsHttpQueue) {
|
||||||
|
// Send POST request.
|
||||||
|
Log.d(TAG, "WS " + wsHttpMessage.method + " : " +
|
||||||
|
wsHttpMessage.message);
|
||||||
|
String postUrl = postServerUrl + roomID + "/" + clientID;
|
||||||
|
HttpURLConnection connection =
|
||||||
|
(HttpURLConnection) new URL(postUrl).openConnection();
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setRequestProperty(
|
||||||
|
"Content-type", "application/x-www-form-urlencoded");
|
||||||
|
connection.setRequestMethod(wsHttpMessage.method);
|
||||||
|
if (wsHttpMessage.message.length() > 0) {
|
||||||
|
String message = "msg=" + encodeURIComponent(wsHttpMessage.message);
|
||||||
|
connection.getOutputStream().write(message.getBytes("UTF-8"));
|
||||||
|
}
|
||||||
|
String replyHeader = connection.getHeaderField(null);
|
||||||
|
if (!replyHeader.startsWith("HTTP/1.1 200 ")) {
|
||||||
|
reportError("Non-200 response to " + wsHttpMessage.method + " : " +
|
||||||
|
connection.getHeaderField(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
reportError("WS POST error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
wsHttpQueue.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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: " + serverURI.toString());
|
Log.d(TAG, "WebSocket connection opened to: " + wsServerUrl);
|
||||||
uiHandler.post(new Runnable() {
|
uiHandler.post(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
state = WebSocketConnectionState.CONNECTED;
|
state = WebSocketConnectionState.CONNECTED;
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ import android.os.Looper;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents;
|
import org.appspot.apprtc.RoomParametersFetcher.RoomParametersFetcherEvents;
|
||||||
@@ -57,23 +57,31 @@ import org.webrtc.SessionDescription;
|
|||||||
public class WebSocketRTCClient implements AppRTCClient,
|
public class WebSocketRTCClient implements AppRTCClient,
|
||||||
RoomParametersFetcherEvents, WebSocketChannelEvents {
|
RoomParametersFetcherEvents, WebSocketChannelEvents {
|
||||||
private static final String TAG = "WSRTCClient";
|
private static final String TAG = "WSRTCClient";
|
||||||
private static final String WSS_SERVER = "wss://apprtc-ws.webrtc.org:8089/ws";
|
private static final String WSS_SERVER =
|
||||||
|
"wss://apprtc-ws.webrtc.org:8089/ws";
|
||||||
|
// TODO(glaznev): remove this hard-coded URL and instead get WebSocket http
|
||||||
|
// server URL from room response once it will be supported by 8-dot-apprtc.
|
||||||
|
private static final String WSS_POST_URL =
|
||||||
|
"https://apprtc-ws.webrtc.org:8089/";
|
||||||
|
|
||||||
private enum ConnectionState {
|
private enum ConnectionState {
|
||||||
NEW, CONNECTED, CLOSED, ERROR
|
NEW, CONNECTED, CLOSED, ERROR
|
||||||
};
|
};
|
||||||
private final Handler uiHandler;
|
private final Handler uiHandler;
|
||||||
|
private boolean loopback;
|
||||||
private SignalingEvents events;
|
private SignalingEvents events;
|
||||||
private SignalingParameters signalingParameters;
|
private SignalingParameters signalingParameters;
|
||||||
private WebSocketChannelClient wsClient;
|
private WebSocketChannelClient wsClient;
|
||||||
private RoomParametersFetcher fetcher;
|
private RoomParametersFetcher fetcher;
|
||||||
private ConnectionState roomState;
|
private ConnectionState roomState;
|
||||||
private LinkedList<String> gaePostQueue;
|
private LinkedList<GAEMessage> gaePostQueue;
|
||||||
|
private String postMessageUrl;
|
||||||
|
private String byeMessageUrl;
|
||||||
|
|
||||||
public WebSocketRTCClient(SignalingEvents events) {
|
public WebSocketRTCClient(SignalingEvents events) {
|
||||||
this.events = events;
|
this.events = events;
|
||||||
uiHandler = new Handler(Looper.getMainLooper());
|
uiHandler = new Handler(Looper.getMainLooper());
|
||||||
gaePostQueue = new LinkedList<String>();
|
gaePostQueue = new LinkedList<GAEMessage>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
@@ -82,14 +90,24 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
@Override
|
@Override
|
||||||
public void onSignalingParametersReady(final SignalingParameters params) {
|
public void onSignalingParametersReady(final SignalingParameters params) {
|
||||||
Log.d(TAG, "Room connection completed.");
|
Log.d(TAG, "Room connection completed.");
|
||||||
if (!params.initiator && params.offerSdp == null) {
|
if (!loopback && !params.initiator && params.offerSdp == null) {
|
||||||
reportError("Offer SDP is not available");
|
reportError("Offer SDP is not available.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (loopback && params.offerSdp != null) {
|
||||||
|
reportError("Loopback room is busy.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
signalingParameters = params;
|
signalingParameters = params;
|
||||||
|
postMessageUrl = params.roomUrl + "message?r=" +
|
||||||
|
params.roomId + "&u=" + params.clientId;
|
||||||
|
byeMessageUrl = params.roomUrl + "bye/" +
|
||||||
|
params.roomId + "/" + params.clientId;
|
||||||
roomState = ConnectionState.CONNECTED;
|
roomState = ConnectionState.CONNECTED;
|
||||||
|
wsClient.setClientParameters(
|
||||||
|
signalingParameters.roomId, signalingParameters.clientId);
|
||||||
|
wsClient.register();
|
||||||
events.onConnectedToRoom(signalingParameters);
|
events.onConnectedToRoom(signalingParameters);
|
||||||
wsClient.register(signalingParameters.roomId, signalingParameters.clientId);
|
|
||||||
events.onChannelOpen();
|
events.onChannelOpen();
|
||||||
if (!signalingParameters.initiator) {
|
if (!signalingParameters.initiator) {
|
||||||
// For call receiver get sdp offer from room parameters.
|
// For call receiver get sdp offer from room parameters.
|
||||||
@@ -112,8 +130,7 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
public void onWebSocketOpen() {
|
public void onWebSocketOpen() {
|
||||||
Log.d(TAG, "Websocket connection completed.");
|
Log.d(TAG, "Websocket connection completed.");
|
||||||
if (roomState == ConnectionState.CONNECTED) {
|
if (roomState == ConnectionState.CONNECTED) {
|
||||||
wsClient.register(
|
wsClient.register();
|
||||||
signalingParameters.roomId, signalingParameters.clientId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,24 +192,30 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
// https://apprtc.appspot.com/?r=NNN, retrieve room parameters
|
// https://apprtc.appspot.com/?r=NNN, retrieve room parameters
|
||||||
// and connect to WebSocket server.
|
// and connect to WebSocket server.
|
||||||
@Override
|
@Override
|
||||||
public void connectToRoom(String url) {
|
public void connectToRoom(String url, boolean loopback) {
|
||||||
|
this.loopback = loopback;
|
||||||
// Get room parameters.
|
// Get room parameters.
|
||||||
roomState = ConnectionState.NEW;
|
roomState = ConnectionState.NEW;
|
||||||
fetcher = new RoomParametersFetcher(this);
|
fetcher = new RoomParametersFetcher(this, loopback);
|
||||||
fetcher.execute(url);
|
fetcher.execute(url);
|
||||||
// Connect to WebSocket server.
|
// Connect to WebSocket server.
|
||||||
wsClient = new WebSocketChannelClient(this);
|
wsClient = new WebSocketChannelClient(this);
|
||||||
wsClient.connect(WSS_SERVER);
|
if (!loopback) {
|
||||||
|
wsClient.connect(WSS_SERVER, WSS_POST_URL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
Log.d(TAG, "Disconnect. Room state: " + roomState);
|
Log.d(TAG, "Disconnect. Room state: " + roomState);
|
||||||
|
wsClient.disconnect();
|
||||||
if (roomState == ConnectionState.CONNECTED) {
|
if (roomState == ConnectionState.CONNECTED) {
|
||||||
Log.d(TAG, "Closing room.");
|
Log.d(TAG, "Closing room.");
|
||||||
sendGAEMessage("{\"type\": \"bye\"}");
|
// TODO(glaznev): Remove json bye message sending once new bye will
|
||||||
|
// be supported on 8-dot.
|
||||||
|
//sendGAEMessage(byeMessageUrl, "");
|
||||||
|
sendGAEMessage(postMessageUrl, "{\"type\": \"bye\"}");
|
||||||
}
|
}
|
||||||
wsClient.disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send local SDP (offer or answer, depending on role) to the
|
// Send local SDP (offer or answer, depending on role) to the
|
||||||
@@ -202,14 +225,26 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
// we might want to filter elsewhere.
|
// we might want to filter elsewhere.
|
||||||
@Override
|
@Override
|
||||||
public void sendOfferSdp(final SessionDescription sdp) {
|
public void sendOfferSdp(final SessionDescription sdp) {
|
||||||
|
if (loopback) {
|
||||||
|
// In loopback mode rename this offer to answer and send it back.
|
||||||
|
SessionDescription sdpAnswer = new SessionDescription(
|
||||||
|
SessionDescription.Type.fromCanonicalForm("answer"),
|
||||||
|
sdp.description);
|
||||||
|
events.onRemoteDescription(sdpAnswer);
|
||||||
|
} else {
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
jsonPut(json, "sdp", sdp.description);
|
jsonPut(json, "sdp", sdp.description);
|
||||||
jsonPut(json, "type", "offer");
|
jsonPut(json, "type", "offer");
|
||||||
sendGAEMessage(json.toString());
|
sendGAEMessage(postMessageUrl, json.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendAnswerSdp(final SessionDescription sdp) {
|
public void sendAnswerSdp(final SessionDescription sdp) {
|
||||||
|
if (loopback) {
|
||||||
|
Log.e(TAG, "Sending answer in loopback mode.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
||||||
reportError("Sending answer SDP in non registered state.");
|
reportError("Sending answer SDP in non registered state.");
|
||||||
return;
|
return;
|
||||||
@@ -223,6 +258,9 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
// Send Ice candidate to the other participant.
|
// Send Ice candidate to the other participant.
|
||||||
@Override
|
@Override
|
||||||
public void sendLocalIceCandidate(final IceCandidate candidate) {
|
public void sendLocalIceCandidate(final IceCandidate candidate) {
|
||||||
|
if (loopback) {
|
||||||
|
events.onRemoteIceCandidate(candidate);
|
||||||
|
} else {
|
||||||
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
||||||
reportError("Sending ICE candidate in non registered state.");
|
reportError("Sending ICE candidate in non registered state.");
|
||||||
return;
|
return;
|
||||||
@@ -234,6 +272,7 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
jsonPut(json, "candidate", candidate.sdp);
|
jsonPut(json, "candidate", candidate.sdp);
|
||||||
wsClient.send(json.toString());
|
wsClient.send(json.toString());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
// Helper functions.
|
// Helper functions.
|
||||||
@@ -258,10 +297,19 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class GAEMessage {
|
||||||
|
GAEMessage(String postUrl, String message) {
|
||||||
|
this.postUrl = postUrl;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
public final String postUrl;
|
||||||
|
public final String message;
|
||||||
|
}
|
||||||
|
|
||||||
// Queue a message for sending to the room and send it if already connected.
|
// Queue a message for sending to the room and send it if already connected.
|
||||||
private synchronized void sendGAEMessage(String msg) {
|
private synchronized void sendGAEMessage(String url, String message) {
|
||||||
synchronized (gaePostQueue) {
|
synchronized (gaePostQueue) {
|
||||||
gaePostQueue.add(msg);
|
gaePostQueue.add(new GAEMessage(url, message));
|
||||||
}
|
}
|
||||||
(new AsyncTask<Void, Void, Void>() {
|
(new AsyncTask<Void, Void, Void>() {
|
||||||
public Void doInBackground(Void... unused) {
|
public Void doInBackground(Void... unused) {
|
||||||
@@ -278,27 +326,32 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
for (String msg : gaePostQueue) {
|
for (GAEMessage gaeMessage : gaePostQueue) {
|
||||||
Log.d(TAG, "ROOM SEND: " + msg);
|
Log.d(TAG, "ROOM SEND to " + gaeMessage.postUrl +
|
||||||
|
". Message: " + gaeMessage.message);
|
||||||
// Check if this is 'bye' message and update room connection state.
|
// Check if this is 'bye' message and update room connection state.
|
||||||
// TODO(glaznev): change this to new bye message format:
|
// TODO(glaznev): Uncomment this check and remove check below
|
||||||
// https://apprtc.appspot.com/bye/{roomid}/{clientid}
|
// once new bye message will be supported by 8-dot.
|
||||||
JSONObject json = new JSONObject(msg);
|
//if (gaeMessage.postUrl.contains("bye")) {
|
||||||
|
// roomState = ConnectionState.CLOSED;
|
||||||
|
//}
|
||||||
|
JSONObject json = new JSONObject(gaeMessage.message);
|
||||||
String type = json.optString("type");
|
String type = json.optString("type");
|
||||||
if (type != null && type.equals("bye")) {
|
if (type != null && type.equals("bye")) {
|
||||||
roomState = ConnectionState.CLOSED;
|
roomState = ConnectionState.CLOSED;
|
||||||
}
|
}
|
||||||
// Send POST request.
|
// Send POST request.
|
||||||
URLConnection connection = new URL(
|
HttpURLConnection connection =
|
||||||
signalingParameters.postMessageUrl).openConnection();
|
(HttpURLConnection) new URL(gaeMessage.postUrl).openConnection();
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
connection.setRequestProperty(
|
connection.setRequestProperty(
|
||||||
"content-type", "text/plain; charset=utf-8");
|
"content-type", "text/plain; charset=utf-8");
|
||||||
connection.getOutputStream().write(msg.getBytes("UTF-8"));
|
connection.getOutputStream().write(
|
||||||
|
gaeMessage.message.getBytes("UTF-8"));
|
||||||
String replyHeader = connection.getHeaderField(null);
|
String replyHeader = connection.getHeaderField(null);
|
||||||
if (!replyHeader.startsWith("HTTP/1.1 200 ")) {
|
if (!replyHeader.startsWith("HTTP/1.1 200 ")) {
|
||||||
reportError("Non-200 response to POST: " +
|
reportError("Non-200 response to POST: " +
|
||||||
connection.getHeaderField(null) + " for msg: " + msg);
|
connection.getHeaderField(null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -306,6 +359,7 @@ public class WebSocketRTCClient implements AppRTCClient,
|
|||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
reportError("GAE POST JSON error: " + e.getMessage());
|
reportError("GAE POST JSON error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
gaePostQueue.clear();
|
gaePostQueue.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
talk/examples/android/third_party/autobanh/NOTICE
vendored
Normal file
3
talk/examples/android/third_party/autobanh/NOTICE
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
AutobahnAndroid
|
||||||
|
Copyright 2011,2012 Tavendo GmbH. Licensed under Apache 2.0
|
||||||
|
This product includes software developed at Tavendo GmbH http://www.tavendo.de
|
||||||
Reference in New Issue
Block a user