Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(344)

Side by Side Diff: webrtc/examples/androidapp/src/org/appspot/apprtc/DirectRTCClient.java

Issue 1963053002: Direct IP connect functionality for AppRTC Android demo. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Removed empty param description since they are not allowed according to the style guide. Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright 2016 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 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 does not support loopback
30 * connections.
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 "("
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 if (connectionParameters.loopback) {
84 reportError("Loopback connections aren't supported by DirectRTCClient.");
85 }
86
87 executor.execute(new Runnable() {
88 @Override
89 public void run() {
90 connectToRoomInternal();
91 }
92 });
93 }
94
95 @Override
96 public void disconnectFromRoom() {
97 executor.execute(new Runnable() {
98 @Override
99 public void run() {
100 disconnectFromRoomInternal();
101 }
102 });
103 executor.requestStop();
104 }
105
106 /**
107 * Connects to the room.
108 *
109 * Runs on the looper thread.
110 */
111 private void connectToRoomInternal() {
112 this.roomState = ConnectionState.NEW;
113
114 String endpoint = connectionParameters.roomId;
115
116 Matcher matcher = IP_PATTERN.matcher(endpoint);
117 if(!matcher.matches()) {
magjed_webrtc 2016/05/11 11:22:56 nit: add space between 'if' and '('
sakal 2016/05/11 12:19:43 Done.
118 reportError("roomId must match IP_PATTERN for DirectRTCClient.");
119 return;
120 }
121
122 String ip = matcher.group(1);
123 String portStr = matcher.group(matcher.groupCount());
124 int port;
125
126 if (portStr != null) {
127 port = Integer.parseInt(portStr.substring(1));
magjed_webrtc 2016/05/11 11:22:56 This will never throw NumberFormatException as lon
sakal 2016/05/11 12:19:43 It will throw a NumberFormatException if the numbe
128 } else {
129 port = DEFAULT_PORT;
130 }
131
132 tcpClient = new TCPChannelClient(executor, this, ip, port);
133 }
134
135 /**
136 * Disconnects from the room.
137 *
138 * Runs on the looper thread.
139 */
140 private void disconnectFromRoomInternal() {
141 roomState = ConnectionState.CLOSED;
142
143 if (tcpClient != null) {
144 tcpClient.disconnect();
145 tcpClient = null;
146 }
147 }
148
149 @Override
150 public void sendOfferSdp(final SessionDescription sdp) {
151 executor.execute(new Runnable() {
152 @Override
153 public void run() {
154 if (roomState != ConnectionState.CONNECTED) {
155 reportError("Sending offer SDP in non connected state.");
156 return;
157 }
158 JSONObject json = new JSONObject();
159 jsonPut(json, "sdp", sdp.description);
160 jsonPut(json, "type", "offer");
161 sendMessage(json.toString());
162 }
163 });
164 }
165
166 @Override
167 public void sendAnswerSdp(final SessionDescription sdp) {
168 executor.execute(new Runnable() {
169 @Override
170 public void run() {
171 JSONObject json = new JSONObject();
172 jsonPut(json, "sdp", sdp.description);
173 jsonPut(json, "type", "answer");
174 sendMessage(json.toString());
175 }
176 });
177 }
178
179 @Override
180 public void sendLocalIceCandidate(final IceCandidate candidate) {
181 executor.execute(new Runnable() {
182 @Override
183 public void run() {
184 JSONObject json = new JSONObject();
185 jsonPut(json, "type", "candidate");
186 jsonPut(json, "label", candidate.sdpMLineIndex);
187 jsonPut(json, "id", candidate.sdpMid);
188 jsonPut(json, "candidate", candidate.sdp);
189
190 if (roomState != ConnectionState.CONNECTED) {
191 reportError("Sending ICE candidate in non connected state.");
192 return;
193 }
194 sendMessage(json.toString());
195 }
196 });
197 }
198
199 /** Send removed Ice candidates to the other participant. */
200 @Override
201 public void sendLocalIceCandidateRemovals(final IceCandidate[] candidates) {
202 executor.execute(new Runnable() {
203 @Override
204 public void run() {
205 JSONObject json = new JSONObject();
206 jsonPut(json, "type", "remove-candidates");
207 JSONArray jsonArray = new JSONArray();
208 for (final IceCandidate candidate : candidates) {
209 jsonArray.put(toJsonCandidate(candidate));
210 }
211 jsonPut(json, "candidates", jsonArray);
212
213 if (roomState != ConnectionState.CONNECTED) {
214 reportError("Sending ICE candidate removals in non connected state.");
215 return;
216 }
217 sendMessage(json.toString());
218 }
219 });
220 }
221
222 // -------------------------------------------------------------------
223 // TCPChannelClient event handlers
224
225 /**
226 * If the client is the server side, this will trigger onConnectedToRoom.
227 */
228 @Override
229 public void onTCPConnected(boolean isServer) {
230 if (isServer) {
231 roomState = ConnectionState.CONNECTED;
232
233 SignalingParameters parameters = new SignalingParameters(
234 // Ice servers are not needed for direct connections.
235 new LinkedList<PeerConnection.IceServer>(),
236 isServer, // Server side acts as the initiator on direct connections.
237 null, // clientId
238 null, // wssUrl
239 null, // wwsPostUrl
240 null, // offerSdp
241 null // iceCandidates
242 );
243 events.onConnectedToRoom(parameters);
244 }
245 }
246
247 @Override
248 public void onTCPMessage(String msg) {
249 try {
250 JSONObject json = new JSONObject(msg);
251 String type = json.optString("type");
252 if (type.equals("candidate")) {
253 events.onRemoteIceCandidate(toJavaCandidate(json));
254 } else if (type.equals("remove-candidates")) {
255 JSONArray candidateArray = json.getJSONArray("candidates");
256 IceCandidate[] candidates = new IceCandidate[candidateArray.length()];
257 for (int i = 0; i < candidateArray.length(); ++i) {
258 candidates[i] = toJavaCandidate(candidateArray.getJSONObject(i));
259 }
260 events.onRemoteIceCandidatesRemoved(candidates);
261 } else if (type.equals("answer")) {
262 SessionDescription sdp = new SessionDescription(
263 SessionDescription.Type.fromCanonicalForm(type),
264 json.getString("sdp"));
265 events.onRemoteDescription(sdp);
266 } else if (type.equals("offer")) {
267 SessionDescription sdp = new SessionDescription(
268 SessionDescription.Type.fromCanonicalForm(type),
269 json.getString("sdp"));
270
271 SignalingParameters parameters = new SignalingParameters(
272 // Ice servers are not needed for direct connections.
273 new LinkedList<PeerConnection.IceServer>(),
274 false, // This code will only be run on the client side. So, we are not the initiator.
275 null, // clientId
276 null, // wssUrl
277 null, // wssPostUrl
278 sdp, // offerSdp
279 null // iceCandidates
280 );
281 roomState = ConnectionState.CONNECTED;
282 events.onConnectedToRoom(parameters);
283 } else {
284 reportError("Unexpected TCP message: " + msg);
285 }
286 } catch (JSONException e) {
287 reportError("TCP message JSON parsing error: " + e.toString());
288 }
289 }
290
291 @Override
292 public void onTCPError(String description) {
293 reportError("TCP connection error: " + description);
294 }
295
296 @Override
297 public void onTCPClose() {
298 events.onChannelClose();
299 }
300
301 // --------------------------------------------------------------------
302 // Helper functions.
303 private void reportError(final String errorMessage) {
304 Log.e(TAG, errorMessage);
305 executor.execute(new Runnable() {
306 @Override
307 public void run() {
308 if (roomState != ConnectionState.ERROR) {
309 roomState = ConnectionState.ERROR;
310 events.onChannelError(errorMessage);
311 }
312 }
313 });
314 }
315
316 private void sendMessage(final String message) {
317 executor.execute(new Runnable() {
318 @Override
319 public void run() {
320 tcpClient.send(message);
321 }
322 });
323 }
324
325 // Put a |key|->|value| mapping in |json|.
326 private static void jsonPut(JSONObject json, String key, Object value) {
327 try {
328 json.put(key, value);
329 } catch (JSONException e) {
330 throw new RuntimeException(e);
331 }
332 }
333
334 // Converts a Java candidate to a JSONObject.
335 private static JSONObject toJsonCandidate(final IceCandidate candidate) {
336 JSONObject json = new JSONObject();
337 jsonPut(json, "label", candidate.sdpMLineIndex);
338 jsonPut(json, "id", candidate.sdpMid);
339 jsonPut(json, "candidate", candidate.sdp);
340 return json;
341 }
342
343 // Converts a JSON candidate to a Java object.
344 private static IceCandidate toJavaCandidate(JSONObject json) throws JSONExcept ion {
345 return new IceCandidate(json.getString("id"),
346 json.getInt("label"),
347 json.getString("candidate"));
348 }
349 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698