infobip-rtc
Version:
Infobip RTC JavaScript SDK - Infobip WebRTC API Implementation
1,026 lines • 86 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { CameraOrientation } from "../options/CameraOrientation";
import { Audio, Media, Participant, State } from "../../util/Participant";
import { CallStatus } from "../CallStatus";
import MonitoredPeerConnection from "../../log/monitor/MonitoredPeerConnection";
import { MediaUpdateStatus } from "./MediaUpdateStatus";
import { v4 as uuid } from "uuid";
import { ErrorLog } from "../../log/Log";
import HangupReasonFactory from "./util/HangupReasonFactory";
import HangupStatusFactory from "../util/HangupStatusFactory";
import { MediaType } from "../../log/util/MediaType";
import SimulcastEncodings from "../../util/SimulcastEncodings";
import VideoType from "../../util/VideoType";
import { ApplicationErrorCode } from "../ApplicationErrorCode";
import { WsEvent } from "../ws/WsEvent";
import { ApiEventEmitter } from "../../util/ApiEventEmitter";
import CallProperties from "./CallProperties";
import { EndpointType, PhoneEndpoint, SipEndpoint, ViberEndpoint, WebrtcEndpoint, WebsocketEndpoint, WhatsAppEndpoint } from "../../util/Endpoint";
import { WsAction } from "../ws/WsAction";
import { EmptyAudioStream } from "./EmptyAudioStream";
import { PeerConnectionTag } from "../../log/monitor/media/PeerConnectionTag";
import { DefaultVideoFilterManager } from "../options/filters/video/DefaultVideoFilterManager";
import { NetworkQualityMonitor } from "./network/NetworkQualityMonitor";
import { CallsApiEvent } from "../event/CallsApiEvents";
import VideoRemovalReason from "../../util/VideoRemovalReason";
import { DefaultLocalCapturer } from "./DefaultLocalCapturer";
import { DefaultServerCapturer } from "./DefaultServerCapturer";
import { DefaultDataChannel } from "./DefaultDataChannel";
import { AudioQualityMode } from "../options/AudioQualityMode";
import { RTCMediaDevice } from "../../RTCMediaDevice";
import { BitrateConfig, configureForSending } from "./util/BitrateUtil";
import { ManagedEventEmitter } from "../../util/ManagedEventEmitter";
import { DefaultAudioFilterManager } from "../options/filters/audio/DefaultAudioFilterManager";
import Browser from "../../util/Browser";
const LOW_DATA_AUDIO_BITRATE = 10000;
var ApplicationCallState;
(function (ApplicationCallState) {
ApplicationCallState[ApplicationCallState["STANDALONE"] = 0] = "STANDALONE";
ApplicationCallState[ApplicationCallState["IN_DIALOG"] = 1] = "IN_DIALOG";
ApplicationCallState[ApplicationCallState["IN_CONFERENCE"] = 2] = "IN_CONFERENCE";
})(ApplicationCallState || (ApplicationCallState = {}));
export class DefaultApplicationCall {
constructor(eventEmitter, gateway, logger, rtcConfig, device, _callsConfigurationId, applicationCallOptions, currentUserIdentity, token, apiUrl, callId = null) {
var _a, _b, _c, _d, _e, _f, _g;
this.gateway = gateway;
this.logger = logger;
this.rtcConfig = rtcConfig;
this.device = device;
this._callsConfigurationId = _callsConfigurationId;
this.applicationCallOptions = applicationCallOptions;
this.currentUserIdentity = currentUserIdentity;
this.token = token;
this.apiUrl = apiUrl;
this.hasRemoteDescription = false;
this.remoteCandidates = [];
this.isEarlyMedia = false;
this.joinedConference = false;
this.joinedDialog = false;
this.participants = {};
this.remoteVideos = {};
this.videoFilterManager = null;
this.audioFilterManager = null;
this.reconnecting = false;
this._recordingState = {};
this.cameraVideoEncodings = new Browser().isMobile() ? SimulcastEncodings.cameraEncodingsMobile : SimulcastEncodings.cameraEncodings;
this.eventEmitter = new ManagedEventEmitter(eventEmitter);
this.callStartTime = new Date();
this.callStatus = CallStatus.INITIALIZED;
this.mediaUpdateStatus = MediaUpdateStatus.IDLE;
this.apiEventEmitter = new ApiEventEmitter();
this.callId = callId || uuid();
this.localScreenShare = { active: false };
this.networkQualityMonitor = new NetworkQualityMonitor();
let videoFilter = (_b = (_a = this.applicationCallOptions) === null || _a === void 0 ? void 0 : _a.videoOptions) === null || _b === void 0 ? void 0 : _b.videoFilter;
if (videoFilter) {
this.videoFilterManager = new DefaultVideoFilterManager(videoFilter);
}
let audioFilter = (_d = (_c = this.applicationCallOptions) === null || _c === void 0 ? void 0 : _c.audioOptions) === null || _d === void 0 ? void 0 : _d.audioFilter;
if (audioFilter) {
this.audioFilterManager = new DefaultAudioFilterManager(audioFilter);
}
this._audioQualityMode = ((_f = (_e = this.applicationCallOptions) === null || _e === void 0 ? void 0 : _e.audioOptions) === null || _f === void 0 ? void 0 : _f.audioQualityMode) || AudioQualityMode.AUTO;
if ((_g = this.applicationCallOptions) === null || _g === void 0 ? void 0 : _g.dataChannel) {
this.createDataChannel();
}
this.initEventHandlers();
this.handleDeviceChange();
}
on(name, handler) {
if (!Object.values(CallsApiEvent)
.find(apiEvent => apiEvent === name)) {
throw new Error(`Unknown event: ${name}!`);
}
this.apiEventEmitter.on(name, handler);
}
id() {
return this.callId;
}
options() {
return this.applicationCallOptions;
}
customData() {
var _a;
return (_a = this.applicationCallOptions) === null || _a === void 0 ? void 0 : _a.customData;
}
callsConfigurationId() {
return this._callsConfigurationId;
}
duration() {
if (!this.callEstablishTime) {
return 0;
}
return !this.callEndTime ? this.getDurationInSeconds(new Date()) : this.getDurationInSeconds(this.callEndTime);
}
endTime() {
return this.callEndTime;
}
establishTime() {
return this.callEstablishTime;
}
hangup() {
if (this.callStatus !== CallStatus.FINISHED) {
this.callStatus = CallStatus.FINISHED;
this.dataChannelCleanup();
this.scheduleHangup({ callId: this.callId, status: { id: '0', name: 'NO_ERROR', description: 'No Error.' } });
this.gateway.send({ action: WsAction.HANGUP, callId: this.callId, reason: 'Normal call clearing' });
}
}
startTime() {
return this.callStartTime;
}
status() {
return this.callStatus;
}
mute(shouldMute) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
this.validateMediaActionAllowed();
if (!this.localAudioStream) {
throw ApplicationErrorCode.MEDIA_ERROR;
}
if (this.emptyAudioStream && !shouldMute) {
this.localAudio.active = true;
this.emptyAudioStream.close();
delete this.emptyAudioStream;
try {
const stream = yield this.getLocalAudioStream(true, false);
yield ((_a = this.audioFilterManager) === null || _a === void 0 ? void 0 : _a.stop());
yield ((_b = this.audioFilterManager) === null || _b === void 0 ? void 0 : _b.start(stream, 0));
const track = this.setAudioStream(stream);
yield this.replaceTrack(this.localAudio, track);
this.sendMute(shouldMute);
}
catch (error) {
return this.throwMediaError(error);
}
}
let audioTracks = this.localAudioStream.getAudioTracks();
if (audioTracks.length === 0) {
throw ApplicationErrorCode.MEDIA_ERROR;
}
let audioTrack = audioTracks[0];
this.localAudio.active = !shouldMute;
audioTrack.enabled = this.localAudio.active;
this.sendMute(shouldMute);
});
}
muted() {
return !this.localAudio.active;
}
sendDTMF(dtmf) {
return __awaiter(this, void 0, void 0, function* () {
if (!dtmf || dtmf.toString().match(/[^0-9*#A-D]/)) {
throw ApplicationErrorCode.MEDIA_ERROR;
}
if (this.callStatus !== CallStatus.ESTABLISHED) {
throw {
id: '10103',
name: 'MEDIA_ERROR',
description: 'Call not established, action not allowed!'
};
}
this.validateMediaActionAllowed();
this.sendDTMFInfo(dtmf, CallProperties.DTMF_TONE_DURATION);
});
}
pauseIncomingVideo() {
this.validateMediaActionAllowed();
if (!this.joinedConference && !this.joinedDialog) {
return;
}
this.gateway.send({
action: WsAction.PAUSE_INCOMING_VIDEO
});
}
resumeIncomingVideo() {
this.validateMediaActionAllowed();
if (!this.joinedConference && !this.joinedDialog) {
return;
}
this.gateway.send({
action: WsAction.RESUME_INCOMING_VIDEO
});
}
setAudioInputDevice(deviceId) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
if (this.callStatus === CallStatus.RINGING) {
this.device.setAudioInputDevice(deviceId);
return;
}
this.device.setAudioInputDevice(deviceId);
(_a = this.localAudioStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach(track => track.stop());
try {
const stream = yield this.getLocalAudioStream(true, false);
yield ((_b = this.audioFilterManager) === null || _b === void 0 ? void 0 : _b.stop());
yield ((_c = this.audioFilterManager) === null || _c === void 0 ? void 0 : _c.start(stream, 0));
const track = this.setAudioStream(stream);
track.enabled = this.localAudio.active;
return this.replaceTrack(this.localAudio, track);
}
catch (error) {
return this.throwMediaError(error);
}
});
}
audioFilter() {
var _a;
return (_a = this.audioFilterManager) === null || _a === void 0 ? void 0 : _a.getAudioFilter();
}
setAudioFilter(audioFilter) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (this.audioFilterManager && this.audioFilterManager.getAudioFilter() === audioFilter) {
return;
}
try {
let previousFilter = this.audioFilterManager;
this.audioFilterManager = audioFilter ? new DefaultAudioFilterManager(audioFilter) : null;
yield (previousFilter === null || previousFilter === void 0 ? void 0 : previousFilter.stop());
if (this.localAudioStream) {
this.localAudioStream.getAudioTracks().forEach((track) => track.stop());
}
const stream = yield this.getLocalAudioStream(true, false);
yield ((_a = this.audioFilterManager) === null || _a === void 0 ? void 0 : _a.start(stream, 0));
const track = this.setAudioStream(stream);
track.enabled = this.localAudio.active;
yield this.replaceTrack(this.localAudio, track);
}
catch (error) {
return this.throwMediaError(error);
}
});
}
clearAudioFilter() {
return this.setAudioFilter(null);
}
videoFilter() {
var _a;
return (_a = this.videoFilterManager) === null || _a === void 0 ? void 0 : _a.getVideoFilter();
}
setVideoFilter(videoFilter) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (this.videoFilterManager && this.videoFilterManager.getVideoFilter() === videoFilter) {
return;
}
let previousFilter = this.videoFilterManager;
this.videoFilterManager = videoFilter ? new DefaultVideoFilterManager(videoFilter) : null;
yield (previousFilter === null || previousFilter === void 0 ? void 0 : previousFilter.stop());
if (this.hasCameraVideo()) {
yield ((_a = this.videoFilterManager) === null || _a === void 0 ? void 0 : _a.start(this.localCameraVideoStream, 0, this.apiEventEmitter));
yield this.updateTransceiver(this.localCameraVideo, this.localCameraVideoStream, this.cameraVideoEncodings);
this.apiEventEmitter.emit(CallsApiEvent.CAMERA_VIDEO_UPDATED, { stream: this.localCameraVideoStream });
}
});
}
clearVideoFilter() {
return this.setVideoFilter(null);
}
setVideoInputDevice(deviceId) {
return __awaiter(this, void 0, void 0, function* () {
if (this.callStatus === CallStatus.RINGING) {
this.device.setVideoInputDevice(deviceId);
return;
}
this.device.setVideoInputDevice(deviceId);
yield this.updateCameraStream(this.cameraOrientation());
});
}
cameraOrientation() {
return this.device.getCameraOrientation();
}
setCameraOrientation(cameraOrientation) {
return this.updateCameraStream(cameraOrientation, false);
}
localCapturer() {
var _a;
return (_a = this.defaultLocalCaputuer) !== null && _a !== void 0 ? _a : (this.defaultLocalCaputuer = new DefaultLocalCapturer(() => this.localCameraVideoStream, () => this.localScreenShareStream, () => { var _a; return (_a = this.videoSubscriberPC) === null || _a === void 0 ? void 0 : _a.peerConnection; }, (identity, videoType) => this.getMidByIdentityAndVideoType(identity, videoType), this.currentUserIdentity));
}
serverCapturer() {
var _a;
return (_a = this.defaultServerCapturer) !== null && _a !== void 0 ? _a : (this.defaultServerCapturer = new DefaultServerCapturer(() => this.localCameraVideoStream, () => this.localScreenShareStream, () => { var _a; return (_a = this.videoSubscriberPC) === null || _a === void 0 ? void 0 : _a.peerConnection; }, (identity, videoType) => this.getMidByIdentityAndVideoType(identity, videoType), this.currentUserIdentity, this.token, this.apiUrl, this.logger));
}
dataChannel() {
return this._dataChannel;
}
setAudioQualityMode(audioQualityMode) {
this._audioQualityMode = audioQualityMode;
const senders = this.audioPC.peerConnection.getSenders();
senders.forEach((sender) => {
var _a;
if (sender.track.kind !== 'audio') {
return;
}
const parameters = sender.getParameters();
(_a = parameters.encodings) !== null && _a !== void 0 ? _a : (parameters.encodings = [{}]);
if (parameters.encodings[0]) {
parameters.encodings[0].priority = 'high';
parameters.encodings[0].networkPriority = 'high';
}
parameters.encodings.forEach(encoding => {
switch (audioQualityMode) {
case AudioQualityMode.LOW_DATA:
encoding.maxBitrate = LOW_DATA_AUDIO_BITRATE;
break;
case AudioQualityMode.AUTO:
case AudioQualityMode.HIGH_QUALITY:
encoding.maxBitrate = undefined;
break;
}
});
sender.setParameters(parameters)
.catch(err => this.logger.error(`Changing data consumption mode failed: ${err === null || err === void 0 ? void 0 : err.message}`));
});
}
audioQualityMode() {
return this._audioQualityMode;
}
hasCameraVideo() {
return this.localCameraVideo && this.localCameraVideo.active;
}
hasScreenShare() {
return this.localScreenShare && this.localScreenShare.active;
}
cameraVideo(localVideo) {
return __awaiter(this, void 0, void 0, function* () {
this.validateMediaActionAllowed();
this.validateMediaUpdateStatus();
if (localVideo !== this.localCameraVideo.active) {
this.localCameraVideo.active = localVideo;
return localVideo ? this.addCameraVideo() : this.removeCameraVideo();
}
});
}
screenShare(screenShare) {
return __awaiter(this, void 0, void 0, function* () {
this.validateMediaActionAllowed();
this.validateMediaUpdateStatus();
if (screenShare !== this.localScreenShare.active) {
this.localScreenShare.active = screenShare;
return screenShare ? this.addScreenShareVideo() : this.removeScreenShareVideo(VideoRemovalReason.USER_REQUEST);
}
});
}
startScreenShare(displayOptions) {
return __awaiter(this, void 0, void 0, function* () {
this.validateMediaActionAllowed();
this.validateMediaUpdateStatus();
this.localScreenShare.active = true;
return this.addScreenShareVideo(displayOptions);
});
}
stopScreenShare() {
return __awaiter(this, void 0, void 0, function* () {
this.validateMediaActionAllowed();
this.validateMediaUpdateStatus();
this.localScreenShare.active = false;
return this.removeScreenShareVideo(VideoRemovalReason.USER_REQUEST);
});
}
recordingState() {
return this._recordingState;
}
createDataChannel() {
this._dataChannel = new DefaultDataChannel(this.gateway, this.logger, this.callId, this.currentUserIdentity, identity => this.participants[identity], ice => this.onIceCandidate(WsAction.ICE_CANDIDATE_DATA_CHANNEL, ice), () => this.joinedDialog || this.joinedConference, this.apiEventEmitter, this.conferenceId);
}
createAudioPeerConnection() {
this.audioPC = MonitoredPeerConnection.create(this.rtcConfig, this.callId, PeerConnectionTag.Audio, this.conferenceId || this.dialogId, MediaType.AUDIO, this.logger, this.gateway);
this.audioPC.peerConnection.onicecandidate = ice => this.onIceCandidate(WsAction.ICE_CANDIDATE, ice);
this.audioPC.peerConnection.ontrack = event => this.onAudioPcTrack(event);
this.audioPC.peerConnection.onconnectionstatechange = () => this.onAudioConnectionStateChanged();
this.audioPC.monitor.onNetworkQualityStatistics((networkQualityStatistics, currentMediaStats) => this.onNetworkQualityStatisticsChanged(networkQualityStatistics, currentMediaStats));
}
setLocalAudioStream(audioStream) {
this.localAudioStream = audioStream;
let track = this.localAudioStream.getAudioTracks()[0];
if (!track) {
this.emptyAudioStream = new EmptyAudioStream();
return;
}
track.enabled = this.localAudio.active;
}
createAudioTransceiver() {
let stream = this.emptyAudioStream ? this.emptyAudioStream.stream() : this.localAudioStream;
let track = stream.getAudioTracks()[0];
this.localAudio.transceiver = this.audioPC.peerConnection.addTransceiver(track, {
streams: [stream],
direction: 'sendrecv'
});
}
setLocalDescription(pc, localDescription) {
return __awaiter(this, void 0, void 0, function* () {
yield pc.setLocalDescription(localDescription);
return localDescription;
});
}
setRemoteCandidates() {
this.hasRemoteDescription = true;
if (this.remoteCandidates.length > 0) {
this.remoteCandidates.forEach(candidate => this.addIceCandidate(candidate));
this.remoteCandidates = [];
}
}
addCameraVideo() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
this.mediaUpdateStatus = MediaUpdateStatus.ADDING_CAMERA_VIDEO;
try {
this.localCameraVideoStream = yield this.getCameraVideoStream(this.cameraOrientation());
yield ((_a = this.videoFilterManager) === null || _a === void 0 ? void 0 : _a.start(this.localCameraVideoStream, 0, this.apiEventEmitter));
}
catch (error) {
this.localCameraVideo.active = false;
this.handleGetUserMediaError(error, "camera");
return;
}
yield this.publishVideo(this.joinedConference || this.joinedDialog);
});
}
addScreenShareVideo(displayOptions) {
return __awaiter(this, void 0, void 0, function* () {
this.mediaUpdateStatus = MediaUpdateStatus.ADDING_SCREEN_SHARE;
let isConference = this.joinedConference || this.joinedDialog;
if (!this.videoPublisherPC) {
this.createVideoPublisherPC(isConference);
}
try {
this.localScreenShareStream = yield this.getScreenShareVideoStream(displayOptions);
}
catch (error) {
this.handleGetUserMediaError(error, "screen-share");
return;
}
try {
yield this.updateTransceiver(this.localScreenShare, this.localScreenShareStream, SimulcastEncodings.screenShareEncodings);
yield this.negotiateVideoPublisher(isConference);
}
catch (error) {
this.handlePublishVideoFlowError(error);
}
});
}
handleCallFlowError(error, sendHangup = false) {
this.logger.log(new ErrorLog(this.callId, error));
if (sendHangup) {
let reason = HangupReasonFactory.getHangupReason(error);
this.gateway.send({ action: WsAction.HANGUP, callId: this.callId, reason: reason });
}
let hangupStatus = HangupStatusFactory.getApplicationHangupStatus(error);
this.eventEmitter.emit('call-error', { status: hangupStatus });
}
validateMediaUpdateStatus() {
if (this.mediaUpdateStatus !== MediaUpdateStatus.IDLE && this.mediaUpdateStatus !== MediaUpdateStatus.MEDIA_PENDING) {
throw new Error("User media already updating.");
}
}
getLocalAudioStream(audio, requestVideo, cameraOrientation, cameraVideoFrameRate) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (!audio && !requestVideo) {
return new MediaStream();
}
let stream = yield this.device.getLocalStream(audio, requestVideo, cameraOrientation, true, false, cameraVideoFrameRate);
if (requestVideo && stream.getVideoTracks().length > 0) {
let videoTrack = stream.getVideoTracks()[0];
this.localCameraVideoStream = new MediaStream([videoTrack]);
yield ((_a = this.videoFilterManager) === null || _a === void 0 ? void 0 : _a.start(this.localCameraVideoStream, 0, this.apiEventEmitter));
this.mediaUpdateStatus = this.reconnecting ? MediaUpdateStatus.RECONNECTING : MediaUpdateStatus.ADDING_CAMERA_VIDEO;
}
return audio ? new MediaStream([stream.getAudioTracks()[0]]) : new MediaStream();
});
}
isFinished() {
return this.callStatus === CallStatus.FINISHED;
}
getMidByIdentityAndVideoType(identity, type) {
var _a;
return (_a = Object.values(this.remoteVideos).find(value => value.participant.endpoint.identifier === identity && value.type === type)) === null || _a === void 0 ? void 0 : _a.mid;
}
validateMediaActionAllowed() {
if (this.reconnecting) {
throw {
id: '10103',
name: 'MEDIA_ERROR',
description: 'Call is reconnecting, action not allowed!'
};
}
}
setAudioStream(stream) {
this.localAudioStream = stream;
return stream.getAudioTracks()[0];
}
sendMute(shouldMute) {
this.gateway.send({
action: WsAction.MUTE,
muted: shouldMute,
callId: this.callId
});
}
removeScreenShareVideoWithReason(reason) {
return __awaiter(this, void 0, void 0, function* () {
if (this.localScreenShare.active) {
this.validateMediaUpdateStatus();
this.localScreenShare.active = false;
return this.removeScreenShareVideo(reason);
}
});
}
initEventHandlers() {
this.eventEmitter.on(WsEvent.TRICKLE_ICE, event => this.handleTrickleIce(event));
this.eventEmitter.once(WsEvent.RINGING, event => this.ringingHandler(event));
this.eventEmitter.on(WsEvent.CALL_RESPONSE, event => this.responseHandler(event));
this.eventEmitter.on(WsEvent.CALL_ACCEPTED, event => this.acceptedHandler());
this.eventEmitter.once(WsEvent.HANGUP, event => this.hangupHandler(event));
this.eventEmitter.once(WsEvent.CALL_ERROR, event => this.errorHandler(event));
this.eventEmitter.on(WsEvent.JOINED_VIDEO_CALL, () => this.videoCallJoinedHandler());
this.eventEmitter.on(WsEvent.PUBLISHED_VIDEO_CALL, event => this.videoCallPublishedHandler(event));
this.eventEmitter.on(WsEvent.UNPUBLISHED_VIDEO_CALL, () => this.videoCallUnpublishedHandler());
this.eventEmitter.on(WsEvent.JOIN_VIDEO_CALL_ERROR, event => this.handleJoinVideoCallError(event));
this.eventEmitter.on(WsEvent.PUBLISH_VIDEO_CALL_ERROR, event => this.publishVideoCallErrorHandler(event));
this.eventEmitter.on(WsEvent.CALL_RECORDING_STARTED, event => this.handleCallRecordingStarted(event));
this.eventEmitter.on(WsEvent.CALL_RECORDING_STOPPED, event => this.handleCallRecordingStopped(event));
this.eventEmitter.on(WsEvent.JOINED_APPLICATION_CONFERENCE, event => this.handleJoinedApplicationConference(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_JOINING, event => this.handleParticipantJoining(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_JOINED, event => this.handleParticipantJoined(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_MEDIA_CHANGED, event => this.handleParticipantMediaChanged(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_STARTED_TALKING, event => this.handleParticipantStartedTalking(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_STOPPED_TALKING, event => this.handleParticipantStoppedTalking(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_LEFT, event => this.handleParticipantLeft(event));
this.eventEmitter.on(WsEvent.LEFT_APPLICATION_CONFERENCE, event => this.handleLeftApplicationConference(event));
this.eventEmitter.on(WsEvent.JOINED_VIDEO_CONFERENCE, () => this.handleJoinedVideoConference());
this.eventEmitter.on(WsEvent.PUBLISHED_VIDEO_CONFERENCE, event => this.videoConferencePublishedHandler(event));
this.eventEmitter.on(WsEvent.UNPUBLISHED_VIDEO_CONFERENCE, () => this.videoConferenceUnpublishedHandler());
this.eventEmitter.on(WsEvent.JOIN_VIDEO_CONFERENCE_ERROR, event => this.handleJoinVideoConferenceError(event));
this.eventEmitter.on(WsEvent.PUBLISH_VIDEO_CONFERENCE_ERROR, event => this.publishVideoConferenceErrorHandler(event));
this.eventEmitter.on(WsEvent.SUBSCRIBED_VIDEO, event => this.subscribedVideoHandler(event));
this.eventEmitter.on(WsEvent.SUBSCRIBE_VIDEO_CONFERENCE_ERROR, event => this.subscribeVideoConferenceError(event));
this.eventEmitter.on(WsEvent.UPDATED_VIDEO, event => this.updatedVideoHandler(event));
this.eventEmitter.on(WsEvent.CONFERENCE_RECORDING_STARTED, event => this.handleConferenceRecordingStarted(event));
this.eventEmitter.on(WsEvent.CONFERENCE_RECORDING_STOPPED, event => this.handleConferenceRecordingStopped(event));
this.eventEmitter.on(WsEvent.DIALOG_CREATED, event => this.handleDialogCreated(event));
this.eventEmitter.on(WsEvent.DIALOG_ESTABLISHED, event => this.handleDialogEstablished(event));
this.eventEmitter.on(WsEvent.DIALOG_FINISHED, event => this.handleDialogFinished(event));
this.eventEmitter.on(WsEvent.DIALOG_FAILED, event => this.handleDialogFailed(event));
this.eventEmitter.on(WsEvent.DIALOG_RECORDING_STARTED, event => this.handleDialogRecordingStarted(event));
this.eventEmitter.on(WsEvent.DIALOG_RECORDING_STOPPED, event => this.handleDialogRecordingStopped(event));
this.eventEmitter.on(WsEvent.SETUP_DATA_CHANNEL, event => this.handleSetupDataChannel(event));
this.eventEmitter.on(WsEvent.SETUP_DATA_CHANNEL_ERROR, event => this.setupDataChannelError(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_NETWORK_QUALITY, event => this.handleParticipantNetworkQuality(event));
this.eventEmitter.on("receiving_video_media", event => this.videoMediaReceivingHandler());
this.eventEmitter.on("reconnecting", () => this.handleReconnecting());
this.eventEmitter.on("reconnected", () => this.handleReconnected());
this.eventEmitter.on(WsEvent.CALL_RECONNECTED, event => this.handleCallReconnected(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_DISCONNECTED, event => this.handleParticipantDisconnected(event));
this.eventEmitter.on(WsEvent.PARTICIPANT_RECONNECTED, event => this.handleParticipantReconnected(event));
}
handleDeviceChange() {
navigator.mediaDevices.ondevicechange = () => __awaiter(this, void 0, void 0, function* () {
let currentlyUsedDeviceId = this.device.getAudioInputDevice() || "default";
if (currentlyUsedDeviceId === "default") {
this.switchToDefaultDevice();
}
else {
let shouldChange = yield this.device.audioInputDeviceShouldChange();
if (shouldChange) {
this.switchToDefaultDevice();
}
}
});
}
switchToDefaultDevice() {
this.setAudioInputDevice("default")
.catch(err => {
this.logger.error(`Switching audio input device failed (${err === null || err === void 0 ? void 0 : err.message})`, this.callId);
});
}
updateVideoBitrate() {
if (this.videoPublisherPC.peerConnection.connectionState !== 'connected') {
return;
}
let desiredBitrate = [
this.hasCameraVideo() ? BitrateConfig.CAMERA : 0,
this.hasScreenShare() ? BitrateConfig.SCREENSHARE : 0
].reduce((prev, curr) => prev + curr, 0);
configureForSending(this.videoPublisherPC.peerConnection, "video", desiredBitrate, this.logger);
}
onVideoPublisherConnectionStateChanged() {
this.updateVideoBitrate();
}
onAudioConnectionStateChanged() {
if (this.audioPC.peerConnection.connectionState === 'connected') {
this.setAudioQualityMode(this._audioQualityMode);
}
}
ringingHandler(event) {
this.callStatus = CallStatus.RINGING;
if (this.applicationCallOptions && event.customData) {
this.applicationCallOptions.customData = Object.assign(Object.assign({}, this.applicationCallOptions.customData), event.customData);
}
this.apiEventEmitter.emit(CallsApiEvent.RINGING, {});
}
responseHandler(event) {
return __awaiter(this, void 0, void 0, function* () {
this.isEarlyMedia = event.isEarlyMedia;
try {
yield this.audioPC.peerConnection.setRemoteDescription(event.description);
this.setRemoteCandidates();
}
catch (error) {
this.handleCallFlowError(error, true);
}
});
}
acceptedHandler() {
if (this.callStatus === CallStatus.ESTABLISHED) {
return;
}
this.callStatus = CallStatus.ESTABLISHED;
this.callEstablishTime = new Date();
this.apiEventEmitter.emit(CallsApiEvent.ESTABLISHED, {
stream: this.remoteAudioStream
});
}
videoCallJoinedHandler() {
if (this.joinedConference || this.joinedDialog) {
return;
}
if (this.hasCameraVideo() || this.hasScreenShare()) {
this.publishVideo(false);
}
}
changeMonitorConferenceId(conferenceId) {
if (this.audioPC) {
this.audioPC.monitor.conferenceId = conferenceId;
}
if (this.videoPublisherPC) {
this.videoPublisherPC.monitor.conferenceId = conferenceId;
}
if (this.videoSubscriberPC) {
this.videoSubscriberPC.monitor.conferenceId = conferenceId;
}
}
handleSetupDataChannel(event) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
(_a = this._dataChannel) === null || _a === void 0 ? void 0 : _a.initialize(event, this.rtcConfig);
});
}
handleJoinedApplicationConference(event) {
this.joinedConference = true;
this.conferenceId = event.id;
this.changeMonitorConferenceId(event.id);
this.participants = this.createParticipantMap(event.participants);
this._recordingState.conferenceRecording = event.recordingType;
this.apiEventEmitter.emit(CallsApiEvent.CONFERENCE_JOINED, {
id: event.id,
name: event.name,
participants: Object.values(this.participants),
recordingType: event.recordingType
});
}
handleDialogCreated(event) {
this.joinedDialog = true;
this.dialogId = event.id;
this.changeMonitorConferenceId(event.id);
this.participants = this.createParticipantMap(event.participants);
const participant = Object.values(this.participants)
.find(participant => participant.endpoint.identifier !== this.currentUserIdentity);
this._recordingState.dialogRecording = event.recordingType;
this.apiEventEmitter.emit(CallsApiEvent.DIALOG_JOINED, {
id: event.id,
remote: participant,
recordingType: event.recordingType
});
}
createParticipantMap(participants) {
return participants.reduce((map, p) => {
let participant = this.loadParticipant(p);
let identifier = participant.endpoint.identifier;
map[identifier] = participant;
return map;
}, {});
}
handleDialogEstablished(event) {
this.participants = this.createParticipantMap(event.participants);
const participant = Object.values(this.participants)
.find(participant => participant.endpoint.identifier !== this.currentUserIdentity);
this._recordingState.dialogRecording = event.recordingType;
if (!this.joinedDialog) {
this.joinedDialog = true;
this.dialogId = event.id;
this.changeMonitorConferenceId(event.id);
this.apiEventEmitter.emit(CallsApiEvent.DIALOG_JOINED, {
id: event.id,
remote: participant,
recordingType: event.recordingType
});
}
if (participant.media.audio.muted) {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_MUTED, { participant: participant });
}
}
handleParticipantJoining(event) {
if (this.joinedConference) {
const participant = this.loadParticipant(event.participant);
const identifier = participant.endpoint.identifier;
if (!this.participants[identifier]) {
this.participants[identifier] = participant;
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_JOINING, { participant: participant });
}
}
}
handleParticipantJoined(event) {
if (this.joinedConference) {
const participant = this.loadParticipant(event.participant);
const identifier = participant.endpoint.identifier;
if (!this.participants[identifier] || this.participants[identifier].state === State.JOINING) {
this.participants[identifier] = participant;
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_JOINED, { participant: participant });
}
}
}
handleParticipantMediaChanged(event) {
var _a, _b;
if (this.joinedConference || this.joinedDialog) {
const endpoint = this.getEndpoint(event.endpoint);
const identifier = endpoint.identifier;
if (this.participants[identifier]) {
let participant = this.participants[identifier];
if (((_a = event.media.audio) === null || _a === void 0 ? void 0 : _a.muted) !== undefined) {
participant.media.audio.muted = event.media.audio.muted;
this.emitMutedEvent(participant);
}
if (((_b = event.media.audio) === null || _b === void 0 ? void 0 : _b.deaf) !== undefined && !this.joinedDialog) {
participant.media.audio.deaf = event.media.audio.deaf;
this.emitDeafEvent(participant);
}
}
}
}
emitDeafEvent(participant) {
if (participant.media.audio.deaf) {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_DEAF, { participant: participant });
}
else {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_UNDEAF, { participant: participant });
}
}
emitMutedEvent(participant) {
if (participant.media.audio.muted) {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_MUTED, { participant: participant });
}
else {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_UNMUTED, { participant: participant });
}
}
emitDisconnectedEvent(participant) {
if (participant.disconnected) {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_DISCONNECTED, { participant: participant });
}
else {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_RECONNECTED, { participant: participant });
}
}
handleParticipantStartedTalking(event) {
if (this.joinedConference) {
const endpoint = this.getEndpoint(event.endpoint);
const identifier = endpoint.identifier;
if (this.participants[identifier]) {
let participant = this.participants[identifier];
participant.media.audio.talking = true;
this.emitTalkingEvent(participant, true);
}
}
}
handleParticipantStoppedTalking(event) {
if (this.joinedConference) {
const endpoint = this.getEndpoint(event.endpoint);
const identifier = endpoint.identifier;
if (this.participants[identifier]) {
let participant = this.participants[identifier];
participant.media.audio.talking = false;
this.emitTalkingEvent(participant, false);
}
}
}
emitTalkingEvent(participant, isTalking) {
if (isTalking) {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_STARTED_TALKING, { participant: participant });
}
else {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_STOPPED_TALKING, { participant: participant });
}
}
handleParticipantLeft(event) {
if (this.joinedConference) {
const participant = this.loadParticipant(event.participant);
const identifier = participant.endpoint.identifier;
delete this.participants[identifier];
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_LEFT, { participant: participant });
}
}
handleJoinedVideoConference() {
return __awaiter(this, void 0, void 0, function* () {
this.videoPublisherCleanup(true);
if (this.hasCameraVideo() || this.hasScreenShare()) {
if (this.mediaUpdateStatus === MediaUpdateStatus.IDLE) {
this.mediaUpdateStatus = MediaUpdateStatus.MIGRATING;
}
yield this.migrateLocalVideos();
try {
yield this.negotiateVideoPublisher(true);
}
catch (error) {
this.handlePublishVideoFlowError(error);
}
}
});
}
subscribedVideoHandler(event) {
this.remoteVideos = this.mapStreamEvent(event.streams);
this.createVideoSubscriberPC();
this.videoSubscriberPC.peerConnection.setRemoteDescription(event.description)
.then(() => this.videoSubscriberPC.peerConnection.createAnswer())
.then(answer => this.setLocalDescription(this.videoSubscriberPC.peerConnection, answer))
.then(answer => {
this.gateway.send({
action: WsAction.START_VIDEO_CONFERENCE,
description: answer
});
})
.catch(error => this.handleSubscribeVideoFlowError(error));
}
handleSubscribeVideoFlowError(error) {
this.logger.error(`Subscribe video flow error occurred: ${error}`, this.callId);
this.videoSubscriberCleanup();
let errorCode = HangupStatusFactory.getApplicationHangupStatus(error);
this.apiEventEmitter.emit(CallsApiEvent.ERROR, { errorCode });
}
updatedVideoHandler(event) {
if (event.streams) {
Object.keys(this.remoteVideos)
.filter(mid => !event.streams[mid])
.forEach(mid => {
let remoteVideo = this.remoteVideos[mid];
delete this.remoteVideos[mid];
this.emitParticipantVideoRemovedEvent(remoteVideo.type, remoteVideo.participant);
});
this.remoteVideos = this.mapStreamEvent(event.streams);
}
else {
this.videoSubscriberPC.peerConnection.restartIce();
}
this.videoSubscriberPC.peerConnection.setRemoteDescription(event.description)
.then(() => this.videoSubscriberPC.peerConnection.createAnswer())
.then(answer => this.setLocalDescription(this.videoSubscriberPC.peerConnection, answer))
.then(answer => {
this.gateway.send({
action: WsAction.START_VIDEO_CONFERENCE,
description: answer
});
})
.catch(error => this.handleSubscribeVideoFlowError(error));
}
emitParticipantVideoRemovedEvent(type, participant) {
if (type === VideoType.CAMERA) {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_CAMERA_VIDEO_REMOVED, { participant: participant });
}
else if (type === VideoType.SCREENSHARE) {
this.apiEventEmitter.emit(CallsApiEvent.PARTICIPANT_SCREEN_SHARE_REMOVED, { participant: participant });
}
}
updateCameraStream(cameraOrientation = CameraOrientation.FRONT, useExactDevice = true) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
if (!this.hasCameraVideo()) {
throw new Error("Camera video is not enabled.");
}
(_a = this.localCameraVideoStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach(track => track.stop());
try {
yield ((_b = this.videoFilterManager) === null || _b === void 0 ? void 0 : _b.stop());
const stream = yield this.getCameraVideoStream(cameraOrientation, useExactDevice);
this.localCameraVideoStream = stream;
this.apiEventEmitter.emit(CallsApiEvent.CAMERA_VIDEO_UPDATED, { stream: stream });
yield ((_c = this.videoFilterManager) === null || _c === void 0 ? void 0 : _c.start(stream, 0, this.apiEventEmitter));
return this.replaceTrack(this.localCameraVideo, stream.getVideoTracks()[0]);
}
catch (error) {
this.throwMediaError(error);
}
});
}
handleLeftApplicationConference(event) {
return __awaiter(this, void 0, void 0, function* () {
this.joinedConference = false;
this.conferenceId = null;
this.changeMonitorConferenceId(null);
this.participants = {};
this.remoteVideos = {};
this.videoPublisherCleanup(true);
this.videoSubscriberCleanup();
this.dataChannelCleanup();
this._recordingState.conferenceRecording = "UNDEFINED";
if (this.hasCameraVideo() || this.hasScreenShare()) {
this.mediaUpdateStatus = MediaUpdateStatus.MIGRATING;
yield this.migrateLocalVideos();
}
this.apiEventEmitter.emit(CallsApiEvent.CONFERENCE_LEFT, { errorCode: event.status });
});
}
handleDialogFinished(event) {
return __awaiter(this, void 0, void 0, function* () {
this.joinedDialog = false;
this.dialogId = null;
this.changeMonitorConferenceId(null);
this.participants = {};
this.remoteVideos = {};
this.videoPublisherCleanup(true);
this.videoSubscriberCleanup();
this.dataChannelCleanup();
this._recordingState.dialogRecording = "UNDEFINED";
if (this.hasCameraVideo() || this.hasScreenShare()) {
this.mediaUpdateStatus = MediaUpdateStatus.MIGRATING;
yield this.migrateLocalVideos();
}
this.apiEventEmitter.emit(CallsApiEvent.DIALOG_LEFT, { errorCode: event.status });
});
}
handleReconnecting() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (this.callStatus !== CallStatus.ESTABLISHED) {
this.logger.warn("Reconnect failed: call is not established");
this.hangupHandler({ callId: this.callId, status: ApplicationErrorCode.NETWORK_ERROR });
return;
}
if (!((_a = this.applicationCallOptions) === null || _a === void 0 ? void 0 : _a.autoReconnect)) {
this.logger.debug("Websocket is reconnecting, but the active call doesn't support reconnect. Hanging up...");
this.hangupHandler({ callId: this.callId, status: ApplicationErrorCode.NETWORK_ERROR });
return;
}
this.reconnecting = true;
this.apiEventEmitter.emit(CallsApiEvent.RECONNECTING, {});
});
}
handleReconnected() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
this.logger.info(`Reconnecting call ${this.callId}, restarting ICE...`);
const audioOffer = yield ((_a = this.audioPC) === null || _a === void 0 ? void 0 : _a.restartIce());
this.gateway.send({
action: WsAction.RECONNECT_APPLICATION_CALL,
callId: this.callId,
audioOffer,
});
});
}
handleCallReconnected(event) {
return __awaiter(this, void 0, void 0, function* () {
this.callStatus = CallStatus.ESTABLISHED;
yield this.audioPC.peerConnection.setRemoteDescription(event.description);
yield this.handleApplicationCallChanges(event);
let stateBeforeReconnect = this.resolveCurrentCallState();
let stateAfterReconnect = this.resolveCallStateAfterReconnect(event);
if (stateBeforeReconnect === stateAfterReconnect) {
yield this.handleCallReconnectedToSameState(stateAfterReconnect, event);
yield this.renegotiateMedia(true);
}
else {
yield this.handleCallReconnectedToNewState(stateBeforeReconnect, stateAfterReconnect, event);
yield this.renegotiateMedia(false);
}
this.reconnecting = false;
this.apiEventEmitter.emit(CallsApiEvent.RECONNECTED, {});
});
}
handleDialogFailed(event) {
return __awaiter(this, void 0, void 0, function* () {
this.joinedDialog = false;
this.dialogId = null;
this.changeMonitorConferenceId(null);
this.participants = {};
this.remoteVideos = {};
this.videoPublisherCleanup(true);
this.videoSubscriberCleanup();
this.dataChannelCleanup();
if (this.hasCameraVideo() || this.hasScreenShare()) {
this.mediaUpdateStatus = MediaUpdateStatus.MIGRATING;
yield this.migrateLocalVideos();
}
this.apiEventEmitter.emit(CallsApiEvent.DIALOG_LEFT, { errorCode: event.status });
});
}
videoSubscriberCleanup() {
if (this.videoSubscriberPC) {
this.videoSubscriberPC.close();
this.videoSubscriberPC = null