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}
This commit is contained in:
@ -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<VideoRenderer.Callbacks> remoteRenderers =
|
||||
new ArrayList<VideoRenderer.Callbacks>();
|
||||
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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user