Cleaning up Android AppRTCDemo.
- Move signaling code from Activity to a separate class and add interface for AppRTC signaling. For now only pure GAE signaling implements this interface. - Move peer connection, video source and peer connection and SDP observer code from Activity to a separate class. - Main Activity class will do only high level calls and event handling for peer connection and signaling classes. - Also add video renderer position update and use full screen for local preview until the connection is established. BUG= R=braveyao@webrtc.org, pthatcher@webrtc.org Review URL: https://webrtc-codereview.appspot.com/24019004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7469 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
@@ -41,33 +41,18 @@ import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.webrtc.DataChannel;
|
||||
import org.appspot.apprtc.AppRTCClient.AppRTCSignalingParameters;
|
||||
import org.webrtc.IceCandidate;
|
||||
import org.webrtc.MediaConstraints;
|
||||
import org.webrtc.MediaStream;
|
||||
import org.webrtc.PeerConnection;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
import org.webrtc.SdpObserver;
|
||||
import org.webrtc.SessionDescription;
|
||||
import org.webrtc.StatsObserver;
|
||||
import org.webrtc.StatsReport;
|
||||
import org.webrtc.VideoCapturer;
|
||||
import org.webrtc.VideoRenderer;
|
||||
import org.webrtc.VideoRendererGui;
|
||||
import org.webrtc.VideoSource;
|
||||
import org.webrtc.VideoTrack;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Main Activity of the AppRTCDemo Android app demonstrating interoperability
|
||||
@@ -75,16 +60,12 @@ import java.util.regex.Pattern;
|
||||
* apprtc.appspot.com demo webapp.
|
||||
*/
|
||||
public class AppRTCDemoActivity extends Activity
|
||||
implements AppRTCClient.IceServersObserver {
|
||||
private static final String TAG = "AppRTCDemoActivity";
|
||||
private PeerConnectionFactory factory;
|
||||
private VideoSource videoSource;
|
||||
private boolean videoSourceStopped;
|
||||
private PeerConnection pc;
|
||||
private final PCObserver pcObserver = new PCObserver();
|
||||
private final SDPObserver sdpObserver = new SDPObserver();
|
||||
private final GAEChannelClient.MessageHandler gaeHandler = new GAEHandler();
|
||||
private AppRTCClient appRtcClient = new AppRTCClient(this, gaeHandler, this);
|
||||
implements AppRTCClient.AppRTCSignalingEvents,
|
||||
PeerConnectionClient.PeerConnectionEvents {
|
||||
private static final String TAG = "AppRTCClient";
|
||||
private PeerConnectionClient pc;
|
||||
private AppRTCClient appRtcClient = new GAERTCClient(this, this);
|
||||
private AppRTCSignalingParameters appRtcParameters;
|
||||
private AppRTCGLView vsv;
|
||||
private VideoRenderer.Callbacks localRender;
|
||||
private VideoRenderer.Callbacks remoteRender;
|
||||
@@ -92,11 +73,8 @@ public class AppRTCDemoActivity extends Activity
|
||||
private final LayoutParams hudLayout =
|
||||
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
private TextView hudView;
|
||||
private LinkedList<IceCandidate> queuedRemoteCandidates =
|
||||
new LinkedList<IceCandidate>();
|
||||
// Synchronize on quit[0] to avoid teardown-related crashes.
|
||||
private final Boolean[] quit = new Boolean[] { false };
|
||||
private MediaConstraints sdpMediaConstraints;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@@ -115,7 +93,7 @@ public class AppRTCDemoActivity extends Activity
|
||||
VideoRendererGui.setView(vsv);
|
||||
remoteRender = VideoRendererGui.create(0, 0, 100, 100,
|
||||
VideoRendererGui.ScalingType.SCALE_ASPECT_FIT);
|
||||
localRender = VideoRendererGui.create(70, 5, 25, 25,
|
||||
localRender = VideoRendererGui.create(0, 0, 100, 100,
|
||||
VideoRendererGui.ScalingType.SCALE_ASPECT_FIT);
|
||||
|
||||
vsv.setOnClickListener(new View.OnClickListener() {
|
||||
@@ -144,12 +122,6 @@ public class AppRTCDemoActivity extends Activity
|
||||
AudioManager.MODE_IN_CALL : AudioManager.MODE_IN_COMMUNICATION);
|
||||
audioManager.setSpeakerphoneOn(!isWiredHeadsetOn);
|
||||
|
||||
sdpMediaConstraints = new MediaConstraints();
|
||||
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
||||
"OfferToReceiveAudio", "true"));
|
||||
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
||||
"OfferToReceiveVideo", "true"));
|
||||
|
||||
final Intent intent = getIntent();
|
||||
if ("android.intent.action.VIEW".equals(intent.getAction())) {
|
||||
connectToRoom(intent.getData().toString());
|
||||
@@ -158,13 +130,46 @@ public class AppRTCDemoActivity extends Activity
|
||||
showGetRoomUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
vsv.onPause();
|
||||
if (pc != null) {
|
||||
pc.stopVideoSource();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
vsv.onResume();
|
||||
if (pc != null) {
|
||||
pc.startVideoSource();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged (Configuration newConfig) {
|
||||
Point displaySize = new Point();
|
||||
getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||
vsv.updateDisplaySize(displaySize);
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
disconnectAndExit();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void showGetRoomUI() {
|
||||
final EditText roomInput = new EditText(this);
|
||||
roomInput.setText("https://apprtc.appspot.com/?r=");
|
||||
roomInput.setSelection(roomInput.getText().length());
|
||||
DialogInterface.OnClickListener listener =
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override public void onClick(DialogInterface dialog, int which) {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
abortUnless(which == DialogInterface.BUTTON_POSITIVE, "lolwat?");
|
||||
dialog.dismiss();
|
||||
connectToRoom(roomInput.getText().toString());
|
||||
@@ -237,66 +242,56 @@ public class AppRTCDemoActivity extends Activity
|
||||
return activeConnectionbuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
vsv.onPause();
|
||||
if (videoSource != null) {
|
||||
videoSource.stop();
|
||||
videoSourceStopped = true;
|
||||
// Disconnect from remote resources, dispose of local resources, and exit.
|
||||
private void disconnectAndExit() {
|
||||
synchronized (quit[0]) {
|
||||
if (quit[0]) {
|
||||
return;
|
||||
}
|
||||
quit[0] = true;
|
||||
if (pc != null) {
|
||||
pc.close();
|
||||
pc = null;
|
||||
}
|
||||
if (appRtcClient != null) {
|
||||
appRtcClient.disconnect();
|
||||
appRtcClient = null;
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
vsv.onResume();
|
||||
if (videoSource != null && videoSourceStopped) {
|
||||
videoSource.restart();
|
||||
// Poor-man's assert(): die with |msg| unless |condition| is true.
|
||||
private static void abortUnless(boolean condition, String msg) {
|
||||
if (!condition) {
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged (Configuration newConfig) {
|
||||
Point displaySize = new Point();
|
||||
getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||
vsv.updateDisplaySize(displaySize);
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
// Just for fun (and to regression-test bug 2302) make sure that DataChannels
|
||||
// can be created, queried, and disposed.
|
||||
private static void createDataChannelToRegressionTestBug2302(
|
||||
PeerConnection pc) {
|
||||
DataChannel dc = pc.createDataChannel("dcLabel", new DataChannel.Init());
|
||||
abortUnless("dcLabel".equals(dc.label()), "Unexpected label corruption?");
|
||||
dc.close();
|
||||
dc.dispose();
|
||||
// Log |msg| and Toast about it.
|
||||
private void logAndToast(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
if (logToast != null) {
|
||||
logToast.cancel();
|
||||
}
|
||||
logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
|
||||
logToast.show();
|
||||
}
|
||||
|
||||
// -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
|
||||
// All events are called from UI thread.
|
||||
@Override
|
||||
public void onIceServers(List<PeerConnection.IceServer> iceServers) {
|
||||
public void onConnectedToRoom(final AppRTCSignalingParameters params) {
|
||||
appRtcParameters = params;
|
||||
abortUnless(PeerConnectionFactory.initializeAndroidGlobals(
|
||||
this, true, true, VideoRendererGui.getEGLContext()),
|
||||
"Failed to initializeAndroidGlobals");
|
||||
factory = new PeerConnectionFactory();
|
||||
|
||||
MediaConstraints pcConstraints = appRtcClient.pcConstraints();
|
||||
pcConstraints.optional.add(
|
||||
new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
|
||||
pc = factory.createPeerConnection(iceServers, pcConstraints, pcObserver);
|
||||
|
||||
createDataChannelToRegressionTestBug2302(pc); // See method comment.
|
||||
|
||||
// Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging.
|
||||
// NOTE: this _must_ happen while |factory| is alive!
|
||||
// Logging.enableTracing(
|
||||
// "logcat:",
|
||||
// EnumSet.of(Logging.TraceLevel.TRACE_ALL),
|
||||
// Logging.Severity.LS_SENSITIVE);
|
||||
logAndToast("Creating peer connection...");
|
||||
pc = new PeerConnectionClient(
|
||||
this, localRender, remoteRender, appRtcParameters, this);
|
||||
|
||||
{
|
||||
final PeerConnection finalPC = pc;
|
||||
final PeerConnectionClient finalPC = pc;
|
||||
final Runnable repeatedStatsLogger = new Runnable() {
|
||||
public void run() {
|
||||
synchronized (quit[0]) {
|
||||
@@ -330,367 +325,66 @@ public class AppRTCDemoActivity extends Activity
|
||||
vsv.postDelayed(repeatedStatsLogger, 1000);
|
||||
}
|
||||
|
||||
{
|
||||
logAndToast("Creating local video source...");
|
||||
MediaStream lMS = factory.createLocalMediaStream("ARDAMS");
|
||||
if (appRtcClient.videoConstraints() != null) {
|
||||
VideoCapturer capturer = getVideoCapturer();
|
||||
videoSource = factory.createVideoSource(
|
||||
capturer, appRtcClient.videoConstraints());
|
||||
VideoTrack videoTrack =
|
||||
factory.createVideoTrack("ARDAMSv0", videoSource);
|
||||
videoTrack.addRenderer(new VideoRenderer(localRender));
|
||||
lMS.addTrack(videoTrack);
|
||||
}
|
||||
if (appRtcClient.audioConstraints() != null) {
|
||||
lMS.addTrack(factory.createAudioTrack(
|
||||
"ARDAMSa0",
|
||||
factory.createAudioSource(appRtcClient.audioConstraints())));
|
||||
}
|
||||
pc.addStream(lMS, new MediaConstraints());
|
||||
}
|
||||
logAndToast("Waiting for ICE candidates...");
|
||||
}
|
||||
|
||||
// Cycle through likely device names for the camera and return the first
|
||||
// capturer that works, or crash if none do.
|
||||
private VideoCapturer getVideoCapturer() {
|
||||
String[] cameraFacing = { "front", "back" };
|
||||
int[] cameraIndex = { 0, 1 };
|
||||
int[] cameraOrientation = { 0, 90, 180, 270 };
|
||||
for (String facing : cameraFacing) {
|
||||
for (int index : cameraIndex) {
|
||||
for (int orientation : cameraOrientation) {
|
||||
String name = "Camera " + index + ", Facing " + facing +
|
||||
", Orientation " + orientation;
|
||||
VideoCapturer capturer = VideoCapturer.create(name);
|
||||
if (capturer != null) {
|
||||
logAndToast("Using camera: " + name);
|
||||
return capturer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Failed to open capturer");
|
||||
logAndToast("Waiting for remote connection...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
public void onChannelOpen() {
|
||||
if (appRtcParameters.initiator) {
|
||||
logAndToast("Creating OFFER...");
|
||||
// Create offer. Offer SDP will be sent to answering client in
|
||||
// PeerConnectionEvents.onLocalDescription event.
|
||||
pc.createOffer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteDescription(final SessionDescription sdp) {
|
||||
logAndToast("Received remote " + sdp.type + " ...");
|
||||
pc.setRemoteDescription(sdp);
|
||||
if (!appRtcParameters.initiator) {
|
||||
logAndToast("Creating ANSWER...");
|
||||
// Create answer. Answer SDP will be sent to offering client in
|
||||
// PeerConnectionEvents.onLocalDescription event.
|
||||
pc.createAnswer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteIceCandidate(final IceCandidate candidate) {
|
||||
pc.addRemoteIceCandidate(candidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelClose() {
|
||||
logAndToast("Remote end hung up; dropping PeerConnection");
|
||||
disconnectAndExit();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
// Poor-man's assert(): die with |msg| unless |condition| is true.
|
||||
private static void abortUnless(boolean condition, String msg) {
|
||||
if (!condition) {
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
@Override
|
||||
public void onChannelError(int code, String description) {
|
||||
logAndToast("Channel error: " + code + ". " + description);
|
||||
disconnectAndExit();
|
||||
}
|
||||
|
||||
// Log |msg| and Toast about it.
|
||||
private void logAndToast(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
if (logToast != null) {
|
||||
logToast.cancel();
|
||||
}
|
||||
logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
|
||||
logToast.show();
|
||||
// -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
|
||||
// Send local peer connection SDP and ICE candidates to remote party.
|
||||
// All callbacks are invoked from UI thread.
|
||||
@Override
|
||||
public void onLocalDescription(final SessionDescription sdp) {
|
||||
logAndToast("Sending " + sdp.type + " ...");
|
||||
appRtcClient.sendLocalDescription(sdp);
|
||||
}
|
||||
|
||||
// Send |json| to the underlying AppEngine Channel.
|
||||
private void sendMessage(JSONObject json) {
|
||||
appRtcClient.sendMessage(json.toString());
|
||||
@Override
|
||||
public void onIceCandidate(final IceCandidate candidate) {
|
||||
appRtcClient.sendLocalIceCandidate(candidate);
|
||||
}
|
||||
|
||||
// Put a |key|->|value| mapping in |json|.
|
||||
private static void jsonPut(JSONObject json, String key, Object value) {
|
||||
try {
|
||||
json.put(key, value);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@Override
|
||||
public void onIceConnected() {
|
||||
logAndToast("ICE connected");
|
||||
VideoRendererGui.update(localRender, 70, 70, 28, 28,
|
||||
VideoRendererGui.ScalingType.SCALE_ASPECT_FIT);
|
||||
}
|
||||
|
||||
// Mangle SDP to prefer ISAC/16000 over any other audio codec.
|
||||
private static String preferISAC(String sdpDescription) {
|
||||
String[] lines = sdpDescription.split("\r\n");
|
||||
int mLineIndex = -1;
|
||||
String isac16kRtpMap = null;
|
||||
Pattern isac16kPattern =
|
||||
Pattern.compile("^a=rtpmap:(\\d+) ISAC/16000[\r]?$");
|
||||
for (int i = 0;
|
||||
(i < lines.length) && (mLineIndex == -1 || isac16kRtpMap == null);
|
||||
++i) {
|
||||
if (lines[i].startsWith("m=audio ")) {
|
||||
mLineIndex = i;
|
||||
continue;
|
||||
}
|
||||
Matcher isac16kMatcher = isac16kPattern.matcher(lines[i]);
|
||||
if (isac16kMatcher.matches()) {
|
||||
isac16kRtpMap = isac16kMatcher.group(1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (mLineIndex == -1) {
|
||||
Log.d(TAG, "No m=audio line, so can't prefer iSAC");
|
||||
return sdpDescription;
|
||||
}
|
||||
if (isac16kRtpMap == null) {
|
||||
Log.d(TAG, "No ISAC/16000 line, so can't prefer iSAC");
|
||||
return sdpDescription;
|
||||
}
|
||||
String[] origMLineParts = lines[mLineIndex].split(" ");
|
||||
StringBuilder newMLine = new StringBuilder();
|
||||
int origPartIndex = 0;
|
||||
// Format is: m=<media> <port> <proto> <fmt> ...
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(isac16kRtpMap);
|
||||
for (; origPartIndex < origMLineParts.length; ++origPartIndex) {
|
||||
if (!origMLineParts[origPartIndex].equals(isac16kRtpMap)) {
|
||||
newMLine.append(" ").append(origMLineParts[origPartIndex]);
|
||||
}
|
||||
}
|
||||
lines[mLineIndex] = newMLine.toString();
|
||||
StringBuilder newSdpDescription = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
newSdpDescription.append(line).append("\r\n");
|
||||
}
|
||||
return newSdpDescription.toString();
|
||||
}
|
||||
|
||||
// Implementation detail: observe ICE & stream changes and react accordingly.
|
||||
private class PCObserver implements PeerConnection.Observer {
|
||||
@Override public void onIceCandidate(final IceCandidate candidate){
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
JSONObject json = new JSONObject();
|
||||
jsonPut(json, "type", "candidate");
|
||||
jsonPut(json, "label", candidate.sdpMLineIndex);
|
||||
jsonPut(json, "id", candidate.sdpMid);
|
||||
jsonPut(json, "candidate", candidate.sdp);
|
||||
sendMessage(json);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void onError(){
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
throw new RuntimeException("PeerConnection error!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void onSignalingChange(
|
||||
PeerConnection.SignalingState newState) {
|
||||
}
|
||||
|
||||
@Override public void onIceConnectionChange(
|
||||
PeerConnection.IceConnectionState newState) {
|
||||
}
|
||||
|
||||
@Override public void onIceGatheringChange(
|
||||
PeerConnection.IceGatheringState newState) {
|
||||
}
|
||||
|
||||
@Override public void onAddStream(final MediaStream stream){
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
abortUnless(stream.audioTracks.size() <= 1 &&
|
||||
stream.videoTracks.size() <= 1,
|
||||
"Weird-looking stream: " + stream);
|
||||
if (stream.videoTracks.size() == 1) {
|
||||
stream.videoTracks.get(0).addRenderer(
|
||||
new VideoRenderer(remoteRender));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void onRemoveStream(final MediaStream stream){
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
stream.videoTracks.get(0).dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void onDataChannel(final DataChannel dc) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
throw new RuntimeException(
|
||||
"AppRTC doesn't use data channels, but got: " + dc.label() +
|
||||
" anyway!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void onRenegotiationNeeded() {
|
||||
// No need to do anything; AppRTC follows a pre-agreed-upon
|
||||
// signaling/negotiation protocol.
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation detail: handle offer creation/signaling and answer setting,
|
||||
// as well as adding remote ICE candidates once the answer SDP is set.
|
||||
private class SDPObserver implements SdpObserver {
|
||||
private SessionDescription localSdp;
|
||||
|
||||
@Override public void onCreateSuccess(final SessionDescription origSdp) {
|
||||
abortUnless(localSdp == null, "multiple SDP create?!?");
|
||||
final SessionDescription sdp = new SessionDescription(
|
||||
origSdp.type, preferISAC(origSdp.description));
|
||||
localSdp = sdp;
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
pc.setLocalDescription(sdpObserver, sdp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper for sending local SDP (offer or answer, depending on role) to the
|
||||
// other participant. Note that it is important to send the output of
|
||||
// create{Offer,Answer} and not merely the current value of
|
||||
// getLocalDescription() because the latter may include ICE candidates that
|
||||
// we might want to filter elsewhere.
|
||||
private void sendLocalDescription() {
|
||||
logAndToast("Sending " + localSdp.type);
|
||||
JSONObject json = new JSONObject();
|
||||
jsonPut(json, "type", localSdp.type.canonicalForm());
|
||||
jsonPut(json, "sdp", localSdp.description);
|
||||
sendMessage(json);
|
||||
}
|
||||
|
||||
@Override public void onSetSuccess() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (appRtcClient.isInitiator()) {
|
||||
if (pc.getRemoteDescription() != null) {
|
||||
// We've set our local offer and received & set the remote
|
||||
// answer, so drain candidates.
|
||||
drainRemoteCandidates();
|
||||
} else {
|
||||
// We've just set our local description so time to send it.
|
||||
sendLocalDescription();
|
||||
}
|
||||
} else {
|
||||
if (pc.getLocalDescription() == null) {
|
||||
// We just set the remote offer, time to create our answer.
|
||||
logAndToast("Creating answer");
|
||||
pc.createAnswer(SDPObserver.this, sdpMediaConstraints);
|
||||
} else {
|
||||
// Answer now set as local description; send it and drain
|
||||
// candidates.
|
||||
sendLocalDescription();
|
||||
drainRemoteCandidates();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void onCreateFailure(final String error) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
throw new RuntimeException("createSDP error: " + error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void onSetFailure(final String error) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
throw new RuntimeException("setSDP error: " + error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void drainRemoteCandidates() {
|
||||
for (IceCandidate candidate : queuedRemoteCandidates) {
|
||||
pc.addIceCandidate(candidate);
|
||||
}
|
||||
queuedRemoteCandidates = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation detail: handler for receiving GAE messages and dispatching
|
||||
// them appropriately.
|
||||
private class GAEHandler implements GAEChannelClient.MessageHandler {
|
||||
@JavascriptInterface public void onOpen() {
|
||||
if (!appRtcClient.isInitiator()) {
|
||||
return;
|
||||
}
|
||||
logAndToast("Creating offer...");
|
||||
pc.createOffer(sdpObserver, sdpMediaConstraints);
|
||||
}
|
||||
|
||||
@JavascriptInterface public void onMessage(String data) {
|
||||
try {
|
||||
JSONObject json = new JSONObject(data);
|
||||
String type = (String) json.get("type");
|
||||
if (type.equals("candidate")) {
|
||||
IceCandidate candidate = new IceCandidate(
|
||||
(String) json.get("id"),
|
||||
json.getInt("label"),
|
||||
(String) json.get("candidate"));
|
||||
if (queuedRemoteCandidates != null) {
|
||||
queuedRemoteCandidates.add(candidate);
|
||||
} else {
|
||||
pc.addIceCandidate(candidate);
|
||||
}
|
||||
} else if (type.equals("answer") || type.equals("offer")) {
|
||||
SessionDescription sdp = new SessionDescription(
|
||||
SessionDescription.Type.fromCanonicalForm(type),
|
||||
preferISAC((String) json.get("sdp")));
|
||||
pc.setRemoteDescription(sdpObserver, sdp);
|
||||
} else if (type.equals("bye")) {
|
||||
logAndToast("Remote end hung up; dropping PeerConnection");
|
||||
disconnectAndExit();
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected message: " + data);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface public void onClose() {
|
||||
disconnectAndExit();
|
||||
}
|
||||
|
||||
@JavascriptInterface public void onError(int code, String description) {
|
||||
disconnectAndExit();
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect from remote resources, dispose of local resources, and exit.
|
||||
private void disconnectAndExit() {
|
||||
synchronized (quit[0]) {
|
||||
if (quit[0]) {
|
||||
return;
|
||||
}
|
||||
quit[0] = true;
|
||||
if (pc != null) {
|
||||
pc.dispose();
|
||||
pc = null;
|
||||
}
|
||||
if (appRtcClient != null) {
|
||||
appRtcClient.sendMessage("{\"type\": \"bye\"}");
|
||||
appRtcClient.disconnect();
|
||||
appRtcClient = null;
|
||||
}
|
||||
if (videoSource != null) {
|
||||
videoSource.dispose();
|
||||
videoSource = null;
|
||||
}
|
||||
if (factory != null) {
|
||||
factory.dispose();
|
||||
factory = null;
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user