Add screensharing behind a feature flag
This commit is contained in:
parent
7c7f7ee5a0
commit
ceffc2380c
49 changed files with 2044 additions and 164 deletions
|
@ -1,10 +1,16 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { CallEndedReason } from 'ringrtc';
|
||||
import {
|
||||
hasScreenCapturePermission,
|
||||
openSystemPreferences,
|
||||
} from 'mac-screen-capture-permissions';
|
||||
import { has, omit } from 'lodash';
|
||||
import { getOwn } from '../../util/getOwn';
|
||||
import { getPlatform } from '../selectors/user';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { notify } from '../../services/notify';
|
||||
import { calling } from '../../services/calling';
|
||||
|
@ -18,6 +24,8 @@ import {
|
|||
GroupCallJoinState,
|
||||
GroupCallVideoRequest,
|
||||
MediaDeviceSettings,
|
||||
PresentedSource,
|
||||
PresentableSource,
|
||||
} from '../../types/Calling';
|
||||
import { callingTones } from '../../util/callingTones';
|
||||
import { requestCameraPermissions } from '../../util/callingPermissions';
|
||||
|
@ -43,6 +51,8 @@ export type GroupCallParticipantInfoType = {
|
|||
demuxId: number;
|
||||
hasRemoteAudio: boolean;
|
||||
hasRemoteVideo: boolean;
|
||||
presenting: boolean;
|
||||
sharingScreen: boolean;
|
||||
speakerTime?: number;
|
||||
videoAspectRatio: number;
|
||||
};
|
||||
|
@ -53,6 +63,7 @@ export type DirectCallStateType = {
|
|||
callState?: CallState;
|
||||
callEndedReason?: CallEndedReason;
|
||||
isIncoming: boolean;
|
||||
isSharingScreen?: boolean;
|
||||
isVideoCall: boolean;
|
||||
hasRemoteVideo?: boolean;
|
||||
};
|
||||
|
@ -73,8 +84,11 @@ export type ActiveCallStateType = {
|
|||
isInSpeakerView: boolean;
|
||||
joinedAt?: number;
|
||||
pip: boolean;
|
||||
presentingSource?: PresentedSource;
|
||||
presentingSourcesAvailable?: Array<PresentableSource>;
|
||||
safetyNumberChangedUuids: Array<string>;
|
||||
settingsDialogOpen: boolean;
|
||||
showNeedsScreenRecordingPermissionsWarning?: boolean;
|
||||
showParticipantsList: boolean;
|
||||
};
|
||||
|
||||
|
@ -160,6 +174,11 @@ export type RemoteVideoChangeType = {
|
|||
hasVideo: boolean;
|
||||
};
|
||||
|
||||
type RemoteSharingScreenChangeType = {
|
||||
conversationId: string;
|
||||
isSharingScreen: boolean;
|
||||
};
|
||||
|
||||
export type SetLocalAudioType = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
@ -236,10 +255,15 @@ const OUTGOING_CALL = 'calling/OUTGOING_CALL';
|
|||
const PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED =
|
||||
'calling/PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED';
|
||||
const REFRESH_IO_DEVICES = 'calling/REFRESH_IO_DEVICES';
|
||||
const REMOTE_SHARING_SCREEN_CHANGE = 'calling/REMOTE_SHARING_SCREEN_CHANGE';
|
||||
const REMOTE_VIDEO_CHANGE = 'calling/REMOTE_VIDEO_CHANGE';
|
||||
const RETURN_TO_ACTIVE_CALL = 'calling/RETURN_TO_ACTIVE_CALL';
|
||||
const SET_LOCAL_AUDIO_FULFILLED = 'calling/SET_LOCAL_AUDIO_FULFILLED';
|
||||
const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED';
|
||||
const SET_PRESENTING = 'calling/SET_PRESENTING';
|
||||
const SET_PRESENTING_SOURCES = 'calling/SET_PRESENTING_SOURCES';
|
||||
const TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS =
|
||||
'calling/TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS';
|
||||
const START_DIRECT_CALL = 'calling/START_DIRECT_CALL';
|
||||
const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
|
||||
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
|
||||
|
@ -326,6 +350,11 @@ type RefreshIODevicesActionType = {
|
|||
payload: MediaDeviceSettings;
|
||||
};
|
||||
|
||||
type RemoteSharingScreenChangeActionType = {
|
||||
type: 'calling/REMOTE_SHARING_SCREEN_CHANGE';
|
||||
payload: RemoteSharingScreenChangeType;
|
||||
};
|
||||
|
||||
type RemoteVideoChangeActionType = {
|
||||
type: 'calling/REMOTE_VIDEO_CHANGE';
|
||||
payload: RemoteVideoChangeType;
|
||||
|
@ -345,6 +374,16 @@ type SetLocalVideoFulfilledActionType = {
|
|||
payload: SetLocalVideoType;
|
||||
};
|
||||
|
||||
type SetPresentingFulfilledActionType = {
|
||||
type: 'calling/SET_PRESENTING';
|
||||
payload?: PresentedSource;
|
||||
};
|
||||
|
||||
type SetPresentingSourcesActionType = {
|
||||
type: 'calling/SET_PRESENTING_SOURCES';
|
||||
payload: Array<PresentableSource>;
|
||||
};
|
||||
|
||||
type ShowCallLobbyActionType = {
|
||||
type: 'calling/SHOW_CALL_LOBBY';
|
||||
payload: ShowCallLobbyType;
|
||||
|
@ -355,6 +394,10 @@ type StartDirectCallActionType = {
|
|||
payload: StartDirectCallType;
|
||||
};
|
||||
|
||||
type ToggleNeedsScreenRecordingPermissionsActionType = {
|
||||
type: 'calling/TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS';
|
||||
};
|
||||
|
||||
type ToggleParticipantsActionType = {
|
||||
type: 'calling/TOGGLE_PARTICIPANTS';
|
||||
};
|
||||
|
@ -387,14 +430,18 @@ export type CallingActionType =
|
|||
| OutgoingCallActionType
|
||||
| PeekNotConnectedGroupCallFulfilledActionType
|
||||
| RefreshIODevicesActionType
|
||||
| RemoteSharingScreenChangeActionType
|
||||
| RemoteVideoChangeActionType
|
||||
| ReturnToActiveCallActionType
|
||||
| SetLocalAudioActionType
|
||||
| SetLocalVideoFulfilledActionType
|
||||
| SetPresentingSourcesActionType
|
||||
| ShowCallLobbyActionType
|
||||
| StartDirectCallActionType
|
||||
| ToggleNeedsScreenRecordingPermissionsActionType
|
||||
| ToggleParticipantsActionType
|
||||
| TogglePipActionType
|
||||
| SetPresentingFulfilledActionType
|
||||
| ToggleSettingsActionType
|
||||
| ToggleSpeakerViewActionType;
|
||||
|
||||
|
@ -438,6 +485,7 @@ function callStateChange(
|
|||
}
|
||||
if (callState === CallState.Ended) {
|
||||
await callingTones.playEndCall();
|
||||
ipcRenderer.send('close-screen-share-controller');
|
||||
}
|
||||
|
||||
dispatch({
|
||||
|
@ -519,10 +567,59 @@ function declineCall(payload: DeclineCallType): DeclineCallActionType {
|
|||
};
|
||||
}
|
||||
|
||||
function getPresentingSources(): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
unknown,
|
||||
| SetPresentingSourcesActionType
|
||||
| ToggleNeedsScreenRecordingPermissionsActionType
|
||||
> {
|
||||
return async (dispatch, getState) => {
|
||||
// We check if the user has permissions first before calling desktopCapturer
|
||||
// Next we call getPresentingSources so that one gets the prompt for permissions,
|
||||
// if necessary.
|
||||
// Finally, we have the if statement which shows the modal, if needed.
|
||||
// It is in this exact order so that during first-time-use one will be
|
||||
// prompted for permissions and if they so happen to deny we can still
|
||||
// capture that state correctly.
|
||||
const platform = getPlatform(getState());
|
||||
const needsPermission =
|
||||
platform === 'darwin' && !hasScreenCapturePermission();
|
||||
|
||||
const sources = await calling.getPresentingSources();
|
||||
|
||||
if (needsPermission) {
|
||||
dispatch({
|
||||
type: TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_PRESENTING_SOURCES,
|
||||
payload: sources,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function groupCallStateChange(
|
||||
payload: GroupCallStateChangeArgumentType
|
||||
): ThunkAction<void, RootStateType, unknown, GroupCallStateChangeActionType> {
|
||||
return (dispatch, getState) => {
|
||||
return async (dispatch, getState) => {
|
||||
let didSomeoneStartPresenting: boolean;
|
||||
const activeCall = getActiveCall(getState().calling);
|
||||
if (activeCall?.callMode === CallMode.Group) {
|
||||
const wasSomeonePresenting = activeCall.remoteParticipants.some(
|
||||
participant => participant.presenting
|
||||
);
|
||||
const isSomeonePresenting = payload.remoteParticipants.some(
|
||||
participant => participant.presenting
|
||||
);
|
||||
didSomeoneStartPresenting = !wasSomeonePresenting && isSomeonePresenting;
|
||||
} else {
|
||||
didSomeoneStartPresenting = false;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: GROUP_CALL_STATE_CHANGE,
|
||||
payload: {
|
||||
|
@ -530,6 +627,10 @@ function groupCallStateChange(
|
|||
ourUuid: getState().user.ourUuid,
|
||||
},
|
||||
});
|
||||
|
||||
if (didSomeoneStartPresenting) {
|
||||
callingTones.someonePresenting();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -601,6 +702,17 @@ function receiveIncomingCall(
|
|||
};
|
||||
}
|
||||
|
||||
function openSystemPreferencesAction(): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
unknown,
|
||||
never
|
||||
> {
|
||||
return () => {
|
||||
openSystemPreferences();
|
||||
};
|
||||
}
|
||||
|
||||
function outgoingCall(payload: StartDirectCallType): OutgoingCallActionType {
|
||||
callingTones.playRingtone();
|
||||
|
||||
|
@ -694,6 +806,15 @@ function refreshIODevices(
|
|||
};
|
||||
}
|
||||
|
||||
function remoteSharingScreenChange(
|
||||
payload: RemoteSharingScreenChangeType
|
||||
): RemoteSharingScreenChangeActionType {
|
||||
return {
|
||||
type: REMOTE_SHARING_SCREEN_CHANGE,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
function remoteVideoChange(
|
||||
payload: RemoteVideoChangeType
|
||||
): RemoteVideoChangeActionType {
|
||||
|
@ -764,7 +885,7 @@ function setLocalVideo(
|
|||
} else if (payload.enabled) {
|
||||
calling.enableLocalCamera();
|
||||
} else {
|
||||
calling.disableLocalCamera();
|
||||
calling.disableLocalVideo();
|
||||
}
|
||||
({ enabled } = payload);
|
||||
} else {
|
||||
|
@ -797,6 +918,35 @@ function setGroupCallVideoRequest(
|
|||
};
|
||||
}
|
||||
|
||||
function setPresenting(
|
||||
sourceToPresent?: PresentedSource
|
||||
): ThunkAction<void, RootStateType, unknown, SetPresentingFulfilledActionType> {
|
||||
return async (dispatch, getState) => {
|
||||
const callingState = getState().calling;
|
||||
const { activeCallState } = callingState;
|
||||
const activeCall = getActiveCall(callingState);
|
||||
if (!activeCall || !activeCallState) {
|
||||
window.log.warn('Trying to present when no call is active');
|
||||
return;
|
||||
}
|
||||
|
||||
calling.setPresenting(
|
||||
activeCall.conversationId,
|
||||
activeCallState.hasLocalVideo,
|
||||
sourceToPresent
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: SET_PRESENTING,
|
||||
payload: sourceToPresent,
|
||||
});
|
||||
|
||||
if (sourceToPresent) {
|
||||
await callingTones.someonePresenting();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function startCallingLobby(
|
||||
payload: StartCallingLobbyType
|
||||
): ThunkAction<void, RootStateType, unknown, never> {
|
||||
|
@ -857,6 +1007,12 @@ function togglePip(): TogglePipActionType {
|
|||
};
|
||||
}
|
||||
|
||||
function toggleScreenRecordingPermissionsDialog(): ToggleNeedsScreenRecordingPermissionsActionType {
|
||||
return {
|
||||
type: TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS,
|
||||
};
|
||||
}
|
||||
|
||||
function toggleSettings(): ToggleSettingsActionType {
|
||||
return {
|
||||
type: TOGGLE_SETTINGS,
|
||||
|
@ -871,31 +1027,36 @@ function toggleSpeakerView(): ToggleSpeakerViewActionType {
|
|||
|
||||
export const actions = {
|
||||
acceptCall,
|
||||
cancelCall,
|
||||
callStateChange,
|
||||
cancelCall,
|
||||
changeIODevice,
|
||||
closeNeedPermissionScreen,
|
||||
declineCall,
|
||||
getPresentingSources,
|
||||
groupCallStateChange,
|
||||
hangUp,
|
||||
keyChanged,
|
||||
keyChangeOk,
|
||||
receiveIncomingCall,
|
||||
keyChanged,
|
||||
openSystemPreferencesAction,
|
||||
outgoingCall,
|
||||
peekNotConnectedGroupCall,
|
||||
receiveIncomingCall,
|
||||
refreshIODevices,
|
||||
remoteSharingScreenChange,
|
||||
remoteVideoChange,
|
||||
returnToActiveCall,
|
||||
setLocalPreview,
|
||||
setRendererCanvas,
|
||||
setLocalAudio,
|
||||
setLocalVideo,
|
||||
setGroupCallVideoRequest,
|
||||
startCallingLobby,
|
||||
setLocalAudio,
|
||||
setLocalPreview,
|
||||
setLocalVideo,
|
||||
setPresenting,
|
||||
setRendererCanvas,
|
||||
showCallLobby,
|
||||
startCall,
|
||||
startCallingLobby,
|
||||
toggleParticipants,
|
||||
togglePip,
|
||||
toggleScreenRecordingPermissionsDialog,
|
||||
toggleSettings,
|
||||
toggleSpeakerView,
|
||||
};
|
||||
|
@ -1270,6 +1431,26 @@ export function reducer(
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === REMOTE_SHARING_SCREEN_CHANGE) {
|
||||
const { conversationId, isSharingScreen } = action.payload;
|
||||
const call = getOwn(state.callsByConversation, conversationId);
|
||||
if (call?.callMode !== CallMode.Direct) {
|
||||
window.log.warn('Cannot update remote video for a non-direct call');
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
callsByConversation: {
|
||||
...callsByConversation,
|
||||
[conversationId]: {
|
||||
...call,
|
||||
isSharingScreen,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === REMOTE_VIDEO_CHANGE) {
|
||||
const { conversationId, hasVideo } = action.payload;
|
||||
const call = getOwn(state.callsByConversation, conversationId);
|
||||
|
@ -1427,6 +1608,59 @@ export function reducer(
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === SET_PRESENTING) {
|
||||
const { activeCallState } = state;
|
||||
if (!activeCallState) {
|
||||
window.log.warn('Cannot toggle presenting when there is no active call');
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeCallState: {
|
||||
...activeCallState,
|
||||
presentingSource: action.payload,
|
||||
presentingSourcesAvailable: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === SET_PRESENTING_SOURCES) {
|
||||
const { activeCallState } = state;
|
||||
if (!activeCallState) {
|
||||
window.log.warn(
|
||||
'Cannot set presenting sources when there is no active call'
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeCallState: {
|
||||
...activeCallState,
|
||||
presentingSourcesAvailable: action.payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS) {
|
||||
const { activeCallState } = state;
|
||||
if (!activeCallState) {
|
||||
window.log.warn(
|
||||
'Cannot set presenting sources when there is no active call'
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeCallState: {
|
||||
...activeCallState,
|
||||
showNeedsScreenRecordingPermissionsWarning: !activeCallState.showNeedsScreenRecordingPermissionsWarning,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === TOGGLE_SPEAKER_VIEW) {
|
||||
const { activeCallState } = state;
|
||||
if (!activeCallState) {
|
||||
|
|
|
@ -78,7 +78,12 @@ const mapStateToActiveCallProp = (
|
|||
isInSpeakerView: activeCallState.isInSpeakerView,
|
||||
joinedAt: activeCallState.joinedAt,
|
||||
pip: activeCallState.pip,
|
||||
presentingSource: activeCallState.presentingSource,
|
||||
presentingSourcesAvailable: activeCallState.presentingSourcesAvailable,
|
||||
settingsDialogOpen: activeCallState.settingsDialogOpen,
|
||||
showNeedsScreenRecordingPermissionsWarning: Boolean(
|
||||
activeCallState.showNeedsScreenRecordingPermissionsWarning
|
||||
),
|
||||
showParticipantsList: activeCallState.showParticipantsList,
|
||||
};
|
||||
|
||||
|
@ -93,6 +98,9 @@ const mapStateToActiveCallProp = (
|
|||
remoteParticipants: [
|
||||
{
|
||||
hasRemoteVideo: Boolean(call.hasRemoteVideo),
|
||||
presenting: Boolean(call.isSharingScreen),
|
||||
title: conversation.title,
|
||||
uuid: conversation.uuid,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -119,6 +127,8 @@ const mapStateToActiveCallProp = (
|
|||
demuxId: remoteParticipant.demuxId,
|
||||
hasRemoteAudio: remoteParticipant.hasRemoteAudio,
|
||||
hasRemoteVideo: remoteParticipant.hasRemoteVideo,
|
||||
presenting: remoteParticipant.presenting,
|
||||
sharingScreen: remoteParticipant.sharingScreen,
|
||||
speakerTime: remoteParticipant.speakerTime,
|
||||
videoAspectRatio: remoteParticipant.videoAspectRatio,
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue