Home Reference Source

src/conferences/ConferenceManager.ts

import EventEmitter from 'eventemitter3';
import {cloneDeep} from 'lodash-es';

import Logs from '../Logs';
import {Conference, MediaTypes, PermissionManager} from '../Sippo';
import {Conference as Room} from '../wac-proxy/stacks/Janus/Conference';
import {Conferences as Rooms} from '../wac-proxy/stacks/Janus/Conferences';
import {ErrorResponse} from '../wac-proxy/stacks/Janus/signaling/ErrorResponse';
import {KManageService} from '../wac-proxy/wac-stack/KManageService';
import {ConferenceLogService} from '../wac-proxy/wac-stack/conference-log/ConferenceLogService';

import {ConferenceLog} from './ConferenceLog';
import {AccessBeforeTimeError} from './errors/AccessBeforeTimeError';
import {WrongPasswordError} from './errors/WrongPasswordError';

const log = Logs.instance.getLogger('SippoJS/calls/ConferenceManager');

interface Events {
	conferenceInvitation: Conference;
}

/**
 * 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
 */
export class ConferenceManager {
	emitter = new EventEmitter<Events>();
	conferences = new Map();
	private conferenceLog?: ConferenceLog;

	private defaultMediaConstraints: MediaStreamConstraints = {
		audio: true,
		video: true,
	};

	/** @nodoc */
	private constructor(
		private conferenceService: Rooms,
		private kManageService: KManageService,
		private conferenceLogService: ConferenceLogService,
		private permissionManager: PermissionManager,
		private ownId: string,
	) {
		this.onIncomingConference = this.onIncomingConference.bind(this);
		this.conferenceService.emitter.on('incomingConference', this.onIncomingConference);
	}

	private async onIncomingConference(room: Room, mediaTypes: MediaTypes): Promise<void> {
		const conference = await this.getConferenceForRoom(room);
		await conference.getLocalMediaHandler().setMediaTypes(mediaTypes);
		this.conferences.set(room.id, conference);
		this.emitter.emit('conferenceInvitation', conference);
	}

	/**
	 * Returns current media constraints used by default when creating conferences
	 */
	getDefaultMediaConstraints(): MediaStreamConstraints {
		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: MediaStreamConstraints): void {
		this.defaultMediaConstraints = cloneDeep(defaultMediaConstraints);
	}

	/**
	 * Creates a new conference room
	 * @param options
	 * @param options.recording boolean indicating whether the conference is going to be recorded or not. This applies
	 * only if the user has {@link PermissionManager.canSetRecordOption} permission
	 */
	async createConference(options: {recording?: boolean} = {recording: false}): Promise<Conference> {
		let recording;
		try {
			recording = this.permissionManager.canSetRecordOption() ? options.recording : this.permissionManager.canAlwaysRecord();
		} catch (error) {
			if (error instanceof Error) {
				log.error('Error acquiring permissions: ', error.message);
				recording = false;
			} else {
				throw error;
			}
		}
		const room = await this.conferenceService.create(recording);
		return this.getConferenceForRoom(room);
	}

	/**
	 * Returns a ConferenceLog reference
	 */
	getConferenceLog(): ConferenceLog {
		if (this.conferenceLog === undefined) {
			this.conferenceLog = new ConferenceLog(this.conferenceLogService, this.ownId);
		}
		return this.conferenceLog;
	}

	/**
	 * Asks the server for the current invitations available to the user
	 */
	async getCurrentInvitations(): Promise<Conference[]> {
		log.debug('calls/ConferenceManager.js/getCurrentInvitations');
		return this.conferenceService.getCurrentInvitations().then(async (rooms) => {
			log.debug('calls/ConferenceManager.js/getCurrentInvitations/rooms', rooms);
			const conferences = rooms.map(async (room) => {
				const conference = await this.getConferenceForRoom(room);
				if (room.invite) {
					await conference.getLocalMediaHandler().setMediaTypes(room.invite.mediatypes);
				}
				this.conferences.set(room.id, conference);
				this.emitter.emit('conferenceInvitation', conference);
				return conference;
			});
			return Promise.all(conferences);
		});
	}

	/**
	 * Get a Conference by uri
	 *
	 * @param uri
	 * @deprecated Use "getMeetingConference" or "getQuickConference" instead
	 */
	async getConferenceByUri(uri: string): Promise<Conference> {
		const room = await this.conferenceService.getRoomByUri(uri);
		return this.getConferenceForRoom(room);
	}

	/**
	 * Get a conference for a meeting
	 *
	 * @param meetingId
	 * @param password
	 * @returns fulfilled when the response is received
	 * @throws {AccessBeforeTimeError} thrown when trying to access the conference before scheduled time
	 * @throws {WrongPasswordError} thrown when the conference is protected by password and the password is not recived or is not valid.
	 */
	async getMeetingConference(meetingId: string, password?: string): Promise<Conference> {
		try {
			const conferenceRoom = await this.conferenceService.getMeetingConference(meetingId, password);
			return await this.getConferenceForRoom(conferenceRoom);
		} catch (error) {
			if (error instanceof ErrorResponse) {
				switch (error.message) {
					case 410:
						throw new AccessBeforeTimeError();
					case 403:
						throw new WrongPasswordError();
				}
			}
			throw error;
		}
	}

	/**
	 * Gets a quick conference
	 *
	 * @param quickConferenceId
	 * @returns fulfilled when the response is received
	 *
	 */
	async getQuickConference(quickConferenceId: string): Promise<Conference> {
		const conferenceRoom = await this.conferenceService.getQuickConference(quickConferenceId);
		return await this.getConferenceForRoom(conferenceRoom);
	}

	private async getConferenceForRoom(room: Room): Promise<Conference> {
		return Conference.create(room, this.kManageService, this.defaultMediaConstraints);
	}
}