1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

Refactor the way group calls hang up (#3234)

* Refactor how group call end calls

We previously used disposeCall to terminate the call which meant that
sometimes a call would never get a hangup event. This changes it so
that we always end a call by calling hangup, then do the cleanup
when the hangup event arrives, so the cleanup is the same whether
we hang up or the other side does.

* Some fixes for failing & hanging tests

* Add type for the call map
This commit is contained in:
David Baker
2023-03-30 16:57:47 +01:00
committed by GitHub
parent 62f1dd79bc
commit bc76532bd5
3 changed files with 22 additions and 11 deletions

View File

@ -511,6 +511,8 @@ export class MockMatrixCall extends TypedEventEmitter<CallEvent, CallEventHandle
public callId = "1"; public callId = "1";
public localUsermediaFeed = { public localUsermediaFeed = {
setAudioVideoMuted: jest.fn<void, [boolean, boolean]>(), setAudioVideoMuted: jest.fn<void, [boolean, boolean]>(),
isAudioMuted: jest.fn().mockReturnValue(false),
isVideoMuted: jest.fn().mockReturnValue(false),
stream: new MockMediaStream("stream"), stream: new MockMediaStream("stream"),
} as unknown as CallFeed; } as unknown as CallFeed;
public remoteUsermediaFeed?: CallFeed; public remoteUsermediaFeed?: CallFeed;

View File

@ -144,6 +144,10 @@ describe("Group Call", function () {
} as unknown as RoomMember; } as unknown as RoomMember;
}); });
afterEach(() => {
groupCall.leave();
});
it.each(Object.values(GroupCallState).filter((v) => v !== GroupCallState.LocalCallFeedUninitialized))( it.each(Object.values(GroupCallState).filter((v) => v !== GroupCallState.LocalCallFeedUninitialized))(
"throws when initializing local call feed in %s state", "throws when initializing local call feed in %s state",
async (state: GroupCallState) => { async (state: GroupCallState) => {

View File

@ -42,6 +42,8 @@ export enum GroupCallTerminationReason {
CallEnded = "call_ended", CallEnded = "call_ended",
} }
export type CallsByUserAndDevice = Map<string, Map<string, MatrixCall>>;
/** /**
* Because event names are just strings, they do need * Because event names are just strings, they do need
* to be unique over all event types of event emitter. * to be unique over all event types of event emitter.
@ -62,7 +64,7 @@ export enum GroupCallEvent {
export type GroupCallEventHandlerMap = { export type GroupCallEventHandlerMap = {
[GroupCallEvent.GroupCallStateChanged]: (newState: GroupCallState, oldState: GroupCallState) => void; [GroupCallEvent.GroupCallStateChanged]: (newState: GroupCallState, oldState: GroupCallState) => void;
[GroupCallEvent.ActiveSpeakerChanged]: (activeSpeaker: CallFeed | undefined) => void; [GroupCallEvent.ActiveSpeakerChanged]: (activeSpeaker: CallFeed | undefined) => void;
[GroupCallEvent.CallsChanged]: (calls: Map<string, Map<string, MatrixCall>>) => void; [GroupCallEvent.CallsChanged]: (calls: CallsByUserAndDevice) => void;
[GroupCallEvent.UserMediaFeedsChanged]: (feeds: CallFeed[]) => void; [GroupCallEvent.UserMediaFeedsChanged]: (feeds: CallFeed[]) => void;
[GroupCallEvent.ScreenshareFeedsChanged]: (feeds: CallFeed[]) => void; [GroupCallEvent.ScreenshareFeedsChanged]: (feeds: CallFeed[]) => void;
[GroupCallEvent.LocalScreenshareStateChanged]: ( [GroupCallEvent.LocalScreenshareStateChanged]: (
@ -528,12 +530,16 @@ export class GroupCall extends TypedEventEmitter<
this.retryCallLoopInterval = undefined; this.retryCallLoopInterval = undefined;
} }
if (this.participantsExpirationTimer !== null) {
clearTimeout(this.participantsExpirationTimer);
this.participantsExpirationTimer = null;
}
if (this.state !== GroupCallState.Entered) { if (this.state !== GroupCallState.Entered) {
return; return;
} }
this.forEachCall((call) => this.disposeCall(call, CallErrorCode.UserHangup)); this.forEachCall((call) => call.hangup(CallErrorCode.UserHangup, false));
this.calls.clear();
this.activeSpeaker = undefined; this.activeSpeaker = undefined;
clearInterval(this.activeSpeakerLoopInterval); clearInterval(this.activeSpeakerLoopInterval);
@ -869,7 +875,8 @@ export class GroupCall extends TypedEventEmitter<
`GroupCall ${this.groupCallId} onIncomingCall() incoming call (userId=${opponentUserId}, callId=${newCall.callId})`, `GroupCall ${this.groupCallId} onIncomingCall() incoming call (userId=${opponentUserId}, callId=${newCall.callId})`,
); );
if (prevCall) this.disposeCall(prevCall, CallErrorCode.Replaced); if (prevCall) prevCall.hangup(CallErrorCode.Replaced, false);
this.initCall(newCall); this.initCall(newCall);
const feeds = this.getLocalFeeds().map((feed) => feed.clone()); const feeds = this.getLocalFeeds().map((feed) => feed.clone());
@ -928,7 +935,7 @@ export class GroupCall extends TypedEventEmitter<
logger.debug( logger.debug(
`GroupCall ${this.groupCallId} placeOutgoingCalls() replacing call (userId=${userId}, deviceId=${deviceId}, callId=${prevCall.callId})`, `GroupCall ${this.groupCallId} placeOutgoingCalls() replacing call (userId=${userId}, deviceId=${deviceId}, callId=${prevCall.callId})`,
); );
this.disposeCall(prevCall, CallErrorCode.NewSession); prevCall.hangup(CallErrorCode.NewSession, false);
} }
const newCall = createNewMatrixCall(this.client, this.room.roomId, { const newCall = createNewMatrixCall(this.client, this.room.roomId, {
@ -979,7 +986,7 @@ export class GroupCall extends TypedEventEmitter<
); );
} }
this.disposeCall(newCall, CallErrorCode.SignallingFailed); newCall.hangup(CallErrorCode.SignallingFailed, false);
if (callMap.get(deviceId) === newCall) callMap.delete(deviceId); if (callMap.get(deviceId) === newCall) callMap.delete(deviceId);
}); });
} }
@ -1101,10 +1108,6 @@ export class GroupCall extends TypedEventEmitter<
return; return;
} }
if (call.state !== CallState.Ended) {
call.hangup(hangupReason, false);
}
const usermediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId); const usermediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);
if (usermediaFeed) { if (usermediaFeed) {
@ -1156,6 +1159,8 @@ export class GroupCall extends TypedEventEmitter<
}; };
private onCallStateChanged = (call: MatrixCall, state: CallState, _oldState: CallState | undefined): void => { private onCallStateChanged = (call: MatrixCall, state: CallState, _oldState: CallState | undefined): void => {
if (state === CallState.Ended) return;
const audioMuted = this.localCallFeed!.isAudioMuted(); const audioMuted = this.localCallFeed!.isAudioMuted();
if (call.localUsermediaStream && call.isMicrophoneMuted() !== audioMuted) { if (call.localUsermediaStream && call.isMicrophoneMuted() !== audioMuted) {
@ -1200,7 +1205,7 @@ export class GroupCall extends TypedEventEmitter<
this.calls.set(opponentUserId, deviceMap); this.calls.set(opponentUserId, deviceMap);
} }
this.disposeCall(prevCall, CallErrorCode.Replaced); prevCall.hangup(CallErrorCode.Replaced, false);
this.initCall(newCall); this.initCall(newCall);
deviceMap.set(prevCall.getOpponentDeviceId()!, newCall); deviceMap.set(prevCall.getOpponentDeviceId()!, newCall);
this.emit(GroupCallEvent.CallsChanged, this.calls); this.emit(GroupCallEvent.CallsChanged, this.calls);