diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index 108c087a66..74686820d9 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -3894,6 +3894,13 @@ button.module-image__border-overlay:focus {
justify-content: center;
overflow: hidden;
+ &--darken {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ background-color: rgba(variables.$color-gray-60, 0.6);
+ }
+
&--blur {
background-repeat: no-repeat;
background-size: cover;
@@ -4636,13 +4643,16 @@ button.module-image__border-overlay:focus {
0px 0px 8px rgba(0, 0, 0, 0.05),
0px 8px 20px rgba(0, 0, 0, 0.3);
cursor: grab;
- // This is just a starting height; the component will figure out what height it should
- // be, given the aspect ratio of the provided video, pinning the width.
- // These both should be kept in sync with the height/width in CallingPip.tsx
+ // This size is just a starting place; the component will figure out what it should
+ // be. When resizing for incoming portrait video, the width will be kept constant. The
+ // width only changes with window size.
+ // These both should be kept in sync with the _normal_ height/width in CallingPip.tsx
height: 286px;
width: 160px;
+
position: fixed;
z-index: variables.$z-index-calling-pip;
+ overflow: hidden;
& .module-ongoing-call__group-call-remote-participant {
border-radius: 0;
@@ -4676,6 +4686,8 @@ button.module-image__border-overlay:focus {
position: absolute;
top: 8px;
inset-inline-start: 8px;
+ // These are the normal widths; the size will change based on whether the window
+ // is large, like the overall pip.
height: 54px;
width: 80px;
@@ -4717,7 +4729,7 @@ button.module-image__border-overlay:focus {
position: absolute;
bottom: 66px;
inset-inline-start: 8px;
- transition: bottom 0.3s variables.$ease-out-local-preview 0.3s;
+ transition: bottom 0.5s variables.$ease-out-local-preview;
&--no-controls {
bottom: 8px;
@@ -4760,16 +4772,18 @@ button.module-image__border-overlay:focus {
&__actions {
position: absolute;
- bottom: 4px;
+ // It starts offscreen then animates in/out as it fades in/outs
+ bottom: -60px;
inset-inline-start: 4px;
inset-inline-end: 4px;
padding: 12px;
height: 56px;
opacity: 0;
- transition: opacity 1s ease-in-out;
+ transition: all 0.5s variables.$ease-out-local-preview;
&--visible {
opacity: 1;
+ bottom: 4px;
}
display: flex;
@@ -4780,13 +4794,20 @@ button.module-image__border-overlay:focus {
justify-content: space-around;
background-color: variables.$color-gray-78;
+ &__spacer {
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
&__button {
flex-shrink: 0;
flex-grow: 0;
}
&__middle-button {
- flex-grow: 1;
+ // icon width, and 15px of border on each side
+ width: 62px;
text-align: center;
+ flex-shrink: 0;
+ flex-grow: 0;
}
.CallingButton__icon {
@@ -4801,7 +4822,7 @@ button.module-image__border-overlay:focus {
inset-inline-end: 16px;
opacity: 0;
- transition: opacity 1s ease-in-out;
+ transition: opacity 0.5s variables.$ease-out-local-preview;
&--visible {
opacity: 1;
diff --git a/ts/components/CallBackgroundBlur.tsx b/ts/components/CallBackgroundBlur.tsx
index 0640111960..ce046620ba 100644
--- a/ts/components/CallBackgroundBlur.tsx
+++ b/ts/components/CallBackgroundBlur.tsx
@@ -8,12 +8,14 @@ export type PropsType = {
avatarUrl?: string;
children?: React.ReactNode;
className?: string;
+ darken?: boolean;
};
export function CallBackgroundBlur({
avatarUrl,
children,
className,
+ darken,
}: PropsType): JSX.Element {
return (
)}
+ {darken && }
{children}
);
diff --git a/ts/components/CallingPip.tsx b/ts/components/CallingPip.tsx
index 7a95c61bc6..5384a94866 100644
--- a/ts/components/CallingPip.tsx
+++ b/ts/components/CallingPip.tsx
@@ -90,15 +90,19 @@ export type PropsType = {
toggleVideo: () => void;
};
-const PIP_STARTING_HEIGHT = 286;
-const PIP_WIDTH = 160;
+const PIP_STARTING_HEIGHT_NORMAL = 286;
+const PIP_STARTING_HEIGHT_LARGE = 400;
+const LARGE_THRESHOLD = 1200;
+
+export const PIP_WIDTH_NORMAL = 160;
+const PIP_WIDTH_LARGE = 224;
const PIP_TOP_MARGIN = 78;
const PIP_PADDING = 8;
// Receiving portrait video will cause the PIP to update to match that video size, but
// we need limits
-export const PIP_MINIMUM_HEIGHT = 180;
-export const PIP_MAXIMUM_HEIGHT = 360;
+export const PIP_MINIMUM_HEIGHT_MULTIPLIER = 1.2;
+export const PIP_MAXIMUM_HEIGHT_MULTIPLIER = 2;
export function CallingPip({
activeCall,
@@ -120,7 +124,6 @@ export function CallingPip({
const videoContainerRef = React.useRef(null);
- const [height, setHeight] = React.useState(PIP_STARTING_HEIGHT);
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);
const [windowHeight, setWindowHeight] = React.useState(window.innerHeight);
const [positionState, setPositionState] = React.useState({
@@ -128,6 +131,14 @@ export function CallingPip({
offsetY: PIP_TOP_MARGIN,
});
+ const isWindowLarge = windowWidth >= LARGE_THRESHOLD;
+ const [height, setHeight] = React.useState(
+ isWindowLarge ? PIP_STARTING_HEIGHT_LARGE : PIP_STARTING_HEIGHT_NORMAL
+ );
+ const [width, setWidth] = React.useState(
+ isWindowLarge ? PIP_WIDTH_LARGE : PIP_WIDTH_NORMAL
+ );
+
useActivateSpeakerViewOnPresenting({
remoteParticipants: activeCall.remoteParticipants,
switchToPresentationView,
@@ -164,11 +175,11 @@ export function CallingPip({
let distanceToLeftEdge: number;
let distanceToRightEdge: number;
if (isRTL) {
- distanceToLeftEdge = innerWidth - (offsetX + PIP_WIDTH);
+ distanceToLeftEdge = innerWidth - (offsetX + width);
distanceToRightEdge = offsetX;
} else {
distanceToLeftEdge = offsetX;
- distanceToRightEdge = innerWidth - (offsetX + PIP_WIDTH);
+ distanceToRightEdge = innerWidth - (offsetX + width);
}
const snapCandidates: Array = [
@@ -208,14 +219,14 @@ export function CallingPip({
case PositionMode.SnapToBottom:
setPositionState({
mode: snapTo.mode,
- offsetX: isRTL ? innerWidth - (offsetX + PIP_WIDTH) : offsetX,
+ offsetX: isRTL ? innerWidth - (offsetX + width) : offsetX,
});
break;
default:
throw missingCaseError(snapTo.mode);
}
}
- }, [height, isRTL, positionState, setPositionState]);
+ }, [height, isRTL, positionState, setPositionState, width]);
React.useEffect(() => {
if (positionState.mode === PositionMode.BeingDragged) {
@@ -248,6 +259,17 @@ export function CallingPip({
};
}, []);
+ // This only runs when isWindowLarge changes, so we aggressively change height + width
+ React.useEffect(() => {
+ if (isWindowLarge) {
+ setHeight(PIP_STARTING_HEIGHT_LARGE);
+ setWidth(PIP_WIDTH_LARGE);
+ } else {
+ setHeight(PIP_STARTING_HEIGHT_NORMAL);
+ setWidth(PIP_WIDTH_NORMAL);
+ }
+ }, [isWindowLarge, setHeight, setWidth]);
+
const [translateX, translateY] = React.useMemo<[number, number]>(() => {
const topMin = PIP_TOP_MARGIN;
const bottomMax = windowHeight - PIP_PADDING - height;
@@ -256,7 +278,7 @@ export function CallingPip({
const leftMin = PIP_PADDING + leftScrollPadding;
const rightScrollPadding = isRTL ? 0 : 1;
- const rightMax = windowWidth - PIP_PADDING - PIP_WIDTH - rightScrollPadding;
+ const rightMax = windowWidth - PIP_PADDING - width - rightScrollPadding;
switch (positionState.mode) {
case PositionMode.BeingDragged:
@@ -264,7 +286,7 @@ export function CallingPip({
isRTL
? windowWidth -
positionState.mouseX -
- (PIP_WIDTH - positionState.dragOffsetX)
+ (width - positionState.dragOffsetX)
: positionState.mouseX - positionState.dragOffsetX,
positionState.mouseY - positionState.dragOffsetY,
];
@@ -291,7 +313,7 @@ export function CallingPip({
default:
throw missingCaseError(positionState);
}
- }, [height, isRTL, windowWidth, windowHeight, positionState]);
+ }, [height, isRTL, width, windowWidth, windowHeight, positionState]);
const localizedTranslateX = isRTL ? -translateX : translateX;
const [showControls, setShowControls] = React.useState(false);
@@ -356,13 +378,17 @@ export function CallingPip({
const isLonelyInCall = !activeCall.remoteParticipants.length;
const isSendingVideo =
activeCall.hasLocalVideo || activeCall.presentingSource;
+ const avatarSize = isWindowLarge
+ ? AvatarSize.NINETY_SIX
+ : AvatarSize.SIXTY_FOUR;
+
if (isLonelyInCall) {
remoteVideoNode = (
{isSendingVideo ? (
// TODO: DESKTOP-8537 - when black bars go away, need to make some CSS changes
<>
-
+
@@ -405,13 +431,15 @@ export function CallingPip({
setRendererCanvas={setRendererCanvas}
setGroupCallVideoRequest={setGroupCallVideoRequest}
height={height}
- width={PIP_WIDTH}
+ width={width}
updateHeight={(newHeight: number) => {
setHeight(newHeight);
}}
/>
);
}
+ const localVideoWidth = isWindowLarge ? 120 : 80;
+ const localVideoHeight = isWindowLarge ? 80 : 54;
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
@@ -449,6 +477,7 @@ export function CallingPip({
ref={videoContainerRef}
style={{
height: `${height}px`,
+ width: `${width}px`,
cursor:
positionState.mode === PositionMode.BeingDragged
? '-webkit-grabbing'
@@ -463,7 +492,14 @@ export function CallingPip({
{remoteVideoNode}
{!isLonelyInCall && activeCall.hasLocalVideo ? (
-
+
) : null}
);
diff --git a/ts/components/CallingPipRemoteVideo.tsx b/ts/components/CallingPipRemoteVideo.tsx
index 4a5b061e3b..e649fd4966 100644
--- a/ts/components/CallingPipRemoteVideo.tsx
+++ b/ts/components/CallingPipRemoteVideo.tsx
@@ -27,15 +27,23 @@ import { isReconnecting } from '../util/callingIsReconnecting';
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
import { assertDev } from '../util/assert';
import type { CallingImageDataCache } from './CallManager';
-import { PIP_MAXIMUM_HEIGHT, PIP_MINIMUM_HEIGHT } from './CallingPip';
+import {
+ PIP_MAXIMUM_HEIGHT_MULTIPLIER,
+ PIP_MINIMUM_HEIGHT_MULTIPLIER,
+ PIP_WIDTH_NORMAL,
+} from './CallingPip';
function BlurredBackground({
activeCall,
activeGroupCallSpeaker,
+ avatarSize,
+ darken,
i18n,
}: {
activeCall: ActiveCallType;
activeGroupCallSpeaker?: undefined | GroupCallRemoteParticipantType;
+ avatarSize: AvatarSize;
+ darken?: boolean;
i18n: LocalizerType;
}): JSX.Element {
const {
@@ -51,7 +59,7 @@ function BlurredBackground({
activeGroupCallSpeaker?.avatarUrl ?? activeCall.conversation.avatarUrl;
return (
-
+
@@ -129,16 +137,14 @@ export function CallingPipRemoteVideo({
return;
}
- const newHeight = clamp(
- Math.floor(width * (1 / videoAspectRatio)),
- 1,
- MAX_FRAME_HEIGHT
- );
+ const ratio = 1 / videoAspectRatio;
+ const newHeight = clamp(Math.floor(width * ratio), 1, MAX_FRAME_HEIGHT);
+
// Update only for portrait video that fits, otherwise leave things as they are
if (
newHeight !== height &&
- newHeight >= PIP_MINIMUM_HEIGHT &&
- newHeight <= PIP_MAXIMUM_HEIGHT
+ ratio >= PIP_MINIMUM_HEIGHT_MULTIPLIER &&
+ ratio <= PIP_MAXIMUM_HEIGHT_MULTIPLIER
) {
updateHeight(newHeight);
}
@@ -179,13 +185,20 @@ export function CallingPipRemoteVideo({
width,
]);
+ const avatarSize =
+ width > PIP_WIDTH_NORMAL ? AvatarSize.NINETY_SIX : AvatarSize.SIXTY_FOUR;
+
switch (activeCall.callMode) {
case CallMode.Direct: {
const { hasRemoteVideo } = activeCall.remoteParticipants[0];
if (!hasRemoteVideo) {
return (
-
+
);
}
@@ -196,7 +209,12 @@ export function CallingPipRemoteVideo({
// TODO: DESKTOP-8537 - when black bars go away, we need to make some CSS changes
return (
-
+
-
+
);
}
@@ -221,6 +243,8 @@ export function CallingPipRemoteVideo({