2021-07-21 23:28:01 -07:00
|
|
|
import EventEmitter from "events";
|
|
|
|
|
|
|
|
const CONF_ROOM = "me.robertlong.conf";
|
|
|
|
const CONF_PARTICIPANT = "me.robertlong.conf.participant";
|
2021-07-23 14:50:33 -07:00
|
|
|
const PARTICIPANT_TIMEOUT = 1000 * 5;
|
2021-07-21 23:28:01 -07:00
|
|
|
|
|
|
|
export class ConferenceCall extends EventEmitter {
|
|
|
|
constructor(client, roomId) {
|
|
|
|
super();
|
|
|
|
this.client = client;
|
|
|
|
this.roomId = roomId;
|
2021-07-23 14:50:33 -07:00
|
|
|
this.joined = false;
|
2021-07-21 23:28:01 -07:00
|
|
|
this.room = client.getRoom(roomId);
|
|
|
|
this.localParticipant = {
|
2021-07-23 14:50:33 -07:00
|
|
|
local: true,
|
2021-07-21 23:28:01 -07:00
|
|
|
userId: client.getUserId(),
|
|
|
|
feed: null,
|
|
|
|
call: null,
|
|
|
|
muted: true,
|
2021-07-23 14:50:33 -07:00
|
|
|
calls: [],
|
2021-07-21 23:28:01 -07:00
|
|
|
};
|
|
|
|
this.participants = [this.localParticipant];
|
2021-07-22 16:41:57 -07:00
|
|
|
|
|
|
|
this.client.on("RoomState.members", this._onMemberChanged);
|
|
|
|
this.client.on("Call.incoming", this._onIncomingCall);
|
2021-07-23 14:50:33 -07:00
|
|
|
}
|
2021-07-22 16:41:57 -07:00
|
|
|
|
2021-07-23 14:50:33 -07:00
|
|
|
join() {
|
|
|
|
this.joined = true;
|
2021-07-22 16:41:57 -07:00
|
|
|
|
2021-07-21 23:28:01 -07:00
|
|
|
const activeConf = this.room.currentState
|
|
|
|
.getStateEvents(CONF_ROOM, "")
|
|
|
|
?.getContent()?.active;
|
|
|
|
|
|
|
|
if (!activeConf) {
|
|
|
|
this.client.sendStateEvent(this.roomId, CONF_ROOM, { active: true }, "");
|
|
|
|
}
|
|
|
|
|
2021-07-23 14:52:42 -07:00
|
|
|
this.room
|
|
|
|
.getMembers()
|
|
|
|
.forEach((member) => this._processMember(member.userId));
|
|
|
|
|
2021-07-21 23:28:01 -07:00
|
|
|
this._updateParticipantState();
|
|
|
|
}
|
|
|
|
|
|
|
|
_updateParticipantState = () => {
|
|
|
|
const userId = this.client.getUserId();
|
|
|
|
const currentMemberState = this.room.currentState.getStateEvents(
|
|
|
|
"m.room.member",
|
|
|
|
userId
|
|
|
|
);
|
|
|
|
|
|
|
|
this.client.sendStateEvent(
|
|
|
|
this.roomId,
|
|
|
|
"m.room.member",
|
|
|
|
{
|
|
|
|
...currentMemberState.getContent(),
|
|
|
|
[CONF_PARTICIPANT]: new Date().getTime(),
|
|
|
|
},
|
|
|
|
userId
|
|
|
|
);
|
|
|
|
|
|
|
|
this._participantStateTimeout = setTimeout(
|
|
|
|
this._updateParticipantState,
|
|
|
|
PARTICIPANT_TIMEOUT
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
_onMemberChanged = (_event, _state, member) => {
|
2021-07-23 14:50:33 -07:00
|
|
|
if (!this.joined) {
|
|
|
|
console.debug("_onMemberChanged", member, "not joined");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-21 23:28:01 -07:00
|
|
|
this._processMember(member.userId);
|
|
|
|
};
|
|
|
|
|
|
|
|
_processMember(userId) {
|
2021-07-23 14:50:33 -07:00
|
|
|
const localUserId = this.client.getUserId();
|
|
|
|
|
|
|
|
if (userId === localUserId || userId < localUserId) {
|
2021-07-21 23:28:01 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const participant = this.participants.find((p) => p.userId === userId);
|
|
|
|
|
|
|
|
const memberStateEvent = this.room.currentState.getStateEvents(
|
|
|
|
"m.room.member",
|
|
|
|
userId
|
|
|
|
);
|
|
|
|
const participantTimeout = memberStateEvent.getContent()[CONF_PARTICIPANT];
|
|
|
|
|
2021-07-23 14:50:33 -07:00
|
|
|
console.debug(
|
|
|
|
"_processMember",
|
|
|
|
new Date().getTime() - participantTimeout > PARTICIPANT_TIMEOUT
|
|
|
|
);
|
|
|
|
|
2021-07-21 23:28:01 -07:00
|
|
|
if (
|
|
|
|
typeof participantTimeout === "number" &&
|
2021-07-23 14:50:33 -07:00
|
|
|
new Date().getTime() - participantTimeout > PARTICIPANT_TIMEOUT
|
2021-07-21 23:28:01 -07:00
|
|
|
) {
|
2021-07-23 14:50:33 -07:00
|
|
|
// if (participant && participant.call) {
|
|
|
|
// participant.call.hangup("user_hangup");
|
|
|
|
// }
|
2021-07-21 23:28:01 -07:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!participant) {
|
|
|
|
this._callUser(userId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_onIncomingCall = (call) => {
|
2021-07-23 14:50:33 -07:00
|
|
|
if (!this.joined) {
|
|
|
|
console.debug(
|
|
|
|
"_onIncomingCall",
|
|
|
|
"Member hasn't joined yet. Not answering."
|
|
|
|
);
|
|
|
|
//call.hangup();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-22 16:41:57 -07:00
|
|
|
console.debug("_onIncomingCall", call);
|
2021-07-21 23:28:01 -07:00
|
|
|
this._addCall(call);
|
|
|
|
call.answer();
|
|
|
|
};
|
|
|
|
|
|
|
|
_callUser = (userId) => {
|
|
|
|
const call = this.client.createCall(this.roomId, userId);
|
|
|
|
console.debug("_callUser", call, userId);
|
|
|
|
// TODO: Handle errors
|
|
|
|
this._addCall(call, userId);
|
|
|
|
call.placeVideoCall();
|
|
|
|
};
|
|
|
|
|
|
|
|
_addCall(call, userId) {
|
2021-07-22 16:41:57 -07:00
|
|
|
const existingCall = this.participants.find(
|
|
|
|
(p) => p.call && p.call.callId === call.callId
|
|
|
|
);
|
|
|
|
|
|
|
|
if (existingCall) {
|
|
|
|
console.debug("found existing call");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-21 23:28:01 -07:00
|
|
|
this.participants.push({
|
2021-07-22 16:41:57 -07:00
|
|
|
userId,
|
2021-07-21 23:28:01 -07:00
|
|
|
feed: null,
|
|
|
|
call,
|
2021-07-23 14:50:33 -07:00
|
|
|
calls: [call],
|
2021-07-21 23:28:01 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
call.on("feeds_changed", () => this._onCallFeedsChanged(call));
|
|
|
|
call.on("hangup", () => this._onCallHangup(call));
|
2021-07-22 16:41:57 -07:00
|
|
|
|
|
|
|
const onReplaced = (newCall) => {
|
|
|
|
this._onCallReplaced(call, newCall);
|
|
|
|
call.removeListener("replaced", onReplaced);
|
|
|
|
};
|
|
|
|
|
|
|
|
call.on("replaced", onReplaced);
|
2021-07-21 23:28:01 -07:00
|
|
|
this._onCallFeedsChanged(call);
|
|
|
|
|
|
|
|
this.emit("participants_changed");
|
|
|
|
}
|
|
|
|
|
|
|
|
_onCallFeedsChanged = (call) => {
|
|
|
|
console.debug("_onCallFeedsChanged", call);
|
|
|
|
const localFeeds = call.getLocalFeeds();
|
|
|
|
|
|
|
|
let participantsChanged = false;
|
|
|
|
|
|
|
|
if (!this.localParticipant.feed && localFeeds.length > 0) {
|
|
|
|
this.localParticipant.feed = localFeeds[0];
|
|
|
|
participantsChanged = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const remoteFeeds = call.getRemoteFeeds();
|
|
|
|
const remoteParticipant = this.participants.find((p) => p.call === call);
|
|
|
|
|
|
|
|
if (remoteFeeds.length > 0 && remoteParticipant.feed !== remoteFeeds[0]) {
|
|
|
|
remoteParticipant.feed = remoteFeeds[0];
|
|
|
|
participantsChanged = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (participantsChanged) {
|
|
|
|
this.emit("participants_changed");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
_onCallHangup = (call) => {
|
2021-07-22 16:41:57 -07:00
|
|
|
console.debug("_onCallHangup", call);
|
|
|
|
|
2021-07-21 23:28:01 -07:00
|
|
|
if (call.hangupReason === "replaced") {
|
2021-07-22 16:41:57 -07:00
|
|
|
console.debug("replaced");
|
2021-07-21 23:28:01 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const participantIndex = this.participants.findIndex(
|
|
|
|
(p) => p.call === call
|
|
|
|
);
|
|
|
|
|
|
|
|
if (participantIndex === -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.participants.splice(participantIndex, 1);
|
|
|
|
|
|
|
|
this.emit("participants_changed");
|
|
|
|
};
|
|
|
|
|
|
|
|
_onCallReplaced = (call, newCall) => {
|
2021-07-22 16:41:57 -07:00
|
|
|
console.debug("_onCallReplaced", call, newCall);
|
2021-07-21 23:28:01 -07:00
|
|
|
|
|
|
|
const remoteParticipant = this.participants.find((p) => p.call === call);
|
|
|
|
|
|
|
|
remoteParticipant.call = newCall;
|
2021-07-23 14:50:33 -07:00
|
|
|
remoteParticipant.calls.push(newCall);
|
2021-07-21 23:28:01 -07:00
|
|
|
|
|
|
|
newCall.on("feeds_changed", () => this._onCallFeedsChanged(newCall));
|
|
|
|
newCall.on("hangup", () => this._onCallHangup(newCall));
|
|
|
|
newCall.on("replaced", (nextCall) =>
|
|
|
|
this._onCallReplaced(newCall, nextCall)
|
|
|
|
);
|
|
|
|
this._onCallFeedsChanged(newCall);
|
|
|
|
|
|
|
|
this.emit("participants_changed");
|
|
|
|
};
|
|
|
|
}
|