Your IP : 3.145.177.173


Current Path : /var/www/axolotl/data/www/arhangelsk.axolotls.ru/bitrix/js/im/call/
Upload File :
Current File : /var/www/axolotl/data/www/arhangelsk.axolotls.ru/bitrix/js/im/call/voximplant_call.js

;(function()
{
	/**
	 * Implements Call interface
	 * Public methods:
	 * - inviteUsers
	 * - cancel
	 * - answer
	 * - decline
	 * - hangup
	 *
	 * Events:
	 * - onCallStateChanged //not sure about this.
	 * - onUserStateChanged
	 * - onStreamReceived
	 * - onStreamRemoved
	 * - onDestroy
	 */

	BX.namespace('BX.Call');

	var ajaxActions = {
		invite: 'im.call.invite',
		cancel: 'im.call.cancel',
		answer: 'im.call.answer',
		decline: 'im.call.decline',
		hangup: 'im.call.hangup',
		ping: 'im.call.ping'
	};

	var pullEvents = {
		ping: 'Call::ping',
		hangup: 'Call::hangup',
		userInviteTimeout: 'Call::userInviteTimeout'
	};

	var clientEvents = {
		voiceStarted: 'Call::voiceStarted',
		voiceStopped: 'Call::voiceStopped',
		microphoneState: 'Call::microphoneState'
	};

	var VoximplantCallEvent = {
		onCallConference: 'VoximplantCall::onCallConference'
	};

	var pingPeriod = 5000;
	var backendPingPeriod = 25000;

	var reinvitePeriod = 5500;

	var connectionRestoreTime = 15000;

	// screensharing workaround
	if(window["BXDesktopSystem"])
	{
		navigator['getDisplayMedia'] = function()
		{
			var mediaParams = {
				audio: false,
				video: {
					mandatory: {
						chromeMediaSource: 'desktop',
						maxWidth: screen.width > 1920 ? screen.width : 1920,
						maxHeight: screen.height > 1080 ? screen.height : 1080,
					},
					optional: [{googTemporalLayeredScreencast: true}],
				},
			};
			return navigator.mediaDevices.getUserMedia(mediaParams);
		};
	}

	BX.Call.VoximplantCall = function(config)
	{
		BX.Call.VoximplantCall.superclass.constructor.apply(this, arguments);

		if(!window.VoxImplant)
		{
			throw new Error("Voximplant SDK is not found");
		}

		this.voximplantCall = null;

		this.signaling = new BX.Call.VoximplantCall.Signaling({
			call: this
		});

		this.peers = {};
		this.joinedElsewhere = false;

		this.screenShared = false;
		this.localVideoShown = false;

		this.clientEventsBound = false;

		this.deviceList = [];

		// event handlers
		this.__onLocalDevicesUpdatedHandler = this.__onLocalDevicesUpdated.bind(this);
		this.__onLocalMediaRendererAddedHandler = this.__onLocalMediaRendererAdded.bind(this);
		this.__onBeforeLocalMediaRendererRemovedHandler = this.__onBeforeLocalMediaRendererRemoved.bind(this);


		this.__onCallDisconnectedHandler = this.__onCallDisconnected.bind(this);
		this.__onCallMessageReceivedHandler = this.__onCallMessageReceived.bind(this);
		this.__onCallEndpointAddedHandler = this.__onCallEndpointAdded.bind(this);

		this.initPeers();

		this.pingUsersInterval = setInterval(this.pingUsers.bind(this), pingPeriod);
		this.pingBackendInterval = setInterval(this.pingBackend.bind(this), backendPingPeriod);

		this.lastPingReceivedTimeout = null;
		this.lastSelfPingReceivedTimeout = null;

		this.reinviteTimeout = null;
	};

	BX.extend(BX.Call.VoximplantCall, BX.Call.AbstractCall);

	BX.Call.VoximplantCall.prototype.initPeers = function ()
	{
		this.users.forEach(function(userId)
		{
			userId = Number(userId);
			this.peers[userId] = this.createPeer(userId);
		}, this);
	};

	BX.Call.VoximplantCall.prototype.reinitPeers = function ()
	{
		for (var userId in this.peers)
		{
			if(this.peers.hasOwnProperty(userId) && this.peers[userId])
			{
				this.peers[userId].destroy();
				this.peers[userId] = null;
			}
		}

		this.initPeers();
	};

	BX.Call.VoximplantCall.prototype.pingUsers = function()
	{
		if (this.ready)
		{
			var users = this.users.concat(this.userId);
			this.signaling.sendPingToUsers({userId: users}, true);
		}
	};

	BX.Call.VoximplantCall.prototype.pingBackend = function()
	{
		if (this.ready)
		{
			this.signaling.sendPingToBackend();
		}
	};

	BX.Call.VoximplantCall.prototype.createPeer = function (userId)
	{
		return new BX.Call.VoximplantCall.Peer({
			call: this,
			userId: userId,
			ready: userId == this.initiatorId,

			onStreamReceived: function(e)
			{
				this.runCallback(BX.Call.Event.onStreamReceived, e);
			}.bind(this),
			onStreamRemoved: function(e)
			{
				this.runCallback(BX.Call.Event.onStreamRemoved, e);
			}.bind(this),
			onStateChanged: this.__onPeerStateChanged.bind(this),
			onInviteTimeout: this.__onPeerInviteTimeout.bind(this)
		})
	};

	BX.Call.VoximplantCall.prototype.getUsers = function ()
	{
		var result = {};
		for (var userId in this.peers)
		{
			result[userId] = this.peers[userId].calculatedState;
		}
		return result;
	};

	BX.Call.VoximplantCall.prototype.getClient = function()
	{
		return new Promise(function(resolve, reject)
		{
			BX.Voximplant.getClient().then(function(client)
			{
				client.enableSilentLogging();
				client.setLoggerCallback(function(e)
				{
					this.log(e.label + ": " + e.message);
				}.bind(this));

				this.bindClientEvents();

				resolve(client);
			}.bind(this)).catch(reject);
		}.bind(this));
	};

	BX.Call.VoximplantCall.prototype.bindClientEvents = function()
	{
		var streamManager = VoxImplant.Hardware.StreamManager.get();

		if(!this.clientEventsBound)
		{
			streamManager.on(VoxImplant.Hardware.HardwareEvents.DevicesUpdated, this.__onLocalDevicesUpdatedHandler);
			streamManager.on(VoxImplant.Hardware.HardwareEvents.MediaRendererAdded, this.__onLocalMediaRendererAddedHandler);
			streamManager.on(VoxImplant.Hardware.HardwareEvents.MediaRendererUpdated, this.__onLocalMediaRendererAddedHandler);
			streamManager.on(VoxImplant.Hardware.HardwareEvents.BeforeMediaRendererRemoved, this.__onBeforeLocalMediaRendererRemovedHandler);
			this.clientEventsBound = true;
		}
	};

	BX.Call.VoximplantCall.prototype.removeClientEvents = function()
	{
		var streamManager = VoxImplant.Hardware.StreamManager.get();
		streamManager.off(VoxImplant.Hardware.HardwareEvents.DevicesUpdated, this.__onLocalDevicesUpdatedHandler);
		streamManager.off(VoxImplant.Hardware.HardwareEvents.MediaRendererAdded, this.__onLocalMediaRendererAddedHandler);
		streamManager.off(VoxImplant.Hardware.HardwareEvents.BeforeMediaRendererRemoved, this.__onBeforeLocalMediaRendererRemovedHandler);
		this.clientEventsBound = false;
	};

	BX.Call.VoximplantCall.prototype.setMuted = function(muted)
	{
		if(this.muted == muted)
		{
			return;
		}

		this.muted = muted;

		if(this.voximplantCall)
		{
			if(this.muted)
			{
				this.voximplantCall.muteMicrophone();
			}
			else
			{
				this.voximplantCall.unmuteMicrophone();
			}
			this.signaling.sendMicrophoneState(!this.muted);
		}
	};

	BX.Call.VoximplantCall.prototype.setVideoEnabled = function(videoEnabled)
	{
		videoEnabled = (videoEnabled === true);
		if(this.videoEnabled == videoEnabled)
		{
			return;
		}

		this.videoEnabled = videoEnabled;
		if(this.voximplantCall)
		{
			if(videoEnabled)
			{
				this._showLocalVideo();
			}
			else
			{
				if(this.localVideoShown)
				{
					VoxImplant.Hardware.StreamManager.get().hideLocalVideo().then(function()
					{
						this.localVideoShown = false;
						this.runCallback(BX.Call.Event.onLocalMediaReceived, {
							tag: "main",
							stream: new MediaStream(),
						});
					}.bind(this));
				}
			}

			this.voximplantCall.sendVideo(this.videoEnabled);
		}
	};

	BX.Call.VoximplantCall.prototype.setCameraId = function(cameraId)
	{
		this.cameraId = cameraId;
		var cameraParams = {
			cameraId: this.cameraId,
		};

		if(this.videoResolution)
		{
			cameraParams.frameHeight = this.videoResolution.height;
			cameraParams.frameWidth =  this.videoResolution.width;
		}
		VoxImplant.Hardware.CameraManager.get().getInputDevices().then(function()
		{
			if(this.voximplantCall)
			{
				VoxImplant.Hardware.CameraManager.get().setCallVideoSettings(this.voximplantCall, cameraParams);
			}

			VoxImplant.Hardware.CameraManager.get().setDefaultVideoSettings(cameraParams);
		}.bind(this));
	};

	BX.Call.VoximplantCall.prototype.setMicrophoneId = function(microphoneId)
	{
		if(this.microphoneId == microphoneId)
		{
			return;
		}

		this.microphoneId = microphoneId;
		if(this.voximplantCall)
		{
			VoxImplant.Hardware.AudioDeviceManager.get().getInputDevices().then(function(){
				VoxImplant.Hardware.AudioDeviceManager.get().setCallAudioSettings(this.voximplantCall, {
					inputId: this.microphoneId
				});
			}.bind(this));
		}
	};

	BX.Call.VoximplantCall.prototype.getCurrentMicrophoneId = function()
	{
		if (this.voximplantCall.peerConnection.impl.getTransceivers)
		{
			var transceivers = this.voximplantCall.peerConnection.impl.getTransceivers();
			if(transceivers.length > 0)
			{
				var audioTrack = transceivers[0].sender.track;
				var audioTrackSettings = audioTrack.getSettings();
				return audioTrackSettings.deviceId;
			}
		}
		return this.microphoneId;
	};

	BX.Call.VoximplantCall.prototype.useHdVideo = function(flag)
	{
		this.videoHd = (flag === true);

		if(this.voximplantCall)
		{
			var cameraParams = {
				cameraId: this.cameraId,
				videoQuality: this.videoHd ? VoxImplant.Hardware.VideoQuality.VIDEO_SIZE_HD : VoxImplant.Hardware.VideoQuality.VIDEO_SIZE_nHD
			};
			VoxImplant.Hardware.CameraManager.get().setCallVideoSettings(this.voximplantCall, cameraParams);
		}
	};

	BX.Call.VoximplantCall.prototype.setVideoQuality = function(videoQuality)
	{
		if(this.videoQuality == videoQuality)
		{
			return;
		}
		this.videoQuality = videoQuality;
		this._applyCurrentVideoQuality();
	};

	/**
	 * Sets bitrate cap for outgoing video
	 * @param bitrate
	 */
	BX.Call.VoximplantCall.prototype._setMaxBitrate = function(bitrate)
	{
		if(this.voximplantCall)
		{
			var transceivers = this.voximplantCall.peerConnection.getTransceivers();
			transceivers.forEach(function (tr)
			{
				if(tr.sender && tr.sender.track && tr.sender.track.kind === 'video' && !tr.stoped && tr.currentDirection.indexOf('send') !== -1)
				{
					var sender = tr.sender;
					var parameters = sender.getParameters();
					if (!parameters.encodings)
					{
						parameters.encodings = [{}];
					}
					if(bitrate === 0)
					{
						delete parameters.encodings[0].maxBitrate;
					}
					else
					{
						parameters.encodings[0].maxBitrate = bitrate * 1000;
					}
					sender.setParameters(parameters);
				}
			}, this);
		}
	};

	BX.Call.VoximplantCall.prototype._applyCurrentVideoQuality = function()
	{
		if(!this.voximplantCall)
		{
			return;
		}

		if('RTCRtpSender' in window && 'setParameters' in window.RTCRtpSender.prototype)
		{
			this._setMaxBitrate(BX.Call.Util.getMaxBitrate(this.videoQuality))
		}
		else
		{
			this._useVideoResolution(BX.Call.Util.getMaxResolution(this.videoQuality, this.useHdVideo()));
		}
	};

	BX.Call.VoximplantCall.prototype._useVideoResolution = function(resolution)
	{
		this.videoResolution = resolution;

		if(this.voximplantCall)
		{
			var cameraParams = {
				cameraId: this.cameraId,
				frameHeight: this.videoResolution.height,
				frameWidth: this.videoResolution.width,
			};
			VoxImplant.Hardware.CameraManager.get().setCallVideoSettings(this.voximplantCall, cameraParams);
		}
	};

	BX.Call.VoximplantCall.prototype._showLocalVideo = function()
	{
		return new Promise(function(resolve, reject)
		{
			VoxImplant.Hardware.StreamManager.get().showLocalVideo().then(
				function()
				{
					this.localVideoShown = true;
					resolve();
				}.bind(this),
				function()
				{
					this.localVideoShown = true;
					resolve();
				}.bind(this)
			)
		}.bind(this))
	};

	BX.Call.VoximplantCall.prototype._hideLocalVideo = function()
	{
		return new Promise(function(resolve, reject)
		{
			VoxImplant.Hardware.StreamManager.get().hideLocalVideo().then(
				function()
				{
					this.localVideoShown = false;
					resolve();
				}.bind(this),
				function()
				{
					this.localVideoShown = false;
					resolve();
				}.bind(this)
			);
		})
	};

	BX.Call.VoximplantCall.prototype.startScreenSharing = function()
	{
		if(!this.voximplantCall)
		{
			return;
		}

		var showLocalView = !this.videoEnabled;
		var replaceTrack = this.videoEnabled;

		this.voximplantCall.shareScreen(showLocalView, replaceTrack).then(function()
		{
			this.log("Screen shared");
			this.screenShared = true;
		}.bind(this));
	};

	BX.Call.VoximplantCall.prototype.stopScreenSharing = function()
	{
		if(!this.voximplantCall)
		{
			return;
		}

		this.voximplantCall.stopSharingScreen().then(function()
		{
			this.log("Screen is no longer shared");
			this.screenShared = false;
		}.bind(this));
	};

	BX.Call.VoximplantCall.prototype.isScreenSharingStarted = function()
	{
		return this.screenShared;
	};

	/**
	 * Invites users to participate in the call.
	 *
	 * @param {Object} config
	 * @param {int[]} [config.users] Array of ids of the users to be invited.
	 */
	BX.Call.VoximplantCall.prototype.inviteUsers = function(config)
	{
		var self = this;
		this.ready = true;
		if(!BX.type.isPlainObject(config))
		{
			config = {};
		}
		var users = BX.type.isArray(config.users) ? config.users : this.users;

		this.attachToConference().then(function()
		{
			self.signaling.sendPingToUsers({userId: users});

			return self.signaling.inviteUsers({
				userIds: users,
				video: self.videoEnabled ? 'Y' : 'N'
			})
		}).then(function(response)
		{
			self.runCallback(BX.Call.Event.onJoin, {
				local: true
			});
			for (var i = 0; i < users.length; i++)
			{
				var userId = parseInt(users[i], 10);
				if(!self.users.includes(userId))
				{
					self.users.push(userId);
				}
				if(!self.peers[userId])
				{
					self.peers[userId] = self.createPeer(userId);

					self.runCallback(BX.Call.Event.onUserInvited, {
						userId: userId
					});
				}
				self.peers[userId].onInvited();
				self.scheduleRepeatInvite();
			}
		}).catch(self.onFatalError.bind(self));
	};

	BX.Call.VoximplantCall.prototype.scheduleRepeatInvite = function()
	{
		clearTimeout(this.reinviteTimeout);
		this.reinviteTimeout = setTimeout(this.repeatInviteUsers.bind(this), reinvitePeriod)
	};

	BX.Call.VoximplantCall.prototype.repeatInviteUsers = function()
	{
		clearTimeout(this.reinviteTimeout);
		if(!this.ready)
		{
			return;
		}
		var usersToRepeatInvite = [];

		for (var userId in this.peers)
		{
			if(this.peers.hasOwnProperty(userId) && this.peers[userId].calculatedState === BX.Call.UserState.Calling)
			{
				usersToRepeatInvite.push(userId);
			}
		}

		if(usersToRepeatInvite.length === 0)
		{
			return;
		}
		this.signaling.inviteUsers({
			userIds: usersToRepeatInvite,
			video: this.videoEnabled ? 'Y' : 'N'
		}).then(function()
		{
			this.scheduleRepeatInvite();
		}.bind(this));
	};

	/**
	 * @param {Object} config
	 * @param {bool} [config.useVideo]
	 */
	BX.Call.VoximplantCall.prototype.answer = function(config)
	{
		this.ready = true;
		if(!BX.type.isPlainObject(config))
		{
			config = {};
		}
		this.videoEnabled = (config.useVideo == true);

		this.signaling.sendAnswer();
		this.attachToConference().then(function ()
		{
			this.log("Attached to conference");
			this.runCallback(BX.Call.Event.onJoin, {
				local: true
			});
		}.bind(this)).catch(this.onFatalError.bind(this));
	};

	BX.Call.VoximplantCall.prototype.decline = function(code)
	{
		this.ready = false;
		var data = {
			callId: this.id,
			callInstanceId: this.instanceId,
		};
		if(code)
		{
			data.code = code
		}

		BX.CallEngine.getRestClient().callMethod(ajaxActions.decline, data);
	};

	BX.Call.VoximplantCall.prototype.hangup = function(code, reason)
	{
		if(!this.ready)
		{
			var error = new Error("Hangup in wrong state");
			this.log(error);
			return;
		}

		var tempError = new Error();
		tempError.name = "Call stack:";
		this.log("Hangup received \n" + tempError.stack);

		var data = {};
		this.ready = false;
		if(typeof(code) != 'undefined')
		{
			data.code = code;
		}
		if(typeof(reason) != 'undefined')
		{
			data.reason = reason;
		}
		this.runCallback(BX.Call.Event.onLeave, {local: true});

		data.userId = this.users;
		this.signaling.sendHangup(data);
		this.muted = false;

		// for future reconnections
		this.reinitPeers();

		if(this.voximplantCall)
		{
			this.voximplantCall._replaceVideoSharing = false;
			try
			{
				this.voximplantCall.hangup();
			}
			catch (e)
			{
				console.error(e);
			}
		}

		this.screenShared = false;
		this._hideLocalVideo();
	};

	BX.Call.VoximplantCall.prototype.attachToConference = function()
	{
		var self = this;

		// workaround to set default video settings before starting call. ugly, but I do not see another way
		var cameraParams = {};
		if (this.cameraId)
		{
			cameraParams.cameraId = this.cameraId;
		}
		cameraParams.videoQuality = this.videoHd ? VoxImplant.Hardware.VideoQuality.VIDEO_SIZE_HD : VoxImplant.Hardware.VideoQuality.VIDEO_SIZE_nHD;
		VoxImplant.Hardware.CameraManager.get().setDefaultVideoSettings(cameraParams);
		if (this.microphoneId)
		{
			VoxImplant.Hardware.AudioDeviceManager.get().setDefaultAudioSettings({
				inputId: this.microphoneId
			});
		}

		return new Promise(function(resolve, reject)
		{
			if(self.voximplantCall && self.voximplantCall.state() === "CONNECTED")
			{
				return resolve();
			}

			self.getClient().then(function(voximplantClient)
			{
				if(self.videoEnabled)
				{
					self._showLocalVideo();
				}

				try
				{
					self.voximplantCall = voximplantClient.callConference({
						number: "bx_conf_" + self.id,
						video: {sendVideo: self.videoEnabled, receiveVideo: true},
						customData: {}
					});
				}
				catch (e)
				{
					console.error(e);
					return reject(e);
				}

				if(!self.voximplantCall)
				{
					self.log("Error: could not create voximplant call");
					return reject({code: "VOX_NO_CALL"});
				}

				self.runCallback(BX.Call.VoximplantCall.Event.onCallConference, {
					call: self
				});

				self.bindCallEvents();

				var onCallConnected = function()
				{
					self.log("Call connected");
					self.voximplantCall.removeEventListener(VoxImplant.CallEvents.Connected, onCallConnected);
					self.voximplantCall.removeEventListener(VoxImplant.CallEvents.Failed, onCallFailed);

					self.voximplantCall.addEventListener(VoxImplant.CallEvents.Failed, self.__onCallDisconnectedHandler);

					if(self.deviceList.length === 0)
					{
						navigator.mediaDevices.enumerateDevices().then(function(deviceList)
						{
							self.deviceList = deviceList;
							self.runCallback(BX.Call.Event.onDeviceListUpdated, {
								deviceList: self.deviceList
							})
						});
					}
					else
					{
						self.runCallback(BX.Call.Event.onDeviceListUpdated, {
							deviceList: self.deviceList
						})
					}
					if(self.muted)
					{
						self.voximplantCall.muteMicrophone();
					}
					self.signaling.sendMicrophoneState(!self.muted);

					setTimeout(function()
					{
						self._applyCurrentVideoQuality();
					}, 1000);

					resolve();
				};

				var onCallFailed = function(e)
				{
					self.log("Could not attach to conference", e);
					self.voximplantCall.removeEventListener(VoxImplant.CallEvents.Connected, onCallConnected);
					self.voximplantCall.removeEventListener(VoxImplant.CallEvents.Failed, onCallFailed);

					var client = VoxImplant.getInstance();
					client.enableSilentLogging(false);
					client.setLoggerCallback(null);

					reject(e);
				};

				self.voximplantCall.addEventListener(VoxImplant.CallEvents.Connected, onCallConnected);
				self.voximplantCall.addEventListener(VoxImplant.CallEvents.Failed, onCallFailed);
			}).catch(self.onFatalError.bind(self));
		});
	};

	BX.Call.VoximplantCall.prototype.bindCallEvents = function()
	{
		this.voximplantCall.addEventListener(VoxImplant.CallEvents.Disconnected, this.__onCallDisconnectedHandler);
		this.voximplantCall.addEventListener(VoxImplant.CallEvents.MessageReceived, this.__onCallMessageReceivedHandler);

		this.voximplantCall.addEventListener(VoxImplant.CallEvents.EndpointAdded, this.__onCallEndpointAddedHandler);
	};

	BX.Call.VoximplantCall.prototype.removeCallEvents = function()
	{
		if(this.voximplantCall)
		{
			this.voximplantCall.removeEventListener(VoxImplant.CallEvents.Disconnected, this.__onCallDisconnectedHandler);
			this.voximplantCall.removeEventListener(VoxImplant.CallEvents.MessageReceived, this.__onCallMessageReceivedHandler);
			this.voximplantCall.removeEventListener(VoxImplant.CallEvents.EndpointAdded, this.__onCallEndpointAddedHandler);
		}
	};

	/**
	 * Adds users, invited by you or someone else
	 * @param {Number[]} users
	 */
	BX.Call.VoximplantCall.prototype.addInvitedUsers = function(users)
	{
		for(var i = 0; i < users.length; i++)
		{
			var userId = Number(users[i]);
			if(userId == this.userId)
			{
				continue;
			}

			if(this.peers[userId])
			{
				if(this.peers[userId].calculatedState === BX.Call.UserState.Failed || this.peers[userId].calculatedState === BX.Call.UserState.Idle)
				{
					this.peers[userId].onInvited();
				}
			}
			else
			{
				this.peers[userId] = this.createPeer(userId);
				this.runCallback(BX.Call.Event.onUserInvited, {
					userId: userId
				});
				this.peers[userId].onInvited();
			}
			if(!this.users.includes(userId))
			{
				this.users.push(userId);
			}
		}
	};

	BX.Call.VoximplantCall.prototype.isAnyoneParticipating = function()
	{
		for (var userId in this.peers)
		{
			if(this.peers[userId].isParticipating())
			{
				return true;
			}
		}

		return false;
	};

	BX.Call.VoximplantCall.prototype.__onPeerStateChanged = function(e)
	{
		this.runCallback(BX.Call.Event.onUserStateChanged, e);

		if(!this.ready)
		{
			return;
		}
		if(e.state == BX.Call.UserState.Failed || e.state == BX.Call.UserState.Unavailable || e.state == BX.Call.UserState.Declined || e.state == BX.Call.UserState.Idle)
		{
			if(!this.isAnyoneParticipating())
			{
				this.hangup();
			}
		}
	};

	BX.Call.VoximplantCall.prototype.__onPeerInviteTimeout = function(e)
	{
		if(!this.ready)
		{
			return;
		}
		this.signaling.sendUserInviteTimeout({
			userId: this.users,
			failedUserId: e.userId
		})
	};

	BX.Call.VoximplantCall.prototype.__onPullEvent = function(command, params, extra)
	{
		var handlers = {
			'Call::answer': this.__onPullEventAnswer.bind(this),
			'Call::hangup': this.__onPullEventHangup.bind(this),
			'Call::usersInvited': this.__onPullEventUsersInvited.bind(this),
			'Call::userInviteTimeout': this.__onPullEventUserInviteTimeout.bind(this),
			'Call::ping': this.__onPullEventPing.bind(this),
			'Call::finish': this.__onPullEventFinish.bind(this)
		};

		if(handlers[command])
		{
			handlers[command].call(this, params);
		}
	};

	BX.Call.VoximplantCall.prototype.__onPullEventAnswer = function(params)
	{
		var senderId = Number(params.senderId);

		if(senderId == this.userId)
		{
			return this.__onPullEventAnswerSelf(params);
		}

		if(!this.peers[senderId])
		{
			this.peers[senderId] = this.createPeer(senderId);
			this.runCallback(BX.Call.Event.onUserInvited, {
				userId: senderId
			});
		}

		if(!this.users.includes(senderId))
		{
			this.users.push(senderId);
		}

		this.peers[senderId].setReady(true);
	};

	BX.Call.VoximplantCall.prototype.__onPullEventAnswerSelf = function(params)
	{
		if(params.callInstanceId === this.instanceId)
		{
			return;
		}

		// call was answered elsewhere
		this.joinedElsewhere = true;
		this.runCallback(BX.Call.Event.onJoin, {
			local: false
		});
	};


	BX.Call.VoximplantCall.prototype.__onPullEventHangup = function(params)
	{
		var senderId = params.senderId;

		if(this.userId == senderId && this.instanceId != params.callInstanceId)
		{
			// Call declined by the same user elsewhere
			this.runCallback(BX.Call.Event.onLeave, {local: false});
			return;
		}

		if(!this.peers[senderId])
			return;

		this.peers[senderId].setReady(false);

		if(params.code == 603)
		{
			this.peers[senderId].setDeclined(true);
		}
		else if (params.code == 486)
		{
			this.peers[senderId].setBusy(true);
			console.error("user " + senderId + " is busy");
		}

		if(this.ready && !this.isAnyoneParticipating())
		{
			this.hangup();
		}
	};

	BX.Call.VoximplantCall.prototype.__onPullEventUsersInvited = function(params)
	{
		this.log('__onPullEventUsersInvited', params);
		var users = params.users;

		this.addInvitedUsers(users);
	};

	BX.Call.VoximplantCall.prototype.__onPullEventUserInviteTimeout = function(params)
	{
		this.log('__onPullEventUserInviteTimeout', params);
		var failedUserId = params.failedUserId;

		if(this.peers[failedUserId])
		{
			this.peers[failedUserId].onInviteTimeout(false);
		}
	};

	BX.Call.VoximplantCall.prototype.__onPullEventPing = function(params)
	{
		if(params.callInstanceId == this.instanceId)
		{
			// ignore self ping
			return;
		}

		if (params.senderId == this.userId)
		{
			if (!this.joinedElsewhere)
			{
				this.runCallback(BX.Call.Event.onJoin, {
					local: false
				});
				this.joinedElsewhere = true;
			}
			clearTimeout(this.lastSelfPingReceivedTimeout);
			this.lastSelfPingReceivedTimeout = setTimeout(this.__onNoSelfPingsReceived.bind(this), pingPeriod * 2.1);
		}
		clearTimeout(this.lastPingReceivedTimeout);
		this.lastPingReceivedTimeout = setTimeout(this.__onNoPingsReceived.bind(this), pingPeriod * 2.1)
	};

	BX.Call.VoximplantCall.prototype.__onNoPingsReceived = function()
	{
		if(!this.ready)
		{
			this.destroy();
		}
	};

	BX.Call.VoximplantCall.prototype.__onNoSelfPingsReceived = function()
	{
		this.runCallback(BX.Call.Event.onLeave, {
			local: false
		});
		this.joinedElsewhere = false;
	};

	BX.Call.VoximplantCall.prototype.__onPullEventFinish = function(params)
	{
		this.destroy();
	};

	BX.Call.VoximplantCall.prototype.__onLocalDevicesUpdated = function(e)
	{
		this.log("__onLocalDevicesUpdated", e);
	};

	BX.Call.VoximplantCall.prototype.__onLocalMediaRendererAdded = function(e)
	{
		var renderer = e.renderer;
		var trackLabel = renderer.stream.getVideoTracks().length > 0 ? renderer.stream.getVideoTracks()[0].label : "";
		this.log("__onLocalMediaRendererAdded", renderer.kind, trackLabel);

		if(renderer.kind === "video")
		{
			if (trackLabel.match(/^screen|window|tab/i))
			{
				var tag = "screen";
			}
			else
			{
				tag = "main";
			}

			this.runCallback(BX.Call.Event.onLocalMediaReceived, {
				tag: tag,
				stream: renderer.stream,
			});
		}
		else if (renderer.kind === "sharing")
		{
			this.runCallback(BX.Call.Event.onLocalMediaReceived, {
				tag: "screen",
				stream: renderer.stream,
			});
		}
	};

	BX.Call.VoximplantCall.prototype.__onBeforeLocalMediaRendererRemoved = function(e)
	{
		var renderer = e.renderer;
		this.log("__onBeforeLocalMediaRendererRemoved", renderer.kind);

		if(renderer.kind === "sharing" && !this.videoEnabled)
		{
			this.runCallback(BX.Call.Event.onLocalMediaReceived, {
				tag: "main",
				stream: new MediaStream(),
			});
		}
	};

	BX.Call.VoximplantCall.prototype.__onCallDisconnected = function(e)
	{
		this.log("__onCallDisconnected", e);

		this.ready = false;
		this.muted = false;
		this.reinitPeers();

		this._hideLocalVideo();
		this.removeCallEvents();
		this.voximplantCall = null;

		var client = VoxImplant.getInstance();
		client.enableSilentLogging(false);
		client.setLoggerCallback(null);

		this.runCallback(BX.Call.Event.onLeave, {
			local: true
		});
	};

	BX.Call.VoximplantCall.prototype.onFatalError = function(error)
	{
		if(error && error.call)
		{
			delete error.call;
		}
		this.log("onFatalError", error);

		this.ready = false;
		this.muted = false;
		this.reinitPeers();

		this._hideLocalVideo().then(function()
		{
			if(this.voximplantCall)
			{
				this.removeCallEvents();
				try
				{
					this.voximplantCall.hangup({
						'X-Reason': 'Fatal error',
						'X-Error': typeof(error) === 'string' ? error : error.code || error.name
					})
				}
				catch (e)
				{ /* nothing :) */ }
				this.voximplantCall = null;
			}

			var client = VoxImplant.getInstance();
			client.enableSilentLogging(false);
			client.setLoggerCallback(null);

			if(typeof(error) === "string")
			{
				this.runCallback(BX.Call.Event.onCallFailure, {
					name: error
				});
			}
			else if(error.name)
			{
				this.runCallback(BX.Call.Event.onCallFailure, error);
			}
		}.bind(this))
	};

	BX.Call.VoximplantCall.prototype.__onCallEndpointAdded = function(e)
	{
		var endpoint = e.endpoint;
		var userName = endpoint.userName;
		this.log("__onCallEndpointAdded (" + userName + ")", e.endpoint);

		if(BX.type.isNotEmptyString(userName) && userName.substr(0, 4) == 'user')
		{
			// user connected to conference
			var userId = parseInt(userName.substr(4));
			if(this.peers[userId])
			{
				this.peers[userId].setEndpoint(endpoint);
			}
		}
		else
		{
			endpoint.addEventListener(VoxImplant.EndpointEvents.InfoUpdated, function(e)
			{
				var endpoint = e.endpoint;
				var userName = endpoint.userName;
				this.log("VoxImplant.EndpointEvents.InfoUpdated (" + userName + ")", e.endpoint);

				if(BX.type.isNotEmptyString(userName) && userName.substr(0, 4) == 'user')
				{
					// user connected to conference
					var userId = parseInt(userName.substr(4));
					if(this.peers[userId])
					{
						this.peers[userId].setEndpoint(endpoint);
					}
				}
			}.bind(this));

			this.log('Unknown endpoint ' + userName);
		}

		this.setVideoQuality(BX.CallEngine.getAllowedVideoQuality(this.voximplantCall.getEndpoints().length));
	};

	BX.Call.VoximplantCall.prototype.__onCallMessageReceived = function(e)
	{
		var message;

		try
		{
			message = JSON.parse(e.text);
		}
		catch(err)
		{
			this.log("Could not parse scenario message.", err);
			return;
		}

		var eventName = message.eventName;
		if(eventName === clientEvents.voiceStarted)
		{
			this.runCallback(BX.Call.Event.onUserVoiceStarted, {
				userId: message.senderId
			});
		}
		else if(eventName === clientEvents.voiceStopped)
		{
			this.runCallback(BX.Call.Event.onUserVoiceStopped, {
				userId: message.senderId
			});
		}
		else if (eventName === clientEvents.microphoneState)
		{
			this.runCallback(BX.Call.Event.onUserMicrophoneState, {
				userId: message.senderId,
				microphoneState: message.microphoneState === "Y"
			});
		}
		else
		{
			this.log("Unknown scenario event " + eventName);
		}
	};

	BX.Call.VoximplantCall.prototype.destroy = function()
	{
		this.ready = false;
		this._hideLocalVideo();
		if(this.voximplantCall)
		{
			this.removeCallEvents();
			if(this.voximplantCall.state() != "ENDED")
			{
				this.voximplantCall.hangup();
			}
			this.voximplantCall = null;
		}

		for(var userId in this.peers)
		{
			if(this.peers.hasOwnProperty(userId) && this.peers[userId])
			{
				this.peers[userId].destroy();
			}
		}

		this.removeClientEvents();

		clearTimeout(this.lastPingReceivedTimeout);
		clearTimeout(this.lastSelfPingReceivedTimeout);
		clearInterval(this.pingUsersInterval);
		clearInterval(this.pingBackendInterval);
		this.runCallback(BX.Call.Event.onDestroy);
	};

	BX.Call.VoximplantCall.Signaling = function(params)
	{
		this.call = params.call;
	};

	BX.Call.VoximplantCall.Signaling.prototype.inviteUsers = function(data)
	{
		return this.__runRestAction(ajaxActions.invite, data);
	};

	BX.Call.VoximplantCall.Signaling.prototype.sendAnswer = function(data)
	{
		return this.__runRestAction(ajaxActions.answer, data);
	};

	BX.Call.VoximplantCall.Signaling.prototype.sendCancel = function(data)
	{
		return this.__runRestAction(ajaxActions.cancel, data);
	};

	BX.Call.VoximplantCall.Signaling.prototype.sendHangup = function(data)
	{
		if(BX.PULL.isPublishingEnabled())
		{
			this.__sendPullEvent(pullEvents.hangup, data);
			data.retransmit = false;
			this.__runRestAction(ajaxActions.hangup, data);
		}
		else
		{
			data.retransmit = true;
			this.__runRestAction(ajaxActions.hangup, data);
		}
	};

	BX.Call.VoximplantCall.Signaling.prototype.sendVoiceStarted = function(data)
	{
		return this.__sendMessage(clientEvents.voiceStarted, data);
	};

	BX.Call.VoximplantCall.Signaling.prototype.sendVoiceStopped = function(data)
	{
		return this.__sendMessage(clientEvents.voiceStopped, data);
	};

	BX.Call.VoximplantCall.Signaling.prototype.sendMicrophoneState = function(microphoneState)
	{
		return this.__sendMessage(clientEvents.microphoneState, {
			microphoneState: microphoneState ? "Y" : "N"
		});
	};


	BX.Call.VoximplantCall.Signaling.prototype.sendPingToUsers = function(data)
	{
		if (BX.PULL.isPublishingEnabled())
		{
			this.__sendPullEvent(pullEvents.ping, data, 0);
		}
	};

	BX.Call.VoximplantCall.Signaling.prototype.sendPingToBackend = function()
	{
		this.__runRestAction(ajaxActions.ping, {retransmit: false});
	};

	BX.Call.VoximplantCall.Signaling.prototype.sendUserInviteTimeout = function(data)
	{
		if (BX.PULL.isPublishingEnabled())
		{
			this.__sendPullEvent(pullEvents.userInviteTimeout, data, 0);
		}
	};

	BX.Call.VoximplantCall.Signaling.prototype.__sendPullEvent = function(eventName, data, expiry)
	{
		expiry = expiry || 5;
		if(!data.userId)
		{
			throw new Error('userId is not found in data');
		}

		if(!BX.type.isArray(data.userId))
		{
			data.userId = [data.userId];
		}
		data.callInstanceId = this.call.instanceId;
		data.senderId = this.call.userId;
		data.callId = this.call.id;
		data.requestId = BX.Call.Engine.getInstance().getUuidv4();

		this.call.log('Sending p2p signaling event ' + eventName + '; ' + JSON.stringify(data));
		BX.PULL.sendMessage(data.userId, 'im', eventName, data, expiry);
	};

	BX.Call.VoximplantCall.Signaling.prototype.__sendMessage = function(eventName, data)
	{
		if(!this.call.voximplantCall)
		{
			return;
		}

		if(!BX.type.isPlainObject(data))
		{
			data = {};
		}
		data.eventName = eventName;
		data.requestId = BX.Call.Engine.getInstance().getUuidv4();

		this.call.voximplantCall.sendMessage(JSON.stringify(data));
	};

	BX.Call.VoximplantCall.Signaling.prototype.__runRestAction = function(signalName, data)
	{
		if(!BX.type.isPlainObject(data))
		{
			data = {};
		}

		data.callId = this.call.id;
		data.callInstanceId = this.call.instanceId;
		data.requestId = BX.Call.Engine.getInstance().getUuidv4();
		return BX.CallEngine.getRestClient().callMethod(signalName, data);
	};

	BX.Call.VoximplantCall.Peer = function(params)
	{
		this.userId = params.userId;
		this.call = params.call;

		this.ready = !!params.ready;
		this.calling = false;
		this.declined = false;
		this.busy = false;
		this.inviteTimeout = false;
		this.endpoint = null;

		this.stream = null;

		this.tracks = {
			audio: null,
			video: null,
			sharing: null
		};

		this.callingTimeout = 0;
		this.connectionRestoreTimeout = 0;

		this.callbacks = {
			onStateChanged: BX.type.isFunction(params.onStateChanged) ? params.onStateChanged : BX.DoNothing,
			onInviteTimeout: BX.type.isFunction(params.onInviteTimeout) ? params.onInviteTimeout : BX.DoNothing,
			onStreamReceived: BX.type.isFunction(params.onStreamReceived) ? params.onStreamReceived : BX.DoNothing,
			onStreamRemoved: BX.type.isFunction(params.onStreamRemoved) ? params.onStreamRemoved : BX.DoNothing
		};

		// event handlers
		this.__onEndpointRemoteMediaAddedHandler = this.__onEndpointRemoteMediaAdded.bind(this);
		this.__onEndpointRemoteMediaRemovedHandler = this.__onEndpointRemoteMediaRemoved.bind(this);
		this.__onEndpointRemovedHandler = this.__onEndpointRemoved.bind(this);

		this.calculatedState = this.calculateState();
	};

	BX.Call.VoximplantCall.Peer.prototype = {

		setReady: function(ready)
		{
			ready = !!ready;
			if (this.ready == ready)
			{
				return;
			}
			this.ready = ready;
			if(this.calling)
			{
				clearTimeout(this.callingTimeout);
				this.calling = false;
				this.inviteTimeout = false;
			}
			if(this.ready)
			{
				this.declined = false;
				this.busy = false;
			}
			else
			{
				clearTimeout(this.connectionRestoreTimeout);
			}

			this.updateCalculatedState();
		},

		setDeclined: function(declined)
		{
			this.declined = declined;
			if(this.calling)
			{
				clearTimeout(this.callingTimeout);
				this.calling = false;
			}
			if(this.declined)
			{
				this.ready = false;
				this.busy = false;
			}
			clearTimeout(this.connectionRestoreTimeout);
			this.updateCalculatedState();
		},

		setBusy: function(busy)
		{
			this.busy = busy;
			if(this.calling)
			{
				clearTimeout(this.callingTimeout);
				this.calling = false;
			}
			if(this.busy)
			{
				this.ready = false;
				this.declined = false;
			}
			clearTimeout(this.connectionRestoreTimeout);
			this.updateCalculatedState();
		},

		setEndpoint: function(endpoint)
		{
			this.log("Adding endpoint with " + endpoint.mediaRenderers.length + " media renderers");

			this.setReady(true);
			this.inviteTimeout = false;
			this.declined = false;
			clearTimeout(this.connectionRestoreTimeout);

			if(this.endpoint)
			{
				this.removeEndpointEventHandlers();
				this.endpoint = null;
			}

			this.endpoint = endpoint;

			for(var i = 0; i < this.endpoint.mediaRenderers.length; i++)
			{
				this.addMediaRenderer(this.endpoint.mediaRenderers[i]);
				if(this.endpoint.mediaRenderers[i].element)
				{
					BX.remove(this.endpoint.mediaRenderers[i].element);
				}
			}

			this.bindEndpointEventHandlers();
		},

		addMediaRenderer: function(mediaRenderer)
		{
			this.log('Adding media renderer');
			if(!this.stream)
			{
				this.stream = new MediaStream();
			}

			mediaRenderer.stream.getTracks().forEach(function(track)
			{
				if (track.kind == "audio")
				{
					this.tracks.audio = track;
				}
				else if (track.kind == "video")
				{
					if(mediaRenderer.kind == "sharing")
					{
						this.tracks.sharing = track;
					}
					else
					{
						this.tracks.video = track;
					}
				}
				else
				{
					this.log("Unknown track kind " + track.kind);
				}

			}, this);

			this.updateMediaStream();
			this.updateCalculatedState();
		},

		updateMediaStream: function()
		{
			if(!this.stream)
			{
				this.stream = new MediaStream();
			}

			this.stream.getTracks().forEach(function(track)
			{
				if(!this.hasTrack(track))
				{
					this.stream.removeTrack(track);
				}
			}, this);

			if(this.tracks.audio && !this.stream.getTrackById(this.tracks.audio.id))
			{
				this.stream.addTrack(this.tracks.audio);
			}

			if(this.tracks.sharing)
			{
				if(this.tracks.video && this.stream.getTrackById(this.tracks.video.id))
				{
					this.stream.removeTrack(this.tracks.video);
				}

				if(!this.stream.getTrackById(this.tracks.sharing.id))
				{
					this.stream.addTrack(this.tracks.sharing);
				}
			}
			else
			{
				if (this.tracks.video && !this.stream.getTrackById(this.tracks.video.id))
				{
					this.stream.addTrack(this.tracks.video);
				}
			}

			this.callbacks.onStreamReceived({
				userId: this.userId,
				stream: this.stream
			});
		},

		hasTrack: function(track)
		{
			for (var kind in this.tracks)
			{
				if (!this.tracks.hasOwnProperty(kind))
				{
					continue;
				}

				if(this.tracks.kind && this.tracks.kind.id == track.id)
				{
					return true;
				}
			}

			return false;
		},

		removeTrack: function(track)
		{
			for (var kind in this.tracks)
			{
				if (!this.tracks.hasOwnProperty(kind))
				{
					continue;
				}

				var localTrackId = this.tracks[kind] ? this.tracks[kind].id : '';
				if(localTrackId == track.id)
				{
					this.tracks[kind] = null;
				}
			}
		},

		bindEndpointEventHandlers: function()
		{
			this.endpoint.addEventListener(VoxImplant.EndpointEvents.RemoteMediaAdded, this.__onEndpointRemoteMediaAddedHandler);
			this.endpoint.addEventListener(VoxImplant.EndpointEvents.RemoteMediaRemoved, this.__onEndpointRemoteMediaRemovedHandler);
			this.endpoint.addEventListener(VoxImplant.EndpointEvents.Removed, this.__onEndpointRemovedHandler);
		},

		removeEndpointEventHandlers: function()
		{
			this.endpoint.removeEventListener(VoxImplant.EndpointEvents.RemoteMediaAdded, this.__onEndpointRemoteMediaAddedHandler);
			this.endpoint.removeEventListener(VoxImplant.EndpointEvents.RemoteMediaRemoved, this.__onEndpointRemoteMediaRemovedHandler);
			this.endpoint.removeEventListener(VoxImplant.EndpointEvents.Removed, this.__onEndpointRemovedHandler);
		},

		calculateState: function()
		{
			if(this.stream)
				return BX.Call.UserState.Connected;

			if(this.endpoint)
				return BX.Call.UserState.Connecting;

			if(this.calling)
				return BX.Call.UserState.Calling;

			if(this.inviteTimeout)
				return BX.Call.UserState.Unavailable;

			if(this.declined)
				return BX.Call.UserState.Declined;

			if(this.busy)
				return BX.Call.UserState.Busy;

			if(this.ready)
				return BX.Call.UserState.Ready;

			return BX.Call.UserState.Idle;
		},

		updateCalculatedState: function()
		{
			var calculatedState = this.calculateState();

			if(this.calculatedState != calculatedState)
			{
				this.callbacks.onStateChanged({
					userId: this.userId,
					state: calculatedState,
					previousState: this.calculatedState
				});
				this.calculatedState = calculatedState;
			}
		},

		isParticipating: function()
		{
			return ((this.calling || this.ready || this.endpoint) && !this.declined);
		},

		waitForConnectionRestore: function()
		{
			clearTimeout(this.connectionRestoreTimeout);
			this.connectionRestoreTimeout = setTimeout(
				this.onConnectionRestoreTimeout.bind(this),
				connectionRestoreTime
			);
		},

		onInvited: function()
		{
			this.ready = false;
			this.inviteTimeout = false;
			this.declined = false;
			this.calling = true;

			clearTimeout(this.connectionRestoreTimeout);
			if(this.callingTimeout)
			{
				clearTimeout(this.callingTimeout);
			}
			this.callingTimeout = setTimeout(function()
			{
				this.onInviteTimeout(true);
			}.bind(this), 30000);
			this.updateCalculatedState();
		},

		onInviteTimeout: function(internal)
		{
			clearTimeout(this.callingTimeout);
			if(!this.calling)
			{
				return;
			}
			this.calling = false;
			this.inviteTimeout = true;
			if(internal)
			{
				this.callbacks.onInviteTimeout({
					userId: this.userId
				});
			}
			this.updateCalculatedState();
		},

		onConnectionRestoreTimeout: function()
		{
			if(this.endpoint || !this.ready)
			{
				return;
			}

			this.log("Done waiting for connection restoration");
			this.setReady(false);
		},

		__onEndpointRemoteMediaAdded: function(e)
		{
			this.log("VoxImplant.EndpointEvents.RemoteMediaAdded", e);

			// voximplant audio auto-play bug workaround:
			if(e.mediaRenderer.element)
			{
				e.mediaRenderer.element.volume = 0;
				e.mediaRenderer.element.srcObject = null;
			}
			this.addMediaRenderer(e.mediaRenderer);
		},

		__onEndpointRemoteMediaRemoved: function(e)
		{
			this.log("VoxImplant.EndpointEvents.RemoteMediaRemoved, track id: " + e.mediaRenderer.stream.getTracks()[0].id, e);

			e.mediaRenderer.stream.getTracks().forEach(function(track)
			{
				this.removeTrack(track);
			}, this);

			if(this.stream)
			{
				this.updateMediaStream();
			}

			this.updateCalculatedState();
		},

		__onEndpointRemoved: function(e)
		{
			this.log("VoxImplant.EndpointEvents.Removed", e);

			if(this.endpoint)
			{
				this.removeEndpointEventHandlers();
				this.endpoint = null;
			}
			if(this.stream)
			{
				this.stream = null;
			}
			for(var kind in this.tracks)
			{
				if(this.tracks.hasOwnProperty(kind))
				{
					this.tracks[kind] = null;
				}
			}

			if(this.ready)
			{
				this.waitForConnectionRestore();
			}

			this.updateCalculatedState();
		},

		log: function()
		{
			this.call.log.apply(this.call, arguments);
		},

		destroy: function()
		{
			if(this.stream)
			{
				this.stream.getTracks().forEach(function(track)
				{
					track.stop();
				});
				this.stream = null;
			}
			if(this.endpoint)
			{
				this.removeEndpointEventHandlers();
				this.endpoint = null;
			}
			for(var kind in this.tracks)
			{
				if(this.tracks.hasOwnProperty(kind))
				{
					if(this.tracks[kind] && this.tracks[kind].stop)
					{
						this.tracks[kind].stop();
					}
					this.tracks[kind] = null;
				}
			}

			this.callbacks['onStateChanged'] = BX.DoNothing;
			this.callbacks['onStreamReceived'] = BX.DoNothing;
			this.callbacks['onStreamRemoved'] = BX.DoNothing;

			clearTimeout(this.callingTimeout);
			clearTimeout(this.connectionRestoreTimeout);
			this.callingTimeout = null;
			this.connectionRestoreTimeout = null;
		}
	};

	BX.Call.VoximplantCall.Event = VoximplantCallEvent;
})();