Android AppRTCDemo improvements:
- Add a room list to ConnectActivity with buttons to add/remove rooms. - Add loopback call button. - Add option to toggle full screen / letterbox video. - Add camera fps settings. - Fix device to landscape orientation for HD video until issue 3936 will be fixed. - Fix a few crashes by avoiding calling peer connection and GAE signaling function while connection is closing. - Better handling GAE channel error - catch channel exceptions and display dialog with error messages. BUG=3939, 3935 R=kjellander@webrtc.org, pthatcher@webrtc.org, tkchin@webrtc.org Review URL: https://webrtc-codereview.appspot.com/26979004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7601 4adac7df-926f-26a2-2b94-8c16560cd09d
@ -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
|
||||
|
After Width: | Height: | Size: 587 B |
After Width: | Height: | Size: 663 B |
BIN
talk/examples/android/res/drawable-hdpi/ic_loopback_call.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 461 B |
After Width: | Height: | Size: 477 B |
BIN
talk/examples/android/res/drawable-ldpi/ic_loopback_call.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 461 B |
After Width: | Height: | Size: 477 B |
BIN
talk/examples/android/res/drawable-mdpi/ic_loopback_call.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 743 B |
After Width: | Height: | Size: 761 B |
BIN
talk/examples/android/res/drawable-xhdpi/ic_loopback_call.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
@ -7,37 +7,66 @@
|
||||
android:weightSum="1"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_centerHorizontal="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
<ImageButton
|
||||
android:id="@+id/add_room_button"
|
||||
android:background="@android:drawable/ic_menu_add"
|
||||
android:contentDescription="@string/add_room_description"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"/>
|
||||
<ImageButton
|
||||
android:id="@+id/remove_room_button"
|
||||
android:background="@android:drawable/ic_delete"
|
||||
android:contentDescription="@string/remove_room_description"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"/>
|
||||
<ImageButton
|
||||
android:id="@+id/connect_button"
|
||||
android:background="@android:drawable/sym_action_call"
|
||||
android:contentDescription="@string/connect_description"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"/>
|
||||
<ImageButton
|
||||
android:id="@+id/connect_loopback_button"
|
||||
android:background="@drawable/ic_loopback_call"
|
||||
android:contentDescription="@string/connect_loopback_description"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"/>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/room_edittext_description"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:lines="1"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/room_name"/>
|
||||
android:layout_margin="5dp"
|
||||
android:text="@string/room_description"/>
|
||||
<EditText
|
||||
android:id="@+id/room_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:id="@+id/room_edittext"
|
||||
android:imeOptions="actionGo"/>
|
||||
android:imeOptions="actionDone"/>
|
||||
<TextView
|
||||
android:id="@+id/room_listview_description"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:lines="1"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_margin="15dp"
|
||||
android:text="@string/room_description"/>
|
||||
<CheckBox
|
||||
android:id="@+id/check_loopback"
|
||||
android:text="@string/room_names"/>
|
||||
<ListView
|
||||
android:id="@+id/room_listview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/loopback_text" />
|
||||
<Button
|
||||
android:id="@+id/connect_button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/connect_text"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
android:choiceMode="singleChoice"
|
||||
android:listSelector="@android:color/darker_gray"
|
||||
android:drawSelectorOnTop="false" />
|
||||
|
||||
</LinearLayout>
|
@ -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"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_scaling_mode"
|
||||
android:background="@drawable/ic_action_return_from_full_screen"
|
||||
android:contentDescription="@string/disconnect_call"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"/>
|
||||
|
||||
|
@ -12,4 +12,11 @@
|
||||
<item>640 x 480</item>
|
||||
<item>320 x 240</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="cameraFps">
|
||||
<item>Default</item>
|
||||
<item>30 fps</item>
|
||||
<item>15 fps</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
@ -3,24 +3,29 @@
|
||||
<string name="app_name" translatable="no">AppRTC</string>
|
||||
<string name="settings_name" translatable="no">AppRTC Settings</string>
|
||||
<string name="disconnect_call">Disconnect Call</string>
|
||||
<string name="room_name">Room name:</string>
|
||||
<string name="room_names">Room names:</string>
|
||||
<string name="room_description">
|
||||
Please enter a room name. Room names are shared with everyone, so think
|
||||
of something unique and send it to a friend.
|
||||
</string>
|
||||
<string name="connect_text">Connect</string>
|
||||
<string name="loopback_text">Loopback connection</string>
|
||||
<string name="invalid_url_title">Invalid URL</string>
|
||||
<string name="invalid_url_text">The URL or room name you entered resulted in an invalid URL: %1$s
|
||||
</string>
|
||||
<string name="channel_error_title">Connection error</string>
|
||||
<string name="connecting_to">Connecting to: %1$s</string>
|
||||
<string name="missing_url">FATAL ERROR: Missing URL to connect to.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="switch_camera">Switch front/back camera</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="add_room_description">Add new room to the list</string>
|
||||
<string name="remove_room_description">Remove room from the list</string>
|
||||
<string name="connect_description">Connect to the room</string>
|
||||
<string name="connect_loopback_description">Loopback connection</string>
|
||||
|
||||
<!-- Settings strings. -->
|
||||
<string name="pref_room_key">room_preference</string>
|
||||
<string name="pref_room_list_key">room_list_preference</string>
|
||||
|
||||
<string name="pref_url_key">url_preference</string>
|
||||
<string name="pref_url_title">Connection URL:</string>
|
||||
@ -33,4 +38,10 @@
|
||||
<string name="pref_resolution_summary">Video resolution.</string>
|
||||
<string name="pref_resolution_dlg">Enter AppRTC local video resolution.</string>
|
||||
<string name="pref_resolution_default">Default</string>
|
||||
|
||||
<string name="pref_fps_key">fps_preference</string>
|
||||
<string name="pref_fps_title">Camera fps.</string>
|
||||
<string name="pref_fps_summary">Camera fps.</string>
|
||||
<string name="pref_fps_dlg">Enter local camera fps.</string>
|
||||
<string name="pref_fps_default">Default</string>
|
||||
</resources>
|
||||
|
@ -15,4 +15,12 @@
|
||||
android:dialogTitle="@string/pref_resolution_dlg"
|
||||
android:entries="@array/videoResolutions"
|
||||
android:entryValues="@array/videoResolutionsValues" />
|
||||
<ListPreference
|
||||
android:key="@string/pref_fps_key"
|
||||
android:title="@string/pref_fps_title"
|
||||
android:summary="@string/pref_fps_summary"
|
||||
android:defaultValue="@string/pref_fps_default"
|
||||
android:dialogTitle="@string/pref_fps_dlg"
|
||||
android:entries="@array/cameraFps"
|
||||
android:entryValues="@array/cameraFps" />
|
||||
</PreferenceScreen>
|
@ -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 {
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<String> roomList;
|
||||
private ArrayAdapter<String> 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>();
|
||||
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<String>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String, Void, String> {
|
||||
@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<String, Void, AppRTCSignalingParameters> {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<IceCandidate> queuedRemoteCandidates =
|
||||
new LinkedList<IceCandidate>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|