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;
position: relative;
width: 100%;
.module-ongoing-call__group-call-remote-participant--audio-muted::before {
display: none;
}
}
&--local {

View file

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

View file

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

View file

@ -7,12 +7,15 @@ import { storiesOf } from '@storybook/react';
import { boolean, select } from '@storybook/addon-knobs';
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 {
DirectCallStateType,
GroupCallStateType,
GroupCallParticipantInfoType,
} from '../state/ducks/calling';
import { CallScreen, PropsType } from './CallScreen';
import { setup as setupI18n } from '../../js/modules/i18n';
@ -20,15 +23,13 @@ import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
function getGroupCallState(
remoteParticipants: Array<GroupCallParticipantInfoType>
): GroupCallStateType {
function getGroupCallState(): GroupCallStateType {
return {
callMode: CallMode.Group,
conversationId: '3051234567',
connectionState: 2,
joinState: 2,
remoteParticipants,
remoteParticipants: [],
};
}
@ -59,24 +60,35 @@ const createProps = (
overrideProps: {
callState?: CallState;
callTypeState?: DirectCallStateType | GroupCallStateType;
groupCallParticipants?: Array<GroupCallRemoteParticipantType>;
hasLocalAudio?: boolean;
hasLocalVideo?: boolean;
hasRemoteVideo?: boolean;
remoteParticipants?: Array<GroupCallParticipantInfoType>;
} = {}
): PropsType => ({
call: overrideProps.callTypeState || getDirectCallState(overrideProps),
conversation: {
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(),
activeCall: {
activeCallState: {
conversationId: '123',
hasLocalAudio: true,
hasLocalVideo: true,
pip: false,
settingsDialogOpen: false,
showParticipantsList: true,
},
call: overrideProps.callTypeState || getDirectCallState(overrideProps),
conversation: {
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
// can't import.
@ -164,16 +176,17 @@ story.add('hasRemoteVideo', () => {
story.add('Group call - 1', () => (
<CallScreen
{...createProps({
callTypeState: getGroupCallState([
callTypeState: getGroupCallState(),
groupCallParticipants: [
{
conversationId: '123',
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
isSelf: false,
title: 'Tyler',
videoAspectRatio: 1.3,
},
]),
],
})}
/>
));
@ -181,32 +194,33 @@ story.add('Group call - 1', () => (
story.add('Group call - Many', () => (
<CallScreen
{...createProps({
callTypeState: getGroupCallState([
callTypeState: getGroupCallState(),
groupCallParticipants: [
{
conversationId: '123',
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
isSelf: false,
title: 'Amy',
videoAspectRatio: 1.3,
},
{
conversationId: '456',
demuxId: 1,
hasRemoteAudio: true,
hasRemoteVideo: true,
isSelf: true,
title: 'Bob',
videoAspectRatio: 1.3,
},
{
conversationId: '789',
demuxId: 2,
hasRemoteAudio: true,
hasRemoteVideo: true,
isSelf: false,
title: 'Alice',
videoAspectRatio: 1.3,
},
]),
],
})}
/>
));

View file

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

View file

@ -20,11 +20,13 @@ function createParticipant(
return {
avatarPath: participantProps.avatarPath,
color: Colors[randomColor],
demuxId: 2,
hasRemoteAudio: Boolean(participantProps.hasRemoteAudio),
hasRemoteVideo: Boolean(participantProps.hasRemoteVideo),
isSelf: Boolean(participantProps.isSelf),
profileName: 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 { ConversationTypeType } from '../state/ducks/conversations';
import { ActiveCallType } from '../state/ducks/calling';
import { CallingPip, PropsType } from './CallingPip';
import {
CallMode,
@ -43,9 +44,23 @@ const defaultCall = {
hasRemoteVideo: true,
};
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
call: overrideProps.call || defaultCall,
conversation: overrideProps.conversation || conversation,
const createProps = (
overrideProps: Partial<PropsType> = {},
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
getGroupCallVideoFrameSource: noop as any,
hangUp: action('hang-up'),
@ -59,39 +74,48 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
const story = storiesOf('Components/CallingPip', module);
story.add('Default', () => {
const props = createProps();
const props = createProps({});
return <CallingPip {...props} />;
});
story.add('Contact (with avatar)', () => {
const props = createProps({
conversation: {
...conversation,
avatarPath: 'https://www.fillmurray.com/64/64',
},
});
const props = createProps(
{},
{
conversation: {
...conversation,
avatarPath: 'https://www.fillmurray.com/64/64',
},
}
);
return <CallingPip {...props} />;
});
story.add('Contact (no color)', () => {
const props = createProps({
conversation: {
...conversation,
color: undefined,
},
});
const props = createProps(
{},
{
conversation: {
...conversation,
color: undefined,
},
}
);
return <CallingPip {...props} />;
});
story.add('Group Call', () => {
const props = createProps({
call: {
callMode: CallMode.Group as CallMode.Group,
conversationId: '3051234567',
connectionState: GroupCallConnectionState.Connected,
joinState: GroupCallJoinState.Joined,
remoteParticipants: [],
},
});
const props = createProps(
{},
{
call: {
callMode: CallMode.Group as CallMode.Group,
conversationId: '3051234567',
connectionState: GroupCallConnectionState.Connected,
joinState: GroupCallJoinState.Joined,
remoteParticipants: [],
},
}
);
return <CallingPip {...props} />;
});

View file

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

View file

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

View file

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

View file

@ -4,9 +4,12 @@
import React, { useState, useMemo } from 'react';
import Measure from 'react-measure';
import { takeWhile, chunk, maxBy, flatten } from 'lodash';
import { VideoFrameSource } from '../types/Calling';
import { GroupCallParticipantInfoType } from '../state/ducks/calling';
import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant';
import {
GroupCallRemoteParticipantType,
VideoFrameSource,
} from '../types/Calling';
import { LocalizerType } from '../types/Util';
const MIN_RENDERED_HEIGHT = 10;
const PARTICIPANT_MARGIN = 10;
@ -17,13 +20,14 @@ interface Dimensions {
}
interface GridArrangement {
rows: Array<Array<GroupCallParticipantInfoType>>;
rows: Array<Array<GroupCallRemoteParticipantType>>;
scalar: number;
}
interface PropsType {
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
@ -52,6 +56,7 @@ interface PropsType {
// 4. Lay out this arrangement on the screen.
export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
getGroupCallVideoFrameSource,
i18n,
remoteParticipants,
}) => {
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,
// 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
// width. So if there were 5 rows and the container was 100px wide, then we can't
// possibly fit more than 500px of participants.
@ -199,12 +204,11 @@ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
return (
<GroupCallRemoteParticipant
key={remoteParticipant.demuxId}
demuxId={remoteParticipant.demuxId}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
hasRemoteAudio={remoteParticipant.hasRemoteAudio}
hasRemoteVideo={remoteParticipant.hasRemoteVideo}
height={gridParticipantHeight}
i18n={i18n}
left={left}
remoteParticipant={remoteParticipant}
top={top}
width={renderedWidth}
/>
@ -235,7 +239,7 @@ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
};
function totalRemoteParticipantWidthAtMinHeight(
remoteParticipants: ReadonlyArray<GroupCallParticipantInfoType>
remoteParticipants: ReadonlyArray<GroupCallRemoteParticipantType>
): number {
return remoteParticipants.reduce(
(result, { videoAspectRatio }) =>

View file

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

View file

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

View file

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

View file

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