diff --git a/src/client.ts b/src/client.ts index 2d8f86e0c..6909ae94b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -146,6 +146,7 @@ import { IThreepid } from "./@types/threepids"; import { CryptoStore } from "./crypto/store/base"; import { GroupCall } from "./webrtc/groupCall"; import { MediaHandler } from "./webrtc/mediaHandler"; +import { GroupCallEventHandler } from "./webrtc/groupCallEventHandler"; export type Store = IStore; export type SessionStore = WebStorageSessionStore; @@ -696,6 +697,7 @@ export class MatrixClient extends EventEmitter { public crypto: Crypto; // XXX: Intended private, used in code. public cryptoCallbacks: ICryptoCallbacks; // XXX: Intended private, used in code. public callEventHandler: CallEventHandler; // XXX: Intended private, used in code. + private groupCallEventHandler: GroupCallEventHandler; public supportsCallTransfer = false; // XXX: Intended private, used in code. public forceTURN = false; // XXX: Intended private, used in code. public iceCandidatePoolSize = 0; // XXX: Intended private, used in code. @@ -815,6 +817,7 @@ export class MatrixClient extends EventEmitter { const call = createNewMatrixCall(this, undefined, undefined); if (call) { this.callEventHandler = new CallEventHandler(this); + this.groupCallEventHandler = new GroupCallEventHandler(this); this.canSupportVoip = true; // Start listening for calls after the initial sync is done // We do not need to backfill the call event buffer @@ -1005,6 +1008,7 @@ export class MatrixClient extends EventEmitter { this.peekSync?.stopPeeking(); this.callEventHandler?.stop(); + this.groupCallEventHandler?.stop(); this.callEventHandler = null; global.clearInterval(this.checkTurnServersIntervalID); @@ -5591,6 +5595,7 @@ export class MatrixClient extends EventEmitter { private startCallEventHandler = (): void => { if (this.isInitialSyncComplete()) { this.callEventHandler.start(); + this.groupCallEventHandler.start(); this.off("sync", this.startCallEventHandler); } }; diff --git a/src/webrtc/groupCall.ts b/src/webrtc/groupCall.ts index 78adffe52..a38c4e2fd 100644 --- a/src/webrtc/groupCall.ts +++ b/src/webrtc/groupCall.ts @@ -19,7 +19,7 @@ export enum GroupCallEvent { LocalMuteStateChanged = "local_mute_state_changed", } -const CONF_ROOM = "me.robertlong.conf"; +export const CONF_ROOM = "me.robertlong.conf"; const CONF_PARTICIPANT = "me.robertlong.conf.participant"; export class GroupCall extends EventEmitter { @@ -48,6 +48,9 @@ export class GroupCall extends EventEmitter { super(); this.room = this.client.getRoom(roomId); + if (!this.room) { + throw new Error("Can't find the room"); + } this.reEmitter = new ReEmitter(this); } diff --git a/src/webrtc/groupCallEventHandler.ts b/src/webrtc/groupCallEventHandler.ts new file mode 100644 index 000000000..d0e54ff43 --- /dev/null +++ b/src/webrtc/groupCallEventHandler.ts @@ -0,0 +1,61 @@ +/* +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixEvent } from '../models/event'; +import { MatrixClient } from '../client'; +import { CONF_ROOM, GroupCall } from "./groupCall"; +import { RoomState } from "../models/room-state"; +import { CallType } from "./call"; + +export class GroupCallEventHandler { + private groupCalls = new Map(); // roomId -> GroupCall + + constructor(private client: MatrixClient) { } + + public start(): void { + this.client.on("RoomState.events", this.onRoomStateChanged); + } + + public stop(): void { + this.client.removeListener("RoomState.events", this.onRoomStateChanged); + } + + private onRoomStateChanged = (_event: MatrixEvent, state: RoomState): void => { + const groupCall = this.groupCalls.get(state.roomId); + const confEvents = state.getStateEvents(CONF_ROOM); + let content; + if (confEvents.length > 0) { + content = confEvents[0].getContent(); + } + + if (groupCall && !content?.active) { + groupCall.leave(); + this.groupCalls.delete(state.roomId); + } else if (!groupCall && content?.active) { + let callType: CallType; + + if (content.callType === "voice") { + callType = CallType.Voice; + } else { + callType = CallType.Video; + } + + const groupCall = this.client.createGroupCall(state.roomId, callType); + this.groupCalls.set(state.roomId, groupCall); + this.client.emit("GroupCall.incoming", groupCall); + } + }; +}