Restore call view mode after presentation end
This commit is contained in:
parent
9e1528fa24
commit
80c90540f6
13 changed files with 289 additions and 51 deletions
|
@ -12,6 +12,7 @@ import {
|
||||||
CallEndedReason,
|
CallEndedReason,
|
||||||
CallMode,
|
CallMode,
|
||||||
CallState,
|
CallState,
|
||||||
|
CallViewMode,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
} from '../types/Calling';
|
} from '../types/Calling';
|
||||||
|
@ -51,7 +52,11 @@ const getCommonActiveCallData = () => ({
|
||||||
hasLocalAudio: boolean('hasLocalAudio', true),
|
hasLocalAudio: boolean('hasLocalAudio', true),
|
||||||
hasLocalVideo: boolean('hasLocalVideo', false),
|
hasLocalVideo: boolean('hasLocalVideo', false),
|
||||||
localAudioLevel: select('localAudioLevel', [0, 0.5, 1], 0),
|
localAudioLevel: select('localAudioLevel', [0, 0.5, 1], 0),
|
||||||
isInSpeakerView: boolean('isInSpeakerView', false),
|
viewMode: select(
|
||||||
|
'viewMode',
|
||||||
|
[CallViewMode.Grid, CallViewMode.Presentation, CallViewMode.Speaker],
|
||||||
|
CallViewMode.Grid
|
||||||
|
),
|
||||||
outgoingRing: boolean('outgoingRing', true),
|
outgoingRing: boolean('outgoingRing', true),
|
||||||
pip: boolean('pip', false),
|
pip: boolean('pip', false),
|
||||||
settingsDialogOpen: boolean('settingsDialogOpen', false),
|
settingsDialogOpen: boolean('settingsDialogOpen', false),
|
||||||
|
@ -101,6 +106,8 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
setOutgoingRing: action('set-outgoing-ring'),
|
setOutgoingRing: action('set-outgoing-ring'),
|
||||||
startCall: action('start-call'),
|
startCall: action('start-call'),
|
||||||
stopRingtone: action('stop-ringtone'),
|
stopRingtone: action('stop-ringtone'),
|
||||||
|
switchToPresentationView: action('switch-to-presentation-view'),
|
||||||
|
switchFromPresentationView: action('switch-from-presentation-view'),
|
||||||
theme: ThemeType.light,
|
theme: ThemeType.light,
|
||||||
toggleParticipants: action('toggle-participants'),
|
toggleParticipants: action('toggle-participants'),
|
||||||
togglePip: action('toggle-pip'),
|
togglePip: action('toggle-pip'),
|
||||||
|
|
|
@ -91,6 +91,8 @@ export type PropsType = {
|
||||||
setPresenting: (_?: PresentedSource) => void;
|
setPresenting: (_?: PresentedSource) => void;
|
||||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||||
stopRingtone: () => unknown;
|
stopRingtone: () => unknown;
|
||||||
|
switchToPresentationView: () => void;
|
||||||
|
switchFromPresentationView: () => void;
|
||||||
hangUpActiveCall: () => void;
|
hangUpActiveCall: () => void;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
togglePip: () => void;
|
togglePip: () => void;
|
||||||
|
@ -127,6 +129,8 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
setOutgoingRing,
|
setOutgoingRing,
|
||||||
startCall,
|
startCall,
|
||||||
|
switchToPresentationView,
|
||||||
|
switchFromPresentationView,
|
||||||
theme,
|
theme,
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
|
@ -270,8 +274,9 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}
|
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}
|
||||||
setLocalPreview={setLocalPreview}
|
setLocalPreview={setLocalPreview}
|
||||||
setRendererCanvas={setRendererCanvas}
|
setRendererCanvas={setRendererCanvas}
|
||||||
|
switchToPresentationView={switchToPresentationView}
|
||||||
|
switchFromPresentationView={switchFromPresentationView}
|
||||||
togglePip={togglePip}
|
togglePip={togglePip}
|
||||||
toggleSpeakerView={toggleSpeakerView}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -313,6 +318,8 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
setLocalVideo={setLocalVideo}
|
setLocalVideo={setLocalVideo}
|
||||||
setPresenting={setPresenting}
|
setPresenting={setPresenting}
|
||||||
stickyControls={showParticipantsList}
|
stickyControls={showParticipantsList}
|
||||||
|
switchToPresentationView={switchToPresentationView}
|
||||||
|
switchFromPresentationView={switchFromPresentationView}
|
||||||
toggleScreenRecordingPermissionsDialog={
|
toggleScreenRecordingPermissionsDialog={
|
||||||
toggleScreenRecordingPermissionsDialog
|
toggleScreenRecordingPermissionsDialog
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { action } from '@storybook/addon-actions';
|
||||||
import type { GroupCallRemoteParticipantType } from '../types/Calling';
|
import type { GroupCallRemoteParticipantType } from '../types/Calling';
|
||||||
import {
|
import {
|
||||||
CallMode,
|
CallMode,
|
||||||
|
CallViewMode,
|
||||||
CallState,
|
CallState,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
|
@ -45,7 +46,7 @@ type OverridePropsBase = {
|
||||||
hasLocalAudio?: boolean;
|
hasLocalAudio?: boolean;
|
||||||
hasLocalVideo?: boolean;
|
hasLocalVideo?: boolean;
|
||||||
localAudioLevel?: number;
|
localAudioLevel?: number;
|
||||||
isInSpeakerView?: boolean;
|
viewMode?: CallViewMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DirectCallOverrideProps = OverridePropsBase & {
|
type DirectCallOverrideProps = OverridePropsBase & {
|
||||||
|
@ -126,9 +127,10 @@ const createActiveCallProp = (
|
||||||
[0, 0.5, 1],
|
[0, 0.5, 1],
|
||||||
overrideProps.localAudioLevel || 0
|
overrideProps.localAudioLevel || 0
|
||||||
),
|
),
|
||||||
isInSpeakerView: boolean(
|
viewMode: select(
|
||||||
'isInSpeakerView',
|
'viewMode',
|
||||||
overrideProps.isInSpeakerView || false
|
[CallViewMode.Grid, CallViewMode.Speaker, CallViewMode.Presentation],
|
||||||
|
overrideProps.viewMode || CallViewMode.Grid
|
||||||
),
|
),
|
||||||
outgoingRing: true,
|
outgoingRing: true,
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -172,6 +174,8 @@ const createProps = (
|
||||||
setPresenting: action('toggle-presenting'),
|
setPresenting: action('toggle-presenting'),
|
||||||
setRendererCanvas: action('set-renderer-canvas'),
|
setRendererCanvas: action('set-renderer-canvas'),
|
||||||
stickyControls: boolean('stickyControls', false),
|
stickyControls: boolean('stickyControls', false),
|
||||||
|
switchToPresentationView: action('switch-to-presentation-view'),
|
||||||
|
switchFromPresentationView: action('switch-from-presentation-view'),
|
||||||
toggleParticipants: action('toggle-participants'),
|
toggleParticipants: action('toggle-participants'),
|
||||||
togglePip: action('toggle-pip'),
|
togglePip: action('toggle-pip'),
|
||||||
toggleScreenRecordingPermissionsDialog: action(
|
toggleScreenRecordingPermissionsDialog: action(
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
||||||
SetLocalVideoType,
|
SetLocalVideoType,
|
||||||
SetRendererCanvasType,
|
SetRendererCanvasType,
|
||||||
} from '../state/ducks/calling';
|
} from '../state/ducks/calling';
|
||||||
|
import { isInSpeakerView } from '../state/selectors/calling';
|
||||||
import { Avatar } from './Avatar';
|
import { Avatar } from './Avatar';
|
||||||
import { CallingHeader } from './CallingHeader';
|
import { CallingHeader } from './CallingHeader';
|
||||||
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
||||||
|
@ -61,6 +62,8 @@ export type PropsType = {
|
||||||
setPresenting: (_?: PresentedSource) => void;
|
setPresenting: (_?: PresentedSource) => void;
|
||||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||||
stickyControls: boolean;
|
stickyControls: boolean;
|
||||||
|
switchToPresentationView: () => void;
|
||||||
|
switchFromPresentationView: () => void;
|
||||||
toggleParticipants: () => void;
|
toggleParticipants: () => void;
|
||||||
togglePip: () => void;
|
togglePip: () => void;
|
||||||
toggleScreenRecordingPermissionsDialog: () => unknown;
|
toggleScreenRecordingPermissionsDialog: () => unknown;
|
||||||
|
@ -120,6 +123,8 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
setPresenting,
|
setPresenting,
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
stickyControls,
|
stickyControls,
|
||||||
|
switchToPresentationView,
|
||||||
|
switchFromPresentationView,
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleScreenRecordingPermissionsDialog,
|
toggleScreenRecordingPermissionsDialog,
|
||||||
|
@ -131,18 +136,17 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
hasLocalVideo,
|
hasLocalVideo,
|
||||||
localAudioLevel,
|
localAudioLevel,
|
||||||
isInSpeakerView,
|
|
||||||
presentingSource,
|
presentingSource,
|
||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
showNeedsScreenRecordingPermissionsWarning,
|
showNeedsScreenRecordingPermissionsWarning,
|
||||||
showParticipantsList,
|
showParticipantsList,
|
||||||
} = activeCall;
|
} = activeCall;
|
||||||
|
|
||||||
useActivateSpeakerViewOnPresenting(
|
useActivateSpeakerViewOnPresenting({
|
||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
isInSpeakerView,
|
switchToPresentationView,
|
||||||
toggleSpeakerView
|
switchFromPresentationView,
|
||||||
);
|
});
|
||||||
|
|
||||||
const activeCallShortcuts = useActiveCallShortcuts(hangUpActiveCall);
|
const activeCallShortcuts = useActiveCallShortcuts(hangUpActiveCall);
|
||||||
useKeyboardShortcuts(activeCallShortcuts);
|
useKeyboardShortcuts(activeCallShortcuts);
|
||||||
|
@ -293,7 +297,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
<GroupCallRemoteParticipants
|
<GroupCallRemoteParticipants
|
||||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isInSpeakerView={isInSpeakerView}
|
isInSpeakerView={isInSpeakerView(activeCall)}
|
||||||
remoteParticipants={activeCall.remoteParticipants}
|
remoteParticipants={activeCall.remoteParticipants}
|
||||||
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||||
remoteAudioLevels={activeCall.remoteAudioLevels}
|
remoteAudioLevels={activeCall.remoteAudioLevels}
|
||||||
|
@ -448,7 +452,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
>
|
>
|
||||||
<CallingHeader
|
<CallingHeader
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isInSpeakerView={isInSpeakerView}
|
isInSpeakerView={isInSpeakerView(activeCall)}
|
||||||
isGroupCall={isGroupCall}
|
isGroupCall={isGroupCall}
|
||||||
message={headerMessage}
|
message={headerMessage}
|
||||||
participantCount={participantCount}
|
participantCount={participantCount}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { CallingPip } from './CallingPip';
|
||||||
import type { ActiveCallType } from '../types/Calling';
|
import type { ActiveCallType } from '../types/Calling';
|
||||||
import {
|
import {
|
||||||
CallMode,
|
CallMode,
|
||||||
|
CallViewMode,
|
||||||
CallState,
|
CallState,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
|
@ -40,7 +41,11 @@ const getCommonActiveCallData = () => ({
|
||||||
hasLocalAudio: boolean('hasLocalAudio', true),
|
hasLocalAudio: boolean('hasLocalAudio', true),
|
||||||
hasLocalVideo: boolean('hasLocalVideo', false),
|
hasLocalVideo: boolean('hasLocalVideo', false),
|
||||||
localAudioLevel: select('localAudioLevel', [0, 0.5, 1], 0),
|
localAudioLevel: select('localAudioLevel', [0, 0.5, 1], 0),
|
||||||
isInSpeakerView: boolean('isInSpeakerView', false),
|
viewMode: select(
|
||||||
|
'viewMode',
|
||||||
|
[CallViewMode.Grid, CallViewMode.Speaker, CallViewMode.Presentation],
|
||||||
|
CallViewMode.Grid
|
||||||
|
),
|
||||||
joinedAt: Date.now(),
|
joinedAt: Date.now(),
|
||||||
outgoingRing: true,
|
outgoingRing: true,
|
||||||
pip: true,
|
pip: true,
|
||||||
|
@ -67,8 +72,9 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
||||||
setLocalPreview: action('set-local-preview'),
|
setLocalPreview: action('set-local-preview'),
|
||||||
setRendererCanvas: action('set-renderer-canvas'),
|
setRendererCanvas: action('set-renderer-canvas'),
|
||||||
|
switchFromPresentationView: action('switch-to-presentation-view'),
|
||||||
|
switchToPresentationView: action('switch-to-presentation-view'),
|
||||||
togglePip: action('toggle-pip'),
|
togglePip: action('toggle-pip'),
|
||||||
toggleSpeakerView: action('toggleSpeakerView'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const story = storiesOf('Components/CallingPip', module);
|
const story = storiesOf('Components/CallingPip', module);
|
||||||
|
|
|
@ -57,8 +57,9 @@ export type PropsType = {
|
||||||
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
|
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
|
||||||
setLocalPreview: (_: SetLocalPreviewType) => void;
|
setLocalPreview: (_: SetLocalPreviewType) => void;
|
||||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||||
|
switchToPresentationView: () => void;
|
||||||
|
switchFromPresentationView: () => void;
|
||||||
togglePip: () => void;
|
togglePip: () => void;
|
||||||
toggleSpeakerView: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PIP_HEIGHT = 156;
|
const PIP_HEIGHT = 156;
|
||||||
|
@ -75,8 +76,9 @@ export const CallingPip = ({
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
setLocalPreview,
|
setLocalPreview,
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
|
switchToPresentationView,
|
||||||
|
switchFromPresentationView,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSpeakerView,
|
|
||||||
}: PropsType): JSX.Element | null => {
|
}: PropsType): JSX.Element | null => {
|
||||||
const videoContainerRef = React.useRef<null | HTMLDivElement>(null);
|
const videoContainerRef = React.useRef<null | HTMLDivElement>(null);
|
||||||
const localVideoRef = React.useRef(null);
|
const localVideoRef = React.useRef(null);
|
||||||
|
@ -88,11 +90,11 @@ export const CallingPip = ({
|
||||||
offsetY: PIP_TOP_MARGIN,
|
offsetY: PIP_TOP_MARGIN,
|
||||||
});
|
});
|
||||||
|
|
||||||
useActivateSpeakerViewOnPresenting(
|
useActivateSpeakerViewOnPresenting({
|
||||||
activeCall.remoteParticipants,
|
remoteParticipants: activeCall.remoteParticipants,
|
||||||
activeCall.isInSpeakerView,
|
switchToPresentationView,
|
||||||
toggleSpeakerView
|
switchFromPresentationView,
|
||||||
);
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setLocalPreview({ element: localVideoRef });
|
setLocalPreview({ element: localVideoRef });
|
||||||
|
|
|
@ -11,19 +11,30 @@ type RemoteParticipant = {
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useActivateSpeakerViewOnPresenting(
|
export function useActivateSpeakerViewOnPresenting({
|
||||||
remoteParticipants: ReadonlyArray<RemoteParticipant>,
|
remoteParticipants,
|
||||||
isInSpeakerView: boolean,
|
switchToPresentationView,
|
||||||
toggleSpeakerView: () => void
|
switchFromPresentationView,
|
||||||
): void {
|
}: {
|
||||||
|
remoteParticipants: ReadonlyArray<RemoteParticipant>;
|
||||||
|
switchToPresentationView: () => void;
|
||||||
|
switchFromPresentationView: () => void;
|
||||||
|
}): void {
|
||||||
const presenterUuid = remoteParticipants.find(
|
const presenterUuid = remoteParticipants.find(
|
||||||
participant => participant.presenting
|
participant => participant.presenting
|
||||||
)?.uuid;
|
)?.uuid;
|
||||||
const prevPresenterUuid = usePrevious(presenterUuid, presenterUuid);
|
const prevPresenterUuid = usePrevious(presenterUuid, presenterUuid);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevPresenterUuid !== presenterUuid && !isInSpeakerView) {
|
if (prevPresenterUuid !== presenterUuid && presenterUuid) {
|
||||||
toggleSpeakerView();
|
switchToPresentationView();
|
||||||
|
} else if (prevPresenterUuid && !presenterUuid) {
|
||||||
|
switchFromPresentationView();
|
||||||
}
|
}
|
||||||
}, [isInSpeakerView, presenterUuid, prevPresenterUuid, toggleSpeakerView]);
|
}, [
|
||||||
|
presenterUuid,
|
||||||
|
prevPresenterUuid,
|
||||||
|
switchToPresentationView,
|
||||||
|
switchFromPresentationView,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import type {
|
||||||
import {
|
import {
|
||||||
CallingDeviceType,
|
CallingDeviceType,
|
||||||
CallMode,
|
CallMode,
|
||||||
|
CallViewMode,
|
||||||
CallState,
|
CallState,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
|
@ -104,7 +105,7 @@ export type ActiveCallStateType = {
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
hasLocalVideo: boolean;
|
hasLocalVideo: boolean;
|
||||||
localAudioLevel: number;
|
localAudioLevel: number;
|
||||||
isInSpeakerView: boolean;
|
viewMode: CallViewMode;
|
||||||
joinedAt?: number;
|
joinedAt?: number;
|
||||||
outgoingRing: boolean;
|
outgoingRing: boolean;
|
||||||
pip: boolean;
|
pip: boolean;
|
||||||
|
@ -427,6 +428,8 @@ const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
|
||||||
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
|
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
|
||||||
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
|
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
|
||||||
const TOGGLE_SPEAKER_VIEW = 'calling/TOGGLE_SPEAKER_VIEW';
|
const TOGGLE_SPEAKER_VIEW = 'calling/TOGGLE_SPEAKER_VIEW';
|
||||||
|
const SWITCH_TO_PRESENTATION_VIEW = 'calling/SWITCH_TO_PRESENTATION_VIEW';
|
||||||
|
const SWITCH_FROM_PRESENTATION_VIEW = 'calling/SWITCH_FROM_PRESENTATION_VIEW';
|
||||||
|
|
||||||
type AcceptCallPendingActionType = {
|
type AcceptCallPendingActionType = {
|
||||||
type: 'calling/ACCEPT_CALL_PENDING';
|
type: 'calling/ACCEPT_CALL_PENDING';
|
||||||
|
@ -597,6 +600,14 @@ type ToggleSpeakerViewActionType = {
|
||||||
type: 'calling/TOGGLE_SPEAKER_VIEW';
|
type: 'calling/TOGGLE_SPEAKER_VIEW';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SwitchToPresentationViewActionType = {
|
||||||
|
type: 'calling/SWITCH_TO_PRESENTATION_VIEW';
|
||||||
|
};
|
||||||
|
|
||||||
|
type SwitchFromPresentationViewActionType = {
|
||||||
|
type: 'calling/SWITCH_FROM_PRESENTATION_VIEW';
|
||||||
|
};
|
||||||
|
|
||||||
export type CallingActionType =
|
export type CallingActionType =
|
||||||
| AcceptCallPendingActionType
|
| AcceptCallPendingActionType
|
||||||
| CancelCallActionType
|
| CancelCallActionType
|
||||||
|
@ -632,7 +643,9 @@ export type CallingActionType =
|
||||||
| TogglePipActionType
|
| TogglePipActionType
|
||||||
| SetPresentingFulfilledActionType
|
| SetPresentingFulfilledActionType
|
||||||
| ToggleSettingsActionType
|
| ToggleSettingsActionType
|
||||||
| ToggleSpeakerViewActionType;
|
| ToggleSpeakerViewActionType
|
||||||
|
| SwitchToPresentationViewActionType
|
||||||
|
| SwitchFromPresentationViewActionType;
|
||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
|
@ -1314,6 +1327,18 @@ function toggleSpeakerView(): ToggleSpeakerViewActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function switchToPresentationView(): SwitchToPresentationViewActionType {
|
||||||
|
return {
|
||||||
|
type: SWITCH_TO_PRESENTATION_VIEW,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchFromPresentationView(): SwitchFromPresentationViewActionType {
|
||||||
|
return {
|
||||||
|
type: SWITCH_FROM_PRESENTATION_VIEW,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
acceptCall,
|
acceptCall,
|
||||||
callStateChange,
|
callStateChange,
|
||||||
|
@ -1349,6 +1374,8 @@ export const actions = {
|
||||||
setOutgoingRing,
|
setOutgoingRing,
|
||||||
startCall,
|
startCall,
|
||||||
startCallingLobby,
|
startCallingLobby,
|
||||||
|
switchToPresentationView,
|
||||||
|
switchFromPresentationView,
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleScreenRecordingPermissionsDialog,
|
toggleScreenRecordingPermissionsDialog,
|
||||||
|
@ -1457,7 +1484,7 @@ export function reducer(
|
||||||
hasLocalAudio: action.payload.hasLocalAudio,
|
hasLocalAudio: action.payload.hasLocalAudio,
|
||||||
hasLocalVideo: action.payload.hasLocalVideo,
|
hasLocalVideo: action.payload.hasLocalVideo,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
pip: false,
|
pip: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
@ -1485,7 +1512,7 @@ export function reducer(
|
||||||
hasLocalAudio: action.payload.hasLocalAudio,
|
hasLocalAudio: action.payload.hasLocalAudio,
|
||||||
hasLocalVideo: action.payload.hasLocalVideo,
|
hasLocalVideo: action.payload.hasLocalVideo,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
pip: false,
|
pip: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
@ -1508,7 +1535,7 @@ export function reducer(
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: action.payload.asVideoCall,
|
hasLocalVideo: action.payload.asVideoCall,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
pip: false,
|
pip: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
@ -1662,7 +1689,7 @@ export function reducer(
|
||||||
hasLocalAudio: action.payload.hasLocalAudio,
|
hasLocalAudio: action.payload.hasLocalAudio,
|
||||||
hasLocalVideo: action.payload.hasLocalVideo,
|
hasLocalVideo: action.payload.hasLocalVideo,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
pip: false,
|
pip: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
@ -2129,11 +2156,61 @@ export function reducer(
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newViewMode: CallViewMode;
|
||||||
|
if (activeCallState.viewMode === CallViewMode.Grid) {
|
||||||
|
newViewMode = CallViewMode.Speaker;
|
||||||
|
} else {
|
||||||
|
// This will switch presentation/speaker to grid
|
||||||
|
newViewMode = CallViewMode.Grid;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
activeCallState: {
|
activeCallState: {
|
||||||
...activeCallState,
|
...activeCallState,
|
||||||
isInSpeakerView: !activeCallState.isInSpeakerView,
|
viewMode: newViewMode,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === SWITCH_TO_PRESENTATION_VIEW) {
|
||||||
|
const { activeCallState } = state;
|
||||||
|
if (!activeCallState) {
|
||||||
|
log.warn('Cannot switch to speaker view when there is no active call');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Presentation" mode reverts to "Grid" when the call is over so don't
|
||||||
|
// switch it if it is in "Speaker" mode.
|
||||||
|
if (activeCallState.viewMode === CallViewMode.Speaker) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeCallState: {
|
||||||
|
...activeCallState,
|
||||||
|
viewMode: CallViewMode.Presentation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === SWITCH_FROM_PRESENTATION_VIEW) {
|
||||||
|
const { activeCallState } = state;
|
||||||
|
if (!activeCallState) {
|
||||||
|
log.warn('Cannot switch to speaker view when there is no active call');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeCallState.viewMode !== CallViewMode.Presentation) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeCallState: {
|
||||||
|
...activeCallState,
|
||||||
|
viewMode: CallViewMode.Grid,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import type {
|
import type {
|
||||||
|
ActiveCallStateType,
|
||||||
CallingStateType,
|
CallingStateType,
|
||||||
CallsByConversationType,
|
CallsByConversationType,
|
||||||
DirectCallStateType,
|
DirectCallStateType,
|
||||||
|
@ -13,6 +14,7 @@ import type {
|
||||||
import { getIncomingCall as getIncomingCallHelper } from '../ducks/calling';
|
import { getIncomingCall as getIncomingCallHelper } from '../ducks/calling';
|
||||||
import { getUserUuid } from './user';
|
import { getUserUuid } from './user';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
|
import { CallViewMode } from '../../types/Calling';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
|
|
||||||
export type CallStateType = DirectCallStateType | GroupCallStateType;
|
export type CallStateType = DirectCallStateType | GroupCallStateType;
|
||||||
|
@ -71,3 +73,12 @@ export const getIncomingCall = createSelector(
|
||||||
return getIncomingCallHelper(callsByConversation, ourUuid);
|
return getIncomingCallHelper(callsByConversation, ourUuid);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const isInSpeakerView = (
|
||||||
|
call: Pick<ActiveCallStateType, 'viewMode'> | undefined
|
||||||
|
): boolean => {
|
||||||
|
return Boolean(
|
||||||
|
call?.viewMode === CallViewMode.Presentation ||
|
||||||
|
call?.viewMode === CallViewMode.Speaker
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -131,7 +131,7 @@ const mapStateToActiveCallProp = (
|
||||||
hasLocalAudio: activeCallState.hasLocalAudio,
|
hasLocalAudio: activeCallState.hasLocalAudio,
|
||||||
hasLocalVideo: activeCallState.hasLocalVideo,
|
hasLocalVideo: activeCallState.hasLocalVideo,
|
||||||
localAudioLevel: activeCallState.localAudioLevel,
|
localAudioLevel: activeCallState.localAudioLevel,
|
||||||
isInSpeakerView: activeCallState.isInSpeakerView,
|
viewMode: activeCallState.viewMode,
|
||||||
joinedAt: activeCallState.joinedAt,
|
joinedAt: activeCallState.joinedAt,
|
||||||
outgoingRing: activeCallState.outgoingRing,
|
outgoingRing: activeCallState.outgoingRing,
|
||||||
pip: activeCallState.pip,
|
pip: activeCallState.pip,
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { calling as callingService } from '../../../services/calling';
|
||||||
import {
|
import {
|
||||||
CallMode,
|
CallMode,
|
||||||
CallState,
|
CallState,
|
||||||
|
CallViewMode,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
} from '../../../types/Calling';
|
} from '../../../types/Calling';
|
||||||
|
@ -52,7 +53,7 @@ describe('calling duck', () => {
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
outgoingRing: true,
|
outgoingRing: true,
|
||||||
|
@ -131,7 +132,7 @@ describe('calling duck', () => {
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
outgoingRing: false,
|
outgoingRing: false,
|
||||||
|
@ -140,6 +141,22 @@ describe('calling duck', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const stateWithActivePresentationViewGroupCall = {
|
||||||
|
...stateWithGroupCall,
|
||||||
|
activeCallState: {
|
||||||
|
...stateWithActiveGroupCall.activeCallState,
|
||||||
|
viewMode: CallViewMode.Presentation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const stateWithActiveSpeakerViewGroupCall = {
|
||||||
|
...stateWithGroupCall,
|
||||||
|
activeCallState: {
|
||||||
|
...stateWithActiveGroupCall.activeCallState,
|
||||||
|
viewMode: CallViewMode.Speaker,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const ourUuid = UUID.generate().toString();
|
const ourUuid = UUID.generate().toString();
|
||||||
|
|
||||||
const getEmptyRootState = () => {
|
const getEmptyRootState = () => {
|
||||||
|
@ -437,7 +454,7 @@ describe('calling duck', () => {
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
outgoingRing: false,
|
outgoingRing: false,
|
||||||
|
@ -530,7 +547,7 @@ describe('calling duck', () => {
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
outgoingRing: false,
|
outgoingRing: false,
|
||||||
|
@ -1122,7 +1139,7 @@ describe('calling duck', () => {
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
outgoingRing: false,
|
outgoingRing: false,
|
||||||
|
@ -1651,7 +1668,7 @@ describe('calling duck', () => {
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -1937,7 +1954,7 @@ describe('calling duck', () => {
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -2013,7 +2030,7 @@ describe('calling duck', () => {
|
||||||
describe('toggleSpeakerView', () => {
|
describe('toggleSpeakerView', () => {
|
||||||
const { toggleSpeakerView } = actions;
|
const { toggleSpeakerView } = actions;
|
||||||
|
|
||||||
it('toggles speaker view', () => {
|
it('toggles speaker view from grid view', () => {
|
||||||
const afterOneToggle = reducer(
|
const afterOneToggle = reducer(
|
||||||
stateWithActiveGroupCall,
|
stateWithActiveGroupCall,
|
||||||
toggleSpeakerView()
|
toggleSpeakerView()
|
||||||
|
@ -2021,9 +2038,92 @@ describe('calling duck', () => {
|
||||||
const afterTwoToggles = reducer(afterOneToggle, toggleSpeakerView());
|
const afterTwoToggles = reducer(afterOneToggle, toggleSpeakerView());
|
||||||
const afterThreeToggles = reducer(afterTwoToggles, toggleSpeakerView());
|
const afterThreeToggles = reducer(afterTwoToggles, toggleSpeakerView());
|
||||||
|
|
||||||
assert.isTrue(afterOneToggle.activeCallState?.isInSpeakerView);
|
assert.strictEqual(
|
||||||
assert.isFalse(afterTwoToggles.activeCallState?.isInSpeakerView);
|
afterOneToggle.activeCallState?.viewMode,
|
||||||
assert.isTrue(afterThreeToggles.activeCallState?.isInSpeakerView);
|
CallViewMode.Speaker
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
afterTwoToggles.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Grid
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
afterThreeToggles.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Speaker
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles speaker view from presentation view', () => {
|
||||||
|
const afterOneToggle = reducer(
|
||||||
|
stateWithActivePresentationViewGroupCall,
|
||||||
|
toggleSpeakerView()
|
||||||
|
);
|
||||||
|
const afterTwoToggles = reducer(afterOneToggle, toggleSpeakerView());
|
||||||
|
const afterThreeToggles = reducer(afterTwoToggles, toggleSpeakerView());
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
afterOneToggle.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Grid
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
afterTwoToggles.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Speaker
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
afterThreeToggles.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Grid
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('switchToPresentationView', () => {
|
||||||
|
const { switchToPresentationView, switchFromPresentationView } = actions;
|
||||||
|
|
||||||
|
it('toggles presentation view from grid view', () => {
|
||||||
|
const afterOneToggle = reducer(
|
||||||
|
stateWithActiveGroupCall,
|
||||||
|
switchToPresentationView()
|
||||||
|
);
|
||||||
|
const afterTwoToggles = reducer(
|
||||||
|
afterOneToggle,
|
||||||
|
switchToPresentationView()
|
||||||
|
);
|
||||||
|
const finalState = reducer(
|
||||||
|
afterOneToggle,
|
||||||
|
switchFromPresentationView()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
afterOneToggle.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Presentation
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
afterTwoToggles.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Presentation
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
finalState.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Grid
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not toggle presentation view from speaker view', () => {
|
||||||
|
const afterOneToggle = reducer(
|
||||||
|
stateWithActiveSpeakerViewGroupCall,
|
||||||
|
switchToPresentationView()
|
||||||
|
);
|
||||||
|
const finalState = reducer(
|
||||||
|
afterOneToggle,
|
||||||
|
switchFromPresentationView()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
afterOneToggle.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Speaker
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
finalState.activeCallState?.viewMode,
|
||||||
|
CallViewMode.Speaker
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { noopAction } from '../../../state/ducks/noop';
|
||||||
import {
|
import {
|
||||||
CallMode,
|
CallMode,
|
||||||
CallState,
|
CallState,
|
||||||
|
CallViewMode,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
} from '../../../types/Calling';
|
} from '../../../types/Calling';
|
||||||
|
@ -52,7 +53,7 @@ describe('state/selectors/calling', () => {
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
localAudioLevel: 0,
|
localAudioLevel: 0,
|
||||||
isInSpeakerView: false,
|
viewMode: CallViewMode.Grid,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
outgoingRing: true,
|
outgoingRing: true,
|
||||||
|
|
|
@ -11,6 +11,14 @@ export enum CallMode {
|
||||||
Group = 'Group',
|
Group = 'Group',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Speaker and Presentation has the same UI, but Presentation mode will switch
|
||||||
|
// to Grid mode when the presentation is over.
|
||||||
|
export enum CallViewMode {
|
||||||
|
Grid = 'Grid',
|
||||||
|
Speaker = 'Speaker',
|
||||||
|
Presentation = 'Presentation',
|
||||||
|
}
|
||||||
|
|
||||||
export type PresentableSource = {
|
export type PresentableSource = {
|
||||||
appIcon?: string;
|
appIcon?: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -29,7 +37,7 @@ type ActiveCallBaseType = {
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
hasLocalVideo: boolean;
|
hasLocalVideo: boolean;
|
||||||
localAudioLevel: number;
|
localAudioLevel: number;
|
||||||
isInSpeakerView: boolean;
|
viewMode: CallViewMode;
|
||||||
isSharingScreen?: boolean;
|
isSharingScreen?: boolean;
|
||||||
joinedAt?: number;
|
joinedAt?: number;
|
||||||
outgoingRing: boolean;
|
outgoingRing: boolean;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue