OLD | NEW |
| (Empty) |
1 /* | |
2 * libjingle | |
3 * Copyright 2014 Google Inc. | |
4 * | |
5 * Redistribution and use in source and binary forms, with or without | |
6 * modification, are permitted provided that the following conditions are met: | |
7 * | |
8 * 1. Redistributions of source code must retain the above copyright notice, | |
9 * this list of conditions and the following disclaimer. | |
10 * 2. Redistributions in binary form must reproduce the above copyright notice, | |
11 * this list of conditions and the following disclaimer in the documentation | |
12 * and/or other materials provided with the distribution. | |
13 * 3. The name of the author may not be used to endorse or promote products | |
14 * derived from this software without specific prior written permission. | |
15 * | |
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | |
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
26 */ | |
27 | |
28 package org.appspot.apprtc; | |
29 | |
30 import org.appspot.apprtc.AppRTCClient.SignalingParameters; | |
31 import org.appspot.apprtc.util.AsyncHttpURLConnection; | |
32 import org.appspot.apprtc.util.AsyncHttpURLConnection.AsyncHttpEvents; | |
33 | |
34 import android.util.Log; | |
35 | |
36 import org.json.JSONArray; | |
37 import org.json.JSONException; | |
38 import org.json.JSONObject; | |
39 import org.webrtc.IceCandidate; | |
40 import org.webrtc.PeerConnection; | |
41 import org.webrtc.SessionDescription; | |
42 | |
43 import java.io.IOException; | |
44 import java.io.InputStream; | |
45 import java.net.HttpURLConnection; | |
46 import java.net.URL; | |
47 import java.util.LinkedList; | |
48 import java.util.Scanner; | |
49 | |
50 /** | |
51 * AsyncTask that converts an AppRTC room URL into the set of signaling | |
52 * parameters to use with that room. | |
53 */ | |
54 public class RoomParametersFetcher { | |
55 private static final String TAG = "RoomRTCClient"; | |
56 private static final int TURN_HTTP_TIMEOUT_MS = 5000; | |
57 private final RoomParametersFetcherEvents events; | |
58 private final String roomUrl; | |
59 private final String roomMessage; | |
60 private AsyncHttpURLConnection httpConnection; | |
61 | |
62 /** | |
63 * Room parameters fetcher callbacks. | |
64 */ | |
65 public static interface RoomParametersFetcherEvents { | |
66 /** | |
67 * Callback fired once the room's signaling parameters | |
68 * SignalingParameters are extracted. | |
69 */ | |
70 public void onSignalingParametersReady(final SignalingParameters params); | |
71 | |
72 /** | |
73 * Callback for room parameters extraction error. | |
74 */ | |
75 public void onSignalingParametersError(final String description); | |
76 } | |
77 | |
78 public RoomParametersFetcher(String roomUrl, String roomMessage, | |
79 final RoomParametersFetcherEvents events) { | |
80 this.roomUrl = roomUrl; | |
81 this.roomMessage = roomMessage; | |
82 this.events = events; | |
83 } | |
84 | |
85 public void makeRequest() { | |
86 Log.d(TAG, "Connecting to room: " + roomUrl); | |
87 httpConnection = new AsyncHttpURLConnection( | |
88 "POST", roomUrl, roomMessage, | |
89 new AsyncHttpEvents() { | |
90 @Override | |
91 public void onHttpError(String errorMessage) { | |
92 Log.e(TAG, "Room connection error: " + errorMessage); | |
93 events.onSignalingParametersError(errorMessage); | |
94 } | |
95 | |
96 @Override | |
97 public void onHttpComplete(String response) { | |
98 roomHttpResponseParse(response); | |
99 } | |
100 }); | |
101 httpConnection.send(); | |
102 } | |
103 | |
104 private void roomHttpResponseParse(String response) { | |
105 Log.d(TAG, "Room response: " + response); | |
106 try { | |
107 LinkedList<IceCandidate> iceCandidates = null; | |
108 SessionDescription offerSdp = null; | |
109 JSONObject roomJson = new JSONObject(response); | |
110 | |
111 String result = roomJson.getString("result"); | |
112 if (!result.equals("SUCCESS")) { | |
113 events.onSignalingParametersError("Room response error: " + result); | |
114 return; | |
115 } | |
116 response = roomJson.getString("params"); | |
117 roomJson = new JSONObject(response); | |
118 String roomId = roomJson.getString("room_id"); | |
119 String clientId = roomJson.getString("client_id"); | |
120 String wssUrl = roomJson.getString("wss_url"); | |
121 String wssPostUrl = roomJson.getString("wss_post_url"); | |
122 boolean initiator = (roomJson.getBoolean("is_initiator")); | |
123 if (!initiator) { | |
124 iceCandidates = new LinkedList<IceCandidate>(); | |
125 String messagesString = roomJson.getString("messages"); | |
126 JSONArray messages = new JSONArray(messagesString); | |
127 for (int i = 0; i < messages.length(); ++i) { | |
128 String messageString = messages.getString(i); | |
129 JSONObject message = new JSONObject(messageString); | |
130 String messageType = message.getString("type"); | |
131 Log.d(TAG, "GAE->C #" + i + " : " + messageString); | |
132 if (messageType.equals("offer")) { | |
133 offerSdp = new SessionDescription( | |
134 SessionDescription.Type.fromCanonicalForm(messageType), | |
135 message.getString("sdp")); | |
136 } else if (messageType.equals("candidate")) { | |
137 IceCandidate candidate = new IceCandidate( | |
138 message.getString("id"), | |
139 message.getInt("label"), | |
140 message.getString("candidate")); | |
141 iceCandidates.add(candidate); | |
142 } else { | |
143 Log.e(TAG, "Unknown message: " + messageString); | |
144 } | |
145 } | |
146 } | |
147 Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId); | |
148 Log.d(TAG, "Initiator: " + initiator); | |
149 Log.d(TAG, "WSS url: " + wssUrl); | |
150 Log.d(TAG, "WSS POST url: " + wssPostUrl); | |
151 | |
152 LinkedList<PeerConnection.IceServer> iceServers = | |
153 iceServersFromPCConfigJSON(roomJson.getString("pc_config")); | |
154 boolean isTurnPresent = false; | |
155 for (PeerConnection.IceServer server : iceServers) { | |
156 Log.d(TAG, "IceServer: " + server); | |
157 if (server.uri.startsWith("turn:")) { | |
158 isTurnPresent = true; | |
159 break; | |
160 } | |
161 } | |
162 // Request TURN servers. | |
163 if (!isTurnPresent) { | |
164 LinkedList<PeerConnection.IceServer> turnServers = | |
165 requestTurnServers(roomJson.getString("turn_url")); | |
166 for (PeerConnection.IceServer turnServer : turnServers) { | |
167 Log.d(TAG, "TurnServer: " + turnServer); | |
168 iceServers.add(turnServer); | |
169 } | |
170 } | |
171 | |
172 SignalingParameters params = new SignalingParameters( | |
173 iceServers, initiator, | |
174 clientId, wssUrl, wssPostUrl, | |
175 offerSdp, iceCandidates); | |
176 events.onSignalingParametersReady(params); | |
177 } catch (JSONException e) { | |
178 events.onSignalingParametersError( | |
179 "Room JSON parsing error: " + e.toString()); | |
180 } catch (IOException e) { | |
181 events.onSignalingParametersError("Room IO error: " + e.toString()); | |
182 } | |
183 } | |
184 | |
185 // Requests & returns a TURN ICE Server based on a request URL. Must be run | |
186 // off the main thread! | |
187 private LinkedList<PeerConnection.IceServer> requestTurnServers(String url) | |
188 throws IOException, JSONException { | |
189 LinkedList<PeerConnection.IceServer> turnServers = | |
190 new LinkedList<PeerConnection.IceServer>(); | |
191 Log.d(TAG, "Request TURN from: " + url); | |
192 HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnecti
on(); | |
193 connection.setConnectTimeout(TURN_HTTP_TIMEOUT_MS); | |
194 connection.setReadTimeout(TURN_HTTP_TIMEOUT_MS); | |
195 int responseCode = connection.getResponseCode(); | |
196 if (responseCode != 200) { | |
197 throw new IOException("Non-200 response when requesting TURN server from " | |
198 + url + " : " + connection.getHeaderField(null)); | |
199 } | |
200 InputStream responseStream = connection.getInputStream(); | |
201 String response = drainStream(responseStream); | |
202 connection.disconnect(); | |
203 Log.d(TAG, "TURN response: " + response); | |
204 JSONObject responseJSON = new JSONObject(response); | |
205 String username = responseJSON.getString("username"); | |
206 String password = responseJSON.getString("password"); | |
207 JSONArray turnUris = responseJSON.getJSONArray("uris"); | |
208 for (int i = 0; i < turnUris.length(); i++) { | |
209 String uri = turnUris.getString(i); | |
210 turnServers.add(new PeerConnection.IceServer(uri, username, password)); | |
211 } | |
212 return turnServers; | |
213 } | |
214 | |
215 // Return the list of ICE servers described by a WebRTCPeerConnection | |
216 // configuration string. | |
217 private LinkedList<PeerConnection.IceServer> iceServersFromPCConfigJSON( | |
218 String pcConfig) throws JSONException { | |
219 JSONObject json = new JSONObject(pcConfig); | |
220 JSONArray servers = json.getJSONArray("iceServers"); | |
221 LinkedList<PeerConnection.IceServer> ret = | |
222 new LinkedList<PeerConnection.IceServer>(); | |
223 for (int i = 0; i < servers.length(); ++i) { | |
224 JSONObject server = servers.getJSONObject(i); | |
225 String url = server.getString("urls"); | |
226 String credential = | |
227 server.has("credential") ? server.getString("credential") : ""; | |
228 ret.add(new PeerConnection.IceServer(url, "", credential)); | |
229 } | |
230 return ret; | |
231 } | |
232 | |
233 // Return the contents of an InputStream as a String. | |
234 private static String drainStream(InputStream in) { | |
235 Scanner s = new Scanner(in).useDelimiter("\\A"); | |
236 return s.hasNext() ? s.next() : ""; | |
237 } | |
238 | |
239 } | |
OLD | NEW |