Group calls: mute in the lobby if joining a large call
This commit is contained in:
parent
09af7eeece
commit
f8bbf5c998
12 changed files with 499 additions and 279 deletions
|
@ -1447,6 +1447,10 @@
|
||||||
"message": "Return to Call",
|
"message": "Return to Call",
|
||||||
"description": "Button label in the call lobby for returning to a call"
|
"description": "Button label in the call lobby for returning to a call"
|
||||||
},
|
},
|
||||||
|
"calling__lobby-automatically-muted-because-there-are-a-lot-of-people": {
|
||||||
|
"message": "Your microphone is muted due to the size of the call",
|
||||||
|
"description": "Shown in a call lobby toast if there are a lot of people already on the call"
|
||||||
|
},
|
||||||
"calling__call-is-full": {
|
"calling__call-is-full": {
|
||||||
"message": "Call is full",
|
"message": "Call is full",
|
||||||
"description": "Text in the call lobby when you can't join because the call is full"
|
"description": "Text in the call lobby when you can't join because the call is full"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2018-2021 Signal Messenger, LLC
|
// Copyright 2018-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
// Using BEM syntax explained here: https://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/
|
// Using BEM syntax explained here: https://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/
|
||||||
|
@ -4285,28 +4285,6 @@ button.module-image__border-overlay:focus {
|
||||||
fill-mode: forwards;
|
fill-mode: forwards;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__toast {
|
|
||||||
@include button-reset();
|
|
||||||
@include font-body-1-bold;
|
|
||||||
background-color: $color-gray-75;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: $color-white;
|
|
||||||
max-width: 80%;
|
|
||||||
opacity: 1;
|
|
||||||
padding: 12px;
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
top: 12px;
|
|
||||||
transition: top 200ms ease-out, opacity 200ms ease-out;
|
|
||||||
user-select: none;
|
|
||||||
z-index: $z-index-above-above-base;
|
|
||||||
|
|
||||||
&--hidden {
|
|
||||||
opacity: 0;
|
|
||||||
top: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-calling-tools {
|
.module-calling-tools {
|
||||||
|
|
24
stylesheets/components/CallingToast.scss
Normal file
24
stylesheets/components/CallingToast.scss
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
.CallingToast {
|
||||||
|
@include button-reset();
|
||||||
|
@include font-body-1-bold;
|
||||||
|
background-color: $color-gray-75;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: $color-white;
|
||||||
|
max-width: 80%;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 12px;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
top: 12px;
|
||||||
|
transition: top 200ms ease-out, opacity 200ms ease-out;
|
||||||
|
user-select: none;
|
||||||
|
z-index: $z-index-above-above-base;
|
||||||
|
|
||||||
|
&--hidden {
|
||||||
|
opacity: 0;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2014-2021 Signal Messenger, LLC
|
// Copyright 2014-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
// Global Settings, Variables, and Mixins
|
// Global Settings, Variables, and Mixins
|
||||||
|
@ -43,6 +43,7 @@
|
||||||
@import './components/CallingPreCallInfo.scss';
|
@import './components/CallingPreCallInfo.scss';
|
||||||
@import './components/CallingScreenSharingController.scss';
|
@import './components/CallingScreenSharingController.scss';
|
||||||
@import './components/CallingSelectPresentingSourcesModal.scss';
|
@import './components/CallingSelectPresentingSourcesModal.scss';
|
||||||
|
@import './components/CallingToast.scss';
|
||||||
@import './components/ChatColorPicker.scss';
|
@import './components/ChatColorPicker.scss';
|
||||||
@import './components/Checkbox.scss';
|
@import './components/Checkbox.scss';
|
||||||
@import './components/CompositionArea.scss';
|
@import './components/CompositionArea.scss';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
@ -51,11 +51,11 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
||||||
(isGroupCall ? times(3, () => getDefaultConversation()) : undefined),
|
(isGroupCall ? times(3, () => getDefaultConversation()) : undefined),
|
||||||
hasLocalAudio: boolean(
|
hasLocalAudio: boolean(
|
||||||
'hasLocalAudio',
|
'hasLocalAudio',
|
||||||
overrideProps.hasLocalAudio || false
|
overrideProps.hasLocalAudio ?? true
|
||||||
),
|
),
|
||||||
hasLocalVideo: boolean(
|
hasLocalVideo: boolean(
|
||||||
'hasLocalVideo',
|
'hasLocalVideo',
|
||||||
overrideProps.hasLocalVideo || false
|
overrideProps.hasLocalVideo ?? false
|
||||||
),
|
),
|
||||||
i18n,
|
i18n,
|
||||||
isGroupCall,
|
isGroupCall,
|
||||||
|
@ -122,9 +122,9 @@ story.add('Local Video', () => {
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
story.add('Local Video', () => {
|
story.add('Initially muted', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
hasLocalVideo: true,
|
hasLocalAudio: false,
|
||||||
});
|
});
|
||||||
return <CallingLobby {...props} />;
|
return <CallingLobby {...props} />;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -13,6 +13,7 @@ import { CallingButton, CallingButtonType } from './CallingButton';
|
||||||
import { TooltipPlacement } from './Tooltip';
|
import { TooltipPlacement } from './Tooltip';
|
||||||
import { CallBackgroundBlur } from './CallBackgroundBlur';
|
import { CallBackgroundBlur } from './CallBackgroundBlur';
|
||||||
import { CallingHeader } from './CallingHeader';
|
import { CallingHeader } from './CallingHeader';
|
||||||
|
import { CallingToast, DEFAULT_LIFETIME } from './CallingToast';
|
||||||
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
||||||
import {
|
import {
|
||||||
CallingLobbyJoinButton,
|
CallingLobbyJoinButton,
|
||||||
|
@ -92,6 +93,21 @@ export const CallingLobby = ({
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
outgoingRing,
|
outgoingRing,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
|
const [isMutedToastVisible, setIsMutedToastVisible] = React.useState(
|
||||||
|
!hasLocalAudio
|
||||||
|
);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isMutedToastVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setIsMutedToastVisible(false);
|
||||||
|
}, DEFAULT_LIFETIME);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [isMutedToastVisible]);
|
||||||
|
|
||||||
const localVideoRef = React.useRef<null | HTMLVideoElement>(null);
|
const localVideoRef = React.useRef<null | HTMLVideoElement>(null);
|
||||||
|
|
||||||
const shouldShowLocalVideo = hasLocalVideo && availableCameras.length > 0;
|
const shouldShowLocalVideo = hasLocalVideo && availableCameras.length > 0;
|
||||||
|
@ -221,6 +237,15 @@ export const CallingLobby = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<CallingToast
|
||||||
|
isVisible={isMutedToastVisible}
|
||||||
|
onClick={() => setIsMutedToastVisible(false)}
|
||||||
|
>
|
||||||
|
{i18n(
|
||||||
|
'calling__lobby-automatically-muted-because-there-are-a-lot-of-people'
|
||||||
|
)}
|
||||||
|
</CallingToast>
|
||||||
|
|
||||||
<CallingHeader
|
<CallingHeader
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isGroupCall={isGroupCall}
|
isGroupCall={isGroupCall}
|
||||||
|
|
27
ts/components/CallingToast.tsx
Normal file
27
ts/components/CallingToast.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { FunctionComponent } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
type PropsType = {
|
||||||
|
isVisible: boolean;
|
||||||
|
onClick: () => unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_LIFETIME = 5000;
|
||||||
|
|
||||||
|
export const CallingToast: FunctionComponent<PropsType> = ({
|
||||||
|
isVisible,
|
||||||
|
onClick,
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<button
|
||||||
|
className={classNames('CallingToast', !isVisible && 'CallingToast--hidden')}
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
|
@ -1,12 +1,12 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import type { ActiveCallType } from '../types/Calling';
|
import type { ActiveCallType } from '../types/Calling';
|
||||||
import { CallMode, GroupCallConnectionState } from '../types/Calling';
|
import { CallMode, GroupCallConnectionState } from '../types/Calling';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
import { CallingToast, DEFAULT_LIFETIME } from './CallingToast';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
|
@ -101,8 +101,6 @@ function useScreenSharingToast({ activeCall, i18n }: PropsType): ToastType {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_DELAY = 5000;
|
|
||||||
|
|
||||||
// In the future, this component should show toasts when users join or leave. See
|
// In the future, this component should show toasts when users join or leave. See
|
||||||
// DESKTOP-902.
|
// DESKTOP-902.
|
||||||
export const CallingToastManager: React.FC<PropsType> = props => {
|
export const CallingToastManager: React.FC<PropsType> = props => {
|
||||||
|
@ -131,7 +129,7 @@ export const CallingToastManager: React.FC<PropsType> = props => {
|
||||||
if (timeoutRef && timeoutRef.current) {
|
if (timeoutRef && timeoutRef.current) {
|
||||||
clearTimeout(timeoutRef.current);
|
clearTimeout(timeoutRef.current);
|
||||||
}
|
}
|
||||||
timeoutRef.current = setTimeout(dismissToast, DEFAULT_DELAY);
|
timeoutRef.current = setTimeout(dismissToast, DEFAULT_LIFETIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
setToastMessage(toast.message);
|
setToastMessage(toast.message);
|
||||||
|
@ -144,17 +142,9 @@ export const CallingToastManager: React.FC<PropsType> = props => {
|
||||||
};
|
};
|
||||||
}, [dismissToast, setToastMessage, timeoutRef, toast]);
|
}, [dismissToast, setToastMessage, timeoutRef, toast]);
|
||||||
|
|
||||||
const isVisible = Boolean(toastMessage);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<CallingToast isVisible={Boolean(toastMessage)} onClick={dismissToast}>
|
||||||
className={classNames('module-ongoing-call__toast', {
|
|
||||||
'module-ongoing-call__toast--hidden': !isVisible,
|
|
||||||
})}
|
|
||||||
type="button"
|
|
||||||
onClick={dismissToast}
|
|
||||||
>
|
|
||||||
{toastMessage}
|
{toastMessage}
|
||||||
</button>
|
</CallingToast>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { DesktopCapturerSource } from 'electron';
|
import type { DesktopCapturerSource } from 'electron';
|
||||||
|
@ -39,10 +39,11 @@ import { uniqBy, noop } from 'lodash';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ActionsType as UxActionsType,
|
ActionsType as UxActionsType,
|
||||||
|
GroupCallParticipantInfoType,
|
||||||
GroupCallPeekInfoType,
|
GroupCallPeekInfoType,
|
||||||
} from '../state/ducks/calling';
|
} from '../state/ducks/calling';
|
||||||
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import { getConversationCallMode } from '../state/ducks/conversations';
|
import { getConversationCallMode } from '../state/ducks/conversations';
|
||||||
import { isConversationTooBigToRing } from '../conversations/isConversationTooBigToRing';
|
|
||||||
import { isMe } from '../util/whatTypeOfConversation';
|
import { isMe } from '../util/whatTypeOfConversation';
|
||||||
import type {
|
import type {
|
||||||
AvailableIODevicesType,
|
AvailableIODevicesType,
|
||||||
|
@ -99,6 +100,7 @@ import {
|
||||||
FALLBACK_NOTIFICATION_TITLE,
|
FALLBACK_NOTIFICATION_TITLE,
|
||||||
} from './notifications';
|
} from './notifications';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
|
import { assert } from '../util/assert';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
processGroupCallRingRequest,
|
processGroupCallRingRequest,
|
||||||
|
@ -308,30 +310,47 @@ export class CallingClass {
|
||||||
RingRTC.setSelfUuid(Buffer.from(uuidToBytes(ourUuid)));
|
RingRTC.setSelfUuid(Buffer.from(uuidToBytes(ourUuid)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async startCallingLobby(
|
async startCallingLobby({
|
||||||
conversationId: string,
|
conversation,
|
||||||
isVideoCall: boolean
|
hasLocalAudio,
|
||||||
): Promise<void> {
|
hasLocalVideo,
|
||||||
|
}: Readonly<{
|
||||||
|
conversation: Readonly<ConversationType>;
|
||||||
|
hasLocalAudio: boolean;
|
||||||
|
hasLocalVideo: boolean;
|
||||||
|
}>): Promise<
|
||||||
|
| undefined
|
||||||
|
| ({ hasLocalAudio: boolean; hasLocalVideo: boolean } & (
|
||||||
|
| { callMode: CallMode.Direct }
|
||||||
|
| {
|
||||||
|
callMode: CallMode.Group;
|
||||||
|
connectionState: GroupCallConnectionState;
|
||||||
|
joinState: GroupCallJoinState;
|
||||||
|
peekInfo?: GroupCallPeekInfoType;
|
||||||
|
remoteParticipants: Array<GroupCallParticipantInfoType>;
|
||||||
|
}
|
||||||
|
))
|
||||||
|
> {
|
||||||
log.info('CallingClass.startCallingLobby()');
|
log.info('CallingClass.startCallingLobby()');
|
||||||
|
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const callMode = getConversationCallMode(conversation);
|
||||||
if (!conversation) {
|
|
||||||
log.error('Could not find conversation, cannot start call lobby');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const conversationProps = conversation.format();
|
|
||||||
const callMode = getConversationCallMode(conversationProps);
|
|
||||||
switch (callMode) {
|
switch (callMode) {
|
||||||
case CallMode.None:
|
case CallMode.None:
|
||||||
log.error('Conversation does not support calls, new call not allowed.');
|
log.error('Conversation does not support calls, new call not allowed.');
|
||||||
return;
|
return;
|
||||||
case CallMode.Direct:
|
case CallMode.Direct: {
|
||||||
if (!this.getRemoteUserIdFromConversation(conversation)) {
|
const conversationModel = window.ConversationController.get(
|
||||||
|
conversation.id
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!conversationModel ||
|
||||||
|
!this.getRemoteUserIdFromConversation(conversationModel)
|
||||||
|
) {
|
||||||
log.error('Missing remote user identifier, new call not allowed.');
|
log.error('Missing remote user identifier, new call not allowed.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case CallMode.Group:
|
case CallMode.Group:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -348,7 +367,7 @@ export class CallingClass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const haveMediaPermissions = await this.requestPermissions(isVideoCall);
|
const haveMediaPermissions = await this.requestPermissions(hasLocalVideo);
|
||||||
if (!haveMediaPermissions) {
|
if (!haveMediaPermissions) {
|
||||||
log.info('Permissions were denied, new call not allowed.');
|
log.info('Permissions were denied, new call not allowed.');
|
||||||
return;
|
return;
|
||||||
|
@ -374,51 +393,53 @@ export class CallingClass {
|
||||||
// is fixed. See DESKTOP-1032.
|
// is fixed. See DESKTOP-1032.
|
||||||
await this.startDeviceReselectionTimer();
|
await this.startDeviceReselectionTimer();
|
||||||
|
|
||||||
|
const enableLocalCameraIfNecessary = hasLocalVideo
|
||||||
|
? () => this.enableLocalCamera()
|
||||||
|
: noop;
|
||||||
|
|
||||||
switch (callMode) {
|
switch (callMode) {
|
||||||
case CallMode.Direct:
|
case CallMode.Direct:
|
||||||
this.uxActions.showCallLobby({
|
// We could easily support this in the future if we need to.
|
||||||
|
assert(
|
||||||
|
hasLocalAudio,
|
||||||
|
'Expected local audio to be enabled for direct call lobbies'
|
||||||
|
);
|
||||||
|
enableLocalCameraIfNecessary();
|
||||||
|
return {
|
||||||
callMode: CallMode.Direct,
|
callMode: CallMode.Direct,
|
||||||
conversationId: conversationProps.id,
|
hasLocalAudio,
|
||||||
hasLocalAudio: true,
|
hasLocalVideo,
|
||||||
hasLocalVideo: isVideoCall,
|
};
|
||||||
});
|
|
||||||
break;
|
|
||||||
case CallMode.Group: {
|
case CallMode.Group: {
|
||||||
if (
|
if (
|
||||||
!conversationProps.groupId ||
|
!conversation.groupId ||
|
||||||
!conversationProps.publicParams ||
|
!conversation.publicParams ||
|
||||||
!conversationProps.secretParams
|
!conversation.secretParams
|
||||||
) {
|
) {
|
||||||
log.error(
|
log.error(
|
||||||
'Conversation is missing required parameters. Cannot connect group call'
|
'Conversation is missing required parameters. Cannot connect group call'
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const groupCall = this.connectGroupCall(conversationProps.id, {
|
const groupCall = this.connectGroupCall(conversation.id, {
|
||||||
groupId: conversationProps.groupId,
|
groupId: conversation.groupId,
|
||||||
publicParams: conversationProps.publicParams,
|
publicParams: conversation.publicParams,
|
||||||
secretParams: conversationProps.secretParams,
|
secretParams: conversation.secretParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
groupCall.setOutgoingAudioMuted(false);
|
groupCall.setOutgoingAudioMuted(!hasLocalAudio);
|
||||||
groupCall.setOutgoingVideoMuted(!isVideoCall);
|
groupCall.setOutgoingVideoMuted(!hasLocalVideo);
|
||||||
|
|
||||||
this.uxActions.showCallLobby({
|
enableLocalCameraIfNecessary();
|
||||||
|
|
||||||
|
return {
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
conversationId: conversationProps.id,
|
|
||||||
isConversationTooBigToRing:
|
|
||||||
isConversationTooBigToRing(conversationProps),
|
|
||||||
...this.formatGroupCallForRedux(groupCall),
|
...this.formatGroupCallForRedux(groupCall),
|
||||||
});
|
};
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw missingCaseError(callMode);
|
throw missingCaseError(callMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVideoCall) {
|
|
||||||
this.enableLocalCamera();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopCallingLobby(conversationId?: string): void {
|
stopCallingLobby(conversationId?: string): void {
|
||||||
|
@ -443,7 +464,6 @@ export class CallingClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
log.error('Could not find conversation, cannot start call');
|
log.error('Could not find conversation, cannot start call');
|
||||||
this.stopCallingLobby();
|
this.stopCallingLobby();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
@ -38,6 +38,7 @@ import { LatestQueue } from '../../util/LatestQueue';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
import type { ConversationChangedActionType } from './conversations';
|
import type { ConversationChangedActionType } from './conversations';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
|
import { strictAssert } from '../../util/assert';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -223,7 +224,7 @@ export type StartCallingLobbyType = {
|
||||||
isVideoCall: boolean;
|
isVideoCall: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShowCallLobbyType =
|
type StartCallingLobbyPayloadType =
|
||||||
| {
|
| {
|
||||||
callMode: CallMode.Direct;
|
callMode: CallMode.Direct;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
@ -299,7 +300,7 @@ const ACCEPT_CALL_PENDING = 'calling/ACCEPT_CALL_PENDING';
|
||||||
const CANCEL_CALL = 'calling/CANCEL_CALL';
|
const CANCEL_CALL = 'calling/CANCEL_CALL';
|
||||||
const CANCEL_INCOMING_GROUP_CALL_RING =
|
const CANCEL_INCOMING_GROUP_CALL_RING =
|
||||||
'calling/CANCEL_INCOMING_GROUP_CALL_RING';
|
'calling/CANCEL_INCOMING_GROUP_CALL_RING';
|
||||||
const SHOW_CALL_LOBBY = 'calling/SHOW_CALL_LOBBY';
|
const START_CALLING_LOBBY = 'calling/START_CALLING_LOBBY';
|
||||||
const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED';
|
const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED';
|
||||||
const CHANGE_IO_DEVICE_FULFILLED = 'calling/CHANGE_IO_DEVICE_FULFILLED';
|
const CHANGE_IO_DEVICE_FULFILLED = 'calling/CHANGE_IO_DEVICE_FULFILLED';
|
||||||
const CLOSE_NEED_PERMISSION_SCREEN = 'calling/CLOSE_NEED_PERMISSION_SCREEN';
|
const CLOSE_NEED_PERMISSION_SCREEN = 'calling/CLOSE_NEED_PERMISSION_SCREEN';
|
||||||
|
@ -344,9 +345,9 @@ type CancelIncomingGroupCallRingActionType = {
|
||||||
payload: CancelIncomingGroupCallRingType;
|
payload: CancelIncomingGroupCallRingType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CallLobbyActionType = {
|
type StartCallingLobbyActionType = {
|
||||||
type: 'calling/SHOW_CALL_LOBBY';
|
type: 'calling/START_CALLING_LOBBY';
|
||||||
payload: ShowCallLobbyType;
|
payload: StartCallingLobbyPayloadType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CallStateChangeFulfilledActionType = {
|
type CallStateChangeFulfilledActionType = {
|
||||||
|
@ -460,8 +461,8 @@ type SetOutgoingRingActionType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type ShowCallLobbyActionType = {
|
type ShowCallLobbyActionType = {
|
||||||
type: 'calling/SHOW_CALL_LOBBY';
|
type: 'calling/START_CALLING_LOBBY';
|
||||||
payload: ShowCallLobbyType;
|
payload: StartCallingLobbyPayloadType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StartDirectCallActionType = {
|
type StartDirectCallActionType = {
|
||||||
|
@ -493,7 +494,7 @@ export type CallingActionType =
|
||||||
| AcceptCallPendingActionType
|
| AcceptCallPendingActionType
|
||||||
| CancelCallActionType
|
| CancelCallActionType
|
||||||
| CancelIncomingGroupCallRingActionType
|
| CancelIncomingGroupCallRingActionType
|
||||||
| CallLobbyActionType
|
| StartCallingLobbyActionType
|
||||||
| CallStateChangeFulfilledActionType
|
| CallStateChangeFulfilledActionType
|
||||||
| ChangeIODeviceFulfilledActionType
|
| ChangeIODeviceFulfilledActionType
|
||||||
| CloseNeedPermissionScreenActionType
|
| CloseNeedPermissionScreenActionType
|
||||||
|
@ -1081,20 +1082,50 @@ function setOutgoingRing(payload: boolean): SetOutgoingRingActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function startCallingLobby(
|
function startCallingLobby({
|
||||||
payload: StartCallingLobbyType
|
conversationId,
|
||||||
): ThunkAction<void, RootStateType, unknown, never> {
|
isVideoCall,
|
||||||
return () => {
|
}: StartCallingLobbyType): ThunkAction<
|
||||||
calling.startCallingLobby(payload.conversationId, payload.isVideoCall);
|
void,
|
||||||
};
|
RootStateType,
|
||||||
}
|
unknown,
|
||||||
|
StartCallingLobbyActionType
|
||||||
|
> {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const conversation = getOwn(
|
||||||
|
state.conversations.conversationLookup,
|
||||||
|
conversationId
|
||||||
|
);
|
||||||
|
strictAssert(
|
||||||
|
conversation,
|
||||||
|
"startCallingLobby: can't start lobby without a conversation"
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: This action should be replaced with an action dispatched in the
|
// The group call device count is considered 0 for a direct call.
|
||||||
// `startCallingLobby` thunk.
|
const groupCall = getGroupCall(conversationId, state.calling);
|
||||||
function showCallLobby(payload: ShowCallLobbyType): CallLobbyActionType {
|
const groupCallDeviceCount =
|
||||||
return {
|
groupCall?.peekInfo.deviceCount ||
|
||||||
type: SHOW_CALL_LOBBY,
|
groupCall?.remoteParticipants.length ||
|
||||||
payload,
|
0;
|
||||||
|
|
||||||
|
const callLobbyData = await calling.startCallingLobby({
|
||||||
|
conversation,
|
||||||
|
hasLocalAudio: groupCallDeviceCount < 8,
|
||||||
|
hasLocalVideo: isVideoCall,
|
||||||
|
});
|
||||||
|
if (!callLobbyData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: START_CALLING_LOBBY,
|
||||||
|
payload: {
|
||||||
|
...callLobbyData,
|
||||||
|
conversationId,
|
||||||
|
isConversationTooBigToRing: isConversationTooBigToRing(conversation),
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1207,7 +1238,6 @@ export const actions = {
|
||||||
setPresenting,
|
setPresenting,
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
setOutgoingRing,
|
setOutgoingRing,
|
||||||
showCallLobby,
|
|
||||||
startCall,
|
startCall,
|
||||||
startCallingLobby,
|
startCallingLobby,
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
|
@ -1261,7 +1291,7 @@ export function reducer(
|
||||||
): CallingStateType {
|
): CallingStateType {
|
||||||
const { callsByConversation } = state;
|
const { callsByConversation } = state;
|
||||||
|
|
||||||
if (action.type === SHOW_CALL_LOBBY) {
|
if (action.type === START_CALLING_LOBBY) {
|
||||||
const { conversationId } = action.payload;
|
const { conversationId } = action.payload;
|
||||||
|
|
||||||
let call: DirectCallStateType | GroupCallStateType;
|
let call: DirectCallStateType | GroupCallStateType;
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
|
import { cloneDeep, noop } from 'lodash';
|
||||||
|
import type { StateType as RootStateType } from '../../../state/reducer';
|
||||||
import { reducer as rootReducer } from '../../../state/reducer';
|
import { reducer as rootReducer } from '../../../state/reducer';
|
||||||
import { noopAction } from '../../../state/ducks/noop';
|
import { noopAction } from '../../../state/ducks/noop';
|
||||||
import type {
|
import type {
|
||||||
|
@ -25,6 +27,8 @@ import {
|
||||||
} from '../../../types/Calling';
|
} from '../../../types/Calling';
|
||||||
import { UUID } from '../../../types/UUID';
|
import { UUID } from '../../../types/UUID';
|
||||||
import type { UUIDStringType } from '../../../types/UUID';
|
import type { UUIDStringType } from '../../../types/UUID';
|
||||||
|
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||||
|
import type { UnwrapPromise } from '../../../types/Util';
|
||||||
|
|
||||||
describe('calling duck', () => {
|
describe('calling duck', () => {
|
||||||
const stateWithDirectCall: CallingStateType = {
|
const stateWithDirectCall: CallingStateType = {
|
||||||
|
@ -1606,19 +1610,150 @@ describe('calling duck', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('showCallLobby', () => {
|
describe('startCallingLobby', () => {
|
||||||
const { showCallLobby } = actions;
|
const { startCallingLobby } = actions;
|
||||||
|
|
||||||
it('saves a direct call and makes it active', () => {
|
let rootState: RootStateType;
|
||||||
const result = reducer(
|
let startCallingLobbyStub: sinon.SinonStub;
|
||||||
getEmptyState(),
|
|
||||||
showCallLobby({
|
beforeEach(function beforeEach() {
|
||||||
callMode: CallMode.Direct,
|
startCallingLobbyStub = this.sandbox
|
||||||
|
.stub(callingService, 'startCallingLobby')
|
||||||
|
.resolves();
|
||||||
|
|
||||||
|
const emptyRootState = getEmptyRootState();
|
||||||
|
rootState = {
|
||||||
|
...emptyRootState,
|
||||||
|
conversations: {
|
||||||
|
...emptyRootState.conversations,
|
||||||
|
conversationLookup: {
|
||||||
|
'fake-conversation-id': getDefaultConversation(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('thunk', () => {
|
||||||
|
it('asks the calling service to start the lobby', async () => {
|
||||||
|
await startCallingLobby({
|
||||||
conversationId: 'fake-conversation-id',
|
conversationId: 'fake-conversation-id',
|
||||||
|
isVideoCall: true,
|
||||||
|
})(noop, () => rootState, null);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(startCallingLobbyStub);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requests audio by default', async () => {
|
||||||
|
await startCallingLobby({
|
||||||
|
conversationId: 'fake-conversation-id',
|
||||||
|
isVideoCall: true,
|
||||||
|
})(noop, () => rootState, null);
|
||||||
|
|
||||||
|
sinon.assert.calledWithMatch(startCallingLobbyStub, {
|
||||||
|
hasLocalAudio: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't request audio if the group call already has 8 devices", async () => {
|
||||||
|
await startCallingLobby({
|
||||||
|
conversationId: 'fake-conversation-id',
|
||||||
|
isVideoCall: true,
|
||||||
|
})(
|
||||||
|
noop,
|
||||||
|
() => {
|
||||||
|
const callingState = cloneDeep(stateWithGroupCall);
|
||||||
|
callingState.callsByConversation[
|
||||||
|
'fake-group-call-conversation-id'
|
||||||
|
].peekInfo.deviceCount = 8;
|
||||||
|
return { ...rootState, calling: callingState };
|
||||||
|
},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
sinon.assert.calledWithMatch(startCallingLobbyStub, {
|
||||||
|
hasLocalVideo: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requests video when starting a video call', async () => {
|
||||||
|
await startCallingLobby({
|
||||||
|
conversationId: 'fake-conversation-id',
|
||||||
|
isVideoCall: true,
|
||||||
|
})(noop, () => rootState, null);
|
||||||
|
|
||||||
|
sinon.assert.calledWithMatch(startCallingLobbyStub, {
|
||||||
|
hasLocalVideo: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't request video when not a video call", async () => {
|
||||||
|
await startCallingLobby({
|
||||||
|
conversationId: 'fake-conversation-id',
|
||||||
|
isVideoCall: false,
|
||||||
|
})(noop, () => rootState, null);
|
||||||
|
|
||||||
|
sinon.assert.calledWithMatch(startCallingLobbyStub, {
|
||||||
|
hasLocalVideo: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches an action if the calling lobby returns something', async () => {
|
||||||
|
startCallingLobbyStub.resolves({
|
||||||
|
callMode: CallMode.Direct,
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
const dispatch = sinon.stub();
|
||||||
|
|
||||||
|
await startCallingLobby({
|
||||||
|
conversationId: 'fake-conversation-id',
|
||||||
|
isVideoCall: true,
|
||||||
|
})(dispatch, () => rootState, null);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't dispatch an action if the calling lobby returns nothing", async () => {
|
||||||
|
const dispatch = sinon.stub();
|
||||||
|
|
||||||
|
await startCallingLobby({
|
||||||
|
conversationId: 'fake-conversation-id',
|
||||||
|
isVideoCall: true,
|
||||||
|
})(dispatch, () => rootState, null);
|
||||||
|
|
||||||
|
sinon.assert.notCalled(dispatch);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('action', () => {
|
||||||
|
const getState = async (
|
||||||
|
callingState: CallingStateType,
|
||||||
|
callingServiceResult: UnwrapPromise<
|
||||||
|
ReturnType<typeof callingService.startCallingLobby>
|
||||||
|
>,
|
||||||
|
conversationId = 'fake-conversation-id'
|
||||||
|
): Promise<CallingStateType> => {
|
||||||
|
startCallingLobbyStub.resolves(callingServiceResult);
|
||||||
|
|
||||||
|
const dispatch = sinon.stub();
|
||||||
|
|
||||||
|
await startCallingLobby({
|
||||||
|
conversationId,
|
||||||
|
isVideoCall: true,
|
||||||
|
})(dispatch, () => ({ ...rootState, calling: callingState }), null);
|
||||||
|
|
||||||
|
const action = dispatch.getCall(0).args[0];
|
||||||
|
|
||||||
|
return reducer(callingState, action);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('saves a direct call and makes it active', async () => {
|
||||||
|
const result = await getState(getEmptyState(), {
|
||||||
|
callMode: CallMode.Direct as const,
|
||||||
|
hasLocalAudio: true,
|
||||||
|
hasLocalVideo: true,
|
||||||
|
});
|
||||||
|
|
||||||
assert.deepEqual(result.callsByConversation['fake-conversation-id'], {
|
assert.deepEqual(result.callsByConversation['fake-conversation-id'], {
|
||||||
callMode: CallMode.Direct,
|
callMode: CallMode.Direct,
|
||||||
|
@ -1639,15 +1774,11 @@ describe('calling duck', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saves a group call and makes it active', () => {
|
it('saves a group call and makes it active', async () => {
|
||||||
const result = reducer(
|
const result = await getState(getEmptyState(), {
|
||||||
getEmptyState(),
|
|
||||||
showCallLobby({
|
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
conversationId: 'fake-conversation-id',
|
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
isConversationTooBigToRing: false,
|
|
||||||
connectionState: GroupCallConnectionState.Connected,
|
connectionState: GroupCallConnectionState.Connected,
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
|
@ -1668,8 +1799,7 @@ describe('calling duck', () => {
|
||||||
videoAspectRatio: 4 / 3,
|
videoAspectRatio: 4 / 3,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
|
||||||
assert.deepEqual(result.callsByConversation['fake-conversation-id'], {
|
assert.deepEqual(result.callsByConversation['fake-conversation-id'], {
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
|
@ -1702,24 +1832,18 @@ describe('calling duck', () => {
|
||||||
assert.isFalse(result.activeCallState?.outgoingRing);
|
assert.isFalse(result.activeCallState?.outgoingRing);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('chooses fallback peek info if none is sent and there is no existing call', () => {
|
it('chooses fallback peek info if none is sent and there is no existing call', async () => {
|
||||||
const result = reducer(
|
const result = await getState(getEmptyState(), {
|
||||||
getEmptyState(),
|
|
||||||
showCallLobby({
|
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
isConversationTooBigToRing: false,
|
|
||||||
connectionState: GroupCallConnectionState.Connected,
|
connectionState: GroupCallConnectionState.Connected,
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
peekInfo: undefined,
|
peekInfo: undefined,
|
||||||
remoteParticipants: [],
|
remoteParticipants: [],
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const call =
|
const call = result.callsByConversation['fake-conversation-id'];
|
||||||
result.callsByConversation['fake-group-call-conversation-id'];
|
|
||||||
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
||||||
uuids: [],
|
uuids: [],
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
|
@ -1727,15 +1851,11 @@ describe('calling duck', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't overwrite an existing group call's peek info if none was sent", () => {
|
it("doesn't overwrite an existing group call's peek info if none was sent", async () => {
|
||||||
const result = reducer(
|
const result = await getState(stateWithGroupCall, {
|
||||||
stateWithGroupCall,
|
|
||||||
showCallLobby({
|
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
isConversationTooBigToRing: false,
|
|
||||||
connectionState: GroupCallConnectionState.Connected,
|
connectionState: GroupCallConnectionState.Connected,
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
peekInfo: undefined,
|
peekInfo: undefined,
|
||||||
|
@ -1750,8 +1870,7 @@ describe('calling duck', () => {
|
||||||
videoAspectRatio: 4 / 3,
|
videoAspectRatio: 4 / 3,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const call =
|
const call =
|
||||||
result.callsByConversation['fake-group-call-conversation-id'];
|
result.callsByConversation['fake-group-call-conversation-id'];
|
||||||
|
@ -1764,15 +1883,23 @@ describe('calling duck', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can overwrite an existing group call's peek info", () => {
|
it("can overwrite an existing group call's peek info", async () => {
|
||||||
const result = reducer(
|
const state = {
|
||||||
stateWithGroupCall,
|
...getEmptyState(),
|
||||||
showCallLobby({
|
callsByConversation: {
|
||||||
|
'fake-conversation-id': {
|
||||||
|
...stateWithGroupCall.callsByConversation[
|
||||||
|
'fake-group-call-conversation-id'
|
||||||
|
],
|
||||||
|
conversationId: 'fake-conversation-id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await getState(state, {
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
isConversationTooBigToRing: false,
|
|
||||||
connectionState: GroupCallConnectionState.Connected,
|
connectionState: GroupCallConnectionState.Connected,
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
|
@ -1793,11 +1920,9 @@ describe('calling duck', () => {
|
||||||
videoAspectRatio: 4 / 3,
|
videoAspectRatio: 4 / 3,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const call =
|
const call = result.callsByConversation['fake-conversation-id'];
|
||||||
result.callsByConversation['fake-group-call-conversation-id'];
|
|
||||||
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
||||||
uuids: [differentCreatorUuid],
|
uuids: [differentCreatorUuid],
|
||||||
creatorUuid: differentCreatorUuid,
|
creatorUuid: differentCreatorUuid,
|
||||||
|
@ -1807,8 +1932,8 @@ describe('calling duck', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't overwrite an existing group call's ring state if it was set previously", () => {
|
it("doesn't overwrite an existing group call's ring state if it was set previously", async () => {
|
||||||
const result = reducer(
|
const result = await getState(
|
||||||
{
|
{
|
||||||
...stateWithGroupCall,
|
...stateWithGroupCall,
|
||||||
callsByConversation: {
|
callsByConversation: {
|
||||||
|
@ -1821,12 +1946,10 @@ describe('calling duck', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
showCallLobby({
|
{
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
isConversationTooBigToRing: false,
|
|
||||||
connectionState: GroupCallConnectionState.Connected,
|
connectionState: GroupCallConnectionState.Connected,
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
peekInfo: undefined,
|
peekInfo: undefined,
|
||||||
|
@ -1841,7 +1964,7 @@ describe('calling duck', () => {
|
||||||
videoAspectRatio: 4 / 3,
|
videoAspectRatio: 4 / 3,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
const call =
|
const call =
|
||||||
result.callsByConversation['fake-group-call-conversation-id'];
|
result.callsByConversation['fake-group-call-conversation-id'];
|
||||||
|
@ -1854,6 +1977,7 @@ describe('calling duck', () => {
|
||||||
assert.strictEqual(call.ringerUuid, ringerUuid);
|
assert.strictEqual(call.ringerUuid, ringerUuid);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('startCall', () => {
|
describe('startCall', () => {
|
||||||
const { startCall } = actions;
|
const { startCall } = actions;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -657,7 +657,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
|
|
||||||
async onOutgoingVideoCallInConversation(): Promise<void> {
|
async onOutgoingVideoCallInConversation(): Promise<void> {
|
||||||
log.info('onOutgoingVideoCallInConversation: about to start a video call');
|
log.info('onOutgoingVideoCallInConversation: about to start a video call');
|
||||||
const isVideoCall = true;
|
|
||||||
|
|
||||||
if (this.model.get('announcementsOnly') && !this.model.areWeAdmin()) {
|
if (this.model.get('announcementsOnly') && !this.model.areWeAdmin()) {
|
||||||
showToast(ToastCannotStartGroupCall);
|
showToast(ToastCannotStartGroupCall);
|
||||||
|
@ -668,10 +667,10 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
log.info(
|
log.info(
|
||||||
'onOutgoingVideoCallInConversation: call is deemed "safe". Making call'
|
'onOutgoingVideoCallInConversation: call is deemed "safe". Making call'
|
||||||
);
|
);
|
||||||
await window.Signal.Services.calling.startCallingLobby(
|
window.reduxActions.calling.startCallingLobby({
|
||||||
this.model.id,
|
conversationId: this.model.id,
|
||||||
isVideoCall
|
isVideoCall: true,
|
||||||
);
|
});
|
||||||
log.info('onOutgoingVideoCallInConversation: started the call');
|
log.info('onOutgoingVideoCallInConversation: started the call');
|
||||||
} else {
|
} else {
|
||||||
log.info(
|
log.info(
|
||||||
|
@ -683,16 +682,14 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
async onOutgoingAudioCallInConversation(): Promise<void> {
|
async onOutgoingAudioCallInConversation(): Promise<void> {
|
||||||
log.info('onOutgoingAudioCallInConversation: about to start an audio call');
|
log.info('onOutgoingAudioCallInConversation: about to start an audio call');
|
||||||
|
|
||||||
const isVideoCall = false;
|
|
||||||
|
|
||||||
if (await this.isCallSafe()) {
|
if (await this.isCallSafe()) {
|
||||||
log.info(
|
log.info(
|
||||||
'onOutgoingAudioCallInConversation: call is deemed "safe". Making call'
|
'onOutgoingAudioCallInConversation: call is deemed "safe". Making call'
|
||||||
);
|
);
|
||||||
await window.Signal.Services.calling.startCallingLobby(
|
window.reduxActions.calling.startCallingLobby({
|
||||||
this.model.id,
|
conversationId: this.model.id,
|
||||||
isVideoCall
|
isVideoCall: false,
|
||||||
);
|
});
|
||||||
log.info('onOutgoingAudioCallInConversation: started the call');
|
log.info('onOutgoingAudioCallInConversation: started the call');
|
||||||
} else {
|
} else {
|
||||||
log.info(
|
log.info(
|
||||||
|
|
Loading…
Reference in a new issue