Your IP : 18.224.32.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/controller.js

;(function()
{
	BX.namespace("BX.Call");
	if(BX.Call.Controller)
	{
		return;
	}

	var USER_LIMIT_FOR_HD = 5;

	BX.Call.Controller = function(config)
	{
		this.messenger = config.messenger;

		this.container = null;

		this.folded = false;

		this.currentCall = null;
		this.childCall = null;
		this.callView = null;
		this.callNotification = null;
		this.invitePopup = null;

		this.isHttps = window.location.protocol === "https:";
		this.callWithMobile = false;

		this.autoCloseCallView = true;

		this.talkingUsers = {};

		// event handlers
		this._onCallUserInvitedHandler = this._onCallUserInvited.bind(this);
		this._onCallDestroyHandler = this._onCallDestroy.bind(this);
		this._onCallUserStateChangedHandler = this._onCallUserStateChanged.bind(this);
		this._onCallUserMicrophoneStateHandler = this._onCallUserMicrophoneState.bind(this);
		this._onCallLocalMediaReceivedHandler = this._onCallLocalMediaReceived.bind(this);
		this._onCallLocalMediaStoppedHandler = this._onCallLocalMediaStopped.bind(this);
		this._onCallUserStreamReceivedHandler = this._onCallUserStreamReceived.bind(this);
		this._onCallUserStreamRemovedHandler = this._onCallUserStreamRemoved.bind(this);
		this._onCallUserVoiceStartedHandler = this._onCallUserVoiceStarted.bind(this);
		this._onCallUserVoiceStoppedHandler = this._onCallUserVoiceStopped.bind(this);
		this._onCallFailureHandler = this._onCallFailure.bind(this);
		this._onCallLeaveHandler = this._onCallLeave.bind(this);
		this._onCallJoinHandler = this._onCallJoin.bind(this);

		this._onBeforeUnloadHandler = this._onBeforeUnload.bind(this);
		this._onImTabChangeHandler = this._onImTabChange.bind(this);
		this._onUpdateChatCounterHandler = this._onUpdateChatCounter.bind(this);


		this._onChildCallFirstStreamHandler = this._onChildCallFirstStream.bind(this);

		this._onWindowFocusHandler = this._onWindowFocus.bind(this);
		this._onWindowBlurHandler = this._onWindowBlur.bind(this);

		if(BX.desktop)
		{
			this.floatingWindow = new BX.Call.FloatingVideo({
				onMainAreaClick: this._onFloatingVideoMainAreaClick.bind(this),
				onButtonClick: this._onFloatingVideoButtonClick.bind(this)
			});
			this.floatingWindowUser = 0;
		}
		this.showFloatingWindowTimeout = 0;
		this.hideIncomingCallTimeout = 0;

		this.init();
	};

	BX.Call.Controller.prototype = {
		init: function()
		{
			BX.addCustomEvent(window, "CallEvents::incomingCall", this.onIncomingCall.bind(this));
			BX.addCustomEvent(BX.Call.Hardware, "HardwareManager::deviceChange", this._onDeviceChange.bind(this));

			if(BX.desktop)
			{
				window.addEventListener("blur", this._onWindowBlurHandler);
				window.addEventListener("focus", this._onWindowFocusHandler);

				BX.desktop.addCustomEvent("BXForegroundChanged", function(focus)
				{
					if(focus)
					{
						this._onWindowFocus();
					}
					else
					{
						this._onWindowBlur();
					}
				}.bind(this));
			}

			if(window['VoxImplant'])
			{
				VoxImplant.getInstance().addEventListener(VoxImplant.Events.MicAccessResult, this.voxMicAccessResult.bind(this));
			}

			window.addEventListener("beforeunload", this._onBeforeUnloadHandler);
			BX.addCustomEvent("OnDesktopTabChange", this._onImTabChangeHandler);

			BX.addCustomEvent(window, "onImUpdateCounterMessage", this._onUpdateChatCounter.bind(this));

			BX.garbage(this.destroy, this);
		},

		/**
		 * Workaround to get current microphoneId
		 * @param e
		 */
		voxMicAccessResult: function(e)
		{
			if(e.stream && e.stream.getAudioTracks().length > 0 && this.callView)
			{
				this.callView.microphoneId = e.stream.getAudioTracks()[0].getSettings().deviceId
			}
		},

		getActiveCallUsers: function()
		{
			var activeUsers = [];
			var userStates = this.currentCall.getUsers();

			for (var userId in userStates)
			{
				if(userStates.hasOwnProperty(userId))
				{
					if(userStates[userId] === BX.Call.UserState.Connected || userStates[userId] === BX.Call.UserState.Connecting || userStates[userId] === BX.Call.UserState.Calling)
					{
						activeUsers.push(userId);
					}
				}
			}
			return activeUsers;
		},

		updateFloatingWindowContent: function()
		{
			if(!this.floatingWindow || !this.currentCall)
			{
				return;
			}
			this.floatingWindow.setTitle(this.currentCall.associatedEntity.name);

			BX.Call.Util.getUserAvatars(this.getActiveCallUsers()).then(function(result)
			{
				this.floatingWindow.setAvatars(result);
			}.bind(this));
		},

		onIncomingCall: function(e)
		{
			var self = this;
			/** @var {BX.Call.AbstractCall} newCall */
			var newCall = e.call;
			this.callWithMobile = (e.isMobile === true);

			var isCurrentCallActive = this.currentCall && (this.callView || this.callNotification);

			if(!isCurrentCallActive)
			{
				if(this.callView)
				{
					return;
				}

				this.checkDesktop().then(function()
				{
					 return BX.Call.Hardware.init();
				}).then(
					function()
					{
						this.currentCall = newCall;
						this.bindCallEvents();
						this.updateFloatingWindowContent();

						var video = e.video === true;
						if(video && !BX.Call.Hardware.hasCamera())
						{
							this.showNotification(BX.message('IM_CALL_ERROR_NO_CAMERA'));
							video = false;
						}

						this.showIncomingCall({
							video: video
						});
					}.bind(this),
					function(error)
					{
						if(self.currentCall)
						{
							self.removeCallEvents();
							self.currentCall = null;
						}
						console.error(error);
						self.log(error);
						if(!self.isHttps)
						{
							self.showNotification(BX.message("IM_CALL_INCOMING_ERROR_HTTPS_REQUIRED"))
						}
						else
						{
							self.showNotification(BX.message("IM_CALL_INCOMING_UNSUPPORTED_BROWSER"))
						}
					}
				);
			}
			else
			{
				if(newCall.id == this.currentCall.id)
				{
					// ignoring
				}
				else if(newCall.parentId == this.currentCall.id)
				{
					if(!this.childCall)
					{
						this.childCall = newCall;
						this.childCall.users.forEach(function(userId)
						{
							this.callView.addUser(userId, BX.Call.UserState.Calling);
						}, this);

						this.answerChildCall();
					}
				}
				else
				{
					// send busy
					newCall.decline(486);
					return false;
				}
			}
		},

		bindCallEvents: function()
		{
			this.currentCall.addEventListener(BX.Call.Event.onUserInvited, this._onCallUserInvitedHandler);
			this.currentCall.addEventListener(BX.Call.Event.onDestroy, this._onCallDestroyHandler);
			this.currentCall.addEventListener(BX.Call.Event.onUserStateChanged, this._onCallUserStateChangedHandler);
			this.currentCall.addEventListener(BX.Call.Event.onUserMicrophoneState, this._onCallUserMicrophoneStateHandler);
			this.currentCall.addEventListener(BX.Call.Event.onLocalMediaReceived, this._onCallLocalMediaReceivedHandler);
			this.currentCall.addEventListener(BX.Call.Event.onLocalMediaStopped, this._onCallLocalMediaStoppedHandler);
			this.currentCall.addEventListener(BX.Call.Event.onStreamReceived, this._onCallUserStreamReceivedHandler);
			this.currentCall.addEventListener(BX.Call.Event.onStreamRemoved, this._onCallUserStreamRemovedHandler);
			this.currentCall.addEventListener(BX.Call.Event.onUserVoiceStarted, this._onCallUserVoiceStartedHandler);
			this.currentCall.addEventListener(BX.Call.Event.onUserVoiceStopped, this._onCallUserVoiceStoppedHandler);
			this.currentCall.addEventListener(BX.Call.Event.onCallFailure, this._onCallFailureHandler);
			this.currentCall.addEventListener(BX.Call.Event.onJoin, this._onCallJoinHandler);
			this.currentCall.addEventListener(BX.Call.Event.onLeave, this._onCallLeaveHandler);
		},

		removeCallEvents: function()
		{
			this.currentCall.removeEventListener(BX.Call.Event.onUserInvited, this._onCallUserInvitedHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onDestroy, this._onCallDestroyHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onUserStateChanged, this._onCallUserStateChangedHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onLocalMediaReceived, this._onCallLocalMediaReceivedHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onLocalMediaStopped, this._onCallLocalMediaStoppedHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onStreamReceived, this._onCallUserStreamReceivedHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onStreamRemoved, this._onCallUserStreamRemovedHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onUserVoiceStarted, this._onCallUserVoiceStartedHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onUserVoiceStopped, this._onCallUserVoiceStoppedHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onCallFailure, this._onCallFailureHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onJoin, this._onCallJoinHandler);
			this.currentCall.removeEventListener(BX.Call.Event.onLeave, this._onCallLeaveHandler);
		},

		bindCallViewEvents: function()
		{
			this.callView.setCallback('onClose', this._onCallViewClose.bind(this));
			this.callView.setCallback('onDestroy', this._onCallViewDestroy.bind(this));
			this.callView.setCallback('onButtonClick', this._onCallViewButtonClick.bind(this));
			this.callView.setCallback('onBodyClick', this._onCallViewBodyClick.bind(this));
			this.callView.setCallback('onReplaceCamera', this._onCallViewReplaceCamera.bind(this));
			this.callView.setCallback('onReplaceMicrophone', this._onCallViewReplaceMicrophone.bind(this));
			this.callView.setCallback('onSetCentralUser', this._onCallViewSetCentralUser.bind(this));
		},

		isMessengerOpen: function()
		{
			return !!this.messenger.popupMessenger;
		},

		createContainer: function()
		{
			this.container = BX.create("div", {
				props: {className: "bx-messenger-call-overlay"},
			});

			if(BX.MessengerWindow)
			{
				BX.MessengerWindow.content.insertBefore(this.container, BX.MessengerWindow.content.firstChild);
			}
			else
			{
				this.messenger.popupMessengerContent.insertBefore(this.container, this.messenger.popupMessengerContent.firstChild);
			}

			this.messenger.popupMessengerContent.classList.add("bx-messenger-call");
		},

		resize: function()
		{

		},

		answerChildCall: function()
		{
			this.removeCallEvents();
			this.childCall.addEventListener(BX.Call.Event.onStreamReceived, this._onChildCallFirstStreamHandler);
			this.childCall.addEventListener(BX.Call.Event.onLocalMediaReceived, this._onCallLocalMediaReceivedHandler);

			this.childCall.answer({
				useVideo: this.currentCall.isVideoEnabled()
			});
		},

		_onChildCallFirstStream: function(e)
		{
			this.log("Finishing one-to-one call, switching to group call");
			this.callView.setStream(e.userId, e.stream);
			this.childCall.removeEventListener(BX.Call.Event.onStreamReceived, this._onChildCallFirstStreamHandler);

			this.removeCallEvents();
			var oldCall = this.currentCall;
			oldCall.hangup();

			this.currentCall = this.childCall;
			this.childCall = null;

			if(this.currentCall.associatedEntity && this.currentCall.associatedEntity.id)
			{
				this.messenger.openMessenger(this.currentCall.associatedEntity.id);
			}

			if(oldCall.muted)
			{
				this.currentCall.setMuted(true);
			}

			this.bindCallEvents();
		},

		checkDesktop: function()
		{
			return new Promise(function(resolve)
			{
				BX.desktopUtils.runningCheck(
					function(){},
					function()
					{
						resolve()
					}
				);
			});
		},

		/**
		 * @param {Object} params
		 * @param {bool} [params.video = false]
		 */
		showIncomingCall: function(params)
		{
			if(!BX.type.isPlainObject(params))
			{
				params = {};
			}
			params.video = params.video == true;

			var allowVideo = this.callWithMobile ? params.video === true : true;

			this.callNotification = new BX.Call.Notification({
				callerName: this.currentCall.associatedEntity.name,
				callerAvatar: this.currentCall.associatedEntity.avatar,
				video: params.video,
				hasCamera: BX.Call.Hardware.hasCamera() && allowVideo,
				onClose: this._onCallNotificationClose.bind(this),
				onDestroy: this._onCallNotificationDestroy.bind(this),
				onButtonClick: this._onCallNotificationButtonClick.bind(this)
			}) ;

			this.callNotification.show();
			clearTimeout(this.hideIncomingCallTimeout);
			this.hideIncomingCallTimeout = setTimeout(function()
			{
				if(this.callNotification)
				{
					this.callNotification.close();
				}
				if(this.currentCall)
				{
					this.removeCallEvents();
					this.currentCall = null;
				}
			}.bind(this), 30 * 1000);

			window.BXIM.repeatSound('ringtone', 3500, true);
		},

		showNotification: function(notificationText)
		{
			BX.UI.Notification.Center.notify({
				content: BX.util.htmlspecialchars(notificationText),
				position: "top-right",
				autoHideDelay: 5000,
				closeButton: true
			});
		},

		showUnsupportedNotification: function()
		{
			if (BX.desktop && BX.desktop.apiReady)
			{
				window.BXIM.openConfirm(
					BX.message('IM_CALL_DESKTOP_TOO_OLD'),
					[
						updateButton = new BX.PopupWindowButton({
							text: BX.message('IM_M_CALL_BTN_UPDATE'),
							className: "popup-window-button-accept",
							events: {
								click: function ()
								{
									window.open(BX.browser.IsMac() ? "http://dl.bitrix24.com/b24/bitrix24_desktop.dmg": "http://dl.bitrix24.com/b24/bitrix24_desktop.exe", "desktopApp");
									this.popupWindow.close();
								}
							}
						}),
						new BX.PopupWindowButton({
							text: BX.message('IM_NOTIFY_CONFIRM_CLOSE'),
							className: "popup-window-button-decline",
							events: {
								click: function ()
								{
									this.popupWindow.close();
								}
							}
						})
					]
				);
			}
			else
			{
				window.BXIM.openConfirm(
					BX.message("IM_CALL_WEBRTC_USE_CHROME_OR_DESKTOP"),
					[
						new BX.PopupWindowButton({
							text: BX.message("IM_CALL_DETAILS"),
							className: "popup-window-button-accept",
							events: {
								click: function ()
								{
									this.popupWindow.close();
									//https://helpdesk.bitrix24.ru/open/11387752/
									top.BX.Helper.show("redirect=detail&code=11387752");
								}
							}
						}),
						new BX.PopupWindowButton({
							text: BX.message('IM_NOTIFY_CONFIRM_CLOSE'),
							className: "popup-window-button-decline",
							events: {
								click: function ()
								{
									this.popupWindow.close();
								}
							}
						}),
					]
				);
			}
		},

		isUserAgentSupported: function()
		{
			if (BX.desktop && BX.desktop.apiReady)
			{
				return BX.desktop.enableInVersion(48);
			}
			if ('VoxImplant' in window)
			{
				return VoxImplant.getInstance().isRTCsupported();
			}
			return BX.Call.Util.isWebRTCSupported();
		},

		startCall: function(dialogId, video)
		{
			if(!this.isUserAgentSupported())
			{
				this.showUnsupportedNotification();
				return;
			}

			if(this.callView || this.currentCall)
			{
				return;
			}

			var provider = BX.Call.Provider.Plain;
			if(BX.Call.Util.isCallServerAllowed() && dialogId.toString().substr(0, 4) === "chat")
			{
				provider = BX.Call.Provider.Voximplant;
			}

			var debug1 = +(new Date());
			this._openMessenger(dialogId).then(function()
			{
				return BX.Call.Hardware.init();
			}.bind(this)).then(function()
			{
				this.createContainer();

				this.callView = new BX.Call.View({
					container: this.container,
					showChatButtons: true,
					userLimit: BX.Call.Util.getUserLimit(),
					language: window.BXIM.language,
					layout: dialogId.toString().startsWith("chat") ? BX.Call.View.Layout.Grid : BX.Call.View.Layout.Centered,
					microphoneId: BX.Call.Hardware.defaultMicrophone
				});
				this.bindCallViewEvents();

				if(video && !BX.Call.Hardware.hasCamera())
				{
					this.showNotification(BX.message('IM_CALL_ERROR_NO_CAMERA'));
					video = false;
				}

				return BX.Call.Engine.getInstance().createCall({
					entityType: 'chat',
					entityId: dialogId,
					provider: provider,
					videoEnabled: !!video,
					enableMicAutoParameters: BX.Call.Hardware.enableMicAutoParameters,
					joinExisting: dialogId.toString().startsWith("chat")
				});
			}.bind(this)).then(function(e)
			{
				var debug2 = +(new Date());
				this.currentCall = e.call;

				this.log("Call creation time: " + (debug2 - debug1) / 1000 + " seconds");

				this.currentCall.useHdVideo(BX.Call.Hardware.preferHdQuality);
				if(BX.Call.Hardware.defaultMicrophone)
				{
					this.currentCall.setMicrophoneId(BX.Call.Hardware.defaultMicrophone);
				}
				if(BX.Call.Hardware.defaultCamera)
				{
					this.currentCall.setCameraId(BX.Call.Hardware.defaultCamera);
				}

				if(this.currentCall.associatedEntity && this.currentCall.associatedEntity.id)
				{
					if(this.messenger.currentTab != this.currentCall.associatedEntity.id)
					{
						this.messenger.openMessenger(this.currentCall.associatedEntity.id);
					}
				}

				this.autoCloseCallView = true;
				this.bindCallEvents();

				this.callView.appendUsers(this.currentCall.getUsers());
				this.callView.show();

				if (e.isNew)
				{
					this.log("Inviting users");
					this.currentCall.inviteUsers();
					window.BXIM.repeatSound('dialtone', 5000, true);
				}
				else
				{
					this.log("Joining existing call");
					this.currentCall.answer({
						useVideo: video
					});
				}
			}.bind(this)).catch(function(error)
			{
				console.error(error);

				var errorCode;
				if (typeof(error) == "string")
				{
					errorCode = error;
				}
				else if (typeof(error) == "object" && error.code)
				{
					errorCode = error.code == 'access_denied' ? 'ACCESS_DENIED' : error.code
				}
				else
				{
					errorCode = 'UNKNOWN_ERROR';
				}
				this._onCallFailure({
					code: errorCode,
					message: error.message || "",
				})

			}.bind(this));
		},

		joinCall: function(callId, video)
		{
			if(!this.isUserAgentSupported())
			{
				this.showUnsupportedNotification();
				return;
			}

			if(this.callView || this.currentCall)
			{
				return;
			}

			var isGroupCall;

			this.log("Joining call " + callId);
			BX.CallEngine.getCallWithId(callId).then(function(result)
			{
				this.currentCall = result.call;
				isGroupCall = this.currentCall.associatedEntity.id.toString().startsWith("chat");
				return this.messenger.openMessenger();
			}.bind(this)).then(function()
			{
				return BX.Call.Hardware.init();
			}.bind(this)).then(function()
			{
				this.createContainer();

				this.callView = new BX.Call.View({
					container: this.container,
					showChatButtons: true,
					userLimit: BX.Call.Util.getUserLimit(),
					language: window.BXIM.language,
					layout: isGroupCall ? BX.Call.View.Layout.Grid : BX.Call.View.Layout.Centered,
					microphoneId: BX.Call.Hardware.defaultMicrophone
				});
				this.autoCloseCallView = true;
				this.bindCallViewEvents();
				this.callView.appendUsers(this.currentCall.getUsers());
				this.callView.show();

				this.currentCall.useHdVideo(BX.Call.Hardware.preferHdQuality);
				if(BX.Call.Hardware.defaultMicrophone)
				{
					this.currentCall.setMicrophoneId(BX.Call.Hardware.defaultMicrophone);
				}
				if(BX.Call.Hardware.defaultCamera)
				{
					this.currentCall.setCameraId(BX.Call.Hardware.defaultCamera);
				}

				this.bindCallEvents();

				if(video && !BX.Call.Hardware.hasCamera())
				{
					this.showNotification(BX.message('IM_CALL_ERROR_NO_CAMERA'));
					video = false;
				}

				this.currentCall.answer({
					useVideo: !!video
				});
			}.bind(this));
		},

		leaveCurrentCall: function()
		{
			if(this.callView)
			{
				this.callView.releaseLocalMedia();
			}

			if(this.currentCall)
			{
				this.currentCall.hangup();
			}
			if(this.callView)
			{
				this.callView.close();
			}
		},

		hasActiveCall: function()
		{
			return (this.currentCall != null && this.currentCall.isAnyoneParticipating()) || (this.callView != null);
		},

		hasVisibleCall: function()
		{
			return !!(this.callView && this.callView.visible && this.callView.size == BX.Call.View.Size.Full);
		},

		useDevicesInCurrentCall: function(deviceList)
		{
			if(!this.currentCall || !this.currentCall.ready)
			{
				return;
			}

			for (var i = 0; i < deviceList.length; i++)
			{
				var deviceInfo = deviceList[i];

				switch (deviceInfo.kind)
				{
					case "audioinput":
						this.currentCall.setMicrophoneId(deviceInfo.deviceId);
						break;
					case "videoinput":
						this.currentCall.setCameraId(deviceInfo.deviceId);
						break;
					case "audiooutput":
						if(this.callView)
						{
							this.callView.setSpeakerId(deviceInfo.deviceId);
						}
						break;
				}
			}
		},

		removeDevicesFromCurrentCall: function(deviceList)
		{
			if(!this.currentCall || !this.currentCall.ready)
			{
				return;
			}

			for (var i = 0; i < deviceList.length; i++)
			{
				var deviceInfo = deviceList[i];

				switch (deviceInfo.kind)
				{
					case "audioinput":
						if(this.currentCall.microphoneId == deviceInfo.deviceId)
						{
							this.currentCall.setMicrophoneId("");
						}
						break;
					case "videoinput":
						if(this.currentCall.cameraId == deviceInfo.deviceId)
						{
							this.currentCall.setCameraId("");
						}
						break;
					case "audiooutput":
						if(this.callView && this.callView.speakerId == deviceInfo.deviceId)
						{
							this.callView.setSpeakerId("");
						}
						break;
				}
			}
		},

		showChat: function()
		{
			if(BX.desktop)
			{
				this.detached = true;
				this.callView.hide();
				this.floatingWindow.setTitle(this.currentCall.associatedEntity.name);
				BX.Call.Util.getUserAvatars(this.getActiveCallUsers()).then(function(result)
				{
					this.floatingWindow.setAvatars(result);
					this.floatingWindow.show();
				}.bind(this));

				this.container.style.width = 0;
			}
			else
			{
				this.fold();
			}
		},

		fold: function()
		{
			if(this.folded || BX.desktop)
			{
				return;
			}

			this.folded = true;
			this.container.classList.add('bx-messenger-call-overlay-folded');
			this.callView.setTitle(this.currentCall.associatedEntity.name);
			this.callView.setSize(BX.Call.View.Size.Folded);
			BX.onCustomEvent(this, "CallController::onFold", {});
		},

		unfold: function()
		{
			if(this.detached)
			{
				this.container.style.removeProperty('width');
				this.callView.show();
				this.detached = false;
				if(this.floatingWindow)
				{
					this.floatingWindow.hide();
				}
			}
			if(this.folded)
			{
				this.folded = false;
				this.container.classList.remove('bx-messenger-call-overlay-folded');
				this.callView.setSize(BX.Call.View.Size.Full);
			}
			BX.onCustomEvent(this, "CallController::onUnfold", {});
		},

		// converter from BX.Promise to normal Promise
		_openMessenger: function(dialogId)
		{
			return new Promise(function (resolve, reject)
			{
				this.messenger.openMessenger(dialogId).then(function()
				{
					resolve();
				}).catch(function(e)
				{
					reject(e);
				})
			}.bind(this))
		},

		// event handlers

		_onCallNotificationClose: function(e)
		{
			clearTimeout(this.hideIncomingCallTimeout);
			window.BXIM.stopRepeatSound('ringtone');
			if(this.callNotification)
			{
				this.callNotification.destroy();
			}
		},

		_onCallNotificationDestroy: function(e)
		{
			this.callNotification = null;
		},

		_onCallNotificationButtonClick: function(e)
		{
			clearTimeout(this.hideIncomingCallTimeout);
			this.callNotification.close();
			switch (e.button)
			{
				case "answer":
					if(BX.desktop)
					{
						BX.desktop.windowCommand("show");
					}

					if(!this.isUserAgentSupported())
					{
						this.log("Error: unsupported user agent");
						this.removeCallEvents();
						this.currentCall.decline();
						this.currentCall = null;

						this.showUnsupportedNotification();
						return;
					}

					if(this.callView)
					{
						this.callView.destroy();
					}

					var dialogId = this.currentCall.associatedEntity && this.currentCall.associatedEntity.id ? this.currentCall.associatedEntity.id : false;
					var isGroupCall = dialogId.toString().startsWith("chat");
					this.messenger.openMessenger(dialogId).then(function()
					{
						this.createContainer();
						this.callView = new BX.Call.View({
							container: this.container,
							users: this.currentCall.users,
							userStates: this.currentCall.getUsers(),
							showChatButtons: true,
							userLimit: BX.Call.Util.getUserLimit(),
							layout: isGroupCall ? BX.Call.View.Layout.Grid : BX.Call.View.Layout.Centered,
							microphoneId: BX.Call.Hardware.defaultMicrophone
						});
						this.autoCloseCallView = true;
						if(this.callWithMobile)
						{
							this.callView.disableAddUser();
						}

						this.bindCallViewEvents();
						this.callView.show();

						this.currentCall.useHdVideo(BX.Call.Hardware.preferHdQuality);
						if(BX.Call.Hardware.defaultMicrophone)
						{
							this.currentCall.setMicrophoneId(BX.Call.Hardware.defaultMicrophone);
						}
						if(BX.Call.Hardware.defaultCamera)
						{
							this.currentCall.setCameraId(BX.Call.Hardware.defaultCamera);
						}

						this.currentCall.answer({
							useVideo: e.video,
							enableMicAutoParameters: BX.Call.Hardware.enableMicAutoParameters
						});

					}.bind(this));

					break;
				case "decline":
					if(this.currentCall)
					{
						this.removeCallEvents();
						this.currentCall.decline();
						this.currentCall = null;
					}
					break;
			}
		},

		_onCallViewClose: function(e)
		{
			this.callView.destroy();
			if(this.floatingWindow)
			{
				this.floatingWindow.close();
			}
		},

		_onCallViewDestroy: function(e)
		{
			if(this.messenger.popupMessengerContent)
			{
				BX.remove(this.container);
			}

			this.messenger.popupMessengerContent.classList.remove("bx-messenger-call");

			this.container = null;
			this.callView = null;
			this.folded = false;
			this.autoCloseCallView = true;

			if(this.currentCall)
			{
				this.currentCall.hangup();
			}
		},

		_onCallViewBodyClick: function(e)
		{
			if(this.folded)
			{
				this.unfold();
			}
		},

		_onCallViewButtonClick: function(e)
		{
			var buttonName = e.buttonName;

			var handlers = {
				hangup: this._onCallViewHangupButtonClick.bind(this),
				close: this._onCallViewCloseButtonClick.bind(this),
				inviteUser: this._onCallViewInviteUserButtonClick.bind(this),
				toggleMute: this._onCallViewToggleMuteButtonClick.bind(this),
				toggleScreenSharing: this._onCallViewToggleScreenSharingButtonClick.bind(this),
				toggleVideo: this._onCallViewToggleVideoButtonClick.bind(this),
				showChat: this._onCallViewShowChatButtonClick.bind(this),
				showHistory: this._onCallViewShowHistoryButtonClick.bind(this),
				fullscreen: this._onCallViewFullScreenButtonClick.bind(this),
			};

			if(BX.type.isFunction(handlers[buttonName]))
			{
				handlers[buttonName].call(this, e);
			}
		},

		_onCallViewHangupButtonClick: function(e)
		{
			this.leaveCurrentCall();
		},

		_onCallViewCloseButtonClick: function(e)
		{
			if(this.callView)
			{
				this.callView.close();
			}
		},

		_onCallViewShowChatButtonClick: function(e)
		{
			this.messenger.openMessenger(this.currentCall.associatedEntity.id);
			this.showChat();
		},

		/**
		 * Returns list of users, that are not currently connected
		 * @return {Array}
		 * @private
		 */
		_getDisconnectedUsers: function()
		{
			var result = [];
			var userStates = this.currentCall.getUsers();

			for(var userId in userStates)
			{
				if(userStates[userId] !== BX.Call.UserState.Connected && BX.Call.Util.userData[userId])
				{
					result.push(BX.Call.Util.userData[userId]);
				}
			}

			return result;
		},

		_onCallViewInviteUserButtonClick: function(e)
		{
			if(this.invitePopup)
			{
				this.invitePopup.close();
				return;
			}

			var userStates = this.currentCall.getUsers();
			var idleUsers = this._getDisconnectedUsers();

			this.invitePopup = new BX.Call.InvitePopup({
				bindElement: e.node,
				zIndex: BX.MessengerCommon.getDefaultZIndex() + 200,
				idleUsers: idleUsers,
				allowNewUsers: Object.keys(userStates).length < BX.Call.Util.getUserLimit() - 1,
				onDestroy: this._onInvitePopupDestroy.bind(this),
				onSelect: this._onInvitePopupSelect.bind(this)
			});

			this.invitePopup.show();
		},

		_onCallViewToggleMuteButtonClick: function(e)
		{
			this.currentCall.setMuted(e.muted);
			this.callView.setMuted(e.muted);
			if(this.floatingWindow)
			{
				this.floatingWindow.setAudioMuted(e.muted);
			}
		},

		_onCallViewToggleScreenSharingButtonClick: function(e)
		{
			if(this.currentCall.isScreenSharingStarted())
			{
				this.currentCall.stopScreenSharing();
			}
			else
			{
				this.callView.releaseLocalMedia();
				this.currentCall.startScreenSharing();
			}
		},

		_onCallViewToggleVideoButtonClick: function(e)
		{
			if(!e.video)
			{
				this.callView.releaseLocalMedia();
			}
			this.currentCall.setVideoEnabled(e.video);
		},

		_onCallViewShowHistoryButtonClick: function(e)
		{
			this.messenger.openHistory(this.currentCall.associatedEntity.id);
		},

		_onCallViewFullScreenButtonClick: function(e)
		{
			if(this.folded)
			{
				this.unfold();
			}
			this.callView.toggleFullScreen();
		},

		_onCallViewReplaceCamera: function(e)
		{
			if(this.currentCall)
			{
				this.currentCall.setCameraId(e.deviceId);
			}

			// maybe update default camera
		},

		_onCallViewReplaceMicrophone: function(e)
		{
			if(this.currentCall)
			{
				this.currentCall.setMicrophoneId(e.deviceId)
			}

			if(this.currentCall instanceof BX.Call.VoximplantCall)
			{
				this.callView.microphoneId = e.deviceId;
			}

			// maybe update default microphone
		},

		_onCallViewSetCentralUser: function(e)
		{
			if(e.stream && this.floatingWindow)
			{
				this.floatingWindowUser = e.userId;
				//this.floatingWindow.setStream(e.stream);
			}
		},

		_onCallUserInvited: function(e)
		{
			if(this.callView)
			{
				this.callView.addUser(e.userId);
			}
		},

		_onCallDestroy: function(e)
		{
			if(this.currentCall)
			{
				this.removeCallEvents();
				this.currentCall = null;
			}
			this.callWithMobile = false;

			if(this.callNotification)
			{
				this.callNotification.close();
			}
			if(this.invitePopup)
			{
				this.invitePopup.close();
			}
			if(this.callView && this.autoCloseCallView)
			{
				this.callView.close();
			}
			if(this.floatingWindow)
			{
				this.floatingWindow.close();
			}

			window.BXIM.messenger.dialogStatusRedraw();
			window.BXIM.stopRepeatSound('dialtone');
			window.BXIM.stopRepeatSound('ringtone');
		},

		_onCallUserStateChanged: function(e)
		{
			setTimeout(this.updateFloatingWindowContent.bind(this), 100);
			if(this.callView)
			{
				this.callView.setUserState(e.userId, e.state);
				if(e.isMobile)
				{
					this.callView.disableAddUser();
					this.callView.disableSwitchCamera();
					this.callView.disableScreenSharing();
					this.callView.disableMediaSelection();
					this.callView.updateButtons();
				}
			}

			if(e.state == BX.Call.UserState.Connecting || e.state == BX.Call.UserState.Connected)
			{
				window.BXIM.stopRepeatSound('dialtone');
			}
			if(e.state == BX.Call.UserState.Connected)
			{
				/*BX.Call.Util.getUser(e.userId).then(function(userData)
				{
					this.showNotification(BX.Call.Util.getCustomMessage("IM_M_CALL_USER_CONNECTED", {
						gender: userData.gender,
						name: userData.name
					}));
				}.bind(this));*/
			}
			else if(e.state == BX.Call.UserState.Idle && e.previousState == BX.Call.UserState.Connected)
			{
				/*BX.Call.Util.getUser(e.userId).then(function(userData)
				{
					this.showNotification(BX.Call.Util.getCustomMessage("IM_M_CALL_USER_DISCONNECTED", {
						gender: userData.gender,
						name: userData.name
					}));
				}.bind(this));*/
			}
			else if(e.state == BX.Call.UserState.Failed)
			{
				BX.Call.Util.getUser(e.userId).then(function(userData)
				{
					this.showNotification(BX.Call.Util.getCustomMessage("IM_M_CALL_USER_FAILED", {
						gender: userData.gender,
						name: userData.name
					}));
				}.bind(this));
			}
			else if(e.state == BX.Call.UserState.Declined)
			{
				BX.Call.Util.getUser(e.userId).then(function(userData)
				{
					this.showNotification(BX.Call.Util.getCustomMessage("IM_M_CALL_USER_DECLINED", {
						gender: userData.gender,
						name: userData.name
					}));
				}.bind(this));
			}
			else if(e.state == BX.Call.UserState.Busy)
			{
				BX.Call.Util.getUser(e.userId).then(function(userData)
				{
					this.showNotification(BX.Call.Util.getCustomMessage("IM_M_CALL_USER_BUSY", {
						gender: userData.gender,
						name: userData.name
					}));
				}.bind(this));
			}
		},

		_onCallUserMicrophoneState: function(e)
		{
			if(!this.callView)
			{
				return;
			}
			this.callView.setUserMicrophoneState(e.userId, e.microphoneState);
		},

		_onCallLocalMediaReceived: function(e)
		{
			this.log("Received local media stream " + e.tag);
			if(this.callView)
			{
				this.callView.setLocalStream(e.stream, e.tag == "main");
				this.callView.setButtonActive("screen", e.tag == "screen");
				if(e.tag == "screen")
				{
					this.callView.disableSwitchCamera();
					this.callView.updateButtons();
				}
				else
				{
					if(!this.currentCall.callFromMobile)
					{
						this.callView.enableSwitchCamera();
						this.callView.updateButtons();
					}
				}
			}

			if(this.currentCall && this.currentCall.videoEnabled && e.stream.getVideoTracks().length === 0)
			{
				this.showNotification(BX.message("IM_CALL_CAMERA_ERROR_FALLBACK_TO_MIC"));
			}
		},

		_onCallLocalMediaStopped: function(e)
		{
			// do nothing
		},

		_onCallUserStreamReceived: function(e)
		{
			if(this.callView)
			{
				this.callView.setStream(e.userId, e.stream);
			}

			if(this.floatingWindow && this.floatingWindowUser == e.userId)
			{
				//this.floatingWindow.setStream(e.stream);
			}
		},

		_onCallUserStreamRemoved: function(e)
		{
			// this is never used, i believe
		},

		_onCallUserVoiceStarted: function(e)
		{
			this.talkingUsers[e.userId] = true;
			if(this.callView)
			{
				this.callView.setUserTalking(e.userId, true);
			}
			if(this.floatingWindow)
			{
				this.floatingWindow.setTalking(Object.keys(this.talkingUsers).map(function(id){
					return Number(id);
				}));
			}
		},

		_onCallUserVoiceStopped: function(e)
		{
			if(this.talkingUsers[e.userId])
			{
				delete this.talkingUsers[e.userId];
			}
			if(this.callView)
			{
				this.callView.setUserTalking(e.userId, false);
			}
			if(this.floatingWindow)
			{
				this.floatingWindow.setTalking(Object.keys(this.talkingUsers).map(function(id){
					return Number(id);
				}));
			}
		},

		_onCallFailure: function(e)
		{
			var errorCode = e.code || e.name || e.error;
			console.error("Call failure: ", e);

			var errorMessage;

			if(e.name == "AuthResult" || errorCode == "AUTHORIZE_ERROR")
			{
				errorMessage = BX.message("IM_CALL_ERROR_AUTHORIZATION");
			}
			else if(e.name == "Failed" && errorCode == 403)
			{
				errorMessage = BX.message("IM_CALL_ERROR_HARDWARE_ACCESS_DENIED");
			}
			else if(errorCode == "ERROR_UNEXPECTED_ANSWER")
			{
				errorMessage = BX.message("IM_CALL_ERROR_UNEXPECTED_ANSWER");
			}
			else if(errorCode == "BLANK_ANSWER_WITH_ERROR_CODE")
			{
				errorMessage = BX.message("IM_CALL_ERROR_BLANK_ANSWER");
			}
			else if(errorCode == "BLANK_ANSWER")
			{
				errorMessage = BX.message("IM_CALL_ERROR_BLANK_ANSWER");
			}
			else if(errorCode == "ACCESS_DENIED")
			{
				errorMessage = BX.message("IM_CALL_ERROR_ACCESS_DENIED");
			}
			else if(errorCode == "NO_WEBRTC")
			{
				errorMessage = this.isHttps ? BX.message("IM_CALL_NO_WEBRT") : BX.message("IM_CALL_ERROR_HTTPS_REQUIRED");
			}
			else if(errorCode == "UNKNOWN_ERROR")
			{
				errorMessage = BX.message("IM_CALL_ERROR_UNKNOWN");
			}
			else
			{
				//errorMessage = BX.message("IM_CALL_ERROR_HARDWARE_ACCESS_DENIED");
				errorMessage = BX.message("IM_CALL_ERROR_UNKNOWN_WITH_CODE")
					.replace("#ERROR_CODE#", errorCode);
			}
			if(this.callView)
			{
				this.callView.showFatalError({text: errorMessage});
			}
			else
			{
				this.showNotification(errorMessage);
			}

			this.autoCloseCallView = false;

			if(this.currentCall)
			{
				this.removeCallEvents();
				this.currentCall = null;
			}
		},

		_onCallJoin: function(e)
		{
			if(e.local)
			{
				// self answer, no nothing
				return;
			}
			// remote answer, stop ringing and hide incoming cal notification
			if(this.currentCall)
			{
				this.removeCallEvents();
				this.currentCall = null;
			}

			if(this.callView)
			{
				this.callView.close();
			}

			if(this.callNotification)
			{
				this.callNotification.close();
			}

			if(this.invitePopup)
			{
				this.invitePopup.close();
			}

			if(this.floatingWindow)
			{
				this.floatingWindow.close();
			}

			window.BXIM.stopRepeatSound('dialtone');
		},

		_onCallLeave: function(e)
		{
			if (!e.local && this.currentCall && this.currentCall.ready)
			{
				this.log(new Error("received remote leave with active call!"));
				return;
			}
			if(this.currentCall)
			{
				this.removeCallEvents();
				this.currentCall = null;
			}

			if(this.callView)
			{
				this.callView.close();
			}

			if(this.invitePopup)
			{
				this.invitePopup.close();
			}

			if(this.floatingWindow)
			{
				this.floatingWindow.close();
			}

			if(this.callNotification)
			{
				this.callNotification.close();
			}

			window.BXIM.messenger.dialogStatusRedraw();
			window.BXIM.stopRepeatSound('dialtone');
			window.BXIM.stopRepeatSound('ringtone');
		},

		_onInvitePopupDestroy: function(e)
		{
			this.invitePopup = null;
		},

		_onInvitePopupSelect: function(e)
		{
			this.invitePopup.close();

			if(!this.currentCall)
			{
				return;
			}

			var userId = e.user.id;

			if(BX.Call.Util.isCallServerAllowed() && this.currentCall instanceof BX.Call.PlainCall)
			{
				// trying to switch to the server version of the call
				this.removeCallEvents();
				BX.Call.Engine.getInstance().createChildCall(
					this.currentCall.id,
					BX.Call.Provider.Voximplant,
					[userId]
				).then(function(e)
				{
					this.childCall = e.call;

					this.childCall.addEventListener(BX.Call.Event.onStreamReceived, this._onChildCallFirstStreamHandler);
					this.childCall.addEventListener(BX.Call.Event.onLocalMediaReceived, this._onCallLocalMediaReceivedHandler);

					this.childCall.inviteUsers({
						users: this.childCall.users
					});

				}.bind(this));
				this.callView.addUser(userId, BX.Call.UserState.Calling);
			}
			else
			{
				var currentUsers = this.currentCall.getUsers();
				if(Object.keys(currentUsers).length < BX.Call.Util.getUserLimit() - 1 || currentUsers.hasOwnProperty(userId))
				{
					this.currentCall.inviteUsers({
						users: [userId]
					});
				}
			}
		},

		_onWindowFocus: function()
		{
			if(!this.detached)
			{
				clearTimeout(this.showFloatingWindowTimeout);
				if(this.floatingWindow)
				{
					this.floatingWindow.hide();
				}
			}
		},

		_onWindowBlur: function(e)
		{
			clearTimeout(this.showFloatingWindowTimeout);
			if(this.currentCall && this.floatingWindow && this.callView)
			{
				this.showFloatingWindowTimeout = setTimeout(function()
				{
					if(this.currentCall && this.floatingWindow && this.callView)
					{
						this.floatingWindow.setTitle(this.currentCall.associatedEntity.name);
						BX.Call.Util.getUserAvatars(this.getActiveCallUsers()).then(function(result)
						{
							this.floatingWindow.setAvatars(result);
							this.floatingWindow.show();
						}.bind(this));
					}
				}.bind(this), 300);
			}
		},

		_onBeforeUnload: function(e)
		{
			if(this.floatingWindow)
			{
				this.floatingWindow.close();
			}
			if(this.callNotification)
			{
				this.callNotification.close();
			}
			if(this.hasActiveCall())
			{
				e.preventDefault();
				e.returnValue = '';
			}
		},

		_onImTabChange: function(currentTab)
		{
			if(currentTab === "notify" && this.currentCall && this.callView)
			{
				this.fold();
			}
		},

		_onUpdateChatCounter: function()
		{
			if(!this.currentCall || !this.currentCall.associatedEntity || !this.currentCall.associatedEntity.id || !this.callView)
			{
				return;
			}

			var newCount = BX.MessengerCommon.getDialogCounter(this.currentCall.associatedEntity.id);
			this.callView.setButtonCounter("chat", newCount);
		},

		_onDeviceChange: function(e)
		{
			if(!this.currentCall || !this.currentCall.ready)
			{
				return;
			}

			var added = e.data.added;
			var removed = e.data.removed;
			if(added.length > 0)
			{
				this.log("New devices: ", added);
				BX.UI.Notification.Center.notify({
					content: BX.message("IM_CALL_DEVICES_FOUND") + "<br><ul>" + added.map(function(deviceInfo){ return "<li>"+deviceInfo.label}) +"</ul>",
					position: "top-right",
					autoHideDelay: 10000,
					closeButton: true,
					//category: "call-device-change",
					actions: [
						{
							title: BX.message("IM_CALL_DEVICES_USE"),
							events: {
								click: function(event, balloon, action) {
									balloon.close();
									this.useDevicesInCurrentCall(added);
								}.bind(this)
							}
						},
					]
				});
			}

			if(removed.length > 0)
			{
				this.log("Removed devices: ", removed);
				this.removeDevicesFromCurrentCall(removed);
				BX.UI.Notification.Center.notify({
					content: BX.message("IM_CALL_DEVICES_DETACHED") + "<br><ul>" + removed.map(function(deviceInfo){ return "<li>"+deviceInfo.label})  +"</ul>",
					position: "top-right",
					autoHideDelay: 10000,
					closeButton: true,
					//category: "call-device-change",
					actions: [{
						title: BX.message("IM_CALL_DEVICES_CLOSE"),
						events: {
							click: function(event, balloon, action) {
								balloon.close();
							}
						}
					}]
				});
			}
		},

		_onFloatingVideoMainAreaClick: function(e)
		{
			BX.desktop.windowCommand("show");
			BX.desktop.changeTab("im");

			if(!this.currentCall)
			{
				return;
			}

			if(this.currentCall.associatedEntity && this.currentCall.associatedEntity.id)
			{
				this.messenger.openMessenger(this.currentCall.associatedEntity.id);
			}
			else if(!this.isMessengerOpen())
			{
				this.messenger.openMessenger();
			}

			if(this.detached)
			{
				this.container.style.removeProperty('width');
				this.callView.show();
				this.detached = false;
			}
		},

		_onFloatingVideoButtonClick: function(e)
		{
			switch (e.buttonName)
			{
				case "toggleMute":
					this._onCallViewToggleMuteButtonClick(e);
					break;
				case "hangup":
					this._onCallViewHangupButtonClick();
					break;
			}
		},

		destroy: function()
		{
			if(this.floatingWindow)
			{
				this.floatingWindow.destroy();
				this.floatingWindow = null;
			}
		},

		log: function()
		{
			if(this.currentCall)
			{
				var arr = [this.currentCall.id];

				BX.CallEngine.log.apply(BX.CallEngine, arr.concat(Array.prototype.slice.call(arguments)));
			}
			else
			{
				BX.CallEngine.log.apply(BX.CallEngine, arguments);
			}
		},

		/*test: async function(users = [473, 464], videoOptions = {width: 1280, height: 720}, audioOptions = false)
		{
			await this.messenger.openMessenger();
			await BX.Call.Hardware.init();
			this.createContainer();
			this.callView = new BX.Call.View({
				container: this.container,
				showChatButtons: true,
				userLimit: 48,
				language: window.BXIM.language,
				layout: BX.Call.View.Layout.Grid,
			});

			let lastUserId = 1;

			this.callView.setCallback('onButtonClick',  (e) => {
				switch (e.buttonName)
				{
					case "hangup":
					case "close":
						this.callView.close();
						break;
					case "inviteUser":
						lastUserId++;
						this.callView.addUser(lastUserId, BX.Call.UserState.Connected);
						this.callView.setStream(lastUserId, stream2);

				}
			});
			this.callView.setCallback('onClose', this._onCallViewClose.bind(this));
			this.callView.show();

			users.forEach(userId => {
				this.callView.addUser(userId, BX.Call.UserState.Connected);
			});

			var stream = await navigator.mediaDevices.getUserMedia({
				audio: audioOptions,
				video: videoOptions,
			});

			var stream2 = await navigator.mediaDevices.getUserMedia({
				audio: false,
				video: {
					width: 640,
					height: 480
				},
			});

			this.callView.setLocalStream(stream);
			users.forEach(userId => {
				this.callView.setStream(userId, stream);
			});
		}*/
	}
})();