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

Side by Side Diff: talk/examples/android/src/org/appspot/apprtc/WebSocketRTCClient.java

Issue 1235563006: Move talk/examples/* to webrtc/examples. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: 201508051337 Created 5 years, 4 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 * 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.RoomParametersFetcher.RoomParametersFetcherEvents;
31 import org.appspot.apprtc.WebSocketChannelClient.WebSocketChannelEvents;
32 import org.appspot.apprtc.WebSocketChannelClient.WebSocketConnectionState;
33 import org.appspot.apprtc.util.AsyncHttpURLConnection;
34 import org.appspot.apprtc.util.AsyncHttpURLConnection.AsyncHttpEvents;
35 import org.appspot.apprtc.util.LooperExecutor;
36
37 import android.util.Log;
38
39 import org.json.JSONException;
40 import org.json.JSONObject;
41 import org.webrtc.IceCandidate;
42 import org.webrtc.SessionDescription;
43
44 /**
45 * Negotiates signaling for chatting with apprtc.appspot.com "rooms".
46 * Uses the client<->server specifics of the apprtc AppEngine webapp.
47 *
48 * <p>To use: create an instance of this object (registering a message handler) and
49 * call connectToRoom(). Once room connection is established
50 * onConnectedToRoom() callback with room parameters is invoked.
51 * Messages to other party (with local Ice candidates and answer SDP) can
52 * be sent after WebSocket connection is established.
53 */
54 public class WebSocketRTCClient implements AppRTCClient,
55 WebSocketChannelEvents {
56 private static final String TAG = "WSRTCClient";
57 private static final String ROOM_JOIN = "join";
58 private static final String ROOM_MESSAGE = "message";
59 private static final String ROOM_LEAVE = "leave";
60
61 private enum ConnectionState {
62 NEW, CONNECTED, CLOSED, ERROR
63 };
64 private enum MessageType {
65 MESSAGE, LEAVE
66 };
67 private final LooperExecutor executor;
68 private boolean initiator;
69 private SignalingEvents events;
70 private WebSocketChannelClient wsClient;
71 private ConnectionState roomState;
72 private RoomConnectionParameters connectionParameters;
73 private String messageUrl;
74 private String leaveUrl;
75
76 public WebSocketRTCClient(SignalingEvents events, LooperExecutor executor) {
77 this.events = events;
78 this.executor = executor;
79 roomState = ConnectionState.NEW;
80 executor.requestStart();
81 }
82
83 // --------------------------------------------------------------------
84 // AppRTCClient interface implementation.
85 // Asynchronously connect to an AppRTC room URL using supplied connection
86 // parameters, retrieves room parameters and connect to WebSocket server.
87 @Override
88 public void connectToRoom(RoomConnectionParameters connectionParameters) {
89 this.connectionParameters = connectionParameters;
90 executor.execute(new Runnable() {
91 @Override
92 public void run() {
93 connectToRoomInternal();
94 }
95 });
96 }
97
98 @Override
99 public void disconnectFromRoom() {
100 executor.execute(new Runnable() {
101 @Override
102 public void run() {
103 disconnectFromRoomInternal();
104 }
105 });
106 executor.requestStop();
107 }
108
109 // Connects to room - function runs on a local looper thread.
110 private void connectToRoomInternal() {
111 String connectionUrl = getConnectionUrl(connectionParameters);
112 Log.d(TAG, "Connect to room: " + connectionUrl);
113 roomState = ConnectionState.NEW;
114 wsClient = new WebSocketChannelClient(executor, this);
115
116 RoomParametersFetcherEvents callbacks = new RoomParametersFetcherEvents() {
117 @Override
118 public void onSignalingParametersReady(
119 final SignalingParameters params) {
120 WebSocketRTCClient.this.executor.execute(new Runnable() {
121 @Override
122 public void run() {
123 WebSocketRTCClient.this.signalingParametersReady(params);
124 }
125 });
126 }
127
128 @Override
129 public void onSignalingParametersError(String description) {
130 WebSocketRTCClient.this.reportError(description);
131 }
132 };
133
134 new RoomParametersFetcher(connectionUrl, null, callbacks).makeRequest();
135 }
136
137 // Disconnect from room and send bye messages - runs on a local looper thread.
138 private void disconnectFromRoomInternal() {
139 Log.d(TAG, "Disconnect. Room state: " + roomState);
140 if (roomState == ConnectionState.CONNECTED) {
141 Log.d(TAG, "Closing room.");
142 sendPostMessage(MessageType.LEAVE, leaveUrl, null);
143 }
144 roomState = ConnectionState.CLOSED;
145 if (wsClient != null) {
146 wsClient.disconnect(true);
147 }
148 }
149
150 // Helper functions to get connection, post message and leave message URLs
151 private String getConnectionUrl(
152 RoomConnectionParameters connectionParameters) {
153 return connectionParameters.roomUrl + "/" + ROOM_JOIN + "/"
154 + connectionParameters.roomId;
155 }
156
157 private String getMessageUrl(RoomConnectionParameters connectionParameters,
158 SignalingParameters signalingParameters) {
159 return connectionParameters.roomUrl + "/" + ROOM_MESSAGE + "/"
160 + connectionParameters.roomId + "/" + signalingParameters.clientId;
161 }
162
163 private String getLeaveUrl(RoomConnectionParameters connectionParameters,
164 SignalingParameters signalingParameters) {
165 return connectionParameters.roomUrl + "/" + ROOM_LEAVE + "/"
166 + connectionParameters.roomId + "/" + signalingParameters.clientId;
167 }
168
169 // Callback issued when room parameters are extracted. Runs on local
170 // looper thread.
171 private void signalingParametersReady(
172 final SignalingParameters signalingParameters) {
173 Log.d(TAG, "Room connection completed.");
174 if (connectionParameters.loopback
175 && (!signalingParameters.initiator
176 || signalingParameters.offerSdp != null)) {
177 reportError("Loopback room is busy.");
178 return;
179 }
180 if (!connectionParameters.loopback
181 && !signalingParameters.initiator
182 && signalingParameters.offerSdp == null) {
183 Log.w(TAG, "No offer SDP in room response.");
184 }
185 initiator = signalingParameters.initiator;
186 messageUrl = getMessageUrl(connectionParameters, signalingParameters);
187 leaveUrl = getLeaveUrl(connectionParameters, signalingParameters);
188 Log.d(TAG, "Message URL: " + messageUrl);
189 Log.d(TAG, "Leave URL: " + leaveUrl);
190 roomState = ConnectionState.CONNECTED;
191
192 // Fire connection and signaling parameters events.
193 events.onConnectedToRoom(signalingParameters);
194
195 // Connect and register WebSocket client.
196 wsClient.connect(signalingParameters.wssUrl, signalingParameters.wssPostUrl) ;
197 wsClient.register(connectionParameters.roomId, signalingParameters.clientId) ;
198 }
199
200 // Send local offer SDP to the other participant.
201 @Override
202 public void sendOfferSdp(final SessionDescription sdp) {
203 executor.execute(new Runnable() {
204 @Override
205 public void run() {
206 if (roomState != ConnectionState.CONNECTED) {
207 reportError("Sending offer SDP in non connected state.");
208 return;
209 }
210 JSONObject json = new JSONObject();
211 jsonPut(json, "sdp", sdp.description);
212 jsonPut(json, "type", "offer");
213 sendPostMessage(MessageType.MESSAGE, messageUrl, json.toString());
214 if (connectionParameters.loopback) {
215 // In loopback mode rename this offer to answer and route it back.
216 SessionDescription sdpAnswer = new SessionDescription(
217 SessionDescription.Type.fromCanonicalForm("answer"),
218 sdp.description);
219 events.onRemoteDescription(sdpAnswer);
220 }
221 }
222 });
223 }
224
225 // Send local answer SDP to the other participant.
226 @Override
227 public void sendAnswerSdp(final SessionDescription sdp) {
228 executor.execute(new Runnable() {
229 @Override
230 public void run() {
231 if (connectionParameters.loopback) {
232 Log.e(TAG, "Sending answer in loopback mode.");
233 return;
234 }
235 JSONObject json = new JSONObject();
236 jsonPut(json, "sdp", sdp.description);
237 jsonPut(json, "type", "answer");
238 wsClient.send(json.toString());
239 }
240 });
241 }
242
243 // Send Ice candidate to the other participant.
244 @Override
245 public void sendLocalIceCandidate(final IceCandidate candidate) {
246 executor.execute(new Runnable() {
247 @Override
248 public void run() {
249 JSONObject json = new JSONObject();
250 jsonPut(json, "type", "candidate");
251 jsonPut(json, "label", candidate.sdpMLineIndex);
252 jsonPut(json, "id", candidate.sdpMid);
253 jsonPut(json, "candidate", candidate.sdp);
254 if (initiator) {
255 // Call initiator sends ice candidates to GAE server.
256 if (roomState != ConnectionState.CONNECTED) {
257 reportError("Sending ICE candidate in non connected state.");
258 return;
259 }
260 sendPostMessage(MessageType.MESSAGE, messageUrl, json.toString());
261 if (connectionParameters.loopback) {
262 events.onRemoteIceCandidate(candidate);
263 }
264 } else {
265 // Call receiver sends ice candidates to websocket server.
266 wsClient.send(json.toString());
267 }
268 }
269 });
270 }
271
272 // --------------------------------------------------------------------
273 // WebSocketChannelEvents interface implementation.
274 // All events are called by WebSocketChannelClient on a local looper thread
275 // (passed to WebSocket client constructor).
276 @Override
277 public void onWebSocketMessage(final String msg) {
278 if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
279 Log.e(TAG, "Got WebSocket message in non registered state.");
280 return;
281 }
282 try {
283 JSONObject json = new JSONObject(msg);
284 String msgText = json.getString("msg");
285 String errorText = json.optString("error");
286 if (msgText.length() > 0) {
287 json = new JSONObject(msgText);
288 String type = json.optString("type");
289 if (type.equals("candidate")) {
290 IceCandidate candidate = new IceCandidate(
291 json.getString("id"),
292 json.getInt("label"),
293 json.getString("candidate"));
294 events.onRemoteIceCandidate(candidate);
295 } else if (type.equals("answer")) {
296 if (initiator) {
297 SessionDescription sdp = new SessionDescription(
298 SessionDescription.Type.fromCanonicalForm(type),
299 json.getString("sdp"));
300 events.onRemoteDescription(sdp);
301 } else {
302 reportError("Received answer for call initiator: " + msg);
303 }
304 } else if (type.equals("offer")) {
305 if (!initiator) {
306 SessionDescription sdp = new SessionDescription(
307 SessionDescription.Type.fromCanonicalForm(type),
308 json.getString("sdp"));
309 events.onRemoteDescription(sdp);
310 } else {
311 reportError("Received offer for call receiver: " + msg);
312 }
313 } else if (type.equals("bye")) {
314 events.onChannelClose();
315 } else {
316 reportError("Unexpected WebSocket message: " + msg);
317 }
318 } else {
319 if (errorText != null && errorText.length() > 0) {
320 reportError("WebSocket error message: " + errorText);
321 } else {
322 reportError("Unexpected WebSocket message: " + msg);
323 }
324 }
325 } catch (JSONException e) {
326 reportError("WebSocket message JSON parsing error: " + e.toString());
327 }
328 }
329
330 @Override
331 public void onWebSocketClose() {
332 events.onChannelClose();
333 }
334
335 @Override
336 public void onWebSocketError(String description) {
337 reportError("WebSocket error: " + description);
338 }
339
340 // --------------------------------------------------------------------
341 // Helper functions.
342 private void reportError(final String errorMessage) {
343 Log.e(TAG, errorMessage);
344 executor.execute(new Runnable() {
345 @Override
346 public void run() {
347 if (roomState != ConnectionState.ERROR) {
348 roomState = ConnectionState.ERROR;
349 events.onChannelError(errorMessage);
350 }
351 }
352 });
353 }
354
355 // Put a |key|->|value| mapping in |json|.
356 private static void jsonPut(JSONObject json, String key, Object value) {
357 try {
358 json.put(key, value);
359 } catch (JSONException e) {
360 throw new RuntimeException(e);
361 }
362 }
363
364 // Send SDP or ICE candidate to a room server.
365 private void sendPostMessage(
366 final MessageType messageType, final String url, final String message) {
367 String logInfo = url;
368 if (message != null) {
369 logInfo += ". Message: " + message;
370 }
371 Log.d(TAG, "C->GAE: " + logInfo);
372 AsyncHttpURLConnection httpConnection = new AsyncHttpURLConnection(
373 "POST", url, message, new AsyncHttpEvents() {
374 @Override
375 public void onHttpError(String errorMessage) {
376 reportError("GAE POST error: " + errorMessage);
377 }
378
379 @Override
380 public void onHttpComplete(String response) {
381 if (messageType == MessageType.MESSAGE) {
382 try {
383 JSONObject roomJson = new JSONObject(response);
384 String result = roomJson.getString("result");
385 if (!result.equals("SUCCESS")) {
386 reportError("GAE POST error: " + result);
387 }
388 } catch (JSONException e) {
389 reportError("GAE POST JSON error: " + e.toString());
390 }
391 }
392 }
393 });
394 httpConnection.send();
395 }
396 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698