| /* | |
| * 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 java.io.IOException; | |
| import java.io.InputStream; | |
| import java.net.HttpURLConnection; | |
| import java.net.URL; | |
| import java.util.ArrayList; | |
| import java.util.Scanner; | |
| import java.util.List; | |
| 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; | |
| /** | |
| * 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; | |
| /** | |
| * 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); | |
| } | |
| public RoomParametersFetcher( | |
| String roomUrl, String roomMessage, final RoomParametersFetcherEvents events) { | |
| this.roomUrl = roomUrl; | |
| this.roomMessage = roomMessage; | |
| this.events = events; | |
| } | |
| 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! | |
| @SuppressWarnings("UseNetworkAnnotations") | |
| 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; | |
| } | |
| // Return the contents of an InputStream as a String. | |
| @SuppressWarnings("ScannerUseDelimiter") | |
| private static String drainStream(InputStream in) { | |
| Scanner s = new Scanner(in, "UTF-8").useDelimiter("\\A"); | |
| return s.hasNext() ? s.next() : ""; | |
| } | |
| } |