You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
TS strict mode compliance in the call / groupcall code (#2805)
* TS strict mode compliance in the call / groupcall code * Also the test * Fix initOpponentCrypto to not panic if it doesn't actually need to init crypto
This commit is contained in:
@@ -99,7 +99,7 @@ describe("CallEventHandler", () => {
|
||||
|
||||
expect(callEventHandler.callEventBuffer.length).toBe(2);
|
||||
expect(callEventHandler.nextSeqByCall.get("123")).toBe(2);
|
||||
expect(callEventHandler.toDeviceEventBuffers.get("123").length).toBe(1);
|
||||
expect(callEventHandler.toDeviceEventBuffers.get("123")?.length).toBe(1);
|
||||
|
||||
const event4 = new MatrixEvent({
|
||||
type: EventType.CallCandidates,
|
||||
@@ -112,7 +112,7 @@ describe("CallEventHandler", () => {
|
||||
|
||||
expect(callEventHandler.callEventBuffer.length).toBe(2);
|
||||
expect(callEventHandler.nextSeqByCall.get("123")).toBe(2);
|
||||
expect(callEventHandler.toDeviceEventBuffers.get("123").length).toBe(2);
|
||||
expect(callEventHandler.toDeviceEventBuffers.get("123")?.length).toBe(2);
|
||||
|
||||
const event5 = new MatrixEvent({
|
||||
type: EventType.CallCandidates,
|
||||
@@ -125,7 +125,7 @@ describe("CallEventHandler", () => {
|
||||
|
||||
expect(callEventHandler.callEventBuffer.length).toBe(5);
|
||||
expect(callEventHandler.nextSeqByCall.get("123")).toBe(5);
|
||||
expect(callEventHandler.toDeviceEventBuffers.get("123").length).toBe(0);
|
||||
expect(callEventHandler.toDeviceEventBuffers.get("123")?.length).toBe(0);
|
||||
});
|
||||
|
||||
it("should ignore a call if invite & hangup come within a single sync", () => {
|
||||
@@ -161,7 +161,7 @@ describe("CallEventHandler", () => {
|
||||
it("should ignore non-call events", async () => {
|
||||
// @ts-ignore Mock handleCallEvent is private
|
||||
jest.spyOn(client.callEventHandler, "handleCallEvent");
|
||||
jest.spyOn(client, "checkTurnServers").mockReturnValue(undefined);
|
||||
jest.spyOn(client, "checkTurnServers").mockReturnValue(Promise.resolve(true));
|
||||
|
||||
const room = new Room("!room:id", client, "@user:id");
|
||||
const timelineData: IRoomTimelineData = { timeline: new EventTimeline(new EventTimelineSet(room, {})) };
|
||||
@@ -186,10 +186,10 @@ describe("CallEventHandler", () => {
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
room = new Room("!room:id", client, client.getUserId());
|
||||
room = new Room("!room:id", client, client.getUserId()!);
|
||||
timelineData = { timeline: new EventTimeline(new EventTimelineSet(room, {})) };
|
||||
|
||||
jest.spyOn(client, "checkTurnServers").mockReturnValue(undefined);
|
||||
jest.spyOn(client, "checkTurnServers").mockReturnValue(Promise.resolve(true));
|
||||
jest.spyOn(client, "getRoom").mockReturnValue(room);
|
||||
jest.spyOn(room, "getMember").mockReturnValue({ user_id: client.getUserId() } as unknown as RoomMember);
|
||||
|
||||
@@ -246,10 +246,10 @@ describe("CallEventHandler", () => {
|
||||
await sync();
|
||||
|
||||
expect(incomingCallListener).toHaveBeenCalled();
|
||||
expect(call.groupCallId).toBe(GROUP_CALL_ID);
|
||||
expect(call!.groupCallId).toBe(GROUP_CALL_ID);
|
||||
// @ts-ignore Mock opponentDeviceId is private
|
||||
expect(call.opponentDeviceId).toBe(DEVICE_ID);
|
||||
expect(call.getOpponentSessionId()).toBe(SESSION_ID);
|
||||
expect(call!.getOpponentSessionId()).toBe(SESSION_ID);
|
||||
// @ts-ignore Mock onIncomingCall is private
|
||||
expect(groupCall.onIncomingCall).toHaveBeenCalledWith(call);
|
||||
|
||||
|
@@ -116,8 +116,8 @@ class MockCall {
|
||||
setAudioVideoMuted: jest.fn<void, [boolean, boolean]>(),
|
||||
stream: new MockMediaStream("stream"),
|
||||
};
|
||||
public remoteUsermediaFeed: CallFeed;
|
||||
public remoteScreensharingFeed: CallFeed;
|
||||
public remoteUsermediaFeed?: CallFeed;
|
||||
public remoteScreensharingFeed?: CallFeed;
|
||||
|
||||
public reject = jest.fn<void, []>();
|
||||
public answerWithCallFeeds = jest.fn<void, [CallFeed[]]>();
|
||||
@@ -128,7 +128,7 @@ class MockCall {
|
||||
on = jest.fn();
|
||||
removeListener = jest.fn();
|
||||
|
||||
getOpponentMember() {
|
||||
getOpponentMember(): Partial<RoomMember> {
|
||||
return {
|
||||
userId: this.opponentUserId,
|
||||
};
|
||||
@@ -276,7 +276,7 @@ describe('Group Call', function() {
|
||||
|
||||
await groupCall.initLocalCallFeed();
|
||||
|
||||
const oldStream = groupCall.localCallFeed.stream as unknown as MockMediaStream;
|
||||
const oldStream = groupCall.localCallFeed?.stream as unknown as MockMediaStream;
|
||||
|
||||
// arbitrary values, important part is that they're the same afterwards
|
||||
await groupCall.setLocalVideoMuted(true);
|
||||
@@ -286,7 +286,7 @@ describe('Group Call', function() {
|
||||
|
||||
groupCall.updateLocalUsermediaStream(newStream);
|
||||
|
||||
expect(groupCall.localCallFeed.stream).toBe(newStream);
|
||||
expect(groupCall.localCallFeed?.stream).toBe(newStream);
|
||||
|
||||
expect(groupCall.isLocalVideoMuted()).toEqual(true);
|
||||
expect(groupCall.isMicrophoneMuted()).toEqual(false);
|
||||
@@ -474,7 +474,7 @@ describe('Group Call', function() {
|
||||
// we should still be muted at this point because the metadata update hasn't sent
|
||||
expect(groupCall.isMicrophoneMuted()).toEqual(true);
|
||||
expect(mockCall.localUsermediaFeed.setAudioVideoMuted).not.toHaveBeenCalled();
|
||||
metadataUpdateResolve();
|
||||
metadataUpdateResolve!();
|
||||
|
||||
await mutePromise;
|
||||
|
||||
@@ -500,7 +500,7 @@ describe('Group Call', function() {
|
||||
// we should be muted at this point, before the metadata update has been sent
|
||||
expect(groupCall.isMicrophoneMuted()).toEqual(true);
|
||||
expect(mockCall.localUsermediaFeed.setAudioVideoMuted).toHaveBeenCalled();
|
||||
metadataUpdateResolve();
|
||||
metadataUpdateResolve!();
|
||||
|
||||
await mutePromise;
|
||||
|
||||
@@ -550,7 +550,7 @@ describe('Group Call', function() {
|
||||
groupCall1.onMemberStateChanged(fakeEvent);
|
||||
groupCall2.onMemberStateChanged(fakeEvent);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
return Promise.resolve({ "event_id": "foo" });
|
||||
};
|
||||
|
||||
client1.sendStateEvent.mockImplementation(fakeSendStateEvents);
|
||||
@@ -644,7 +644,7 @@ describe('Group Call', function() {
|
||||
expect(client1.sendToDevice).toHaveBeenCalled();
|
||||
|
||||
const oldCall = groupCall1.getCallByUserId(client2.userId);
|
||||
oldCall.emit(CallEvent.Hangup, oldCall);
|
||||
oldCall!.emit(CallEvent.Hangup, oldCall!);
|
||||
|
||||
client1.sendToDevice.mockClear();
|
||||
|
||||
@@ -660,11 +660,11 @@ describe('Group Call', function() {
|
||||
// when we placed the call, we could await on enter which waited for the call to
|
||||
// be made. We don't have that luxury now, so first have to wait for the call
|
||||
// to even be created...
|
||||
let newCall: MatrixCall;
|
||||
let newCall: MatrixCall | undefined;
|
||||
while (
|
||||
(newCall = groupCall1.getCallByUserId(client2.userId)) === undefined ||
|
||||
newCall.peerConn === undefined ||
|
||||
newCall.callId == oldCall.callId
|
||||
newCall.callId == oldCall!.callId
|
||||
) {
|
||||
await flushPromises();
|
||||
}
|
||||
@@ -704,7 +704,7 @@ describe('Group Call', function() {
|
||||
groupCall1.setMicrophoneMuted(false);
|
||||
groupCall1.setLocalVideoMuted(false);
|
||||
|
||||
const call = groupCall1.getCallByUserId(client2.userId);
|
||||
const call = groupCall1.getCallByUserId(client2.userId)!;
|
||||
call.isMicrophoneMuted = jest.fn().mockReturnValue(true);
|
||||
call.setMicrophoneMuted = jest.fn();
|
||||
call.isLocalVideoMuted = jest.fn().mockReturnValue(true);
|
||||
@@ -743,13 +743,13 @@ describe('Group Call', function() {
|
||||
it("should mute local audio when calling setMicrophoneMuted()", async () => {
|
||||
const groupCall = await createAndEnterGroupCall(mockClient, room);
|
||||
|
||||
groupCall.localCallFeed.setAudioVideoMuted = jest.fn();
|
||||
groupCall.localCallFeed!.setAudioVideoMuted = jest.fn();
|
||||
const setAVMutedArray = groupCall.calls.map(call => {
|
||||
call.localUsermediaFeed.setAudioVideoMuted = jest.fn();
|
||||
return call.localUsermediaFeed.setAudioVideoMuted;
|
||||
call.localUsermediaFeed!.setAudioVideoMuted = jest.fn();
|
||||
return call.localUsermediaFeed!.setAudioVideoMuted;
|
||||
});
|
||||
const tracksArray = groupCall.calls.reduce((acc, call) => {
|
||||
acc.push(...call.localUsermediaStream.getAudioTracks());
|
||||
const tracksArray = groupCall.calls.reduce((acc: MediaStreamTrack[], call: MatrixCall) => {
|
||||
acc.push(...call.localUsermediaStream!.getAudioTracks());
|
||||
return acc;
|
||||
}, []);
|
||||
const sendMetadataUpdateArray = groupCall.calls.map(call => {
|
||||
@@ -759,8 +759,8 @@ describe('Group Call', function() {
|
||||
|
||||
await groupCall.setMicrophoneMuted(true);
|
||||
|
||||
groupCall.localCallFeed.stream.getAudioTracks().forEach(track => expect(track.enabled).toBe(false));
|
||||
expect(groupCall.localCallFeed.setAudioVideoMuted).toHaveBeenCalledWith(true, null);
|
||||
groupCall.localCallFeed!.stream.getAudioTracks().forEach(track => expect(track.enabled).toBe(false));
|
||||
expect(groupCall.localCallFeed!.setAudioVideoMuted).toHaveBeenCalledWith(true, null);
|
||||
setAVMutedArray.forEach(f => expect(f).toHaveBeenCalledWith(true, null));
|
||||
tracksArray.forEach(track => expect(track.enabled).toBe(false));
|
||||
sendMetadataUpdateArray.forEach(f => expect(f).toHaveBeenCalled());
|
||||
@@ -771,14 +771,14 @@ describe('Group Call', function() {
|
||||
it("should mute local video when calling setLocalVideoMuted()", async () => {
|
||||
const groupCall = await createAndEnterGroupCall(mockClient, room);
|
||||
|
||||
groupCall.localCallFeed.setAudioVideoMuted = jest.fn();
|
||||
groupCall.localCallFeed!.setAudioVideoMuted = jest.fn();
|
||||
const setAVMutedArray = groupCall.calls.map(call => {
|
||||
call.localUsermediaFeed.setAudioVideoMuted = jest.fn();
|
||||
call.localUsermediaFeed.isVideoMuted = jest.fn().mockReturnValue(true);
|
||||
return call.localUsermediaFeed.setAudioVideoMuted;
|
||||
call.localUsermediaFeed!.setAudioVideoMuted = jest.fn();
|
||||
call.localUsermediaFeed!.isVideoMuted = jest.fn().mockReturnValue(true);
|
||||
return call.localUsermediaFeed!.setAudioVideoMuted;
|
||||
});
|
||||
const tracksArray = groupCall.calls.reduce((acc, call) => {
|
||||
acc.push(...call.localUsermediaStream.getVideoTracks());
|
||||
const tracksArray = groupCall.calls.reduce((acc: MediaStreamTrack[], call: MatrixCall) => {
|
||||
acc.push(...call.localUsermediaStream!.getVideoTracks());
|
||||
return acc;
|
||||
}, []);
|
||||
const sendMetadataUpdateArray = groupCall.calls.map(call => {
|
||||
@@ -788,8 +788,8 @@ describe('Group Call', function() {
|
||||
|
||||
await groupCall.setLocalVideoMuted(true);
|
||||
|
||||
groupCall.localCallFeed.stream.getVideoTracks().forEach(track => expect(track.enabled).toBe(false));
|
||||
expect(groupCall.localCallFeed.setAudioVideoMuted).toHaveBeenCalledWith(null, true);
|
||||
groupCall.localCallFeed!.stream.getVideoTracks().forEach(track => expect(track.enabled).toBe(false));
|
||||
expect(groupCall.localCallFeed!.setAudioVideoMuted).toHaveBeenCalledWith(null, true);
|
||||
setAVMutedArray.forEach(f => expect(f).toHaveBeenCalledWith(null, true));
|
||||
tracksArray.forEach(track => expect(track.enabled).toBe(false));
|
||||
sendMetadataUpdateArray.forEach(f => expect(f).toHaveBeenCalled());
|
||||
@@ -827,9 +827,9 @@ describe('Group Call', function() {
|
||||
]));
|
||||
call.onSDPStreamMetadataChangedReceived(metadataEvent);
|
||||
|
||||
const feed = groupCall.getUserMediaFeedByUserId(call.invitee);
|
||||
expect(feed.isAudioMuted()).toBe(true);
|
||||
expect(feed.isVideoMuted()).toBe(false);
|
||||
const feed = groupCall.getUserMediaFeedByUserId(call.invitee!);
|
||||
expect(feed!.isAudioMuted()).toBe(true);
|
||||
expect(feed!.isVideoMuted()).toBe(false);
|
||||
|
||||
groupCall.terminate();
|
||||
});
|
||||
@@ -850,9 +850,9 @@ describe('Group Call', function() {
|
||||
]));
|
||||
call.onSDPStreamMetadataChangedReceived(metadataEvent);
|
||||
|
||||
const feed = groupCall.getUserMediaFeedByUserId(call.invitee);
|
||||
expect(feed.isAudioMuted()).toBe(false);
|
||||
expect(feed.isVideoMuted()).toBe(true);
|
||||
const feed = groupCall.getUserMediaFeedByUserId(call.invitee!);
|
||||
expect(feed!.isAudioMuted()).toBe(false);
|
||||
expect(feed!.isVideoMuted()).toBe(true);
|
||||
|
||||
groupCall.terminate();
|
||||
});
|
||||
|
@@ -38,7 +38,7 @@ export const acquireContext = (): AudioContext => {
|
||||
export const releaseContext = () => {
|
||||
refCount--;
|
||||
if (refCount === 0) {
|
||||
audioContext.close();
|
||||
audioContext?.close();
|
||||
audioContext = null;
|
||||
}
|
||||
};
|
||||
|
@@ -331,7 +331,7 @@ function getTransceiverKey(purpose: SDPStreamMetadataPurpose, kind: TransceiverK
|
||||
* @param {MatrixClient} opts.client The Matrix Client instance to send events to.
|
||||
*/
|
||||
export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap> {
|
||||
public roomId: string;
|
||||
public roomId?: string;
|
||||
public callId: string;
|
||||
public invitee?: string;
|
||||
public state = CallState.Fledgling;
|
||||
@@ -361,15 +361,15 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
private transceivers = new Map<TransceiverKey, RTCRtpTransceiver>();
|
||||
|
||||
private inviteOrAnswerSent = false;
|
||||
private waitForLocalAVStream: boolean;
|
||||
private waitForLocalAVStream = false;
|
||||
private successor?: MatrixCall;
|
||||
private opponentMember?: RoomMember;
|
||||
private opponentVersion?: number | string;
|
||||
// The party ID of the other side: undefined if we haven't chosen a partner
|
||||
// yet, null if we have but they didn't send a party ID.
|
||||
private opponentPartyId: string | null;
|
||||
private opponentCaps: CallCapabilities;
|
||||
private iceDisconnectedTimeout: ReturnType<typeof setTimeout>;
|
||||
private opponentPartyId: string | null | undefined;
|
||||
private opponentCaps?: CallCapabilities;
|
||||
private iceDisconnectedTimeout?: ReturnType<typeof setTimeout>;
|
||||
private inviteTimeout?: ReturnType<typeof setTimeout>;
|
||||
private readonly removeTrackListeners = new Map<MediaStream, () => void>();
|
||||
|
||||
@@ -384,7 +384,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
// Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example
|
||||
private makingOffer = false;
|
||||
private ignoreOffer: boolean;
|
||||
private ignoreOffer = false;
|
||||
|
||||
private responsePromiseChain?: Promise<void>;
|
||||
|
||||
@@ -399,17 +399,21 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
private callLengthInterval?: ReturnType<typeof setInterval>;
|
||||
private callLength = 0;
|
||||
|
||||
private opponentDeviceId: string;
|
||||
private opponentDeviceInfo: DeviceInfo;
|
||||
private opponentSessionId: string;
|
||||
public groupCallId: string;
|
||||
private opponentDeviceId?: string;
|
||||
private opponentDeviceInfo?: DeviceInfo;
|
||||
private opponentSessionId?: string;
|
||||
public groupCallId?: string;
|
||||
|
||||
constructor(opts: CallOpts) {
|
||||
super();
|
||||
|
||||
this.roomId = opts.roomId;
|
||||
this.invitee = opts.invitee;
|
||||
this.client = opts.client;
|
||||
this.forceTURN = opts.forceTURN;
|
||||
|
||||
if (!this.client.deviceId) throw new Error("Client must have a device ID to start calls");
|
||||
|
||||
this.forceTURN = opts.forceTURN ?? false;
|
||||
this.ourPartyId = this.client.deviceId;
|
||||
this.opponentDeviceId = opts.opponentDeviceId;
|
||||
this.opponentSessionId = opts.opponentSessionId;
|
||||
@@ -448,7 +452,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* @param label A human readable label for this datachannel
|
||||
* @param options An object providing configuration options for the data channel.
|
||||
*/
|
||||
public createDataChannel(label: string, options: RTCDataChannelInit) {
|
||||
public createDataChannel(label: string, options: RTCDataChannelInit | undefined) {
|
||||
const dataChannel = this.peerConn!.createDataChannel(label, options);
|
||||
this.emit(CallEvent.DataChannel, dataChannel);
|
||||
return dataChannel;
|
||||
@@ -458,7 +462,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return this.opponentMember;
|
||||
}
|
||||
|
||||
public getOpponentSessionId(): string {
|
||||
public getOpponentSessionId(): string | undefined {
|
||||
return this.opponentSessionId;
|
||||
}
|
||||
|
||||
@@ -570,8 +574,13 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.opponentDeviceInfo = new DeviceInfo(this.opponentDeviceId);
|
||||
return;
|
||||
}
|
||||
// if we've got to this point, we do want to init crypto, so throw if we can't
|
||||
if (!this.client.crypto) throw new Error("Crypto is not initialised.");
|
||||
|
||||
const userId = this.invitee || this.getOpponentMember()?.userId;
|
||||
|
||||
if (!userId) throw new Error("Couldn't find opponent user ID to init crypto");
|
||||
|
||||
const userId = this.invitee || this.getOpponentMember().userId;
|
||||
const deviceInfoMap = await this.client.crypto.deviceList.downloadKeys([userId], false);
|
||||
this.opponentDeviceInfo = deviceInfoMap[userId][this.opponentDeviceId];
|
||||
if (this.opponentDeviceInfo === undefined) {
|
||||
@@ -749,7 +758,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// accumulate which makes the SDP very large very quickly: in fact it only takes
|
||||
// about 6 video tracks to exceed the maximum size of an Olm-encrypted
|
||||
// Matrix event.
|
||||
const transceiver = this.transceivers.get(tKey);
|
||||
const transceiver = this.transceivers.get(tKey)!;
|
||||
|
||||
// this is what would allow us to use addTransceiver(), but it's not available
|
||||
// on Firefox yet. We call it anyway if we have it.
|
||||
@@ -764,10 +773,15 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// doesn't yet implement RTCRTPSender.setStreams()
|
||||
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1510802) so we'd have no way to group the
|
||||
// two tracks together into a stream.
|
||||
const newSender = this.peerConn.addTrack(track, callFeed.stream);
|
||||
const newSender = this.peerConn!.addTrack(track, callFeed.stream);
|
||||
|
||||
// now go & fish for the new transceiver
|
||||
this.transceivers.set(tKey, this.peerConn.getTransceivers().find(t => t.sender === newSender));
|
||||
const newTransciever = this.peerConn!.getTransceivers().find(t => t.sender === newSender);
|
||||
if (newTransciever) {
|
||||
this.transceivers.set(tKey, newTransciever);
|
||||
} else {
|
||||
logger.warn("Didn't find a matching transceiver after adding track!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -797,8 +811,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// There is no way to actually remove a transceiver, so this just sets it to inactive
|
||||
// (or recvonly) and replaces the source with nothing.
|
||||
if (this.transceivers.has(transceiverKey)) {
|
||||
const transceiver = this.transceivers.get(transceiverKey);
|
||||
if (transceiver.sender) this.peerConn.removeTrack(transceiver.sender);
|
||||
const transceiver = this.transceivers.get(transceiverKey)!;
|
||||
if (transceiver.sender) this.peerConn!.removeTrack(transceiver.sender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,7 +864,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
if (!this.peerConn) return;
|
||||
|
||||
const statsReport = await this.peerConn.getStats();
|
||||
const stats = [];
|
||||
const stats: any[] = [];
|
||||
statsReport.forEach(item => {
|
||||
stats.push(item);
|
||||
});
|
||||
@@ -917,8 +931,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.hangupParty = CallParty.Remote; // effectively
|
||||
this.setState(CallState.Ended);
|
||||
this.stopAllMedia();
|
||||
if (this.peerConn.signalingState != 'closed') {
|
||||
this.peerConn.close();
|
||||
if (this.peerConn!.signalingState != 'closed') {
|
||||
this.peerConn!.close();
|
||||
}
|
||||
this.emit(CallEvent.Hangup, this);
|
||||
}
|
||||
@@ -947,7 +961,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
private shouldAnswerWithMediaType(
|
||||
wantedValue: boolean | undefined,
|
||||
valueOfTheOtherSide: boolean | undefined,
|
||||
valueOfTheOtherSide: boolean,
|
||||
type: "audio" | "video",
|
||||
): boolean {
|
||||
if (wantedValue && !valueOfTheOtherSide) {
|
||||
@@ -1186,7 +1200,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
for (const transceiver of [audioTransceiver, videoTransceiver]) {
|
||||
// this is slightly mixing the track and transceiver API but is basically just shorthand
|
||||
// for removing the sender.
|
||||
if (transceiver && transceiver.sender) this.peerConn.removeTrack(transceiver.sender);
|
||||
if (transceiver && transceiver.sender) this.peerConn!.removeTrack(transceiver.sender);
|
||||
}
|
||||
|
||||
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);
|
||||
@@ -1215,9 +1229,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
const sender = this.transceivers.get(getTransceiverKey(
|
||||
SDPStreamMetadataPurpose.Usermedia, "video",
|
||||
)).sender;
|
||||
))?.sender;
|
||||
|
||||
sender?.replaceTrack(track);
|
||||
sender?.replaceTrack(track ?? null);
|
||||
|
||||
this.pushNewLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare, false);
|
||||
|
||||
@@ -1230,8 +1244,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const track = this.localUsermediaStream?.getTracks().find((track) => track.kind === "video");
|
||||
const sender = this.transceivers.get(getTransceiverKey(
|
||||
SDPStreamMetadataPurpose.Usermedia, "video",
|
||||
)).sender;
|
||||
sender?.replaceTrack(track);
|
||||
))?.sender;
|
||||
sender?.replaceTrack(track ?? null);
|
||||
|
||||
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);
|
||||
this.deleteFeedByStream(this.localScreensharingStream!);
|
||||
@@ -1298,8 +1312,13 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
`) to peer connection`,
|
||||
);
|
||||
|
||||
const newSender = this.peerConn!.addTrack(track, this.localUsermediaStream);
|
||||
this.transceivers.set(tKey, this.peerConn.getTransceivers().find(t => t.sender === newSender));
|
||||
const newSender = this.peerConn!.addTrack(track, this.localUsermediaStream!);
|
||||
const newTransciever = this.peerConn!.getTransceivers().find(t => t.sender === newSender);
|
||||
if (newTransciever) {
|
||||
this.transceivers.set(tKey, newTransciever);
|
||||
} else {
|
||||
logger.warn("Couldn't find matching transceiver for newly added track!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1436,7 +1455,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const micShouldBeMuted = this.isMicrophoneMuted() || this.remoteOnHold;
|
||||
const vidShouldBeMuted = this.isLocalVideoMuted() || this.remoteOnHold;
|
||||
|
||||
logger.log(`call ${this.callId} updateMuteStatus stream ${this.localUsermediaStream.id} micShouldBeMuted ${
|
||||
logger.log(`call ${this.callId} updateMuteStatus stream ${this.localUsermediaStream!.id} micShouldBeMuted ${
|
||||
micShouldBeMuted} vidShouldBeMuted ${vidShouldBeMuted}`);
|
||||
setTracksEnabled(this.localUsermediaStream!.getAudioTracks(), !micShouldBeMuted);
|
||||
setTracksEnabled(this.localUsermediaStream!.getVideoTracks(), !vidShouldBeMuted);
|
||||
@@ -1463,7 +1482,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
if (requestScreenshareFeed) {
|
||||
this.peerConn.addTransceiver("video", {
|
||||
this.peerConn!.addTransceiver("video", {
|
||||
direction: "recvonly",
|
||||
});
|
||||
}
|
||||
@@ -1535,7 +1554,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// bandwidth when transmitting silence
|
||||
private mungeSdp(description: RTCSessionDescriptionInit, mods: CodecParamsMod[]): void {
|
||||
// The only way to enable DTX at this time is through SDP munging
|
||||
const sdp = parseSdp(description.sdp);
|
||||
const sdp = parseSdp(description.sdp!);
|
||||
|
||||
sdp.media.forEach(media => {
|
||||
const payloadTypeToCodecMap = new Map<number, string>();
|
||||
@@ -1570,7 +1589,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
if (!found) {
|
||||
media.fmtp.push({
|
||||
payload: codecToPayloadTypeMap.get(mod.codec),
|
||||
payload: codecToPayloadTypeMap.get(mod.codec)!,
|
||||
config: extraconfig.join(";"),
|
||||
});
|
||||
}
|
||||
@@ -1580,13 +1599,13 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
private async createOffer(): Promise<RTCSessionDescriptionInit> {
|
||||
const offer = await this.peerConn.createOffer();
|
||||
const offer = await this.peerConn!.createOffer();
|
||||
this.mungeSdp(offer, getCodecParamMods(this.isPtt));
|
||||
return offer;
|
||||
}
|
||||
|
||||
private async createAnswer(): Promise<RTCSessionDescriptionInit> {
|
||||
const answer = await this.peerConn.createAnswer();
|
||||
const answer = await this.peerConn!.createAnswer();
|
||||
this.mungeSdp(answer, getCodecParamMods(this.isPtt));
|
||||
return answer;
|
||||
}
|
||||
@@ -1665,7 +1684,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
};
|
||||
|
||||
private onIceGatheringStateChange = (event: Event): void => {
|
||||
logger.debug(`Call ${this.callId} ice gathering state changed to ${this.peerConn.iceGatheringState}`);
|
||||
logger.debug(`Call ${this.callId} ice gathering state changed to ${this.peerConn!.iceGatheringState}`);
|
||||
if (this.peerConn?.iceGatheringState === 'complete') {
|
||||
this.queueCandidate(null);
|
||||
}
|
||||
@@ -1688,10 +1707,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
if (this.opponentPartyId === undefined) {
|
||||
// we haven't picked an opponent yet so save the candidates
|
||||
if (fromPartyId) {
|
||||
logger.info(`Call ${this.callId} Buffering ${candidates.length} candidates until we pick an opponent`);
|
||||
const bufferedCandidates = this.remoteCandidateBuffer.get(fromPartyId) || [];
|
||||
bufferedCandidates.push(...candidates);
|
||||
this.remoteCandidateBuffer.set(fromPartyId, bufferedCandidates);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1905,7 +1926,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
try {
|
||||
await this.gotLocalOffer();
|
||||
} catch (e) {
|
||||
this.getLocalOfferFailed(e);
|
||||
this.getLocalOfferFailed(e as Error);
|
||||
return;
|
||||
} finally {
|
||||
this.makingOffer = false;
|
||||
@@ -1932,7 +1953,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
try {
|
||||
await this.peerConn.setLocalDescription(offer);
|
||||
await this.peerConn!.setLocalDescription(offer);
|
||||
} catch (err) {
|
||||
logger.debug(`Call ${this.callId} Error setting local description!`, err);
|
||||
this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true);
|
||||
@@ -2057,7 +2078,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
// ideally we'd consider the call to be connected when we get media but
|
||||
// chrome doesn't implement any of the 'onstarted' events yet
|
||||
if (["connected", "completed"].includes(this.peerConn?.iceConnectionState)) {
|
||||
if (["connected", "completed"].includes(this.peerConn?.iceConnectionState ?? '')) {
|
||||
clearTimeout(this.iceDisconnectedTimeout);
|
||||
this.setState(CallState.Connected);
|
||||
|
||||
@@ -2069,7 +2090,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
} else if (this.peerConn?.iceConnectionState == 'failed') {
|
||||
// Firefox for Android does not yet have support for restartIce()
|
||||
if (this.peerConn?.restartIce) {
|
||||
// (the types say it's always defined though, so we have to cast
|
||||
// to prevent typescript from warning).
|
||||
if (this.peerConn?.restartIce as (() => void) | null) {
|
||||
this.candidatesEnded = false;
|
||||
this.peerConn!.restartIce();
|
||||
} else {
|
||||
@@ -2085,7 +2108,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// the peer, since we don't want to block the line if they're not saying anything.
|
||||
// Experimenting in Chrome, this happens after 5 or 6 seconds, which is probably
|
||||
// fast enough.
|
||||
if (this.isPtt && ["failed", "disconnected"].includes(this.peerConn.iceConnectionState)) {
|
||||
if (this.isPtt && ["failed", "disconnected"].includes(this.peerConn!.iceConnectionState)) {
|
||||
for (const feed of this.getRemoteFeeds()) {
|
||||
feed.setAudioVideoMuted(true, true);
|
||||
}
|
||||
@@ -2243,16 +2266,16 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.emit(CallEvent.SendVoipEvent, {
|
||||
type: "toDevice",
|
||||
eventType,
|
||||
userId: this.invitee || this.getOpponentMember().userId,
|
||||
userId: this.invitee || this.getOpponentMember()?.userId,
|
||||
opponentDeviceId: this.opponentDeviceId,
|
||||
content,
|
||||
});
|
||||
|
||||
const userId = this.invitee || this.getOpponentMember().userId;
|
||||
const userId = this.invitee || this.getOpponentMember()!.userId;
|
||||
if (this.client.getUseE2eForGroupCall()) {
|
||||
await this.client.encryptAndSendToDevices([{
|
||||
userId,
|
||||
deviceInfo: this.opponentDeviceInfo,
|
||||
deviceInfo: this.opponentDeviceInfo!,
|
||||
}], {
|
||||
type: eventType,
|
||||
content,
|
||||
@@ -2273,7 +2296,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
userId: this.invitee || this.getOpponentMember()?.userId,
|
||||
});
|
||||
|
||||
await this.client.sendEvent(this.roomId, eventType, realContent);
|
||||
await this.client.sendEvent(this.roomId!, eventType, realContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2322,7 +2345,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// Call this method before sending an invite or answer message
|
||||
private discardDuplicateCandidates(): number {
|
||||
let discardCount = 0;
|
||||
const newQueue = [];
|
||||
const newQueue: RTCIceCandidate[] = [];
|
||||
|
||||
for (let i = 0; i < this.candidateSendQueue.length; i++) {
|
||||
const candidate = this.candidateSendQueue[i];
|
||||
@@ -2647,7 +2670,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.opponentPartyId = msg.party_id || null;
|
||||
}
|
||||
this.opponentCaps = msg.capabilities || {} as CallCapabilities;
|
||||
this.opponentMember = this.client.getRoom(this.roomId).getMember(ev.getSender());
|
||||
this.opponentMember = this.client.getRoom(this.roomId)!.getMember(ev.getSender()) ?? undefined;
|
||||
}
|
||||
|
||||
private async addBufferedIceCandidates(): Promise<void> {
|
||||
|
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
|
||||
import { MatrixEvent } from '../models/event';
|
||||
import { logger } from '../logger';
|
||||
import { CallDirection, CallErrorCode, CallState, createNewMatrixCall, MatrixCall } from './call';
|
||||
import { CallDirection, CallError, CallErrorCode, CallState, createNewMatrixCall, MatrixCall } from './call';
|
||||
import { EventType } from '../@types/event';
|
||||
import { ClientEvent, MatrixClient } from '../client';
|
||||
import { MCallAnswer, MCallHangupReject } from "./callEventTypes";
|
||||
@@ -152,7 +152,7 @@ export class CallEventHandler {
|
||||
this.toDeviceEventBuffers.set(content.call_id, []);
|
||||
}
|
||||
|
||||
const buffer = this.toDeviceEventBuffers.get(content.call_id);
|
||||
const buffer = this.toDeviceEventBuffers.get(content.call_id)!;
|
||||
const index = buffer.findIndex((e) => e.getContent().seq > content.seq);
|
||||
|
||||
if (index === -1) {
|
||||
@@ -172,7 +172,7 @@ export class CallEventHandler {
|
||||
while (nextEvent && nextEvent.getContent().seq === this.nextSeqByCall.get(callId)) {
|
||||
this.callEventBuffer.push(nextEvent);
|
||||
this.nextSeqByCall.set(callId, nextEvent.getContent().seq + 1);
|
||||
nextEvent = buffer.shift();
|
||||
nextEvent = buffer!.shift();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -194,7 +194,7 @@ export class CallEventHandler {
|
||||
|
||||
let opponentDeviceId: string | undefined;
|
||||
|
||||
let groupCall: GroupCall;
|
||||
let groupCall: GroupCall | undefined;
|
||||
if (groupCallId) {
|
||||
groupCall = this.client.groupCallEventHandler.getGroupCallById(groupCallId);
|
||||
|
||||
@@ -241,7 +241,7 @@ export class CallEventHandler {
|
||||
return; // This invite was meant for another user in the room
|
||||
}
|
||||
|
||||
const timeUntilTurnCresExpire = this.client.getTurnServersExpiry() - Date.now();
|
||||
const timeUntilTurnCresExpire = (this.client.getTurnServersExpiry() ?? 0) - Date.now();
|
||||
logger.info("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
|
||||
call = createNewMatrixCall(
|
||||
this.client,
|
||||
@@ -267,12 +267,14 @@ export class CallEventHandler {
|
||||
try {
|
||||
await call.initWithInvite(event);
|
||||
} catch (e) {
|
||||
if (e instanceof CallError) {
|
||||
if (e.code === GroupCallErrorCode.UnknownDevice) {
|
||||
groupCall?.emit(GroupCallEvent.Error, e);
|
||||
} else {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.calls.set(call.callId, call);
|
||||
|
||||
// if we stashed candidate events for that call ID, play them back now
|
||||
@@ -292,7 +294,7 @@ export class CallEventHandler {
|
||||
if (
|
||||
call.roomId === thisCall.roomId &&
|
||||
thisCall.direction === CallDirection.Outbound &&
|
||||
call.getOpponentMember().userId === thisCall.invitee &&
|
||||
call.getOpponentMember()?.userId === thisCall.invitee &&
|
||||
isCalling
|
||||
) {
|
||||
existingCall = thisCall;
|
||||
|
@@ -27,7 +27,7 @@ const SPEAKING_SAMPLE_COUNT = 8; // samples
|
||||
|
||||
export interface ICallFeedOpts {
|
||||
client: MatrixClient;
|
||||
roomId: string;
|
||||
roomId?: string;
|
||||
userId: string;
|
||||
stream: MediaStream;
|
||||
purpose: SDPStreamMetadataPurpose;
|
||||
@@ -67,7 +67,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
public speakingVolumeSamples: number[];
|
||||
|
||||
private client: MatrixClient;
|
||||
private roomId: string;
|
||||
private roomId?: string;
|
||||
private audioMuted: boolean;
|
||||
private videoMuted: boolean;
|
||||
private localVolume = 1;
|
||||
@@ -295,8 +295,8 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
clearTimeout(this.volumeLooperTimeout);
|
||||
this.stream?.removeEventListener("addtrack", this.onAddTrack);
|
||||
if (this.audioContext) {
|
||||
this.audioContext = null;
|
||||
this.analyser = null;
|
||||
this.audioContext = undefined;
|
||||
this.analyser = undefined;
|
||||
releaseContext();
|
||||
}
|
||||
this._disposed = true;
|
||||
|
@@ -9,6 +9,7 @@ import { CallErrorCode,
|
||||
MatrixCall,
|
||||
setTracksEnabled,
|
||||
createNewMatrixCall,
|
||||
CallError,
|
||||
} from "./call";
|
||||
import { RoomMember } from "../models/room-member";
|
||||
import { Room } from "../models/room";
|
||||
@@ -56,7 +57,7 @@ export type GroupCallEventHandlerMap = {
|
||||
[GroupCallEvent.UserMediaFeedsChanged]: (feeds: CallFeed[]) => void;
|
||||
[GroupCallEvent.ScreenshareFeedsChanged]: (feeds: CallFeed[]) => void;
|
||||
[GroupCallEvent.LocalScreenshareStateChanged]: (
|
||||
isScreensharing: boolean, feed: CallFeed, sourceId: string,
|
||||
isScreensharing: boolean, feed?: CallFeed, sourceId?: string,
|
||||
) => void;
|
||||
[GroupCallEvent.LocalMuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;
|
||||
[GroupCallEvent.ParticipantsChanged]: (participants: RoomMember[]) => void;
|
||||
@@ -136,7 +137,7 @@ export enum GroupCallState {
|
||||
|
||||
interface ICallHandlers {
|
||||
onCallFeedsChanged: (feeds: CallFeed[]) => void;
|
||||
onCallStateChanged: (state: CallState, oldState: CallState) => void;
|
||||
onCallStateChanged: (state: CallState, oldState: CallState | undefined) => void;
|
||||
onCallHangup: (call: MatrixCall) => void;
|
||||
onCallReplaced: (newCall: MatrixCall) => void;
|
||||
}
|
||||
@@ -232,7 +233,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}
|
||||
|
||||
public getLocalFeeds(): CallFeed[] {
|
||||
const feeds = [];
|
||||
const feeds: CallFeed[] = [];
|
||||
|
||||
if (this.localCallFeed) feeds.push(this.localCallFeed);
|
||||
if (this.localScreenshareFeed) feeds.push(this.localScreenshareFeed);
|
||||
@@ -311,11 +312,11 @@ export class GroupCall extends TypedEventEmitter<
|
||||
await this.initLocalCallFeed();
|
||||
}
|
||||
|
||||
this.addParticipant(this.room.getMember(this.client.getUserId()));
|
||||
this.addParticipant(this.room.getMember(this.client.getUserId()!)!);
|
||||
|
||||
await this.sendMemberStateEvent();
|
||||
|
||||
this.activeSpeaker = null;
|
||||
this.activeSpeaker = undefined;
|
||||
|
||||
this.setState(GroupCallState.Entered);
|
||||
|
||||
@@ -343,7 +344,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
private dispose() {
|
||||
if (this.localCallFeed) {
|
||||
this.removeUserMediaFeed(this.localCallFeed);
|
||||
this.localCallFeed = null;
|
||||
this.localCallFeed = undefined;
|
||||
}
|
||||
|
||||
if (this.localScreenshareFeed) {
|
||||
@@ -359,7 +360,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeParticipant(this.room.getMember(this.client.getUserId()));
|
||||
this.removeParticipant(this.room.getMember(this.client.getUserId()!)!);
|
||||
|
||||
this.removeMemberStateEvent();
|
||||
|
||||
@@ -367,7 +368,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
this.removeCall(this.calls[this.calls.length - 1], CallErrorCode.UserHangup);
|
||||
}
|
||||
|
||||
this.activeSpeaker = null;
|
||||
this.activeSpeaker = undefined;
|
||||
clearTimeout(this.activeSpeakerLoopTimeout);
|
||||
|
||||
this.retryCallCounts.clear();
|
||||
@@ -470,7 +471,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
this.setMicrophoneMuted(true);
|
||||
}, this.pttMaxTransmitTime);
|
||||
} else if (muted && !this.isMicrophoneMuted()) {
|
||||
clearTimeout(this.transmitTimer);
|
||||
if (this.transmitTimer !== null) clearTimeout(this.transmitTimer);
|
||||
this.transmitTimer = null;
|
||||
}
|
||||
}
|
||||
@@ -502,7 +503,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}
|
||||
|
||||
for (const call of this.calls) {
|
||||
setTracksEnabled(call.localUsermediaFeed.stream.getAudioTracks(), !muted);
|
||||
setTracksEnabled(call.localUsermediaFeed!.stream.getAudioTracks(), !muted);
|
||||
}
|
||||
|
||||
this.emit(GroupCallEvent.LocalMuteStateChanged, muted, this.isLocalVideoMuted());
|
||||
@@ -576,7 +577,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
this.localScreenshareFeed = new CallFeed({
|
||||
client: this.client,
|
||||
roomId: this.room.roomId,
|
||||
userId: this.client.getUserId(),
|
||||
userId: this.client.getUserId()!,
|
||||
stream,
|
||||
purpose: SDPStreamMetadataPurpose.Screenshare,
|
||||
audioMuted: false,
|
||||
@@ -593,7 +594,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
|
||||
// TODO: handle errors
|
||||
await Promise.all(this.calls.map(call => call.pushLocalFeed(
|
||||
this.localScreenshareFeed.clone(),
|
||||
this.localScreenshareFeed!.clone(),
|
||||
)));
|
||||
|
||||
await this.sendMemberStateEvent();
|
||||
@@ -603,7 +604,10 @@ export class GroupCall extends TypedEventEmitter<
|
||||
if (opts.throwOnFail) throw error;
|
||||
logger.error("Enabling screensharing error", error);
|
||||
this.emit(GroupCallEvent.Error,
|
||||
new GroupCallError(GroupCallErrorCode.NoUserMedia, "Failed to get screen-sharing stream: ", error),
|
||||
new GroupCallError(
|
||||
GroupCallErrorCode.NoUserMedia,
|
||||
"Failed to get screen-sharing stream: ", error as Error,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -611,8 +615,8 @@ export class GroupCall extends TypedEventEmitter<
|
||||
await Promise.all(this.calls.map(call => {
|
||||
if (call.localScreensharingFeed) call.removeLocalFeed(call.localScreensharingFeed);
|
||||
}));
|
||||
this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream);
|
||||
this.removeScreenshareFeed(this.localScreenshareFeed);
|
||||
this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed!.stream);
|
||||
this.removeScreenshareFeed(this.localScreenshareFeed!);
|
||||
this.localScreenshareFeed = undefined;
|
||||
this.localDesktopCapturerSourceId = undefined;
|
||||
await this.sendMemberStateEvent();
|
||||
@@ -652,8 +656,8 @@ export class GroupCall extends TypedEventEmitter<
|
||||
return;
|
||||
}
|
||||
|
||||
const opponentMemberId = newCall.getOpponentMember().userId;
|
||||
const existingCall = this.getCallByUserId(opponentMemberId);
|
||||
const opponentMemberId = newCall.getOpponentMember()?.userId;
|
||||
const existingCall = opponentMemberId ? this.getCallByUserId(opponentMemberId) : null;
|
||||
|
||||
if (existingCall && existingCall.callId === newCall.callId) {
|
||||
return;
|
||||
@@ -709,7 +713,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
const res = await send();
|
||||
|
||||
// Clear the old interval first, so that it isn't forgot
|
||||
clearInterval(this.resendMemberStateTimer);
|
||||
if (this.resendMemberStateTimer !== null) clearInterval(this.resendMemberStateTimer);
|
||||
// Resend the state event every so often so it doesn't become stale
|
||||
this.resendMemberStateTimer = setInterval(async () => {
|
||||
logger.log("Resending call member state");
|
||||
@@ -720,13 +724,13 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}
|
||||
|
||||
private async removeMemberStateEvent(): Promise<ISendEventResponse> {
|
||||
clearInterval(this.resendMemberStateTimer);
|
||||
if (this.resendMemberStateTimer !== null) clearInterval(this.resendMemberStateTimer);
|
||||
this.resendMemberStateTimer = null;
|
||||
return await this.updateMemberCallState(undefined);
|
||||
}
|
||||
|
||||
private async updateMemberCallState(memberCallState?: IGroupCallRoomMemberCallState): Promise<ISendEventResponse> {
|
||||
const localUserId = this.client.getUserId();
|
||||
const localUserId = this.client.getUserId()!;
|
||||
|
||||
const memberState = this.getMemberStateEvents(localUserId)?.getContent<IGroupCallRoomMemberState>();
|
||||
|
||||
@@ -766,7 +770,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
// The member events may be received for another room, which we will ignore.
|
||||
if (event.getRoomId() !== this.room.roomId) return;
|
||||
|
||||
const member = this.room.getMember(event.getStateKey());
|
||||
const member = this.room.getMember(event.getStateKey()!);
|
||||
if (!member) {
|
||||
logger.warn(`Couldn't find room member for ${event.getStateKey()}: ignoring member state event!`);
|
||||
return;
|
||||
@@ -816,7 +820,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}, content["m.expires_ts"] - Date.now()));
|
||||
|
||||
// Don't process your own member.
|
||||
const localUserId = this.client.getUserId();
|
||||
const localUserId = this.client.getUserId()!;
|
||||
|
||||
if (member.userId === localUserId) {
|
||||
return;
|
||||
@@ -860,6 +864,11 @@ export class GroupCall extends TypedEventEmitter<
|
||||
},
|
||||
);
|
||||
|
||||
if (!newCall) {
|
||||
logger.error("Failed to create call!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingCall) {
|
||||
logger.debug(`Replacing call ${existingCall.callId} to ${member.userId} with ${newCall.callId}`);
|
||||
this.replaceCall(existingCall, newCall, CallErrorCode.NewSession);
|
||||
@@ -884,7 +893,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
);
|
||||
} catch (e) {
|
||||
logger.warn(`Failed to place call to ${member.userId}!`, e);
|
||||
if (e.code === GroupCallErrorCode.UnknownDevice) {
|
||||
if (e instanceof CallError && e.code === GroupCallErrorCode.UnknownDevice) {
|
||||
this.emit(GroupCallEvent.Error, e);
|
||||
} else {
|
||||
this.emit(
|
||||
@@ -904,7 +913,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}
|
||||
};
|
||||
|
||||
public getDeviceForMember(userId: string): IGroupCallRoomMemberDevice {
|
||||
public getDeviceForMember(userId: string): IGroupCallRoomMemberDevice | undefined {
|
||||
const memberStateEvent = this.getMemberStateEvents(userId);
|
||||
|
||||
if (!memberStateEvent) {
|
||||
@@ -931,7 +940,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
|
||||
private onRetryCallLoop = () => {
|
||||
for (const event of this.getMemberStateEvents()) {
|
||||
const memberId = event.getStateKey();
|
||||
const memberId = event.getStateKey()!;
|
||||
const existingCall = this.calls.find((call) => getCallUserId(call) === memberId);
|
||||
const retryCallCount = this.retryCallCounts.get(memberId) || 0;
|
||||
|
||||
@@ -948,7 +957,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
* Call Event Handlers
|
||||
*/
|
||||
|
||||
public getCallByUserId(userId: string): MatrixCall {
|
||||
public getCallByUserId(userId: string): MatrixCall | undefined {
|
||||
return this.calls.find((call) => getCallUserId(call) === userId);
|
||||
}
|
||||
|
||||
@@ -996,7 +1005,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
|
||||
const onCallFeedsChanged = () => this.onCallFeedsChanged(call);
|
||||
const onCallStateChanged =
|
||||
(state: CallState, oldState: CallState) => this.onCallStateChanged(call, state, oldState);
|
||||
(state: CallState, oldState: CallState | undefined) => this.onCallStateChanged(call, state, oldState);
|
||||
const onCallHangup = this.onCallHangup;
|
||||
const onCallReplaced = (newCall: MatrixCall) => this.replaceCall(call, newCall);
|
||||
|
||||
@@ -1029,7 +1038,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
onCallStateChanged,
|
||||
onCallHangup,
|
||||
onCallReplaced,
|
||||
} = this.callHandlers.get(opponentMemberId);
|
||||
} = this.callHandlers.get(opponentMemberId)!;
|
||||
|
||||
call.removeListener(CallEvent.FeedsChanged, onCallFeedsChanged);
|
||||
call.removeListener(CallEvent.State, onCallStateChanged);
|
||||
@@ -1095,8 +1104,8 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}
|
||||
};
|
||||
|
||||
private onCallStateChanged = (call: MatrixCall, state: CallState, _oldState: CallState) => {
|
||||
const audioMuted = this.localCallFeed.isAudioMuted();
|
||||
private onCallStateChanged = (call: MatrixCall, state: CallState, _oldState: CallState | undefined) => {
|
||||
const audioMuted = this.localCallFeed!.isAudioMuted();
|
||||
|
||||
if (
|
||||
call.localUsermediaStream &&
|
||||
@@ -1105,7 +1114,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
call.setMicrophoneMuted(audioMuted);
|
||||
}
|
||||
|
||||
const videoMuted = this.localCallFeed.isVideoMuted();
|
||||
const videoMuted = this.localCallFeed!.isVideoMuted();
|
||||
|
||||
if (
|
||||
call.localUsermediaStream &&
|
||||
@@ -1115,7 +1124,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}
|
||||
|
||||
if (state === CallState.Connected) {
|
||||
this.retryCallCounts.delete(getCallUserId(call));
|
||||
this.retryCallCounts.delete(getCallUserId(call)!);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1177,8 +1186,8 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}
|
||||
|
||||
private onActiveSpeakerLoop = () => {
|
||||
let topAvg: number;
|
||||
let nextActiveSpeaker: string;
|
||||
let topAvg: number | undefined = undefined;
|
||||
let nextActiveSpeaker: string | undefined = undefined;
|
||||
|
||||
for (const callFeed of this.userMediaFeeds) {
|
||||
if (callFeed.userId === this.client.getUserId() && this.userMediaFeeds.length > 1) {
|
||||
@@ -1200,7 +1209,7 @@ export class GroupCall extends TypedEventEmitter<
|
||||
}
|
||||
}
|
||||
|
||||
if (nextActiveSpeaker && this.activeSpeaker !== nextActiveSpeaker && topAvg > SPEAKING_THRESHOLD) {
|
||||
if (nextActiveSpeaker && this.activeSpeaker !== nextActiveSpeaker && topAvg && topAvg > SPEAKING_THRESHOLD) {
|
||||
this.activeSpeaker = nextActiveSpeaker;
|
||||
this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker);
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ export class GroupCallEventHandler {
|
||||
}
|
||||
|
||||
private getRoomDeferred(roomId: string): RoomDeferred {
|
||||
let deferred: RoomDeferred = this.roomDeferreds.get(roomId);
|
||||
let deferred = this.roomDeferreds.get(roomId);
|
||||
if (deferred === undefined) {
|
||||
let resolveFunc: () => void;
|
||||
deferred = {
|
||||
@@ -99,7 +99,7 @@ export class GroupCallEventHandler {
|
||||
resolveFunc = resolve;
|
||||
}),
|
||||
};
|
||||
deferred.resolve = resolveFunc;
|
||||
deferred.resolve = resolveFunc!;
|
||||
this.roomDeferreds.set(roomId, deferred);
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export class GroupCallEventHandler {
|
||||
return this.getRoomDeferred(roomId).prom;
|
||||
}
|
||||
|
||||
public getGroupCallById(groupCallId: string): GroupCall {
|
||||
public getGroupCallById(groupCallId: string): GroupCall | undefined {
|
||||
return [...this.groupCalls.values()].find((groupCall) => groupCall.groupCallId === groupCallId);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export class GroupCallEventHandler {
|
||||
}
|
||||
|
||||
logger.info("Group call event handler processed room", room.roomId);
|
||||
this.getRoomDeferred(room.roomId).resolve();
|
||||
this.getRoomDeferred(room.roomId).resolve!();
|
||||
}
|
||||
|
||||
private createGroupCallFromRoomStateEvent(event: MatrixEvent): GroupCall | undefined {
|
||||
|
@@ -228,8 +228,8 @@ export class MediaHandler extends TypedEventEmitter<
|
||||
this.localUserMediaStream = stream;
|
||||
}
|
||||
} else {
|
||||
stream = this.localUserMediaStream.clone();
|
||||
logger.log(`mediaHandler clone userMediaStream ${this.localUserMediaStream.id} new stream ${
|
||||
stream = this.localUserMediaStream!.clone();
|
||||
logger.log(`mediaHandler clone userMediaStream ${this.localUserMediaStream?.id} new stream ${
|
||||
stream.id} shouldRequestAudio ${shouldRequestAudio} shouldRequestVideo ${shouldRequestVideo}`);
|
||||
|
||||
if (!shouldRequestAudio) {
|
||||
@@ -282,12 +282,11 @@ export class MediaHandler extends TypedEventEmitter<
|
||||
* @param reusable is allowed to be reused by the MediaHandler
|
||||
* @returns {MediaStream} based on passed parameters
|
||||
*/
|
||||
public async getScreensharingStream(opts: IScreensharingOpts = {}, reusable = true): Promise<MediaStream | null> {
|
||||
public async getScreensharingStream(opts: IScreensharingOpts = {}, reusable = true): Promise<MediaStream> {
|
||||
let stream: MediaStream;
|
||||
|
||||
if (this.screensharingStreams.length === 0) {
|
||||
const screenshareConstraints = this.getScreenshareContraints(opts);
|
||||
if (!screenshareConstraints) return null;
|
||||
|
||||
if (opts.desktopCapturerSourceId) {
|
||||
// We are using Electron
|
||||
@@ -385,7 +384,7 @@ export class MediaHandler extends TypedEventEmitter<
|
||||
if (desktopCapturerSourceId) {
|
||||
logger.debug("Using desktop capturer source", desktopCapturerSourceId);
|
||||
return {
|
||||
audio,
|
||||
audio: audio ?? false,
|
||||
video: {
|
||||
mandatory: {
|
||||
chromeMediaSource: "desktop",
|
||||
@@ -396,7 +395,7 @@ export class MediaHandler extends TypedEventEmitter<
|
||||
} else {
|
||||
logger.debug("Not using desktop capturer source");
|
||||
return {
|
||||
audio,
|
||||
audio: audio ?? false,
|
||||
video: true,
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user