Add new WIP ConfCallManager

This commit is contained in:
Robert Long
2021-08-05 18:21:56 -07:00
parent 274b3336c9
commit dff8a1acd3
2 changed files with 283 additions and 15 deletions

View File

@@ -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);
};
}