UNPKG

infobip-rtc

Version:

Infobip RTC JavaScript SDK - Infobip WebRTC API Implementation

1,026 lines 86 kB
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