Group Calling: Improve mute state styling

This commit is contained in:
Josh Perez 2020-11-19 13:13:36 -05:00 committed by Josh Perez
parent c6eafbb8d5
commit 5cc7c9a66a
15 changed files with 203 additions and 126 deletions

View file

@ -6460,6 +6460,10 @@ button.module-image__border-overlay:focus {
justify-content: center; justify-content: center;
position: relative; position: relative;
width: 100%; width: 100%;
.module-ongoing-call__group-call-remote-participant--audio-muted::before {
display: none;
}
} }
&--local { &--local {

View file

@ -8,6 +8,15 @@ import { getInitials } from '../util/getInitials';
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
import { ColorType } from '../types/Colors'; import { ColorType } from '../types/Colors';
export enum AvatarSize {
TWENTY_EIGHT = 28,
THIRTY_TWO = 32,
FIFTY_TWO = 52,
EIGHTY = 80,
NINETY_SIX = 96,
ONE_HUNDRED_TWELVE = 112,
}
export type Props = { export type Props = {
avatarPath?: string; avatarPath?: string;
color?: ColorType; color?: ColorType;
@ -18,7 +27,7 @@ export type Props = {
name?: string; name?: string;
phoneNumber?: string; phoneNumber?: string;
profileName?: string; profileName?: string;
size: 28 | 32 | 52 | 80 | 96 | 112; size: AvatarSize;
onClick?: () => unknown; onClick?: () => unknown;

View file

@ -13,17 +13,15 @@ import {
CallMode, CallMode,
CallState, CallState,
GroupCallJoinState, GroupCallJoinState,
GroupCallRemoteParticipantType,
VideoFrameSource, VideoFrameSource,
} from '../types/Calling'; } from '../types/Calling';
import { ConversationType } from '../state/ducks/conversations'; import { ConversationType } from '../state/ducks/conversations';
import { import {
AcceptCallType, AcceptCallType,
ActiveCallStateType, ActiveCallType,
CancelCallType, CancelCallType,
DeclineCallType, DeclineCallType,
DirectCallStateType, DirectCallStateType,
GroupCallStateType,
HangUpType, HangUpType,
SetLocalAudioType, SetLocalAudioType,
SetLocalPreviewType, SetLocalPreviewType,
@ -35,13 +33,6 @@ import { LocalizerType } from '../types/Util';
import { ColorType } from '../types/Colors'; import { ColorType } from '../types/Colors';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
interface ActiveCallType {
activeCallState: ActiveCallStateType;
call: DirectCallStateType | GroupCallStateType;
conversation: ConversationType;
groupCallParticipants: Array<GroupCallRemoteParticipantType>;
}
export interface PropsType { export interface PropsType {
activeCall?: ActiveCallType; activeCall?: ActiveCallType;
availableCameras: Array<MediaDeviceInfo>; availableCameras: Array<MediaDeviceInfo>;
@ -205,8 +196,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
if (pip) { if (pip) {
return ( return (
<CallingPip <CallingPip
call={call} activeCall={activeCall}
conversation={conversation}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall} getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
hangUp={hangUp} hangUp={hangUp}
hasLocalVideo={hasLocalVideo} hasLocalVideo={hasLocalVideo}
@ -221,8 +211,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
return ( return (
<> <>
<CallScreen <CallScreen
call={call} activeCall={activeCall}
conversation={conversation}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall} getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
hangUp={hangUp} hangUp={hangUp}
hasLocalAudio={hasLocalAudio} hasLocalAudio={hasLocalAudio}

View file

@ -7,12 +7,15 @@ import { storiesOf } from '@storybook/react';
import { boolean, select } from '@storybook/addon-knobs'; import { boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { CallMode, CallState } from '../types/Calling'; import {
CallMode,
CallState,
GroupCallRemoteParticipantType,
} from '../types/Calling';
import { Colors } from '../types/Colors'; import { Colors } from '../types/Colors';
import { import {
DirectCallStateType, DirectCallStateType,
GroupCallStateType, GroupCallStateType,
GroupCallParticipantInfoType,
} from '../state/ducks/calling'; } from '../state/ducks/calling';
import { CallScreen, PropsType } from './CallScreen'; import { CallScreen, PropsType } from './CallScreen';
import { setup as setupI18n } from '../../js/modules/i18n'; import { setup as setupI18n } from '../../js/modules/i18n';
@ -20,15 +23,13 @@ import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
function getGroupCallState( function getGroupCallState(): GroupCallStateType {
remoteParticipants: Array<GroupCallParticipantInfoType>
): GroupCallStateType {
return { return {
callMode: CallMode.Group, callMode: CallMode.Group,
conversationId: '3051234567', conversationId: '3051234567',
connectionState: 2, connectionState: 2,
joinState: 2, joinState: 2,
remoteParticipants, remoteParticipants: [],
}; };
} }
@ -59,24 +60,35 @@ const createProps = (
overrideProps: { overrideProps: {
callState?: CallState; callState?: CallState;
callTypeState?: DirectCallStateType | GroupCallStateType; callTypeState?: DirectCallStateType | GroupCallStateType;
groupCallParticipants?: Array<GroupCallRemoteParticipantType>;
hasLocalAudio?: boolean; hasLocalAudio?: boolean;
hasLocalVideo?: boolean; hasLocalVideo?: boolean;
hasRemoteVideo?: boolean; hasRemoteVideo?: boolean;
remoteParticipants?: Array<GroupCallParticipantInfoType>;
} = {} } = {}
): PropsType => ({ ): PropsType => ({
call: overrideProps.callTypeState || getDirectCallState(overrideProps), activeCall: {
conversation: { activeCallState: {
id: '3051234567', conversationId: '123',
avatarPath: undefined, hasLocalAudio: true,
color: Colors[0], hasLocalVideo: true,
title: 'Rick Sanchez', pip: false,
name: 'Rick Sanchez', settingsDialogOpen: false,
phoneNumber: '3051234567', showParticipantsList: true,
profileName: 'Rick Sanchez', },
markedUnread: false, call: overrideProps.callTypeState || getDirectCallState(overrideProps),
type: 'direct', conversation: {
lastUpdated: Date.now(), id: '3051234567',
avatarPath: undefined,
color: Colors[0],
title: 'Rick Sanchez',
name: 'Rick Sanchez',
phoneNumber: '3051234567',
profileName: 'Rick Sanchez',
markedUnread: false,
type: 'direct',
lastUpdated: Date.now(),
},
groupCallParticipants: overrideProps.groupCallParticipants || [],
}, },
// We allow `any` here because this is fake and actually comes from RingRTC, which we // We allow `any` here because this is fake and actually comes from RingRTC, which we
// can't import. // can't import.
@ -164,16 +176,17 @@ story.add('hasRemoteVideo', () => {
story.add('Group call - 1', () => ( story.add('Group call - 1', () => (
<CallScreen <CallScreen
{...createProps({ {...createProps({
callTypeState: getGroupCallState([ callTypeState: getGroupCallState(),
groupCallParticipants: [
{ {
conversationId: '123',
demuxId: 0, demuxId: 0,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isSelf: false, isSelf: false,
title: 'Tyler',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
}, },
]), ],
})} })}
/> />
)); ));
@ -181,32 +194,33 @@ story.add('Group call - 1', () => (
story.add('Group call - Many', () => ( story.add('Group call - Many', () => (
<CallScreen <CallScreen
{...createProps({ {...createProps({
callTypeState: getGroupCallState([ callTypeState: getGroupCallState(),
groupCallParticipants: [
{ {
conversationId: '123',
demuxId: 0, demuxId: 0,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isSelf: false, isSelf: false,
title: 'Amy',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
}, },
{ {
conversationId: '456',
demuxId: 1, demuxId: 1,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isSelf: true, isSelf: true,
title: 'Bob',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
}, },
{ {
conversationId: '789',
demuxId: 2, demuxId: 2,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isSelf: false, isSelf: false,
title: 'Alice',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
}, },
]), ],
})} })}
/> />
)); ));

View file

@ -4,10 +4,8 @@
import React, { useState, useRef, useEffect, useCallback } from 'react'; import React, { useState, useRef, useEffect, useCallback } from 'react';
import { noop } from 'lodash'; import { noop } from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import { ConversationType } from '../state/ducks/conversations';
import { import {
DirectCallStateType, ActiveCallType,
GroupCallStateType,
HangUpType, HangUpType,
SetLocalAudioType, SetLocalAudioType,
SetLocalPreviewType, SetLocalPreviewType,
@ -31,8 +29,7 @@ import { DirectCallRemoteParticipant } from './DirectCallRemoteParticipant';
import { GroupCallRemoteParticipants } from './GroupCallRemoteParticipants'; import { GroupCallRemoteParticipants } from './GroupCallRemoteParticipants';
export type PropsType = { export type PropsType = {
call: DirectCallStateType | GroupCallStateType; activeCall: ActiveCallType;
conversation: ConversationType;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
hangUp: (_: HangUpType) => void; hangUp: (_: HangUpType) => void;
hasLocalAudio: boolean; hasLocalAudio: boolean;
@ -58,8 +55,7 @@ export type PropsType = {
}; };
export const CallScreen: React.FC<PropsType> = ({ export const CallScreen: React.FC<PropsType> = ({
call, activeCall,
conversation,
getGroupCallVideoFrameSource, getGroupCallVideoFrameSource,
hangUp, hangUp,
hasLocalAudio, hasLocalAudio,
@ -76,6 +72,8 @@ export const CallScreen: React.FC<PropsType> = ({
togglePip, togglePip,
toggleSettings, toggleSettings,
}) => { }) => {
const { call, conversation, groupCallParticipants } = activeCall;
const toggleAudio = useCallback(() => { const toggleAudio = useCallback(() => {
setLocalAudio({ setLocalAudio({
enabled: !hasLocalAudio, enabled: !hasLocalAudio,
@ -170,8 +168,9 @@ export const CallScreen: React.FC<PropsType> = ({
isConnected = call.connectionState === GroupCallConnectionState.Connected; isConnected = call.connectionState === GroupCallConnectionState.Connected;
remoteParticipantsElement = ( remoteParticipantsElement = (
<GroupCallRemoteParticipants <GroupCallRemoteParticipants
remoteParticipants={call.remoteParticipants}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource} getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
i18n={i18n}
remoteParticipants={groupCallParticipants}
/> />
); );
break; break;

View file

@ -20,11 +20,13 @@ function createParticipant(
return { return {
avatarPath: participantProps.avatarPath, avatarPath: participantProps.avatarPath,
color: Colors[randomColor], color: Colors[randomColor],
demuxId: 2,
hasRemoteAudio: Boolean(participantProps.hasRemoteAudio), hasRemoteAudio: Boolean(participantProps.hasRemoteAudio),
hasRemoteVideo: Boolean(participantProps.hasRemoteVideo), hasRemoteVideo: Boolean(participantProps.hasRemoteVideo),
isSelf: Boolean(participantProps.isSelf), isSelf: Boolean(participantProps.isSelf),
profileName: participantProps.title, profileName: participantProps.title,
title: String(participantProps.title), title: String(participantProps.title),
videoAspectRatio: 1.3,
}; };
} }

View file

@ -9,6 +9,7 @@ import { action } from '@storybook/addon-actions';
import { ColorType } from '../types/Colors'; import { ColorType } from '../types/Colors';
import { ConversationTypeType } from '../state/ducks/conversations'; import { ConversationTypeType } from '../state/ducks/conversations';
import { ActiveCallType } from '../state/ducks/calling';
import { CallingPip, PropsType } from './CallingPip'; import { CallingPip, PropsType } from './CallingPip';
import { import {
CallMode, CallMode,
@ -43,9 +44,23 @@ const defaultCall = {
hasRemoteVideo: true, hasRemoteVideo: true,
}; };
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({ const createProps = (
call: overrideProps.call || defaultCall, overrideProps: Partial<PropsType> = {},
conversation: overrideProps.conversation || conversation, activeCall: Partial<ActiveCallType> = {}
): PropsType => ({
activeCall: {
activeCallState: {
conversationId: '123',
hasLocalAudio: true,
hasLocalVideo: true,
pip: false,
settingsDialogOpen: false,
showParticipantsList: true,
},
call: activeCall.call || defaultCall,
conversation: activeCall.conversation || conversation,
groupCallParticipants: [],
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
getGroupCallVideoFrameSource: noop as any, getGroupCallVideoFrameSource: noop as any,
hangUp: action('hang-up'), hangUp: action('hang-up'),
@ -59,39 +74,48 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
const story = storiesOf('Components/CallingPip', module); const story = storiesOf('Components/CallingPip', module);
story.add('Default', () => { story.add('Default', () => {
const props = createProps(); const props = createProps({});
return <CallingPip {...props} />; return <CallingPip {...props} />;
}); });
story.add('Contact (with avatar)', () => { story.add('Contact (with avatar)', () => {
const props = createProps({ const props = createProps(
conversation: { {},
...conversation, {
avatarPath: 'https://www.fillmurray.com/64/64', conversation: {
}, ...conversation,
}); avatarPath: 'https://www.fillmurray.com/64/64',
},
}
);
return <CallingPip {...props} />; return <CallingPip {...props} />;
}); });
story.add('Contact (no color)', () => { story.add('Contact (no color)', () => {
const props = createProps({ const props = createProps(
conversation: { {},
...conversation, {
color: undefined, conversation: {
}, ...conversation,
}); color: undefined,
},
}
);
return <CallingPip {...props} />; return <CallingPip {...props} />;
}); });
story.add('Group Call', () => { story.add('Group Call', () => {
const props = createProps({ const props = createProps(
call: { {},
callMode: CallMode.Group as CallMode.Group, {
conversationId: '3051234567', call: {
connectionState: GroupCallConnectionState.Connected, callMode: CallMode.Group as CallMode.Group,
joinState: GroupCallJoinState.Joined, conversationId: '3051234567',
remoteParticipants: [], connectionState: GroupCallConnectionState.Connected,
}, joinState: GroupCallJoinState.Joined,
}); remoteParticipants: [],
},
}
);
return <CallingPip {...props} />; return <CallingPip {...props} />;
}); });

View file

@ -5,19 +5,16 @@ import React from 'react';
import { Tooltip } from './Tooltip'; import { Tooltip } from './Tooltip';
import { CallingPipRemoteVideo } from './CallingPipRemoteVideo'; import { CallingPipRemoteVideo } from './CallingPipRemoteVideo';
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
import { ConversationType } from '../state/ducks/conversations';
import { VideoFrameSource } from '../types/Calling'; import { VideoFrameSource } from '../types/Calling';
import { import {
DirectCallStateType, ActiveCallType,
GroupCallStateType,
HangUpType, HangUpType,
SetLocalPreviewType, SetLocalPreviewType,
SetRendererCanvasType, SetRendererCanvasType,
} from '../state/ducks/calling'; } from '../state/ducks/calling';
export type PropsType = { export type PropsType = {
call: DirectCallStateType | GroupCallStateType; activeCall: ActiveCallType;
conversation: ConversationType;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
hangUp: (_: HangUpType) => void; hangUp: (_: HangUpType) => void;
hasLocalVideo: boolean; hasLocalVideo: boolean;
@ -33,8 +30,7 @@ const PIP_DEFAULT_Y = 56;
const PIP_PADDING = 8; const PIP_PADDING = 8;
export const CallingPip = ({ export const CallingPip = ({
call, activeCall,
conversation,
getGroupCallVideoFrameSource, getGroupCallVideoFrameSource,
hangUp, hangUp,
hasLocalVideo, hasLocalVideo,
@ -167,8 +163,7 @@ export const CallingPip = ({
}} }}
> >
<CallingPipRemoteVideo <CallingPipRemoteVideo
call={call} activeCall={activeCall}
conversation={conversation}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource} getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
i18n={i18n} i18n={i18n}
setRendererCanvas={setRendererCanvas} setRendererCanvas={setRendererCanvas}
@ -185,7 +180,7 @@ export const CallingPip = ({
aria-label={i18n('calling__hangup')} aria-label={i18n('calling__hangup')}
className="module-calling-pip__button--hangup" className="module-calling-pip__button--hangup"
onClick={() => { onClick={() => {
hangUp({ conversationId: conversation.id }); hangUp({ conversationId: activeCall.conversation.id });
}} }}
type="button" type="button"
/> />

View file

@ -7,29 +7,24 @@ import { CallBackgroundBlur } from './CallBackgroundBlur';
import { DirectCallRemoteParticipant } from './DirectCallRemoteParticipant'; import { DirectCallRemoteParticipant } from './DirectCallRemoteParticipant';
import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant'; import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant';
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
import { ConversationType } from '../state/ducks/conversations';
import { CallMode, VideoFrameSource } from '../types/Calling'; import { CallMode, VideoFrameSource } from '../types/Calling';
import { import { ActiveCallType, SetRendererCanvasType } from '../state/ducks/calling';
DirectCallStateType,
GroupCallStateType,
SetRendererCanvasType,
} from '../state/ducks/calling';
export interface PropsType { export interface PropsType {
call: DirectCallStateType | GroupCallStateType; activeCall: ActiveCallType;
conversation: ConversationType;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
i18n: LocalizerType; i18n: LocalizerType;
setRendererCanvas: (_: SetRendererCanvasType) => void; setRendererCanvas: (_: SetRendererCanvasType) => void;
} }
export const CallingPipRemoteVideo = ({ export const CallingPipRemoteVideo = ({
call, activeCall,
conversation,
getGroupCallVideoFrameSource, getGroupCallVideoFrameSource,
i18n, i18n,
setRendererCanvas, setRendererCanvas,
}: PropsType): JSX.Element => { }: PropsType): JSX.Element => {
const { call, conversation } = activeCall;
if (call.callMode === CallMode.Direct) { if (call.callMode === CallMode.Direct) {
if (!call.hasRemoteVideo) { if (!call.hasRemoteVideo) {
const { const {
@ -76,17 +71,17 @@ export const CallingPipRemoteVideo = ({
} }
if (call.callMode === CallMode.Group) { if (call.callMode === CallMode.Group) {
const speaker = call.remoteParticipants[0]; const { groupCallParticipants } = activeCall;
const speaker = groupCallParticipants[0];
return ( return (
<div className="module-calling-pip__video--remote"> <div className="module-calling-pip__video--remote">
<GroupCallRemoteParticipant <GroupCallRemoteParticipant
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
i18n={i18n}
isInPip isInPip
key={speaker.demuxId} key={speaker.demuxId}
demuxId={speaker.demuxId} remoteParticipant={speaker}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
hasRemoteAudio={speaker.hasRemoteAudio}
hasRemoteVideo={speaker.hasRemoteVideo}
/> />
</div> </div>
); );

View file

@ -11,17 +11,21 @@ import React, {
} from 'react'; } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { noop } from 'lodash'; import { noop } from 'lodash';
import { VideoFrameSource } from '../types/Calling'; import {
GroupCallRemoteParticipantType,
VideoFrameSource,
} from '../types/Calling';
import { LocalizerType } from '../types/Util';
import { CallBackgroundBlur } from './CallBackgroundBlur'; import { CallBackgroundBlur } from './CallBackgroundBlur';
import { Avatar, AvatarSize } from './Avatar';
// The max size video frame we'll support (in RGBA) // The max size video frame we'll support (in RGBA)
const FRAME_BUFFER_SIZE = 1920 * 1080 * 4; const FRAME_BUFFER_SIZE = 1920 * 1080 * 4;
interface BasePropsType { interface BasePropsType {
demuxId: number;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
hasRemoteAudio: boolean; i18n: LocalizerType;
hasRemoteVideo: boolean; remoteParticipant: GroupCallRemoteParticipantType;
} }
interface InPipPropsType { interface InPipPropsType {
@ -29,23 +33,28 @@ interface InPipPropsType {
} }
interface NotInPipPropsType { interface NotInPipPropsType {
isInPip?: false;
width: number;
height: number; height: number;
isInPip?: false;
left: number; left: number;
top: number; top: number;
width: number;
} }
type PropsType = BasePropsType & (InPipPropsType | NotInPipPropsType); type PropsType = BasePropsType & (InPipPropsType | NotInPipPropsType);
export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo( export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
props => { props => {
const { getGroupCallVideoFrameSource } = props;
const { const {
avatarPath,
color,
profileName,
title,
demuxId, demuxId,
getGroupCallVideoFrameSource,
hasRemoteAudio, hasRemoteAudio,
hasRemoteVideo, hasRemoteVideo,
} = props; } = props.remoteParticipant;
const [isWide, setIsWide] = useState(true); const [isWide, setIsWide] = useState(true);
@ -132,13 +141,25 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
canvasStyles = { height: '100%' }; canvasStyles = { height: '100%' };
} }
let avatarSize: number;
// TypeScript isn't smart enough to know that `isInPip` by itself disambiguates the // TypeScript isn't smart enough to know that `isInPip` by itself disambiguates the
// types, so we have to use `props.isInPip` instead. // types, so we have to use `props.isInPip` instead.
// eslint-disable-next-line react/destructuring-assignment // eslint-disable-next-line react/destructuring-assignment
if (props.isInPip) { if (props.isInPip) {
containerStyles = canvasStyles; containerStyles = canvasStyles;
avatarSize = AvatarSize.FIFTY_TWO;
} else { } else {
const { top, left, width, height } = props; const { top, left, width, height } = props;
const shorterDimension = Math.min(width, height);
if (shorterDimension >= 240) {
avatarSize = AvatarSize.ONE_HUNDRED_TWELVE;
} else if (shorterDimension >= 180) {
avatarSize = AvatarSize.EIGHTY;
} else {
avatarSize = AvatarSize.FIFTY_TWO;
}
containerStyles = { containerStyles = {
height, height,
@ -177,9 +198,17 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
}} }}
/> />
) : ( ) : (
<CallBackgroundBlur> <CallBackgroundBlur avatarPath={avatarPath} color={color}>
{/* TODO: Improve the styling here. See DESKTOP-894. */} <Avatar
<span /> avatarPath={avatarPath}
color={color || 'ultramarine'}
noteToSelf={false}
conversationType="direct"
i18n={props.i18n}
profileName={profileName}
title={title}
size={avatarSize}
/>
</CallBackgroundBlur> </CallBackgroundBlur>
)} )}
</div> </div>

View file

@ -4,9 +4,12 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import Measure from 'react-measure'; import Measure from 'react-measure';
import { takeWhile, chunk, maxBy, flatten } from 'lodash'; import { takeWhile, chunk, maxBy, flatten } from 'lodash';
import { VideoFrameSource } from '../types/Calling';
import { GroupCallParticipantInfoType } from '../state/ducks/calling';
import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant'; import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant';
import {
GroupCallRemoteParticipantType,
VideoFrameSource,
} from '../types/Calling';
import { LocalizerType } from '../types/Util';
const MIN_RENDERED_HEIGHT = 10; const MIN_RENDERED_HEIGHT = 10;
const PARTICIPANT_MARGIN = 10; const PARTICIPANT_MARGIN = 10;
@ -17,13 +20,14 @@ interface Dimensions {
} }
interface GridArrangement { interface GridArrangement {
rows: Array<Array<GroupCallParticipantInfoType>>; rows: Array<Array<GroupCallRemoteParticipantType>>;
scalar: number; scalar: number;
} }
interface PropsType { interface PropsType {
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
remoteParticipants: ReadonlyArray<GroupCallParticipantInfoType>; i18n: LocalizerType;
remoteParticipants: ReadonlyArray<GroupCallRemoteParticipantType>;
} }
// This component lays out group call remote participants. It uses a custom layout // This component lays out group call remote participants. It uses a custom layout
@ -52,6 +56,7 @@ interface PropsType {
// 4. Lay out this arrangement on the screen. // 4. Lay out this arrangement on the screen.
export const GroupCallRemoteParticipants: React.FC<PropsType> = ({ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
getGroupCallVideoFrameSource, getGroupCallVideoFrameSource,
i18n,
remoteParticipants, remoteParticipants,
}) => { }) => {
const [containerDimensions, setContainerDimensions] = useState<Dimensions>({ const [containerDimensions, setContainerDimensions] = useState<Dimensions>({
@ -82,7 +87,7 @@ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
// //
// This is primarily memoized for clarity, not performance. We only need the result, // This is primarily memoized for clarity, not performance. We only need the result,
// not any of the "intermediate" values. // not any of the "intermediate" values.
const visibleParticipants: Array<GroupCallParticipantInfoType> = useMemo(() => { const visibleParticipants: Array<GroupCallRemoteParticipantType> = useMemo(() => {
// Imagine that we laid out all of the rows end-to-end. That's the maximum total // Imagine that we laid out all of the rows end-to-end. That's the maximum total
// width. So if there were 5 rows and the container was 100px wide, then we can't // width. So if there were 5 rows and the container was 100px wide, then we can't
// possibly fit more than 500px of participants. // possibly fit more than 500px of participants.
@ -199,12 +204,11 @@ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
return ( return (
<GroupCallRemoteParticipant <GroupCallRemoteParticipant
key={remoteParticipant.demuxId} key={remoteParticipant.demuxId}
demuxId={remoteParticipant.demuxId}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource} getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
hasRemoteAudio={remoteParticipant.hasRemoteAudio}
hasRemoteVideo={remoteParticipant.hasRemoteVideo}
height={gridParticipantHeight} height={gridParticipantHeight}
i18n={i18n}
left={left} left={left}
remoteParticipant={remoteParticipant}
top={top} top={top}
width={renderedWidth} width={renderedWidth}
/> />
@ -235,7 +239,7 @@ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
}; };
function totalRemoteParticipantWidthAtMinHeight( function totalRemoteParticipantWidthAtMinHeight(
remoteParticipants: ReadonlyArray<GroupCallParticipantInfoType> remoteParticipants: ReadonlyArray<GroupCallRemoteParticipantType>
): number { ): number {
return remoteParticipants.reduce( return remoteParticipants.reduce(
(result, { videoAspectRatio }) => (result, { videoAspectRatio }) =>

View file

@ -9,6 +9,7 @@ import { missingCaseError } from '../../util/missingCaseError';
import { notify } from '../../services/notify'; import { notify } from '../../services/notify';
import { calling } from '../../services/calling'; import { calling } from '../../services/calling';
import { StateType as RootStateType } from '../reducer'; import { StateType as RootStateType } from '../reducer';
import { ConversationType } from './conversations';
import { import {
CallMode, CallMode,
CallState, CallState,
@ -16,6 +17,7 @@ import {
ChangeIODevicePayloadType, ChangeIODevicePayloadType,
GroupCallConnectionState, GroupCallConnectionState,
GroupCallJoinState, GroupCallJoinState,
GroupCallRemoteParticipantType,
MediaDeviceSettings, MediaDeviceSettings,
} from '../../types/Calling'; } from '../../types/Calling';
import { callingTones } from '../../util/callingTones'; import { callingTones } from '../../util/callingTones';
@ -54,6 +56,13 @@ export interface GroupCallStateType {
remoteParticipants: Array<GroupCallParticipantInfoType>; remoteParticipants: Array<GroupCallParticipantInfoType>;
} }
export interface ActiveCallType {
activeCallState: ActiveCallStateType;
call: DirectCallStateType | GroupCallStateType;
conversation: ConversationType;
groupCallParticipants: Array<GroupCallRemoteParticipantType>;
}
export interface ActiveCallStateType { export interface ActiveCallStateType {
conversationId: string; conversationId: string;
joinedAt?: number; joinedAt?: number;

View file

@ -65,12 +65,14 @@ const mapStateToActiveCallProp = (state: StateType) => {
groupCallParticipants.push({ groupCallParticipants.push({
avatarPath: remoteConversation.avatarPath, avatarPath: remoteConversation.avatarPath,
color: remoteConversation.color, color: remoteConversation.color,
demuxId: remoteParticipant.demuxId,
firstName: remoteConversation.firstName, firstName: remoteConversation.firstName,
hasRemoteAudio: remoteParticipant.hasRemoteAudio, hasRemoteAudio: remoteParticipant.hasRemoteAudio,
hasRemoteVideo: remoteParticipant.hasRemoteVideo, hasRemoteVideo: remoteParticipant.hasRemoteVideo,
isSelf: remoteParticipant.isSelf, isSelf: remoteParticipant.isSelf,
profileName: remoteConversation.profileName, profileName: remoteConversation.profileName,
title: remoteConversation.title, title: remoteConversation.title,
videoAspectRatio: remoteParticipant.videoAspectRatio,
}); });
} }
); );

View file

@ -61,12 +61,14 @@ export enum GroupCallJoinState {
export interface GroupCallRemoteParticipantType { export interface GroupCallRemoteParticipantType {
avatarPath?: string; avatarPath?: string;
color?: ColorType; color?: ColorType;
demuxId: number;
firstName?: string; firstName?: string;
hasRemoteAudio: boolean; hasRemoteAudio: boolean;
hasRemoteVideo: boolean; hasRemoteVideo: boolean;
isSelf: boolean; isSelf: boolean;
profileName?: string; profileName?: string;
title: string; title: string;
videoAspectRatio: number;
} }
// Should match RingRTC's VideoFrameSource // Should match RingRTC's VideoFrameSource

View file

@ -14382,7 +14382,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/CallScreen.js", "path": "ts/components/CallScreen.js",
"line": " const localVideoRef = react_1.useRef(null);", "line": " const localVideoRef = react_1.useRef(null);",
"lineNumber": 39, "lineNumber": 40,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-10-26T21:35:52.858Z", "updated": "2020-10-26T21:35:52.858Z",
"reasonDetail": "Used to get the local video element for rendering." "reasonDetail": "Used to get the local video element for rendering."
@ -14427,7 +14427,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/CallingPip.tsx", "path": "ts/components/CallingPip.tsx",
"line": " const videoContainerRef = React.useRef(null);", "line": " const videoContainerRef = React.useRef(null);",
"lineNumber": 46, "lineNumber": 42,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-10-26T19:12:24.410Z", "updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Element is measured. Its HTML is not used." "reasonDetail": "Element is measured. Its HTML is not used."
@ -14436,7 +14436,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/CallingPip.tsx", "path": "ts/components/CallingPip.tsx",
"line": " const localVideoRef = React.useRef(null);", "line": " const localVideoRef = React.useRef(null);",
"lineNumber": 47, "lineNumber": 43,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-10-26T19:12:24.410Z", "updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Used to get the local video element for rendering." "reasonDetail": "Used to get the local video element for rendering."
@ -14571,7 +14571,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js", "path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const remoteVideoRef = react_1.useRef(null);", "line": " const remoteVideoRef = react_1.useRef(null);",
"lineNumber": 24, "lineNumber": 26,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-11-11T21:56:04.179Z", "updated": "2020-11-11T21:56:04.179Z",
"reasonDetail": "Needed to render the remote video element." "reasonDetail": "Needed to render the remote video element."
@ -14580,7 +14580,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js", "path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const canvasContextRef = react_1.useRef(null);", "line": " const canvasContextRef = react_1.useRef(null);",
"lineNumber": 25, "lineNumber": 27,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-11-17T23:29:38.698Z", "updated": "2020-11-17T23:29:38.698Z",
"reasonDetail": "Doesn't touch the DOM." "reasonDetail": "Doesn't touch the DOM."
@ -14589,7 +14589,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js", "path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const frameBufferRef = react_1.useRef(new ArrayBuffer(FRAME_BUFFER_SIZE));", "line": " const frameBufferRef = react_1.useRef(new ArrayBuffer(FRAME_BUFFER_SIZE));",
"lineNumber": 26, "lineNumber": 28,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-11-17T16:24:25.480Z", "updated": "2020-11-17T16:24:25.480Z",
"reasonDetail": "Doesn't touch the DOM." "reasonDetail": "Doesn't touch the DOM."