Track acceptedTime during call, fix call screen duration

This commit is contained in:
Jamie Kyle 2023-09-20 07:00:01 -07:00 committed by GitHub
parent 95b0f67a47
commit bc67d421ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 92 additions and 64 deletions

View file

@ -149,7 +149,6 @@ function ActiveCallManager({
conversation, conversation,
hasLocalAudio, hasLocalAudio,
hasLocalVideo, hasLocalVideo,
joinedAt,
peekedParticipants, peekedParticipants,
pip, pip,
presentingSourcesAvailable, presentingSourcesAvailable,
@ -327,7 +326,6 @@ function ActiveCallManager({
groupMembers={groupMembers} groupMembers={groupMembers}
hangUpActiveCall={hangUpActiveCall} hangUpActiveCall={hangUpActiveCall}
i18n={i18n} i18n={i18n}
joinedAt={joinedAt}
me={me} me={me}
openSystemPreferencesAction={openSystemPreferencesAction} openSystemPreferencesAction={openSystemPreferencesAction}
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation} setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}

View file

@ -57,7 +57,6 @@ export type PropsType = {
groupMembers?: Array<Pick<ConversationType, 'id' | 'firstName' | 'title'>>; groupMembers?: Array<Pick<ConversationType, 'id' | 'firstName' | 'title'>>;
hangUpActiveCall: (reason: string) => void; hangUpActiveCall: (reason: string) => void;
i18n: LocalizerType; i18n: LocalizerType;
joinedAt?: number;
me: ConversationType; me: ConversationType;
openSystemPreferencesAction: () => unknown; openSystemPreferencesAction: () => unknown;
setGroupCallVideoRequest: ( setGroupCallVideoRequest: (
@ -82,7 +81,7 @@ export type PropsType = {
type DirectCallHeaderMessagePropsType = { type DirectCallHeaderMessagePropsType = {
i18n: LocalizerType; i18n: LocalizerType;
callState: CallState; callState: CallState;
joinedAt?: number; joinedAt: number | null;
}; };
export const isInSpeakerView = ( export const isInSpeakerView = (
@ -104,7 +103,7 @@ function DirectCallHeaderMessage({
>(); >();
useEffect(() => { useEffect(() => {
if (!joinedAt) { if (joinedAt == null) {
return noop; return noop;
} }
// It's really jumpy with a value of 500ms. // It's really jumpy with a value of 500ms.
@ -136,7 +135,6 @@ export function CallScreen({
groupMembers, groupMembers,
hangUpActiveCall, hangUpActiveCall,
i18n, i18n,
joinedAt,
me, me,
openSystemPreferencesAction, openSystemPreferencesAction,
setGroupCallVideoRequest, setGroupCallVideoRequest,
@ -289,7 +287,7 @@ export function CallScreen({
<DirectCallHeaderMessage <DirectCallHeaderMessage
i18n={i18n} i18n={i18n}
callState={activeCall.callState || CallState.Prering} callState={activeCall.callState || CallState.Prering}
joinedAt={joinedAt} joinedAt={activeCall.joinedAt}
/> />
); );
headerTitle = isRinging ? undefined : conversation.title; headerTitle = isRinging ? undefined : conversation.title;

View file

@ -2101,8 +2101,14 @@ export class CallingClass {
return; return;
} }
let acceptedTime: number | null = null;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
call.handleStateChanged = async () => { call.handleStateChanged = async () => {
if (call.state === CallState.Accepted) {
acceptedTime = acceptedTime ?? Date.now();
}
if (call.state === CallState.Ended) { if (call.state === CallState.Ended) {
this.stopDeviceReselectionTimer(); this.stopDeviceReselectionTimer();
this.lastMediaDeviceSettings = undefined; this.lastMediaDeviceSettings = undefined;
@ -2121,6 +2127,7 @@ export class CallingClass {
conversationId: conversation.id, conversationId: conversation.id,
callState: call.state, callState: call.state,
callEndedReason: call.endedReason, callEndedReason: call.endedReason,
acceptedTime,
}); });
}; };

View file

@ -121,7 +121,7 @@ export type ActiveCallStateType = {
hasLocalVideo: boolean; hasLocalVideo: boolean;
localAudioLevel: number; localAudioLevel: number;
viewMode: CallViewMode; viewMode: CallViewMode;
joinedAt?: number; joinedAt: number | null;
outgoingRing: boolean; outgoingRing: boolean;
pip: boolean; pip: boolean;
presentingSource?: PresentedSource; presentingSource?: PresentedSource;
@ -150,7 +150,7 @@ export type AcceptCallType = ReadonlyDeep<{
export type CallStateChangeType = ReadonlyDeep<{ export type CallStateChangeType = ReadonlyDeep<{
conversationId: string; conversationId: string;
acceptedTime?: number; acceptedTime: number | null;
callState: CallState; callState: CallState;
callEndedReason?: CallEndedReason; callEndedReason?: CallEndedReason;
}>; }>;
@ -1647,6 +1647,7 @@ export function reducer(
settingsDialogOpen: false, settingsDialogOpen: false,
showParticipantsList: false, showParticipantsList: false,
outgoingRing, outgoingRing,
joinedAt: null,
}, },
}; };
} }
@ -1675,6 +1676,7 @@ export function reducer(
settingsDialogOpen: false, settingsDialogOpen: false,
showParticipantsList: false, showParticipantsList: false,
outgoingRing: true, outgoingRing: true,
joinedAt: null,
}, },
}; };
} }
@ -1698,6 +1700,7 @@ export function reducer(
settingsDialogOpen: false, settingsDialogOpen: false,
showParticipantsList: false, showParticipantsList: false,
outgoingRing: false, outgoingRing: false,
joinedAt: null,
}, },
}; };
} }
@ -1852,6 +1855,7 @@ export function reducer(
settingsDialogOpen: false, settingsDialogOpen: false,
showParticipantsList: false, showParticipantsList: false,
outgoingRing: true, outgoingRing: true,
joinedAt: null,
}, },
}; };
} }
@ -1882,7 +1886,7 @@ export function reducer(
) { ) {
activeCallState = { activeCallState = {
...state.activeCallState, ...state.activeCallState,
joinedAt: action.payload.acceptedTime, joinedAt: action.payload.acceptedTime ?? null,
}; };
} else { } else {
({ activeCallState } = state); ({ activeCallState } = state);

View file

@ -14,7 +14,10 @@ import type { ConversationType } from '../ducks/conversations';
import { getIncomingCall } from '../selectors/calling'; import { getIncomingCall } from '../selectors/calling';
import { isGroupCallOutboundRingEnabled } from '../../util/isGroupCallOutboundRingEnabled'; import { isGroupCallOutboundRingEnabled } from '../../util/isGroupCallOutboundRingEnabled';
import type { import type {
ActiveCallBaseType,
ActiveCallType, ActiveCallType,
ActiveDirectCallType,
ActiveGroupCallType,
GroupCallRemoteParticipantType, GroupCallRemoteParticipantType,
} from '../../types/Calling'; } from '../../types/Calling';
import { isAciString } from '../../util/isAciString'; import { isAciString } from '../../util/isAciString';
@ -138,7 +141,7 @@ const mapStateToActiveCallProp = (
return convoForAci ? conversationSelector(convoForAci.id) : undefined; return convoForAci ? conversationSelector(convoForAci.id) : undefined;
}); });
const baseResult = { const baseResult: ActiveCallBaseType = {
conversation, conversation,
hasLocalAudio: activeCallState.hasLocalAudio, hasLocalAudio: activeCallState.hasLocalAudio,
hasLocalVideo: activeCallState.hasLocalVideo, hasLocalVideo: activeCallState.hasLocalVideo,
@ -185,7 +188,7 @@ const mapStateToActiveCallProp = (
serviceId: conversation.serviceId, serviceId: conversation.serviceId,
}, },
], ],
}; } satisfies ActiveDirectCallType;
case CallMode.Group: { case CallMode.Group: {
const conversationsWithSafetyNumberChanges: Array<ConversationType> = []; const conversationsWithSafetyNumberChanges: Array<ConversationType> = [];
const groupMembers: Array<ConversationType> = []; const groupMembers: Array<ConversationType> = [];
@ -282,7 +285,7 @@ const mapStateToActiveCallProp = (
peekedParticipants, peekedParticipants,
remoteParticipants, remoteParticipants,
remoteAudioLevels: call.remoteAudioLevels || new Map<number, number>(), remoteAudioLevels: call.remoteAudioLevels || new Map<number, number>(),
}; } satisfies ActiveGroupCallType;
} }
default: default:
throw missingCaseError(call); throw missingCaseError(call);

View file

@ -4,12 +4,16 @@
import { assert } from 'chai'; import { assert } from 'chai';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import { cloneDeep, noop } from 'lodash'; import { cloneDeep, noop } from 'lodash';
import type { PeekInfo } from '@signalapp/ringrtc';
import type { StateType as RootStateType } from '../../../state/reducer'; import type { StateType as RootStateType } from '../../../state/reducer';
import { reducer as rootReducer } from '../../../state/reducer'; import { reducer as rootReducer } from '../../../state/reducer';
import { noopAction } from '../../../state/ducks/noop'; import { noopAction } from '../../../state/ducks/noop';
import type { import type {
ActiveCallStateType,
CallingStateType, CallingStateType,
DirectCallStateType,
GroupCallStateChangeActionType, GroupCallStateChangeActionType,
GroupCallStateType,
} from '../../../state/ducks/calling'; } from '../../../state/ducks/calling';
import { import {
actions, actions,
@ -33,25 +37,30 @@ import type { UnwrapPromise } from '../../../types/Util';
const ACI_1 = generateAci(); const ACI_1 = generateAci();
type CallingStateTypeWithActiveCall = CallingStateType & {
activeCallState: ActiveCallStateType;
};
describe('calling duck', () => { describe('calling duck', () => {
const directCallState: DirectCallStateType = {
callMode: CallMode.Direct,
conversationId: 'fake-direct-call-conversation-id',
callState: CallState.Accepted,
isIncoming: false,
isVideoCall: false,
hasRemoteVideo: false,
};
const stateWithDirectCall: CallingStateType = { const stateWithDirectCall: CallingStateType = {
...getEmptyState(), ...getEmptyState(),
callsByConversation: { callsByConversation: {
'fake-direct-call-conversation-id': { [directCallState.conversationId]: directCallState,
callMode: CallMode.Direct as CallMode.Direct,
conversationId: 'fake-direct-call-conversation-id',
callState: CallState.Accepted,
isIncoming: false,
isVideoCall: false,
hasRemoteVideo: false,
},
}, },
}; };
const stateWithActiveDirectCall = { const stateWithActiveDirectCall: CallingStateTypeWithActiveCall = {
...stateWithDirectCall, ...stateWithDirectCall,
activeCallState: { activeCallState: {
conversationId: 'fake-direct-call-conversation-id', conversationId: directCallState.conversationId,
hasLocalAudio: true, hasLocalAudio: true,
hasLocalVideo: false, hasLocalVideo: false,
localAudioLevel: 0, localAudioLevel: 0,
@ -61,20 +70,21 @@ describe('calling duck', () => {
outgoingRing: true, outgoingRing: true,
pip: false, pip: false,
settingsDialogOpen: false, settingsDialogOpen: false,
joinedAt: null,
}, },
}; };
const stateWithIncomingDirectCall = { const stateWithIncomingDirectCall: CallingStateType = {
...getEmptyState(), ...getEmptyState(),
callsByConversation: { callsByConversation: {
'fake-direct-call-conversation-id': { 'fake-direct-call-conversation-id': {
callMode: CallMode.Direct as CallMode.Direct, callMode: CallMode.Direct,
conversationId: 'fake-direct-call-conversation-id', conversationId: 'fake-direct-call-conversation-id',
callState: CallState.Ringing, callState: CallState.Ringing,
isIncoming: true, isIncoming: true,
isVideoCall: false, isVideoCall: false,
hasRemoteVideo: false, hasRemoteVideo: false,
}, } satisfies DirectCallStateType,
}, },
}; };
@ -83,11 +93,11 @@ describe('calling duck', () => {
const remoteAci = generateAci(); const remoteAci = generateAci();
const ringerAci = generateAci(); const ringerAci = generateAci();
const stateWithGroupCall = { const stateWithGroupCall: CallingStateType = {
...getEmptyState(), ...getEmptyState(),
callsByConversation: { callsByConversation: {
'fake-group-call-conversation-id': { 'fake-group-call-conversation-id': {
callMode: CallMode.Group as CallMode.Group, callMode: CallMode.Group,
conversationId: 'fake-group-call-conversation-id', conversationId: 'fake-group-call-conversation-id',
connectionState: GroupCallConnectionState.Connected, connectionState: GroupCallConnectionState.Connected,
joinState: GroupCallJoinState.NotJoined, joinState: GroupCallJoinState.NotJoined,
@ -109,11 +119,11 @@ describe('calling duck', () => {
videoAspectRatio: 4 / 3, videoAspectRatio: 4 / 3,
}, },
], ],
}, } satisfies GroupCallStateType,
}, },
}; };
const stateWithIncomingGroupCall = { const stateWithIncomingGroupCall: CallingStateType = {
...stateWithGroupCall, ...stateWithGroupCall,
callsByConversation: { callsByConversation: {
...stateWithGroupCall.callsByConversation, ...stateWithGroupCall.callsByConversation,
@ -127,7 +137,7 @@ describe('calling duck', () => {
}, },
}; };
const stateWithActiveGroupCall = { const stateWithActiveGroupCall: CallingStateTypeWithActiveCall = {
...stateWithGroupCall, ...stateWithGroupCall,
activeCallState: { activeCallState: {
conversationId: 'fake-group-call-conversation-id', conversationId: 'fake-group-call-conversation-id',
@ -140,18 +150,20 @@ describe('calling duck', () => {
outgoingRing: false, outgoingRing: false,
pip: false, pip: false,
settingsDialogOpen: false, settingsDialogOpen: false,
joinedAt: null,
}, },
}; };
const stateWithActivePresentationViewGroupCall = { const stateWithActivePresentationViewGroupCall: CallingStateTypeWithActiveCall =
...stateWithGroupCall, {
activeCallState: { ...stateWithGroupCall,
...stateWithActiveGroupCall.activeCallState, activeCallState: {
viewMode: CallViewMode.Presentation, ...stateWithActiveGroupCall.activeCallState,
}, viewMode: CallViewMode.Presentation,
}; },
};
const stateWithActiveSpeakerViewGroupCall = { const stateWithActiveSpeakerViewGroupCall: CallingStateTypeWithActiveCall = {
...stateWithGroupCall, ...stateWithGroupCall,
activeCallState: { activeCallState: {
...stateWithActiveGroupCall.activeCallState, ...stateWithActiveGroupCall.activeCallState,
@ -240,20 +252,18 @@ describe('calling duck', () => {
isSharingScreen: true, isSharingScreen: true,
}; };
const state = { const state: CallingStateTypeWithActiveCall = {
...stateWithActiveDirectCall, ...stateWithActiveDirectCall,
}; };
const nextState = reducer(state, remoteSharingScreenChange(payload)); const nextState = reducer(state, remoteSharingScreenChange(payload));
const expectedState = { const expectedState: CallingStateTypeWithActiveCall = {
...stateWithActiveDirectCall, ...stateWithActiveDirectCall,
callsByConversation: { callsByConversation: {
'fake-direct-call-conversation-id': { [directCallState.conversationId]: {
...stateWithActiveDirectCall.callsByConversation[ ...directCallState,
'fake-direct-call-conversation-id'
],
isSharingScreen: true, isSharingScreen: true,
}, } satisfies DirectCallStateType,
}, },
}; };
@ -276,7 +286,7 @@ describe('calling duck', () => {
id: 'window:786', id: 'window:786',
name: 'Application', name: 'Application',
}; };
const getState = () => ({ const getState = (): RootStateType => ({
...getEmptyRootState(), ...getEmptyRootState(),
calling: { calling: {
...stateWithActiveGroupCall, ...stateWithActiveGroupCall,
@ -301,7 +311,7 @@ describe('calling duck', () => {
id: 'window:786', id: 'window:786',
name: 'Application', name: 'Application',
}; };
const getState = () => ({ const getState = (): RootStateType => ({
...getEmptyRootState(), ...getEmptyRootState(),
calling: { calling: {
...stateWithActiveGroupCall, ...stateWithActiveGroupCall,
@ -325,7 +335,7 @@ describe('calling duck', () => {
name: 'Application', name: 'Application',
}; };
const getState = () => ({ const getState = (): RootStateType => ({
...getEmptyRootState(), ...getEmptyRootState(),
calling: { calling: {
...stateWithActiveGroupCall, ...stateWithActiveGroupCall,
@ -352,7 +362,7 @@ describe('calling duck', () => {
const dispatch = sinon.spy(); const dispatch = sinon.spy();
const { setPresenting } = actions; const { setPresenting } = actions;
const getState = () => ({ const getState = (): RootStateType => ({
...getEmptyRootState(), ...getEmptyRootState(),
calling: { calling: {
...stateWithActiveGroupCall, ...stateWithActiveGroupCall,
@ -386,7 +396,7 @@ describe('calling duck', () => {
}); });
describe('accepting a direct call', () => { describe('accepting a direct call', () => {
const getState = () => ({ const getState = (): RootStateType => ({
...getEmptyRootState(), ...getEmptyRootState(),
calling: stateWithIncomingDirectCall, calling: stateWithIncomingDirectCall,
}); });
@ -472,12 +482,13 @@ describe('calling duck', () => {
outgoingRing: false, outgoingRing: false,
pip: false, pip: false,
settingsDialogOpen: false, settingsDialogOpen: false,
}); joinedAt: null,
} satisfies ActiveCallStateType);
}); });
}); });
describe('accepting a group call', () => { describe('accepting a group call', () => {
const getState = () => ({ const getState = (): RootStateType => ({
...getEmptyRootState(), ...getEmptyRootState(),
calling: stateWithIncomingGroupCall, calling: stateWithIncomingGroupCall,
}); });
@ -565,7 +576,8 @@ describe('calling duck', () => {
outgoingRing: false, outgoingRing: false,
pip: false, pip: false,
settingsDialogOpen: false, settingsDialogOpen: false,
}); joinedAt: null,
} satisfies ActiveCallStateType);
}); });
}); });
}); });
@ -677,7 +689,7 @@ describe('calling duck', () => {
}); });
describe('declining a direct call', () => { describe('declining a direct call', () => {
const getState = () => ({ const getState = (): RootStateType => ({
...getEmptyRootState(), ...getEmptyRootState(),
calling: stateWithIncomingDirectCall, calling: stateWithIncomingDirectCall,
}); });
@ -735,7 +747,7 @@ describe('calling duck', () => {
}); });
describe('declining a group call', () => { describe('declining a group call', () => {
const getState = () => ({ const getState = (): RootStateType => ({
...getEmptyRootState(), ...getEmptyRootState(),
calling: stateWithIncomingGroupCall, calling: stateWithIncomingGroupCall,
}); });
@ -1157,7 +1169,8 @@ describe('calling duck', () => {
outgoingRing: false, outgoingRing: false,
pip: false, pip: false,
settingsDialogOpen: false, settingsDialogOpen: false,
}); joinedAt: null,
} satisfies ActiveCallStateType);
}); });
it('if the call is active, updates the active call state', () => { it('if the call is active, updates the active call state', () => {
@ -1225,7 +1238,7 @@ describe('calling duck', () => {
}); });
it('stops ringing if someone enters the call', () => { it('stops ringing if someone enters the call', () => {
const state = { const state: CallingStateType = {
...stateWithActiveGroupCall, ...stateWithActiveGroupCall,
activeCallState: { activeCallState: {
...stateWithActiveGroupCall.activeCallState, ...stateWithActiveGroupCall.activeCallState,
@ -1328,7 +1341,7 @@ describe('calling duck', () => {
}); });
it('closes the PiP', () => { it('closes the PiP', () => {
const state = { const state: CallingStateType = {
...stateWithActiveDirectCall, ...stateWithActiveDirectCall,
activeCallState: { activeCallState: {
...stateWithActiveDirectCall.activeCallState, ...stateWithActiveDirectCall.activeCallState,
@ -1576,9 +1589,11 @@ describe('calling duck', () => {
noop, noop,
() => { () => {
const callingState = cloneDeep(stateWithGroupCall); const callingState = cloneDeep(stateWithGroupCall);
callingState.callsByConversation[ const call = callingState.callsByConversation[
'fake-group-call-conversation-id' 'fake-group-call-conversation-id'
].peekInfo.deviceCount = 8; ] as GroupCallStateType;
const peekInfo = call.peekInfo as unknown as PeekInfo;
peekInfo.deviceCount = 8;
return { ...rootState, calling: callingState }; return { ...rootState, calling: callingState };
}, },
null null
@ -1686,7 +1701,8 @@ describe('calling duck', () => {
pip: false, pip: false,
settingsDialogOpen: false, settingsDialogOpen: false,
outgoingRing: true, outgoingRing: true,
}); joinedAt: null,
} satisfies ActiveCallStateType);
}); });
it('saves a group call and makes it active', async () => { it('saves a group call and makes it active', async () => {
@ -1972,6 +1988,7 @@ describe('calling duck', () => {
pip: false, pip: false,
settingsDialogOpen: false, settingsDialogOpen: false,
outgoingRing: true, outgoingRing: true,
joinedAt: null,
}); });
}); });

View file

@ -72,6 +72,7 @@ describe('state/selectors/calling', () => {
outgoingRing: true, outgoingRing: true,
pip: false, pip: false,
settingsDialogOpen: false, settingsDialogOpen: false,
joinedAt: null,
}, },
}; };

View file

@ -32,14 +32,14 @@ export type PresentedSource = {
name: string; name: string;
}; };
type ActiveCallBaseType = { export type ActiveCallBaseType = {
conversation: ConversationType; conversation: ConversationType;
hasLocalAudio: boolean; hasLocalAudio: boolean;
hasLocalVideo: boolean; hasLocalVideo: boolean;
localAudioLevel: number; localAudioLevel: number;
viewMode: CallViewMode; viewMode: CallViewMode;
isSharingScreen?: boolean; isSharingScreen?: boolean;
joinedAt?: number; joinedAt: number | null;
outgoingRing: boolean; outgoingRing: boolean;
pip: boolean; pip: boolean;
presentingSource?: PresentedSource; presentingSource?: PresentedSource;