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

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: Add Override to checkOnLooperThread 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()) {
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 try {
128 port = Integer.parseInt(portStr);
129 } catch (NumberFormatException e) {
130 reportError("Invalid port number: " + portStr);
131 return;
132 }
133 } else {
134 port = DEFAULT_PORT;
135 }
136
137 tcpClient = new TCPChannelClient(executor, this, ip, port);
138 }
139
140 /**
141 * Disconnects from the room.
142 *
143 * Runs on the looper thread.
144 */
145 private void disconnectFromRoomInternal() {
146 roomState = ConnectionState.CLOSED;
147
148 if (tcpClient != null) {
149 tcpClient.disconnect();
150 tcpClient = null;
151 }
152 }
153
154 @Override
155 public void sendOfferSdp(final SessionDescription sdp) {
156 executor.execute(new Runnable() {
157 @Override
158 public void run() {
159 if (roomState != ConnectionState.CONNECTED) {
160 reportError("Sending offer SDP in non connected state.");
161 return;
162 }
163 JSONObject json = new JSONObject();
164 jsonPut(json, "sdp", sdp.description);
165 jsonPut(json, "type", "offer");
166 sendMessage(json.toString());
167 }
168 });
169 }
170
171 @Override
172 public void sendAnswerSdp(final SessionDescription sdp) {
173 executor.execute(new Runnable() {
174 @Override
175 public void run() {
176 JSONObject json = new JSONObject();
177 jsonPut(json, "sdp", sdp.description);
178 jsonPut(json, "type", "answer");
179 sendMessage(json.toString());
180 }
181 });
182 }
183
184 @Override
185 public void sendLocalIceCandidate(final IceCandidate candidate) {
186 executor.execute(new Runnable() {
187 @Override
188 public void run() {
189 JSONObject json = new JSONObject();
190 jsonPut(json, "type", "candidate");
191 jsonPut(json, "label", candidate.sdpMLineIndex);
192 jsonPut(json, "id", candidate.sdpMid);
193 jsonPut(json, "candidate", candidate.sdp);
194
195 if (roomState != ConnectionState.CONNECTED) {
196 reportError("Sending ICE candidate in non connected state.");
197 return;
198 }
199 sendMessage(json.toString());
200 }
201 });
202 }
203
204 /** Send removed Ice candidates to the other participant. */
205 @Override
206 public void sendLocalIceCandidateRemovals(final IceCandidate[] candidates) {
207 executor.execute(new Runnable() {
208 @Override
209 public void run() {
210 JSONObject json = new JSONObject();
211 jsonPut(json, "type", "remove-candidates");
212 JSONArray jsonArray = new JSONArray();
213 for (final IceCandidate candidate : candidates) {
214 jsonArray.put(toJsonCandidate(candidate));
215 }
216 jsonPut(json, "candidates", jsonArray);
217
218 if (roomState != ConnectionState.CONNECTED) {
219 reportError("Sending ICE candidate removals in non connected state.");
220 return;
221 }
222 sendMessage(json.toString());
223 }
224 });
225 }
226
227 // -------------------------------------------------------------------
228 // TCPChannelClient event handlers
229
230 /**
231 * If the client is the server side, this will trigger onConnectedToRoom.
232 */
233 @Override
234 public void onTCPConnected(boolean isServer) {
235 if (isServer) {
236 roomState = ConnectionState.CONNECTED;
237
238 SignalingParameters parameters = new SignalingParameters(
239 // Ice servers are not needed for direct connections.
240 new LinkedList<PeerConnection.IceServer>(),
241 isServer, // Server side acts as the initiator on direct connections.
242 null, // clientId
243 null, // wssUrl
244 null, // wwsPostUrl
245 null, // offerSdp
246 null // iceCandidates
247 );
248 events.onConnectedToRoom(parameters);
249 }
250 }
251
252 @Override
253 public void onTCPMessage(String msg) {
254 try {
255 JSONObject json = new JSONObject(msg);
256 String type = json.optString("type");
257 if (type.equals("candidate")) {
258 events.onRemoteIceCandidate(toJavaCandidate(json));
259 } else if (type.equals("remove-candidates")) {
260 JSONArray candidateArray = json.getJSONArray("candidates");
261 IceCandidate[] candidates = new IceCandidate[candidateArray.length()];
262 for (int i = 0; i < candidateArray.length(); ++i) {
263 candidates[i] = toJavaCandidate(candidateArray.getJSONObject(i));
264 }
265 events.onRemoteIceCandidatesRemoved(candidates);
266 } else if (type.equals("answer")) {
267 SessionDescription sdp = new SessionDescription(
268 SessionDescription.Type.fromCanonicalForm(type),
269 json.getString("sdp"));
270 events.onRemoteDescription(sdp);
271 } else if (type.equals("offer")) {
272 SessionDescription sdp = new SessionDescription(
273 SessionDescription.Type.fromCanonicalForm(type),
274 json.getString("sdp"));
275
276 SignalingParameters parameters = new SignalingParameters(
277 // Ice servers are not needed for direct connections.
278 new LinkedList<PeerConnection.IceServer>(),
279 false, // This code will only be run on the client side. So, we are not the initiator.
280 null, // clientId
281 null, // wssUrl
282 null, // wssPostUrl
283 sdp, // offerSdp
284 null // iceCandidates
285 );
286 roomState = ConnectionState.CONNECTED;
287 events.onConnectedToRoom(parameters);
288 } else {
289 reportError("Unexpected TCP message: " + msg);
290 }
291 } catch (JSONException e) {
292 reportError("TCP message JSON parsing error: " + e.toString());
293 }
294 }
295
296 @Override
297 public void onTCPError(String description) {
298 reportError("TCP connection error: " + description);
299 }
300
301 @Override
302 public void onTCPClose() {
303 events.onChannelClose();
304 }
305
306 // --------------------------------------------------------------------
307 // Helper functions.
308 private void reportError(final String errorMessage) {
309 Log.e(TAG, errorMessage);
310 executor.execute(new Runnable() {
311 @Override
312 public void run() {
313 if (roomState != ConnectionState.ERROR) {
314 roomState = ConnectionState.ERROR;
315 events.onChannelError(errorMessage);
316 }
317 }
318 });
319 }
320
321 private void sendMessage(final String message) {
322 executor.execute(new Runnable() {
323 @Override
324 public void run() {
325 tcpClient.send(message);
326 }
327 });
328 }
329
330 // Put a |key|->|value| mapping in |json|.
331 private static void jsonPut(JSONObject json, String key, Object value) {
332 try {
333 json.put(key, value);
334 } catch (JSONException e) {
335 throw new RuntimeException(e);
336 }
337 }
338
339 // Converts a Java candidate to a JSONObject.
340 private static JSONObject toJsonCandidate(final IceCandidate candidate) {
341 JSONObject json = new JSONObject();
342 jsonPut(json, "label", candidate.sdpMLineIndex);
343 jsonPut(json, "id", candidate.sdpMid);
344 jsonPut(json, "candidate", candidate.sdp);
345 return json;
346 }
347
348 // Converts a JSON candidate to a Java object.
349 private static IceCandidate toJavaCandidate(JSONObject json) throws JSONExcept ion {
350 return new IceCandidate(json.getString("id"),
351 json.getInt("label"),
352 json.getString("candidate"));
353 }
354 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698