// Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback } from 'react'; import { CallNeedPermissionScreen } from './CallNeedPermissionScreen'; import { CallScreen } from './CallScreen'; import { CallingLobby } from './CallingLobby'; import { CallingParticipantsList } from './CallingParticipantsList'; import { CallingPip } from './CallingPip'; import { IncomingCallBar } from './IncomingCallBar'; import { CallEndedReason, CallMode, CallState, GroupCallJoinState, VideoFrameSource, } from '../types/Calling'; import { ConversationType } from '../state/ducks/conversations'; import { AcceptCallType, ActiveCallType, CancelCallType, DeclineCallType, DirectCallStateType, HangUpType, SetLocalAudioType, SetLocalPreviewType, SetLocalVideoType, SetRendererCanvasType, StartCallType, } from '../state/ducks/calling'; import { LocalizerType } from '../types/Util'; import { ColorType } from '../types/Colors'; import { missingCaseError } from '../util/missingCaseError'; export interface PropsType { activeCall?: ActiveCallType; availableCameras: Array; cancelCall: (_: CancelCallType) => void; closeNeedPermissionScreen: () => void; getGroupCallVideoFrameSource: ( conversationId: string, demuxId: number ) => VideoFrameSource; incomingCall?: { call: DirectCallStateType; conversation: ConversationType; }; renderDeviceSelection: () => JSX.Element; startCall: (payload: StartCallType) => void; toggleParticipants: () => void; acceptCall: (_: AcceptCallType) => void; declineCall: (_: DeclineCallType) => void; i18n: LocalizerType; me: { avatarPath?: string; color?: ColorType; name?: string; phoneNumber?: string; profileName?: string; title: string; }; setLocalAudio: (_: SetLocalAudioType) => void; setLocalVideo: (_: SetLocalVideoType) => void; setLocalPreview: (_: SetLocalPreviewType) => void; setRendererCanvas: (_: SetRendererCanvasType) => void; hangUp: (_: HangUpType) => void; togglePip: () => void; toggleSettings: () => void; } interface ActiveCallManagerPropsType extends PropsType { activeCall: ActiveCallType; } const ActiveCallManager: React.FC = ({ activeCall, availableCameras, cancelCall, closeNeedPermissionScreen, hangUp, i18n, getGroupCallVideoFrameSource, me, renderDeviceSelection, setLocalAudio, setLocalPreview, setLocalVideo, setRendererCanvas, startCall, toggleParticipants, togglePip, toggleSettings, }) => { const { call, activeCallState, conversation, groupCallParticipants, } = activeCall; const { hasLocalAudio, hasLocalVideo, joinedAt, pip, settingsDialogOpen, showParticipantsList, } = activeCallState; const cancelActiveCall = useCallback(() => { cancelCall({ conversationId: conversation.id }); }, [cancelCall, conversation.id]); const joinActiveCall = useCallback(() => { startCall({ callMode: call.callMode, conversationId: conversation.id, hasLocalAudio, hasLocalVideo, }); }, [startCall, call.callMode, conversation.id, hasLocalAudio, hasLocalVideo]); const getGroupCallVideoFrameSourceForActiveCall = useCallback( (demuxId: number) => { return getGroupCallVideoFrameSource(conversation.id, demuxId); }, [getGroupCallVideoFrameSource, conversation.id] ); let showCallLobby: boolean; switch (call.callMode) { case CallMode.Direct: { const { callState, callEndedReason } = call; const ended = callState === CallState.Ended; if ( ended && callEndedReason === CallEndedReason.RemoteHangupNeedPermission ) { return ( ); } showCallLobby = !callState; break; } case CallMode.Group: { showCallLobby = call.joinState === GroupCallJoinState.NotJoined; break; } default: throw missingCaseError(call); } if (showCallLobby) { const participantNames = groupCallParticipants.map(participant => participant.isSelf ? i18n('you') : participant.firstName || participant.title ); return ( <> {settingsDialogOpen && renderDeviceSelection()} {showParticipantsList && call.callMode === CallMode.Group ? ( ) : null} ); } if (pip) { return ( ); } return ( <> {settingsDialogOpen && renderDeviceSelection()} {showParticipantsList && call.callMode === CallMode.Group ? ( ) : null} ); }; export const CallManager: React.FC = props => { const { activeCall, incomingCall, acceptCall, declineCall, i18n } = props; if (activeCall) { // `props` should logically have an `activeCall` at this point, but TypeScript can't // figure that out, so we pass it in again. return ; } // In the future, we may want to show the incoming call bar when a call is active. if (incomingCall) { return ( ); } return null; };