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