Restore call view mode after presentation end

This commit is contained in:
Fedor Indutny 2022-05-25 11:03:27 -07:00 committed by GitHub
parent 9e1528fa24
commit 80c90540f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 289 additions and 51 deletions

View file

@ -12,6 +12,7 @@ import {
CallEndedReason,
CallMode,
CallState,
CallViewMode,
GroupCallConnectionState,
GroupCallJoinState,
} from '../types/Calling';
@ -51,7 +52,11 @@ const getCommonActiveCallData = () => ({
hasLocalAudio: boolean('hasLocalAudio', true),
hasLocalVideo: boolean('hasLocalVideo', false),
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),
pip: boolean('pip', false),
settingsDialogOpen: boolean('settingsDialogOpen', false),
@ -101,6 +106,8 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
setOutgoingRing: action('set-outgoing-ring'),
startCall: action('start-call'),
stopRingtone: action('stop-ringtone'),
switchToPresentationView: action('switch-to-presentation-view'),
switchFromPresentationView: action('switch-from-presentation-view'),
theme: ThemeType.light,
toggleParticipants: action('toggle-participants'),
togglePip: action('toggle-pip'),

View file

@ -91,6 +91,8 @@ export type PropsType = {
setPresenting: (_?: PresentedSource) => void;
setRendererCanvas: (_: SetRendererCanvasType) => void;
stopRingtone: () => unknown;
switchToPresentationView: () => void;
switchFromPresentationView: () => void;
hangUpActiveCall: () => void;
theme: ThemeType;
togglePip: () => void;
@ -127,6 +129,8 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
setRendererCanvas,
setOutgoingRing,
startCall,
switchToPresentationView,
switchFromPresentationView,
theme,
toggleParticipants,
togglePip,
@ -270,8 +274,9 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}
setLocalPreview={setLocalPreview}
setRendererCanvas={setRendererCanvas}
switchToPresentationView={switchToPresentationView}
switchFromPresentationView={switchFromPresentationView}
togglePip={togglePip}
toggleSpeakerView={toggleSpeakerView}
/>
);
}
@ -313,6 +318,8 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
setLocalVideo={setLocalVideo}
setPresenting={setPresenting}
stickyControls={showParticipantsList}
switchToPresentationView={switchToPresentationView}
switchFromPresentationView={switchFromPresentationView}
toggleScreenRecordingPermissionsDialog={
toggleScreenRecordingPermissionsDialog
}

View file

@ -10,6 +10,7 @@ import { action } from '@storybook/addon-actions';
import type { GroupCallRemoteParticipantType } from '../types/Calling';
import {
CallMode,
CallViewMode,
CallState,
GroupCallConnectionState,
GroupCallJoinState,
@ -45,7 +46,7 @@ type OverridePropsBase = {
hasLocalAudio?: boolean;
hasLocalVideo?: boolean;
localAudioLevel?: number;
isInSpeakerView?: boolean;
viewMode?: CallViewMode;
};
type DirectCallOverrideProps = OverridePropsBase & {
@ -126,9 +127,10 @@ const createActiveCallProp = (
[0, 0.5, 1],
overrideProps.localAudioLevel || 0
),
isInSpeakerView: boolean(
'isInSpeakerView',
overrideProps.isInSpeakerView || false
viewMode: select(
'viewMode',
[CallViewMode.Grid, CallViewMode.Speaker, CallViewMode.Presentation],
overrideProps.viewMode || CallViewMode.Grid
),
outgoingRing: true,
pip: false,
@ -172,6 +174,8 @@ const createProps = (
setPresenting: action('toggle-presenting'),
setRendererCanvas: action('set-renderer-canvas'),
stickyControls: boolean('stickyControls', false),
switchToPresentationView: action('switch-to-presentation-view'),
switchFromPresentationView: action('switch-from-presentation-view'),
toggleParticipants: action('toggle-participants'),
togglePip: action('toggle-pip'),
toggleScreenRecordingPermissionsDialog: action(

View file

@ -12,6 +12,7 @@ import type {
SetLocalVideoType,
SetRendererCanvasType,
} from '../state/ducks/calling';
import { isInSpeakerView } from '../state/selectors/calling';
import { Avatar } from './Avatar';
import { CallingHeader } from './CallingHeader';
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
@ -61,6 +62,8 @@ export type PropsType = {
setPresenting: (_?: PresentedSource) => void;
setRendererCanvas: (_: SetRendererCanvasType) => void;
stickyControls: boolean;
switchToPresentationView: () => void;
switchFromPresentationView: () => void;
toggleParticipants: () => void;
togglePip: () => void;
toggleScreenRecordingPermissionsDialog: () => unknown;
@ -120,6 +123,8 @@ export const CallScreen: React.FC<PropsType> = ({
setPresenting,
setRendererCanvas,
stickyControls,
switchToPresentationView,
switchFromPresentationView,
toggleParticipants,
togglePip,
toggleScreenRecordingPermissionsDialog,
@ -131,18 +136,17 @@ export const CallScreen: React.FC<PropsType> = ({
hasLocalAudio,
hasLocalVideo,
localAudioLevel,
isInSpeakerView,
presentingSource,
remoteParticipants,
showNeedsScreenRecordingPermissionsWarning,
showParticipantsList,
} = activeCall;
useActivateSpeakerViewOnPresenting(
useActivateSpeakerViewOnPresenting({
remoteParticipants,
isInSpeakerView,
toggleSpeakerView
);
switchToPresentationView,
switchFromPresentationView,
});
const activeCallShortcuts = useActiveCallShortcuts(hangUpActiveCall);
useKeyboardShortcuts(activeCallShortcuts);
@ -293,7 +297,7 @@ export const CallScreen: React.FC<PropsType> = ({
<GroupCallRemoteParticipants
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
i18n={i18n}
isInSpeakerView={isInSpeakerView}
isInSpeakerView={isInSpeakerView(activeCall)}
remoteParticipants={activeCall.remoteParticipants}
setGroupCallVideoRequest={setGroupCallVideoRequest}
remoteAudioLevels={activeCall.remoteAudioLevels}
@ -448,7 +452,7 @@ export const CallScreen: React.FC<PropsType> = ({
>
<CallingHeader
i18n={i18n}
isInSpeakerView={isInSpeakerView}
isInSpeakerView={isInSpeakerView(activeCall)}
isGroupCall={isGroupCall}
message={headerMessage}
participantCount={participantCount}

View file

@ -14,6 +14,7 @@ import { CallingPip } from './CallingPip';
import type { ActiveCallType } from '../types/Calling';
import {
CallMode,
CallViewMode,
CallState,
GroupCallConnectionState,
GroupCallJoinState,
@ -40,7 +41,11 @@ const getCommonActiveCallData = () => ({
hasLocalAudio: boolean('hasLocalAudio', true),
hasLocalVideo: boolean('hasLocalVideo', false),
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(),
outgoingRing: true,
pip: true,
@ -67,8 +72,9 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
setGroupCallVideoRequest: action('set-group-call-video-request'),
setLocalPreview: action('set-local-preview'),
setRendererCanvas: action('set-renderer-canvas'),
switchFromPresentationView: action('switch-to-presentation-view'),
switchToPresentationView: action('switch-to-presentation-view'),
togglePip: action('toggle-pip'),
toggleSpeakerView: action('toggleSpeakerView'),
});
const story = storiesOf('Components/CallingPip', module);

View file

@ -57,8 +57,9 @@ export type PropsType = {
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
setLocalPreview: (_: SetLocalPreviewType) => void;
setRendererCanvas: (_: SetRendererCanvasType) => void;
switchToPresentationView: () => void;
switchFromPresentationView: () => void;
togglePip: () => void;
toggleSpeakerView: () => void;
};
const PIP_HEIGHT = 156;
@ -75,8 +76,9 @@ export const CallingPip = ({
setGroupCallVideoRequest,
setLocalPreview,
setRendererCanvas,
switchToPresentationView,
switchFromPresentationView,
togglePip,
toggleSpeakerView,
}: PropsType): JSX.Element | null => {
const videoContainerRef = React.useRef<null | HTMLDivElement>(null);
const localVideoRef = React.useRef(null);
@ -88,11 +90,11 @@ export const CallingPip = ({
offsetY: PIP_TOP_MARGIN,
});
useActivateSpeakerViewOnPresenting(
activeCall.remoteParticipants,
activeCall.isInSpeakerView,
toggleSpeakerView
);
useActivateSpeakerViewOnPresenting({
remoteParticipants: activeCall.remoteParticipants,
switchToPresentationView,
switchFromPresentationView,
});
React.useEffect(() => {
setLocalPreview({ element: localVideoRef });

View file

@ -11,19 +11,30 @@ type RemoteParticipant = {
uuid?: string;
};
export function useActivateSpeakerViewOnPresenting(
remoteParticipants: ReadonlyArray<RemoteParticipant>,
isInSpeakerView: boolean,
toggleSpeakerView: () => void
): void {
export function useActivateSpeakerViewOnPresenting({
remoteParticipants,
switchToPresentationView,
switchFromPresentationView,
}: {
remoteParticipants: ReadonlyArray<RemoteParticipant>;
switchToPresentationView: () => void;
switchFromPresentationView: () => void;
}): void {
const presenterUuid = remoteParticipants.find(
participant => participant.presenting
)?.uuid;
const prevPresenterUuid = usePrevious(presenterUuid, presenterUuid);
useEffect(() => {
if (prevPresenterUuid !== presenterUuid && !isInSpeakerView) {
toggleSpeakerView();
if (prevPresenterUuid !== presenterUuid && presenterUuid) {
switchToPresentationView();
} else if (prevPresenterUuid && !presenterUuid) {
switchFromPresentationView();
}
}, [isInSpeakerView, presenterUuid, prevPresenterUuid, toggleSpeakerView]);
}, [
presenterUuid,
prevPresenterUuid,
switchToPresentationView,
switchFromPresentationView,
]);
}

View file

@ -27,6 +27,7 @@ import type {
import {
CallingDeviceType,
CallMode,
CallViewMode,
CallState,
GroupCallConnectionState,
GroupCallJoinState,
@ -104,7 +105,7 @@ export type ActiveCallStateType = {
hasLocalAudio: boolean;
hasLocalVideo: boolean;
localAudioLevel: number;
isInSpeakerView: boolean;
viewMode: CallViewMode;
joinedAt?: number;
outgoingRing: boolean;
pip: boolean;
@ -427,6 +428,8 @@ const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
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: 'calling/ACCEPT_CALL_PENDING';
@ -597,6 +600,14 @@ type ToggleSpeakerViewActionType = {
type: 'calling/TOGGLE_SPEAKER_VIEW';
};
type SwitchToPresentationViewActionType = {
type: 'calling/SWITCH_TO_PRESENTATION_VIEW';
};
type SwitchFromPresentationViewActionType = {
type: 'calling/SWITCH_FROM_PRESENTATION_VIEW';
};
export type CallingActionType =
| AcceptCallPendingActionType
| CancelCallActionType
@ -632,7 +643,9 @@ export type CallingActionType =
| TogglePipActionType
| SetPresentingFulfilledActionType
| ToggleSettingsActionType
| ToggleSpeakerViewActionType;
| ToggleSpeakerViewActionType
| SwitchToPresentationViewActionType
| SwitchFromPresentationViewActionType;
// 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 = {
acceptCall,
callStateChange,
@ -1349,6 +1374,8 @@ export const actions = {
setOutgoingRing,
startCall,
startCallingLobby,
switchToPresentationView,
switchFromPresentationView,
toggleParticipants,
togglePip,
toggleScreenRecordingPermissionsDialog,
@ -1457,7 +1484,7 @@ export function reducer(
hasLocalAudio: action.payload.hasLocalAudio,
hasLocalVideo: action.payload.hasLocalVideo,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
pip: false,
safetyNumberChangedUuids: [],
settingsDialogOpen: false,
@ -1485,7 +1512,7 @@ export function reducer(
hasLocalAudio: action.payload.hasLocalAudio,
hasLocalVideo: action.payload.hasLocalVideo,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
pip: false,
safetyNumberChangedUuids: [],
settingsDialogOpen: false,
@ -1508,7 +1535,7 @@ export function reducer(
hasLocalAudio: true,
hasLocalVideo: action.payload.asVideoCall,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
pip: false,
safetyNumberChangedUuids: [],
settingsDialogOpen: false,
@ -1662,7 +1689,7 @@ export function reducer(
hasLocalAudio: action.payload.hasLocalAudio,
hasLocalVideo: action.payload.hasLocalVideo,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
pip: false,
safetyNumberChangedUuids: [],
settingsDialogOpen: false,
@ -2129,11 +2156,61 @@ export function reducer(
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 {
...state,
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,
},
};
}

View file

@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
import type { StateType } from '../reducer';
import type {
ActiveCallStateType,
CallingStateType,
CallsByConversationType,
DirectCallStateType,
@ -13,6 +14,7 @@ import type {
import { getIncomingCall as getIncomingCallHelper } from '../ducks/calling';
import { getUserUuid } from './user';
import { getOwn } from '../../util/getOwn';
import { CallViewMode } from '../../types/Calling';
import type { UUIDStringType } from '../../types/UUID';
export type CallStateType = DirectCallStateType | GroupCallStateType;
@ -71,3 +73,12 @@ export const getIncomingCall = createSelector(
return getIncomingCallHelper(callsByConversation, ourUuid);
}
);
export const isInSpeakerView = (
call: Pick<ActiveCallStateType, 'viewMode'> | undefined
): boolean => {
return Boolean(
call?.viewMode === CallViewMode.Presentation ||
call?.viewMode === CallViewMode.Speaker
);
};

View file

@ -131,7 +131,7 @@ const mapStateToActiveCallProp = (
hasLocalAudio: activeCallState.hasLocalAudio,
hasLocalVideo: activeCallState.hasLocalVideo,
localAudioLevel: activeCallState.localAudioLevel,
isInSpeakerView: activeCallState.isInSpeakerView,
viewMode: activeCallState.viewMode,
joinedAt: activeCallState.joinedAt,
outgoingRing: activeCallState.outgoingRing,
pip: activeCallState.pip,

View file

@ -23,6 +23,7 @@ import { calling as callingService } from '../../../services/calling';
import {
CallMode,
CallState,
CallViewMode,
GroupCallConnectionState,
GroupCallJoinState,
} from '../../../types/Calling';
@ -52,7 +53,7 @@ describe('calling duck', () => {
hasLocalAudio: true,
hasLocalVideo: false,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
showParticipantsList: false,
safetyNumberChangedUuids: [],
outgoingRing: true,
@ -131,7 +132,7 @@ describe('calling duck', () => {
hasLocalAudio: true,
hasLocalVideo: false,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
showParticipantsList: false,
safetyNumberChangedUuids: [],
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 getEmptyRootState = () => {
@ -437,7 +454,7 @@ describe('calling duck', () => {
hasLocalAudio: true,
hasLocalVideo: true,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
showParticipantsList: false,
safetyNumberChangedUuids: [],
outgoingRing: false,
@ -530,7 +547,7 @@ describe('calling duck', () => {
hasLocalAudio: true,
hasLocalVideo: true,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
showParticipantsList: false,
safetyNumberChangedUuids: [],
outgoingRing: false,
@ -1122,7 +1139,7 @@ describe('calling duck', () => {
hasLocalAudio: true,
hasLocalVideo: false,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
showParticipantsList: false,
safetyNumberChangedUuids: [],
outgoingRing: false,
@ -1651,7 +1668,7 @@ describe('calling duck', () => {
hasLocalAudio: true,
hasLocalVideo: true,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
showParticipantsList: false,
safetyNumberChangedUuids: [],
pip: false,
@ -1937,7 +1954,7 @@ describe('calling duck', () => {
hasLocalAudio: true,
hasLocalVideo: false,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
showParticipantsList: false,
safetyNumberChangedUuids: [],
pip: false,
@ -2013,7 +2030,7 @@ describe('calling duck', () => {
describe('toggleSpeakerView', () => {
const { toggleSpeakerView } = actions;
it('toggles speaker view', () => {
it('toggles speaker view from grid view', () => {
const afterOneToggle = reducer(
stateWithActiveGroupCall,
toggleSpeakerView()
@ -2021,9 +2038,92 @@ describe('calling duck', () => {
const afterTwoToggles = reducer(afterOneToggle, toggleSpeakerView());
const afterThreeToggles = reducer(afterTwoToggles, toggleSpeakerView());
assert.isTrue(afterOneToggle.activeCallState?.isInSpeakerView);
assert.isFalse(afterTwoToggles.activeCallState?.isInSpeakerView);
assert.isTrue(afterThreeToggles.activeCallState?.isInSpeakerView);
assert.strictEqual(
afterOneToggle.activeCallState?.viewMode,
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
);
});
});
});

View file

@ -7,6 +7,7 @@ import { noopAction } from '../../../state/ducks/noop';
import {
CallMode,
CallState,
CallViewMode,
GroupCallConnectionState,
GroupCallJoinState,
} from '../../../types/Calling';
@ -52,7 +53,7 @@ describe('state/selectors/calling', () => {
hasLocalAudio: true,
hasLocalVideo: false,
localAudioLevel: 0,
isInSpeakerView: false,
viewMode: CallViewMode.Grid,
showParticipantsList: false,
safetyNumberChangedUuids: [],
outgoingRing: true,

View file

@ -11,6 +11,14 @@ export enum CallMode {
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 = {
appIcon?: string;
id: string;
@ -29,7 +37,7 @@ type ActiveCallBaseType = {
hasLocalAudio: boolean;
hasLocalVideo: boolean;
localAudioLevel: number;
isInSpeakerView: boolean;
viewMode: CallViewMode;
isSharingScreen?: boolean;
joinedAt?: number;
outgoingRing: boolean;