Calling missing media keys indicator

This commit is contained in:
ayumi-signal 2024-01-23 11:08:21 -08:00 committed by GitHub
parent 436ee1a18f
commit d97aa68716
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 280 additions and 84 deletions

View file

@ -1864,6 +1864,14 @@
"messageformat": "You won't receive their voice or video and they won't receive yours.", "messageformat": "You won't receive their voice or video and they won't receive yours.",
"description": "Shown in the modal dialog to describe how blocking works in a group call" "description": "Shown in the modal dialog to describe how blocking works in a group call"
}, },
"icu:calling__missing-media-keys": {
"messageformat": "Can't receive audio and video from {name}",
"description": "When you can't view someone's audio and video in a call because their media keys are unavailable"
},
"icu:calling__missing-media-keys-info": {
"messageformat": "This may be because they have not verified your safety number change, there's a problem with their device, or they have blocked you.",
"description": "Detailed explanation why you can't view someone's audio and video in a call because their media keys are unavailable."
},
"icu:calling__overflow__scroll-up": { "icu:calling__overflow__scroll-up": {
"messageformat": "Scroll up", "messageformat": "Scroll up",
"description": "Label for the \"scroll up\" button in a call's overflow area" "description": "Label for the \"scroll up\" button in a call's overflow area"

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 1C9.82441 1 7.69767 1.64514 5.88873 2.85383C4.07979 4.06253 2.66989 5.78049 1.83733 7.79048C1.00477 9.80047 0.786929 12.0122 1.21137 14.146C1.6358 16.2798 2.68345 18.2398 4.22183 19.7782C5.76021 21.3166 7.72022 22.3642 9.85401 22.7886C11.9878 23.2131 14.1995 22.9952 16.2095 22.1627C18.2195 21.3301 19.9375 19.9202 21.1462 18.1113C22.3549 16.3023 23 14.1756 23 12C23 9.08262 21.8411 6.28473 19.7782 4.22183C17.7153 2.15893 14.9174 1 12 1ZM13.25 6L12.75 13.5H11.25L10.75 6H13.25ZM12 18C11.7033 18 11.4133 17.912 11.1666 17.7472C10.92 17.5824 10.7277 17.3481 10.6142 17.074C10.5007 16.7999 10.4709 16.4983 10.5288 16.2074C10.5867 15.9164 10.7296 15.6491 10.9393 15.4393C11.1491 15.2296 11.4164 15.0867 11.7074 15.0288C11.9983 14.9709 12.2999 15.0006 12.574 15.1142C12.8481 15.2277 13.0824 15.42 13.2472 15.6666C13.412 15.9133 13.5 16.2033 13.5 16.5C13.5 16.8978 13.342 17.2794 13.0607 17.5607C12.7794 17.842 12.3978 18 12 18Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -3824,6 +3824,14 @@ button.module-image__border-overlay:focus {
padding-block: 0 14px; padding-block: 0 14px;
padding-inline: 16px; padding-inline: 16px;
} }
.module-ongoing-call__group-call-remote-participant__error-icon {
margin-block-end: 16px;
}
.module-ongoing-call__group-call-remote-participant__error {
display: block;
}
} }
&--hand-raised { &--hand-raised {
@ -3861,6 +3869,7 @@ button.module-image__border-overlay:focus {
margin-bottom: 1rem; margin-bottom: 1rem;
&__footer { &__footer {
height: 40px;
padding-block: 0 8px; padding-block: 0 8px;
padding-inline: 10px; padding-inline: 10px;
} }
@ -4029,23 +4038,38 @@ button.module-image__border-overlay:focus {
background-color: $color-gray-78; background-color: $color-gray-78;
} }
&__blocked { &-background .module-calling__background--blur {
@include color-svg('../images/icons/v3/block/block.svg', $color-white); pointer-events: none;
height: 24px; }
margin-bottom: 16px;
width: 24px;
&--info { &__error {
@include button-reset; // Hide it here in the general case, and reveal it in the grid layout
background-color: $color-gray-75; // when @container size is big enough
border-radius: 16px; display: none;
color: $color-white;
line-height: 16px;
padding-block: 3px;
padding-inline: 10px;
}
&--modal-title { margin-block-end: 12px;
margin-inline: 8px;
font-size: 12px;
line-height: 16px;
color: $color-white;
text-align: center;
z-index: $z-index-base;
}
&__more-info {
@include button-reset;
padding-block: 3px;
padding-inline: 10px;
border-radius: 16px;
background-color: $color-gray-75;
color: $color-white;
font-size: 12px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
z-index: $z-index-above-base;
&-modal-title {
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
display: -webkit-box; display: -webkit-box;
@ -4054,6 +4078,23 @@ button.module-image__border-overlay:focus {
} }
} }
&__error-icon {
width: 24px;
height: 24px;
margin-block-end: 8px;
&--blocked {
@include color-svg('../images/icons/v3/block/block.svg', $color-white);
}
&--missing-media-keys {
@include color-svg(
'../images/icons/v3/error/error-circle-solid.svg',
$color-white
);
}
}
&__footer { &__footer {
display: flex; display: flex;
position: absolute; position: absolute;

View file

@ -326,6 +326,7 @@ export function GroupCall1(): JSX.Element {
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isHandRaised: false, isHandRaised: false,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -353,6 +354,7 @@ export function GroupCallYourHandRaised(): JSX.Element {
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isHandRaised: false, isHandRaised: false,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -372,26 +374,32 @@ export function GroupCallYourHandRaised(): JSX.Element {
const PARTICIPANT_EMOJIS = ['❤️', '🤔', '✨', '😂', '🦄'] as const; const PARTICIPANT_EMOJIS = ['❤️', '🤔', '✨', '😂', '🦄'] as const;
// We generate these upfront so that the list is stable when you move the slider. // We generate these upfront so that the list is stable when you move the slider.
const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({ const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => {
aci: generateAci(), const mediaKeysReceived = (index + 1) % 20 !== 0;
demuxId: index,
hasRemoteAudio: index % 3 !== 0, return {
hasRemoteVideo: index % 4 !== 0, aci: generateAci(),
isHandRaised: (index - 3) % 10 === 0, addedTime: Date.now() - 60000,
presenting: false, demuxId: index,
sharingScreen: false, hasRemoteAudio: mediaKeysReceived ? index % 3 !== 0 : false,
videoAspectRatio: Math.random() < 0.7 ? 1.3 : Math.random() * 0.4 + 0.6, hasRemoteVideo: mediaKeysReceived ? index % 4 !== 0 : false,
...getDefaultConversationWithServiceId({ isHandRaised: (index - 3) % 10 === 0,
isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1, mediaKeysReceived,
title: `Participant ${ presenting: false,
(index - 2) % 4 === 0 sharingScreen: false,
? PARTICIPANT_EMOJIS[ videoAspectRatio: Math.random() < 0.7 ? 1.3 : Math.random() * 0.4 + 0.6,
Math.floor((index - 2) / 4) % PARTICIPANT_EMOJIS.length ...getDefaultConversationWithServiceId({
] isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1,
: '' title: `Participant ${
} ${index + 1}`, (index - 2) % 4 === 0
}), ? PARTICIPANT_EMOJIS[
})); Math.floor((index - 2) / 4) % PARTICIPANT_EMOJIS.length
]
: ''
} ${index + 1}`,
}),
};
});
export function GroupCallManyPaginated(): JSX.Element { export function GroupCallManyPaginated(): JSX.Element {
const props = createProps({ const props = createProps({
@ -471,6 +479,7 @@ export function GroupCallReconnecting(): JSX.Element {
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isHandRaised: false, isHandRaised: false,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -793,3 +802,38 @@ function useHandRaiser(
}, [frequency, call, max, min]); }, [frequency, call, max, min]);
return call; return call;
} }
export function GroupCallSomeoneMissingMediaKeys(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants
.slice(0, 5)
.map((participant, index) => ({
...participant,
addedTime: index === 1 ? Date.now() - 60000 : undefined,
hasRemoteAudio: false,
hasRemoteVideo: false,
mediaKeysReceived: index !== 1,
})),
})}
/>
);
}
export function GroupCallSomeoneBlocked(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants
.slice(0, 5)
.map((participant, index) => ({
...participant,
isBlocked: index === 1,
})),
})}
/>
);
}

View file

@ -26,6 +26,7 @@ function createParticipant(
hasRemoteAudio: Boolean(participantProps.hasRemoteAudio), hasRemoteAudio: Boolean(participantProps.hasRemoteAudio),
hasRemoteVideo: Boolean(participantProps.hasRemoteVideo), hasRemoteVideo: Boolean(participantProps.hasRemoteVideo),
isHandRaised: Boolean(participantProps.isHandRaised), isHandRaised: Boolean(participantProps.isHandRaised),
mediaKeysReceived: Boolean(participantProps.mediaKeysReceived),
presenting: Boolean(participantProps.presenting), presenting: Boolean(participantProps.presenting),
sharingScreen: Boolean(participantProps.sharingScreen), sharingScreen: Boolean(participantProps.sharingScreen),
videoAspectRatio: 1.3, videoAspectRatio: 1.3,

View file

@ -24,6 +24,7 @@ const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({
hasRemoteAudio: index % 3 !== 0, hasRemoteAudio: index % 3 !== 0,
hasRemoteVideo: index % 4 !== 0, hasRemoteVideo: index % 4 !== 0,
isHandRaised: (index - 2) % 8 === 0, isHandRaised: (index - 2) % 8 === 0,
mediaKeysReceived: (index + 1) % 20 !== 0,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 1.3, videoAspectRatio: 1.3,

View file

@ -35,13 +35,17 @@ const getFrameBuffer = memoize(() => Buffer.alloc(FRAME_BUFFER_SIZE));
const createProps = ( const createProps = (
overrideProps: OverridePropsType, overrideProps: OverridePropsType,
{ {
addedTime,
isBlocked = false, isBlocked = false,
hasRemoteAudio = false,
presenting = false,
isHandRaised = false, isHandRaised = false,
hasRemoteAudio = false,
mediaKeysReceived = true,
presenting = false,
}: { }: {
addedTime?: number;
isBlocked?: boolean; isBlocked?: boolean;
hasRemoteAudio?: boolean; hasRemoteAudio?: boolean;
mediaKeysReceived?: boolean;
presenting?: boolean; presenting?: boolean;
isHandRaised?: boolean; isHandRaised?: boolean;
} = {} } = {}
@ -54,10 +58,12 @@ const createProps = (
audioLevel: 0, audioLevel: 0,
remoteParticipant: { remoteParticipant: {
aci: generateAci(), aci: generateAci(),
addedTime,
demuxId: 123, demuxId: 123,
hasRemoteAudio, hasRemoteAudio,
hasRemoteVideo: true, hasRemoteVideo: true,
isHandRaised, isHandRaised,
mediaKeysReceived,
presenting, presenting,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -165,3 +171,24 @@ export function Blocked(): JSX.Element {
/> />
); );
} }
export function NoMediaKeys(): JSX.Element {
return (
<GroupCallRemoteParticipant
{...createProps(
{
isInPip: false,
height: 120,
left: 0,
top: 0,
width: 120,
},
{
addedTime: Date.now() - 60 * 1000,
hasRemoteAudio: true,
mediaKeysReceived: false,
}
)}
/>
);
}

View file

@ -27,9 +27,12 @@ import { ContactName } from './conversation/ContactName';
import { useIntersectionObserver } from '../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../hooks/useIntersectionObserver';
import { MAX_FRAME_HEIGHT, MAX_FRAME_WIDTH } from '../calling/constants'; import { MAX_FRAME_HEIGHT, MAX_FRAME_WIDTH } from '../calling/constants';
import { useValueAtFixedRate } from '../hooks/useValueAtFixedRate'; import { useValueAtFixedRate } from '../hooks/useValueAtFixedRate';
import { Theme } from '../util/theme';
import { isOlderThan } from '../util/timestamp';
const MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES = 10000; const MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES = 10000;
const MAX_TIME_TO_SHOW_STALE_SCREENSHARE_FRAMES = 60000; const MAX_TIME_TO_SHOW_STALE_SCREENSHARE_FRAMES = 60000;
const DELAY_TO_SHOW_MISSING_MEDIA_KEYS = 5000;
type BasePropsType = { type BasePropsType = {
getFrameBuffer: () => Buffer; getFrameBuffer: () => Buffer;
@ -77,6 +80,7 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
const { const {
acceptedMessageRequest, acceptedMessageRequest,
addedTime,
avatarPath, avatarPath,
color, color,
demuxId, demuxId,
@ -85,6 +89,7 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
isHandRaised, isHandRaised,
isBlocked, isBlocked,
isMe, isMe,
mediaKeysReceived,
profileName, profileName,
sharedGroupNames, sharedGroupNames,
sharingScreen, sharingScreen,
@ -102,7 +107,7 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
const [isWide, setIsWide] = useState<boolean>( const [isWide, setIsWide] = useState<boolean>(
videoAspectRatio ? videoAspectRatio >= 1 : true videoAspectRatio ? videoAspectRatio >= 1 : true
); );
const [showBlockInfo, setShowBlockInfo] = useState(false); const [showErrorDialog, setShowErrorDialog] = useState(false);
// We have some state (`hasReceivedVideoRecently`) and this ref. We can't have a // We have some state (`hasReceivedVideoRecently`) and this ref. We can't have a
// single state value like `lastReceivedVideoAt` because (1) it won't automatically // single state value like `lastReceivedVideoAt` because (1) it won't automatically
@ -129,6 +134,11 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
const wantsToShowVideo = hasRemoteVideo && !isBlocked && isVisible; const wantsToShowVideo = hasRemoteVideo && !isBlocked && isVisible;
const hasVideoToShow = wantsToShowVideo && hasReceivedVideoRecently; const hasVideoToShow = wantsToShowVideo && hasReceivedVideoRecently;
const showMissingMediaKeys = Boolean(
!mediaKeysReceived &&
addedTime &&
isOlderThan(addedTime, DELAY_TO_SHOW_MISSING_MEDIA_KEYS)
);
const videoFrameSource = useMemo( const videoFrameSource = useMemo(
() => getGroupCallVideoFrameSource(demuxId), () => getGroupCallVideoFrameSource(demuxId),
@ -293,29 +303,94 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
} }
} }
let noVideoNode: ReactNode;
let errorDialogTitle: ReactNode;
let errorDialogBody = '';
if (!hasVideoToShow) {
const showDialogButton = (
<button
type="button"
className="module-ongoing-call__group-call-remote-participant__more-info"
onClick={() => {
setShowErrorDialog(true);
}}
>
{i18n('icu:moreInfo')}
</button>
);
if (isBlocked) {
noVideoNode = (
<>
<i className="module-ongoing-call__group-call-remote-participant__error-icon module-ongoing-call__group-call-remote-participant__error-icon--blocked" />
{showDialogButton}
</>
);
errorDialogTitle = (
<div className="module-ongoing-call__group-call-remote-participant__more-info-modal-title">
<Intl
i18n={i18n}
id="icu:calling__you-have-blocked"
components={{
name: <ContactName key="name" title={title} />,
}}
/>
</div>
);
errorDialogBody = i18n('icu:calling__block-info');
} else if (showMissingMediaKeys) {
noVideoNode = (
<>
<i className="module-ongoing-call__group-call-remote-participant__error-icon module-ongoing-call__group-call-remote-participant__error-icon--missing-media-keys" />
<div className="module-ongoing-call__group-call-remote-participant__error">
{i18n('icu:calling__missing-media-keys', { name: title })}
</div>
{showDialogButton}
</>
);
errorDialogTitle = (
<div className="module-ongoing-call__group-call-remote-participant__more-info-modal-title">
<Intl
i18n={i18n}
id="icu:calling__missing-media-keys"
components={{
name: <ContactName key="name" title={title} />,
}}
/>
</div>
);
errorDialogBody = i18n('icu:calling__missing-media-keys-info');
} else {
noVideoNode = (
<Avatar
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
badge={undefined}
color={color || AvatarColors[0]}
noteToSelf={false}
conversationType="direct"
i18n={i18n}
isMe={isMe}
profileName={profileName}
title={title}
sharedGroupNames={sharedGroupNames}
size={avatarSize}
/>
);
}
}
return ( return (
<> <>
{showBlockInfo && ( {showErrorDialog && (
<ConfirmationDialog <ConfirmationDialog
dialogName="GroupCallRemoteParticipant.blockInfo" dialogName="GroupCallRemoteParticipant.blockInfo"
cancelText={i18n('icu:ok')} cancelText={i18n('icu:ok')}
i18n={i18n} i18n={i18n}
onClose={() => { onClose={() => setShowErrorDialog(false)}
setShowBlockInfo(false); theme={Theme.Dark}
}} title={errorDialogTitle}
title={
<div className="module-ongoing-call__group-call-remote-participant__blocked--modal-title">
<Intl
i18n={i18n}
id="icu:calling__you-have-blocked"
components={{
name: <ContactName key="name" title={title} />,
}}
/>
</div>
}
> >
{i18n('icu:calling__block-info')} {errorDialogBody}
</ConfirmationDialog> </ConfirmationDialog>
)} )}
@ -372,40 +447,12 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
}} }}
/> />
)} )}
{!hasVideoToShow && ( {noVideoNode && (
<CallBackgroundBlur <CallBackgroundBlur
avatarPath={avatarPath} avatarPath={avatarPath}
className="module-ongoing-call__group-call-remote-participant-background" className="module-ongoing-call__group-call-remote-participant-background"
> >
{isBlocked ? ( {noVideoNode}
<>
<i className="module-ongoing-call__group-call-remote-participant__blocked" />
<button
type="button"
className="module-ongoing-call__group-call-remote-participant__blocked--info"
onClick={() => {
setShowBlockInfo(true);
}}
>
{i18n('icu:moreInfo')}
</button>
</>
) : (
<Avatar
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
badge={undefined}
color={color || AvatarColors[0]}
noteToSelf={false}
conversationType="direct"
i18n={i18n}
isMe={isMe}
profileName={profileName}
title={title}
sharedGroupNames={sharedGroupNames}
size={avatarSize}
/>
)}
</CallBackgroundBlur> </CallBackgroundBlur>
)} )}
</div> </div>

View file

@ -1086,11 +1086,14 @@ export class CallingClass {
aci = '00000000-0000-4000-8000-000000000000'; aci = '00000000-0000-4000-8000-000000000000';
} }
assertDev(isAciString(aci), 'remote participant aci must be a aci'); assertDev(isAciString(aci), 'remote participant aci must be a aci');
return { return {
aci, aci,
addedTime: normalizeGroupCallTimestamp(remoteDeviceState.addedTime),
demuxId: remoteDeviceState.demuxId, demuxId: remoteDeviceState.demuxId,
hasRemoteAudio: !remoteDeviceState.audioMuted, hasRemoteAudio: !remoteDeviceState.audioMuted,
hasRemoteVideo: !remoteDeviceState.videoMuted, hasRemoteVideo: !remoteDeviceState.videoMuted,
mediaKeysReceived: remoteDeviceState.mediaKeysReceived,
presenting: Boolean(remoteDeviceState.presenting), presenting: Boolean(remoteDeviceState.presenting),
sharingScreen: Boolean(remoteDeviceState.sharingScreen), sharingScreen: Boolean(remoteDeviceState.sharingScreen),
speakerTime: normalizeGroupCallTimestamp( speakerTime: normalizeGroupCallTimestamp(

View file

@ -76,9 +76,11 @@ export type GroupCallPeekInfoType = ReadonlyDeep<{
// eslint-disable-next-line local-rules/type-alias-readonlydeep // eslint-disable-next-line local-rules/type-alias-readonlydeep
export type GroupCallParticipantInfoType = { export type GroupCallParticipantInfoType = {
aci: AciString; aci: AciString;
addedTime?: number;
demuxId: number; demuxId: number;
hasRemoteAudio: boolean; hasRemoteAudio: boolean;
hasRemoteVideo: boolean; hasRemoteVideo: boolean;
mediaKeysReceived: boolean;
presenting: boolean; presenting: boolean;
sharingScreen: boolean; sharingScreen: boolean;
speakerTime?: number; speakerTime?: number;

View file

@ -246,10 +246,12 @@ const mapStateToActiveCallProp = (
remoteParticipants.push({ remoteParticipants.push({
...remoteConversation, ...remoteConversation,
aci: remoteParticipant.aci, aci: remoteParticipant.aci,
addedTime: remoteParticipant.addedTime,
demuxId: remoteParticipant.demuxId, demuxId: remoteParticipant.demuxId,
hasRemoteAudio: remoteParticipant.hasRemoteAudio, hasRemoteAudio: remoteParticipant.hasRemoteAudio,
hasRemoteVideo: remoteParticipant.hasRemoteVideo, hasRemoteVideo: remoteParticipant.hasRemoteVideo,
isHandRaised: raisedHands.has(remoteParticipant.demuxId), isHandRaised: raisedHands.has(remoteParticipant.demuxId),
mediaKeysReceived: remoteParticipant.mediaKeysReceived,
presenting: remoteParticipant.presenting, presenting: remoteParticipant.presenting,
sharingScreen: remoteParticipant.sharingScreen, sharingScreen: remoteParticipant.sharingScreen,
speakerTime: remoteParticipant.speakerTime, speakerTime: remoteParticipant.speakerTime,

View file

@ -118,6 +118,7 @@ describe('calling duck', () => {
demuxId: 123, demuxId: 123,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,
@ -906,6 +907,7 @@ describe('calling duck', () => {
demuxId: 123, demuxId: 123,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,
@ -935,6 +937,7 @@ describe('calling duck', () => {
demuxId: 123, demuxId: 123,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,
@ -966,6 +969,7 @@ describe('calling duck', () => {
demuxId: 456, demuxId: 456,
hasRemoteAudio: false, hasRemoteAudio: false,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 16 / 9, videoAspectRatio: 16 / 9,
@ -993,6 +997,7 @@ describe('calling duck', () => {
demuxId: 456, demuxId: 456,
hasRemoteAudio: false, hasRemoteAudio: false,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 16 / 9, videoAspectRatio: 16 / 9,
@ -1037,6 +1042,7 @@ describe('calling duck', () => {
demuxId: 456, demuxId: 456,
hasRemoteAudio: false, hasRemoteAudio: false,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 16 / 9, videoAspectRatio: 16 / 9,
@ -1089,6 +1095,7 @@ describe('calling duck', () => {
demuxId: 456, demuxId: 456,
hasRemoteAudio: false, hasRemoteAudio: false,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 16 / 9, videoAspectRatio: 16 / 9,
@ -1128,6 +1135,7 @@ describe('calling duck', () => {
demuxId: 456, demuxId: 456,
hasRemoteAudio: false, hasRemoteAudio: false,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 16 / 9, videoAspectRatio: 16 / 9,
@ -1160,6 +1168,7 @@ describe('calling duck', () => {
demuxId: 456, demuxId: 456,
hasRemoteAudio: false, hasRemoteAudio: false,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 16 / 9, videoAspectRatio: 16 / 9,
@ -1204,6 +1213,7 @@ describe('calling duck', () => {
demuxId: 456, demuxId: 456,
hasRemoteAudio: false, hasRemoteAudio: false,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 16 / 9, videoAspectRatio: 16 / 9,
@ -1882,6 +1892,7 @@ describe('calling duck', () => {
demuxId: 123, demuxId: 123,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,
@ -1908,6 +1919,7 @@ describe('calling duck', () => {
demuxId: 123, demuxId: 123,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,
@ -1954,6 +1966,7 @@ describe('calling duck', () => {
demuxId: 123, demuxId: 123,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,
@ -2004,6 +2017,7 @@ describe('calling duck', () => {
demuxId: 123, demuxId: 123,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,
@ -2048,6 +2062,7 @@ describe('calling duck', () => {
demuxId: 123, demuxId: 123,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
mediaKeysReceived: true,
presenting: false, presenting: false,
sharingScreen: false, sharingScreen: false,
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,

View file

@ -156,10 +156,12 @@ export enum GroupCallJoinState {
export type GroupCallRemoteParticipantType = ConversationType & { export type GroupCallRemoteParticipantType = ConversationType & {
aci: AciString; aci: AciString;
addedTime?: number;
demuxId: number; demuxId: number;
hasRemoteAudio: boolean; hasRemoteAudio: boolean;
hasRemoteVideo: boolean; hasRemoteVideo: boolean;
isHandRaised: boolean; isHandRaised: boolean;
mediaKeysReceived: boolean;
presenting: boolean; presenting: boolean;
sharingScreen: boolean; sharingScreen: boolean;
speakerTime?: number; speakerTime?: number;