signal-desktop/ts/components/CallingPipRemoteVideo.tsx

268 lines
8.2 KiB
TypeScript
Raw Normal View History

2023-01-03 11:55:46 -08:00
// Copyright 2020 Signal Messenger, LLC
2020-11-17 10:07:53 -05:00
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useEffect } from 'react';
import { clamp, isNumber, maxBy } from 'lodash';
2023-01-09 10:38:57 -08:00
import type { VideoFrameSource } from '@signalapp/ringrtc';
2022-12-09 13:37:45 -07:00
import { Avatar, AvatarSize } from './Avatar';
2020-11-17 10:07:53 -05:00
import { CallBackgroundBlur } from './CallBackgroundBlur';
import { DirectCallRemoteParticipant } from './DirectCallRemoteParticipant';
import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant';
import type { LocalizerType } from '../types/Util';
import {
GroupCallJoinState,
type ActiveCallType,
type GroupCallRemoteParticipantType,
type GroupCallVideoRequest,
} from '../types/Calling';
import { CallMode } from '../types/CallDisposition';
2021-08-05 20:17:05 -04:00
import { AvatarColors } from '../types/Colors';
import type { SetRendererCanvasType } from '../state/ducks/calling';
import { useGetCallingFrameBuffer } from '../calling/useGetCallingFrameBuffer';
import { MAX_FRAME_HEIGHT } from '../calling/constants';
2021-09-17 18:24:21 -04:00
import { usePageVisibility } from '../hooks/usePageVisibility';
import { missingCaseError } from '../util/missingCaseError';
import { nonRenderedRemoteParticipant } from '../util/ringrtc/nonRenderedRemoteParticipant';
import { isReconnecting } from '../util/callingIsReconnecting';
2024-02-22 13:19:50 -08:00
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
import { assertDev } from '../util/assert';
import type { CallingImageDataCache } from './CallManager';
2025-04-16 06:37:24 +10:00
import {
PIP_MAXIMUM_HEIGHT_MULTIPLIER,
PIP_MINIMUM_HEIGHT_MULTIPLIER,
PIP_WIDTH_NORMAL,
} from './CallingPip';
function BlurredBackground({
activeCall,
activeGroupCallSpeaker,
2025-04-16 06:37:24 +10:00
avatarSize,
darken,
i18n,
}: {
activeCall: ActiveCallType;
activeGroupCallSpeaker?: undefined | GroupCallRemoteParticipantType;
2025-04-16 06:37:24 +10:00
avatarSize: AvatarSize;
darken?: boolean;
i18n: LocalizerType;
2022-11-17 16:45:19 -08:00
}): JSX.Element {
const {
avatarPlaceholderGradient,
color,
type: conversationType,
phoneNumber,
profileName,
2021-05-07 17:21:10 -05:00
sharedGroupNames,
title,
} = activeCall.conversation;
const avatarUrl =
activeGroupCallSpeaker?.avatarUrl ?? activeCall.conversation.avatarUrl;
return (
2025-04-16 06:37:24 +10:00
<CallBackgroundBlur avatarUrl={avatarUrl} darken={darken}>
<div className="module-calling-pip__video--avatar">
<Avatar
avatarPlaceholderGradient={avatarPlaceholderGradient}
avatarUrl={avatarUrl}
badge={undefined}
color={color || AvatarColors[0]}
noteToSelf={false}
conversationType={conversationType}
i18n={i18n}
phoneNumber={phoneNumber}
profileName={profileName}
title={title}
2025-04-16 06:37:24 +10:00
size={avatarSize}
sharedGroupNames={sharedGroupNames}
/>
</div>
</CallBackgroundBlur>
);
2022-11-17 16:45:19 -08:00
}
export type PropsType = {
activeCall: ActiveCallType;
2020-11-17 10:07:53 -05:00
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
i18n: LocalizerType;
imageDataCache: React.RefObject<CallingImageDataCache>;
2022-09-07 08:52:55 -07:00
setGroupCallVideoRequest: (
_: Array<GroupCallVideoRequest>,
speakerHeight: number
) => void;
2020-11-17 10:07:53 -05:00
setRendererCanvas: (_: SetRendererCanvasType) => void;
height: number;
width: number;
updateHeight: (newHeight: number) => void;
};
2020-11-17 10:07:53 -05:00
2022-11-17 16:45:19 -08:00
export function CallingPipRemoteVideo({
activeCall,
2020-11-17 10:07:53 -05:00
getGroupCallVideoFrameSource,
imageDataCache,
2020-11-17 10:07:53 -05:00
i18n,
setGroupCallVideoRequest,
2020-11-17 10:07:53 -05:00
setRendererCanvas,
height,
width,
updateHeight,
2022-11-17 16:45:19 -08:00
}: PropsType): JSX.Element {
2020-12-02 12:14:03 -06:00
const { conversation } = activeCall;
const getGroupCallFrameBuffer = useGetCallingFrameBuffer();
const isPageVisible = usePageVisibility();
2021-11-11 16:43:05 -06:00
const activeGroupCallSpeaker: undefined | GroupCallRemoteParticipantType =
React.useMemo(() => {
2024-02-22 13:19:50 -08:00
if (!isGroupOrAdhocActiveCall(activeCall)) {
2021-11-11 16:43:05 -06:00
return undefined;
}
if (activeCall.joinState !== GroupCallJoinState.Joined) {
return undefined;
}
2021-11-11 16:43:05 -06:00
return maxBy(activeCall.remoteParticipants, participant =>
participant.presenting ? Infinity : participant.speakerTime || -Infinity
);
2024-02-22 13:19:50 -08:00
}, [activeCall]);
useEffect(() => {
if (isGroupOrAdhocActiveCall(activeCall)) {
if (!activeGroupCallSpeaker || !activeGroupCallSpeaker.hasRemoteVideo) {
return;
}
const { videoAspectRatio } = activeGroupCallSpeaker;
if (!isNumber(videoAspectRatio)) {
return;
}
2025-04-16 06:37:24 +10:00
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 &&
2025-04-16 06:37:24 +10:00
ratio >= PIP_MINIMUM_HEIGHT_MULTIPLIER &&
ratio <= PIP_MAXIMUM_HEIGHT_MULTIPLIER
) {
updateHeight(newHeight);
}
if (isPageVisible) {
const participants = activeCall.remoteParticipants.map(participant => {
if (participant === activeGroupCallSpeaker) {
return {
demuxId: participant.demuxId,
width,
height: newHeight,
};
}
return nonRenderedRemoteParticipant(participant);
});
setGroupCallVideoRequest(participants, newHeight);
} else {
setGroupCallVideoRequest(
activeCall.remoteParticipants.map(nonRenderedRemoteParticipant),
0
);
}
} else {
// eslint-disable-next-line no-lonely-if
if (!activeCall.hasRemoteVideo) {
// eslint-disable-next-line no-useless-return
return;
}
// TODO: DESKTOP-8537 - with direct call video stats, call updateHeight as needed
}
}, [
2024-02-22 13:19:50 -08:00
activeCall,
activeGroupCallSpeaker,
height,
isPageVisible,
setGroupCallVideoRequest,
updateHeight,
width,
]);
2025-04-16 06:37:24 +10:00
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 (
<div className="module-calling-pip__video--remote">
2025-04-16 06:37:24 +10:00
<BlurredBackground
activeCall={activeCall}
avatarSize={avatarSize}
i18n={i18n}
/>
</div>
);
}
2024-02-22 13:19:50 -08:00
assertDev(
conversation.type === 'direct',
'CallingPipRemoteVideo for direct call must be associated with direct conversation'
);
// TODO: DESKTOP-8537 - when black bars go away, we need to make some CSS changes
return (
<div className="module-calling-pip__video--remote">
2025-04-16 06:37:24 +10:00
<BlurredBackground
activeCall={activeCall}
avatarSize={avatarSize}
darken
i18n={i18n}
/>
<DirectCallRemoteParticipant
conversation={conversation}
hasRemoteVideo={hasRemoteVideo}
i18n={i18n}
isReconnecting={isReconnecting(activeCall)}
setRendererCanvas={setRendererCanvas}
/>
</div>
);
}
case CallMode.Group:
2024-02-22 13:19:50 -08:00
case CallMode.Adhoc:
if (!activeGroupCallSpeaker) {
return (
<div className="module-calling-pip__video--remote">
2025-04-16 06:37:24 +10:00
<BlurredBackground
activeCall={activeCall}
avatarSize={avatarSize}
i18n={i18n}
/>
</div>
);
}
return (
<div className="module-calling-pip__video--remote">
<BlurredBackground
activeCall={activeCall}
activeGroupCallSpeaker={activeGroupCallSpeaker}
2025-04-16 06:37:24 +10:00
avatarSize={avatarSize}
darken={activeGroupCallSpeaker.hasRemoteVideo}
i18n={i18n}
/>
<GroupCallRemoteParticipant
getFrameBuffer={getGroupCallFrameBuffer}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
imageDataCache={imageDataCache}
i18n={i18n}
isInPip
joinedAt={activeCall.joinedAt}
remoteParticipant={activeGroupCallSpeaker}
remoteParticipantsCount={activeCall.remoteParticipants.length}
isActiveSpeakerInSpeakerView={false}
isCallReconnecting={isReconnecting(activeCall)}
/>
</div>
);
default:
throw missingCaseError(activeCall);
2020-11-17 10:07:53 -05:00
}
2022-11-17 16:45:19 -08:00
}