diff --git a/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java b/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java
index acaa31e785..dd1692cf0f 100644
--- a/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java
+++ b/talk/app/webrtc/java/android/org/webrtc/VideoRendererGui.java
@@ -253,6 +253,8 @@ public class VideoRendererGui implements GLSurfaceView.Renderer {
private FloatBuffer textureCoords;
// Flag if texture vertices or coordinates update is needed.
private boolean updateTextureProperties;
+ // Texture properties update lock.
+ private final Object updateTextureLock = new Object();
// Viewport dimensions.
private int screenWidth;
private int screenHeight;
@@ -319,64 +321,68 @@ public class VideoRendererGui implements GLSurfaceView.Renderer {
scalingType == ScalingType.SCALE_FILL) {
return;
}
- // Re - calculate texture vertices to preserve video aspect ratio.
- float texRight = this.texRight;
- float texLeft = this.texLeft;
- float texTop = this.texTop;
- float texBottom = this.texBottom;
- float texOffsetU = 0;
- float texOffsetV = 0;
- float displayWidth = (texRight - texLeft) * screenWidth / 2;
- float displayHeight = (texTop - texBottom) * screenHeight / 2;
- Log.d(TAG, "ID: " + id + ". Display: " + displayWidth +
- " x " + displayHeight + ". Video: " + videoWidth +
- " x " + videoHeight);
- if (displayWidth > 1 && displayHeight > 1 &&
- videoWidth > 1 && videoHeight > 1) {
- float displayAspectRatio = displayWidth / displayHeight;
- float videoAspectRatio = (float)videoWidth / videoHeight;
- if (scalingType == ScalingType.SCALE_ASPECT_FIT) {
- // Need to re-adjust vertices width or height to match video AR.
- if (displayAspectRatio > videoAspectRatio) {
- float deltaX = (displayWidth - videoAspectRatio * displayHeight) /
- instance.screenWidth;
- texRight -= deltaX;
- texLeft += deltaX;
- } else {
- float deltaY = (displayHeight - displayWidth / videoAspectRatio) /
- instance.screenHeight;
- texTop -= deltaY;
- texBottom += deltaY;
+ synchronized(updateTextureLock) {
+ // Re - calculate texture vertices to preserve video aspect ratio.
+ float texRight = this.texRight;
+ float texLeft = this.texLeft;
+ float texTop = this.texTop;
+ float texBottom = this.texBottom;
+ float texOffsetU = 0;
+ float texOffsetV = 0;
+ float displayWidth = (texRight - texLeft) * screenWidth / 2;
+ float displayHeight = (texTop - texBottom) * screenHeight / 2;
+ Log.d(TAG, "ID: " + id + ". Display: " + displayWidth +
+ " x " + displayHeight + ". Video: " + videoWidth +
+ " x " + videoHeight);
+ if (displayWidth > 1 && displayHeight > 1 &&
+ videoWidth > 1 && videoHeight > 1) {
+ float displayAspectRatio = displayWidth / displayHeight;
+ float videoAspectRatio = (float)videoWidth / videoHeight;
+ if (scalingType == ScalingType.SCALE_ASPECT_FIT) {
+ // Need to re-adjust vertices width or height to match video AR.
+ if (displayAspectRatio > videoAspectRatio) {
+ float deltaX = (displayWidth - videoAspectRatio * displayHeight) /
+ instance.screenWidth;
+ texRight -= deltaX;
+ texLeft += deltaX;
+ } else {
+ float deltaY = (displayHeight - displayWidth / videoAspectRatio) /
+ instance.screenHeight;
+ texTop -= deltaY;
+ texBottom += deltaY;
+ }
}
- }
- if (scalingType == ScalingType.SCALE_ASPECT_FILL) {
- // Need to re-adjust UV coordinates to match display AR.
- if (displayAspectRatio > videoAspectRatio) {
- texOffsetV = (1.0f - videoAspectRatio / displayAspectRatio) / 2.0f;
- } else {
- texOffsetU = (1.0f - displayAspectRatio / videoAspectRatio) / 2.0f;
+ if (scalingType == ScalingType.SCALE_ASPECT_FILL) {
+ // Need to re-adjust UV coordinates to match display AR.
+ if (displayAspectRatio > videoAspectRatio) {
+ texOffsetV = (1.0f - videoAspectRatio / displayAspectRatio) /
+ 2.0f;
+ } else {
+ texOffsetU = (1.0f - displayAspectRatio / videoAspectRatio) /
+ 2.0f;
+ }
}
- }
- Log.d(TAG, " Texture vertices: (" + texLeft + "," + texBottom +
- ") - (" + texRight + "," + texTop + ")");
- float textureVeticesFloat[] = new float[] {
- texLeft, texTop,
- texLeft, texBottom,
- texRight, texTop,
- texRight, texBottom
- };
- textureVertices = directNativeFloatBuffer(textureVeticesFloat);
+ Log.d(TAG, " Texture vertices: (" + texLeft + "," + texBottom +
+ ") - (" + texRight + "," + texTop + ")");
+ float textureVeticesFloat[] = new float[] {
+ texLeft, texTop,
+ texLeft, texBottom,
+ texRight, texTop,
+ texRight, texBottom
+ };
+ textureVertices = directNativeFloatBuffer(textureVeticesFloat);
- Log.d(TAG, " Texture UV offsets: " + texOffsetU + ", " + texOffsetV);
- float textureCoordinatesFloat[] = new float[] {
- texOffsetU, texOffsetV, // left top
- texOffsetU, 1.0f - texOffsetV, // left bottom
- 1.0f - texOffsetU, texOffsetV, // right top
- 1.0f - texOffsetU, 1.0f - texOffsetV // right bottom
- };
- textureCoords = directNativeFloatBuffer(textureCoordinatesFloat);
+ Log.d(TAG, " Texture UV offsets: " + texOffsetU + ", " + texOffsetV);
+ float textureCoordinatesFloat[] = new float[] {
+ texOffsetU, texOffsetV, // left top
+ texOffsetU, 1.0f - texOffsetV, // left bottom
+ 1.0f - texOffsetU, texOffsetV, // right top
+ 1.0f - texOffsetU, 1.0f - texOffsetV // right bottom
+ };
+ textureCoords = directNativeFloatBuffer(textureCoordinatesFloat);
+ }
+ updateTextureProperties = false;
}
- updateTextureProperties = false;
}
private void draw() {
@@ -489,19 +495,23 @@ public class VideoRendererGui implements GLSurfaceView.Renderer {
}
public void setScreenSize(final int screenWidth, final int screenHeight) {
- this.screenWidth = screenWidth;
- this.screenHeight = screenHeight;
- updateTextureProperties = true;
+ synchronized(updateTextureLock) {
+ this.screenWidth = screenWidth;
+ this.screenHeight = screenHeight;
+ updateTextureProperties = true;
+ }
}
public void setPosition(int x, int y, int width, int height,
ScalingType scalingType) {
- texLeft = (x - 50) / 50.0f;
- texTop = (50 - y) / 50.0f;
- texRight = Math.min(1.0f, (x + width - 50) / 50.0f);
- texBottom = Math.max(-1.0f, (50 - y - height) / 50.0f);
- this.scalingType = scalingType;
- updateTextureProperties = true;
+ synchronized(updateTextureLock) {
+ texLeft = (x - 50) / 50.0f;
+ texTop = (50 - y) / 50.0f;
+ texRight = Math.min(1.0f, (x + width - 50) / 50.0f);
+ texBottom = Math.max(-1.0f, (50 - y - height) / 50.0f);
+ this.scalingType = scalingType;
+ updateTextureProperties = true;
+ }
}
@Override
diff --git a/talk/examples/android/res/drawable-hdpi/ic_action_full_screen.png b/talk/examples/android/res/drawable-hdpi/ic_action_full_screen.png
new file mode 100644
index 0000000000..22f30d31ca
Binary files /dev/null and b/talk/examples/android/res/drawable-hdpi/ic_action_full_screen.png differ
diff --git a/talk/examples/android/res/drawable-hdpi/ic_action_return_from_full_screen.png b/talk/examples/android/res/drawable-hdpi/ic_action_return_from_full_screen.png
new file mode 100644
index 0000000000..d9436e5248
Binary files /dev/null and b/talk/examples/android/res/drawable-hdpi/ic_action_return_from_full_screen.png differ
diff --git a/talk/examples/android/res/drawable-hdpi/ic_loopback_call.png b/talk/examples/android/res/drawable-hdpi/ic_loopback_call.png
new file mode 100644
index 0000000000..39311853b3
Binary files /dev/null and b/talk/examples/android/res/drawable-hdpi/ic_loopback_call.png differ
diff --git a/talk/examples/android/res/drawable-ldpi/ic_action_full_screen.png b/talk/examples/android/res/drawable-ldpi/ic_action_full_screen.png
new file mode 100644
index 0000000000..e4a9ff0a8e
Binary files /dev/null and b/talk/examples/android/res/drawable-ldpi/ic_action_full_screen.png differ
diff --git a/talk/examples/android/res/drawable-ldpi/ic_action_return_from_full_screen.png b/talk/examples/android/res/drawable-ldpi/ic_action_return_from_full_screen.png
new file mode 100644
index 0000000000..f5c80f00e7
Binary files /dev/null and b/talk/examples/android/res/drawable-ldpi/ic_action_return_from_full_screen.png differ
diff --git a/talk/examples/android/res/drawable-ldpi/ic_loopback_call.png b/talk/examples/android/res/drawable-ldpi/ic_loopback_call.png
new file mode 100644
index 0000000000..39311853b3
Binary files /dev/null and b/talk/examples/android/res/drawable-ldpi/ic_loopback_call.png differ
diff --git a/talk/examples/android/res/drawable-mdpi/ic_action_full_screen.png b/talk/examples/android/res/drawable-mdpi/ic_action_full_screen.png
new file mode 100644
index 0000000000..e4a9ff0a8e
Binary files /dev/null and b/talk/examples/android/res/drawable-mdpi/ic_action_full_screen.png differ
diff --git a/talk/examples/android/res/drawable-mdpi/ic_action_return_from_full_screen.png b/talk/examples/android/res/drawable-mdpi/ic_action_return_from_full_screen.png
new file mode 100644
index 0000000000..f5c80f00e7
Binary files /dev/null and b/talk/examples/android/res/drawable-mdpi/ic_action_return_from_full_screen.png differ
diff --git a/talk/examples/android/res/drawable-mdpi/ic_loopback_call.png b/talk/examples/android/res/drawable-mdpi/ic_loopback_call.png
new file mode 100644
index 0000000000..39311853b3
Binary files /dev/null and b/talk/examples/android/res/drawable-mdpi/ic_loopback_call.png differ
diff --git a/talk/examples/android/res/drawable-xhdpi/ic_action_full_screen.png b/talk/examples/android/res/drawable-xhdpi/ic_action_full_screen.png
new file mode 100644
index 0000000000..6d90c071d5
Binary files /dev/null and b/talk/examples/android/res/drawable-xhdpi/ic_action_full_screen.png differ
diff --git a/talk/examples/android/res/drawable-xhdpi/ic_action_return_from_full_screen.png b/talk/examples/android/res/drawable-xhdpi/ic_action_return_from_full_screen.png
new file mode 100644
index 0000000000..a773b34208
Binary files /dev/null and b/talk/examples/android/res/drawable-xhdpi/ic_action_return_from_full_screen.png differ
diff --git a/talk/examples/android/res/drawable-xhdpi/ic_loopback_call.png b/talk/examples/android/res/drawable-xhdpi/ic_loopback_call.png
new file mode 100644
index 0000000000..39311853b3
Binary files /dev/null and b/talk/examples/android/res/drawable-xhdpi/ic_loopback_call.png differ
diff --git a/talk/examples/android/res/layout/activity_connect.xml b/talk/examples/android/res/layout/activity_connect.xml
index 2d6f6f8adc..5ca0f191b1 100644
--- a/talk/examples/android/res/layout/activity_connect.xml
+++ b/talk/examples/android/res/layout/activity_connect.xml
@@ -7,37 +7,66 @@
android:weightSum="1"
android:layout_margin="8dp"
android:layout_centerHorizontal="true">
+
+
+
+
+
+
+
+ android:layout_margin="5dp"
+ android:text="@string/room_description"/>
+ android:imeOptions="actionDone"/>
-
+
-
+ android:choiceMode="singleChoice"
+ android:listSelector="@android:color/darker_gray"
+ android:drawSelectorOnTop="false" />
\ No newline at end of file
diff --git a/talk/examples/android/res/layout/fragment_menubar.xml b/talk/examples/android/res/layout/fragment_menubar.xml
index 1ff0a97261..77dc819db5 100644
--- a/talk/examples/android/res/layout/fragment_menubar.xml
+++ b/talk/examples/android/res/layout/fragment_menubar.xml
@@ -13,6 +13,7 @@
android:id="@+id/button_disconnect"
android:background="@drawable/disconnect"
android:contentDescription="@string/disconnect_call"
+ android:layout_marginRight="16dp"
android:layout_width="48dp"
android:layout_height="48dp"/>
@@ -22,6 +23,7 @@
android:id="@+id/button_switch_camera"
android:background="@android:drawable/ic_menu_camera"
android:contentDescription="@string/switch_camera"
+ android:layout_marginRight="8dp"
android:layout_width="48dp"
android:layout_height="48dp"/>
@@ -29,6 +31,14 @@
android:id="@+id/button_toggle_debug"
android:background="@android:drawable/ic_menu_info_details"
android:contentDescription="@string/disconnect_call"
+ android:layout_marginRight="8dp"
+ android:layout_width="48dp"
+ android:layout_height="48dp"/>
+
+
diff --git a/talk/examples/android/res/values/arrays.xml b/talk/examples/android/res/values/arrays.xml
index af56eb94ac..3127a85970 100644
--- a/talk/examples/android/res/values/arrays.xml
+++ b/talk/examples/android/res/values/arrays.xml
@@ -12,4 +12,11 @@
- 640 x 480
- 320 x 240
+
+
+ - Default
+ - 30 fps
+ - 15 fps
+
+
diff --git a/talk/examples/android/res/values/strings.xml b/talk/examples/android/res/values/strings.xml
index abe599c4e0..2a1d64ab36 100644
--- a/talk/examples/android/res/values/strings.xml
+++ b/talk/examples/android/res/values/strings.xml
@@ -3,24 +3,29 @@
AppRTC
AppRTC Settings
Disconnect Call
- Room name:
+ Room names:
Please enter a room name. Room names are shared with everyone, so think
of something unique and send it to a friend.
Connect
- Loopback connection
Invalid URL
The URL or room name you entered resulted in an invalid URL: %1$s
+ Connection error
Connecting to: %1$s
FATAL ERROR: Missing URL to connect to.
OK
Switch front/back camera
Settings
+ Add new room to the list
+ Remove room from the list
+ Connect to the room
+ Loopback connection
room_preference
+ room_list_preference
url_preference
Connection URL:
@@ -33,4 +38,10 @@
Video resolution.
Enter AppRTC local video resolution.
Default
+
+ fps_preference
+ Camera fps.
+ Camera fps.
+ Enter local camera fps.
+ Default
diff --git a/talk/examples/android/res/xml/preferences.xml b/talk/examples/android/res/xml/preferences.xml
index efabd3e3b4..f3f91d87c9 100644
--- a/talk/examples/android/res/xml/preferences.xml
+++ b/talk/examples/android/res/xml/preferences.xml
@@ -15,4 +15,12 @@
android:dialogTitle="@string/pref_resolution_dlg"
android:entries="@array/videoResolutions"
android:entryValues="@array/videoResolutionsValues" />
+
\ No newline at end of file
diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
index 4728e51587..5c34fcad19 100644
--- a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
@@ -80,7 +80,9 @@ public interface AppRTCClient {
}
/**
- * Signaling callbacks.
+ * Callback interface for messages delivered on signalling channel.
+ *
+ * Methods are guaranteed to be invoked on the UI thread of |activity|.
*/
public static interface AppRTCSignalingEvents {
/**
diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
index 8a875b205c..ad0e2d5506 100644
--- a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
+++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
@@ -32,6 +32,7 @@ import android.app.AlertDialog;
import android.app.Fragment;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
@@ -45,7 +46,6 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
-import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
@@ -58,6 +58,7 @@ import org.webrtc.StatsObserver;
import org.webrtc.StatsReport;
import org.webrtc.VideoRenderer;
import org.webrtc.VideoRendererGui;
+import org.webrtc.VideoRendererGui.ScalingType;
/**
* Main Activity of the AppRTCDemo Android app demonstrating interoperability
@@ -76,13 +77,14 @@ public class AppRTCDemoActivity extends Activity
private GLSurfaceView videoView;
private VideoRenderer.Callbacks localRender;
private VideoRenderer.Callbacks remoteRender;
+ private ScalingType scalingType;
private Toast logToast;
private final LayoutParams hudLayout =
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
private TextView hudView;
private TextView roomName;
- // Synchronize on quit[0] to avoid teardown-related crashes.
- private final Boolean[] quit = new Boolean[] { false };
+ private ImageButton videoScalingButton;
+ private boolean iceConnected;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -102,6 +104,7 @@ public class AppRTCDemoActivity extends Activity
Thread.setDefaultUncaughtExceptionHandler(
new UnhandledExceptionHandler(this));
+ iceConnected = false;
rootView = findViewById(android.R.id.content);
menuBar = findViewById(R.id.menubar_fragment);
@@ -109,10 +112,9 @@ public class AppRTCDemoActivity extends Activity
videoView = (GLSurfaceView) findViewById(R.id.glview);
VideoRendererGui.setView(videoView);
- remoteRender = VideoRendererGui.create(0, 0, 100, 100,
- VideoRendererGui.ScalingType.SCALE_ASPECT_FILL);
- localRender = VideoRendererGui.create(0, 0, 100, 100,
- VideoRendererGui.ScalingType.SCALE_ASPECT_FILL);
+ scalingType = ScalingType.SCALE_ASPECT_FILL;
+ remoteRender = VideoRendererGui.create(0, 0, 100, 100, scalingType);
+ localRender = VideoRendererGui.create(0, 0, 100, 100, scalingType);
videoView.setOnClickListener(
new View.OnClickListener() {
@@ -143,7 +145,9 @@ public class AppRTCDemoActivity extends Activity
new View.OnClickListener() {
@Override
public void onClick(View view) {
- pc.switchCamera();
+ if (pc != null) {
+ pc.switchCamera();
+ }
}
});
@@ -157,6 +161,24 @@ public class AppRTCDemoActivity extends Activity
}
});
+ videoScalingButton = (ImageButton) findViewById(R.id.button_scaling_mode);
+ videoScalingButton.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (scalingType == ScalingType.SCALE_ASPECT_FILL) {
+ videoScalingButton.setBackgroundResource(
+ R.drawable.ic_action_full_screen);
+ scalingType = ScalingType.SCALE_ASPECT_FIT;
+ } else {
+ videoScalingButton.setBackgroundResource(
+ R.drawable.ic_action_return_from_full_screen);
+ scalingType = ScalingType.SCALE_ASPECT_FILL;
+ }
+ updateVideoView();
+ }
+ });
+
hudView = new TextView(this);
hudView.setTextColor(Color.BLACK);
hudView.setBackgroundColor(Color.WHITE);
@@ -184,7 +206,11 @@ public class AppRTCDemoActivity extends Activity
(loopback != null && loopback.equals("loopback"))) {
logAndToast(getString(R.string.connecting_to, url));
appRtcClient.connectToRoom(url.toString());
- roomName.setText(room);
+ if (room != null && !room.equals("")) {
+ roomName.setText(room);
+ } else {
+ roomName.setText("loopback");
+ }
} else {
logAndToast("Empty or missing room name!");
finish();
@@ -230,6 +256,16 @@ public class AppRTCDemoActivity extends Activity
super.onDestroy();
}
+ private void updateVideoView() {
+ VideoRendererGui.update(remoteRender, 0, 0, 100, 100, scalingType);
+ if (iceConnected) {
+ VideoRendererGui.update(localRender, 70, 70, 28, 28,
+ ScalingType.SCALE_ASPECT_FIT);
+ } else {
+ VideoRendererGui.update(localRender, 0, 0, 100, 100, scalingType);
+ }
+ }
+
// Update the heads-up display with information from |reports|.
private void updateHUD(StatsReport[] reports) {
StringBuilder builder = new StringBuilder();
@@ -279,21 +315,28 @@ public class AppRTCDemoActivity extends Activity
// Disconnect from remote resources, dispose of local resources, and exit.
private void disconnect() {
- 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();
+ if (appRtcClient != null) {
+ appRtcClient.disconnect();
+ appRtcClient = null;
}
+ if (pc != null) {
+ pc.close();
+ pc = null;
+ }
+ finish();
+ }
+
+ private void disconnectWithMessage(String errorMessage) {
+ new AlertDialog.Builder(this)
+ .setTitle(getText(R.string.channel_error_title))
+ .setMessage(errorMessage)
+ .setCancelable(false)
+ .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ disconnect();
+ }
+ }).create().show();
}
// Poor-man's assert(): die with |msg| unless |condition| is true.
@@ -324,39 +367,41 @@ public class AppRTCDemoActivity extends Activity
logAndToast("Creating peer connection...");
pc = new PeerConnectionClient(
this, localRender, remoteRender, appRtcParameters, this);
+ if (pc.isHDVideo()) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ } else {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
{
- final PeerConnectionClient finalPC = pc;
final Runnable repeatedStatsLogger = new Runnable() {
- public void run() {
- synchronized (quit[0]) {
- if (quit[0]) {
- return;
- }
- final Runnable runnableThis = this;
- if (hudView.getVisibility() == View.INVISIBLE) {
- videoView.postDelayed(runnableThis, 1000);
- return;
- }
- boolean success = finalPC.getStats(new StatsObserver() {
- public void onComplete(final StatsReport[] reports) {
- runOnUiThread(new Runnable() {
- public void run() {
- updateHUD(reports);
- }
- });
- for (StatsReport report : reports) {
- Log.d(TAG, "Stats: " + report.toString());
- }
- videoView.postDelayed(runnableThis, 1000);
+ public void run() {
+ if (pc == null) {
+ return;
+ }
+ final Runnable runnableThis = this;
+ if (hudView.getVisibility() == View.INVISIBLE) {
+ videoView.postDelayed(runnableThis, 1000);
+ return;
+ }
+ boolean success = pc.getStats(new StatsObserver() {
+ public void onComplete(final StatsReport[] reports) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ updateHUD(reports);
+ }
+ });
+ for (StatsReport report : reports) {
+ Log.d(TAG, "Stats: " + report.toString());
}
- }, null);
- if (!success) {
- throw new RuntimeException("getStats() return false!");
- }
+ videoView.postDelayed(runnableThis, 1000);
+ }
+ }, null);
+ if (!success) {
+ throw new RuntimeException("getStats() return false!");
}
}
- };
+ };
videoView.postDelayed(repeatedStatsLogger, 1000);
}
@@ -365,6 +410,9 @@ public class AppRTCDemoActivity extends Activity
@Override
public void onChannelOpen() {
+ if (pc == null) {
+ return;
+ }
if (appRtcParameters.initiator) {
logAndToast("Creating OFFER...");
// Create offer. Offer SDP will be sent to answering client in
@@ -375,6 +423,9 @@ public class AppRTCDemoActivity extends Activity
@Override
public void onRemoteDescription(final SessionDescription sdp) {
+ if (pc == null) {
+ return;
+ }
logAndToast("Received remote " + sdp.type + " ...");
pc.setRemoteDescription(sdp);
if (!appRtcParameters.initiator) {
@@ -387,7 +438,9 @@ public class AppRTCDemoActivity extends Activity
@Override
public void onRemoteIceCandidate(final IceCandidate candidate) {
- pc.addRemoteIceCandidate(candidate);
+ if (pc != null) {
+ pc.addRemoteIceCandidate(candidate);
+ }
}
@Override
@@ -398,8 +451,7 @@ public class AppRTCDemoActivity extends Activity
@Override
public void onChannelError(int code, String description) {
- logAndToast("Channel error: " + code + ". " + description);
- disconnect();
+ disconnectWithMessage(description);
}
// -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
@@ -407,19 +459,35 @@ public class AppRTCDemoActivity extends Activity
// All callbacks are invoked from UI thread.
@Override
public void onLocalDescription(final SessionDescription sdp) {
- logAndToast("Sending " + sdp.type + " ...");
- appRtcClient.sendLocalDescription(sdp);
+ if (appRtcClient != null) {
+ logAndToast("Sending " + sdp.type + " ...");
+ appRtcClient.sendLocalDescription(sdp);
+ }
}
@Override
public void onIceCandidate(final IceCandidate candidate) {
- appRtcClient.sendLocalIceCandidate(candidate);
+ if (appRtcClient != null) {
+ appRtcClient.sendLocalIceCandidate(candidate);
+ }
}
@Override
public void onIceConnected() {
logAndToast("ICE connected");
- VideoRendererGui.update(localRender, 70, 70, 28, 28,
- VideoRendererGui.ScalingType.SCALE_ASPECT_FIT);
+ iceConnected = true;
+ updateVideoView();
}
+
+ @Override
+ public void onIceDisconnected() {
+ logAndToast("ICE disconnected");
+ disconnect();
+ }
+
+ @Override
+ public void onPeerConnectionError(String description) {
+ disconnectWithMessage(description);
+ }
+
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java b/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java
index 6a2ca5b315..8f00c1c815 100644
--- a/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java
+++ b/talk/examples/android/src/org/appspot/apprtc/ConnectActivity.java
@@ -32,7 +32,6 @@ import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
@@ -44,26 +43,40 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.webkit.URLUtil;
-import android.widget.Button;
-import android.widget.CheckBox;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ListView;
import android.widget.TextView;
+import java.util.ArrayList;
+
+import org.json.JSONArray;
+import org.json.JSONException;
import org.webrtc.MediaCodecVideoEncoder;
+
/**
* Handles the initial setup where the user selects which room to join.
*/
public class ConnectActivity extends Activity {
private static final String TAG = "ConnectActivity";
- private Button connectButton;
+ private ImageButton addRoomButton;
+ private ImageButton removeRoomButton;
+ private ImageButton connectButton;
+ private ImageButton connectLoopbackButton;
private EditText roomEditText;
- private CheckBox loopbackCheckBox;
+ private ListView roomListView;
private SharedPreferences sharedPref;
private String keyprefUrl;
private String keyprefResolution;
+ private String keyprefFps;
private String keyprefRoom;
+ private String keyprefRoomList;
+ private ArrayList roomList;
+ private ArrayAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -74,7 +87,9 @@ public class ConnectActivity extends Activity {
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
keyprefUrl = getString(R.string.pref_url_key);
keyprefResolution = getString(R.string.pref_resolution_key);
+ keyprefFps = getString(R.string.pref_fps_key);
keyprefRoom = getString(R.string.pref_room_key);
+ keyprefRoomList = getString(R.string.pref_room_list_key);
// If an implicit VIEW intent is launching the app, go directly to that URL.
final Intent intent = getIntent();
@@ -85,17 +100,14 @@ public class ConnectActivity extends Activity {
setContentView(R.layout.activity_connect);
- loopbackCheckBox = (CheckBox) findViewById(R.id.check_loopback);
- loopbackCheckBox.setChecked(false);
-
roomEditText = (EditText) findViewById(R.id.room_edittext);
roomEditText.setOnEditorActionListener(
new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(
TextView textView, int i, KeyEvent keyEvent) {
- if (i == EditorInfo.IME_ACTION_GO) {
- connectButton.performClick();
+ if (i == EditorInfo.IME_ACTION_DONE) {
+ addRoomButton.performClick();
return true;
}
return false;
@@ -103,42 +115,18 @@ public class ConnectActivity extends Activity {
});
roomEditText.requestFocus();
- connectButton = (Button) findViewById(R.id.connect_button);
- connectButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- String url = sharedPref.getString(keyprefUrl,
- getString(R.string.pref_url_default));
- if (loopbackCheckBox.isChecked()) {
- url += "/?debug=loopback";
- } else {
- url += "/?r=" + roomEditText.getText();
- }
+ roomListView = (ListView) findViewById(R.id.room_listview);
+ roomListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
- // Add video resolution constraints.
- String resolution = sharedPref.getString(keyprefResolution,
- getString(R.string.pref_resolution_default));
- String[] dimensions = resolution.split("[ x]+");
- if (dimensions.length == 2) {
- try {
- int maxWidth = Integer.parseInt(dimensions[0]);
- int maxHeight = Integer.parseInt(dimensions[1]);
- if (maxWidth > 0 && maxHeight > 0) {
- url += "&video=minHeight=" + maxHeight + ",maxHeight=" +
- maxHeight + ",minWidth=" + maxWidth + ",maxWidth=" + maxWidth;
- }
- } catch (NumberFormatException e) {
- Log.e(TAG, "Wrong video resolution setting: " + resolution);
- }
- } else {
- if (MediaCodecVideoEncoder.isPlatformSupported()) {
- url += "&hd=true";
- }
- }
- // TODO(kjellander): Add support for custom parameters to the URL.
- connectToRoom(url);
- }
- });
+ addRoomButton = (ImageButton) findViewById(R.id.add_room_button);
+ addRoomButton.setOnClickListener(addRoomListener);
+ removeRoomButton = (ImageButton) findViewById(R.id.remove_room_button);
+ removeRoomButton.setOnClickListener(removeRoomListener);
+ connectButton = (ImageButton) findViewById(R.id.connect_button);
+ connectButton.setOnClickListener(connectListener);
+ connectLoopbackButton =
+ (ImageButton) findViewById(R.id.connect_loopback_button);
+ connectLoopbackButton.setOnClickListener(connectListener);
}
@Override
@@ -163,8 +151,10 @@ public class ConnectActivity extends Activity {
public void onPause() {
super.onPause();
String room = roomEditText.getText().toString();
+ String roomListJson = new JSONArray(roomList).toString();
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(keyprefRoom, room);
+ editor.putString(keyprefRoomList, roomListJson);
editor.commit();
}
@@ -173,8 +163,100 @@ public class ConnectActivity extends Activity {
super.onResume();
String room = sharedPref.getString(keyprefRoom, "");
roomEditText.setText(room);
+ roomList = new ArrayList();
+ String roomListJson = sharedPref.getString(keyprefRoomList, null);
+ if (roomListJson != null) {
+ try {
+ JSONArray jsonArray = new JSONArray(roomListJson);
+ for (int i = 0; i < jsonArray.length(); i++) {
+ roomList.add(jsonArray.get(i).toString());
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "Failed to load room list: " + e.toString());
+ }
+ }
+ adapter = new ArrayAdapter(
+ this, android.R.layout.simple_list_item_1, roomList);
+ roomListView.setAdapter(adapter);
+ if (adapter.getCount() > 0) {
+ roomListView.requestFocus();
+ roomListView.setItemChecked(0, true);
+ }
}
+ private final OnClickListener connectListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ boolean loopback = false;
+ if (view.getId() == R.id.connect_loopback_button) {
+ loopback = true;
+ }
+ String url = sharedPref.getString(keyprefUrl,
+ getString(R.string.pref_url_default));
+ if (loopback) {
+ url += "/?debug=loopback";
+ } else {
+ String roomName = getSelectedItem();
+ if (roomName == null) {
+ roomName = roomEditText.getText().toString();
+ }
+ url += "/?r=" + roomName;
+ }
+ String parametersResolution = null;
+ String parametersFps = null;
+ // Add video resolution constraints.
+ String resolution = sharedPref.getString(keyprefResolution,
+ getString(R.string.pref_resolution_default));
+ String[] dimensions = resolution.split("[ x]+");
+ if (dimensions.length == 2) {
+ try {
+ int maxWidth = Integer.parseInt(dimensions[0]);
+ int maxHeight = Integer.parseInt(dimensions[1]);
+ if (maxWidth > 0 && maxHeight > 0) {
+ parametersResolution = "minHeight=" + maxHeight + ",maxHeight=" +
+ maxHeight + ",minWidth=" + maxWidth + ",maxWidth=" + maxWidth;
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Wrong video resolution setting: " + resolution);
+ }
+ }
+ // Add camera fps constraints.
+ String fps = sharedPref.getString(keyprefFps,
+ getString(R.string.pref_fps_default));
+ String[] fpsValues = fps.split("[ x]+");
+ if (fpsValues.length == 2) {
+ try {
+ int cameraFps = Integer.parseInt(fpsValues[0]);
+ if (cameraFps > 0) {
+ parametersFps = "minFrameRate=" + cameraFps +
+ ",maxFrameRate=" + cameraFps;
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Wrong camera fps setting: " + fps);
+ }
+ }
+ // Modify connection URL.
+ if (parametersResolution != null || parametersFps != null) {
+ url += "&video=";
+ if (parametersResolution != null) {
+ url += parametersResolution;
+ if (parametersFps != null) {
+ url += ",";
+ }
+ }
+ if (parametersFps != null) {
+ url += parametersFps;
+ }
+ } else {
+ if (MediaCodecVideoEncoder.isPlatformSupported()) {
+ url += "&hd=true";
+ }
+ }
+ // TODO(kjellander): Add support for custom parameters to the URL.
+ connectToRoom(url);
+ }
+ };
+
private void connectToRoom(String roomUrl) {
if (validateUrl(roomUrl)) {
Uri url = Uri.parse(roomUrl);
@@ -199,4 +281,42 @@ public class ConnectActivity extends Activity {
}).create().show();
return false;
}
+
+ private final OnClickListener addRoomListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ String newRoom = roomEditText.getText().toString();
+ if (newRoom.length() > 0 && !roomList.contains(newRoom)) {
+ adapter.add(newRoom);
+ adapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ private final OnClickListener removeRoomListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ String selectedRoom = getSelectedItem();
+ if (selectedRoom != null) {
+ adapter.remove(selectedRoom);
+ adapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ private String getSelectedItem() {
+ int position = AdapterView.INVALID_POSITION;
+ if (roomListView.getCheckedItemCount() > 0 && adapter.getCount() > 0) {
+ position = roomListView.getCheckedItemPosition();
+ if (position >= adapter.getCount()) {
+ position = AdapterView.INVALID_POSITION;
+ }
+ }
+ if (position != AdapterView.INVALID_POSITION) {
+ return adapter.getItem(position);
+ } else {
+ return null;
+ }
+ }
+
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/GAEChannelClient.java b/talk/examples/android/src/org/appspot/apprtc/GAEChannelClient.java
index bcc06ab49c..5fd0a54ed4 100644
--- a/talk/examples/android/src/org/appspot/apprtc/GAEChannelClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/GAEChannelClient.java
@@ -51,15 +51,12 @@ public class GAEChannelClient {
/**
* Callback interface for messages delivered on the Google AppEngine channel.
- *
- * Methods are guaranteed to be invoked on the UI thread of |activity| passed
- * to GAEChannelClient's constructor.
*/
public interface GAEMessageHandler {
public void onOpen();
- public void onMessage(String data);
+ public void onMessage(final String data);
public void onClose();
- public void onError(int code, String description);
+ public void onError(final int code, final String description);
}
/** Asynchronously open an AppEngine channel. */
@@ -83,8 +80,7 @@ public class GAEChannelClient {
", desc: " + description);
}
});
- proxyingMessageHandler =
- new ProxyingMessageHandler(activity, handler, token);
+ proxyingMessageHandler = new ProxyingMessageHandler(handler, token);
webView.addJavascriptInterface(
proxyingMessageHandler, "androidMessageHandler");
webView.loadUrl("file:///android_asset/channel.html");
@@ -102,72 +98,52 @@ public class GAEChannelClient {
}
// Helper class for proxying callbacks from the Java<->JS interaction
- // (private, background) thread to the Activity's UI thread.
+ // (private, background) thread.
private static class ProxyingMessageHandler {
- private final Activity activity;
private final GAEMessageHandler handler;
- private final boolean[] disconnected = { false };
+ private boolean disconnected = false;
private final String token;
- public
- ProxyingMessageHandler(Activity activity, GAEMessageHandler handler,
- String token) {
- this.activity = activity;
+ public ProxyingMessageHandler(GAEMessageHandler handler, String token) {
this.handler = handler;
this.token = token;
}
public void disconnect() {
- disconnected[0] = true;
+ disconnected = true;
}
- private boolean disconnected() {
- return disconnected[0];
- }
-
- @JavascriptInterface public String getToken() {
+ @JavascriptInterface
+ public String getToken() {
return token;
}
- @JavascriptInterface public void onOpen() {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- if (!disconnected()) {
- handler.onOpen();
- }
- }
- });
+ @JavascriptInterface
+ public void onOpen() {
+ if (!disconnected) {
+ handler.onOpen();
+ }
}
- @JavascriptInterface public void onMessage(final String data) {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- if (!disconnected()) {
- handler.onMessage(data);
- }
- }
- });
+ @JavascriptInterface
+ public void onMessage(final String data) {
+ if (!disconnected) {
+ handler.onMessage(data);
+ }
}
- @JavascriptInterface public void onClose() {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- if (!disconnected()) {
- handler.onClose();
- }
- }
- });
+ @JavascriptInterface
+ public void onClose() {
+ if (!disconnected) {
+ handler.onClose();
+ }
}
- @JavascriptInterface public void onError(
- final int code, final String description) {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- if (!disconnected()) {
- handler.onError(code, description);
- }
- }
- });
+ @JavascriptInterface
+ public void onError(final int code, final String description) {
+ if (!disconnected) {
+ handler.onError(code, description);
+ }
}
}
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java b/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java
index 1d1f81705d..c3d95641da 100644
--- a/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/GAERTCClient.java
@@ -29,7 +29,6 @@ package org.appspot.apprtc;
import android.app.Activity;
import android.os.AsyncTask;
import android.util.Log;
-import android.webkit.JavascriptInterface;
import org.json.JSONArray;
import org.json.JSONException;
@@ -41,7 +40,6 @@ import org.webrtc.SessionDescription;
import java.io.IOException;
import java.io.InputStream;
-import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedList;
@@ -83,11 +81,6 @@ public class GAERTCClient implements AppRTCClient {
*/
@Override
public void connectToRoom(String url) {
- while (url.indexOf('?') < 0) {
- // Keep redirecting until we get a room number.
- (new RedirectResolver()).execute(url);
- return; // RedirectResolver above calls us back with the next URL.
- }
(new RoomParameterGetter()).execute(url);
}
@@ -97,6 +90,7 @@ public class GAERTCClient implements AppRTCClient {
@Override
public void disconnect() {
if (channelClient != null) {
+ Log.d(TAG, "Closing GAE Channel.");
sendMessage("{\"type\": \"bye\"}");
channelClient.close();
channelClient = null;
@@ -151,68 +145,36 @@ public class GAERTCClient implements AppRTCClient {
}
}
- // Load the given URL and return the value of the Location header of the
- // resulting 302 response. If the result is not a 302, throws.
- private class RedirectResolver extends AsyncTask {
- @Override
- protected String doInBackground(String... urls) {
- if (urls.length != 1) {
- throw new RuntimeException("Must be called with a single URL");
- }
- try {
- return followRedirect(urls[0]);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- protected void onPostExecute(String url) {
- connectToRoom(url);
- }
-
- private String followRedirect(String url) throws IOException {
- HttpURLConnection connection = (HttpURLConnection)
- new URL(url).openConnection();
- connection.setInstanceFollowRedirects(false);
- int code = connection.getResponseCode();
- if (code != HttpURLConnection.HTTP_MOVED_TEMP) {
- throw new IOException("Unexpected response: " + code + " for " + url +
- ", with contents: " + drainStream(connection.getInputStream()));
- }
- int n = 0;
- String name, value;
- while ((name = connection.getHeaderFieldKey(n)) != null) {
- value = connection.getHeaderField(n);
- if (name.equals("Location")) {
- return value;
- }
- ++n;
- }
- throw new IOException("Didn't find Location header!");
- }
- }
-
// AsyncTask that converts an AppRTC room URL into the set of signaling
// parameters to use with that room.
private class RoomParameterGetter
extends AsyncTask {
+ private Exception exception = null;
+
@Override
protected AppRTCSignalingParameters doInBackground(String... urls) {
if (urls.length != 1) {
- throw new RuntimeException("Must be called with a single URL");
+ exception = new RuntimeException("Must be called with a single URL");
+ return null;
}
try {
+ exception = null;
return getParametersForRoomUrl(urls[0]);
} catch (JSONException e) {
- throw new RuntimeException(e);
+ exception = e;
} catch (IOException e) {
- throw new RuntimeException(e);
+ exception = e;
}
+ return null;
}
@Override
protected void onPostExecute(AppRTCSignalingParameters params) {
+ if (exception != null) {
+ Log.e(TAG, "Room connection error: " + exception.toString());
+ events.onChannelError(0, exception.getMessage());
+ return;
+ }
channelClient =
new GAEChannelClient(activity, channelToken, gaeHandler);
synchronized (sendQueue) {
@@ -445,42 +407,67 @@ public class GAERTCClient implements AppRTCClient {
// Implementation detail: handler for receiving GAE messages and dispatching
// them appropriately.
private class GAEHandler implements GAEChannelClient.GAEMessageHandler {
- @JavascriptInterface public void onOpen() {
- events.onChannelOpen();
- }
+ private boolean channelOpen = false;
- @JavascriptInterface public void onMessage(String msg) {
- Log.d(TAG, "RECEIVE: " + msg);
- try {
- JSONObject json = new JSONObject(msg);
- 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"));
- events.onRemoteIceCandidate(candidate);
- } else if (type.equals("answer") || type.equals("offer")) {
- SessionDescription sdp = new SessionDescription(
- SessionDescription.Type.fromCanonicalForm(type),
- (String)json.get("sdp"));
- events.onRemoteDescription(sdp);
- } else if (type.equals("bye")) {
- events.onChannelClose();
- } else {
- throw new RuntimeException("Unexpected message: " + msg);
+ public void onOpen() {
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ events.onChannelOpen();
+ channelOpen = true;
}
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
+ });
}
- @JavascriptInterface public void onClose() {
- events.onChannelClose();
+ public void onMessage(final String msg) {
+ Log.d(TAG, "RECEIVE: " + msg);
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ if (!channelOpen) {
+ return;
+ }
+ try {
+ JSONObject json = new JSONObject(msg);
+ 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"));
+ events.onRemoteIceCandidate(candidate);
+ } else if (type.equals("answer") || type.equals("offer")) {
+ SessionDescription sdp = new SessionDescription(
+ SessionDescription.Type.fromCanonicalForm(type),
+ (String)json.get("sdp"));
+ events.onRemoteDescription(sdp);
+ } else if (type.equals("bye")) {
+ events.onChannelClose();
+ } else {
+ events.onChannelError(1, "Unexpected channel message: " + msg);
+ }
+ } catch (JSONException e) {
+ events.onChannelError(1, "Channel message JSON parsing error: " +
+ e.toString());
+ }
+ }
+ });
}
- @JavascriptInterface public void onError(int code, String description) {
- events.onChannelError(code, description);
+ public void onClose() {
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ events.onChannelClose();
+ channelOpen = false;
+ }
+ });
+ }
+
+ public void onError(final int code, final String description) {
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ events.onChannelError(code, description);
+ channelOpen = false;
+ }
+ });
}
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
index 68159989f3..daea5fb067 100644
--- a/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
+++ b/talk/examples/android/src/org/appspot/apprtc/PeerConnectionClient.java
@@ -37,6 +37,7 @@ import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.MediaStreamTrack;
import org.webrtc.PeerConnection;
+import org.webrtc.MediaConstraints.KeyValuePair;
import org.webrtc.PeerConnection.IceConnectionState;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
@@ -60,13 +61,13 @@ public class PeerConnectionClient {
private boolean videoSourceStopped;
private final PCObserver pcObserver = new PCObserver();
private final SDPObserver sdpObserver = new SDPObserver();
- private final MediaConstraints videoConstraints;
private final VideoRenderer.Callbacks localRender;
private final VideoRenderer.Callbacks remoteRender;
private LinkedList queuedRemoteCandidates =
new LinkedList();
- private final MediaConstraints sdpMediaConstraints;
- private final PeerConnectionEvents events;
+ private MediaConstraints sdpMediaConstraints;
+ private MediaConstraints videoConstraints;
+ private PeerConnectionEvents events;
private boolean isInitiator;
private boolean useFrontFacingCamera = true;
private SessionDescription localSdp = null; // either offer or answer SDP
@@ -79,7 +80,6 @@ public class PeerConnectionClient {
AppRTCSignalingParameters appRtcParameters,
PeerConnectionEvents events) {
this.activity = activity;
- this.videoConstraints = appRtcParameters.videoConstraints;
this.localRender = localRender;
this.remoteRender = remoteRender;
this.events = events;
@@ -90,9 +90,9 @@ public class PeerConnectionClient {
"OfferToReceiveAudio", "true"));
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
"OfferToReceiveVideo", "true"));
+ videoConstraints = appRtcParameters.videoConstraints;
factory = new PeerConnectionFactory();
-
MediaConstraints pcConstraints = appRtcParameters.pcConstraints;
pcConstraints.optional.add(
new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
@@ -122,38 +122,91 @@ public class PeerConnectionClient {
}
}
+ public boolean isHDVideo() {
+ if (videoConstraints == null) {
+ return false;
+ }
+ int minWidth = 0;
+ int minHeight = 0;
+ for (KeyValuePair keyValuePair : videoConstraints.mandatory) {
+ if (keyValuePair.getKey().equals("minWidth")) {
+ try {
+ minWidth = Integer.parseInt(keyValuePair.getValue());
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Can not parse video width from video constraints");
+ }
+ } else if (keyValuePair.getKey().equals("minHeight")) {
+ try {
+ minHeight = Integer.parseInt(keyValuePair.getValue());
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Can not parse video height from video constraints");
+ }
+ }
+ }
+ if (minWidth * minHeight >= 1280 * 720) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
return pc.getStats(observer, track);
}
public void createOffer() {
- isInitiator = true;
- pc.createOffer(sdpObserver, sdpMediaConstraints);
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ if (pc != null) {
+ isInitiator = true;
+ pc.createOffer(sdpObserver, sdpMediaConstraints);
+ }
+ }
+ });
}
public void createAnswer() {
- isInitiator = false;
- pc.createAnswer(sdpObserver, sdpMediaConstraints);
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ if (pc != null) {
+ isInitiator = false;
+ pc.createAnswer(sdpObserver, sdpMediaConstraints);
+ }
+ }
+ });
}
- public void addRemoteIceCandidate(IceCandidate candidate) {
- if (queuedRemoteCandidates != null) {
- queuedRemoteCandidates.add(candidate);
- } else {
- pc.addIceCandidate(candidate);
- }
+ public void addRemoteIceCandidate(final IceCandidate candidate) {
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ if (pc != null) {
+ if (queuedRemoteCandidates != null) {
+ queuedRemoteCandidates.add(candidate);
+ } else {
+ pc.addIceCandidate(candidate);
+ }
+ }
+ }
+ });
}
- public void setRemoteDescription(SessionDescription sdp) {
- SessionDescription sdpISAC = new SessionDescription(
- sdp.type, preferISAC(sdp.description));
- Log.d(TAG, "Set remote SDP");
- pc.setRemoteDescription(sdpObserver, sdpISAC);
+ public void setRemoteDescription(final SessionDescription sdp) {
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ if (pc != null) {
+ SessionDescription sdpISAC = new SessionDescription(
+ sdp.type, preferISAC(sdp.description));
+ Log.d(TAG, "Set remote SDP");
+ pc.setRemoteDescription(sdpObserver, sdpISAC);
+ }
+ }
+ });
}
public void stopVideoSource() {
if (videoSource != null) {
+ Log.d(TAG, "Stop video source.");
videoSource.stop();
videoSourceStopped = true;
}
@@ -161,24 +214,30 @@ public class PeerConnectionClient {
public void startVideoSource() {
if (videoSource != null && videoSourceStopped) {
+ Log.d(TAG, "Restart video source.");
videoSource.restart();
videoSourceStopped = false;
}
}
public void close() {
- if (pc != null) {
- pc.dispose();
- pc = null;
- }
- if (videoSource != null) {
- videoSource.dispose();
- videoSource = null;
- }
- if (factory != null) {
- factory.dispose();
- factory = null;
- }
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ Log.d(TAG, "Closing peer connection.");
+ if (pc != null) {
+ pc.dispose();
+ pc = null;
+ }
+ if (videoSource != null) {
+ videoSource.dispose();
+ videoSource = null;
+ }
+ if (factory != null) {
+ factory.dispose();
+ factory = null;
+ }
+ }
+ });
}
/**
@@ -200,6 +259,25 @@ public class PeerConnectionClient {
* CONNECTED).
*/
public void onIceConnected();
+
+ /**
+ * Callback fired once connection is closed (IceConnectionState is
+ * DISCONNECTED).
+ */
+ public void onIceDisconnected();
+
+ /**
+ * Callback fired once peer connection error happened.
+ */
+ public void onPeerConnectionError(String description);
+ }
+
+ private void reportError(final String errorMessage) {
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ events.onPeerConnectionError(errorMessage);
+ }
+ });
}
// Cycle through likely device names for the camera and return the first
@@ -225,29 +303,30 @@ public class PeerConnectionClient {
}
}
}
- throw new RuntimeException("Failed to open capturer");
+ reportError("Failed to open capturer");
+ return null;
}
private VideoTrack createVideoTrack(boolean frontFacing) {
- VideoCapturer capturer = getVideoCapturer(frontFacing);
- if (videoSource != null) {
- videoSource.stop();
- videoSource.dispose();
- }
+ VideoCapturer capturer = getVideoCapturer(frontFacing);
+ if (videoSource != null) {
+ videoSource.stop();
+ videoSource.dispose();
+ }
- videoSource = factory.createVideoSource(
- capturer, videoConstraints);
- String trackExtension = frontFacing ? "frontFacing" : "backFacing";
- VideoTrack videoTrack =
- factory.createVideoTrack("ARDAMSv0" + trackExtension, videoSource);
- videoTrack.addRenderer(new VideoRenderer(localRender));
- return videoTrack;
+ videoSource = factory.createVideoSource(
+ capturer, videoConstraints);
+ String trackExtension = frontFacing ? "frontFacing" : "backFacing";
+ VideoTrack videoTrack =
+ factory.createVideoTrack("ARDAMSv0" + trackExtension, videoSource);
+ videoTrack.addRenderer(new VideoRenderer(localRender));
+ return videoTrack;
}
// Poor-man's assert(): die with |msg| unless |condition| is true.
- private static void abortUnless(boolean condition, String msg) {
+ private void abortUnless(boolean condition, String msg) {
if (!condition) {
- throw new RuntimeException(msg);
+ reportError(msg);
}
}
@@ -355,19 +434,15 @@ public class PeerConnectionClient {
@Override
public void onIceCandidate(final IceCandidate candidate){
activity.runOnUiThread(new Runnable() {
- public void run() {
- events.onIceCandidate(candidate);
- }
- });
+ public void run() {
+ events.onIceCandidate(candidate);
+ }
+ });
}
@Override
- public void onError(){
- activity.runOnUiThread(new Runnable() {
- public void run() {
- throw new RuntimeException("PeerConnection error!");
- }
- });
+ public void onError() {
+ reportError("PeerConnection error!");
}
@Override
@@ -386,47 +461,50 @@ public class PeerConnectionClient {
events.onIceConnected();
}
});
+ } else if (newState == IceConnectionState.DISCONNECTED) {
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ events.onIceDisconnected();
+ }
+ });
+ } else if (newState == IceConnectionState.FAILED) {
+ reportError("ICE connection failed.");
}
}
@Override
public void onIceGatheringChange(
- PeerConnection.IceGatheringState newState) {
+ PeerConnection.IceGatheringState newState) {
}
@Override
public void onAddStream(final MediaStream stream){
activity.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));
- }
+ 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){
activity.runOnUiThread(new Runnable() {
- public void run() {
- stream.videoTracks.get(0).dispose();
- }
- });
+ public void run() {
+ stream.videoTracks.get(0).dispose();
+ }
+ });
}
@Override
public void onDataChannel(final DataChannel dc) {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- throw new RuntimeException(
- "AppRTC doesn't use data channels, but got: " + dc.label() +
- " anyway!");
- }
- });
+ reportError("AppRTC doesn't use data channels, but got: " + dc.label() +
+ " anyway!");
}
@Override
@@ -446,65 +524,62 @@ public class PeerConnectionClient {
origSdp.type, preferISAC(origSdp.description));
localSdp = sdp;
activity.runOnUiThread(new Runnable() {
- public void run() {
+ public void run() {
+ if (pc != null) {
Log.d(TAG, "Set local SDP from " + sdp.type);
pc.setLocalDescription(sdpObserver, sdp);
}
- });
+ }
+ });
}
@Override
public void onSetSuccess() {
activity.runOnUiThread(new Runnable() {
- public void run() {
- if (isInitiator) {
- // For offering peer connection we first create offer and set
- // local SDP, then after receiving answer set remote SDP.
- if (pc.getRemoteDescription() == null) {
- // We've just set our local SDP so time to send it.
- Log.d(TAG, "Local SDP set succesfully");
- events.onLocalDescription(localSdp);
- } else {
- // We've just set remote description,
- // so drain remote ICE candidates.
- Log.d(TAG, "Remote SDP set succesfully");
- drainRemoteCandidates();
- }
+ public void run() {
+ if (pc == null) {
+ return;
+ }
+ if (isInitiator) {
+ // For offering peer connection we first create offer and set
+ // local SDP, then after receiving answer set remote SDP.
+ if (pc.getRemoteDescription() == null) {
+ // We've just set our local SDP so time to send it.
+ Log.d(TAG, "Local SDP set succesfully");
+ events.onLocalDescription(localSdp);
} else {
- // For answering peer connection we set remote SDP and then
- // create answer and set local SDP.
- if (pc.getLocalDescription() != null) {
- // We've just set our local SDP so time to send it and drain
- // remote ICE candidates.
- Log.d(TAG, "Local SDP set succesfully");
- events.onLocalDescription(localSdp);
- drainRemoteCandidates();
- } else {
- // We've just set remote SDP - do nothing for now -
- // answer will be created soon.
- Log.d(TAG, "Remote SDP set succesfully");
- }
+ // We've just set remote description,
+ // so drain remote ICE candidates.
+ Log.d(TAG, "Remote SDP set succesfully");
+ drainRemoteCandidates();
+ }
+ } else {
+ // For answering peer connection we set remote SDP and then
+ // create answer and set local SDP.
+ if (pc.getLocalDescription() != null) {
+ // We've just set our local SDP so time to send it and drain
+ // remote ICE candidates.
+ Log.d(TAG, "Local SDP set succesfully");
+ events.onLocalDescription(localSdp);
+ drainRemoteCandidates();
+ } else {
+ // We've just set remote SDP - do nothing for now -
+ // answer will be created soon.
+ Log.d(TAG, "Remote SDP set succesfully");
}
}
- });
+ }
+ });
}
@Override
public void onCreateFailure(final String error) {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- throw new RuntimeException("createSDP error: " + error);
- }
- });
+ reportError("createSDP error: " + error);
}
@Override
public void onSetFailure(final String error) {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- throw new RuntimeException("setSDP error: " + error);
- }
- });
+ reportError("setSDP error: " + error);
}
}
@@ -524,11 +599,7 @@ public class PeerConnectionClient {
@Override
public void onSetFailure(final String error) {
- activity.runOnUiThread(new Runnable() {
- public void run() {
- throw new RuntimeException("setSDP error while switching camera: " + error);
- }
- });
+ reportError("setSDP error while switching camera: " + error);
}
}
}
diff --git a/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java b/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java
index e974aafce8..2354ceb4aa 100644
--- a/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java
+++ b/talk/examples/android/src/org/appspot/apprtc/SettingsActivity.java
@@ -38,12 +38,14 @@ public class SettingsActivity extends Activity
private SettingsFragment settingsFragment;
private String keyprefUrl;
private String keyprefResolution;
+ private String keyprefFps;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
keyprefUrl = getString(R.string.pref_url_key);
keyprefResolution = getString(R.string.pref_resolution_key);
+ keyprefFps = getString(R.string.pref_fps_key);
// Display the fragment as the main content.
settingsFragment = new SettingsFragment();
@@ -61,6 +63,7 @@ public class SettingsActivity extends Activity
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
updateSummary(sharedPreferences, keyprefUrl);
updateSummary(sharedPreferences, keyprefResolution);
+ updateSummary(sharedPreferences, keyprefFps);
}
@Override
@@ -74,7 +77,8 @@ public class SettingsActivity extends Activity
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
- if (key.equals(keyprefUrl) || key.equals(keyprefResolution)) {
+ if (key.equals(keyprefUrl) || key.equals(keyprefResolution) ||
+ key.equals(keyprefFps)) {
updateSummary(sharedPreferences, key);
}
}
diff --git a/talk/libjingle_examples.gyp b/talk/libjingle_examples.gyp
index a610146eed..d0f1747626 100755
--- a/talk/libjingle_examples.gyp
+++ b/talk/libjingle_examples.gyp
@@ -317,14 +317,26 @@
'examples/android/build.xml',
'examples/android/jni/Android.mk',
'examples/android/project.properties',
- 'examples/android/res/drawable-hdpi/ic_launcher.png',
'examples/android/res/drawable-hdpi/disconnect.png',
- 'examples/android/res/drawable-ldpi/ic_launcher.png',
+ 'examples/android/res/drawable-hdpi/ic_action_full_screen.png',
+ 'examples/android/res/drawable-hdpi/ic_action_return_from_full_screen.png',
+ 'examples/android/res/drawable-hdpi/ic_loopback_call.png',
+ 'examples/android/res/drawable-hdpi/ic_launcher.png',
'examples/android/res/drawable-ldpi/disconnect.png',
- 'examples/android/res/drawable-mdpi/ic_launcher.png',
+ 'examples/android/res/drawable-ldpi/ic_action_full_screen.png',
+ 'examples/android/res/drawable-ldpi/ic_action_return_from_full_screen.png',
+ 'examples/android/res/drawable-ldpi/ic_loopback_call.png',
+ 'examples/android/res/drawable-ldpi/ic_launcher.png',
'examples/android/res/drawable-mdpi/disconnect.png',
- 'examples/android/res/drawable-xhdpi/ic_launcher.png',
+ 'examples/android/res/drawable-mdpi/ic_action_full_screen.png',
+ 'examples/android/res/drawable-mdpi/ic_action_return_from_full_screen.png',
+ 'examples/android/res/drawable-mdpi/ic_loopback_call.png',
+ 'examples/android/res/drawable-mdpi/ic_launcher.png',
'examples/android/res/drawable-xhdpi/disconnect.png',
+ 'examples/android/res/drawable-xhdpi/ic_action_full_screen.png',
+ 'examples/android/res/drawable-xhdpi/ic_action_return_from_full_screen.png',
+ 'examples/android/res/drawable-xhdpi/ic_loopback_call.png',
+ 'examples/android/res/drawable-xhdpi/ic_launcher.png',
'examples/android/res/layout/activity_connect.xml',
'examples/android/res/layout/activity_fullscreen.xml',
'examples/android/res/layout/fragment_menubar.xml',