src/calls/CallManager.js
import {List, Set} from 'immutable';
import {cloneDeep} from 'lodash-es';
import {EventEmitter} from '../eventemitter';
import {bindMethods} from '../utils';
import Logs from '../Logs';
import {Call} from './Call';
import {CallStatus} from './CallStatus';
import {CallDirection} from './CallDirection';
import {RemoteCall} from './RemoteCall';
import {RemoteSession} from '../session/RemoteSession';
import {CallLog} from './CallLog';
const log = Logs.instance.getLogger('SippoJS/CallManager');
/**
* This class provides access to every call related feature
* This class must never be directly instantiated, instances of this class
* can be obtained using {@link Session#getCallManager}.
*
* ## Events
*
* - `incomingCall` ({@link Call}) - Emitted every time a new incoming call is received.
*/
export class CallManager {
/**
* @protected
* @param {WacProxy} wacProxy
* @param {UserManager} userManager
* @param {string} ownGatewayUsername
*/
static newInstance(wacProxy, userManager, ownGatewayUsername) {
return new CallManager(wacProxy, userManager, ownGatewayUsername);
}
/**
* Creates a new call manager
* @protected
*/
constructor(wacProxy, userManager, ownGatewayUsername) {
this._wacProxy = wacProxy;
this._callAttemptService = wacProxy.getCallAttemptService();
this._userManager = userManager;
this._defaultMediaConstraints = {
audio: true,
video: true,
};
this._ownGatewayUsername = ownGatewayUsername;
this._pollingCalls = new List();
this._incomingCalls = new Set();
this._outgoingCalls = new Set();
this._callLog = null;
/** @type {EventEmitter} */
this.emitter = new EventEmitter();
bindMethods(this, [
'_onCallAttemptCreated',
'_onRinging',
]);
this._bindEventHandlers();
}
/**
* @private
*/
_bindEventHandlers() {
this._callAttemptService.emitter.on('created', this._onCallAttemptCreated);
this._wacProxy.on('qs-ringing', this._onRinging);
}
/**
* @private
*/
_onCallAttemptCreated(event) {
this.userManager.resolveUser(event.caller).then((user) => {
return Call.newInstance(this.wacProxy, this.userManager, {
gatewayCaller: event.caller,
gatewayCallee: this._ownGatewayUsername,
remoteUser: user,
pollingCall: true,
});
}).then((call) => {
call.setContext(event.context);
this._addIncomingCall(call);
this.emitter.emit('incomingCall', call);
}).catch(error => log.error(error));
}
/**
* @private
*/
async _onRinging(event) {
const isDatapipe = event.media && event.media.data;
const isReconnection = this._incomingCalls.find(c => c.getRemoteGatewayUser() === event.userid);
if (isDatapipe || isReconnection) {
return;
}
try {
const user = await this._userManager.resolveUser(event.userid);
const call = await Call.newInstance(this._wacProxy, this._userManager, {
id: event.callid,
gatewayCaller: event.userid,
gatewayCallee: this._ownGatewayUsername,
remoteUser: user,
mediaConstraints: this._defaultMediaConstraints,
});
await call.getLocalMediaHandler().setMediaTypes({
audio: event.media.audio,
video: event.media.video,
screen: false,
});
this._addIncomingCall(call);
this.emitter.emit('incomingCall', call);
} catch (error) {
log.error(error);
}
}
_addIncomingCall(call) {
this._incomingCalls = this._incomingCalls.add(call);
call.waitForStatus(CallStatus.DISCONNECTED).then(() => {
this._incomingCalls = this._incomingCalls.delete(call);
});
}
_addOutgoingCall(call) {
this._outgoingCalls = this._outgoingCalls.add(call);
call.waitForStatus(CallStatus.DISCONNECTED).then(() => {
this._outgoingCalls = this._outgoingCalls.delete(call);
});
}
/**
* @return {MediaStreamConstraints}
*/
getDefaultMediaConstraints() {
return cloneDeep(this._defaultMediaConstraints);
}
/**
* Sets the constraints that will be used as initial value for media requests when
* initializing calls. 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>
* callManager.setDefaultMediaConstraints({
* audio: {
* autoGainControl: true,
* echoCancellation: true,
* noiseSuppression: true,
* },
* video: true,
* });
* @param {MediaStreamConstraints} defaultMediaConstraints
*/
setDefaultMediaConstraints(defaultMediaConstraints) {
this._defaultMediaConstraints = cloneDeep(defaultMediaConstraints);
}
/**
* @return {Promise<CallLog>}
*/
getCallLog() {
if (this._callLog === null) {
const callService = this._wacProxy.getCallService();
this._callLog = CallLog.create(callService, this._userManager);
}
return this._callLog;
}
/**
* Creates a new call instance for communication with the specified recipient.
* @param {String} to The user identifier of the call recipient.
* @return {Promise<Call, TypeError>}
*/
createCall(to) {
if (typeof to !== 'string') {
return Promise.reject(new TypeError(`${to} is not a string`));
}
return this._userManager.resolveUser(to).then((user) => {
const gwUsername = user ? user.gatewayUsername : to;
return Call.newInstance(this._wacProxy, this._userManager, {
direction: CallDirection.OUTGOING,
gatewayCaller: this._ownGatewayUsername,
gatewayCallee: gwUsername,
remoteUser: user,
mediaConstraints: this._defaultMediaConstraints,
});
}).then((call) => {
this._addOutgoingCall(call);
return call;
});
}
/**
* Get a list of remote calls where user is currently participating
* @return {Promise<RemoteCall[]>}
*/
getRemoteCalls() {
return Promise.resolve().then(() => this._wacProxy.getRemoteSessions()).then((sessions) => {
let calls = [];
sessions.forEach((session) => {
session.calls.forEach((call) => {
let dev = new RemoteSession(session.id, session.name);
let item = new RemoteCall(call.id, call.participants, dev);
calls.push(item);
});
});
return calls;
});
}
/**
* Pulls remote call from remote session.
*
* @example
* const callManager = await session.getCallManager();
* const calls = await callManager.getRemoteCalls();
* console.log('got remote calls', calls);
* if (!calls) {
* return;
* }
* const call = await cm.pull(calls[0]);
* await call.connect();
*
* @param {RemoteCall} remoteCall
* @return {Promise<Call, Error>}
*/
async pull(remoteCall) {
const user = await this._userManager.resolveUser(remoteCall.participants[0]);
let call = await Call.newInstance(this._wacProxy, this._userManager, {
direction: CallDirection.OUTGOING,
gatewayCaller: this._ownGatewayUsername,
gatewayCallee: remoteCall.participants[0],
remoteUser: user,
defaultMediaConstraints: this._defaultMediaConstraints,
pullFrom: remoteCall,
});
return call;
}
}