summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js
diff options
context:
space:
mode:
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js')
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js1184
1 files changed, 0 insertions, 1184 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js
deleted file mode 100644
index d9c044b..0000000
--- a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js
+++ /dev/null
@@ -1,1184 +0,0 @@
-"use strict";
-
-var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
-Object.defineProperty(exports, "__esModule", {
- value: true
-});
-exports.OtherUserSpeakingError = exports.GroupCallUnknownDeviceError = exports.GroupCallType = exports.GroupCallTerminationReason = exports.GroupCallStatsReportEvent = exports.GroupCallState = exports.GroupCallIntent = exports.GroupCallEvent = exports.GroupCallErrorCode = exports.GroupCallError = exports.GroupCall = void 0;
-var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
-var _typedEventEmitter = require("../models/typed-event-emitter");
-var _callFeed = require("./callFeed");
-var _call = require("./call");
-var _roomState = require("../models/room-state");
-var _logger = require("../logger");
-var _ReEmitter = require("../ReEmitter");
-var _callEventTypes = require("./callEventTypes");
-var _event = require("../@types/event");
-var _callEventHandler = require("./callEventHandler");
-var _groupCallEventHandler = require("./groupCallEventHandler");
-var _utils = require("../utils");
-var _groupCallStats = require("./stats/groupCallStats");
-var _statsReport = require("./stats/statsReport");
-function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
-function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
-let GroupCallIntent;
-exports.GroupCallIntent = GroupCallIntent;
-(function (GroupCallIntent) {
- GroupCallIntent["Ring"] = "m.ring";
- GroupCallIntent["Prompt"] = "m.prompt";
- GroupCallIntent["Room"] = "m.room";
-})(GroupCallIntent || (exports.GroupCallIntent = GroupCallIntent = {}));
-let GroupCallType;
-exports.GroupCallType = GroupCallType;
-(function (GroupCallType) {
- GroupCallType["Video"] = "m.video";
- GroupCallType["Voice"] = "m.voice";
-})(GroupCallType || (exports.GroupCallType = GroupCallType = {}));
-let GroupCallTerminationReason;
-exports.GroupCallTerminationReason = GroupCallTerminationReason;
-(function (GroupCallTerminationReason) {
- GroupCallTerminationReason["CallEnded"] = "call_ended";
-})(GroupCallTerminationReason || (exports.GroupCallTerminationReason = GroupCallTerminationReason = {}));
-/**
- * Because event names are just strings, they do need
- * to be unique over all event types of event emitter.
- * Some objects could emit more then one set of events.
- */
-let GroupCallEvent;
-exports.GroupCallEvent = GroupCallEvent;
-(function (GroupCallEvent) {
- GroupCallEvent["GroupCallStateChanged"] = "group_call_state_changed";
- GroupCallEvent["ActiveSpeakerChanged"] = "active_speaker_changed";
- GroupCallEvent["CallsChanged"] = "calls_changed";
- GroupCallEvent["UserMediaFeedsChanged"] = "user_media_feeds_changed";
- GroupCallEvent["ScreenshareFeedsChanged"] = "screenshare_feeds_changed";
- GroupCallEvent["LocalScreenshareStateChanged"] = "local_screenshare_state_changed";
- GroupCallEvent["LocalMuteStateChanged"] = "local_mute_state_changed";
- GroupCallEvent["ParticipantsChanged"] = "participants_changed";
- GroupCallEvent["Error"] = "group_call_error";
-})(GroupCallEvent || (exports.GroupCallEvent = GroupCallEvent = {}));
-let GroupCallStatsReportEvent;
-exports.GroupCallStatsReportEvent = GroupCallStatsReportEvent;
-(function (GroupCallStatsReportEvent) {
- GroupCallStatsReportEvent["ConnectionStats"] = "GroupCall.connection_stats";
- GroupCallStatsReportEvent["ByteSentStats"] = "GroupCall.byte_sent_stats";
-})(GroupCallStatsReportEvent || (exports.GroupCallStatsReportEvent = GroupCallStatsReportEvent = {}));
-let GroupCallErrorCode;
-exports.GroupCallErrorCode = GroupCallErrorCode;
-(function (GroupCallErrorCode) {
- GroupCallErrorCode["NoUserMedia"] = "no_user_media";
- GroupCallErrorCode["UnknownDevice"] = "unknown_device";
- GroupCallErrorCode["PlaceCallFailed"] = "place_call_failed";
-})(GroupCallErrorCode || (exports.GroupCallErrorCode = GroupCallErrorCode = {}));
-class GroupCallError extends Error {
- constructor(code, msg, err) {
- // Still don't think there's any way to have proper nested errors
- if (err) {
- super(msg + ": " + err);
- (0, _defineProperty2.default)(this, "code", void 0);
- } else {
- super(msg);
- (0, _defineProperty2.default)(this, "code", void 0);
- }
- this.code = code;
- }
-}
-exports.GroupCallError = GroupCallError;
-class GroupCallUnknownDeviceError extends GroupCallError {
- constructor(userId) {
- super(GroupCallErrorCode.UnknownDevice, "No device found for " + userId);
- this.userId = userId;
- }
-}
-exports.GroupCallUnknownDeviceError = GroupCallUnknownDeviceError;
-class OtherUserSpeakingError extends Error {
- constructor() {
- super("Cannot unmute: another user is speaking");
- }
-}
-exports.OtherUserSpeakingError = OtherUserSpeakingError;
-let GroupCallState;
-exports.GroupCallState = GroupCallState;
-(function (GroupCallState) {
- GroupCallState["LocalCallFeedUninitialized"] = "local_call_feed_uninitialized";
- GroupCallState["InitializingLocalCallFeed"] = "initializing_local_call_feed";
- GroupCallState["LocalCallFeedInitialized"] = "local_call_feed_initialized";
- GroupCallState["Entered"] = "entered";
- GroupCallState["Ended"] = "ended";
-})(GroupCallState || (exports.GroupCallState = GroupCallState = {}));
-const DEVICE_TIMEOUT = 1000 * 60 * 60; // 1 hour
-
-function getCallUserId(call) {
- var _call$getOpponentMemb;
- return ((_call$getOpponentMemb = call.getOpponentMember()) === null || _call$getOpponentMemb === void 0 ? void 0 : _call$getOpponentMemb.userId) || call.invitee || null;
-}
-class GroupCall extends _typedEventEmitter.TypedEventEmitter {
- // Config
-
- // user_id -> device_id -> MatrixCall
- // user_id -> device_id -> ICallHandlers
-
- // user_id -> device_id -> count
-
- constructor(client, room, type, isPtt, intent, groupCallId, dataChannelsEnabled, dataChannelOptions, isCallWithoutVideoAndAudio) {
- var _room$currentState$ge, _room$currentState$ge2;
- super();
- this.client = client;
- this.room = room;
- this.type = type;
- this.isPtt = isPtt;
- this.intent = intent;
- this.dataChannelsEnabled = dataChannelsEnabled;
- this.dataChannelOptions = dataChannelOptions;
- (0, _defineProperty2.default)(this, "activeSpeakerInterval", 1000);
- (0, _defineProperty2.default)(this, "retryCallInterval", 5000);
- (0, _defineProperty2.default)(this, "participantTimeout", 1000 * 15);
- (0, _defineProperty2.default)(this, "pttMaxTransmitTime", 1000 * 20);
- (0, _defineProperty2.default)(this, "activeSpeaker", void 0);
- (0, _defineProperty2.default)(this, "localCallFeed", void 0);
- (0, _defineProperty2.default)(this, "localScreenshareFeed", void 0);
- (0, _defineProperty2.default)(this, "localDesktopCapturerSourceId", void 0);
- (0, _defineProperty2.default)(this, "userMediaFeeds", []);
- (0, _defineProperty2.default)(this, "screenshareFeeds", []);
- (0, _defineProperty2.default)(this, "groupCallId", void 0);
- (0, _defineProperty2.default)(this, "allowCallWithoutVideoAndAudio", void 0);
- (0, _defineProperty2.default)(this, "calls", new Map());
- (0, _defineProperty2.default)(this, "callHandlers", new Map());
- (0, _defineProperty2.default)(this, "activeSpeakerLoopInterval", void 0);
- (0, _defineProperty2.default)(this, "retryCallLoopInterval", void 0);
- (0, _defineProperty2.default)(this, "retryCallCounts", new Map());
- (0, _defineProperty2.default)(this, "reEmitter", void 0);
- (0, _defineProperty2.default)(this, "transmitTimer", null);
- (0, _defineProperty2.default)(this, "participantsExpirationTimer", null);
- (0, _defineProperty2.default)(this, "resendMemberStateTimer", null);
- (0, _defineProperty2.default)(this, "initWithAudioMuted", false);
- (0, _defineProperty2.default)(this, "initWithVideoMuted", false);
- (0, _defineProperty2.default)(this, "initCallFeedPromise", void 0);
- (0, _defineProperty2.default)(this, "stats", void 0);
- (0, _defineProperty2.default)(this, "onConnectionStats", report => {
- // @TODO: Implement data argumentation
- this.emit(GroupCallStatsReportEvent.ConnectionStats, {
- report
- });
- });
- (0, _defineProperty2.default)(this, "onByteSentStats", report => {
- // @TODO: Implement data argumentation
- this.emit(GroupCallStatsReportEvent.ByteSentStats, {
- report
- });
- });
- (0, _defineProperty2.default)(this, "_state", GroupCallState.LocalCallFeedUninitialized);
- (0, _defineProperty2.default)(this, "_participants", new Map());
- (0, _defineProperty2.default)(this, "_creationTs", null);
- (0, _defineProperty2.default)(this, "_enteredViaAnotherSession", false);
- (0, _defineProperty2.default)(this, "onIncomingCall", newCall => {
- var _newCall$getOpponentM, _this$calls$get;
- // The incoming calls may be for another room, which we will ignore.
- if (newCall.roomId !== this.room.roomId) {
- return;
- }
- if (newCall.state !== _call.CallState.Ringing) {
- _logger.logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call no longer in ringing state - ignoring`);
- return;
- }
- if (!newCall.groupCallId || newCall.groupCallId !== this.groupCallId) {
- _logger.logger.log(`GroupCall ${this.groupCallId} onIncomingCall() ignored because it doesn't match the current group call`);
- newCall.reject();
- return;
- }
- const opponentUserId = (_newCall$getOpponentM = newCall.getOpponentMember()) === null || _newCall$getOpponentM === void 0 ? void 0 : _newCall$getOpponentM.userId;
- if (opponentUserId === undefined) {
- _logger.logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call with no member - ignoring`);
- return;
- }
- const deviceMap = (_this$calls$get = this.calls.get(opponentUserId)) !== null && _this$calls$get !== void 0 ? _this$calls$get : new Map();
- const prevCall = deviceMap.get(newCall.getOpponentDeviceId());
- if ((prevCall === null || prevCall === void 0 ? void 0 : prevCall.callId) === newCall.callId) return;
- _logger.logger.log(`GroupCall ${this.groupCallId} onIncomingCall() incoming call (userId=${opponentUserId}, callId=${newCall.callId})`);
- if (prevCall) prevCall.hangup(_call.CallErrorCode.Replaced, false);
- this.initCall(newCall);
- const feeds = this.getLocalFeeds().map(feed => feed.clone());
- if (!this.callExpected(newCall)) {
- // Disable our tracks for users not explicitly participating in the
- // call but trying to receive the feeds
- for (const feed of feeds) {
- (0, _call.setTracksEnabled)(feed.stream.getAudioTracks(), false);
- (0, _call.setTracksEnabled)(feed.stream.getVideoTracks(), false);
- }
- }
- newCall.answerWithCallFeeds(feeds);
- deviceMap.set(newCall.getOpponentDeviceId(), newCall);
- this.calls.set(opponentUserId, deviceMap);
- this.emit(GroupCallEvent.CallsChanged, this.calls);
- });
- (0, _defineProperty2.default)(this, "onRetryCallLoop", () => {
- let needsRetry = false;
- for (const [{
- userId
- }, participantMap] of this.participants) {
- const callMap = this.calls.get(userId);
- let retriesMap = this.retryCallCounts.get(userId);
- for (const [deviceId, participant] of participantMap) {
- var _retriesMap$get, _retriesMap;
- const call = callMap === null || callMap === void 0 ? void 0 : callMap.get(deviceId);
- const retries = (_retriesMap$get = (_retriesMap = retriesMap) === null || _retriesMap === void 0 ? void 0 : _retriesMap.get(deviceId)) !== null && _retriesMap$get !== void 0 ? _retriesMap$get : 0;
- if ((call === null || call === void 0 ? void 0 : call.getOpponentSessionId()) !== participant.sessionId && this.wantsOutgoingCall(userId, deviceId) && retries < 3) {
- if (retriesMap === undefined) {
- retriesMap = new Map();
- this.retryCallCounts.set(userId, retriesMap);
- }
- retriesMap.set(deviceId, retries + 1);
- needsRetry = true;
- }
- }
- }
- if (needsRetry) this.placeOutgoingCalls();
- });
- (0, _defineProperty2.default)(this, "onCallFeedsChanged", call => {
- const opponentMemberId = getCallUserId(call);
- const opponentDeviceId = call.getOpponentDeviceId();
- if (!opponentMemberId) {
- throw new Error("Cannot change call feeds without user id");
- }
- const currentUserMediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);
- const remoteUsermediaFeed = call.remoteUsermediaFeed;
- const remoteFeedChanged = remoteUsermediaFeed !== currentUserMediaFeed;
- if (remoteFeedChanged) {
- if (!currentUserMediaFeed && remoteUsermediaFeed) {
- this.addUserMediaFeed(remoteUsermediaFeed);
- } else if (currentUserMediaFeed && remoteUsermediaFeed) {
- this.replaceUserMediaFeed(currentUserMediaFeed, remoteUsermediaFeed);
- } else if (currentUserMediaFeed && !remoteUsermediaFeed) {
- this.removeUserMediaFeed(currentUserMediaFeed);
- }
- }
- const currentScreenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId);
- const remoteScreensharingFeed = call.remoteScreensharingFeed;
- const remoteScreenshareFeedChanged = remoteScreensharingFeed !== currentScreenshareFeed;
- if (remoteScreenshareFeedChanged) {
- if (!currentScreenshareFeed && remoteScreensharingFeed) {
- this.addScreenshareFeed(remoteScreensharingFeed);
- } else if (currentScreenshareFeed && remoteScreensharingFeed) {
- this.replaceScreenshareFeed(currentScreenshareFeed, remoteScreensharingFeed);
- } else if (currentScreenshareFeed && !remoteScreensharingFeed) {
- this.removeScreenshareFeed(currentScreenshareFeed);
- }
- }
- });
- (0, _defineProperty2.default)(this, "onCallStateChanged", (call, state, _oldState) => {
- var _call$getOpponentMemb2;
- if (state === _call.CallState.Ended) return;
- const audioMuted = this.localCallFeed.isAudioMuted();
- if (call.localUsermediaStream && call.isMicrophoneMuted() !== audioMuted) {
- call.setMicrophoneMuted(audioMuted);
- }
- const videoMuted = this.localCallFeed.isVideoMuted();
- if (call.localUsermediaStream && call.isLocalVideoMuted() !== videoMuted) {
- call.setLocalVideoMuted(videoMuted);
- }
- const opponentUserId = (_call$getOpponentMemb2 = call.getOpponentMember()) === null || _call$getOpponentMemb2 === void 0 ? void 0 : _call$getOpponentMemb2.userId;
- if (state === _call.CallState.Connected && opponentUserId) {
- const retriesMap = this.retryCallCounts.get(opponentUserId);
- retriesMap === null || retriesMap === void 0 ? void 0 : retriesMap.delete(call.getOpponentDeviceId());
- if ((retriesMap === null || retriesMap === void 0 ? void 0 : retriesMap.size) === 0) this.retryCallCounts.delete(opponentUserId);
- }
- });
- (0, _defineProperty2.default)(this, "onCallHangup", call => {
- var _call$getOpponentMemb3, _call$getOpponentMemb4;
- if (call.hangupReason === _call.CallErrorCode.Replaced) return;
- const opponentUserId = (_call$getOpponentMemb3 = (_call$getOpponentMemb4 = call.getOpponentMember()) === null || _call$getOpponentMemb4 === void 0 ? void 0 : _call$getOpponentMemb4.userId) !== null && _call$getOpponentMemb3 !== void 0 ? _call$getOpponentMemb3 : this.room.getMember(call.invitee).userId;
- const deviceMap = this.calls.get(opponentUserId);
-
- // Sanity check that this call is in fact in the map
- if ((deviceMap === null || deviceMap === void 0 ? void 0 : deviceMap.get(call.getOpponentDeviceId())) === call) {
- this.disposeCall(call, call.hangupReason);
- deviceMap.delete(call.getOpponentDeviceId());
- if (deviceMap.size === 0) this.calls.delete(opponentUserId);
- this.emit(GroupCallEvent.CallsChanged, this.calls);
- }
- });
- (0, _defineProperty2.default)(this, "onCallReplaced", (prevCall, newCall) => {
- const opponentUserId = prevCall.getOpponentMember().userId;
- let deviceMap = this.calls.get(opponentUserId);
- if (deviceMap === undefined) {
- deviceMap = new Map();
- this.calls.set(opponentUserId, deviceMap);
- }
- prevCall.hangup(_call.CallErrorCode.Replaced, false);
- this.initCall(newCall);
- deviceMap.set(prevCall.getOpponentDeviceId(), newCall);
- this.emit(GroupCallEvent.CallsChanged, this.calls);
- });
- (0, _defineProperty2.default)(this, "onActiveSpeakerLoop", () => {
- let topAvg = undefined;
- let nextActiveSpeaker = undefined;
- for (const callFeed of this.userMediaFeeds) {
- if (callFeed.isLocal() && this.userMediaFeeds.length > 1) continue;
- const total = callFeed.speakingVolumeSamples.reduce((acc, volume) => acc + Math.max(volume, _callFeed.SPEAKING_THRESHOLD));
- const avg = total / callFeed.speakingVolumeSamples.length;
- if (!topAvg || avg > topAvg) {
- topAvg = avg;
- nextActiveSpeaker = callFeed;
- }
- }
- if (nextActiveSpeaker && this.activeSpeaker !== nextActiveSpeaker && topAvg && topAvg > _callFeed.SPEAKING_THRESHOLD) {
- this.activeSpeaker = nextActiveSpeaker;
- this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker);
- }
- });
- (0, _defineProperty2.default)(this, "onRoomState", () => this.updateParticipants());
- (0, _defineProperty2.default)(this, "onParticipantsChanged", () => {
- // Re-run setTracksEnabled on all calls, so that participants that just
- // left get denied access to our media, and participants that just
- // joined get granted access
- this.forEachCall(call => {
- const expected = this.callExpected(call);
- for (const feed of call.getLocalFeeds()) {
- (0, _call.setTracksEnabled)(feed.stream.getAudioTracks(), !feed.isAudioMuted() && expected);
- (0, _call.setTracksEnabled)(feed.stream.getVideoTracks(), !feed.isVideoMuted() && expected);
- }
- });
- if (this.state === GroupCallState.Entered) this.placeOutgoingCalls();
- });
- (0, _defineProperty2.default)(this, "onStateChanged", (newState, oldState) => {
- if (newState === GroupCallState.Entered || oldState === GroupCallState.Entered || newState === GroupCallState.Ended) {
- // We either entered, left, or ended the call
- this.updateParticipants();
- this.updateMemberState().catch(e => _logger.logger.error(`GroupCall ${this.groupCallId} onStateChanged() failed to update member state devices"`, e));
- }
- });
- (0, _defineProperty2.default)(this, "onLocalFeedsChanged", () => {
- if (this.state === GroupCallState.Entered) {
- this.updateMemberState().catch(e => _logger.logger.error(`GroupCall ${this.groupCallId} onLocalFeedsChanged() failed to update member state feeds`, e));
- }
- });
- this.reEmitter = new _ReEmitter.ReEmitter(this);
- this.groupCallId = groupCallId !== null && groupCallId !== void 0 ? groupCallId : (0, _call.genCallID)();
- this.creationTs = (_room$currentState$ge = (_room$currentState$ge2 = room.currentState.getStateEvents(_event.EventType.GroupCallPrefix, this.groupCallId)) === null || _room$currentState$ge2 === void 0 ? void 0 : _room$currentState$ge2.getTs()) !== null && _room$currentState$ge !== void 0 ? _room$currentState$ge : null;
- this.updateParticipants();
- room.on(_roomState.RoomStateEvent.Update, this.onRoomState);
- this.on(GroupCallEvent.ParticipantsChanged, this.onParticipantsChanged);
- this.on(GroupCallEvent.GroupCallStateChanged, this.onStateChanged);
- this.on(GroupCallEvent.LocalScreenshareStateChanged, this.onLocalFeedsChanged);
- this.allowCallWithoutVideoAndAudio = !!isCallWithoutVideoAndAudio;
- const userID = this.client.getUserId() || "unknown";
- this.stats = new _groupCallStats.GroupCallStats(this.groupCallId, userID);
- this.stats.reports.on(_statsReport.StatsReport.CONNECTION_STATS, this.onConnectionStats);
- this.stats.reports.on(_statsReport.StatsReport.BYTE_SENT_STATS, this.onByteSentStats);
- }
- async create() {
- this.creationTs = Date.now();
- this.client.groupCallEventHandler.groupCalls.set(this.room.roomId, this);
- this.client.emit(_groupCallEventHandler.GroupCallEventHandlerEvent.Outgoing, this);
- const groupCallState = {
- "m.intent": this.intent,
- "m.type": this.type,
- "io.element.ptt": this.isPtt,
- // TODO: Specify data-channels better
- "dataChannelsEnabled": this.dataChannelsEnabled,
- "dataChannelOptions": this.dataChannelsEnabled ? this.dataChannelOptions : undefined
- };
- await this.client.sendStateEvent(this.room.roomId, _event.EventType.GroupCallPrefix, groupCallState, this.groupCallId);
- return this;
- }
- /**
- * The group call's state.
- */
- get state() {
- return this._state;
- }
- set state(value) {
- const prevValue = this._state;
- if (value !== prevValue) {
- this._state = value;
- this.emit(GroupCallEvent.GroupCallStateChanged, value, prevValue);
- }
- }
- /**
- * The current participants in the call, as a map from members to device IDs
- * to participant info.
- */
- get participants() {
- return this._participants;
- }
- set participants(value) {
- const prevValue = this._participants;
- const participantStateEqual = (x, y) => x.sessionId === y.sessionId && x.screensharing === y.screensharing;
- const deviceMapsEqual = (x, y) => (0, _utils.mapsEqual)(x, y, participantStateEqual);
-
- // Only update if the map actually changed
- if (!(0, _utils.mapsEqual)(value, prevValue, deviceMapsEqual)) {
- this._participants = value;
- this.emit(GroupCallEvent.ParticipantsChanged, value);
- }
- }
- /**
- * The timestamp at which the call was created, or null if it has not yet
- * been created.
- */
- get creationTs() {
- return this._creationTs;
- }
- set creationTs(value) {
- this._creationTs = value;
- }
- /**
- * Whether the local device has entered this call via another session, such
- * as a widget.
- */
- get enteredViaAnotherSession() {
- return this._enteredViaAnotherSession;
- }
- set enteredViaAnotherSession(value) {
- this._enteredViaAnotherSession = value;
- this.updateParticipants();
- }
-
- /**
- * Executes the given callback on all calls in this group call.
- * @param f - The callback.
- */
- forEachCall(f) {
- for (const deviceMap of this.calls.values()) {
- for (const call of deviceMap.values()) f(call);
- }
- }
- getLocalFeeds() {
- const feeds = [];
- if (this.localCallFeed) feeds.push(this.localCallFeed);
- if (this.localScreenshareFeed) feeds.push(this.localScreenshareFeed);
- return feeds;
- }
- hasLocalParticipant() {
- var _this$participants$ge, _this$participants$ge2;
- return (_this$participants$ge = (_this$participants$ge2 = this.participants.get(this.room.getMember(this.client.getUserId()))) === null || _this$participants$ge2 === void 0 ? void 0 : _this$participants$ge2.has(this.client.getDeviceId())) !== null && _this$participants$ge !== void 0 ? _this$participants$ge : false;
- }
-
- /**
- * Determines whether the given call is one that we were expecting to exist
- * given our knowledge of who is participating in the group call.
- */
- callExpected(call) {
- var _this$participants$ge3;
- const userId = getCallUserId(call);
- const member = userId === null ? null : this.room.getMember(userId);
- const deviceId = call.getOpponentDeviceId();
- return member !== null && deviceId !== undefined && ((_this$participants$ge3 = this.participants.get(member)) === null || _this$participants$ge3 === void 0 ? void 0 : _this$participants$ge3.get(deviceId)) !== undefined;
- }
- async initLocalCallFeed() {
- if (this.state !== GroupCallState.LocalCallFeedUninitialized) {
- throw new Error(`Cannot initialize local call feed in the "${this.state}" state.`);
- }
- this.state = GroupCallState.InitializingLocalCallFeed;
-
- // wraps the real method to serialise calls, because we don't want to try starting
- // multiple call feeds at once
- if (this.initCallFeedPromise) return this.initCallFeedPromise;
- try {
- this.initCallFeedPromise = this.initLocalCallFeedInternal();
- await this.initCallFeedPromise;
- } finally {
- this.initCallFeedPromise = undefined;
- }
- }
- async initLocalCallFeedInternal() {
- _logger.logger.log(`GroupCall ${this.groupCallId} initLocalCallFeedInternal() running`);
- let stream;
- try {
- stream = await this.client.getMediaHandler().getUserMediaStream(true, this.type === GroupCallType.Video);
- } catch (error) {
- // If is allowed to join a call without a media stream, then we
- // don't throw an error here. But we need an empty Local Feed to establish
- // a connection later.
- if (this.allowCallWithoutVideoAndAudio) {
- stream = new MediaStream();
- } else {
- this.state = GroupCallState.LocalCallFeedUninitialized;
- throw error;
- }
- }
-
- // The call could've been disposed while we were waiting, and could
- // also have been started back up again (hello, React 18) so if we're
- // still in this 'initializing' state, carry on, otherwise bail.
- if (this._state !== GroupCallState.InitializingLocalCallFeed) {
- this.client.getMediaHandler().stopUserMediaStream(stream);
- throw new Error("Group call disposed while gathering media stream");
- }
- const callFeed = new _callFeed.CallFeed({
- client: this.client,
- roomId: this.room.roomId,
- userId: this.client.getUserId(),
- deviceId: this.client.getDeviceId(),
- stream,
- purpose: _callEventTypes.SDPStreamMetadataPurpose.Usermedia,
- audioMuted: this.initWithAudioMuted || stream.getAudioTracks().length === 0 || this.isPtt,
- videoMuted: this.initWithVideoMuted || stream.getVideoTracks().length === 0
- });
- (0, _call.setTracksEnabled)(stream.getAudioTracks(), !callFeed.isAudioMuted());
- (0, _call.setTracksEnabled)(stream.getVideoTracks(), !callFeed.isVideoMuted());
- this.localCallFeed = callFeed;
- this.addUserMediaFeed(callFeed);
- this.state = GroupCallState.LocalCallFeedInitialized;
- }
- async updateLocalUsermediaStream(stream) {
- if (this.localCallFeed) {
- const oldStream = this.localCallFeed.stream;
- this.localCallFeed.setNewStream(stream);
- const micShouldBeMuted = this.localCallFeed.isAudioMuted();
- const vidShouldBeMuted = this.localCallFeed.isVideoMuted();
- _logger.logger.log(`GroupCall ${this.groupCallId} updateLocalUsermediaStream() (oldStreamId=${oldStream.id}, newStreamId=${stream.id}, micShouldBeMuted=${micShouldBeMuted}, vidShouldBeMuted=${vidShouldBeMuted})`);
- (0, _call.setTracksEnabled)(stream.getAudioTracks(), !micShouldBeMuted);
- (0, _call.setTracksEnabled)(stream.getVideoTracks(), !vidShouldBeMuted);
- this.client.getMediaHandler().stopUserMediaStream(oldStream);
- }
- }
- async enter() {
- if (this.state === GroupCallState.LocalCallFeedUninitialized) {
- await this.initLocalCallFeed();
- } else if (this.state !== GroupCallState.LocalCallFeedInitialized) {
- throw new Error(`Cannot enter call in the "${this.state}" state`);
- }
- _logger.logger.log(`GroupCall ${this.groupCallId} enter() running`);
- this.state = GroupCallState.Entered;
- this.client.on(_callEventHandler.CallEventHandlerEvent.Incoming, this.onIncomingCall);
- for (const call of this.client.callEventHandler.calls.values()) {
- this.onIncomingCall(call);
- }
- this.retryCallLoopInterval = setInterval(this.onRetryCallLoop, this.retryCallInterval);
- this.activeSpeaker = undefined;
- this.onActiveSpeakerLoop();
- this.activeSpeakerLoopInterval = setInterval(this.onActiveSpeakerLoop, this.activeSpeakerInterval);
- }
- dispose() {
- if (this.localCallFeed) {
- this.removeUserMediaFeed(this.localCallFeed);
- this.localCallFeed = undefined;
- }
- if (this.localScreenshareFeed) {
- this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream);
- this.removeScreenshareFeed(this.localScreenshareFeed);
- this.localScreenshareFeed = undefined;
- this.localDesktopCapturerSourceId = undefined;
- }
- this.client.getMediaHandler().stopAllStreams();
- if (this.transmitTimer !== null) {
- clearTimeout(this.transmitTimer);
- this.transmitTimer = null;
- }
- if (this.retryCallLoopInterval !== undefined) {
- clearInterval(this.retryCallLoopInterval);
- this.retryCallLoopInterval = undefined;
- }
- if (this.participantsExpirationTimer !== null) {
- clearTimeout(this.participantsExpirationTimer);
- this.participantsExpirationTimer = null;
- }
- if (this.state !== GroupCallState.Entered) {
- return;
- }
- this.forEachCall(call => call.hangup(_call.CallErrorCode.UserHangup, false));
- this.activeSpeaker = undefined;
- clearInterval(this.activeSpeakerLoopInterval);
- this.retryCallCounts.clear();
- clearInterval(this.retryCallLoopInterval);
- this.client.removeListener(_callEventHandler.CallEventHandlerEvent.Incoming, this.onIncomingCall);
- this.stats.stop();
- }
- leave() {
- this.dispose();
- this.state = GroupCallState.LocalCallFeedUninitialized;
- }
- async terminate(emitStateEvent = true) {
- this.dispose();
- this.room.off(_roomState.RoomStateEvent.Update, this.onRoomState);
- this.client.groupCallEventHandler.groupCalls.delete(this.room.roomId);
- this.client.emit(_groupCallEventHandler.GroupCallEventHandlerEvent.Ended, this);
- this.state = GroupCallState.Ended;
- if (emitStateEvent) {
- const existingStateEvent = this.room.currentState.getStateEvents(_event.EventType.GroupCallPrefix, this.groupCallId);
- await this.client.sendStateEvent(this.room.roomId, _event.EventType.GroupCallPrefix, _objectSpread(_objectSpread({}, existingStateEvent.getContent()), {}, {
- "m.terminated": GroupCallTerminationReason.CallEnded
- }), this.groupCallId);
- }
- }
-
- /*
- * Local Usermedia
- */
-
- isLocalVideoMuted() {
- if (this.localCallFeed) {
- return this.localCallFeed.isVideoMuted();
- }
- return true;
- }
- isMicrophoneMuted() {
- if (this.localCallFeed) {
- return this.localCallFeed.isAudioMuted();
- }
- return true;
- }
-
- /**
- * Sets the mute state of the local participants's microphone.
- * @param muted - Whether to mute the microphone
- * @returns Whether muting/unmuting was successful
- */
- async setMicrophoneMuted(muted) {
- // hasAudioDevice can block indefinitely if the window has lost focus,
- // and it doesn't make much sense to keep a device from being muted, so
- // we always allow muted = true changes to go through
- if (!muted && !(await this.client.getMediaHandler().hasAudioDevice())) {
- return false;
- }
- const sendUpdatesBefore = !muted && this.isPtt;
-
- // set a timer for the maximum transmit time on PTT calls
- if (this.isPtt) {
- // Set or clear the max transmit timer
- if (!muted && this.isMicrophoneMuted()) {
- this.transmitTimer = setTimeout(() => {
- this.setMicrophoneMuted(true);
- }, this.pttMaxTransmitTime);
- } else if (muted && !this.isMicrophoneMuted()) {
- if (this.transmitTimer !== null) clearTimeout(this.transmitTimer);
- this.transmitTimer = null;
- }
- }
- this.forEachCall(call => {
- var _call$localUsermediaF;
- return (_call$localUsermediaF = call.localUsermediaFeed) === null || _call$localUsermediaF === void 0 ? void 0 : _call$localUsermediaF.setAudioVideoMuted(muted, null);
- });
- const sendUpdates = async () => {
- const updates = [];
- this.forEachCall(call => updates.push(call.sendMetadataUpdate()));
- await Promise.all(updates).catch(e => _logger.logger.info(`GroupCall ${this.groupCallId} setMicrophoneMuted() failed to send some metadata updates`, e));
- };
- if (sendUpdatesBefore) await sendUpdates();
- if (this.localCallFeed) {
- _logger.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() (streamId=${this.localCallFeed.stream.id}, muted=${muted})`);
-
- // We needed this here to avoid an error in case user join a call without a device.
- // I can not use .then .catch functions because linter :-(
- try {
- if (!muted) {
- const stream = await this.client.getMediaHandler().getUserMediaStream(true, !this.localCallFeed.isVideoMuted());
- if (stream === null) {
- // if case permission denied to get a stream stop this here
- /* istanbul ignore next */
- _logger.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no device to receive local stream, muted=${muted}`);
- return false;
- }
- }
- } catch (e) {
- /* istanbul ignore next */
- _logger.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no device or permission to receive local stream, muted=${muted}`);
- return false;
- }
- this.localCallFeed.setAudioVideoMuted(muted, null);
- // I don't believe its actually necessary to enable these tracks: they
- // are the one on the GroupCall's own CallFeed and are cloned before being
- // given to any of the actual calls, so these tracks don't actually go
- // anywhere. Let's do it anyway to avoid confusion.
- (0, _call.setTracksEnabled)(this.localCallFeed.stream.getAudioTracks(), !muted);
- } else {
- _logger.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no stream muted (muted=${muted})`);
- this.initWithAudioMuted = muted;
- }
- this.forEachCall(call => (0, _call.setTracksEnabled)(call.localUsermediaFeed.stream.getAudioTracks(), !muted && this.callExpected(call)));
- this.emit(GroupCallEvent.LocalMuteStateChanged, muted, this.isLocalVideoMuted());
- if (!sendUpdatesBefore) await sendUpdates();
- return true;
- }
-
- /**
- * Sets the mute state of the local participants's video.
- * @param muted - Whether to mute the video
- * @returns Whether muting/unmuting was successful
- */
- async setLocalVideoMuted(muted) {
- // hasAudioDevice can block indefinitely if the window has lost focus,
- // and it doesn't make much sense to keep a device from being muted, so
- // we always allow muted = true changes to go through
- if (!muted && !(await this.client.getMediaHandler().hasVideoDevice())) {
- return false;
- }
- if (this.localCallFeed) {
- /* istanbul ignore next */
- _logger.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() (stream=${this.localCallFeed.stream.id}, muted=${muted})`);
- try {
- const stream = await this.client.getMediaHandler().getUserMediaStream(true, !muted);
- await this.updateLocalUsermediaStream(stream);
- this.localCallFeed.setAudioVideoMuted(null, muted);
- (0, _call.setTracksEnabled)(this.localCallFeed.stream.getVideoTracks(), !muted);
- } catch (_) {
- // No permission to video device
- /* istanbul ignore next */
- _logger.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no device or permission to receive local stream, muted=${muted}`);
- return false;
- }
- } else {
- _logger.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no stream muted (muted=${muted})`);
- this.initWithVideoMuted = muted;
- }
- const updates = [];
- this.forEachCall(call => updates.push(call.setLocalVideoMuted(muted)));
- await Promise.all(updates);
-
- // We setTracksEnabled again, independently from the call doing it
- // internally, since we might not be expecting the call
- this.forEachCall(call => (0, _call.setTracksEnabled)(call.localUsermediaFeed.stream.getVideoTracks(), !muted && this.callExpected(call)));
- this.emit(GroupCallEvent.LocalMuteStateChanged, this.isMicrophoneMuted(), muted);
- return true;
- }
- async setScreensharingEnabled(enabled, opts = {}) {
- if (enabled === this.isScreensharing()) {
- return enabled;
- }
- if (enabled) {
- try {
- _logger.logger.log(`GroupCall ${this.groupCallId} setScreensharingEnabled() is asking for screensharing permissions`);
- const stream = await this.client.getMediaHandler().getScreensharingStream(opts);
- for (const track of stream.getTracks()) {
- const onTrackEnded = () => {
- this.setScreensharingEnabled(false);
- track.removeEventListener("ended", onTrackEnded);
- };
- track.addEventListener("ended", onTrackEnded);
- }
- _logger.logger.log(`GroupCall ${this.groupCallId} setScreensharingEnabled() granted screensharing permissions. Setting screensharing enabled on all calls`);
- this.localDesktopCapturerSourceId = opts.desktopCapturerSourceId;
- this.localScreenshareFeed = new _callFeed.CallFeed({
- client: this.client,
- roomId: this.room.roomId,
- userId: this.client.getUserId(),
- deviceId: this.client.getDeviceId(),
- stream,
- purpose: _callEventTypes.SDPStreamMetadataPurpose.Screenshare,
- audioMuted: false,
- videoMuted: false
- });
- this.addScreenshareFeed(this.localScreenshareFeed);
- this.emit(GroupCallEvent.LocalScreenshareStateChanged, true, this.localScreenshareFeed, this.localDesktopCapturerSourceId);
-
- // TODO: handle errors
- this.forEachCall(call => call.pushLocalFeed(this.localScreenshareFeed.clone()));
- return true;
- } catch (error) {
- if (opts.throwOnFail) throw error;
- _logger.logger.error(`GroupCall ${this.groupCallId} setScreensharingEnabled() enabling screensharing error`, error);
- this.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.NoUserMedia, "Failed to get screen-sharing stream: ", error));
- return false;
- }
- } else {
- this.forEachCall(call => {
- if (call.localScreensharingFeed) call.removeLocalFeed(call.localScreensharingFeed);
- });
- this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream);
- this.removeScreenshareFeed(this.localScreenshareFeed);
- this.localScreenshareFeed = undefined;
- this.localDesktopCapturerSourceId = undefined;
- this.emit(GroupCallEvent.LocalScreenshareStateChanged, false, undefined, undefined);
- return false;
- }
- }
- isScreensharing() {
- return !!this.localScreenshareFeed;
- }
-
- /*
- * 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.
- */
-
- /**
- * Determines whether a given participant expects us to call them (versus
- * them calling us).
- * @param userId - The participant's user ID.
- * @param deviceId - The participant's device ID.
- * @returns Whether we need to place an outgoing call to the participant.
- */
- wantsOutgoingCall(userId, deviceId) {
- const localUserId = this.client.getUserId();
- const localDeviceId = this.client.getDeviceId();
- return (
- // If a user's ID is less than our own, they'll call us
- userId >= localUserId && (
- // If this is another one of our devices, compare device IDs to tell whether it'll call us
- userId !== localUserId || deviceId > localDeviceId)
- );
- }
-
- /**
- * Places calls to all participants that we're responsible for calling.
- */
- placeOutgoingCalls() {
- let callsChanged = false;
- for (const [{
- userId
- }, participantMap] of this.participants) {
- var _this$calls$get2;
- const callMap = (_this$calls$get2 = this.calls.get(userId)) !== null && _this$calls$get2 !== void 0 ? _this$calls$get2 : new Map();
- for (const [deviceId, participant] of participantMap) {
- const prevCall = callMap.get(deviceId);
- if ((prevCall === null || prevCall === void 0 ? void 0 : prevCall.getOpponentSessionId()) !== participant.sessionId && this.wantsOutgoingCall(userId, deviceId)) {
- callsChanged = true;
- if (prevCall !== undefined) {
- _logger.logger.debug(`GroupCall ${this.groupCallId} placeOutgoingCalls() replacing call (userId=${userId}, deviceId=${deviceId}, callId=${prevCall.callId})`);
- prevCall.hangup(_call.CallErrorCode.NewSession, false);
- }
- const newCall = (0, _call.createNewMatrixCall)(this.client, this.room.roomId, {
- invitee: userId,
- opponentDeviceId: deviceId,
- opponentSessionId: participant.sessionId,
- groupCallId: this.groupCallId
- });
- if (newCall === null) {
- _logger.logger.error(`GroupCall ${this.groupCallId} placeOutgoingCalls() failed to create call (userId=${userId}, device=${deviceId})`);
- callMap.delete(deviceId);
- } else {
- this.initCall(newCall);
- callMap.set(deviceId, newCall);
- _logger.logger.debug(`GroupCall ${this.groupCallId} placeOutgoingCalls() placing call (userId=${userId}, deviceId=${deviceId}, sessionId=${participant.sessionId})`);
- newCall.placeCallWithCallFeeds(this.getLocalFeeds().map(feed => feed.clone()), participant.screensharing).then(() => {
- if (this.dataChannelsEnabled) {
- newCall.createDataChannel("datachannel", this.dataChannelOptions);
- }
- }).catch(e => {
- _logger.logger.warn(`GroupCall ${this.groupCallId} placeOutgoingCalls() failed to place call (userId=${userId})`, e);
- if (e instanceof _call.CallError && e.code === GroupCallErrorCode.UnknownDevice) {
- this.emit(GroupCallEvent.Error, e);
- } else {
- this.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.PlaceCallFailed, `Failed to place call to ${userId}`));
- }
- newCall.hangup(_call.CallErrorCode.SignallingFailed, false);
- if (callMap.get(deviceId) === newCall) callMap.delete(deviceId);
- });
- }
- }
- }
- if (callMap.size > 0) {
- this.calls.set(userId, callMap);
- } else {
- this.calls.delete(userId);
- }
- }
- if (callsChanged) this.emit(GroupCallEvent.CallsChanged, this.calls);
- }
-
- /*
- * Room Member State
- */
-
- getMemberStateEvents(userId) {
- return userId === undefined ? this.room.currentState.getStateEvents(_event.EventType.GroupCallMemberPrefix) : this.room.currentState.getStateEvents(_event.EventType.GroupCallMemberPrefix, userId);
- }
- initCall(call) {
- const opponentMemberId = getCallUserId(call);
- if (!opponentMemberId) {
- throw new Error("Cannot init call without user id");
- }
- const onCallFeedsChanged = () => this.onCallFeedsChanged(call);
- const onCallStateChanged = (state, oldState) => this.onCallStateChanged(call, state, oldState);
- const onCallHangup = this.onCallHangup;
- const onCallReplaced = newCall => this.onCallReplaced(call, newCall);
- let deviceMap = this.callHandlers.get(opponentMemberId);
- if (deviceMap === undefined) {
- deviceMap = new Map();
- this.callHandlers.set(opponentMemberId, deviceMap);
- }
- deviceMap.set(call.getOpponentDeviceId(), {
- onCallFeedsChanged,
- onCallStateChanged,
- onCallHangup,
- onCallReplaced
- });
- call.on(_call.CallEvent.FeedsChanged, onCallFeedsChanged);
- call.on(_call.CallEvent.State, onCallStateChanged);
- call.on(_call.CallEvent.Hangup, onCallHangup);
- call.on(_call.CallEvent.Replaced, onCallReplaced);
- call.isPtt = this.isPtt;
- this.reEmitter.reEmit(call, Object.values(_call.CallEvent));
- call.initStats(this.stats);
- onCallFeedsChanged();
- }
- disposeCall(call, hangupReason) {
- const opponentMemberId = getCallUserId(call);
- const opponentDeviceId = call.getOpponentDeviceId();
- if (!opponentMemberId) {
- throw new Error("Cannot dispose call without user id");
- }
- const deviceMap = this.callHandlers.get(opponentMemberId);
- const {
- onCallFeedsChanged,
- onCallStateChanged,
- onCallHangup,
- onCallReplaced
- } = deviceMap.get(opponentDeviceId);
- call.removeListener(_call.CallEvent.FeedsChanged, onCallFeedsChanged);
- call.removeListener(_call.CallEvent.State, onCallStateChanged);
- call.removeListener(_call.CallEvent.Hangup, onCallHangup);
- call.removeListener(_call.CallEvent.Replaced, onCallReplaced);
- deviceMap.delete(opponentMemberId);
- if (deviceMap.size === 0) this.callHandlers.delete(opponentMemberId);
- if (call.hangupReason === _call.CallErrorCode.Replaced) {
- return;
- }
- const usermediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);
- if (usermediaFeed) {
- this.removeUserMediaFeed(usermediaFeed);
- }
- const screenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId);
- if (screenshareFeed) {
- this.removeScreenshareFeed(screenshareFeed);
- }
- }
- /*
- * UserMedia CallFeed Event Handlers
- */
-
- getUserMediaFeed(userId, deviceId) {
- return this.userMediaFeeds.find(f => f.userId === userId && f.deviceId === deviceId);
- }
- addUserMediaFeed(callFeed) {
- this.userMediaFeeds.push(callFeed);
- callFeed.measureVolumeActivity(true);
- this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);
- }
- replaceUserMediaFeed(existingFeed, replacementFeed) {
- const feedIndex = this.userMediaFeeds.findIndex(f => f.userId === existingFeed.userId && f.deviceId === existingFeed.deviceId);
- if (feedIndex === -1) {
- throw new Error("Couldn't find user media feed to replace");
- }
- this.userMediaFeeds.splice(feedIndex, 1, replacementFeed);
- existingFeed.dispose();
- replacementFeed.measureVolumeActivity(true);
- this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);
- }
- removeUserMediaFeed(callFeed) {
- const feedIndex = this.userMediaFeeds.findIndex(f => f.userId === callFeed.userId && f.deviceId === callFeed.deviceId);
- if (feedIndex === -1) {
- throw new Error("Couldn't find user media feed to remove");
- }
- this.userMediaFeeds.splice(feedIndex, 1);
- callFeed.dispose();
- this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);
- if (this.activeSpeaker === callFeed) {
- this.activeSpeaker = this.userMediaFeeds[0];
- this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker);
- }
- }
- /*
- * Screenshare Call Feed Event Handlers
- */
-
- getScreenshareFeed(userId, deviceId) {
- return this.screenshareFeeds.find(f => f.userId === userId && f.deviceId === deviceId);
- }
- addScreenshareFeed(callFeed) {
- this.screenshareFeeds.push(callFeed);
- this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);
- }
- replaceScreenshareFeed(existingFeed, replacementFeed) {
- const feedIndex = this.screenshareFeeds.findIndex(f => f.userId === existingFeed.userId && f.deviceId === existingFeed.deviceId);
- if (feedIndex === -1) {
- throw new Error("Couldn't find screenshare feed to replace");
- }
- this.screenshareFeeds.splice(feedIndex, 1, replacementFeed);
- existingFeed.dispose();
- this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);
- }
- removeScreenshareFeed(callFeed) {
- const feedIndex = this.screenshareFeeds.findIndex(f => f.userId === callFeed.userId && f.deviceId === callFeed.deviceId);
- if (feedIndex === -1) {
- throw new Error("Couldn't find screenshare feed to remove");
- }
- this.screenshareFeeds.splice(feedIndex, 1);
- callFeed.dispose();
- this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);
- }
-
- /**
- * Recalculates and updates the participant map to match the room state.
- */
- updateParticipants() {
- const localMember = this.room.getMember(this.client.getUserId());
- if (!localMember) {
- // The client hasn't fetched enough of the room state to get our own member
- // event. This probably shouldn't happen, but sanity check & exit for now.
- _logger.logger.warn(`GroupCall ${this.groupCallId} updateParticipants() tried to update participants before local room member is available`);
- return;
- }
- if (this.participantsExpirationTimer !== null) {
- clearTimeout(this.participantsExpirationTimer);
- this.participantsExpirationTimer = null;
- }
- if (this.state === GroupCallState.Ended) {
- this.participants = new Map();
- return;
- }
- const participants = new Map();
- const now = Date.now();
- const entered = this.state === GroupCallState.Entered || this.enteredViaAnotherSession;
- let nextExpiration = Infinity;
- for (const e of this.getMemberStateEvents()) {
- const member = this.room.getMember(e.getStateKey());
- const content = e.getContent();
- const calls = Array.isArray(content["m.calls"]) ? content["m.calls"] : [];
- const call = calls.find(call => call["m.call_id"] === this.groupCallId);
- const devices = Array.isArray(call === null || call === void 0 ? void 0 : call["m.devices"]) ? call["m.devices"] : [];
-
- // Filter out invalid and expired devices
- let validDevices = devices.filter(d => typeof d.device_id === "string" && typeof d.session_id === "string" && typeof d.expires_ts === "number" && d.expires_ts > now && Array.isArray(d.feeds));
-
- // Apply local echo for the unentered case
- if (!entered && (member === null || member === void 0 ? void 0 : member.userId) === this.client.getUserId()) {
- validDevices = validDevices.filter(d => d.device_id !== this.client.getDeviceId());
- }
-
- // Must have a connected device and be joined to the room
- if (validDevices.length > 0 && (member === null || member === void 0 ? void 0 : member.membership) === "join") {
- const deviceMap = new Map();
- participants.set(member, deviceMap);
- for (const d of validDevices) {
- deviceMap.set(d.device_id, {
- sessionId: d.session_id,
- screensharing: d.feeds.some(f => f.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare)
- });
- if (d.expires_ts < nextExpiration) nextExpiration = d.expires_ts;
- }
- }
- }
-
- // Apply local echo for the entered case
- if (entered) {
- let deviceMap = participants.get(localMember);
- if (deviceMap === undefined) {
- deviceMap = new Map();
- participants.set(localMember, deviceMap);
- }
- if (!deviceMap.has(this.client.getDeviceId())) {
- deviceMap.set(this.client.getDeviceId(), {
- sessionId: this.client.getSessionId(),
- screensharing: this.getLocalFeeds().some(f => f.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare)
- });
- }
- }
- this.participants = participants;
- if (nextExpiration < Infinity) {
- this.participantsExpirationTimer = setTimeout(() => this.updateParticipants(), nextExpiration - now);
- }
- }
-
- /**
- * Updates the local user's member state with the devices returned by the given function.
- * @param fn - A function from the current devices to the new devices. If it
- * returns null, the update will be skipped.
- * @param keepAlive - Whether the request should outlive the window.
- */
- async updateDevices(fn, keepAlive = false) {
- var _event$getContent;
- const now = Date.now();
- const localUserId = this.client.getUserId();
- const event = this.getMemberStateEvents(localUserId);
- const content = (_event$getContent = event === null || event === void 0 ? void 0 : event.getContent()) !== null && _event$getContent !== void 0 ? _event$getContent : {};
- const calls = Array.isArray(content["m.calls"]) ? content["m.calls"] : [];
- let call = null;
- const otherCalls = [];
- for (const c of calls) {
- if (c["m.call_id"] === this.groupCallId) {
- call = c;
- } else {
- otherCalls.push(c);
- }
- }
- if (call === null) call = {};
- const devices = Array.isArray(call["m.devices"]) ? call["m.devices"] : [];
-
- // Filter out invalid and expired devices
- const validDevices = devices.filter(d => typeof d.device_id === "string" && typeof d.session_id === "string" && typeof d.expires_ts === "number" && d.expires_ts > now && Array.isArray(d.feeds));
- const newDevices = fn(validDevices);
- if (newDevices === null) return;
- const newCalls = [...otherCalls];
- if (newDevices.length > 0) {
- newCalls.push(_objectSpread(_objectSpread({}, call), {}, {
- "m.call_id": this.groupCallId,
- "m.devices": newDevices
- }));
- }
- const newContent = {
- "m.calls": newCalls
- };
- await this.client.sendStateEvent(this.room.roomId, _event.EventType.GroupCallMemberPrefix, newContent, localUserId, {
- keepAlive
- });
- }
- async addDeviceToMemberState() {
- await this.updateDevices(devices => [...devices.filter(d => d.device_id !== this.client.getDeviceId()), {
- device_id: this.client.getDeviceId(),
- session_id: this.client.getSessionId(),
- expires_ts: Date.now() + DEVICE_TIMEOUT,
- feeds: this.getLocalFeeds().map(feed => ({
- purpose: feed.purpose
- }))
- // TODO: Add data channels
- }]);
- }
-
- async updateMemberState() {
- // Clear the old update interval before proceeding
- if (this.resendMemberStateTimer !== null) {
- clearInterval(this.resendMemberStateTimer);
- this.resendMemberStateTimer = null;
- }
- if (this.state === GroupCallState.Entered) {
- // Add the local device
- await this.addDeviceToMemberState();
-
- // Resend the state event every so often so it doesn't become stale
- this.resendMemberStateTimer = setInterval(async () => {
- _logger.logger.log(`GroupCall ${this.groupCallId} updateMemberState() resending call member state"`);
- try {
- await this.addDeviceToMemberState();
- } catch (e) {
- _logger.logger.error(`GroupCall ${this.groupCallId} updateMemberState() failed to resend call member state`, e);
- }
- }, DEVICE_TIMEOUT * 3 / 4);
- } else {
- // Remove the local device
- await this.updateDevices(devices => devices.filter(d => d.device_id !== this.client.getDeviceId()), true);
- }
- }
-
- /**
- * Cleans up our member state by filtering out logged out devices, inactive
- * devices, and our own device (if we know we haven't entered).
- */
- async cleanMemberState() {
- const {
- devices: myDevices
- } = await this.client.getDevices();
- const deviceMap = new Map(myDevices.map(d => [d.device_id, d]));
-
- // updateDevices takes care of filtering out inactive devices for us
- await this.updateDevices(devices => {
- const newDevices = devices.filter(d => {
- const device = deviceMap.get(d.device_id);
- return (device === null || device === void 0 ? void 0 : device.last_seen_ts) !== undefined && !(d.device_id === this.client.getDeviceId() && this.state !== GroupCallState.Entered && !this.enteredViaAnotherSession);
- });
-
- // Skip the update if the devices are unchanged
- return newDevices.length === devices.length ? null : newDevices;
- });
- }
- getGroupCallStats() {
- return this.stats;
- }
-}
-exports.GroupCall = GroupCall;
-//# sourceMappingURL=groupCall.js.map \ No newline at end of file