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,
|
MatrixClient,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
Room,
|
Room,
|
||||||
|
RoomMember,
|
||||||
RoomState,
|
RoomState,
|
||||||
RoomStateEvent,
|
RoomStateEvent,
|
||||||
RoomStateEventHandlerMap,
|
RoomStateEventHandlerMap,
|
||||||
@@ -33,7 +34,7 @@ import {
|
|||||||
import { TypedEventEmitter } from "../../src/models/typed-event-emitter";
|
import { TypedEventEmitter } from "../../src/models/typed-event-emitter";
|
||||||
import { ReEmitter } from "../../src/ReEmitter";
|
import { ReEmitter } from "../../src/ReEmitter";
|
||||||
import { SyncState } from "../../src/sync";
|
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 { CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from "../../src/webrtc/callEventHandler";
|
||||||
import { CallFeed } from "../../src/webrtc/callFeed";
|
import { CallFeed } from "../../src/webrtc/callFeed";
|
||||||
import { GroupCallEventHandlerMap } from "../../src/webrtc/groupCall";
|
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 USERMEDIA_STREAM_ID = "mock_stream_from_media_handler";
|
||||||
export const SCREENSHARE_STREAM_ID = "mock_screen_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 {
|
class MockMediaStreamAudioSourceNode {
|
||||||
public connect() {}
|
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 {
|
export class MockCallFeed {
|
||||||
constructor(
|
constructor(
|
||||||
public userId: string,
|
public userId: string,
|
||||||
|
@@ -1392,7 +1392,7 @@ describe('Call', function() {
|
|||||||
it("ends call on onHangupReceived() if state is ringing", async () => {
|
it("ends call on onHangupReceived() if state is ringing", async () => {
|
||||||
expect(call.callHasEnded()).toBe(false);
|
expect(call.callHasEnded()).toBe(false);
|
||||||
|
|
||||||
call.state = CallState.Ringing;
|
(call as any).state = CallState.Ringing;
|
||||||
call.onHangupReceived({} as MCallHangupReject);
|
call.onHangupReceived({} as MCallHangupReject);
|
||||||
|
|
||||||
expect(call.callHasEnded()).toBe(true);
|
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) => {
|
)("ends call on onRejectReceived() if in correct state (state=%s)", async (state: CallState) => {
|
||||||
expect(call.callHasEnded()).toBe(false);
|
expect(call.callHasEnded()).toBe(false);
|
||||||
|
|
||||||
call.state = state;
|
(call as any).state = state;
|
||||||
call.onRejectReceived({} as MCallHangupReject);
|
call.onRejectReceived({} as MCallHangupReject);
|
||||||
|
|
||||||
expect(call.callHasEnded()).toBe(
|
expect(call.callHasEnded()).toBe(
|
||||||
|
@@ -17,26 +17,23 @@ limitations under the License.
|
|||||||
import { SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes";
|
import { SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes";
|
||||||
import { CallFeed } from "../../../src/webrtc/callFeed";
|
import { CallFeed } from "../../../src/webrtc/callFeed";
|
||||||
import { TestClient } from "../../TestClient";
|
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", () => {
|
describe("CallFeed", () => {
|
||||||
let client;
|
const roomId = "room1";
|
||||||
|
let client: TestClient;
|
||||||
beforeEach(() => {
|
let call: MockMatrixCall;
|
||||||
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
client.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("muting", () => {
|
|
||||||
let feed: CallFeed;
|
let feed: CallFeed;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
|
||||||
|
call = new MockMatrixCall(roomId);
|
||||||
|
|
||||||
feed = new CallFeed({
|
feed = new CallFeed({
|
||||||
client,
|
client: client.client,
|
||||||
roomId: "room1",
|
call: call.typed(),
|
||||||
|
roomId,
|
||||||
userId: "user1",
|
userId: "user1",
|
||||||
// @ts-ignore Mock
|
// @ts-ignore Mock
|
||||||
stream: new MockMediaStream("stream1"),
|
stream: new MockMediaStream("stream1"),
|
||||||
@@ -46,6 +43,11 @@ describe("CallFeed", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
client.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("muting", () => {
|
||||||
describe("muting by default", () => {
|
describe("muting by default", () => {
|
||||||
it("should mute audio by default", () => {
|
it("should mute audio by default", () => {
|
||||||
expect(feed.isAudioMuted()).toBeTruthy();
|
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,
|
MockMediaStream,
|
||||||
MockMediaStreamTrack,
|
MockMediaStreamTrack,
|
||||||
MockRTCPeerConnection,
|
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';
|
} from '../../test-utils/webrtc';
|
||||||
import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes";
|
import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes";
|
||||||
import { sleep } from "../../../src/utils";
|
import { sleep } from "../../../src/utils";
|
||||||
@@ -41,16 +51,6 @@ import { CallFeed } from '../../../src/webrtc/callFeed';
|
|||||||
import { CallEvent, CallState } from '../../../src/webrtc/call';
|
import { CallEvent, CallState } from '../../../src/webrtc/call';
|
||||||
import { flushPromises } from '../../test-utils/flushPromises';
|
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 = [
|
const FAKE_STATE_EVENTS = [
|
||||||
{
|
{
|
||||||
getContent: () => ({
|
getContent: () => ({
|
||||||
@@ -123,42 +123,6 @@ const createAndEnterGroupCall = async (cli: MatrixClient, room: Room): Promise<G
|
|||||||
return groupCall;
|
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() {
|
describe('Group Call', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
installWebRTCMocks();
|
installWebRTCMocks();
|
||||||
@@ -351,7 +315,7 @@ describe('Group Call', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("call feeds changing", () => {
|
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 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"));
|
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");
|
jest.spyOn(groupCall, "emit");
|
||||||
|
|
||||||
call = new MockCall(room.roomId, groupCall.groupCallId);
|
call = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||||
|
|
||||||
await groupCall.create();
|
await groupCall.create();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores changes, if we can't get user id of opponent", async () => {
|
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 });
|
jest.spyOn(call, "getOpponentMember").mockReturnValue({ userId: undefined });
|
||||||
|
|
||||||
// @ts-ignore Mock
|
// @ts-ignore Mock
|
||||||
@@ -514,10 +478,11 @@ describe('Group Call', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("sends metadata updates before unmuting in PTT mode", async () => {
|
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(
|
groupCall.calls.set(
|
||||||
mockCall.getOpponentMember() as RoomMember,
|
mockCall.getOpponentMember() as RoomMember,
|
||||||
new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]),
|
new Map([[mockCall.getOpponentDeviceId()!, mockCall.typed()]]),
|
||||||
);
|
);
|
||||||
|
|
||||||
let metadataUpdateResolve: () => void;
|
let metadataUpdateResolve: () => void;
|
||||||
@@ -539,10 +504,11 @@ describe('Group Call', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("sends metadata updates after muting in PTT mode", async () => {
|
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(
|
groupCall.calls.set(
|
||||||
mockCall.getOpponentMember() as RoomMember,
|
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
|
// 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();
|
expect(client1.sendToDevice).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const oldCall = groupCall1.calls.get(
|
const oldCall = groupCall1.calls.get(
|
||||||
groupCall1.room.getMember(client2.userId)!,
|
groupCall1.room.getMember(client2.userId)!,
|
||||||
)!.get(client2.deviceId)!;
|
)!.get(client2.deviceId)!;
|
||||||
@@ -719,6 +686,7 @@ describe('Group Call', function() {
|
|||||||
// to even be created...
|
// to even be created...
|
||||||
let newCall: MatrixCall | undefined;
|
let newCall: MatrixCall | undefined;
|
||||||
while (
|
while (
|
||||||
|
// @ts-ignore
|
||||||
(newCall = groupCall1.calls.get(
|
(newCall = groupCall1.calls.get(
|
||||||
groupCall1.room.getMember(client2.userId)!,
|
groupCall1.room.getMember(client2.userId)!,
|
||||||
)?.get(client2.deviceId)) === undefined
|
)?.get(client2.deviceId)) === undefined
|
||||||
@@ -763,6 +731,7 @@ describe('Group Call', function() {
|
|||||||
groupCall1.setMicrophoneMuted(false);
|
groupCall1.setMicrophoneMuted(false);
|
||||||
groupCall1.setLocalVideoMuted(false);
|
groupCall1.setLocalVideoMuted(false);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const call = groupCall1.calls.get(
|
const call = groupCall1.calls.get(
|
||||||
groupCall1.room.getMember(client2.userId)!,
|
groupCall1.room.getMember(client2.userId)!,
|
||||||
)!.get(client2.deviceId)!;
|
)!.get(client2.deviceId)!;
|
||||||
@@ -874,7 +843,10 @@ describe('Group Call', function() {
|
|||||||
// It takes a bit of time for the calls to get created
|
// It takes a bit of time for the calls to get created
|
||||||
await sleep(10);
|
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.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||||
// @ts-ignore Mock
|
// @ts-ignore Mock
|
||||||
call.pushRemoteFeed(new MockMediaStream("stream", [
|
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
|
// It takes a bit of time for the calls to get created
|
||||||
await sleep(10);
|
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.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||||
// @ts-ignore Mock
|
// @ts-ignore Mock
|
||||||
call.pushRemoteFeed(new MockMediaStream("stream", [
|
call.pushRemoteFeed(new MockMediaStream("stream", [
|
||||||
@@ -939,7 +914,7 @@ describe('Group Call', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("ignores incoming calls for other rooms", async () => {
|
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);
|
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 () => {
|
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);
|
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 () => {
|
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;
|
mockCall.state = CallState.Connected;
|
||||||
|
|
||||||
mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall);
|
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 () => {
|
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);
|
mockClient.emit(CallEventHandlerEvent.Incoming, mockCall as unknown as MatrixCall);
|
||||||
|
|
||||||
expect(mockCall.reject).not.toHaveBeenCalled();
|
expect(mockCall.reject).not.toHaveBeenCalled();
|
||||||
expect(mockCall.answerWithCallFeeds).toHaveBeenCalled();
|
expect(mockCall.answerWithCallFeeds).toHaveBeenCalled();
|
||||||
|
// @ts-ignore
|
||||||
expect(groupCall.calls).toEqual(new Map([[
|
expect(groupCall.calls).toEqual(new Map([[
|
||||||
groupCall.room.getMember(FAKE_USER_ID_1)!,
|
groupCall.room.getMember(FAKE_USER_ID_1)!,
|
||||||
new Map([[FAKE_DEVICE_ID_1, mockCall]]),
|
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 () => {
|
it("replaces calls if it already has one with the same user", async () => {
|
||||||
const oldMockCall = new MockCall(room.roomId, groupCall.groupCallId);
|
const oldMockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||||
const newMockCall = new MockCall(room.roomId, groupCall.groupCallId);
|
const newMockCall = new MockMatrixCall(room.roomId, groupCall.groupCallId);
|
||||||
newMockCall.opponentMember = oldMockCall.opponentMember; // Ensure referential equality
|
newMockCall.opponentMember = oldMockCall.opponentMember; // Ensure referential equality
|
||||||
newMockCall.callId = "not " + oldMockCall.callId;
|
newMockCall.callId = "not " + oldMockCall.callId;
|
||||||
|
|
||||||
@@ -989,6 +965,7 @@ describe('Group Call', function() {
|
|||||||
|
|
||||||
expect(oldMockCall.hangup).toHaveBeenCalled();
|
expect(oldMockCall.hangup).toHaveBeenCalled();
|
||||||
expect(newMockCall.answerWithCallFeeds).toHaveBeenCalled();
|
expect(newMockCall.answerWithCallFeeds).toHaveBeenCalled();
|
||||||
|
// @ts-ignore
|
||||||
expect(groupCall.calls).toEqual(new Map([[
|
expect(groupCall.calls).toEqual(new Map([[
|
||||||
groupCall.room.getMember(FAKE_USER_ID_1)!,
|
groupCall.room.getMember(FAKE_USER_ID_1)!,
|
||||||
new Map([[FAKE_DEVICE_ID_1, newMockCall]]),
|
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
|
// First we leave the call since we have already entered
|
||||||
groupCall.leave();
|
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>([
|
mockClient.callEventHandler!.calls = new Map<string, MatrixCall>([
|
||||||
[call.callId, call.typed()],
|
[call.callId, call.typed()],
|
||||||
]);
|
]);
|
||||||
@@ -1072,7 +1049,10 @@ describe('Group Call', function() {
|
|||||||
// It takes a bit of time for the calls to get created
|
// It takes a bit of time for the calls to get created
|
||||||
await sleep(10);
|
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.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||||
call.onNegotiateReceived({
|
call.onNegotiateReceived({
|
||||||
getContent: () => ({
|
getContent: () => ({
|
||||||
|
@@ -334,7 +334,6 @@ 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 hangupParty?: CallParty;
|
public hangupParty?: CallParty;
|
||||||
public hangupReason?: string;
|
public hangupReason?: string;
|
||||||
public direction?: CallDirection;
|
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.
|
// This should be set by the consumer on incoming & outgoing calls.
|
||||||
public isPtt = false;
|
public isPtt = false;
|
||||||
|
|
||||||
|
private _state = CallState.Fledgling;
|
||||||
private readonly client: MatrixClient;
|
private readonly client: MatrixClient;
|
||||||
private readonly forceTURN?: boolean;
|
private readonly forceTURN?: boolean;
|
||||||
private readonly turnServers: Array<TurnServer>;
|
private readonly turnServers: Array<TurnServer>;
|
||||||
@@ -482,6 +482,16 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
return this.remoteAssertedIdentity;
|
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 {
|
public get type(): CallType {
|
||||||
return (this.hasLocalUserMediaVideoTrack || this.hasRemoteUserMediaVideoTrack)
|
return (this.hasLocalUserMediaVideoTrack || this.hasRemoteUserMediaVideoTrack)
|
||||||
? CallType.Video
|
? CallType.Video
|
||||||
@@ -646,6 +656,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
|
|
||||||
this.feeds.push(new CallFeed({
|
this.feeds.push(new CallFeed({
|
||||||
client: this.client,
|
client: this.client,
|
||||||
|
call: this,
|
||||||
roomId: this.roomId,
|
roomId: this.roomId,
|
||||||
userId,
|
userId,
|
||||||
deviceId: this.getOpponentDeviceId(),
|
deviceId: this.getOpponentDeviceId(),
|
||||||
@@ -689,6 +700,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
|
|
||||||
this.feeds.push(new CallFeed({
|
this.feeds.push(new CallFeed({
|
||||||
client: this.client,
|
client: this.client,
|
||||||
|
call: this,
|
||||||
roomId: this.roomId,
|
roomId: this.roomId,
|
||||||
audioMuted: false,
|
audioMuted: false,
|
||||||
videoMuted: false,
|
videoMuted: false,
|
||||||
@@ -928,7 +940,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(CallState.Ringing);
|
this.state = CallState.Ringing;
|
||||||
|
|
||||||
if (event.getLocalAge()) {
|
if (event.getLocalAge()) {
|
||||||
// Time out the call if it's ringing for too long
|
// 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) {
|
if (this.state == CallState.Ringing) {
|
||||||
logger.debug(`Call ${this.callId} invite has expired. Hanging up.`);
|
logger.debug(`Call ${this.callId} invite has expired. Hanging up.`);
|
||||||
this.hangupParty = CallParty.Remote; // effectively
|
this.hangupParty = CallParty.Remote; // effectively
|
||||||
this.setState(CallState.Ended);
|
this.state = CallState.Ended;
|
||||||
this.stopAllMedia();
|
this.stopAllMedia();
|
||||||
if (this.peerConn!.signalingState != 'closed') {
|
if (this.peerConn!.signalingState != 'closed') {
|
||||||
this.peerConn!.close();
|
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
|
// 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
|
// 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)
|
// 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(
|
private shouldAnswerWithMediaType(
|
||||||
@@ -1004,7 +1016,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
const answerWithAudio = this.shouldAnswerWithMediaType(audio, this.hasRemoteUserMediaAudioTrack, "audio");
|
const answerWithAudio = this.shouldAnswerWithMediaType(audio, this.hasRemoteUserMediaAudioTrack, "audio");
|
||||||
const answerWithVideo = this.shouldAnswerWithMediaType(video, this.hasRemoteUserMediaVideoTrack, "video");
|
const answerWithVideo = this.shouldAnswerWithMediaType(video, this.hasRemoteUserMediaVideoTrack, "video");
|
||||||
|
|
||||||
this.setState(CallState.WaitLocalMedia);
|
this.state = CallState.WaitLocalMedia;
|
||||||
this.waitForLocalAVStream = true;
|
this.waitForLocalAVStream = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1034,7 +1046,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
if (answerWithVideo) {
|
if (answerWithVideo) {
|
||||||
// Try to answer without video
|
// Try to answer without video
|
||||||
logger.warn(`Call ${this.callId} Failed to getUserMedia(), trying to getUserMedia() without video`);
|
logger.warn(`Call ${this.callId} Failed to getUserMedia(), trying to getUserMedia() without video`);
|
||||||
this.setState(prevState);
|
this.state = prevState;
|
||||||
this.waitForLocalAVStream = false;
|
this.waitForLocalAVStream = false;
|
||||||
await this.answer(answerWithAudio, false);
|
await this.answer(answerWithAudio, false);
|
||||||
} else {
|
} else {
|
||||||
@@ -1043,7 +1055,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (this.waitForLocalAVStream) {
|
} 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`);
|
logger.debug(`Call ${this.callId} gotUserMediaForInvite`);
|
||||||
// Now we wait for the negotiationneeded event
|
// Now we wait for the negotiationneeded event
|
||||||
@@ -1530,7 +1542,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
this.inviteOrAnswerSent = true;
|
this.inviteOrAnswerSent = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// We've failed to answer: back to the ringing state
|
// 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);
|
if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);
|
||||||
|
|
||||||
let code = CallErrorCode.SendAnswer;
|
let code = CallErrorCode.SendAnswer;
|
||||||
@@ -1627,7 +1639,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
this.pushLocalFeed(feed);
|
this.pushLocalFeed(feed);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(CallState.CreateAnswer);
|
this.state = CallState.CreateAnswer;
|
||||||
|
|
||||||
let answer: RTCSessionDescriptionInit;
|
let answer: RTCSessionDescriptionInit;
|
||||||
try {
|
try {
|
||||||
@@ -1645,7 +1657,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
// make sure we're still going
|
// make sure we're still going
|
||||||
if (this.callHasEnded()) return;
|
if (this.callHasEnded()) return;
|
||||||
|
|
||||||
this.setState(CallState.Connecting);
|
this.state = CallState.Connecting;
|
||||||
|
|
||||||
// Allow a short time for initial candidates to be gathered
|
// Allow a short time for initial candidates to be gathered
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
@@ -1762,7 +1774,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
this.chooseOpponent(event);
|
this.chooseOpponent(event);
|
||||||
await this.addBufferedIceCandidates();
|
await this.addBufferedIceCandidates();
|
||||||
|
|
||||||
this.setState(CallState.Connecting);
|
this.state = CallState.Connecting;
|
||||||
|
|
||||||
const sdpStreamMetadata = content[SDPStreamMetadataKey];
|
const sdpStreamMetadata = content[SDPStreamMetadataKey];
|
||||||
if (sdpStreamMetadata) {
|
if (sdpStreamMetadata) {
|
||||||
@@ -2034,7 +2046,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
this.sendCandidateQueue();
|
this.sendCandidateQueue();
|
||||||
if (this.state === CallState.CreateOffer) {
|
if (this.state === CallState.CreateOffer) {
|
||||||
this.inviteOrAnswerSent = true;
|
this.inviteOrAnswerSent = true;
|
||||||
this.setState(CallState.InviteSent);
|
this.state = CallState.InviteSent;
|
||||||
this.inviteTimeout = setTimeout(() => {
|
this.inviteTimeout = setTimeout(() => {
|
||||||
this.inviteTimeout = undefined;
|
this.inviteTimeout = undefined;
|
||||||
if (this.state === CallState.InviteSent) {
|
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
|
// 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.state = CallState.Connected;
|
||||||
|
|
||||||
if (!this.callLengthInterval) {
|
if (!this.callLengthInterval) {
|
||||||
this.callLengthInterval = setInterval(() => {
|
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)`);
|
logger.info(`Hanging up call ${this.callId} (ICE disconnected for too long)`);
|
||||||
this.hangup(CallErrorCode.IceFailed, false);
|
this.hangup(CallErrorCode.IceFailed, false);
|
||||||
}, 30 * 1000);
|
}, 30 * 1000);
|
||||||
this.setState(CallState.Connecting);
|
this.state = CallState.Connecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In PTT mode, override feed status to muted when we lose connection to
|
// 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);
|
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
|
* Internal
|
||||||
* @param {string} eventType
|
* @param {string} eventType
|
||||||
@@ -2444,7 +2450,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
|
|
||||||
this.hangupParty = hangupParty;
|
this.hangupParty = hangupParty;
|
||||||
this.hangupReason = hangupReason;
|
this.hangupReason = hangupReason;
|
||||||
this.setState(CallState.Ended);
|
this.state = CallState.Ended;
|
||||||
|
|
||||||
if (this.inviteTimeout) {
|
if (this.inviteTimeout) {
|
||||||
clearTimeout(this.inviteTimeout);
|
clearTimeout(this.inviteTimeout);
|
||||||
@@ -2578,7 +2584,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
if (!audio) {
|
if (!audio) {
|
||||||
throw new Error("You CANNOT start a call without audio");
|
throw new Error("You CANNOT start a call without audio");
|
||||||
}
|
}
|
||||||
this.setState(CallState.WaitLocalMedia);
|
this.state = CallState.WaitLocalMedia;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stream = await this.client.getMediaHandler().getUserMediaStream(audio, video);
|
const stream = await this.client.getMediaHandler().getUserMediaStream(audio, video);
|
||||||
|
@@ -20,6 +20,7 @@ import { MatrixClient } from "../client";
|
|||||||
import { RoomMember } from "../models/room-member";
|
import { RoomMember } from "../models/room-member";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||||
|
import { CallEvent, CallState, MatrixCall } from "./call";
|
||||||
|
|
||||||
const POLLING_INTERVAL = 200; // ms
|
const POLLING_INTERVAL = 200; // ms
|
||||||
export const SPEAKING_THRESHOLD = -60; // dB
|
export const SPEAKING_THRESHOLD = -60; // dB
|
||||||
@@ -40,6 +41,10 @@ export interface ICallFeedOpts {
|
|||||||
* Whether or not the remote SDPStreamMetadata says video is muted
|
* Whether or not the remote SDPStreamMetadata says video is muted
|
||||||
*/
|
*/
|
||||||
videoMuted: boolean;
|
videoMuted: boolean;
|
||||||
|
/**
|
||||||
|
* The MatrixCall which is the source of this CallFeed
|
||||||
|
*/
|
||||||
|
call?: MatrixCall;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CallFeedEvent {
|
export enum CallFeedEvent {
|
||||||
@@ -47,6 +52,7 @@ export enum CallFeedEvent {
|
|||||||
MuteStateChanged = "mute_state_changed",
|
MuteStateChanged = "mute_state_changed",
|
||||||
LocalVolumeChanged = "local_volume_changed",
|
LocalVolumeChanged = "local_volume_changed",
|
||||||
VolumeChanged = "volume_changed",
|
VolumeChanged = "volume_changed",
|
||||||
|
ConnectedChanged = "connected_changed",
|
||||||
Speaking = "speaking",
|
Speaking = "speaking",
|
||||||
Disposed = "disposed",
|
Disposed = "disposed",
|
||||||
}
|
}
|
||||||
@@ -56,6 +62,7 @@ type EventHandlerMap = {
|
|||||||
[CallFeedEvent.MuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;
|
[CallFeedEvent.MuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;
|
||||||
[CallFeedEvent.LocalVolumeChanged]: (localVolume: number) => void;
|
[CallFeedEvent.LocalVolumeChanged]: (localVolume: number) => void;
|
||||||
[CallFeedEvent.VolumeChanged]: (volume: number) => void;
|
[CallFeedEvent.VolumeChanged]: (volume: number) => void;
|
||||||
|
[CallFeedEvent.ConnectedChanged]: (connected: boolean) => void;
|
||||||
[CallFeedEvent.Speaking]: (speaking: boolean) => void;
|
[CallFeedEvent.Speaking]: (speaking: boolean) => void;
|
||||||
[CallFeedEvent.Disposed]: () => void;
|
[CallFeedEvent.Disposed]: () => void;
|
||||||
};
|
};
|
||||||
@@ -69,6 +76,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
|||||||
public speakingVolumeSamples: number[];
|
public speakingVolumeSamples: number[];
|
||||||
|
|
||||||
private client: MatrixClient;
|
private client: MatrixClient;
|
||||||
|
private call?: MatrixCall;
|
||||||
private roomId?: string;
|
private roomId?: string;
|
||||||
private audioMuted: boolean;
|
private audioMuted: boolean;
|
||||||
private videoMuted: boolean;
|
private videoMuted: boolean;
|
||||||
@@ -81,11 +89,13 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
|||||||
private speaking = false;
|
private speaking = false;
|
||||||
private volumeLooperTimeout?: ReturnType<typeof setTimeout>;
|
private volumeLooperTimeout?: ReturnType<typeof setTimeout>;
|
||||||
private _disposed = false;
|
private _disposed = false;
|
||||||
|
private _connected = false;
|
||||||
|
|
||||||
public constructor(opts: ICallFeedOpts) {
|
public constructor(opts: ICallFeedOpts) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.client = opts.client;
|
this.client = opts.client;
|
||||||
|
this.call = opts.call;
|
||||||
this.roomId = opts.roomId;
|
this.roomId = opts.roomId;
|
||||||
this.userId = opts.userId;
|
this.userId = opts.userId;
|
||||||
this.deviceId = opts.deviceId;
|
this.deviceId = opts.deviceId;
|
||||||
@@ -101,6 +111,21 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
|||||||
if (this.hasAudioTrack) {
|
if (this.hasAudioTrack) {
|
||||||
this.initVolumeMeasuring();
|
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 {
|
private get hasAudioTrack(): boolean {
|
||||||
@@ -145,6 +170,14 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
|||||||
this.emit(CallFeedEvent.NewStream, this.stream);
|
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 callRoom member
|
||||||
* @returns member of the callRoom
|
* @returns member of the callRoom
|
||||||
@@ -297,6 +330,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
|||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
clearTimeout(this.volumeLooperTimeout);
|
clearTimeout(this.volumeLooperTimeout);
|
||||||
this.stream?.removeEventListener("addtrack", this.onAddTrack);
|
this.stream?.removeEventListener("addtrack", this.onAddTrack);
|
||||||
|
this.call?.removeListener(CallEvent.State, this.onCallState);
|
||||||
if (this.audioContext) {
|
if (this.audioContext) {
|
||||||
this.audioContext = undefined;
|
this.audioContext = undefined;
|
||||||
this.analyser = undefined;
|
this.analyser = undefined;
|
||||||
|
@@ -168,11 +168,11 @@ export class GroupCall extends TypedEventEmitter<
|
|||||||
public localCallFeed?: CallFeed;
|
public localCallFeed?: CallFeed;
|
||||||
public localScreenshareFeed?: CallFeed;
|
public localScreenshareFeed?: CallFeed;
|
||||||
public localDesktopCapturerSourceId?: string;
|
public localDesktopCapturerSourceId?: string;
|
||||||
public readonly calls = new Map<RoomMember, Map<string, MatrixCall>>();
|
|
||||||
public readonly userMediaFeeds: CallFeed[] = [];
|
public readonly userMediaFeeds: CallFeed[] = [];
|
||||||
public readonly screenshareFeeds: CallFeed[] = [];
|
public readonly screenshareFeeds: CallFeed[] = [];
|
||||||
public groupCallId: string;
|
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 callHandlers = new Map<string, Map<string, ICallHandlers>>(); // User ID -> device ID -> handlers
|
||||||
private activeSpeakerLoopInterval?: ReturnType<typeof setTimeout>;
|
private activeSpeakerLoopInterval?: ReturnType<typeof setTimeout>;
|
||||||
private retryCallLoopInterval?: ReturnType<typeof setTimeout>;
|
private retryCallLoopInterval?: ReturnType<typeof setTimeout>;
|
||||||
|
Reference in New Issue
Block a user