signal-desktop/ts/state/smart/CallManager.tsx

215 lines
6.4 KiB
TypeScript
Raw Normal View History

2021-01-08 22:57:54 +00:00
// Copyright 2020-2021 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
2020-08-27 00:03:42 +00:00
import React from 'react';
2020-06-04 18:16:19 +00:00
import { connect } from 'react-redux';
2020-12-02 18:14:03 +00:00
import { memoize } from 'lodash';
2020-06-04 18:16:19 +00:00
import { mapDispatchToProps } from '../actions';
import { CallManager } from '../../components/CallManager';
2020-11-13 19:57:55 +00:00
import { calling as callingService } from '../../services/calling';
2020-12-02 18:14:03 +00:00
import { getUserUuid, getIntl } from '../selectors/user';
import { getMe, getConversationSelector } from '../selectors/conversations';
2020-12-02 18:14:03 +00:00
import { getActiveCall } from '../ducks/calling';
import { ConversationType } from '../ducks/conversations';
2020-11-13 19:57:55 +00:00
import { getIncomingCall } from '../selectors/calling';
import {
2020-12-02 18:14:03 +00:00
ActiveCallType,
CallMode,
GroupCallRemoteParticipantType,
} from '../../types/Calling';
2020-06-04 18:16:19 +00:00
import { StateType } from '../reducer';
2020-12-02 18:14:03 +00:00
import { missingCaseError } from '../../util/missingCaseError';
2020-08-27 00:03:42 +00:00
import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
import {
SmartSafetyNumberViewer,
Props as SafetyNumberViewerProps,
} from './SafetyNumberViewer';
2020-08-27 00:03:42 +00:00
function renderDeviceSelection(): JSX.Element {
return <SmartCallingDeviceSelection />;
2020-08-27 00:03:42 +00:00
}
function renderSafetyNumberViewer(props: SafetyNumberViewerProps): JSX.Element {
return <SmartSafetyNumberViewer {...props} />;
}
2020-11-13 19:57:55 +00:00
const getGroupCallVideoFrameSource = callingService.getGroupCallVideoFrameSource.bind(
callingService
);
2020-12-02 18:14:03 +00:00
const mapStateToActiveCallProp = (
state: StateType
): undefined | ActiveCallType => {
2020-10-08 01:25:33 +00:00
const { calling } = state;
const { activeCallState } = calling;
if (!activeCallState) {
return undefined;
}
const call = getActiveCall(calling);
if (!call) {
window.log.error(
'There was an active call state but no corresponding call'
);
return undefined;
}
2020-11-17 15:07:53 +00:00
const conversationSelector = getConversationSelector(state);
const conversation = conversationSelector(activeCallState.conversationId);
if (!conversation) {
window.log.error('The active call has no corresponding conversation');
return undefined;
}
2020-12-02 18:14:03 +00:00
const conversationSelectorByUuid = memoize<
(uuid: string) => undefined | ConversationType
>(uuid => {
const conversationId = window.ConversationController.ensureContactIds({
uuid,
});
2020-12-02 18:14:03 +00:00
return conversationId ? conversationSelector(conversationId) : undefined;
});
2020-12-02 18:14:03 +00:00
const baseResult = {
conversation,
hasLocalAudio: activeCallState.hasLocalAudio,
hasLocalVideo: activeCallState.hasLocalVideo,
2021-01-08 22:57:54 +00:00
isInSpeakerView: activeCallState.isInSpeakerView,
2020-12-02 18:14:03 +00:00
joinedAt: activeCallState.joinedAt,
pip: activeCallState.pip,
settingsDialogOpen: activeCallState.settingsDialogOpen,
showParticipantsList: activeCallState.showParticipantsList,
};
2020-11-17 15:07:53 +00:00
2020-12-02 18:14:03 +00:00
switch (call.callMode) {
case CallMode.Direct:
return {
...baseResult,
callEndedReason: call.callEndedReason,
callMode: CallMode.Direct,
callState: call.callState,
peekedParticipants: [],
remoteParticipants: [
{
hasRemoteVideo: Boolean(call.hasRemoteVideo),
},
],
};
case CallMode.Group: {
const conversationsWithSafetyNumberChanges: Array<ConversationType> = [];
2020-12-02 18:14:03 +00:00
const remoteParticipants: Array<GroupCallRemoteParticipantType> = [];
const peekedParticipants: Array<ConversationType> = [];
2020-12-02 18:14:03 +00:00
for (let i = 0; i < call.remoteParticipants.length; i += 1) {
const remoteParticipant = call.remoteParticipants[i];
const remoteConversation = conversationSelectorByUuid(
remoteParticipant.uuid
);
2020-11-17 15:07:53 +00:00
if (!remoteConversation) {
window.log.error(
'Remote participant has no corresponding conversation'
);
2020-12-02 18:14:03 +00:00
continue;
2020-11-17 15:07:53 +00:00
}
2020-12-02 18:14:03 +00:00
remoteParticipants.push({
...remoteConversation,
demuxId: remoteParticipant.demuxId,
2020-11-17 15:07:53 +00:00
hasRemoteAudio: remoteParticipant.hasRemoteAudio,
hasRemoteVideo: remoteParticipant.hasRemoteVideo,
speakerTime: remoteParticipant.speakerTime,
videoAspectRatio: remoteParticipant.videoAspectRatio,
2020-11-17 15:07:53 +00:00
});
}
for (
let i = 0;
i < activeCallState.safetyNumberChangedUuids.length;
i += 1
) {
const uuid = activeCallState.safetyNumberChangedUuids[i];
const remoteConversation = conversationSelectorByUuid(uuid);
if (!remoteConversation) {
window.log.error(
'Remote participant has no corresponding conversation'
);
continue;
}
conversationsWithSafetyNumberChanges.push(remoteConversation);
}
2020-12-02 18:14:03 +00:00
for (let i = 0; i < call.peekInfo.uuids.length; i += 1) {
const peekedParticipantUuid = call.peekInfo.uuids[i];
2020-11-17 15:07:53 +00:00
2020-12-02 18:14:03 +00:00
const peekedConversation = conversationSelectorByUuid(
peekedParticipantUuid
);
if (!peekedConversation) {
window.log.error(
'Remote participant has no corresponding conversation'
);
continue;
}
peekedParticipants.push(peekedConversation);
2020-12-02 18:14:03 +00:00
}
return {
...baseResult,
callMode: CallMode.Group,
connectionState: call.connectionState,
conversationsWithSafetyNumberChanges,
2020-12-02 18:14:03 +00:00
deviceCount: call.peekInfo.deviceCount,
joinState: call.joinState,
maxDevices: call.peekInfo.maxDevices,
peekedParticipants,
remoteParticipants,
};
}
default:
throw missingCaseError(call);
}
2020-06-04 18:16:19 +00:00
};
const mapStateToIncomingCallProp = (state: StateType) => {
const call = getIncomingCall(state);
if (!call) {
return undefined;
}
const conversation = getConversationSelector(state)(call.conversationId);
if (!conversation) {
window.log.error('The incoming call has no corresponding conversation');
return undefined;
}
return {
call,
conversation,
};
};
const mapStateToProps = (state: StateType) => ({
activeCall: mapStateToActiveCallProp(state),
availableCameras: state.calling.availableCameras,
2020-11-13 19:57:55 +00:00
getGroupCallVideoFrameSource,
i18n: getIntl(state),
incomingCall: mapStateToIncomingCallProp(state),
2020-12-02 18:14:03 +00:00
me: {
...getMe(state),
// `getMe` returns a `ConversationType` which might not have a UUID, at least
// according to the type. This ensures one is set.
uuid: getUserUuid(state),
},
renderDeviceSelection,
renderSafetyNumberViewer,
});
2020-06-04 18:16:19 +00:00
const smart = connect(mapStateToProps, mapDispatchToProps);
export const SmartCallManager = smart(CallManager);