Add expandable video pip to 1:1 conversations.
This commit is contained in:
parent
6c94be70dc
commit
cee2702fdf
8 changed files with 334 additions and 16 deletions
|
@ -718,5 +718,10 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
public void onPageChanged(@NonNull CallParticipantsState.SelectedPage page) {
|
public void onPageChanged(@NonNull CallParticipantsState.SelectedPage page) {
|
||||||
viewModel.setIsViewingFocusedParticipant(page);
|
viewModel.setIsViewingFocusedParticipant(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocalPictureInPictureClicked() {
|
||||||
|
viewModel.onLocalPictureInPictureClicked();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,8 @@ public final class CallParticipantsState {
|
||||||
webRtcViewModel.getGroupState().isNotIdle(),
|
webRtcViewModel.getGroupState().isNotIdle(),
|
||||||
webRtcViewModel.getState(),
|
webRtcViewModel.getState(),
|
||||||
webRtcViewModel.getRemoteParticipants().size(),
|
webRtcViewModel.getRemoteParticipants().size(),
|
||||||
oldState.isViewingFocusedParticipant);
|
oldState.isViewingFocusedParticipant,
|
||||||
|
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
|
||||||
|
|
||||||
List<CallParticipant> participantsByLastSpoke = new ArrayList<>(webRtcViewModel.getRemoteParticipants());
|
List<CallParticipant> participantsByLastSpoke = new ArrayList<>(webRtcViewModel.getRemoteParticipants());
|
||||||
Collections.sort(participantsByLastSpoke, ComparatorCompat.reversed((p1, p2) -> Long.compare(p1.getLastSpoke(), p2.getLastSpoke())));
|
Collections.sort(participantsByLastSpoke, ComparatorCompat.reversed((p1, p2) -> Long.compare(p1.getLastSpoke(), p2.getLastSpoke())));
|
||||||
|
@ -207,7 +208,8 @@ public final class CallParticipantsState {
|
||||||
oldState.getGroupCallState().isNotIdle(),
|
oldState.getGroupCallState().isNotIdle(),
|
||||||
oldState.callState,
|
oldState.callState,
|
||||||
oldState.getAllRemoteParticipants().size(),
|
oldState.getAllRemoteParticipants().size(),
|
||||||
oldState.isViewingFocusedParticipant);
|
oldState.isViewingFocusedParticipant,
|
||||||
|
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
|
||||||
|
|
||||||
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
||||||
|
|
||||||
|
@ -223,6 +225,28 @@ public final class CallParticipantsState {
|
||||||
oldState.remoteDevicesCount);
|
oldState.remoteDevicesCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull CallParticipantsState setExpanded(@NonNull CallParticipantsState oldState, boolean expanded) {
|
||||||
|
WebRtcLocalRenderState localRenderState = determineLocalRenderMode(oldState.localParticipant,
|
||||||
|
oldState.isInPipMode,
|
||||||
|
oldState.showVideoForOutgoing,
|
||||||
|
oldState.getGroupCallState().isNotIdle(),
|
||||||
|
oldState.callState,
|
||||||
|
oldState.getAllRemoteParticipants().size(),
|
||||||
|
oldState.isViewingFocusedParticipant,
|
||||||
|
expanded);
|
||||||
|
|
||||||
|
return new CallParticipantsState(oldState.callState,
|
||||||
|
oldState.groupCallState,
|
||||||
|
oldState.remoteParticipants,
|
||||||
|
oldState.localParticipant,
|
||||||
|
oldState.focusedParticipant,
|
||||||
|
localRenderState,
|
||||||
|
oldState.isInPipMode,
|
||||||
|
oldState.showVideoForOutgoing,
|
||||||
|
oldState.isViewingFocusedParticipant,
|
||||||
|
oldState.remoteDevicesCount);
|
||||||
|
}
|
||||||
|
|
||||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull SelectedPage selectedPage) {
|
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull SelectedPage selectedPage) {
|
||||||
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
||||||
|
|
||||||
|
@ -232,7 +256,8 @@ public final class CallParticipantsState {
|
||||||
oldState.getGroupCallState().isNotIdle(),
|
oldState.getGroupCallState().isNotIdle(),
|
||||||
oldState.callState,
|
oldState.callState,
|
||||||
oldState.getAllRemoteParticipants().size(),
|
oldState.getAllRemoteParticipants().size(),
|
||||||
selectedPage == SelectedPage.FOCUSED);
|
selectedPage == SelectedPage.FOCUSED,
|
||||||
|
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
|
||||||
|
|
||||||
return new CallParticipantsState(oldState.callState,
|
return new CallParticipantsState(oldState.callState,
|
||||||
oldState.groupCallState,
|
oldState.groupCallState,
|
||||||
|
@ -252,12 +277,15 @@ public final class CallParticipantsState {
|
||||||
boolean isNonIdleGroupCall,
|
boolean isNonIdleGroupCall,
|
||||||
@NonNull WebRtcViewModel.State callState,
|
@NonNull WebRtcViewModel.State callState,
|
||||||
int numberOfRemoteParticipants,
|
int numberOfRemoteParticipants,
|
||||||
boolean isViewingFocusedParticipant)
|
boolean isViewingFocusedParticipant,
|
||||||
|
boolean isExpanded)
|
||||||
{
|
{
|
||||||
boolean displayLocal = (numberOfRemoteParticipants == 0 || !isInPip) && (isNonIdleGroupCall || localParticipant.isVideoEnabled());
|
boolean displayLocal = (numberOfRemoteParticipants == 0 || !isInPip) && (isNonIdleGroupCall || localParticipant.isVideoEnabled());
|
||||||
WebRtcLocalRenderState localRenderState = WebRtcLocalRenderState.GONE;
|
WebRtcLocalRenderState localRenderState = WebRtcLocalRenderState.GONE;
|
||||||
|
|
||||||
if (displayLocal || showVideoForOutgoing) {
|
if (isExpanded && (localParticipant.isVideoEnabled() || isNonIdleGroupCall)) {
|
||||||
|
return WebRtcLocalRenderState.EXPANDED;
|
||||||
|
} else if (displayLocal || showVideoForOutgoing) {
|
||||||
if (callState == WebRtcViewModel.State.CALL_CONNECTED) {
|
if (callState == WebRtcViewModel.State.CALL_CONNECTED) {
|
||||||
if (isViewingFocusedParticipant || numberOfRemoteParticipants > 1) {
|
if (isViewingFocusedParticipant || numberOfRemoteParticipants > 1) {
|
||||||
localRenderState = WebRtcLocalRenderState.SMALLER_RECTANGLE;
|
localRenderState = WebRtcLocalRenderState.SMALLER_RECTANGLE;
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
package org.thoughtcrime.securesms.components.webrtc;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helps manage the expansion and shrinking of the in-app pip.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
final class PictureInPictureExpansionHelper {
|
||||||
|
|
||||||
|
private State state = State.IS_SHRUNKEN;
|
||||||
|
|
||||||
|
public boolean isExpandedOrExpanding() {
|
||||||
|
return state == State.IS_EXPANDED || state == State.IS_EXPANDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShrunkenOrShrinking() {
|
||||||
|
return state == State.IS_SHRUNKEN || state == State.IS_SHRINKING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expand(@NonNull View toExpand, @NonNull Callback callback) {
|
||||||
|
if (isExpandedOrExpanding()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
performExpandAnimation(toExpand, new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationWillStart() {
|
||||||
|
state = State.IS_EXPANDING;
|
||||||
|
callback.onAnimationWillStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureExpanded() {
|
||||||
|
callback.onPictureInPictureExpanded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureNotVisible() {
|
||||||
|
callback.onPictureInPictureNotVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationHasFinished() {
|
||||||
|
state = State.IS_EXPANDED;
|
||||||
|
callback.onAnimationHasFinished();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shrink(@NonNull View toExpand, @NonNull Callback callback) {
|
||||||
|
if (isShrunkenOrShrinking()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
performShrinkAnimation(toExpand, new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationWillStart() {
|
||||||
|
state = State.IS_SHRINKING;
|
||||||
|
callback.onAnimationWillStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureExpanded() {
|
||||||
|
callback.onPictureInPictureExpanded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureNotVisible() {
|
||||||
|
callback.onPictureInPictureNotVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationHasFinished() {
|
||||||
|
state = State.IS_SHRUNKEN;
|
||||||
|
callback.onAnimationHasFinished();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performExpandAnimation(@NonNull View target, @NonNull Callback callback) {
|
||||||
|
ViewGroup parent = (ViewGroup) target.getParent();
|
||||||
|
|
||||||
|
float x = target.getX();
|
||||||
|
float y = target.getY();
|
||||||
|
float scaleX = parent.getMeasuredWidth() / (float) target.getMeasuredWidth();
|
||||||
|
float scaleY = parent.getMeasuredHeight() / (float) target.getMeasuredHeight();
|
||||||
|
float scale = Math.max(scaleX, scaleY);
|
||||||
|
|
||||||
|
callback.onAnimationWillStart();
|
||||||
|
|
||||||
|
target.animate()
|
||||||
|
.setDuration(200)
|
||||||
|
.x((parent.getMeasuredWidth() - target.getMeasuredWidth()) / 2f)
|
||||||
|
.y((parent.getMeasuredHeight() - target.getMeasuredHeight()) / 2f)
|
||||||
|
.scaleX(scale)
|
||||||
|
.scaleY(scale)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
callback.onPictureInPictureExpanded();
|
||||||
|
target.animate()
|
||||||
|
.setDuration(100)
|
||||||
|
.alpha(0f)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
callback.onPictureInPictureNotVisible();
|
||||||
|
|
||||||
|
target.setX(x);
|
||||||
|
target.setY(y);
|
||||||
|
target.setScaleX(0f);
|
||||||
|
target.setScaleY(0f);
|
||||||
|
target.setAlpha(1f);
|
||||||
|
|
||||||
|
target.animate()
|
||||||
|
.setDuration(200)
|
||||||
|
.scaleX(1f)
|
||||||
|
.scaleY(1f)
|
||||||
|
.withEndAction(callback::onAnimationHasFinished);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performShrinkAnimation(@NonNull View target, @NonNull Callback callback) {
|
||||||
|
ViewGroup parent = (ViewGroup) target.getParent();
|
||||||
|
|
||||||
|
float x = target.getX();
|
||||||
|
float y = target.getY();
|
||||||
|
float scaleX = parent.getMeasuredWidth() / (float) target.getMeasuredWidth();
|
||||||
|
float scaleY = parent.getMeasuredHeight() / (float) target.getMeasuredHeight();
|
||||||
|
float scale = Math.max(scaleX, scaleY);
|
||||||
|
|
||||||
|
callback.onAnimationWillStart();
|
||||||
|
|
||||||
|
target.animate()
|
||||||
|
.setDuration(200)
|
||||||
|
.scaleX(0f)
|
||||||
|
.scaleY(0f)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
target.setX((parent.getMeasuredWidth() - target.getMeasuredWidth()) / 2f);
|
||||||
|
target.setY((parent.getMeasuredHeight() - target.getMeasuredHeight()) / 2f);
|
||||||
|
target.setAlpha(0f);
|
||||||
|
target.setScaleX(scale);
|
||||||
|
target.setScaleY(scale);
|
||||||
|
|
||||||
|
callback.onPictureInPictureNotVisible();
|
||||||
|
|
||||||
|
target.animate()
|
||||||
|
.setDuration(100)
|
||||||
|
.alpha(1f)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
callback.onPictureInPictureExpanded();
|
||||||
|
|
||||||
|
target.animate()
|
||||||
|
.scaleX(1f)
|
||||||
|
.scaleY(1f)
|
||||||
|
.x(x)
|
||||||
|
.y(y)
|
||||||
|
.withEndAction(callback::onAnimationHasFinished);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
IS_EXPANDING,
|
||||||
|
IS_EXPANDED,
|
||||||
|
IS_SHRINKING,
|
||||||
|
IS_SHRUNKEN
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
/**
|
||||||
|
* Called when an animation (shrink or expand) will begin. This happens before any animation
|
||||||
|
* is executed.
|
||||||
|
*/
|
||||||
|
void onAnimationWillStart();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the PiP is covering the whole screen. This is when any staging / teardown of the
|
||||||
|
* large local renderer should occur.
|
||||||
|
*/
|
||||||
|
void onPictureInPictureExpanded();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the PiP is not visible on the screen anymore. This is when any staging / teardown
|
||||||
|
* of the pip should occur.
|
||||||
|
*/
|
||||||
|
void onPictureInPictureNotVisible();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the animation is complete. Useful for e.g. adjusting the pip's final location to
|
||||||
|
* make sure it is respecting the screen space available.
|
||||||
|
*/
|
||||||
|
void onAnimationHasFinished();
|
||||||
|
}
|
||||||
|
}
|
|
@ -222,8 +222,9 @@ public class PictureInPictureGestureHelper extends GestureDetector.SimpleOnGestu
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapUp(MotionEvent e) {
|
public boolean onSingleTapUp(MotionEvent e) {
|
||||||
child.performClick();
|
isDragging = false;
|
||||||
|
|
||||||
|
child.performClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package org.thoughtcrime.securesms.components.webrtc;
|
package org.thoughtcrime.securesms.components.webrtc;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorInflater;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.ColorMatrix;
|
import android.graphics.ColorMatrix;
|
||||||
import android.graphics.ColorMatrixColorFilter;
|
import android.graphics.ColorMatrixColorFilter;
|
||||||
|
@ -8,7 +12,13 @@ import android.util.AttributeSet;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewPropertyAnimator;
|
||||||
|
import android.view.animation.AlphaAnimation;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.AnimationSet;
|
||||||
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.view.animation.ScaleAnimation;
|
||||||
|
import android.view.animation.TranslateAnimation;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -102,6 +112,8 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
|
|
||||||
private WebRtcCallParticipantsPagerAdapter pagerAdapter;
|
private WebRtcCallParticipantsPagerAdapter pagerAdapter;
|
||||||
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
|
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
|
||||||
|
private PictureInPictureExpansionHelper pictureInPictureExpansionHelper;
|
||||||
|
|
||||||
|
|
||||||
private final Set<View> incomingCallViews = new HashSet<>();
|
private final Set<View> incomingCallViews = new HashSet<>();
|
||||||
private final Set<View> topViews = new HashSet<>();
|
private final Set<View> topViews = new HashSet<>();
|
||||||
|
@ -211,7 +223,14 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
answer.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallPressed));
|
answer.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallPressed));
|
||||||
answerWithAudio.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallWithVoiceOnlyPressed));
|
answerWithAudio.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallWithVoiceOnlyPressed));
|
||||||
|
|
||||||
pictureInPictureGestureHelper = PictureInPictureGestureHelper.applyTo(smallLocalRenderFrame);
|
pictureInPictureGestureHelper = PictureInPictureGestureHelper.applyTo(smallLocalRenderFrame);
|
||||||
|
pictureInPictureExpansionHelper = new PictureInPictureExpansionHelper();
|
||||||
|
|
||||||
|
smallLocalRenderFrame.setOnClickListener(v -> {
|
||||||
|
if (controlsListener != null) {
|
||||||
|
controlsListener.onLocalPictureInPictureClicked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
startCall.setOnClickListener(v -> {
|
startCall.setOnClickListener(v -> {
|
||||||
if (controlsListener != null) {
|
if (controlsListener != null) {
|
||||||
|
@ -301,7 +320,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
|
|
||||||
pagerAdapter.submitList(pages);
|
pagerAdapter.submitList(pages);
|
||||||
recyclerAdapter.submitList(state.getListParticipants());
|
recyclerAdapter.submitList(state.getListParticipants());
|
||||||
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant());
|
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant(), state.getFocusedParticipant());
|
||||||
|
|
||||||
if (state.isLargeVideoGroup() && !state.isInPipMode()) {
|
if (state.isLargeVideoGroup() && !state.isInPipMode()) {
|
||||||
layoutParticipantsForLargeCount();
|
layoutParticipantsForLargeCount();
|
||||||
|
@ -310,7 +329,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant) {
|
public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
|
||||||
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||||
largeLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
largeLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||||
|
|
||||||
|
@ -321,9 +340,18 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
largeLocalRender.init(localCallParticipant.getVideoSink().getEglBase());
|
largeLocalRender.init(localCallParticipant.getVideoSink().getEglBase());
|
||||||
}
|
}
|
||||||
|
|
||||||
smallLocalRender.setCallParticipant(localCallParticipant);
|
|
||||||
smallLocalRender.setRenderInPip(true);
|
|
||||||
videoToggle.setChecked(localCallParticipant.isVideoEnabled(), false);
|
videoToggle.setChecked(localCallParticipant.isVideoEnabled(), false);
|
||||||
|
smallLocalRender.setRenderInPip(true);
|
||||||
|
|
||||||
|
if (state == WebRtcLocalRenderState.EXPANDED) {
|
||||||
|
expandPip(localCallParticipant, focusedParticipant);
|
||||||
|
return;
|
||||||
|
} else if (state == WebRtcLocalRenderState.SMALL_RECTANGLE && pictureInPictureExpansionHelper.isExpandedOrExpanding()) {
|
||||||
|
shrinkPip(localCallParticipant);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||||
|
}
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case GONE:
|
case GONE:
|
||||||
|
@ -559,6 +587,54 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void expandPip(@NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
|
||||||
|
pictureInPictureExpansionHelper.expand(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationWillStart() {
|
||||||
|
largeLocalRender.attachBroadcastVideoSink(localCallParticipant.getVideoSink());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureExpanded() {
|
||||||
|
largeLocalRenderFrame.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureNotVisible() {
|
||||||
|
smallLocalRender.setCallParticipant(focusedParticipant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationHasFinished() {
|
||||||
|
pictureInPictureGestureHelper.adjustPip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shrinkPip(@NonNull CallParticipant localCallParticipant) {
|
||||||
|
pictureInPictureExpansionHelper.shrink(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationWillStart() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureExpanded() {
|
||||||
|
largeLocalRenderFrame.setVisibility(View.GONE);
|
||||||
|
largeLocalRender.attachBroadcastVideoSink(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureInPictureNotVisible() {
|
||||||
|
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationHasFinished() {
|
||||||
|
pictureInPictureGestureHelper.adjustPip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void animatePipToLargeRectangle() {
|
private void animatePipToLargeRectangle() {
|
||||||
ResizeAnimation animation = new ResizeAnimation(smallLocalRenderFrame, ViewUtil.dpToPx(90), ViewUtil.dpToPx(160));
|
ResizeAnimation animation = new ResizeAnimation(smallLocalRenderFrame, ViewUtil.dpToPx(90), ViewUtil.dpToPx(160));
|
||||||
animation.setDuration(PIP_RESIZE_DURATION);
|
animation.setDuration(PIP_RESIZE_DURATION);
|
||||||
|
@ -753,5 +829,6 @@ public class WebRtcCallView extends FrameLayout {
|
||||||
void onAcceptCallPressed();
|
void onAcceptCallPressed();
|
||||||
void onShowParticipantsList();
|
void onShowParticipantsList();
|
||||||
void onPageChanged(@NonNull CallParticipantsState.SelectedPage page);
|
void onPageChanged(@NonNull CallParticipantsState.SelectedPage page);
|
||||||
|
void onLocalPictureInPictureClicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,16 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||||
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), page));
|
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onLocalPictureInPictureClicked() {
|
||||||
|
CallParticipantsState state = participantsState.getValue();
|
||||||
|
if (state.getGroupCallState() != WebRtcViewModel.GroupCallState.IDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
participantsState.setValue(CallParticipantsState.setExpanded(participantsState.getValue(),
|
||||||
|
state.getLocalRenderState() != WebRtcLocalRenderState.EXPANDED));
|
||||||
|
}
|
||||||
|
|
||||||
public void onDismissedVideoTooltip() {
|
public void onDismissedVideoTooltip() {
|
||||||
canDisplayTooltipIfNeeded = false;
|
canDisplayTooltipIfNeeded = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,6 @@ public enum WebRtcLocalRenderState {
|
||||||
SMALL_RECTANGLE,
|
SMALL_RECTANGLE,
|
||||||
SMALLER_RECTANGLE,
|
SMALLER_RECTANGLE,
|
||||||
LARGE,
|
LARGE,
|
||||||
LARGE_NO_VIDEO
|
LARGE_NO_VIDEO,
|
||||||
|
EXPANDED
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,10 +95,8 @@
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="1"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
app:layout_constraintVertical_bias="0">
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:id="@+id/call_screen_pip"
|
android:id="@+id/call_screen_pip"
|
||||||
|
@ -110,7 +108,8 @@
|
||||||
android:translationY="100000dp"
|
android:translationY="100000dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:cardCornerRadius="8dp"
|
app:cardCornerRadius="8dp"
|
||||||
tools:background="@color/red"
|
tools:translationX="0dp"
|
||||||
|
tools:translationY="0dp"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
|
Loading…
Add table
Reference in a new issue