You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Don't expose calls
on GroupCall
(#2941)
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
Room,
|
||||
RoomMember,
|
||||
RoomState,
|
||||
RoomStateEvent,
|
||||
RoomStateEventHandlerMap,
|
||||
@@ -33,7 +34,7 @@ import {
|
||||
import { TypedEventEmitter } from "../../src/models/typed-event-emitter";
|
||||
import { ReEmitter } from "../../src/ReEmitter";
|
||||
import { SyncState } from "../../src/sync";
|
||||
import { CallEvent, CallEventHandlerMap, MatrixCall } from "../../src/webrtc/call";
|
||||
import { CallEvent, CallEventHandlerMap, CallState, MatrixCall } from "../../src/webrtc/call";
|
||||
import { CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from "../../src/webrtc/callEventHandler";
|
||||
import { CallFeed } from "../../src/webrtc/callFeed";
|
||||
import { GroupCallEventHandlerMap } from "../../src/webrtc/groupCall";
|
||||
@@ -83,6 +84,17 @@ export const DUMMY_SDP = (
|
||||
export const USERMEDIA_STREAM_ID = "mock_stream_from_media_handler";
|
||||
export const SCREENSHARE_STREAM_ID = "mock_screen_stream_from_media_handler";
|
||||
|
||||
export const FAKE_ROOM_ID = "!fake:test.dummy";
|
||||
export const FAKE_CONF_ID = "fakegroupcallid";
|
||||
|
||||
export const FAKE_USER_ID_1 = "@alice:test.dummy";
|
||||
export const FAKE_DEVICE_ID_1 = "@AAAAAA";
|
||||
export const FAKE_SESSION_ID_1 = "alice1";
|
||||
export const FAKE_USER_ID_2 = "@bob:test.dummy";
|
||||
export const FAKE_DEVICE_ID_2 = "@BBBBBB";
|
||||
export const FAKE_SESSION_ID_2 = "bob1";
|
||||
export const FAKE_USER_ID_3 = "@charlie:test.dummy";
|
||||
|
||||
class MockMediaStreamAudioSourceNode {
|
||||
public connect() {}
|
||||
}
|
||||
@@ -431,6 +443,43 @@ export class MockCallMatrixClient extends TypedEventEmitter<EmittedEvents, Emitt
|
||||
}
|
||||
}
|
||||
|
||||
export class MockMatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap> {
|
||||
constructor(public roomId: string, public groupCallId?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public state = CallState.Ringing;
|
||||
public opponentUserId = FAKE_USER_ID_1;
|
||||
public opponentDeviceId = FAKE_DEVICE_ID_1;
|
||||
public opponentMember = { userId: this.opponentUserId };
|
||||
public callId = "1";
|
||||
public localUsermediaFeed = {
|
||||
setAudioVideoMuted: jest.fn<void, [boolean, boolean]>(),
|
||||
stream: new MockMediaStream("stream"),
|
||||
};
|
||||
public remoteUsermediaFeed?: CallFeed;
|
||||
public remoteScreensharingFeed?: CallFeed;
|
||||
|
||||
public reject = jest.fn<void, []>();
|
||||
public answerWithCallFeeds = jest.fn<void, [CallFeed[]]>();
|
||||
public hangup = jest.fn<void, []>();
|
||||
|
||||
public sendMetadataUpdate = jest.fn<void, []>();
|
||||
|
||||
public on = jest.fn();
|
||||
public removeListener = jest.fn();
|
||||
|
||||
public getOpponentMember(): Partial<RoomMember> {
|
||||
return this.opponentMember;
|
||||
}
|
||||
|
||||
public getOpponentDeviceId(): string | undefined {
|
||||
return this.opponentDeviceId;
|
||||
}
|
||||
|
||||
public typed(): MatrixCall { return this as unknown as MatrixCall; }
|
||||
}
|
||||
|
||||
export class MockCallFeed {
|
||||
constructor(
|
||||
public userId: string,
|
||||
|
@@ -1392,7 +1392,7 @@ describe('Call', function() {
|
||||
it("ends call on onHangupReceived() if state is ringing", async () => {
|
||||
expect(call.callHasEnded()).toBe(false);
|
||||
|
||||
call.state = CallState.Ringing;
|
||||
(call as any).state = CallState.Ringing;
|
||||
call.onHangupReceived({} as MCallHangupReject);
|
||||
|
||||
expect(call.callHasEnded()).toBe(true);
|
||||
@@ -1424,7 +1424,7 @@ describe('Call', function() {
|
||||
)("ends call on onRejectReceived() if in correct state (state=%s)", async (state: CallState) => {
|
||||
expect(call.callHasEnded()).toBe(false);
|
||||
|
||||
call.state = state;
|
||||
(call as any).state = state;
|
||||
call.onRejectReceived({} as MCallHangupReject);
|
||||
|
||||
expect(call.callHasEnded()).toBe(
|
||||
|
@@ -17,26 +17,23 @@ limitations under the License.
|
||||
import { SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes";
|
||||
import { CallFeed } from "../../../src/webrtc/callFeed";
|
||||
import { TestClient } from "../../TestClient";
|
||||
import { MockMediaStream, MockMediaStreamTrack } from "../../test-utils/webrtc";
|
||||
import { MockMatrixCall, MockMediaStream, MockMediaStreamTrack } from "../../test-utils/webrtc";
|
||||
import { CallEvent, CallState } from "../../../src/webrtc/call";
|
||||
|
||||
describe("CallFeed", () => {
|
||||
let client;
|
||||
|
||||
beforeEach(() => {
|
||||
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
client.stop();
|
||||
});
|
||||
|
||||
describe("muting", () => {
|
||||
const roomId = "room1";
|
||||
let client: TestClient;
|
||||
let call: MockMatrixCall;
|
||||
let feed: CallFeed;
|
||||
|
||||
beforeEach(() => {
|
||||
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
|
||||
call = new MockMatrixCall(roomId);
|
||||
|
||||
feed = new CallFeed({
|
||||
client,
|
||||
roomId: "room1",
|
||||
client: client.client,
|
||||
call: call.typed(),
|
||||
roomId,
|
||||
userId: "user1",
|
||||
// @ts-ignore Mock
|
||||
stream: new MockMediaStream("stream1"),
|
||||
@@ -46,6 +43,11 @@ describe("CallFeed", () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
client.stop();
|
||||
});
|
||||
|
||||
describe("muting", () => {
|
||||
describe("muting by default", () => {
|
||||
it("should mute audio by default", () => {
|
||||
expect(feed.isAudioMuted()).toBeTruthy();
|
||||
@@ -86,4 +88,23 @@ describe("CallFeed", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("connected", () => {
|
||||
it.each([true, false])("should always be connected, if isLocal()", (val: boolean) => {
|
||||
// @ts-ignore
|
||||
feed._connected = val;
|
||||
jest.spyOn(feed, "isLocal").mockReturnValue(true);
|
||||
|
||||
expect(feed.connected).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[CallState.Connected, true],
|
||||
[CallState.Connecting, false],
|
||||
])("should react to call state, when !isLocal()", (state: CallState, expected: Boolean) => {
|
||||
call.emit(CallEvent.State, state);
|
||||
|
||||
expect(feed.connected).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -33,6 +33,16 @@ import {
|
||||
MockMediaStream,
|
||||
MockMediaStreamTrack,
|
||||
MockRTCPeerConnection,
|
||||
MockMatrixCall,
|
||||
FAKE_ROOM_ID,
|
||||
FAKE_USER_ID_1,
|
||||
FAKE_CONF_ID,
|
||||
FAKE_DEVICE_ID_2,
|
||||
FAKE_SESSION_ID_2,
|
||||
FAKE_USER_ID_2,
|
||||
FAKE_DEVICE_ID_1,
|
||||
FAKE_SESSION_ID_1,
|
||||
FAKE_USER_ID_3,
|
||||
} from '../../test-utils/webrtc';
|
||||
import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes";
|
||||
import { sleep } from "../../../src/utils";
|
||||
@@ -41,16 +51,6 @@ import { CallFeed } from '../../../src/webrtc/callFeed';
|
||||
import { CallEvent, CallState } from '../../../src/webrtc/call';
|
||||
import { flushPromises } from '../../test-utils/flushPromises';
|
||||
|
||||
const FAKE_ROOM_ID = "!fake:test.dummy";
|
||||
const FAKE_CONF_ID = "fakegroupcallid";
|
||||
|
||||
const FAKE_USER_ID_1 = "@alice:test.dummy";
|
||||
const FAKE_DEVICE_ID_1 = "@AAAAAA";
|
||||
const FAKE_SESSION_ID_1 = "alice1";
|
||||
const FAKE_USER_ID_2 = "@bob:test.dummy";
|
||||
const FAKE_DEVICE_ID_2 = "@BBBBBB";
|
||||
const FAKE_SESSION_ID_2 = "bob1";
|
||||
const FAKE_USER_ID_3 = "@charlie:test.dummy";
|
||||
const FAKE_STATE_EVENTS = [
|
||||
{
|
||||
getContent: () => ({
|
||||
@@ -123,42 +123,6 @@ const createAndEnterGroupCall = async (cli: MatrixClient, room: Room): Promise<G
|
||||
return groupCall;
|
||||
};
|
||||
|
||||
class MockCall {
|
||||
constructor(public roomId: string, public groupCallId: string) {
|
||||
}
|
||||
|
||||
public state = CallState.Ringing;
|
||||
public opponentUserId = FAKE_USER_ID_1;
|
||||
public opponentDeviceId = FAKE_DEVICE_ID_1;
|
||||
public opponentMember = { userId: this.opponentUserId };
|
||||
public callId = "1";
|
||||
public localUsermediaFeed = {
|
||||
setAudioVideoMuted: jest.fn<void, [boolean, boolean]>(),
|
||||
stream: new MockMediaStream("stream"),
|
||||
};
|
||||
public remoteUsermediaFeed?: CallFeed;
|
||||
public remoteScreensharingFeed?: CallFeed;
|
||||
|
||||
public reject = jest.fn<void, []>();
|
||||
public answerWithCallFeeds = jest.fn<void, [CallFeed[]]>();
|
||||
public hangup = jest.fn<void, []>();
|
||||
|
||||
public sendMetadataUpdate = jest.fn<void, []>();
|
||||
|
||||
public on = jest.fn();
|
||||
public removeListener = jest.fn();
|
||||
|
||||
public getOpponentMember(): Partial<RoomMember> {
|
||||
return this.opponentMember;
|
||||
}
|
||||
|
||||
public getOpponentDeviceId(): string {
|
||||
return this.opponentDeviceId;
|
||||
}
|
||||
|
||||
public typed(): MatrixCall { return this as unknown as MatrixCall; }
|
||||
}
|
||||
|
||||
describe('Group Call', function() {
|
||||
beforeEach(function() {
|
||||
installWebRTCMocks();
|
||||
@@ -351,7 +315,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
describe("call feeds changing", () => {
|
||||
let call: MockCall;
|
||||
let call: MockMatrixCall;
|
||||
const currentFeed = new MockCallFeed(FAKE_USER_ID_1, FAKE_DEVICE_ID_1, new MockMediaStream("current"));
|
||||
const newFeed = new MockCallFeed(FAKE_USER_ID_1, FAKE_DEVICE_ID_1, new MockMediaStream("new"));
|
||||
|
||||
@@ -361,13 +325,13 @@ describe('Group Call', function() {
|
||||
|
||||
jest.spyOn(groupCall, "emit");
|
||||
|
||||
call = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
call = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||
|
||||
await groupCall.create();
|
||||
});
|
||||
|
||||
it("ignores changes, if we can't get user id of opponent", async () => {
|
||||
const call = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
const call = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||
jest.spyOn(call, "getOpponentMember").mockReturnValue({ userId: undefined });
|
||||
|
||||
// @ts-ignore Mock
|
||||
@@ -514,10 +478,11 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("sends metadata updates before unmuting in PTT mode", async () => {
|
||||
const mockCall = new MockCall(FAKE_ROOM_ID, groupCall.groupCallId);
|
||||
const mockCall = new MockMatrixCall(FAKE_ROOM_ID, groupCall.groupCallId);
|
||||
// @ts-ignore
|
||||
groupCall.calls.set(
|
||||
mockCall.getOpponentMember() as RoomMember,
|
||||
new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]),
|
||||
new Map([[mockCall.getOpponentDeviceId()!, mockCall.typed()]]),
|
||||
);
|
||||
|
||||
let metadataUpdateResolve: () => void;
|
||||
@@ -539,10 +504,11 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("sends metadata updates after muting in PTT mode", async () => {
|
||||
const mockCall = new MockCall(FAKE_ROOM_ID, groupCall.groupCallId);
|
||||
const mockCall = new MockMatrixCall(FAKE_ROOM_ID, groupCall.groupCallId);
|
||||
// @ts-ignore
|
||||
groupCall.calls.set(
|
||||
mockCall.getOpponentMember() as RoomMember,
|
||||
new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]),
|
||||
new Map([[mockCall.getOpponentDeviceId()!, mockCall.typed()]]),
|
||||
);
|
||||
|
||||
// the call starts muted, so unmute to get in the right state to test
|
||||
@@ -698,6 +664,7 @@ describe('Group Call', function() {
|
||||
|
||||
expect(client1.sendToDevice).toHaveBeenCalled();
|
||||
|
||||
// @ts-ignore
|
||||
const oldCall = groupCall1.calls.get(
|
||||
groupCall1.room.getMember(client2.userId)!,
|
||||
)!.get(client2.deviceId)!;
|
||||
@@ -719,6 +686,7 @@ describe('Group Call', function() {
|
||||
// to even be created...
|
||||
let newCall: MatrixCall | undefined;
|
||||
while (
|
||||
// @ts-ignore
|
||||
(newCall = groupCall1.calls.get(
|
||||
groupCall1.room.getMember(client2.userId)!,
|
||||
)?.get(client2.deviceId)) === undefined
|
||||
@@ -763,6 +731,7 @@ describe('Group Call', function() {
|
||||
groupCall1.setMicrophoneMuted(false);
|
||||
groupCall1.setLocalVideoMuted(false);
|
||||
|
||||
// @ts-ignore
|
||||
const call = groupCall1.calls.get(
|
||||
groupCall1.room.getMember(client2.userId)!,
|
||||
)!.get(client2.deviceId)!;
|
||||
@@ -874,7 +843,10 @@ describe('Group Call', function() {
|
||||
// It takes a bit of time for the calls to get created
|
||||
await sleep(10);
|
||||
|
||||
const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!;
|
||||
// @ts-ignore
|
||||
const call = groupCall.calls
|
||||
.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!
|
||||
.get(FAKE_DEVICE_ID_2)!;
|
||||
call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||
// @ts-ignore Mock
|
||||
call.pushRemoteFeed(new MockMediaStream("stream", [
|
||||
@@ -897,7 +869,10 @@ describe('Group Call', function() {
|
||||
// It takes a bit of time for the calls to get created
|
||||
await sleep(10);
|
||||
|
||||
const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!;
|
||||
// @ts-ignore
|
||||
const call = groupCall.calls
|
||||
.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!
|
||||
.get(FAKE_DEVICE_ID_2)!;
|
||||
call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||
// @ts-ignore Mock
|
||||
call.pushRemoteFeed(new MockMediaStream("stream", [
|
||||
@@ -939,7 +914,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("ignores incoming calls for other rooms", async () => {
|
||||
const mockCall = new MockCall("!someotherroom.fake.dummy", groupCall.groupCallId);
|
||||
const mockCall = new MockMatrixCall("!someotherroom.fake.dummy", groupCall.groupCallId);
|
||||
|
||||
mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall);
|
||||
|
||||
@@ -948,7 +923,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("rejects incoming calls for the wrong group call", async () => {
|
||||
const mockCall = new MockCall(room.roomId, "not " + groupCall.groupCallId);
|
||||
const mockCall = new MockMatrixCall(room.roomId, "not " + groupCall.groupCallId);
|
||||
|
||||
mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall);
|
||||
|
||||
@@ -956,7 +931,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("ignores incoming calls not in the ringing state", async () => {
|
||||
const mockCall = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
const mockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||
mockCall.state = CallState.Connected;
|
||||
|
||||
mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall);
|
||||
@@ -966,12 +941,13 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("answers calls for the right room & group call ID", async () => {
|
||||
const mockCall = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
const mockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||
|
||||
mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall);
|
||||
|
||||
expect(mockCall.reject).not.toHaveBeenCalled();
|
||||
expect(mockCall.answerWithCallFeeds).toHaveBeenCalled();
|
||||
// @ts-ignore
|
||||
expect(groupCall.calls).toEqual(new Map([[
|
||||
groupCall.room.getMember(FAKE_USER_ID_1)!,
|
||||
new Map([[FAKE_DEVICE_ID_1, mockCall]]),
|
||||
@@ -979,8 +955,8 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("replaces calls if it already has one with the same user", async () => {
|
||||
const oldMockCall = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
const newMockCall = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
const oldMockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||
const newMockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||
newMockCall.opponentMember = oldMockCall.opponentMember; // Ensure referential equality
|
||||
newMockCall.callId = "not " + oldMockCall.callId;
|
||||
|
||||
@@ -989,6 +965,7 @@ describe('Group Call', function() {
|
||||
|
||||
expect(oldMockCall.hangup).toHaveBeenCalled();
|
||||
expect(newMockCall.answerWithCallFeeds).toHaveBeenCalled();
|
||||
// @ts-ignore
|
||||
expect(groupCall.calls).toEqual(new Map([[
|
||||
groupCall.room.getMember(FAKE_USER_ID_1)!,
|
||||
new Map([[FAKE_DEVICE_ID_1, newMockCall]]),
|
||||
@@ -999,7 +976,7 @@ describe('Group Call', function() {
|
||||
// First we leave the call since we have already entered
|
||||
groupCall.leave();
|
||||
|
||||
const call = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
const call = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||
mockClient.callEventHandler!.calls = new Map<string, MatrixCall>([
|
||||
[call.callId, call.typed()],
|
||||
]);
|
||||
@@ -1072,7 +1049,10 @@ describe('Group Call', function() {
|
||||
// It takes a bit of time for the calls to get created
|
||||
await sleep(10);
|
||||
|
||||
const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!;
|
||||
// @ts-ignore
|
||||
const call = groupCall.calls
|
||||
.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!
|
||||
.get(FAKE_DEVICE_ID_2)!;
|
||||
call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||
call.onNegotiateReceived({
|
||||
getContent: () => ({
|
||||
|
@@ -334,7 +334,6 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
public roomId?: string;
|
||||
public callId: string;
|
||||
public invitee?: string;
|
||||
public state = CallState.Fledgling;
|
||||
public hangupParty?: CallParty;
|
||||
public hangupReason?: string;
|
||||
public direction?: CallDirection;
|
||||
@@ -346,6 +345,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// This should be set by the consumer on incoming & outgoing calls.
|
||||
public isPtt = false;
|
||||
|
||||
private _state = CallState.Fledgling;
|
||||
private readonly client: MatrixClient;
|
||||
private readonly forceTURN?: boolean;
|
||||
private readonly turnServers: Array<TurnServer>;
|
||||
@@ -482,6 +482,16 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return this.remoteAssertedIdentity;
|
||||
}
|
||||
|
||||
public get state(): CallState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
private set state(state: CallState) {
|
||||
const oldState = this._state;
|
||||
this._state = state;
|
||||
this.emit(CallEvent.State, state, oldState);
|
||||
}
|
||||
|
||||
public get type(): CallType {
|
||||
return (this.hasLocalUserMediaVideoTrack || this.hasRemoteUserMediaVideoTrack)
|
||||
? CallType.Video
|
||||
@@ -646,6 +656,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
this.feeds.push(new CallFeed({
|
||||
client: this.client,
|
||||
call: this,
|
||||
roomId: this.roomId,
|
||||
userId,
|
||||
deviceId: this.getOpponentDeviceId(),
|
||||
@@ -689,6 +700,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
this.feeds.push(new CallFeed({
|
||||
client: this.client,
|
||||
call: this,
|
||||
roomId: this.roomId,
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
@@ -928,7 +940,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(CallState.Ringing);
|
||||
this.state = CallState.Ringing;
|
||||
|
||||
if (event.getLocalAge()) {
|
||||
// Time out the call if it's ringing for too long
|
||||
@@ -936,7 +948,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
if (this.state == CallState.Ringing) {
|
||||
logger.debug(`Call ${this.callId} invite has expired. Hanging up.`);
|
||||
this.hangupParty = CallParty.Remote; // effectively
|
||||
this.setState(CallState.Ended);
|
||||
this.state = CallState.Ended;
|
||||
this.stopAllMedia();
|
||||
if (this.peerConn!.signalingState != 'closed') {
|
||||
this.peerConn!.close();
|
||||
@@ -963,7 +975,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// perverse as it may seem, sometimes we want to instantiate a call with a
|
||||
// hangup message (because when getting the state of the room on load, events
|
||||
// come in reverse order and we want to remember that a call has been hung up)
|
||||
this.setState(CallState.Ended);
|
||||
this.state = CallState.Ended;
|
||||
}
|
||||
|
||||
private shouldAnswerWithMediaType(
|
||||
@@ -1004,7 +1016,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const answerWithAudio = this.shouldAnswerWithMediaType(audio, this.hasRemoteUserMediaAudioTrack, "audio");
|
||||
const answerWithVideo = this.shouldAnswerWithMediaType(video, this.hasRemoteUserMediaVideoTrack, "video");
|
||||
|
||||
this.setState(CallState.WaitLocalMedia);
|
||||
this.state = CallState.WaitLocalMedia;
|
||||
this.waitForLocalAVStream = true;
|
||||
|
||||
try {
|
||||
@@ -1034,7 +1046,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
if (answerWithVideo) {
|
||||
// Try to answer without video
|
||||
logger.warn(`Call ${this.callId} Failed to getUserMedia(), trying to getUserMedia() without video`);
|
||||
this.setState(prevState);
|
||||
this.state = prevState;
|
||||
this.waitForLocalAVStream = false;
|
||||
await this.answer(answerWithAudio, false);
|
||||
} else {
|
||||
@@ -1043,7 +1055,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
}
|
||||
} else if (this.waitForLocalAVStream) {
|
||||
this.setState(CallState.WaitLocalMedia);
|
||||
this.state = CallState.WaitLocalMedia;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1495,7 +1507,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
});
|
||||
}
|
||||
|
||||
this.setState(CallState.CreateOffer);
|
||||
this.state = CallState.CreateOffer;
|
||||
|
||||
logger.debug(`Call ${this.callId} gotUserMediaForInvite`);
|
||||
// Now we wait for the negotiationneeded event
|
||||
@@ -1530,7 +1542,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.inviteOrAnswerSent = true;
|
||||
} catch (error) {
|
||||
// We've failed to answer: back to the ringing state
|
||||
this.setState(CallState.Ringing);
|
||||
this.state = CallState.Ringing;
|
||||
if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);
|
||||
|
||||
let code = CallErrorCode.SendAnswer;
|
||||
@@ -1627,7 +1639,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.pushLocalFeed(feed);
|
||||
}
|
||||
|
||||
this.setState(CallState.CreateAnswer);
|
||||
this.state = CallState.CreateAnswer;
|
||||
|
||||
let answer: RTCSessionDescriptionInit;
|
||||
try {
|
||||
@@ -1645,7 +1657,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// make sure we're still going
|
||||
if (this.callHasEnded()) return;
|
||||
|
||||
this.setState(CallState.Connecting);
|
||||
this.state = CallState.Connecting;
|
||||
|
||||
// Allow a short time for initial candidates to be gathered
|
||||
await new Promise(resolve => {
|
||||
@@ -1762,7 +1774,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.chooseOpponent(event);
|
||||
await this.addBufferedIceCandidates();
|
||||
|
||||
this.setState(CallState.Connecting);
|
||||
this.state = CallState.Connecting;
|
||||
|
||||
const sdpStreamMetadata = content[SDPStreamMetadataKey];
|
||||
if (sdpStreamMetadata) {
|
||||
@@ -2034,7 +2046,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.sendCandidateQueue();
|
||||
if (this.state === CallState.CreateOffer) {
|
||||
this.inviteOrAnswerSent = true;
|
||||
this.setState(CallState.InviteSent);
|
||||
this.state = CallState.InviteSent;
|
||||
this.inviteTimeout = setTimeout(() => {
|
||||
this.inviteTimeout = undefined;
|
||||
if (this.state === CallState.InviteSent) {
|
||||
@@ -2088,7 +2100,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// chrome doesn't implement any of the 'onstarted' events yet
|
||||
if (["connected", "completed"].includes(this.peerConn?.iceConnectionState ?? '')) {
|
||||
clearTimeout(this.iceDisconnectedTimeout);
|
||||
this.setState(CallState.Connected);
|
||||
this.state = CallState.Connected;
|
||||
|
||||
if (!this.callLengthInterval) {
|
||||
this.callLengthInterval = setInterval(() => {
|
||||
@@ -2112,7 +2124,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
logger.info(`Hanging up call ${this.callId} (ICE disconnected for too long)`);
|
||||
this.hangup(CallErrorCode.IceFailed, false);
|
||||
}, 30 * 1000);
|
||||
this.setState(CallState.Connecting);
|
||||
this.state = CallState.Connecting;
|
||||
}
|
||||
|
||||
// In PTT mode, override feed status to muted when we lose connection to
|
||||
@@ -2244,12 +2256,6 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true);
|
||||
};
|
||||
|
||||
private setState(state: CallState): void {
|
||||
const oldState = this.state;
|
||||
this.state = state;
|
||||
this.emit(CallEvent.State, state, oldState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
* @param {string} eventType
|
||||
@@ -2444,7 +2450,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
this.hangupParty = hangupParty;
|
||||
this.hangupReason = hangupReason;
|
||||
this.setState(CallState.Ended);
|
||||
this.state = CallState.Ended;
|
||||
|
||||
if (this.inviteTimeout) {
|
||||
clearTimeout(this.inviteTimeout);
|
||||
@@ -2578,7 +2584,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
if (!audio) {
|
||||
throw new Error("You CANNOT start a call without audio");
|
||||
}
|
||||
this.setState(CallState.WaitLocalMedia);
|
||||
this.state = CallState.WaitLocalMedia;
|
||||
|
||||
try {
|
||||
const stream = await this.client.getMediaHandler().getUserMediaStream(audio, video);
|
||||
|
@@ -20,6 +20,7 @@ import { MatrixClient } from "../client";
|
||||
import { RoomMember } from "../models/room-member";
|
||||
import { logger } from "../logger";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||
import { CallEvent, CallState, MatrixCall } from "./call";
|
||||
|
||||
const POLLING_INTERVAL = 200; // ms
|
||||
export const SPEAKING_THRESHOLD = -60; // dB
|
||||
@@ -40,6 +41,10 @@ export interface ICallFeedOpts {
|
||||
* Whether or not the remote SDPStreamMetadata says video is muted
|
||||
*/
|
||||
videoMuted: boolean;
|
||||
/**
|
||||
* The MatrixCall which is the source of this CallFeed
|
||||
*/
|
||||
call?: MatrixCall;
|
||||
}
|
||||
|
||||
export enum CallFeedEvent {
|
||||
@@ -47,6 +52,7 @@ export enum CallFeedEvent {
|
||||
MuteStateChanged = "mute_state_changed",
|
||||
LocalVolumeChanged = "local_volume_changed",
|
||||
VolumeChanged = "volume_changed",
|
||||
ConnectedChanged = "connected_changed",
|
||||
Speaking = "speaking",
|
||||
Disposed = "disposed",
|
||||
}
|
||||
@@ -56,6 +62,7 @@ type EventHandlerMap = {
|
||||
[CallFeedEvent.MuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;
|
||||
[CallFeedEvent.LocalVolumeChanged]: (localVolume: number) => void;
|
||||
[CallFeedEvent.VolumeChanged]: (volume: number) => void;
|
||||
[CallFeedEvent.ConnectedChanged]: (connected: boolean) => void;
|
||||
[CallFeedEvent.Speaking]: (speaking: boolean) => void;
|
||||
[CallFeedEvent.Disposed]: () => void;
|
||||
};
|
||||
@@ -69,6 +76,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
public speakingVolumeSamples: number[];
|
||||
|
||||
private client: MatrixClient;
|
||||
private call?: MatrixCall;
|
||||
private roomId?: string;
|
||||
private audioMuted: boolean;
|
||||
private videoMuted: boolean;
|
||||
@@ -81,11 +89,13 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
private speaking = false;
|
||||
private volumeLooperTimeout?: ReturnType<typeof setTimeout>;
|
||||
private _disposed = false;
|
||||
private _connected = false;
|
||||
|
||||
public constructor(opts: ICallFeedOpts) {
|
||||
super();
|
||||
|
||||
this.client = opts.client;
|
||||
this.call = opts.call;
|
||||
this.roomId = opts.roomId;
|
||||
this.userId = opts.userId;
|
||||
this.deviceId = opts.deviceId;
|
||||
@@ -101,6 +111,21 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
if (this.hasAudioTrack) {
|
||||
this.initVolumeMeasuring();
|
||||
}
|
||||
|
||||
if (opts.call) {
|
||||
opts.call.addListener(CallEvent.State, this.onCallState);
|
||||
this.onCallState(opts.call.state);
|
||||
}
|
||||
}
|
||||
|
||||
public get connected(): boolean {
|
||||
// Local feeds are always considered connected
|
||||
return this.isLocal() || this._connected;
|
||||
}
|
||||
|
||||
private set connected(connected: boolean) {
|
||||
this._connected = connected;
|
||||
this.emit(CallFeedEvent.ConnectedChanged, this.connected);
|
||||
}
|
||||
|
||||
private get hasAudioTrack(): boolean {
|
||||
@@ -145,6 +170,14 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
this.emit(CallFeedEvent.NewStream, this.stream);
|
||||
};
|
||||
|
||||
private onCallState = (state: CallState): void => {
|
||||
if (state === CallState.Connected) {
|
||||
this.connected = true;
|
||||
} else if (state === CallState.Connecting) {
|
||||
this.connected = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns callRoom member
|
||||
* @returns member of the callRoom
|
||||
@@ -297,6 +330,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
public dispose(): void {
|
||||
clearTimeout(this.volumeLooperTimeout);
|
||||
this.stream?.removeEventListener("addtrack", this.onAddTrack);
|
||||
this.call?.removeListener(CallEvent.State, this.onCallState);
|
||||
if (this.audioContext) {
|
||||
this.audioContext = undefined;
|
||||
this.analyser = undefined;
|
||||
|
@@ -168,11 +168,11 @@ export class GroupCall extends TypedEventEmitter<
|
||||
public localCallFeed?: CallFeed;
|
||||
public localScreenshareFeed?: CallFeed;
|
||||
public localDesktopCapturerSourceId?: string;
|
||||
public readonly calls = new Map<RoomMember, Map<string, MatrixCall>>();
|
||||
public readonly userMediaFeeds: CallFeed[] = [];
|
||||
public readonly screenshareFeeds: CallFeed[] = [];
|
||||
public groupCallId: string;
|
||||
|
||||
private readonly calls = new Map<RoomMember, Map<string, MatrixCall>>(); // RoomMember -> device ID -> MatrixCall
|
||||
private callHandlers = new Map<string, Map<string, ICallHandlers>>(); // User ID -> device ID -> handlers
|
||||
private activeSpeakerLoopInterval?: ReturnType<typeof setTimeout>;
|
||||
private retryCallLoopInterval?: ReturnType<typeof setTimeout>;
|
||||
|
Reference in New Issue
Block a user