OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * Copyright 2015 The WebRTC Project Authors. All rights reserved. | |
magjed_webrtc
2016/05/10 14:50:02
nit: 2016
sakal
2016/05/11 08:38:50
Done.
| |
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 android.util.Log; | |
14 | |
15 import org.appspot.apprtc.util.LooperExecutor; | |
16 import org.json.JSONArray; | |
17 import org.json.JSONException; | |
18 import org.json.JSONObject; | |
19 import org.webrtc.IceCandidate; | |
20 import org.webrtc.PeerConnection; | |
21 import org.webrtc.SessionDescription; | |
22 | |
23 import java.util.LinkedList; | |
24 import java.util.regex.Matcher; | |
25 import java.util.regex.Pattern; | |
26 | |
27 /** | |
28 * Implementation of AppRTCClient that uses direct TCP connection as the signali ng channel. | |
29 * This eliminates the need for an external server. This class should not be use d for loopback | |
30 * connections. They are not supported. | |
magjed_webrtc
2016/05/10 14:50:02
nit: Merge last two sentences to something like: "
sakal
2016/05/11 08:38:50
Done.
| |
31 */ | |
32 public class DirectRTCClient implements AppRTCClient, TCPChannelClient.TCPChanne lEvents { | |
33 private static final String TAG = "DirectRTCClient"; | |
34 private static final int DEFAULT_PORT = 8888; | |
35 | |
36 // Regex pattern used for checking if room id looks like an IP. | |
37 static final Pattern IP_PATTERN = Pattern.compile( | |
38 "(" | |
magjed_webrtc
2016/05/10 14:50:02
nit: 4 space indent. Same for other places as well
sakal
2016/05/11 08:38:49
Done.
| |
39 // IPv4 | |
40 + "((\\d+\\.){3}\\d+)|" | |
41 // IPv6 | |
42 + "\\[((([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})?::" | |
43 + "(([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})?)\\]|" | |
44 + "\\[(([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})\\]|" | |
45 // IPv6 without [] | |
46 + "((([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:)*[0 -9a-fA-F]{1,4})?)|" | |
47 + "(([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})|" | |
48 // Literals | |
49 + "localhost" | |
50 + ")" | |
51 // Optional port number | |
52 + "(:\\d+)?" | |
53 ); | |
54 | |
55 private final LooperExecutor executor; | |
56 private final SignalingEvents events; | |
57 private TCPChannelClient tcpClient; | |
58 private RoomConnectionParameters connectionParameters; | |
59 | |
60 private enum ConnectionState { | |
61 NEW, CONNECTED, CLOSED, ERROR | |
62 }; | |
63 | |
64 // All alterations of the room state should be done from inside the looper thr ead | |
65 private ConnectionState roomState; | |
66 | |
67 public DirectRTCClient(SignalingEvents events) { | |
68 this.events = events; | |
69 executor = new LooperExecutor(); | |
70 | |
71 executor.requestStart(); | |
72 roomState = ConnectionState.NEW; | |
73 } | |
74 | |
75 /** | |
76 * Connects to the room, roomId in connectionsParameters is required. roomId m ust be a valid | |
77 * IP address matching IP_PATTERN. | |
78 */ | |
79 @Override | |
80 public void connectToRoom(RoomConnectionParameters connectionParameters) { | |
81 this.connectionParameters = connectionParameters; | |
82 | |
83 // Loopback connections should always use the WebSocketRTCClient | |
magjed_webrtc
2016/05/10 14:50:02
nit: The code is very clear, so I would just remov
sakal
2016/05/11 08:38:50
Done.
| |
84 // They are not supported by this client | |
85 if (connectionParameters.loopback) { | |
86 reportError("Loopback connections aren't supported by DirectRTCClient."); | |
87 } | |
88 | |
89 executor.execute(new Runnable() { | |
90 @Override | |
91 public void run() { | |
92 connectToRoomInternal(); | |
93 } | |
94 }); | |
95 } | |
96 | |
97 @Override | |
98 public void disconnectFromRoom() { | |
99 executor.execute(new Runnable() { | |
100 @Override | |
101 public void run() { | |
102 disconnectFromRoomInternal(); | |
103 } | |
104 }); | |
105 executor.requestStop(); | |
106 } | |
107 | |
108 /** | |
109 * Connects to the room. | |
110 * | |
111 * Runs on the looper thread. | |
112 */ | |
113 private void connectToRoomInternal() { | |
114 this.roomState = ConnectionState.NEW; | |
115 | |
116 String endpoint = connectionParameters.roomId; | |
117 | |
118 Matcher matcher = IP_PATTERN.matcher(endpoint); | |
119 matcher.matches(); | |
magjed_webrtc
2016/05/10 14:50:02
Check if matches() returns false and/or check Ille
sakal
2016/05/11 08:38:50
Done.
| |
120 | |
121 String ip = matcher.group(1); | |
122 String portStr = endpoint.substring(ip.length()); | |
magjed_webrtc
2016/05/10 14:50:02
nit: I think it's a little bit clearer if you use
sakal
2016/05/11 08:38:49
matcher.group(2) would returns some part inside th
| |
123 int port; | |
124 | |
125 if (!portStr.isEmpty()) { | |
126 port = Integer.parseInt(portStr.substring(1)); | |
127 } else { | |
128 port = DEFAULT_PORT; | |
129 } | |
130 | |
131 tcpClient = new TCPChannelClient(executor, this, ip, port); | |
132 } | |
133 | |
134 /** | |
135 * Disconnects from the room. | |
136 * | |
137 * Runs on the looper thread. | |
138 */ | |
139 private void disconnectFromRoomInternal() { | |
140 roomState = ConnectionState.CLOSED; | |
141 | |
142 if (tcpClient != null) { | |
143 tcpClient.disconnect(); | |
144 tcpClient = null; | |
145 } | |
146 } | |
147 | |
148 @Override | |
149 public void sendOfferSdp(final SessionDescription sdp) { | |
150 executor.execute(new Runnable() { | |
151 @Override | |
152 public void run() { | |
153 if (roomState != ConnectionState.CONNECTED) { | |
154 reportError("Sending offer SDP in non connected state."); | |
155 return; | |
156 } | |
157 JSONObject json = new JSONObject(); | |
158 jsonPut(json, "sdp", sdp.description); | |
159 jsonPut(json, "type", "offer"); | |
160 sendMessage(json.toString()); | |
161 } | |
162 }); | |
163 } | |
164 | |
165 @Override | |
166 public void sendAnswerSdp(final SessionDescription sdp) { | |
167 executor.execute(new Runnable() { | |
168 @Override | |
169 public void run() { | |
170 JSONObject json = new JSONObject(); | |
171 jsonPut(json, "sdp", sdp.description); | |
172 jsonPut(json, "type", "answer"); | |
173 sendMessage(json.toString()); | |
174 } | |
175 }); | |
176 } | |
177 | |
178 @Override | |
179 public void sendLocalIceCandidate(final IceCandidate candidate) { | |
180 executor.execute(new Runnable() { | |
181 @Override | |
182 public void run() { | |
183 JSONObject json = new JSONObject(); | |
184 jsonPut(json, "type", "candidate"); | |
185 jsonPut(json, "label", candidate.sdpMLineIndex); | |
186 jsonPut(json, "id", candidate.sdpMid); | |
187 jsonPut(json, "candidate", candidate.sdp); | |
188 | |
189 if (roomState != ConnectionState.CONNECTED) { | |
190 reportError("Sending ICE candidate in non connected state."); | |
191 return; | |
192 } | |
193 sendMessage(json.toString()); | |
194 } | |
195 }); | |
196 } | |
197 | |
198 /** Send removed Ice candidates to the other participant. */ | |
199 @Override | |
200 public void sendLocalIceCandidateRemovals(final IceCandidate[] candidates) { | |
201 executor.execute(new Runnable() { | |
202 @Override | |
203 public void run() { | |
204 JSONObject json = new JSONObject(); | |
205 jsonPut(json, "type", "remove-candidates"); | |
206 JSONArray jsonArray = new JSONArray(); | |
207 for (final IceCandidate candidate : candidates) { | |
208 jsonArray.put(toJsonCandidate(candidate)); | |
209 } | |
210 jsonPut(json, "candidates", jsonArray); | |
211 | |
212 if (roomState != ConnectionState.CONNECTED) { | |
213 reportError("Sending ICE candidate removals in non connected state."); | |
214 return; | |
215 } | |
216 sendMessage(json.toString()); | |
217 } | |
218 }); | |
219 } | |
220 | |
221 // ------------------------------------------------------------------- | |
222 // TCPChannelClient event handlers | |
223 | |
224 /** | |
225 * If the client is the server side, this will trigger onConnectedToRoom. | |
226 * | |
227 * @param isServer | |
228 */ | |
229 @Override | |
230 public void onTCPConnected(boolean isServer) { | |
231 if (isServer) { | |
232 roomState = ConnectionState.CONNECTED; | |
233 | |
234 SignalingParameters parameters = new SignalingParameters( | |
235 new LinkedList<PeerConnection.IceServer>(), isServer, null, null, null, null, null); | |
magjed_webrtc
2016/05/10 14:50:02
nit: Add comments about the null parameters, e.g.
sakal
2016/05/11 08:38:50
Done.
| |
236 events.onConnectedToRoom(parameters); | |
237 } | |
238 } | |
239 | |
240 @Override | |
241 public void onTCPMessage(String msg) { | |
242 try { | |
243 JSONObject json = new JSONObject(msg); | |
244 String type = json.optString("type"); | |
245 if (type.equals("candidate")) { | |
246 events.onRemoteIceCandidate(toJavaCandidate(json)); | |
247 } else if (type.equals("remove-candidates")) { | |
248 JSONArray candidateArray = json.getJSONArray("candidates"); | |
249 IceCandidate[] candidates = new IceCandidate[candidateArray.length()]; | |
250 for (int i = 0; i < candidateArray.length(); ++i) { | |
251 candidates[i] = toJavaCandidate(candidateArray.getJSONObject(i)); | |
252 } | |
253 events.onRemoteIceCandidatesRemoved(candidates); | |
254 } else if (type.equals("answer")) { | |
255 SessionDescription sdp = new SessionDescription( | |
256 SessionDescription.Type.fromCanonicalForm(type), | |
257 json.getString("sdp")); | |
258 events.onRemoteDescription(sdp); | |
259 } else if (type.equals("offer")) { | |
260 SessionDescription sdp = new SessionDescription( | |
261 SessionDescription.Type.fromCanonicalForm(type), | |
262 json.getString("sdp")); | |
263 | |
264 SignalingParameters parameters = new SignalingParameters( | |
265 new LinkedList<PeerConnection.IceServer>(), false, null, null, n ull, sdp, null); | |
magjed_webrtc
2016/05/10 14:50:02
ditto: Add comments about the null parameters, e.g
sakal
2016/05/11 08:38:49
Done.
| |
266 roomState = ConnectionState.CONNECTED; | |
267 events.onConnectedToRoom(parameters); | |
268 } else if (type.equals("bye")) { | |
269 events.onChannelClose(); | |
270 } else { | |
271 reportError("Unexpected TCP message: " + msg); | |
272 } | |
273 } catch (JSONException e) { | |
274 reportError("TCP message JSON parsing error: " + e.toString()); | |
275 } | |
276 } | |
277 | |
278 @Override | |
279 public void onTCPError(String description) { | |
280 reportError("TCP connection error: " + description); | |
281 } | |
282 | |
283 @Override | |
284 public void onTCPClose() { | |
285 events.onChannelClose(); | |
286 } | |
287 | |
288 // -------------------------------------------------------------------- | |
289 // Helper functions. | |
290 private void reportError(final String errorMessage) { | |
291 Log.e(TAG, errorMessage); | |
292 executor.execute(new Runnable() { | |
293 @Override | |
294 public void run() { | |
295 if (roomState != ConnectionState.ERROR) { | |
296 roomState = ConnectionState.ERROR; | |
297 events.onChannelError(errorMessage); | |
298 } | |
299 } | |
300 }); | |
301 } | |
302 | |
303 private void sendMessage(final String message) { | |
304 executor.execute(new Runnable() { | |
305 @Override | |
306 public void run() { | |
307 tcpClient.send(message); | |
308 } | |
309 }); | |
310 } | |
311 | |
312 // Put a |key|->|value| mapping in |json|. | |
313 private static void jsonPut(JSONObject json, String key, Object value) { | |
314 try { | |
315 json.put(key, value); | |
316 } catch (JSONException e) { | |
317 throw new RuntimeException(e); | |
318 } | |
319 } | |
320 | |
321 // Converts a Java candidate to a JSONObject. | |
322 private static JSONObject toJsonCandidate(final IceCandidate candidate) { | |
323 JSONObject json = new JSONObject(); | |
324 jsonPut(json, "label", candidate.sdpMLineIndex); | |
325 jsonPut(json, "id", candidate.sdpMid); | |
326 jsonPut(json, "candidate", candidate.sdp); | |
327 return json; | |
328 } | |
329 | |
330 // Converts a JSON candidate to a Java object. | |
331 private static IceCandidate toJavaCandidate(JSONObject json) throws JSONExcept ion { | |
332 return new IceCandidate(json.getString("id"), | |
333 json.getInt("label"), | |
334 json.getString("candidate")); | |
335 } | |
336 } | |
OLD | NEW |