/*
|
* Copyright 2014 The WebRTC Project Authors. All rights reserved.
|
*
|
* Use of this source code is governed by a BSD-style license
|
* that can be found in the LICENSE file in the root of the source
|
* tree. An additional intellectual property rights grant can be found
|
* in the file PATENTS. All contributing project authors may
|
* be found in the AUTHORS file in the root of the source tree.
|
*/
|
|
package org.appspot.apprtc;
|
|
import android.util.Log;
|
|
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
|
import org.appspot.apprtc.util.AsyncHttpURLConnection;
|
import org.appspot.apprtc.util.AsyncHttpURLConnection.AsyncHttpEvents;
|
import org.json.JSONArray;
|
import org.json.JSONException;
|
import org.json.JSONObject;
|
import org.webrtc.IceCandidate;
|
import org.webrtc.PeerConnection;
|
import org.webrtc.SessionDescription;
|
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.net.HttpURLConnection;
|
import java.net.URL;
|
import java.util.ArrayList;
|
import java.util.List;
|
import java.util.Scanner;
|
|
/**
|
* AsyncTask that converts an AppRTC room URL into the set of signaling
|
* parameters to use with that room.
|
*/
|
public class RoomParametersFetcher {
|
private static final String TAG = "RoomRTCClient";
|
private static final int TURN_HTTP_TIMEOUT_MS = 5000;
|
private final RoomParametersFetcherEvents events;
|
private final String roomUrl;
|
private final String roomMessage;
|
|
public RoomParametersFetcher(
|
String roomUrl, String roomMessage, final RoomParametersFetcherEvents events) {
|
this.roomUrl = roomUrl;
|
this.roomMessage = roomMessage;
|
this.events = events;
|
}
|
|
// Return the contents of an InputStream as a String.
|
private static String drainStream(InputStream in) {
|
Scanner s = new Scanner(in, "UTF-8").useDelimiter("\\A");
|
return s.hasNext() ? s.next() : "";
|
}
|
|
public void makeRequest() {
|
Log.d(TAG, "Connecting to room: " + roomUrl);
|
AsyncHttpURLConnection httpConnection =
|
new AsyncHttpURLConnection("POST", roomUrl, roomMessage, 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();
|
}
|
|
private void roomHttpResponseParse(String response) {
|
Log.d(TAG, "Room response: " + response);
|
try {
|
List<IceCandidate> iceCandidates = null;
|
SessionDescription offerSdp = null;
|
JSONObject roomJson = new JSONObject(response);
|
|
String result = roomJson.getString("result");
|
if (!result.equals("SUCCESS")) {
|
events.onSignalingParametersError("Room response error: " + result);
|
return;
|
}
|
response = roomJson.getString("params");
|
roomJson = new JSONObject(response);
|
String roomId = roomJson.getString("room_id");
|
String clientId = roomJson.getString("client_id");
|
String wssUrl = roomJson.getString("wss_url");
|
String wssPostUrl = roomJson.getString("wss_post_url");
|
boolean initiator = (roomJson.getBoolean("is_initiator"));
|
if (!initiator) {
|
iceCandidates = new ArrayList<>();
|
String messagesString = roomJson.getString("messages");
|
JSONArray messages = new JSONArray(messagesString);
|
for (int i = 0; i < messages.length(); ++i) {
|
String messageString = messages.getString(i);
|
JSONObject message = new JSONObject(messageString);
|
String messageType = message.getString("type");
|
Log.d(TAG, "GAE->C #" + i + " : " + messageString);
|
if (messageType.equals("offer")) {
|
offerSdp = new SessionDescription(
|
SessionDescription.Type.fromCanonicalForm(messageType), message.getString("sdp"));
|
} else if (messageType.equals("candidate")) {
|
IceCandidate candidate = new IceCandidate(
|
message.getString("id"), message.getInt("label"), message.getString("candidate"));
|
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, "WSS url: " + wssUrl);
|
Log.d(TAG, "WSS POST url: " + wssPostUrl);
|
|
List<PeerConnection.IceServer> iceServers =
|
iceServersFromPCConfigJSON(roomJson.getString("pc_config"));
|
boolean isTurnPresent = false;
|
for (PeerConnection.IceServer server : iceServers) {
|
Log.d(TAG, "IceServer: " + server);
|
for (String uri : server.urls) {
|
if (uri.startsWith("turn:")) {
|
isTurnPresent = true;
|
break;
|
}
|
}
|
}
|
// Request TURN servers.
|
if (!isTurnPresent && !roomJson.optString("ice_server_url").isEmpty()) {
|
List<PeerConnection.IceServer> turnServers =
|
requestTurnServers(roomJson.getString("ice_server_url"));
|
for (PeerConnection.IceServer turnServer : turnServers) {
|
Log.d(TAG, "TurnServer: " + turnServer);
|
iceServers.add(turnServer);
|
}
|
}
|
|
SignalingParameters params = new SignalingParameters(
|
iceServers, initiator, 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());
|
}
|
}
|
|
// Requests & returns a TURN ICE Server based on a request URL. Must be run
|
// off the main thread!
|
private List<PeerConnection.IceServer> requestTurnServers(String url)
|
throws IOException, JSONException {
|
List<PeerConnection.IceServer> turnServers = new ArrayList<>();
|
Log.d(TAG, "Request TURN from: " + url);
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
connection.setDoOutput(true);
|
connection.setRequestProperty("REFERER", "https://appr.tc");
|
connection.setConnectTimeout(TURN_HTTP_TIMEOUT_MS);
|
connection.setReadTimeout(TURN_HTTP_TIMEOUT_MS);
|
int responseCode = connection.getResponseCode();
|
if (responseCode != 200) {
|
throw new IOException("Non-200 response when requesting TURN server from " + url + " : "
|
+ connection.getHeaderField(null));
|
}
|
InputStream responseStream = connection.getInputStream();
|
String response = drainStream(responseStream);
|
connection.disconnect();
|
Log.d(TAG, "TURN response: " + response);
|
JSONObject responseJSON = new JSONObject(response);
|
JSONArray iceServers = responseJSON.getJSONArray("iceServers");
|
for (int i = 0; i < iceServers.length(); ++i) {
|
JSONObject server = iceServers.getJSONObject(i);
|
JSONArray turnUrls = server.getJSONArray("urls");
|
String username = server.has("username") ? server.getString("username") : "";
|
String credential = server.has("credential") ? server.getString("credential") : "";
|
for (int j = 0; j < turnUrls.length(); j++) {
|
String turnUrl = turnUrls.getString(j);
|
PeerConnection.IceServer turnServer =
|
PeerConnection.IceServer.builder(turnUrl)
|
.setUsername(username)
|
.setPassword(credential)
|
.createIceServer();
|
turnServers.add(turnServer);
|
}
|
}
|
return turnServers;
|
}
|
|
// Return the list of ICE servers described by a WebRTCPeerConnection
|
// configuration string.
|
private List<PeerConnection.IceServer> iceServersFromPCConfigJSON(String pcConfig)
|
throws JSONException {
|
JSONObject json = new JSONObject(pcConfig);
|
JSONArray servers = json.getJSONArray("iceServers");
|
List<PeerConnection.IceServer> ret = new ArrayList<>();
|
for (int i = 0; i < servers.length(); ++i) {
|
JSONObject server = servers.getJSONObject(i);
|
String url = server.getString("urls");
|
String credential = server.has("credential") ? server.getString("credential") : "";
|
PeerConnection.IceServer turnServer =
|
PeerConnection.IceServer.builder(url)
|
.setPassword(credential)
|
.createIceServer();
|
ret.add(turnServer);
|
}
|
return ret;
|
}
|
|
/**
|
* Room parameters fetcher callbacks.
|
*/
|
public interface RoomParametersFetcherEvents {
|
/**
|
* Callback fired once the room's signaling parameters
|
* SignalingParameters are extracted.
|
*/
|
void onSignalingParametersReady(final SignalingParameters params);
|
|
/**
|
* Callback for room parameters extraction error.
|
*/
|
void onSignalingParametersError(final String description);
|
}
|
}
|