mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-15 00:04:59 +08:00
Add new WIP ConfCallManager
This commit is contained in:
parent
274b3336c9
commit
dff8a1acd3
@ -16,6 +16,7 @@ limitations under the License.
|
||||
|
||||
import EventEmitter from "events";
|
||||
import { ConferenceCallDebugger } from "./ConferenceCallDebugger";
|
||||
import { randomString } from "./randomstring";
|
||||
|
||||
const CONF_ROOM = "me.robertlong.conf";
|
||||
const CONF_PARTICIPANT = "me.robertlong.conf.participant";
|
||||
@ -186,8 +187,17 @@ export class ConferenceCallManager extends EventEmitter {
|
||||
|
||||
call.removeListener("hangup", onHangup);
|
||||
call.removeListener("replaced", onReplaced);
|
||||
|
||||
const userId = call.opponentMember.userId;
|
||||
this._addCall(call, userId);
|
||||
const existingParticipant = this.participants.find(
|
||||
(p) => p.userId === userId
|
||||
);
|
||||
|
||||
if (existingParticipant) {
|
||||
existingParticipant.call = call;
|
||||
}
|
||||
|
||||
this._addCall(call);
|
||||
call.answer();
|
||||
this.emit("call", call);
|
||||
}
|
||||
@ -270,7 +280,7 @@ export class ConferenceCallManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const call = this.client.createCall(this.roomId, userId);
|
||||
this._addCall(call, userId);
|
||||
this._addCall(call);
|
||||
call.placeVideoCall().then(() => {
|
||||
this.emit("call", call);
|
||||
});
|
||||
@ -314,23 +324,21 @@ export class ConferenceCallManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const userId = call.opponentMember.userId;
|
||||
this._addCall(call, userId);
|
||||
const existingParticipant = this.participants.find(
|
||||
(p) => p.userId === userId
|
||||
);
|
||||
|
||||
if (existingParticipant) {
|
||||
existingParticipant.call = call;
|
||||
}
|
||||
|
||||
this._addCall(call);
|
||||
call.answer();
|
||||
this.emit("call", call);
|
||||
};
|
||||
|
||||
_addCall(call, userId) {
|
||||
if (call.state === "ended") {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingCall = this.participants.find(
|
||||
(p) => !p.local && p.call && p.call.callId === call.callId
|
||||
);
|
||||
|
||||
if (existingCall) {
|
||||
return;
|
||||
}
|
||||
_addCall(call) {
|
||||
const userId = call.opponentMember.userId;
|
||||
|
||||
this.participants.push({
|
||||
userId,
|
||||
@ -450,3 +458,221 @@ export class ConferenceCallManager extends EventEmitter {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* - incoming
|
||||
* - you have not joined
|
||||
* - you have joined
|
||||
* - initial room members
|
||||
* - new room members
|
||||
*/
|
||||
|
||||
class ConferenceCallManager2 extends EventEmitter {
|
||||
constructor(client) {
|
||||
super();
|
||||
|
||||
this.client = client;
|
||||
|
||||
this.room = null;
|
||||
|
||||
// The session id is used to re-initiate calls if the user's participant
|
||||
// session id has changed
|
||||
this.sessionId = randomString(16);
|
||||
|
||||
this._memberParticipantStateTimeout = null;
|
||||
|
||||
// Whether or not we have entered the conference call.
|
||||
this.entered = false;
|
||||
|
||||
// The MatrixCalls that were picked up by the Call.incoming listener,
|
||||
// before the user entered the conference call.
|
||||
this._incomingCallQueue = [];
|
||||
|
||||
// A representation of the conference call data for each room member
|
||||
// that has entered the call.
|
||||
this.participants = [];
|
||||
|
||||
this.localParticipant = null;
|
||||
|
||||
this.client.on("RoomState.members", this._onRoomStateMembers);
|
||||
this.client.on("Call.incoming", this._onIncomingCall);
|
||||
}
|
||||
|
||||
async enter(roomId, timeout = 30000) {
|
||||
// Ensure that we have joined the provided room.
|
||||
await this.client.joinRoom(roomId);
|
||||
|
||||
// Get the room info, wait if it hasn't been fetched yet.
|
||||
// Timeout after 30 seconds or the provided duration.
|
||||
const room = await new Promise((resolve, reject) => {
|
||||
const initialRoom = manager.client.getRoom(roomId);
|
||||
|
||||
if (initialRoom) {
|
||||
resolve(initialRoom);
|
||||
return;
|
||||
}
|
||||
|
||||
const roomTimeout = setTimeout(() => {
|
||||
reject(new Error("Room could not be found."));
|
||||
}, timeout);
|
||||
|
||||
const roomCallback = (room) => {
|
||||
if (room && room.roomId === roomId) {
|
||||
this.client.removeListener("Room", roomCallback);
|
||||
clearTimeout(roomTimeout);
|
||||
resolve(room);
|
||||
}
|
||||
};
|
||||
|
||||
this.client.on("Room", roomCallback);
|
||||
});
|
||||
|
||||
this.room = room;
|
||||
|
||||
// Ensure that this room is marked as a conference room so clients can react appropriately
|
||||
const activeConf = room.currentState
|
||||
.getStateEvents(CONF_ROOM, "")
|
||||
?.getContent()?.active;
|
||||
|
||||
if (!activeConf) {
|
||||
this.client.sendStateEvent(room.roomId, CONF_ROOM, { active: true }, "");
|
||||
}
|
||||
|
||||
// Request permissions and get the user's webcam/mic stream if we haven't yet.
|
||||
const userId = this.client.getUserId();
|
||||
const stream = await this.client.getLocalVideoStream();
|
||||
|
||||
this.localParticipant = {
|
||||
userId,
|
||||
stream,
|
||||
};
|
||||
|
||||
this.participants.push(this.localParticipant);
|
||||
this.emit("debugstate", userId, null, "you");
|
||||
|
||||
// Announce to the other room members that we have entered the room.
|
||||
// Continue doing so every PARTICIPANT_TIMEOUT ms
|
||||
this._updateMemberParticipantState();
|
||||
|
||||
// Set up participants for the members currently in the room.
|
||||
// Other members will be picked up by the RoomState.members event.
|
||||
const initialMembers = room.getMembers();
|
||||
|
||||
for (const member of initialMembers) {
|
||||
this._onMemberChanged(member);
|
||||
}
|
||||
|
||||
this.entered = true;
|
||||
}
|
||||
|
||||
_updateMemberParticipantState = () => {
|
||||
const userId = this.client.getUserId();
|
||||
const currentMemberState = this.room.currentState.getStateEvents(
|
||||
"m.room.member",
|
||||
userId
|
||||
);
|
||||
|
||||
this.client.sendStateEvent(
|
||||
this.room.roomId,
|
||||
"m.room.member",
|
||||
{
|
||||
...currentMemberState.getContent(),
|
||||
[CONF_PARTICIPANT]: {
|
||||
sessionId: this.sessionId,
|
||||
expiresAt: new Date().getTime() + PARTICIPANT_TIMEOUT * 2,
|
||||
},
|
||||
},
|
||||
userId
|
||||
);
|
||||
|
||||
this._memberParticipantStateTimeout = setTimeout(
|
||||
this._updateMemberParticipantState,
|
||||
PARTICIPANT_TIMEOUT
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Call Setup
|
||||
*
|
||||
* There are two different paths for calls to be created:
|
||||
* 1. Incoming calls triggered by the Call.incoming event.
|
||||
* 2. Outgoing calls to the initial members of a room or new members
|
||||
* as they are observed by the RoomState.members event.
|
||||
*/
|
||||
|
||||
_onIncomingCall = (call) => {
|
||||
// The incoming calls may be for another room, which we will ignore.
|
||||
if (call.roomId !== this.room.roomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we haven't entered yet, add the call to a queue which we'll use later.
|
||||
if (!this.entered) {
|
||||
this._incomingCallQueue.push(call);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user calling has an existing participant and use this call instead.
|
||||
const userId = call.opponentMember.userId;
|
||||
const existingParticipant = manager.participants.find(
|
||||
(p) => p.userId === userId
|
||||
);
|
||||
|
||||
if (existingParticipant) {
|
||||
// This also fires the hangup event and triggers those side-effects
|
||||
existingParticipant.call.hangup("user_hangup", false);
|
||||
existingParticipant.call = call;
|
||||
}
|
||||
|
||||
call.answer();
|
||||
|
||||
this.emit("call", call);
|
||||
};
|
||||
|
||||
_onRoomStateMembers = (_event, _state, member) => {
|
||||
this._onMemberChanged(member);
|
||||
};
|
||||
|
||||
_onMemberChanged = (member) => {
|
||||
// Don't process new members until we've entered the conference call.
|
||||
if (!this.entered) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The member events may be received for another room, which we will ignore.
|
||||
if (member.roomId !== this.room.roomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const localUserId = this.client.getUserId();
|
||||
|
||||
if (member.userId === localUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const memberStateEvent = this.room.currentState.getStateEvents(
|
||||
"m.room.member",
|
||||
member.userId
|
||||
);
|
||||
const { expiresAt, sessionId } =
|
||||
memberStateEvent.getContent()[CONF_PARTICIPANT];
|
||||
|
||||
const now = new Date().getTime();
|
||||
|
||||
if (expiresAt < now) {
|
||||
this.emit("debugstate", member.userId, null, "inactive");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the session id and expiration time of the existing participant to see if we should
|
||||
// hang up the existing call and create a new one or ignore the changed member.
|
||||
const participant = this.participants.find((p) => p.userId === userId);
|
||||
|
||||
if (participant && participant.sessionId !== sessionId) {
|
||||
this.emit("debugstate", member.userId, null, "inactive");
|
||||
participant.call.hangup("user_hangup", false);
|
||||
}
|
||||
|
||||
this.emit("call", call);
|
||||
};
|
||||
}
|
||||
|
42
src/randomstring.js
Normal file
42
src/randomstring.js
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
||||
const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const DIGITS = "0123456789";
|
||||
|
||||
export function randomString(len) {
|
||||
return randomStringFrom(len, UPPERCASE + LOWERCASE + DIGITS);
|
||||
}
|
||||
|
||||
export function randomLowercaseString(len) {
|
||||
return randomStringFrom(len, LOWERCASE);
|
||||
}
|
||||
|
||||
export function randomUppercaseString(len) {
|
||||
return randomStringFrom(len, UPPERCASE);
|
||||
}
|
||||
|
||||
function randomStringFrom(len, chars) {
|
||||
let ret = "";
|
||||
|
||||
for (let i = 0; i < len; ++i) {
|
||||
ret += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Reference in New Issue
Block a user