From 0af2f797a9315c6e9eda76f80c7587c2bf20e2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 4 Dec 2022 15:45:55 +0100 Subject: [PATCH 1/7] Refactor state handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/call.spec.ts | 4 +-- src/webrtc/call.ts | 50 +++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index ee52a518a36..d1cb5d29616 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -1392,7 +1392,7 @@ describe('Call', function() { it("ends call on onHangupReceived() if state is ringing", async () => { expect(call.callHasEnded()).toBe(false); - call.state = CallState.Ringing; + (call as any).state = CallState.Ringing; call.onHangupReceived({} as MCallHangupReject); expect(call.callHasEnded()).toBe(true); @@ -1424,7 +1424,7 @@ describe('Call', function() { )("ends call on onRejectReceived() if in correct state (state=%s)", async (state: CallState) => { expect(call.callHasEnded()).toBe(false); - call.state = state; + (call as any).state = state; call.onRejectReceived({} as MCallHangupReject); expect(call.callHasEnded()).toBe( diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 8b4882c960a..b2d0c1e46db 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -334,7 +334,6 @@ export class MatrixCall extends TypedEventEmitter; @@ -482,6 +482,16 @@ export class MatrixCall extends TypedEventEmitter { @@ -1762,7 +1772,7 @@ export class MatrixCall extends TypedEventEmitter { this.inviteTimeout = undefined; if (this.state === CallState.InviteSent) { @@ -2088,7 +2098,7 @@ export class MatrixCall extends TypedEventEmitter { @@ -2112,7 +2122,7 @@ export class MatrixCall extends TypedEventEmitter Date: Sun, 4 Dec 2022 15:50:08 +0100 Subject: [PATCH 2/7] Don't expose `calls` on `GroupCall` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/groupCall.spec.ts | 26 ++++++++++++++--------- src/webrtc/call.ts | 2 ++ src/webrtc/callFeed.ts | 34 ++++++++++++++++++++++++++++++ src/webrtc/groupCall.ts | 2 +- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/spec/unit/webrtc/groupCall.spec.ts b/spec/unit/webrtc/groupCall.spec.ts index 3c9266b98c8..a300ebac8ed 100644 --- a/spec/unit/webrtc/groupCall.spec.ts +++ b/spec/unit/webrtc/groupCall.spec.ts @@ -515,7 +515,7 @@ describe('Group Call', function() { it("sends metadata updates before unmuting in PTT mode", async () => { const mockCall = new MockCall(FAKE_ROOM_ID, groupCall.groupCallId); - groupCall.calls.set( + (groupCall as any).calls.set( mockCall.getOpponentMember() as RoomMember, new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]), ); @@ -540,7 +540,7 @@ describe('Group Call', function() { it("sends metadata updates after muting in PTT mode", async () => { const mockCall = new MockCall(FAKE_ROOM_ID, groupCall.groupCallId); - groupCall.calls.set( + (groupCall as any).calls.set( mockCall.getOpponentMember() as RoomMember, new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]), ); @@ -698,7 +698,7 @@ describe('Group Call', function() { expect(client1.sendToDevice).toHaveBeenCalled(); - const oldCall = groupCall1.calls.get( + const oldCall = (groupCall1 as any).calls.get( groupCall1.room.getMember(client2.userId)!, )!.get(client2.deviceId)!; oldCall.emit(CallEvent.Hangup, oldCall!); @@ -719,7 +719,7 @@ describe('Group Call', function() { // to even be created... let newCall: MatrixCall | undefined; while ( - (newCall = groupCall1.calls.get( + (newCall = (groupCall1 as any).calls.get( groupCall1.room.getMember(client2.userId)!, )?.get(client2.deviceId)) === undefined || newCall.peerConn === undefined @@ -763,7 +763,7 @@ describe('Group Call', function() { groupCall1.setMicrophoneMuted(false); groupCall1.setLocalVideoMuted(false); - const call = groupCall1.calls.get( + const call = (groupCall1 as any).calls.get( groupCall1.room.getMember(client2.userId)!, )!.get(client2.deviceId)!; call.isMicrophoneMuted = jest.fn().mockReturnValue(true); @@ -874,7 +874,9 @@ describe('Group Call', function() { // It takes a bit of time for the calls to get created await sleep(10); - const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!; + const call = (groupCall as any).calls + .get(groupCall.room.getMember(FAKE_USER_ID_2)!)! + .get(FAKE_DEVICE_ID_2)!; call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember; // @ts-ignore Mock call.pushRemoteFeed(new MockMediaStream("stream", [ @@ -897,7 +899,9 @@ describe('Group Call', function() { // It takes a bit of time for the calls to get created await sleep(10); - const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!; + const call = (groupCall as any).calls + .get(groupCall.room.getMember(FAKE_USER_ID_2)!)! + .get(FAKE_DEVICE_ID_2)!; call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember; // @ts-ignore Mock call.pushRemoteFeed(new MockMediaStream("stream", [ @@ -972,7 +976,7 @@ describe('Group Call', function() { expect(mockCall.reject).not.toHaveBeenCalled(); expect(mockCall.answerWithCallFeeds).toHaveBeenCalled(); - expect(groupCall.calls).toEqual(new Map([[ + expect((groupCall as any).calls).toEqual(new Map([[ groupCall.room.getMember(FAKE_USER_ID_1)!, new Map([[FAKE_DEVICE_ID_1, mockCall]]), ]])); @@ -989,7 +993,7 @@ describe('Group Call', function() { expect(oldMockCall.hangup).toHaveBeenCalled(); expect(newMockCall.answerWithCallFeeds).toHaveBeenCalled(); - expect(groupCall.calls).toEqual(new Map([[ + expect((groupCall as any).calls).toEqual(new Map([[ groupCall.room.getMember(FAKE_USER_ID_1)!, new Map([[FAKE_DEVICE_ID_1, newMockCall]]), ]])); @@ -1072,7 +1076,9 @@ describe('Group Call', function() { // It takes a bit of time for the calls to get created await sleep(10); - const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!; + const call = (groupCall as any).calls + .get(groupCall.room.getMember(FAKE_USER_ID_2)!)! + .get(FAKE_DEVICE_ID_2)!; call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember; call.onNegotiateReceived({ getContent: () => ({ diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index b2d0c1e46db..ad5e6668bcd 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -656,6 +656,7 @@ export class MatrixCall extends TypedEventEmitter void; [CallFeedEvent.LocalVolumeChanged]: (localVolume: number) => void; [CallFeedEvent.VolumeChanged]: (volume: number) => void; + [CallFeedEvent.ConnectedChanged]: (connected: boolean) => void; [CallFeedEvent.Speaking]: (speaking: boolean) => void; [CallFeedEvent.Disposed]: () => void; }; @@ -69,6 +76,7 @@ export class CallFeed extends TypedEventEmitter public speakingVolumeSamples: number[]; private client: MatrixClient; + private call?: MatrixCall; private roomId?: string; private audioMuted: boolean; private videoMuted: boolean; @@ -81,11 +89,13 @@ export class CallFeed extends TypedEventEmitter private speaking = false; private volumeLooperTimeout?: ReturnType; private _disposed = false; + private _connected = false; public constructor(opts: ICallFeedOpts) { super(); this.client = opts.client; + this.call = opts.call; this.roomId = opts.roomId; this.userId = opts.userId; this.deviceId = opts.deviceId; @@ -101,6 +111,21 @@ export class CallFeed extends TypedEventEmitter if (this.hasAudioTrack) { this.initVolumeMeasuring(); } + + if (opts.call) { + opts.call.addListener(CallEvent.State, this.onCallState); + this.onCallState(opts.call.state); + } + } + + public get connected(): boolean { + // Local feeds are always considered connected + return this.isLocal() || this._connected; + } + + private set connected(connected: boolean) { + this._connected = connected; + this.emit(CallFeedEvent.ConnectedChanged, connected); } private get hasAudioTrack(): boolean { @@ -145,6 +170,14 @@ export class CallFeed extends TypedEventEmitter this.emit(CallFeedEvent.NewStream, this.stream); }; + private onCallState = (state: CallState): void => { + if (state === CallState.Connected) { + this.connected = true; + } else if (state === CallState.Connecting) { + this.connected = false; + } + }; + /** * Returns callRoom member * @returns member of the callRoom @@ -297,6 +330,7 @@ export class CallFeed extends TypedEventEmitter public dispose(): void { clearTimeout(this.volumeLooperTimeout); this.stream?.removeEventListener("addtrack", this.onAddTrack); + this.call?.removeListener(CallEvent.State, this.onCallState); if (this.audioContext) { this.audioContext = undefined; this.analyser = undefined; diff --git a/src/webrtc/groupCall.ts b/src/webrtc/groupCall.ts index fde46182bc1..5941b6a3728 100644 --- a/src/webrtc/groupCall.ts +++ b/src/webrtc/groupCall.ts @@ -168,11 +168,11 @@ export class GroupCall extends TypedEventEmitter< public localCallFeed?: CallFeed; public localScreenshareFeed?: CallFeed; public localDesktopCapturerSourceId?: string; - public readonly calls = new Map>(); public readonly userMediaFeeds: CallFeed[] = []; public readonly screenshareFeeds: CallFeed[] = []; public groupCallId: string; + private readonly calls = new Map>(); // RoomMember -> device ID -> MatrixCall private callHandlers = new Map>(); // User ID -> device ID -> handlers private activeSpeakerLoopInterval?: ReturnType; private retryCallLoopInterval?: ReturnType; From 30c1baff42561005d0ac64ce94aca4b14a0f4219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 5 Dec 2022 12:23:34 +0100 Subject: [PATCH 3/7] Move `MockCall` to `MockMatrixCall` in `webrtc.ts` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/test-utils/webrtc.ts | 53 +++++++++++++++++++- spec/unit/webrtc/groupCall.spec.ts | 80 ++++++++---------------------- 2 files changed, 74 insertions(+), 59 deletions(-) diff --git a/spec/test-utils/webrtc.ts b/spec/test-utils/webrtc.ts index 6a1b1a35794..2286940d2a0 100644 --- a/spec/test-utils/webrtc.ts +++ b/spec/test-utils/webrtc.ts @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { EventEmitter } from "stream"; + import { ClientEvent, ClientEventHandlerMap, @@ -26,6 +28,7 @@ import { MatrixClient, MatrixEvent, Room, + RoomMember, RoomState, RoomStateEvent, RoomStateEventHandlerMap, @@ -33,7 +36,7 @@ import { import { TypedEventEmitter } from "../../src/models/typed-event-emitter"; import { ReEmitter } from "../../src/ReEmitter"; import { SyncState } from "../../src/sync"; -import { CallEvent, CallEventHandlerMap, MatrixCall } from "../../src/webrtc/call"; +import { CallEvent, CallEventHandlerMap, CallState, MatrixCall } from "../../src/webrtc/call"; import { CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from "../../src/webrtc/callEventHandler"; import { CallFeed } from "../../src/webrtc/callFeed"; import { GroupCallEventHandlerMap } from "../../src/webrtc/groupCall"; @@ -83,6 +86,17 @@ export const DUMMY_SDP = ( export const USERMEDIA_STREAM_ID = "mock_stream_from_media_handler"; export const SCREENSHARE_STREAM_ID = "mock_screen_stream_from_media_handler"; +export const FAKE_ROOM_ID = "!fake:test.dummy"; +export const FAKE_CONF_ID = "fakegroupcallid"; + +export const FAKE_USER_ID_1 = "@alice:test.dummy"; +export const FAKE_DEVICE_ID_1 = "@AAAAAA"; +export const FAKE_SESSION_ID_1 = "alice1"; +export const FAKE_USER_ID_2 = "@bob:test.dummy"; +export const FAKE_DEVICE_ID_2 = "@BBBBBB"; +export const FAKE_SESSION_ID_2 = "bob1"; +export const FAKE_USER_ID_3 = "@charlie:test.dummy"; + class MockMediaStreamAudioSourceNode { public connect() {} } @@ -431,6 +445,43 @@ export class MockCallMatrixClient extends TypedEventEmitter(), + stream: new MockMediaStream("stream"), + }; + public remoteUsermediaFeed?: CallFeed; + public remoteScreensharingFeed?: CallFeed; + + public reject = jest.fn(); + public answerWithCallFeeds = jest.fn(); + public hangup = jest.fn(); + + public sendMetadataUpdate = jest.fn(); + + public on = jest.fn(); + public removeListener = jest.fn(); + + public getOpponentMember(): Partial { + return this.opponentMember; + } + + public getOpponentDeviceId(): string | undefined { + return this.opponentDeviceId; + } + + public typed(): MatrixCall { return this as unknown as MatrixCall; } +} + export class MockCallFeed { constructor( public userId: string, diff --git a/spec/unit/webrtc/groupCall.spec.ts b/spec/unit/webrtc/groupCall.spec.ts index a300ebac8ed..0e6605fc79f 100644 --- a/spec/unit/webrtc/groupCall.spec.ts +++ b/spec/unit/webrtc/groupCall.spec.ts @@ -33,6 +33,16 @@ import { MockMediaStream, MockMediaStreamTrack, MockRTCPeerConnection, + MockMatrixCall, + FAKE_ROOM_ID, + FAKE_USER_ID_1, + FAKE_CONF_ID, + FAKE_DEVICE_ID_2, + FAKE_SESSION_ID_2, + FAKE_USER_ID_2, + FAKE_DEVICE_ID_1, + FAKE_SESSION_ID_1, + FAKE_USER_ID_3, } from '../../test-utils/webrtc'; import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes"; import { sleep } from "../../../src/utils"; @@ -41,16 +51,6 @@ import { CallFeed } from '../../../src/webrtc/callFeed'; import { CallEvent, CallState } from '../../../src/webrtc/call'; import { flushPromises } from '../../test-utils/flushPromises'; -const FAKE_ROOM_ID = "!fake:test.dummy"; -const FAKE_CONF_ID = "fakegroupcallid"; - -const FAKE_USER_ID_1 = "@alice:test.dummy"; -const FAKE_DEVICE_ID_1 = "@AAAAAA"; -const FAKE_SESSION_ID_1 = "alice1"; -const FAKE_USER_ID_2 = "@bob:test.dummy"; -const FAKE_DEVICE_ID_2 = "@BBBBBB"; -const FAKE_SESSION_ID_2 = "bob1"; -const FAKE_USER_ID_3 = "@charlie:test.dummy"; const FAKE_STATE_EVENTS = [ { getContent: () => ({ @@ -123,42 +123,6 @@ const createAndEnterGroupCall = async (cli: MatrixClient, room: Room): Promise(), - stream: new MockMediaStream("stream"), - }; - public remoteUsermediaFeed?: CallFeed; - public remoteScreensharingFeed?: CallFeed; - - public reject = jest.fn(); - public answerWithCallFeeds = jest.fn(); - public hangup = jest.fn(); - - public sendMetadataUpdate = jest.fn(); - - public on = jest.fn(); - public removeListener = jest.fn(); - - public getOpponentMember(): Partial { - return this.opponentMember; - } - - public getOpponentDeviceId(): string { - return this.opponentDeviceId; - } - - public typed(): MatrixCall { return this as unknown as MatrixCall; } -} - describe('Group Call', function() { beforeEach(function() { installWebRTCMocks(); @@ -351,7 +315,7 @@ describe('Group Call', function() { }); describe("call feeds changing", () => { - let call: MockCall; + let call: MockMatrixCall; const currentFeed = new MockCallFeed(FAKE_USER_ID_1, FAKE_DEVICE_ID_1, new MockMediaStream("current")); const newFeed = new MockCallFeed(FAKE_USER_ID_1, FAKE_DEVICE_ID_1, new MockMediaStream("new")); @@ -361,13 +325,13 @@ describe('Group Call', function() { jest.spyOn(groupCall, "emit"); - call = new MockCall(room.roomId, groupCall.groupCallId); + call = new MockMatrixCall(room.roomId, groupCall.groupCallId); await groupCall.create(); }); it("ignores changes, if we can't get user id of opponent", async () => { - const call = new MockCall(room.roomId, groupCall.groupCallId); + const call = new MockMatrixCall(room.roomId, groupCall.groupCallId); jest.spyOn(call, "getOpponentMember").mockReturnValue({ userId: undefined }); // @ts-ignore Mock @@ -514,7 +478,7 @@ describe('Group Call', function() { }); it("sends metadata updates before unmuting in PTT mode", async () => { - const mockCall = new MockCall(FAKE_ROOM_ID, groupCall.groupCallId); + const mockCall = new MockMatrixCall(FAKE_ROOM_ID, groupCall.groupCallId); (groupCall as any).calls.set( mockCall.getOpponentMember() as RoomMember, new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]), @@ -539,7 +503,7 @@ describe('Group Call', function() { }); it("sends metadata updates after muting in PTT mode", async () => { - const mockCall = new MockCall(FAKE_ROOM_ID, groupCall.groupCallId); + const mockCall = new MockMatrixCall(FAKE_ROOM_ID, groupCall.groupCallId); (groupCall as any).calls.set( mockCall.getOpponentMember() as RoomMember, new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]), @@ -943,7 +907,7 @@ describe('Group Call', function() { }); it("ignores incoming calls for other rooms", async () => { - const mockCall = new MockCall("!someotherroom.fake.dummy", groupCall.groupCallId); + const mockCall = new MockMatrixCall("!someotherroom.fake.dummy", groupCall.groupCallId); mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall); @@ -952,7 +916,7 @@ describe('Group Call', function() { }); it("rejects incoming calls for the wrong group call", async () => { - const mockCall = new MockCall(room.roomId, "not " + groupCall.groupCallId); + const mockCall = new MockMatrixCall(room.roomId, "not " + groupCall.groupCallId); mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall); @@ -960,7 +924,7 @@ describe('Group Call', function() { }); it("ignores incoming calls not in the ringing state", async () => { - const mockCall = new MockCall(room.roomId, groupCall.groupCallId); + const mockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId); mockCall.state = CallState.Connected; mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall); @@ -970,7 +934,7 @@ describe('Group Call', function() { }); it("answers calls for the right room & group call ID", async () => { - const mockCall = new MockCall(room.roomId, groupCall.groupCallId); + const mockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId); mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall); @@ -983,8 +947,8 @@ describe('Group Call', function() { }); it("replaces calls if it already has one with the same user", async () => { - const oldMockCall = new MockCall(room.roomId, groupCall.groupCallId); - const newMockCall = new MockCall(room.roomId, groupCall.groupCallId); + const oldMockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId); + const newMockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId); newMockCall.opponentMember = oldMockCall.opponentMember; // Ensure referential equality newMockCall.callId = "not " + oldMockCall.callId; @@ -1003,7 +967,7 @@ describe('Group Call', function() { // First we leave the call since we have already entered groupCall.leave(); - const call = new MockCall(room.roomId, groupCall.groupCallId); + const call = new MockMatrixCall(room.roomId, groupCall.groupCallId); mockClient.callEventHandler!.calls = new Map([ [call.callId, call.typed()], ]); From 987e8ef795c254322e096730fd3c108b8276f56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 5 Dec 2022 12:24:08 +0100 Subject: [PATCH 4/7] Add `connected` tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/callFeed.spec.ts | 54 +++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/spec/unit/webrtc/callFeed.spec.ts b/spec/unit/webrtc/callFeed.spec.ts index 635fa14fd8f..29b0e39f442 100644 --- a/spec/unit/webrtc/callFeed.spec.ts +++ b/spec/unit/webrtc/callFeed.spec.ts @@ -17,13 +17,30 @@ limitations under the License. import { SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes"; import { CallFeed } from "../../../src/webrtc/callFeed"; import { TestClient } from "../../TestClient"; -import { MockMediaStream, MockMediaStreamTrack } from "../../test-utils/webrtc"; +import { MockMatrixCall, MockMediaStream, MockMediaStreamTrack } from "../../test-utils/webrtc"; +import { CallEvent, CallState } from "../../../src/webrtc/call"; describe("CallFeed", () => { - let client; + const roomId = "room1"; + let client: TestClient; + let call: MockMatrixCall; + let feed: CallFeed; beforeEach(() => { client = new TestClient("@alice:foo", "somedevice", "token", undefined, {}); + call = new MockMatrixCall(roomId); + + feed = new CallFeed({ + client: client.client, + call: call.typed(), + roomId, + userId: "user1", + // @ts-ignore Mock + stream: new MockMediaStream("stream1"), + purpose: SDPStreamMetadataPurpose.Usermedia, + audioMuted: false, + videoMuted: false, + }); }); afterEach(() => { @@ -31,21 +48,6 @@ describe("CallFeed", () => { }); describe("muting", () => { - let feed: CallFeed; - - beforeEach(() => { - feed = new CallFeed({ - client, - roomId: "room1", - userId: "user1", - // @ts-ignore Mock - stream: new MockMediaStream("stream1"), - purpose: SDPStreamMetadataPurpose.Usermedia, - audioMuted: false, - videoMuted: false, - }); - }); - describe("muting by default", () => { it("should mute audio by default", () => { expect(feed.isAudioMuted()).toBeTruthy(); @@ -86,4 +88,22 @@ describe("CallFeed", () => { }); }); }); + + describe("connected", () => { + it.each([true, false])("should always be connected, if isLocal()", (val: boolean) => { + (feed as any)._connected = val; + jest.spyOn(feed, "isLocal").mockReturnValue(true); + + expect(feed.connected).toBeTruthy(); + }); + + it.each([ + [CallState.Connected, true], + [CallState.Connecting, false], + ])("should react to call state, when !isLocal()", (state: CallState, expected: Boolean) => { + call.emit(CallEvent.State, state); + + expect(feed.connected).toBe(expected); + }); + }); }); From 32526d1724681f7306ef8816cb303ed3b0f468ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 5 Dec 2022 17:18:41 +0100 Subject: [PATCH 5/7] Use `TypedEventEmitter` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/test-utils/webrtc.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/test-utils/webrtc.ts b/spec/test-utils/webrtc.ts index 2286940d2a0..c2f1a0b25a3 100644 --- a/spec/test-utils/webrtc.ts +++ b/spec/test-utils/webrtc.ts @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventEmitter } from "stream"; - import { ClientEvent, ClientEventHandlerMap, @@ -445,7 +443,7 @@ export class MockCallMatrixClient extends TypedEventEmitter { constructor(public roomId: string, public groupCallId?: string) { super(); } From bdcd61a2de285fd47fedd6a6469b1c9f0c31d8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 5 Dec 2022 17:19:14 +0100 Subject: [PATCH 6/7] Don't break on `isLocal()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/webrtc/callFeed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webrtc/callFeed.ts b/src/webrtc/callFeed.ts index a08a891725b..14abc30d22a 100644 --- a/src/webrtc/callFeed.ts +++ b/src/webrtc/callFeed.ts @@ -125,7 +125,7 @@ export class CallFeed extends TypedEventEmitter private set connected(connected: boolean) { this._connected = connected; - this.emit(CallFeedEvent.ConnectedChanged, connected); + this.emit(CallFeedEvent.ConnectedChanged, this.connected); } private get hasAudioTrack(): boolean { From 92809a8171a318c046d744b0ed67b0d6937c4b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 5 Dec 2022 17:24:27 +0100 Subject: [PATCH 7/7] Don't use `as any` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- spec/unit/webrtc/callFeed.spec.ts | 3 ++- spec/unit/webrtc/groupCall.spec.ts | 34 +++++++++++++++++++----------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/spec/unit/webrtc/callFeed.spec.ts b/spec/unit/webrtc/callFeed.spec.ts index 29b0e39f442..e14a1a0c56b 100644 --- a/spec/unit/webrtc/callFeed.spec.ts +++ b/spec/unit/webrtc/callFeed.spec.ts @@ -91,7 +91,8 @@ describe("CallFeed", () => { describe("connected", () => { it.each([true, false])("should always be connected, if isLocal()", (val: boolean) => { - (feed as any)._connected = val; + // @ts-ignore + feed._connected = val; jest.spyOn(feed, "isLocal").mockReturnValue(true); expect(feed.connected).toBeTruthy(); diff --git a/spec/unit/webrtc/groupCall.spec.ts b/spec/unit/webrtc/groupCall.spec.ts index 0e6605fc79f..ad77e15bdc7 100644 --- a/spec/unit/webrtc/groupCall.spec.ts +++ b/spec/unit/webrtc/groupCall.spec.ts @@ -479,9 +479,10 @@ describe('Group Call', function() { it("sends metadata updates before unmuting in PTT mode", async () => { const mockCall = new MockMatrixCall(FAKE_ROOM_ID, groupCall.groupCallId); - (groupCall as any).calls.set( + // @ts-ignore + groupCall.calls.set( mockCall.getOpponentMember() as RoomMember, - new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]), + new Map([[mockCall.getOpponentDeviceId()!, mockCall.typed()]]), ); let metadataUpdateResolve: () => void; @@ -504,9 +505,10 @@ describe('Group Call', function() { it("sends metadata updates after muting in PTT mode", async () => { const mockCall = new MockMatrixCall(FAKE_ROOM_ID, groupCall.groupCallId); - (groupCall as any).calls.set( + // @ts-ignore + groupCall.calls.set( mockCall.getOpponentMember() as RoomMember, - new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]), + new Map([[mockCall.getOpponentDeviceId()!, mockCall.typed()]]), ); // the call starts muted, so unmute to get in the right state to test @@ -662,7 +664,8 @@ describe('Group Call', function() { expect(client1.sendToDevice).toHaveBeenCalled(); - const oldCall = (groupCall1 as any).calls.get( + // @ts-ignore + const oldCall = groupCall1.calls.get( groupCall1.room.getMember(client2.userId)!, )!.get(client2.deviceId)!; oldCall.emit(CallEvent.Hangup, oldCall!); @@ -683,7 +686,8 @@ describe('Group Call', function() { // to even be created... let newCall: MatrixCall | undefined; while ( - (newCall = (groupCall1 as any).calls.get( + // @ts-ignore + (newCall = groupCall1.calls.get( groupCall1.room.getMember(client2.userId)!, )?.get(client2.deviceId)) === undefined || newCall.peerConn === undefined @@ -727,7 +731,8 @@ describe('Group Call', function() { groupCall1.setMicrophoneMuted(false); groupCall1.setLocalVideoMuted(false); - const call = (groupCall1 as any).calls.get( + // @ts-ignore + const call = groupCall1.calls.get( groupCall1.room.getMember(client2.userId)!, )!.get(client2.deviceId)!; call.isMicrophoneMuted = jest.fn().mockReturnValue(true); @@ -838,7 +843,8 @@ describe('Group Call', function() { // It takes a bit of time for the calls to get created await sleep(10); - const call = (groupCall as any).calls + // @ts-ignore + const call = groupCall.calls .get(groupCall.room.getMember(FAKE_USER_ID_2)!)! .get(FAKE_DEVICE_ID_2)!; call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember; @@ -863,7 +869,8 @@ describe('Group Call', function() { // It takes a bit of time for the calls to get created await sleep(10); - const call = (groupCall as any).calls + // @ts-ignore + const call = groupCall.calls .get(groupCall.room.getMember(FAKE_USER_ID_2)!)! .get(FAKE_DEVICE_ID_2)!; call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember; @@ -940,7 +947,8 @@ describe('Group Call', function() { expect(mockCall.reject).not.toHaveBeenCalled(); expect(mockCall.answerWithCallFeeds).toHaveBeenCalled(); - expect((groupCall as any).calls).toEqual(new Map([[ + // @ts-ignore + expect(groupCall.calls).toEqual(new Map([[ groupCall.room.getMember(FAKE_USER_ID_1)!, new Map([[FAKE_DEVICE_ID_1, mockCall]]), ]])); @@ -957,7 +965,8 @@ describe('Group Call', function() { expect(oldMockCall.hangup).toHaveBeenCalled(); expect(newMockCall.answerWithCallFeeds).toHaveBeenCalled(); - expect((groupCall as any).calls).toEqual(new Map([[ + // @ts-ignore + expect(groupCall.calls).toEqual(new Map([[ groupCall.room.getMember(FAKE_USER_ID_1)!, new Map([[FAKE_DEVICE_ID_1, newMockCall]]), ]])); @@ -1040,7 +1049,8 @@ describe('Group Call', function() { // It takes a bit of time for the calls to get created await sleep(10); - const call = (groupCall as any).calls + // @ts-ignore + const call = groupCall.calls .get(groupCall.room.getMember(FAKE_USER_ID_2)!)! .get(FAKE_DEVICE_ID_2)!; call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;