/* * Copyright 2014 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ package org.appspot.apprtc; import org.appspot.apprtc.AppRTCClient.SignalingParameters; import org.appspot.apprtc.util.AsyncHttpURLConnection; import org.appspot.apprtc.util.AsyncHttpURLConnection.AsyncHttpEvents; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.webrtc.IceCandidate; import org.webrtc.PeerConnection; import org.webrtc.SessionDescription; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedList; import java.util.Scanner; /** * AsyncTask that converts an AppRTC room URL into the set of signaling * parameters to use with that room. */ public class RoomParametersFetcher { private static final String TAG = "RoomRTCClient"; private static final int TURN_HTTP_TIMEOUT_MS = 5000; private final RoomParametersFetcherEvents events; private final String roomUrl; private final String roomMessage; private AsyncHttpURLConnection httpConnection; /** * Room parameters fetcher callbacks. */ public static interface RoomParametersFetcherEvents { /** * Callback fired once the room's signaling parameters * SignalingParameters are extracted. */ public void onSignalingParametersReady(final SignalingParameters params); /** * Callback for room parameters extraction error. */ public void onSignalingParametersError(final String description); } public RoomParametersFetcher(String roomUrl, String roomMessage, final RoomParametersFetcherEvents events) { this.roomUrl = roomUrl; this.roomMessage = roomMessage; this.events = events; } public void makeRequest() { Log.d(TAG, "Connecting to room: " + roomUrl); httpConnection = new AsyncHttpURLConnection( "POST", roomUrl, roomMessage, new AsyncHttpEvents() { @Override public void onHttpError(String errorMessage) { Log.e(TAG, "Room connection error: " + errorMessage); events.onSignalingParametersError(errorMessage); } @Override public void onHttpComplete(String response) { roomHttpResponseParse(response); } }); httpConnection.send(); } private void roomHttpResponseParse(String response) { Log.d(TAG, "Room response: " + response); try { LinkedList iceCandidates = null; SessionDescription offerSdp = null; JSONObject roomJson = new JSONObject(response); String result = roomJson.getString("result"); if (!result.equals("SUCCESS")) { events.onSignalingParametersError("Room response error: " + result); return; } response = roomJson.getString("params"); roomJson = new JSONObject(response); String roomId = roomJson.getString("room_id"); String clientId = roomJson.getString("client_id"); String wssUrl = roomJson.getString("wss_url"); String wssPostUrl = roomJson.getString("wss_post_url"); boolean initiator = (roomJson.getBoolean("is_initiator")); if (!initiator) { iceCandidates = new LinkedList(); String messagesString = roomJson.getString("messages"); JSONArray messages = new JSONArray(messagesString); for (int i = 0; i < messages.length(); ++i) { String messageString = messages.getString(i); JSONObject message = new JSONObject(messageString); String messageType = message.getString("type"); Log.d(TAG, "GAE->C #" + i + " : " + messageString); if (messageType.equals("offer")) { offerSdp = new SessionDescription( SessionDescription.Type.fromCanonicalForm(messageType), message.getString("sdp")); } else if (messageType.equals("candidate")) { IceCandidate candidate = new IceCandidate( message.getString("id"), message.getInt("label"), message.getString("candidate")); iceCandidates.add(candidate); } else { Log.e(TAG, "Unknown message: " + messageString); } } } Log.d(TAG, "RoomId: " + roomId + ". ClientId: " + clientId); Log.d(TAG, "Initiator: " + initiator); Log.d(TAG, "WSS url: " + wssUrl); Log.d(TAG, "WSS POST url: " + wssPostUrl); LinkedList iceServers = iceServersFromPCConfigJSON(roomJson.getString("pc_config")); boolean isTurnPresent = false; for (PeerConnection.IceServer server : iceServers) { Log.d(TAG, "IceServer: " + server); if (server.uri.startsWith("turn:")) { isTurnPresent = true; break; } } // Request TURN servers. if (!isTurnPresent) { LinkedList turnServers = requestTurnServers(roomJson.getString("turn_url")); for (PeerConnection.IceServer turnServer : turnServers) { Log.d(TAG, "TurnServer: " + turnServer); iceServers.add(turnServer); } } SignalingParameters params = new SignalingParameters( iceServers, initiator, clientId, wssUrl, wssPostUrl, offerSdp, iceCandidates); events.onSignalingParametersReady(params); } catch (JSONException e) { events.onSignalingParametersError( "Room JSON parsing error: " + e.toString()); } catch (IOException e) { events.onSignalingParametersError("Room IO error: " + e.toString()); } } // Requests & returns a TURN ICE Server based on a request URL. Must be run // off the main thread! private LinkedList requestTurnServers(String url) throws IOException, JSONException { LinkedList turnServers = new LinkedList(); Log.d(TAG, "Request TURN from: " + url); HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setConnectTimeout(TURN_HTTP_TIMEOUT_MS); connection.setReadTimeout(TURN_HTTP_TIMEOUT_MS); int responseCode = connection.getResponseCode(); if (responseCode != 200) { throw new IOException("Non-200 response when requesting TURN server from " + url + " : " + connection.getHeaderField(null)); } InputStream responseStream = connection.getInputStream(); String response = drainStream(responseStream); connection.disconnect(); Log.d(TAG, "TURN response: " + response); JSONObject responseJSON = new JSONObject(response); String username = responseJSON.getString("username"); String password = responseJSON.getString("password"); JSONArray turnUris = responseJSON.getJSONArray("uris"); for (int i = 0; i < turnUris.length(); i++) { String uri = turnUris.getString(i); turnServers.add(new PeerConnection.IceServer(uri, username, password)); } return turnServers; } // Return the list of ICE servers described by a WebRTCPeerConnection // configuration string. private LinkedList iceServersFromPCConfigJSON( String pcConfig) throws JSONException { JSONObject json = new JSONObject(pcConfig); JSONArray servers = json.getJSONArray("iceServers"); LinkedList ret = new LinkedList(); for (int i = 0; i < servers.length(); ++i) { JSONObject server = servers.getJSONObject(i); String url = server.getString("urls"); String credential = server.has("credential") ? server.getString("credential") : ""; ret.add(new PeerConnection.IceServer(url, "", credential)); } return ret; } // Return the contents of an InputStream as a String. private static String drainStream(InputStream in) { Scanner s = new Scanner(in).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } }