src/conferences/ConferenceManager.js
import {cloneDeep} from 'lodash-es';
import {EventEmitter} from '../eventemitter';
import {bindMethods} from '../utils';
import {Conference} from './Conference';
import {ConferenceLog} from './ConferenceLog';
import Logs from '../Logs';
const log = Logs.instance.getLogger('SippoJS/calls/ConferenceManager');
/**
* This class provides access to every conference related feature
* This class must never be directly instantiated, instances of this class
* can be obtained using Session#getConferenceManager.
*
* ## Events
*
* - `conferenceInvitation` {@link Conference} - Emitted every time an invitation to
* a conference room is received
* - `conferenceInvitationRequest` {@link any} - Emitted every time a request for
* an invitation to a conference room is received
* - `conferenceInvitationRequestCancelled` {@link any} -
*/
export class ConferenceManager {
/**
* @protected
* @param {WacProxy} wacProxy
* @param {UserManager} userManager
*/
static newInstance(wacProxy, userManager) {
return new ConferenceManager(wacProxy, userManager);
}
/**
* @private
*/
constructor(wacProxy, userManager) {
/** @type {WacProxy} */
this._wacProxy = wacProxy;
/** @type {UserManager} */
this._userManager = userManager;
this._defaultMediaConstraints = {
audio: true,
video: true,
};
this._conferences = new Map();
this._conferenceLog = null;
// [SIPPOJS-493] The cancel can arrive while the username is being resolved
// This array keeps the invitation requests whose incoming process is in progress
this._incomingInvitationRequestIds = new Set();
/** @type {EventEmitter} */
this.emitter = new EventEmitter();
bindMethods(this, [
'_onIncomingConference',
'_onConferenceInvitationRequest',
'_onConferenceInvitationRequestCancelled',
]);
this._bindEventHandlers();
/*
let existingConferences = this._wacProxy.conference.fetch() || [];
existingConferences.forEach(stackConference => {
this._onIncomingConference(stackConference, mediaConstraints, true).catch(console.error);
});
*/
}
/**
* @private
*/
_bindEventHandlers() {
this._wacProxy.conference.on('incomingConference', this._onIncomingConference);
this._wacProxy.conference.on('invitationRequest', this._onConferenceInvitationRequest);
this._wacProxy.conference.on('cancelInvitationRequest', this._onConferenceInvitationRequestCancelled);
}
/**
* @private
*/
async _onIncomingConference(room, mediaTypes) {
const conference = await Conference.create(this._userManager, room, this._defaultMediaConstraints);
await conference.getLocalMediaHandler().setMediaTypes(mediaTypes);
this._conferences.set(room.id, conference);
this.emitter.emit('conferenceInvitation', conference);
}
/**
* @private
*/
_onConferenceInvitationRequest(event) {
this._incomingInvitationRequestIds.add(event.id);
let [gatewayUsername, session] = event.user.split('/');
let call = {
id: event.id,
from: gatewayUsername,
hasRemoteVideo: () => event.video,
hasStatus: () => true,
getRemoteUser: () => call.remoteUser,
getRemoteGatewayUser: () => gatewayUsername,
acceptInvitationRequest: () => event.accept(),
_connect: (conference) => {
call.status = 'trying';
return conference.inviteParticipant(gatewayUsername, session, {
audio: true,
video: call.hasRemoteVideo(),
}, true, event.id, false);
},
_disconnect: () => {
event.reject();
},
};
this._userManager.getUsername(gatewayUsername).then((username) => {
return this._userManager.resolveUser(username);
}).then((user) => {
// Detect if an invitation request cancel has arrived while we were resolving the user
if (!this._incomingInvitationRequestIds.has(event.id)) {
return;
}
this._incomingInvitationRequestIds.delete(event.id);
call.remoteUser = user || gatewayUsername;
this.emitter.emit('conferenceInvitationRequest', call);
});
}
/**
* @private
*/
_onConferenceInvitationRequestCancelled(event) {
this._incomingInvitationRequestIds.delete(event.id);
this.emitter.emit('conferenceInvitationRequestCancelled', event);
}
/**
* @return {MediaStreamConstraints}
*/
getDefaultMediaConstraints() {
return cloneDeep(this._defaultMediaConstraints);
}
/**
* Sets the constraints that will be used as initial value for media requests when
* initializing conferences. For more information about valid values see
* {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints}
* @example <caption>Enabling noise reduction techniques</caption>
* conferenceManager.setDefaultMediaConstraints({
* audio: {
* autoGainControl: true,
* echoCancellation: true,
* noiseSuppression: true,
* },
* video: true,
* });
* @param {MediaStreamConstraints} defaultMediaConstraints
*/
setDefaultMediaConstraints(defaultMediaConstraints) {
this._defaultMediaConstraints = cloneDeep(defaultMediaConstraints);
}
/**
* Creates a new conference room
* @return {Promise<Conference>}
*/
createConference() {
return this._wacProxy.conference.create().then((room) => {
return Conference.create(this._userManager, room, this._defaultMediaConstraints);
});
}
/**
* @return {ConferenceLog}
*/
getConferenceLog() {
if (this._conferenceLog === null) {
this._conferenceLog = new ConferenceLog(this._wacProxy.getConferenceLogService());
}
return this._conferenceLog;
}
/**
* @param {string} participant
* @param {object} mediaTypes
* @returns {Promise<object>} fulfilled when the response is received
*/
requestConferenceInvitation(participant, mediaTypes = {audio: true, video: true}) {
return this._userManager.resolveUser(participant).then((user) => {
let gwUsername = user && user.gatewayUsername ? user.gatewayUsername : participant;
let call = {
getRemoteUser: () => user,
to: gwUsername,
hasLocalVideo: () => !!mediaTypes.video,
hasLocalOnlyVideo: () => false,
_disconnect: () => {
this._wacProxy.conference.cancelInvitationRequest(gwUsername);
},
_connect: () => {
return this._wacProxy.conference.requestInvitation(gwUsername, mediaTypes);
},
};
return call;
});
}
async _getConferenceForRoom(room) {
const conference = await Conference.create(this._userManager, room, this._defaultMediaConstraints);
await conference.getLocalMediaHandler().setMediaTypes(room.invite.mediatypes);
this._conferences.set(room.id, conference);
return conference;
}
/**
* Asks the server for the current invitations available to the user
* @return {Promise<Conference[]>}
*/
getCurrentInvitations() {
log.debug('calls/ConferenceManager.js/getCurrentInvitations');
return this._wacProxy.conference.getCurrentInvitations().then((rooms) => {
log.debug('calls/ConferenceManager.js/getCurrentInvitations/rooms', rooms);
let conferences = rooms.map(async (room) => {
if (room.isRequest) {
delete room.isRequest;
this._onConferenceInvitationRequest(room);
return room;
} else {
const conf = await this._getConferenceForRoom(room);
this.emitter.emit('conferenceInvitation', conf);
return conf;
}
});
return Promise.all(conferences);
});
}
/**
* Get a Conference by uri
*
* @param {string} uri
* @returns {Promise<Conference>} fulfilled when the response is received
*/
async getConferenceByUri(uri) {
const room = await this._wacProxy.conference.getRoomByUri(uri);
return await Conference.create(this._userManager, room, this._defaultMediaConstraints);
}
}