Consider own join time for group call missing media key check

Co-authored-by: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
ayumi-signal 2024-11-01 14:12:49 -07:00 committed by GitHub
parent f9b2261783
commit ec9041937f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 82 additions and 15 deletions

View file

@ -34,6 +34,7 @@ import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGr
import enMessages from '../../_locales/en/messages.json';
import { CallingToastProvider, useCallingToasts } from './CallingToast';
import type { CallingImageDataCache } from './CallManager';
import { MINUTE } from '../util/durations';
const MAX_PARTICIPANTS = 75;
const LOCAL_DEMUX_ID = 1;
@ -158,7 +159,7 @@ const createActiveCallProp = (
overrideProps: DirectCallOverrideProps | GroupCallOverrideProps
) => {
const baseResult = {
joinedAt: Date.now(),
joinedAt: Date.now() - MINUTE,
conversation,
hasLocalAudio: overrideProps.hasLocalAudio ?? false,
hasLocalVideo: overrideProps.hasLocalVideo ?? false,

View file

@ -660,8 +660,7 @@ export function CallScreen({
/>
);
}
// joinedAt is only available for direct calls
if (isConnected) {
if (isConnected && activeCall.callMode === CallMode.Direct) {
return <CallDuration joinedAt={activeCall.joinedAt} />;
}
if (hasLocalVideo) {
@ -713,6 +712,7 @@ export function CallScreen({
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
imageDataCache={imageDataCache}
i18n={i18n}
joinedAt={activeCall.joinedAt}
remoteParticipants={activeCall.remoteParticipants}
setGroupCallVideoRequest={setGroupCallVideoRequest}
remoteAudioLevels={activeCall.remoteAudioLevels}

View file

@ -21,6 +21,7 @@ import { getDefaultConversation } from '../test-both/helpers/getDefaultConversat
import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGroupCallVideoFrameSource';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import { MINUTE } from '../util/durations';
const i18n = setupI18n('en', enMessages);
@ -47,7 +48,7 @@ const getCommonActiveCallData = (overrides: Overrides) => ({
hasLocalVideo: overrides.hasLocalVideo ?? false,
localAudioLevel: overrides.localAudioLevel ?? 0,
viewMode: overrides.viewMode ?? CallViewMode.Paginated,
joinedAt: Date.now(),
joinedAt: Date.now() - MINUTE,
outgoingRing: true,
pip: true,
settingsDialogOpen: false,

View file

@ -188,6 +188,7 @@ export function CallingPipRemoteVideo({
imageDataCache={imageDataCache}
i18n={i18n}
isInPip
joinedAt={activeCall.joinedAt}
remoteParticipant={activeGroupCallSpeaker}
remoteParticipantsCount={activeCall.remoteParticipants.length}
isActiveSpeakerInSpeakerView={false}

View file

@ -14,6 +14,7 @@ import { FRAME_BUFFER_SIZE } from '../calling/constants';
import enMessages from '../../_locales/en/messages.json';
import { generateAci } from '../types/ServiceId';
import type { CallingImageDataCache } from './CallManager';
import { MINUTE } from '../util/durations';
const MAX_PARTICIPANTS = 32;
@ -48,6 +49,7 @@ const defaultProps = {
imageDataCache: React.createRef<CallingImageDataCache>(),
i18n,
isCallReconnecting: false,
joinedAt: new Date().getTime() - MINUTE,
onParticipantVisibilityChanged: action('onParticipantVisibilityChanged'),
remoteAudioLevels: new Map<number, number>(),
remoteParticipantsCount: 1,

View file

@ -22,6 +22,7 @@ export type PropsType = {
i18n: LocalizerType;
imageDataCache: React.RefObject<CallingImageDataCache>;
isCallReconnecting: boolean;
joinedAt: number | null;
onClickRaisedHand?: () => void;
onParticipantVisibilityChanged: (
demuxId: number,
@ -38,6 +39,7 @@ export function GroupCallOverflowArea({
imageDataCache,
i18n,
isCallReconnecting,
joinedAt,
onClickRaisedHand,
onParticipantVisibilityChanged,
overflowedParticipants,
@ -138,6 +140,7 @@ export function GroupCallOverflowArea({
isActiveSpeakerInSpeakerView={false}
isCallReconnecting={isCallReconnecting}
isInOverflow
joinedAt={joinedAt}
/>
))}
</div>

View file

@ -12,6 +12,7 @@ import { setupI18n } from '../util/setupI18n';
import { generateAci } from '../types/ServiceId';
import enMessages from '../../_locales/en/messages.json';
import type { CallingImageDataCache } from './CallManager';
import { MINUTE } from '../util/durations';
const i18n = setupI18n('en', enMessages);
@ -79,6 +80,7 @@ const createProps = (
remoteParticipantsCount: 1,
isActiveSpeakerInSpeakerView: false,
isCallReconnecting: false,
joinedAt: new Date().getTime() - MINUTE,
...overrideProps,
});
@ -186,7 +188,7 @@ export function NoMediaKeys(): JSX.Element {
width: 120,
},
{
addedTime: Date.now() - 60 * 1000,
addedTime: Date.now() - MINUTE,
hasRemoteAudio: true,
mediaKeysReceived: false,
}

View file

@ -44,6 +44,7 @@ type BasePropsType = {
isActiveSpeakerInSpeakerView: boolean;
isCallReconnecting: boolean;
isInOverflow?: boolean;
joinedAt: number | null;
onClickRaisedHand?: () => void;
onVisibilityChanged?: (demuxId: number, isVisible: boolean) => unknown;
remoteParticipant: GroupCallRemoteParticipantType;
@ -82,6 +83,7 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
isActiveSpeakerInSpeakerView,
isCallReconnecting,
isInOverflow,
joinedAt,
} = props;
const {
@ -150,10 +152,17 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
const wantsToShowVideo = hasRemoteVideo && !isBlocked && isVisible;
const hasVideoToShow = wantsToShowVideo && hasReceivedVideoRecently;
// Use the later of participant join time (addedTime) vs your join time (joinedAt)
const timeForMissingMediaKeysCheck =
addedTime && joinedAt && addedTime > joinedAt ? addedTime : joinedAt;
const showMissingMediaKeys = Boolean(
!mediaKeysReceived &&
addedTime &&
isOlderThan(addedTime, DELAY_TO_SHOW_MISSING_MEDIA_KEYS)
timeForMissingMediaKeysCheck &&
isOlderThan(
timeForMissingMediaKeysCheck,
DELAY_TO_SHOW_MISSING_MEDIA_KEYS
)
);
const videoFrameSource = useMemo(

View file

@ -63,6 +63,7 @@ type PropsType = {
i18n: LocalizerType;
imageDataCache: React.RefObject<CallingImageDataCache>;
isCallReconnecting: boolean;
joinedAt: number | null;
remoteParticipants: ReadonlyArray<GroupCallRemoteParticipantType>;
setGroupCallVideoRequest: (
_: Array<GroupCallVideoRequest>,
@ -115,6 +116,7 @@ export function GroupCallRemoteParticipants({
imageDataCache,
i18n,
isCallReconnecting,
joinedAt,
remoteParticipants,
setGroupCallVideoRequest,
remoteAudioLevels,
@ -359,6 +361,7 @@ export function GroupCallRemoteParticipants({
remoteParticipantsCount={remoteParticipants.length}
isActiveSpeakerInSpeakerView={isInSpeakerView}
isCallReconnecting={isCallReconnecting}
joinedAt={joinedAt}
/>
);
});
@ -517,6 +520,7 @@ export function GroupCallRemoteParticipants({
imageDataCache={imageDataCache}
i18n={i18n}
isCallReconnecting={isCallReconnecting}
joinedAt={joinedAt}
onClickRaisedHand={onClickRaisedHand}
onParticipantVisibilityChanged={onParticipantVisibilityChanged}
overflowedParticipants={overflowedParticipants}

View file

@ -3408,14 +3408,22 @@ export function reducer(
state.activeCallState?.state === 'Active' &&
state.activeCallState?.conversationId === conversationId
) {
newActiveCallState =
connectionState === GroupCallConnectionState.NotConnected
? undefined
: {
...state.activeCallState,
hasLocalAudio,
hasLocalVideo,
};
if (connectionState === GroupCallConnectionState.NotConnected) {
newActiveCallState = undefined;
} else {
const joinedAt =
state.activeCallState.joinedAt ??
(connectionState === GroupCallConnectionState.Connected
? new Date().getTime()
: null);
newActiveCallState = {
...state.activeCallState,
hasLocalAudio,
hasLocalVideo,
joinedAt,
};
}
// The first time we detect call participants in the lobby, check participant count
// and mute ourselves if over the threshold.

View file

@ -1265,6 +1265,42 @@ describe('calling duck', () => {
);
assert.isTrue(result.activeCallState?.hasLocalAudio);
assert.isTrue(result.activeCallState?.hasLocalVideo);
assert.isNumber(result.activeCallState?.joinedAt);
});
it('keeps existing activeCallState.joinedAt', () => {
const joinedAt = new Date().getTime() - 1000;
const result = reducer(
{
...stateWithActiveGroupCall,
activeCallState: {
...stateWithActiveDirectCall.activeCallState,
joinedAt,
},
},
getAction({
callMode: CallMode.Group,
conversationId: 'fake-group-call-conversation-id',
connectionState: GroupCallConnectionState.Connected,
joinState: GroupCallJoinState.Joined,
localDemuxId: 1,
hasLocalAudio: true,
hasLocalVideo: true,
peekInfo: {
acis: [],
pendingAcis: [],
maxDevices: 16,
deviceCount: 0,
},
remoteParticipants: [],
})
);
strictAssert(
result.activeCallState?.state === 'Active',
'state is active'
);
assert.equal(result.activeCallState?.joinedAt, joinedAt);
});
it("doesn't stop ringing if nobody is in the call", () => {