From 07a050f9952b30088d9ef527a01da7722424c01f Mon Sep 17 00:00:00 2001 From: sakal Date: Mon, 13 Feb 2017 05:58:27 -0800 Subject: [PATCH] Add support for swapping feeds in Android AppRTCMobile. BUG=webrtc:6937 Review-Url: https://codereview.webrtc.org/2682943006 Cr-Commit-Position: refs/heads/master@{#16582} --- webrtc/examples/BUILD.gn | 3 +- .../examples/androidapp/AndroidManifest.xml | 5 +- .../androidapp/res/layout/activity_call.xml | 33 ++--- .../src/org/appspot/apprtc/CallActivity.java | 131 +++++++++--------- .../appspot/apprtc/PercentFrameLayout.java | 94 ------------- 5 files changed, 83 insertions(+), 183 deletions(-) delete mode 100644 webrtc/examples/androidapp/src/org/appspot/apprtc/PercentFrameLayout.java 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); - } - } - } -}