diff --git a/webrtc/examples/BUILD.gn b/webrtc/examples/BUILD.gn
index 3ee9ae5e9f..59842fc524 100644
--- a/webrtc/examples/BUILD.gn
+++ b/webrtc/examples/BUILD.gn
@@ -64,6 +64,8 @@ if (is_android) {
}
android_library("AppRTCMobile_javalib") {
+ android_manifest = "androidapp/AndroidManifest.xml"
+
java_files = [
"androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java",
"androidapp/src/org/appspot/apprtc/AppRTCBluetoothManager.java",
@@ -77,7 +79,6 @@ if (is_android) {
"androidapp/src/org/appspot/apprtc/DirectRTCClient.java",
"androidapp/src/org/appspot/apprtc/HudFragment.java",
"androidapp/src/org/appspot/apprtc/PeerConnectionClient.java",
- "androidapp/src/org/appspot/apprtc/PercentFrameLayout.java",
"androidapp/src/org/appspot/apprtc/RoomParametersFetcher.java",
"androidapp/src/org/appspot/apprtc/SettingsActivity.java",
"androidapp/src/org/appspot/apprtc/SettingsFragment.java",
diff --git a/webrtc/examples/androidapp/AndroidManifest.xml b/webrtc/examples/androidapp/AndroidManifest.xml
index f777ee5a7d..f85bf4e528 100644
--- a/webrtc/examples/androidapp/AndroidManifest.xml
+++ b/webrtc/examples/androidapp/AndroidManifest.xml
@@ -19,10 +19,13 @@
+
+
+ android:debuggable="true"
+ android:supportsRtl="false">
-
+
-
-
-
+ android:layout_height="match_parent" />
-
-
-
+
-
+
diff --git a/webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java b/webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java
index 309a5be0da..0197bcb322 100644
--- a/webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java
+++ b/webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java
@@ -134,34 +134,37 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
// Peer connection statistics callback period in ms.
private static final int STAT_CALLBACK_PERIOD = 1000;
- // Local preview screen position before call is connected.
- private static final int LOCAL_X_CONNECTING = 0;
- private static final int LOCAL_Y_CONNECTING = 0;
- private static final int LOCAL_WIDTH_CONNECTING = 100;
- private static final int LOCAL_HEIGHT_CONNECTING = 100;
- // Local preview screen position after call is connected.
- private static final int LOCAL_X_CONNECTED = 72;
- private static final int LOCAL_Y_CONNECTED = 72;
- private static final int LOCAL_WIDTH_CONNECTED = 25;
- private static final int LOCAL_HEIGHT_CONNECTED = 25;
- // Remote video screen position
- private static final int REMOTE_X = 0;
- private static final int REMOTE_Y = 0;
- private static final int REMOTE_WIDTH = 100;
- private static final int REMOTE_HEIGHT = 100;
+
+ private class ProxyRenderer implements VideoRenderer.Callbacks {
+ private VideoRenderer.Callbacks target;
+
+ synchronized public void renderFrame(VideoRenderer.I420Frame frame) {
+ if (target == null) {
+ Logging.d(TAG, "Dropping frame in proxy because target is null.");
+ VideoRenderer.renderFrameDone(frame);
+ return;
+ }
+
+ target.renderFrame(frame);
+ }
+
+ synchronized public void setTarget(VideoRenderer.Callbacks target) {
+ this.target = target;
+ }
+ }
+
+ private final ProxyRenderer remoteProxyRenderer = new ProxyRenderer();
+ private final ProxyRenderer localProxyRenderer = new ProxyRenderer();
private PeerConnectionClient peerConnectionClient = null;
private AppRTCClient appRtcClient;
private SignalingParameters signalingParameters;
private AppRTCAudioManager audioManager = null;
private EglBase rootEglBase;
- private SurfaceViewRenderer localRender;
- private SurfaceViewRenderer remoteRenderScreen;
+ private SurfaceViewRenderer pipRenderer;
+ private SurfaceViewRenderer fullscreenRenderer;
private VideoFileRenderer videoFileRenderer;
private final List remoteRenderers =
new ArrayList();
- private PercentFrameLayout localRenderLayout;
- private PercentFrameLayout remoteRenderLayout;
- private ScalingType scalingType;
private Toast logToast;
private boolean commandLineRun;
private int runTimeMs;
@@ -176,6 +179,8 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
private boolean screencaptureEnabled = false;
private static Intent mediaProjectionPermissionResultData;
private static int mediaProjectionPermissionResultCode;
+ // True if local view is in the fullscreen renderer.
+ private boolean isSwappedFeeds;
// Controls
private CallFragment callFragment;
@@ -198,13 +203,10 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
iceConnected = false;
signalingParameters = null;
- scalingType = ScalingType.SCALE_ASPECT_FILL;
// Create UI controls.
- localRender = (SurfaceViewRenderer) findViewById(R.id.local_video_view);
- remoteRenderScreen = (SurfaceViewRenderer) findViewById(R.id.remote_video_view);
- localRenderLayout = (PercentFrameLayout) findViewById(R.id.local_video_layout);
- remoteRenderLayout = (PercentFrameLayout) findViewById(R.id.remote_video_layout);
+ pipRenderer = (SurfaceViewRenderer) findViewById(R.id.pip_video_view);
+ fullscreenRenderer = (SurfaceViewRenderer) findViewById(R.id.fullscreen_video_view);
callFragment = new CallFragment();
hudFragment = new HudFragment();
@@ -216,15 +218,23 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
}
};
- localRender.setOnClickListener(listener);
- remoteRenderScreen.setOnClickListener(listener);
- remoteRenderers.add(remoteRenderScreen);
+ // Swap feeds on pip view click.
+ pipRenderer.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ setSwappedFeeds(!isSwappedFeeds);
+ }
+ });
+
+ fullscreenRenderer.setOnClickListener(listener);
+ remoteRenderers.add(remoteProxyRenderer);
final Intent intent = getIntent();
// Create video renderers.
rootEglBase = EglBase.create();
- localRender.init(rootEglBase.getEglBaseContext(), null);
+ pipRenderer.init(rootEglBase.getEglBaseContext(), null);
+ pipRenderer.setScalingType(ScalingType.SCALE_ASPECT_FIT);
String saveRemoteVideoToFile = intent.getStringExtra(EXTRA_SAVE_REMOTE_VIDEO_TO_FILE);
// When saveRemoteVideoToFile is set we save the video from the remote to a file.
@@ -240,12 +250,14 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
"Failed to open video file for output: " + saveRemoteVideoToFile, e);
}
}
- remoteRenderScreen.init(rootEglBase.getEglBaseContext(), null);
+ fullscreenRenderer.init(rootEglBase.getEglBaseContext(), null);
+ fullscreenRenderer.setScalingType(ScalingType.SCALE_ASPECT_FILL);
- localRender.setZOrderMediaOverlay(true);
- localRender.setEnableHardwareScaler(true /* enabled */);
- remoteRenderScreen.setEnableHardwareScaler(true /* enabled */);
- updateVideoView();
+ pipRenderer.setZOrderMediaOverlay(true);
+ pipRenderer.setEnableHardwareScaler(true /* enabled */);
+ fullscreenRenderer.setEnableHardwareScaler(true /* enabled */);
+ // Start with local feed in fullscreen and swap it to the pip when the call is connected.
+ setSwappedFeeds(true /* isSwappedFeeds */);
// Check for mandatory permissions.
for (String permission : MANDATORY_PERMISSIONS) {
@@ -508,8 +520,7 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
@Override
public void onVideoScalingSwitch(ScalingType scalingType) {
- this.scalingType = scalingType;
- updateVideoView();
+ fullscreenRenderer.setScalingType(scalingType);
}
@Override
@@ -547,26 +558,6 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
ft.commit();
}
- private void updateVideoView() {
- remoteRenderLayout.setPosition(REMOTE_X, REMOTE_Y, REMOTE_WIDTH, REMOTE_HEIGHT);
- remoteRenderScreen.setScalingType(scalingType);
- remoteRenderScreen.setMirror(false);
-
- if (iceConnected) {
- localRenderLayout.setPosition(
- LOCAL_X_CONNECTED, LOCAL_Y_CONNECTED, LOCAL_WIDTH_CONNECTED, LOCAL_HEIGHT_CONNECTED);
- localRender.setScalingType(ScalingType.SCALE_ASPECT_FIT);
- } else {
- localRenderLayout.setPosition(
- LOCAL_X_CONNECTING, LOCAL_Y_CONNECTING, LOCAL_WIDTH_CONNECTING, LOCAL_HEIGHT_CONNECTING);
- localRender.setScalingType(scalingType);
- }
- localRender.setMirror(true);
-
- localRender.requestLayout();
- remoteRenderScreen.requestLayout();
- }
-
private void startCall() {
if (appRtcClient == null) {
Log.e(TAG, "AppRTC client is not allocated for a call.");
@@ -603,10 +594,9 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
Log.w(TAG, "Call is connected in closed or error state");
return;
}
- // Update video view.
- updateVideoView();
// Enable statistics callback.
peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD);
+ setSwappedFeeds(false /* isSwappedFeeds */);
}
// This method is called when the audio manager reports audio device change,
@@ -621,6 +611,8 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
// Disconnect from remote resources, dispose of local resources, and exit.
private void disconnect() {
activityRunning = false;
+ remoteProxyRenderer.setTarget(null);
+ localProxyRenderer.setTarget(null);
if (appRtcClient != null) {
appRtcClient.disconnectFromRoom();
appRtcClient = null;
@@ -629,17 +621,17 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
peerConnectionClient.close();
peerConnectionClient = null;
}
- if (localRender != null) {
- localRender.release();
- localRender = null;
+ if (pipRenderer != null) {
+ pipRenderer.release();
+ pipRenderer = null;
}
if (videoFileRenderer != null) {
videoFileRenderer.release();
videoFileRenderer = null;
}
- if (remoteRenderScreen != null) {
- remoteRenderScreen.release();
- remoteRenderScreen = null;
+ if (fullscreenRenderer != null) {
+ fullscreenRenderer.release();
+ fullscreenRenderer = null;
}
if (audioManager != null) {
audioManager.stop();
@@ -728,6 +720,15 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
return videoCapturer;
}
+ private void setSwappedFeeds(boolean isSwappedFeeds) {
+ Logging.d(TAG, "setSwappedFeeds: " + isSwappedFeeds);
+ this.isSwappedFeeds = isSwappedFeeds;
+ localProxyRenderer.setTarget(isSwappedFeeds ? fullscreenRenderer : pipRenderer);
+ remoteProxyRenderer.setTarget(isSwappedFeeds ? pipRenderer : fullscreenRenderer);
+ fullscreenRenderer.setMirror(isSwappedFeeds);
+ pipRenderer.setMirror(!isSwappedFeeds);
+ }
+
// -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
// All callbacks are invoked from websocket signaling looper thread and
// are routed to UI thread.
@@ -740,7 +741,7 @@ public class CallActivity extends Activity implements AppRTCClient.SignalingEven
if (peerConnectionParameters.videoCallEnabled) {
videoCapturer = createVideoCapturer();
}
- peerConnectionClient.createPeerConnection(rootEglBase.getEglBaseContext(), localRender,
+ peerConnectionClient.createPeerConnection(rootEglBase.getEglBaseContext(), localProxyRenderer,
remoteRenderers, videoCapturer, signalingParameters);
if (signalingParameters.initiator) {
diff --git a/webrtc/examples/androidapp/src/org/appspot/apprtc/PercentFrameLayout.java b/webrtc/examples/androidapp/src/org/appspot/apprtc/PercentFrameLayout.java
deleted file mode 100644
index 81f22ebde5..0000000000
--- a/webrtc/examples/androidapp/src/org/appspot/apprtc/PercentFrameLayout.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2015 The WebRTC Project Authors. All rights reserved.
- *
- * Use of this source code is governed by a BSD-style license
- * that can be found in the LICENSE file in the root of the source
- * tree. An additional intellectual property rights grant can be found
- * in the file PATENTS. All contributing project authors may
- * be found in the AUTHORS file in the root of the source tree.
- */
-
-package org.appspot.apprtc;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Simple container that confines the children to a subrectangle specified as percentage values of
- * the container size. The children are centered horizontally and vertically inside the confined
- * space.
- */
-public class PercentFrameLayout extends ViewGroup {
- private int xPercent = 0;
- private int yPercent = 0;
- private int widthPercent = 100;
- private int heightPercent = 100;
-
- public PercentFrameLayout(Context context) {
- super(context);
- }
-
- public PercentFrameLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public PercentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public void setPosition(int xPercent, int yPercent, int widthPercent, int heightPercent) {
- this.xPercent = xPercent;
- this.yPercent = yPercent;
- this.widthPercent = widthPercent;
- this.heightPercent = heightPercent;
- }
-
- @Override
- public boolean shouldDelayChildPressedState() {
- return false;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int width = getDefaultSize(Integer.MAX_VALUE, widthMeasureSpec);
- final int height = getDefaultSize(Integer.MAX_VALUE, heightMeasureSpec);
- setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
-
- final int childWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(width * widthPercent / 100, MeasureSpec.AT_MOST);
- final int childHeightMeasureSpec =
- MeasureSpec.makeMeasureSpec(height * heightPercent / 100, MeasureSpec.AT_MOST);
- for (int i = 0; i < getChildCount(); ++i) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- final int width = right - left;
- final int height = bottom - top;
- // Sub-rectangle specified by percentage values.
- final int subWidth = width * widthPercent / 100;
- final int subHeight = height * heightPercent / 100;
- final int subLeft = left + width * xPercent / 100;
- final int subTop = top + height * yPercent / 100;
-
- for (int i = 0; i < getChildCount(); ++i) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final int childWidth = child.getMeasuredWidth();
- final int childHeight = child.getMeasuredHeight();
- // Center child both vertically and horizontally.
- final int childLeft = subLeft + (subWidth - childWidth) / 2;
- final int childTop = subTop + (subHeight - childHeight) / 2;
- child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
- }
- }
- }
-}