1 /* 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.appspot.apprtc; 12 13 import org.appspot.apprtc.AppRTCClient.SignalingParameters; 14 import org.appspot.apprtc.util.AsyncHttpURLConnection; 15 import org.appspot.apprtc.util.AsyncHttpURLConnection.AsyncHttpEvents; 16 17 import android.util.Log; 18 19 import org.json.JSONArray; 20 import org.json.JSONException; 21 import org.json.JSONObject; 22 import org.webrtc.IceCandidate; 23 import org.webrtc.PeerConnection; 24 import org.webrtc.SessionDescription; 25 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.net.HttpURLConnection; 29 import java.net.URL; 30 import java.util.LinkedList; 31 import java.util.Scanner; 32 33 /** 34 * AsyncTask that converts an AppRTC room URL into the set of signaling 35 * parameters to use with that room. 36 */ 37 public class RoomParametersFetcher { 38 private static final String TAG = "RoomRTCClient"; 39 private static final int TURN_HTTP_TIMEOUT_MS = 5000; 40 private final RoomParametersFetcherEvents events; 41 private final String roomUrl; 42 private final String roomMessage; 43 private AsyncHttpURLConnection httpConnection; 44 45 /** 46 * Room parameters fetcher callbacks. 47 */ 48 public static interface RoomParametersFetcherEvents { 49 /** 50 * Callback fired once the room's signaling parameters 51 * SignalingParameters are extracted. 52 */ onSignalingParametersReady(final SignalingParameters params)53 public void onSignalingParametersReady(final SignalingParameters params); 54 55 /** 56 * Callback for room parameters extraction error. 57 */ onSignalingParametersError(final String description)58 public void onSignalingParametersError(final String description); 59 } 60 RoomParametersFetcher(String roomUrl, String roomMessage, final RoomParametersFetcherEvents events)61 public RoomParametersFetcher(String roomUrl, String roomMessage, 62 final RoomParametersFetcherEvents events) { 63 this.roomUrl = roomUrl; 64 this.roomMessage = roomMessage; 65 this.events = events; 66 } 67 makeRequest()68 public void makeRequest() { 69 Log.d(TAG, "Connecting to room: " + roomUrl); 70 httpConnection = new AsyncHttpURLConnection( 71 "POST", roomUrl, roomMessage, 72 new AsyncHttpEvents() { 73 @Override 74 public void onHttpError(String errorMessage) { 75 Log.e(TAG, "Room connection error: " + errorMessage); 76 events.onSignalingParametersError(errorMessage); 77 } 78 79 @Override 80 public void onHttpComplete(String response) { 81 roomHttpResponseParse(response); 82 } 83 }); 84 httpConnection.send(); 85 } 86 roomHttpResponseParse(String response)87 private void roomHttpResponseParse(String response) { 88 Log.d(TAG, "Room response: " + response); 89 try { 90 LinkedList<IceCandidate> iceCandidates = null; 91 SessionDescription offerSdp = null; 92 JSONObject roomJson = new JSONObject(response); 93 94 String result = roomJson.getString("result"); 95 if (!result.equals("SUCCESS")) { 96 events.onSignalingParametersError("Room response error: " + result); 97 return; 98 } 99 response = roomJson.getString("params"); 100 roomJson = new JSONObject(response); 101 String roomId = roomJson.getString("room_id"); 102 String clientId = roomJson.getString("client_id"); 103 String wssUrl = roomJson.getString("wss_url"); 104 String wssPostUrl = roomJson.getString("wss_post_url"); 105 boolean initiator = (roomJson.getBoolean("is_initiator")); 106 if (!initiator) { 107 iceCandidates = new LinkedList<IceCandidate>(); 108 String messagesString = roomJson.getString("messages"); 109 JSONArray messages = new JSONArray(messagesString); 110 for (int i = 0; i < messages.length(); ++i) { 111 String messageString = messages.getString(i); 112 JSONObject message = new JSONObject(messageString); 113 String messageType = message.getString("type"); 114 Log.d(TAG, "GAE->C #" + i + " : " + messageString); 115 if (messageType.equals("offer")) { 116 offerSdp = new SessionDescription( 117 SessionDescription.Type.fromCanonicalForm(messageType), 118 message.getString("sdp")); 119 } else if (messageType.equals("candidate")) { 120 IceCandidate candidate = new IceCandidate( 121 message.getString("id"), 122 message.getInt("label"), 123 message.getString("candidate")); 124 iceCandidates.add(candidate); 125 } else { 126 Log.e(TAG, "Unknown message: " + messageString); 127 } 128 } 129 } 130 Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId); 131 Log.d(TAG, "Initiator: " + initiator); 132 Log.d(TAG, "WSS url: " + wssUrl); 133 Log.d(TAG, "WSS POST url: " + wssPostUrl); 134 135 LinkedList<PeerConnection.IceServer> iceServers = 136 iceServersFromPCConfigJSON(roomJson.getString("pc_config")); 137 boolean isTurnPresent = false; 138 for (PeerConnection.IceServer server : iceServers) { 139 Log.d(TAG, "IceServer: " + server); 140 if (server.uri.startsWith("turn:")) { 141 isTurnPresent = true; 142 break; 143 } 144 } 145 // Request TURN servers. 146 if (!isTurnPresent) { 147 LinkedList<PeerConnection.IceServer> turnServers = 148 requestTurnServers(roomJson.getString("turn_url")); 149 for (PeerConnection.IceServer turnServer : turnServers) { 150 Log.d(TAG, "TurnServer: " + turnServer); 151 iceServers.add(turnServer); 152 } 153 } 154 155 SignalingParameters params = new SignalingParameters( 156 iceServers, initiator, 157 clientId, wssUrl, wssPostUrl, 158 offerSdp, iceCandidates); 159 events.onSignalingParametersReady(params); 160 } catch (JSONException e) { 161 events.onSignalingParametersError( 162 "Room JSON parsing error: " + e.toString()); 163 } catch (IOException e) { 164 events.onSignalingParametersError("Room IO error: " + e.toString()); 165 } 166 } 167 168 // Requests & returns a TURN ICE Server based on a request URL. Must be run 169 // off the main thread! requestTurnServers(String url)170 private LinkedList<PeerConnection.IceServer> requestTurnServers(String url) 171 throws IOException, JSONException { 172 LinkedList<PeerConnection.IceServer> turnServers = 173 new LinkedList<PeerConnection.IceServer>(); 174 Log.d(TAG, "Request TURN from: " + url); 175 HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); 176 connection.setConnectTimeout(TURN_HTTP_TIMEOUT_MS); 177 connection.setReadTimeout(TURN_HTTP_TIMEOUT_MS); 178 int responseCode = connection.getResponseCode(); 179 if (responseCode != 200) { 180 throw new IOException("Non-200 response when requesting TURN server from " 181 + url + " : " + connection.getHeaderField(null)); 182 } 183 InputStream responseStream = connection.getInputStream(); 184 String response = drainStream(responseStream); 185 connection.disconnect(); 186 Log.d(TAG, "TURN response: " + response); 187 JSONObject responseJSON = new JSONObject(response); 188 String username = responseJSON.getString("username"); 189 String password = responseJSON.getString("password"); 190 JSONArray turnUris = responseJSON.getJSONArray("uris"); 191 for (int i = 0; i < turnUris.length(); i++) { 192 String uri = turnUris.getString(i); 193 turnServers.add(new PeerConnection.IceServer(uri, username, password)); 194 } 195 return turnServers; 196 } 197 198 // Return the list of ICE servers described by a WebRTCPeerConnection 199 // configuration string. iceServersFromPCConfigJSON( String pcConfig)200 private LinkedList<PeerConnection.IceServer> iceServersFromPCConfigJSON( 201 String pcConfig) throws JSONException { 202 JSONObject json = new JSONObject(pcConfig); 203 JSONArray servers = json.getJSONArray("iceServers"); 204 LinkedList<PeerConnection.IceServer> ret = 205 new LinkedList<PeerConnection.IceServer>(); 206 for (int i = 0; i < servers.length(); ++i) { 207 JSONObject server = servers.getJSONObject(i); 208 String url = server.getString("urls"); 209 String credential = 210 server.has("credential") ? server.getString("credential") : ""; 211 ret.add(new PeerConnection.IceServer(url, "", credential)); 212 } 213 return ret; 214 } 215 216 // Return the contents of an InputStream as a String. drainStream(InputStream in)217 private static String drainStream(InputStream in) { 218 Scanner s = new Scanner(in).useDelimiter("\\A"); 219 return s.hasNext() ? s.next() : ""; 220 } 221 222 } 223