Your IP : 3.19.61.127


Current Path : /home/bitrix/ext_www/klimatlend.ua/bitrix/js/im/
Upload File :
Current File : /home/bitrix/ext_www/klimatlend.ua/bitrix/js/im/call.js

(function()
{
	var janus = null;
	var roomId = null;

	var Publisher = function(config)
	{
		this.mediaState = {
			video: null,
			audio: null
		};

		this.webrtcState = null;

		this.callbacks = {
			onAttached: (BX.type.isFunction(config.onAttached) ? config.onAttached : null),
			onDetached: (BX.type.isFunction(config.onDetached) ? config.onDetached : null),
			onRoomJoined: (BX.type.isFunction(config.onRoomJoined) ? config.onRoomJoined : null),
			onRoomLeft: (BX.type.isFunction(config.onRoomLeft) ? config.onRoomLeft : null),
			onError: (BX.type.isFunction(config.onError) ? config.onError : null),
			onRemoteFeed: (BX.type.isFunction(config.onRemoteFeed) ? config.onRemoteFeed : null),
			onDestroyed: (BX.type.isFunction(config.onDestroyed) ? config.onDestroyed : null),
			onPublisherLeft: (BX.type.isFunction(config.onPublisherLeft) ? config.onPublisherLeft : null),
			onWebrtcState: (BX.type.isFunction(config.onWebrtcState) ? config.onWebrtcState : null),
		};

		this.userId = config.userId;

		this.pluginHandle = null;
		this.localStream = config.stream;

		this.init();
	};

	Publisher.prototype.init = function()
	{
		this.attach();
	};

	Publisher.prototype.attach = function()
	{
		var self = this;
		janus.attach({
			plugin: 'janus.plugin.videoroom',
			success: this.onAttached.bind(this),
			error: this.onError.bind(this),
			consentDialog: function() {console.log('consentDialog??')},
			webrtcState: function(state) {
				self.webrtcState = state;
			},
			mediaState: function (media, state) {
				if(self.mediaState.hasOwnProperty(media))
				{
					self.mediaState[media] = state;
				}
			},
			onmessage: this.onMessage.bind(this),
			onlocalstream: function() {console.log('onlocalstream')},
			onremotestream: function() {console.log('onremotestream')},
			oncleanup: function() {console.log('oncleanup')},
			ondetached: function() {console.log('Publisher detached')}
		})
	};

	Publisher.prototype.onAttached = function(pluginHandle)
	{
		this.pluginHandle = pluginHandle;
		console.log("Plugin attached! (" + pluginHandle.getPlugin() + ", id=" + pluginHandle.getId() + ")");
		console.log("  -- This is a publisher/manager");

		this.joinRoom();
	};

	Publisher.prototype.onError = function(error)
	{
		if(BX.type.isFunction(this.callbacks.onError))
		{
			this.callbacks.onError({
				target: this,
				errorCode: 0,
				error: error
			});
		}
	};

	Publisher.prototype.onMessage = function(msg, jsep)
	{
		console.log('Received message:', msg);
		var event = msg["videoroom"];
		if (event != undefined && event != null)
		{
			if (event === "joined")
			{
				// Publisher/manager created, negotiate WebRTC and attach to existing feeds, if any
				myid = msg["id"];
				console.log("Successfully joined room " + msg["room"] + " with ID " + myid);
				if(this.localStream)
				{
					this.publishStream();
				}

				if (msg["publishers"] !== undefined && msg["publishers"] !== null)
				{
					if(BX.type.isFunction(this.callbacks.onRemoteFeed))
						this.callbacks.onRemoteFeed(msg["publishers"]);
				}
			}
			else if (event === "destroyed")
			{
				console.log("The room has been destroyed!");
				if(BX.type.isFunction(this.callbacks.onDestroyed))
				{
					this.callbacks.onDestroyed();
				}
			}
			else if (event === "event")
			{
				// Any new feed to attach to?
				if (msg["publishers"] !== undefined && msg["publishers"] !== null)
				{
					if(BX.type.isFunction(this.callbacks.onRemoteFeed))
					{
						this.callbacks.onRemoteFeed(msg["publishers"]);
					}
				}
				else if (msg["leaving"] !== undefined && msg["leaving"] !== null)
				{
					// One of the publishers has gone away?
					if(BX.type.isFunction(this.callbacks.onPublisherLeft))
					{
						this.callbacks.onPublisherLeft(msg["leaving"]);
					}
				}
				else if (msg["unpublished"] !== undefined && msg["unpublished"] !== null)
				{
					// One of the publishers has unpublished?
					var unpublished = msg["unpublished"];
					Janus.log("Publisher left: " + unpublished);
					if (unpublished === 'ok')
					{
						// That's us
						this.pluginHandle.hangup();

						return;
					}
					else
					{
						if(BX.type.isFunction(this.callbacks.onPublisherLeft))
						{
							this.callbacks.onPublisherLeft(msg["unpublished"]);
						}
					}
				} else if (msg["error"] !== undefined && msg["error"] !== null)
				{
					if(BX.type.isFunction(this.callbacks.onError))
					{
						this.callbacks.onError({
							target: this,
							errorCode: msg["error_code"],
							error: msg["error"]
						});
					}
				}
			}
		}
		if (jsep !== undefined && jsep !== null)
		{
			Janus.debug("Handling SDP as well...");
			Janus.debug(jsep);
			this.pluginHandle.handleRemoteJsep({jsep: jsep});
		}
	};

	Publisher.prototype.joinRoom = function()
	{
		var self = this;
		return new Promise(function(resolve, reject)
		{
			var request = { "request": "join", "room": roomId, "ptype": "publisher", "display": 'user' + self.userId };
			self.pluginHandle.send({
				message: request,
				success: function(data) {resolve(data)},
				error: function(error) {reject(error)}
			});
		});
	};

	Publisher.prototype.publishStream = function()
	{
		var self = this;
		this.pluginHandle.createOffer({
			media: { audioRecv: false, videoRecv: false, audioSend: true, videoSend: true},	// Publishers are sendonly
			stream: self.localStream,
			success: function(jsep)
			{
				Janus.debug("Got publisher SDP!");
				Janus.debug(jsep);
				var publish = { "request": "configure", "audio": true, "video": true };
				self.pluginHandle.send({"message": publish, "jsep": jsep});
			},
			error: function(error)
			{
				Janus.error("WebRTC error:", error);
			}
		});
	};

	Publisher.prototype.unpublishStream = function()
	{
		var message = { "request": "unpublish"};
		this.pluginHandle.send({message: message});
	};
	
	Publisher.prototype.changeStream = function(stream)
	{
		var self = this;
		return new Promise(function(resolve, reject)
		{
			// straughtforward hangup works slightly faster :)
			//self.unpublishStream();
			self.pluginHandle.hangup();
			setTimeout(function()
			{
				self.localStream = stream;
				self.publishStream();
				return resolve();
			}, 1000);
		})
	};

	Publisher.prototype.dispose = function()
	{
		if(this.pluginHandle)
		{
			this.pluginHandle.hangup();
			this.pluginHandle.detach();
		}

		this.pluginHandle = null;
		this.localStream = null;
	}

	Receiver = function(config)
	{
		this.feedId = config.feedId;
		this.userId = config.userId;

		this.webrtcState = null;

		this.pluginHandle = null;
		this.stream = null;

		this.callbacks = {
			onRemoteStream: (BX.type.isFunction(config.onRemoteStream) ? config.onRemoteStream : null)
		};

		this.init();
	};

	Receiver.prototype.init = function()
	{
		this.attach();
	};

	Receiver.prototype.attach = function()
	{
		var self = this;
		janus.attach({
			plugin: "janus.plugin.videoroom",
			success: this.onAttached.bind(this),
			error: this.onError.bind(this),
			webrtcState: function(state) {
				self.webrtcState = state;
			},

			onmessage: this.onMessage.bind(this),
			onlocalstream: function(stream) { /*The subscriber stream is recvonly, we don't expect anything here*/ },
			onremotestream: this.onRemoteStream.bind(this),
			oncleanup: function() {console.log('oncleanup')}
		});
	};

	Receiver.prototype.onAttached = function(pluginHandle)
	{
		this.pluginHandle = pluginHandle;

		this.joinRoom();
	};

	Receiver.prototype.onMessage = function(msg, jsep)
	{
		var self = this;
		Janus.debug(" ::: Got a message (listener) :::");
		Janus.debug(JSON.stringify(msg));
		var event = msg["videoroom"];
		Janus.debug("Event: " + event);
		if (event != undefined && event != null)
		{
			if (event === "attached")
			{
				console.log("Successfully attached to feed ", msg);
			}
			else if (msg["error"] !== undefined && msg["error"] !== null)
			{
				console.log('Receiver error: ', msg["error"]);
			}
			else
			{
				console.log('Empty message from media gateway');
			}
		}
		if (jsep !== undefined && jsep !== null)
		{
			Janus.debug("Handling SDP as well...");
			Janus.debug(jsep);
			// Answer and attach
			this.pluginHandle.createAnswer({
				jsep: jsep,
				media: {audioRecv: true, videoRecv: true, audioSend: false, videoSend: false},	// We want recvonly audio/video
				success: function (jsep)
				{
					Janus.debug("Got SDP!");
					Janus.debug(jsep);
					var body = {"request": "start", "room": roomId};
					self.pluginHandle.send({"message": body, "jsep": jsep});
				},
				error: function (error)
				{
					console.log("WebRTC error:", error);
				}
			});
		}
	};

	Receiver.prototype.onRemoteStream = function(stream)
	{
		this.stream = stream;
		console.log('remote stream received');
		var event = {
			target: this,
			stream: stream
		};

		if(BX.type.isFunction(this.callbacks.onRemoteStream))
			this.callbacks.onRemoteStream(event);
	};

	Receiver.prototype.onError = function(error)
	{
		console.log('Receiver error: ', error);
	};

	Receiver.prototype.joinRoom = function()
	{
		console.log('feed: ', this.feedId);
		var request = {
			request: "join",
			room: roomId,
			ptype: "listener",
			feed: this.feedId
		};

		this.pluginHandle.send({message: request});
	};

	Receiver.prototype.dispose = function()
	{
		this.stream = null;
		if(this.pluginHandle)
		{
			this.pluginHandle.hangup();
			this.pluginHandle.detach();
		}
	};

	var CallView = function(config)
	{
		this.server = config.server;
		this.apiSecret = config.apiSecret;

		roomId = config.roomId;

		this.elements = {
			root: BX.type.isDomNode(config.element) ? config.element : null,
			errors: null,
			main: null,
			mainPlaceholder: null,
			self: {
				main: null,
				video: null
			},
			receivers: {},
			buttons: {
				mic: {
					button: null,
					icon: null
				} ,
				camera: {
					button: null,
					icon: null
				},
				connect: {
					button: null,
					icon: null
				},
				log: {
					button: null,
					icon: null
				},
				signout: {
					button: null,
					icon: null
				}
			},
			hardware: {
				main: null,
				mic: null,
				camera: null,
				resolution: null
			},
			log: null
		};

		this.chatId = config.chatId;
		this.userCount = config.userCount;
		this.userId = config.userId;
		this.userDetails = config.userDetails;

		this.publisher = null;
		this.receivers = [];

		this.localStream = null;
		this.hardware = this.getDefaultHardware();

		this.state = {
			mic: true,
			camera: true,
			connect: true,
			selectMic: true,
			selectCamera: true,
			log: false
		};

		this.resolutions = {
			QVGA: {
				description: 'QVGA (320x240)',
				width: {max: 320, min: 320},
				height: {max: 240, min: 240}
			},
			VGA: {
				description: 'VGA (640x480)',
				width: {max: 640, min: 320, ideal: 640},
				height: {max: 480, min: 240, ideal: 480}
			},
			HD: {
				description: 'HD (1280x720)',
				width: {max: 1280, min: 320, ideal: 1280},
				height: {max: 720, min: 240, ideal: 720}
			}
		};

		this.logText = '';

		this.init();
	};

	CallView.prototype.init = function()
	{
		this.render();
		this.bindEvents();
		this.elements.self.video.volume = 0;
		Janus.init({
			debug: false,
			callback: this.onJanusInited.bind(this)
		});
	};

	CallView.prototype.bindEvents = function()
	{
		this.elements.hardware.mic.addEventListener('change', this.onChangeMicrophone.bind(this));
		this.elements.hardware.camera.addEventListener('change', this.onChangeCamera.bind(this));
		this.elements.hardware.resolution.addEventListener('change', this.onChangeResolution.bind(this));

		this.elements.buttons.mic.button.addEventListener('click', this.onMicClick.bind(this));
		this.elements.buttons.camera.button.addEventListener('click', this.onCameraClick.bind(this));
		this.elements.buttons.log.button.addEventListener('click', this.onLogClick.bind(this));
		this.elements.buttons.signout.button.addEventListener('click', this.onSignOutClick.bind(this));

		//this.elements.buttons.connect.button.addEventListener('click', this.onConnectClick.bind(this));
	};

	CallView.prototype.getDefaultHardware = function()
	{
		return {
			mic: localStorage.getItem('im-call-prototype-hardware-mic'),
			camera: localStorage.getItem('im-call-prototype-hardware-camera'),
			resolution: localStorage.getItem('im-call-prototype-hardware-resolution') || 'HD'
		}
	};

	CallView.prototype.showLocalVideo = function()
	{
		BX.removeClass(this.elements.self.main, 'im-call-hidden');
	};

	CallView.prototype.showHardwareSettings = function()
	{
		BX.removeClass(this.elements.hardware.main, 'im-call-hidden');
	};

	CallView.prototype.onJanusInited = function()
	{
		this.log(BX.message('IM_CALL_CONNECTING'));
		janus = new Janus({
			server: this.server,
			apisecret: this.apiSecret,
			success: this.onJanusConnected.bind(this),
			error: this.onJanusError.bind(this),
			destroyed: function(){ console.log('something went terribly wrong'); }
		});
	};

	CallView.prototype.onJanusConnected = function()
	{
		this.log(BX.message('IM_CALL_CONNECTED'));
		var self = this;
		this.createLocalStream().then(
			function()
			{
				attachMediaStream(self.elements.self.video, self.localStream);
				return self.fillHardware();
			},
			function(error)
			{
				self.showError(BX.message('IM_CALL_ERROR_HARDWARE') + ' ' + error);
				return self.fillHardware();
			}
		).then(function()
		{
			self.showHardwareSettings();

			if(self.localStream)
			{
				self.showLocalVideo();
				self.log(BX.message('IM_CALL_PUBLISHING'));
			}

			self.publisher = new Publisher({
				userId: self.userId,
				stream: self.localStream,
				onRemoteFeed: self.onRemoteFeed.bind(self),
				onAttached: self.onPublisherAttached.bind(self),
				onDetached: function() {self.log(BX.message('IM_CALL_PUBLISHING_STOPPED'));},
				onPublisherLeft: self.onPublisherLeft.bind(self),
				onError: function (e)
				{
					self.showError(BX.message('IM_CALL_PUBLISHING_ERROR') + ': ' + e.error);
				},
				onWebrtcState: function()
				{

				}

			});
		});
	};

	CallView.prototype.onPublisherAttached = function()
	{

	};

	CallView.prototype.onJanusError = function(error)
	{
		this.showError(BX.message('IM_CALL_ERROR_CONNECTION') + ' ' + error);
	};

	CallView.prototype.createLocalStream = function()
	{
		var self = this;
		this.log(BX.message('IM_CALL_HARDWARE_REQUEST'));
		return new Promise(function(resolve, reject)
		{
			navigator.mediaDevices.getUserMedia(self.getMediaConstraints()).then(
				function (stream)
				{
					self.log(BX.message('IM_CALL_ACCESS_GRANTED'));
					self.localStream = stream;
					return resolve(stream);
				},
				function (error)
				{
					self.log(BX.message('IM_CALL_ERROR_HARDWARE') + ' ' + error);
					reject(error);
				}
			);
		});
	};

	CallView.prototype.fillHardware = function()
	{
		var self = this;

		var videoTrackLabel = (function()
		{
			if(!self.localStream)
				return '';

			var videoTracks = self.localStream.getVideoTracks();
			if(videoTracks.length > 0 && videoTracks[0].label)
				return videoTracks[0].label;
			else
				return '';
		})();
		var audioTrackLabel = (function()
		{
			if(!self.localStream)
				return '';
			var audioTracks = self.localStream.getAudioTracks();
			if(audioTracks.length > 0 && audioTracks[0].label)
				return audioTracks[0].label;
			else
				return '';
		})();

		return new Promise(function(resolve, reject)
		{
			var option;
			self.elements.hardware.camera.options.length = 0;
			self.elements.hardware.mic.options.length = 0;
			self.elements.hardware.resolution.options.length = 0;

			for(resolution in self.resolutions)
			{
				option = BX.create('option', {text: self.resolutions[resolution].description, attrs:{value: resolution}});
				if(resolution === self.hardware.resolution)
				{
					option.selected = true;
				}
				self.elements.hardware.resolution.options.add(option);
			}

			navigator.mediaDevices.enumerateDevices().then(function(devices)
			{
				devices.forEach(function (device)
				{
					var option;
					if(device.label == '')
						return;

					if(device.kind === 'audioinput' || device.deviceId === self.hardware.mic)
					{
						option = BX.create('option', {text: device.label, attrs:{value: device.deviceId}});

						if(device.label === audioTrackLabel)
						{
							option.selected = true;
						}
						self.elements.hardware.mic.options.add(option);
					}
					else if(device.kind === 'videoinput')
					{
						option = BX.create('option', {text: device.label, attrs:{value: device.deviceId}});
						if(device.label === videoTrackLabel || device.deviceId == self.hardware.camera)
						{
							option.selected = true;
						}
						self.elements.hardware.camera.options.add(option);

					}

				});
				return resolve();
			}).catch(function(error)
			{
				return reject(error);
			})

		});
	};

	CallView.prototype.getMediaConstraints = function()
	{
		var constraints = {
			audio: {},
			video: false
		};

		if(this.hardware.mic)
		{
			constraints.audio.deviceId = {exact: this.hardware.mic};
		}

		if(this.state.camera)
		{
			constraints.video = {};
			if(this.hardware.camera)
			{
				constraints.video.deviceId = {exact: this.hardware.camera};
			}

			if(this.resolutions[this.hardware.resolution])
			{
				constraints.video.width = this.resolutions[this.hardware.resolution].width;
				constraints.video.height = this.resolutions[this.hardware.resolution].height;
			}
		}

		return constraints;
	};

	CallView.prototype.onRemoteFeed = function(feeds)
	{
		for(var feedIndex in feeds)
		{
			if(feeds.hasOwnProperty(feedIndex))
			{
				this.attachRemoteFeed(feeds[feedIndex]);
			}
		}
	};

	CallView.prototype.onPublisherLeft = function(feedId)
	{
		var self = this;
		console.log('onPublisherLeft: ', feedId);
		this.receivers.forEach(function(receiver, index)
		{
			if(receiver.feedId == feedId)
			{
				self.log(self.getUserName(receiver.userId) + ' ' + BX.message('IM_CALL_USER_DISCONNECTED'));
				receiver.dispose();
				self.receivers.splice(index, 1);
			}
		});

		if(self.elements.receivers[feedId])
		{
			BX.cleanNode(self.elements.receivers[feedId].main, true);
			delete(self.elements.receivers[feedId]);

			if(this.receivers.length == 0)
				BX.removeClass(this.elements.mainPlaceholder, 'im-call-hidden');
		}
	};

	CallView.prototype.onRemoteStream = function(e)
	{
		if(e.target)
		{
			this.renderReceiver(e.target);
		}
	};

	CallView.prototype.onChangeMicrophone = function(e)
	{
		var mic = e.target.value;
		localStorage.setItem('im-call-prototype-hardware-mic', mic);
		this.hardware.mic = mic;
		this.reapplyConstraints();
	};

	CallView.prototype.onChangeCamera = function(e)
	{
		var camera = e.target.value;
		this.hardware.camera = camera;
		localStorage.setItem('im-call-prototype-hardware-camera', camera);
		this.reapplyConstraints();
	};

	CallView.prototype.onChangeResolution = function(e)
	{
		var resolution = e.target.value;
		if(!this.resolutions[resolution])
			return;

		this.hardware.resolution = resolution;
		localStorage.setItem('im-call-prototype-hardware-resolution', resolution);
		this.reapplyConstraints();
	};

	CallView.prototype.onMicClick = function(e)
	{
		this.setMicState(!this.state.mic);
	};

	CallView.prototype.onCameraClick = function(e)
	{
		this.setCameraState(!this.state.camera);
	};

	CallView.prototype.onConnectClick = function(e)
	{

	};

	CallView.prototype.onLogClick = function(e)
	{
		this.setLogState(!this.state.log);
	};

	CallView.prototype.onSignOutClick = function(e)
	{
		this.dispose();
	};

	CallView.prototype.setMicState = function(state)
	{
		if(!this.localStream || !this.publisher.webrtcState)
			return;

		state = (state == true);
		this.state.mic = state;
		if(state)
		{
			this.publisher.pluginHandle.unmuteAudio();
		}
		else
		{
			this.publisher.pluginHandle.muteAudio();
		}

		this.renderButtons()
	};

	CallView.prototype.setCameraState = function(state)
	{
		state = (state == true);
		this.state.camera = state;

		if(this.localStream)
		{
			this.reapplyConstraints();
		}
		this.renderButtons();
	};

	CallView.prototype.setLogState = function(state)
	{
		state = (state == true);
		this.state.log = state;

		if(this.state.log)
			BX.removeClass(this.elements.log, 'im-call-hidden');
		else
			BX.addClass(this.elements.log, 'im-call-hidden');
	};

	CallView.prototype.reapplyConstraints = function()
	{
		var self = this;
		this.elements.self.video.src = null;
		this.elements.self.video.pause();
		BX.showWait(this.elements.self.main);
		self.state.selectCamera = false;
		self.state.selectMic = false;
		self.renderButtons();
		BX.webrtc.stopMediaStream(this.localStream);
		this.localStream = null;
		this.publisher.unpublishStream();

		this.createLocalStream().then(function()
		{
			self.publisher.changeStream(self.localStream).then(function()
			{
				BX.closeWait(self.elements.self.main);

				self.state.selectCamera = self.state.camera;
				self.state.selectMic = true;
				self.renderButtons();
				attachMediaStream(self.elements.self.video, self.localStream);
			});
		}).catch(function(error)
		{
			self.showError('IM_CALL_ERROR_HARDWARE' + ' ' + error);
			BX.closeWait(self.elements.self.main);

			self.state.selectCamera = self.state.camera;
			self.state.selectMic = true;
			self.renderButtons();

		});
	};

	CallView.prototype.attachRemoteFeed = function(feedParams)
	{
		var id = feedParams.id;
		var display = feedParams.display || '';
		var userId;

		if (display.search('user') === 0)
			userId = display.substring(4);

		this.log(this.getUserName(userId) + ' ' + BX.message('IM_CALL_USER_CONNECTED'));

		var receiver = new Receiver({
			feedId: id,
			userId: userId,
			onRemoteStream: this.onRemoteStream.bind(this)
		});

		var receiverExists = this.receivers.some(function(element)
		{
			return (element.feedId == id);
		});

		if(!receiverExists)
		{
			this.receivers.push(receiver);
		}

		this.renderReceiver(receiver);
	};

	CallView.prototype.renderReceiver = function(receiver)
	{
		if(!this.elements.receivers[receiver.feedId])
		{
			this.elements.receivers[receiver.feedId] = this.createReceiverNode(receiver);
			this.elements.main.appendChild(this.elements.receivers[receiver.feedId].main);
			BX.addClass(this.elements.mainPlaceholder, 'im-call-hidden');
		}

		this.elements.receivers[receiver.feedId].caption.innerText = this.getUserName(receiver.userId);
		this.elements.receivers[receiver.feedId].resolution.innerText = '';
		this.elements.receivers[receiver.feedId].bitrate.innerText = '';
		if(receiver.stream)
		{
			attachMediaStream(this.elements.receivers[receiver.feedId].streamVideo, receiver.stream);
			if(receiver.stream.getVideoTracks().length > 0)
			{
				BX.addClass(this.elements.receivers[receiver.feedId].avatar, 'im-call-hidden');
				BX.removeClass(this.elements.receivers[receiver.feedId].stream, 'im-call-hidden');
			}
			else
			{
				BX.removeClass(this.elements.receivers[receiver.feedId].avatar, 'im-call-hidden');
				BX.addClass(this.elements.receivers[receiver.feedId].stream, 'im-call-hidden');
			}
		}
		else
		{
			this.elements.receivers[receiver.feedId].avatarImg.src = this.getUserAvatar(receiver.userId, true);
			this.elements.receivers[receiver.feedId].streamVideo.src = '';
			BX.removeClass(this.elements.receivers[receiver.feedId].avatar, 'im-call-hidden');
			BX.addClass(this.elements.receivers[receiver.feedId].stream, 'im-call-hidden');
		}
	};

	CallView.prototype.renderButtons = function()
	{
		if(this.state.mic)
			BX.removeClass(this.elements.buttons.mic.icon, 'im-call-button-disabled');
		else
			BX.addClass(this.elements.buttons.mic.icon, 'im-call-button-disabled');

		if(this.state.camera)
		{
			BX.removeClass(this.elements.buttons.camera.icon, 'im-call-button-disabled');
			BX.removeClass(this.elements.self.main, 'im-call-hidden');
			this.elements.hardware.camera.disabled = false;
		}
		else
		{
			BX.addClass(this.elements.buttons.camera.icon, 'im-call-button-disabled');
			BX.addClass(this.elements.self.main, 'im-call-hidden');
			this.elements.hardware.camera.disabled = true;
		}

		if(this.state.connect)
			BX.removeClass(this.elements.buttons.connect.icon, 'im-call-button-disabled');
		else
			BX.addClass(this.elements.buttons.connect.icon, 'im-call-button-disabled');
	};

	CallView.prototype.showBitrate = function(receiver, bitrate)
	{

	};

	CallView.prototype.showResolution = function(receiver, resolution)
	{

	};

	CallView.prototype.createReceiverNode = function(receiver)
	{
		var streamNode, videoNode, captionNode, resolutionNode, bitrateNode, avatarNode, avatarImg, fullScreenNode;

		var mainNode = BX.create('div', {props: {className: 'im-video-other'}, children: [
			streamNode = BX.create('div', {props: {className: 'im-video-other-video im-call-hidden'}, children: [
				videoNode = BX.create('video'),
				fullScreenNode = BX.create('div', {
					props: {className: 'im-video-other-button im-video-other-button-fullscreen'},
					children: [
						BX.create('div', {props: {className: 'im-video-other-button-icon'}, children: [
							BX.create('i', {props: {className: 'fa fa-arrows-alt'}, attrs: {'aria-hidden': true}})
						]})
					],
					events: {
						click: function(e) {this.toggleFullScreen(receiver)}.bind(this)
					}
				})
				]}),
			avatarNode = BX.create('div', {props: {className: 'im-video-other-avatar'}, children: [
				avatarImg = BX.create('img', {props: {className: 'im-video-other-avatar-img'}})
			]}),
			captionNode = BX.create('div', {props: {className: 'im-video-other-caption'}}),
			resolutionNode = BX.create('div', {props: {className: 'im-video-other-resolution'}}),
			bitrateNode = BX.create('div', {props: {className: 'im-video-other-bitrate'}})
		]});

		var result = {
			main: mainNode,
			stream: streamNode,
			streamVideo: videoNode,
			buttons: {
				fullScreen: fullScreenNode
			},
			avatar: avatarNode,
			avatarImg: avatarImg,
			caption: captionNode,
			resolution: resolutionNode,
			bitrate: bitrateNode
		};
		return result;
	};

	CallView.prototype.toggleFullScreen = function(receiver)
	{
		if(!this.elements.receivers[receiver.feedId])
			return;

		var fullScreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement;

		if(fullScreenElement)
		{
			if(document.cancelFullscreen)
				document.cancelFullscreen();
			else if (document.mozCancelFullScreen)
				document.mozCancelFullScreen();
			else if (document.webkitCancelFullScreen)
				document.webkitCancelFullScreen();

			return;
		}

		var videoElement = this.elements.receivers[receiver.feedId].stream;

		if(videoElement.requestFullScreen)
			videoElement.requestFullScreen();
		else if(videoElement.mozRequestFullScreen)
			videoElement.mozRequestFullScreen();
		else if(videoElement.webkitRequestFullScreen)
			videoElement.webkitRequestFullScreen();
		else
			console.log('fullscreen mode is not supported');
	};

	CallView.prototype.showError = function(errorText)
	{
		this.log(errorText);
		var errorNode = BX.create('div', {props: {className: 'im-call-errors-error'}, children: [
			BX.create('div', {props: {
				className: 'im-call-errors-error-close'},
				children: [
					BX.create('i', {props: {className: 'fa fa-times'}})
				],
				events: {
					click: function() {BX.cleanNode(errorNode, true);}
				}
			}),
			BX.create('div', {props: {className: 'im-call-errors-error-text'}, text: errorText})
		]});

		this.elements.errors.appendChild(errorNode);
	};

	CallView.prototype.getUserName = function(userId)
	{
		userId = parseInt(userId);
		var result = '';
		if(this.userDetails[userId])
		{
			result = this.userDetails[userId].name;
		}

		return result;
	};

	CallView.prototype.getUserAvatar = function(userId, hr)
	{
		hr = (hr == true);
		userId = parseInt(userId);
		var result = '';
		if(hr)
		{
			if(this.userDetails.hrphoto && this.userDetails.hrphoto[userId])
			{
				result = this.userDetails.hrphoto[userId];
			}
		}
		else
		{
			if(this.userDetails[userId])
			{
				result = this.userDetails[userId].avatar;
			}
		}

		return result;
	};

	CallView.prototype.render = function()
	{
		if(!this.elements.root)
		{
			this.elements.root = BX.create('div', {props: {className: 'im-call'}});
			document.body.appendChild(this.elements.root);
		}
		else
		{
			BX.addClass(this.elements.root, 'im-call');
		}

		BX.adjust(this.elements.root, {children: [
			this.elements.errors = BX.create('div', {props: {className: 'im-call-errors'}}),
			this.elements.main = BX.create('div', {props: {className: 'im-call-main'}, children: [
				this.elements.mainPlaceholder = BX.create('div', {props: {className: 'im-call-main-placeholder'}, text: BX.message('IM_CALL_WAITING_CONNECT')})
			]}),
			BX.create('div', {props: {className: 'im-call-buttons'}, children: [
				this.elements.buttons.mic.button = BX.create('div', {props: {className: 'im-call-button im-call-button-mic'}, children: [
					this.elements.buttons.mic.icon = BX.create('div', {props: {className: 'im-call-button-icon'}, html: '<i class="fa fa-microphone" aria-hidden="true"></i>'})
				]}),
				this.elements.buttons.camera.button = BX.create('div', {props: {className: 'im-call-button im-call-button-camera'}, children: [
					this.elements.buttons.camera.icon = BX.create('div', {props: {className: 'im-call-button-icon'}, html: '<i class="fa fa-video-camera" aria-hidden="true"></i>'})
				]}),
				this.elements.buttons.log.button = BX.create('div', {props: {className: 'im-call-button im-call-button-log '}, children: [
					this.elements.buttons.log.icon = BX.create('div', {props: {className: 'im-call-button-icon'}, html: '<i class="fa fa-list" aria-hidden="true"></i>'})
				]}),
				this.elements.buttons.signout.button = BX.create('div', {props: {className: 'im-call-button im-call-button-signout im-call-button-last'}, children: [
					this.elements.buttons.signout.icon = BX.create('div', {props: {className: 'im-call-button-icon'}, html: '<i class="fa fa-sign-out" aria-hidden="true"></i>'})
				]}),
				this.elements.hardware.main = BX.create('div', {props: {className: 'im-call-hardware im-call-hidden'}, children: [
					BX.create('div', {props: {className: 'im-call-hardware-mic'}, children: [
						BX.create('label', {attrs: {'for': 'im-call-hardware-select-mic'}, text: BX.message('IM_CALL_HARDWARE_MIC') + ':'}),
						this.elements.hardware.mic = BX.create('select', {attrs: {'id': 'im-call-hardware-select-mic'}})
					]}),
					BX.create('div', {props: {className: 'im-call-hardware-camera'}, children: [
						BX.create('label', {attrs: {'for': 'im-call-hardware-select-camera'}, text: BX.message('IM_CALL_HARDWARE_CAMERA') + ':'}),
						this.elements.hardware.camera = BX.create('select', {attrs: {'id': 'im-call-hardware-select-camera'}})
					]}),
					BX.create('div', {props: {className: 'im-call-hardware-resolution'}, children: [
						BX.create('label', {attrs: {'for': 'im-call-hardware-select-resolution'}, text: BX.message('IM_CALL_HARDWARE_RESOLUTION') + ':'}),
						this.elements.hardware.resolution = BX.create('select', {attrs: {'id': 'im-call-hardware-select-resolution'}})
					]})
				]}),
				this.elements.self.main = BX.create('div', {props: {className: 'im-call-video-self im-call-hidden'}, children: [
					BX.create('div', {props: {className: 'im-call-video-self-video'}, children: [
						this.elements.self.video = BX.create('video')
					]})
				]})
			]}),
			this.elements.log = BX.create('pre', {props: {className: 'im-call-log im-call-hidden'}})
		]});

		return this.elements.root;
	};

	CallView.prototype.dispose = function()
	{
		var self = this;
		
		for(feedId in this.elements.receivers)
		{
			this.elements.receivers[feedId].streamVideo.pause();
			this.elements.receivers[feedId].streamVideo.src = '';
		}
		
		this.receivers.forEach(function(receiver)
		{
			receiver.dispose();
		});
		this.receivers = [];
		if(this.publisher)
		{
			this.publisher.dispose();
		}

		if(this.localStream)
		{
			BX.webrtc.stopMediaStream(this.localStream);
		}

		this.localStream = null;
		
		BX.remove(this.elements.root, true);
	};

	CallView.prototype.log = function(text)
	{
		var date = new Date();
		var timeString = lpad(date.getHours(), 2, '0') + ':' + lpad(date.getMinutes(), 2, '0') + ':' + lpad(date.getSeconds(), 2, '0');

		var logString = timeString + ': ' + text + '\n';

		this.logText = this.logText + logString;

		this.elements.log.innerText = this.logText;
	};

	function lpad(string, length, filler)
	{
		if(!BX.type.isString(string))
			string = string.toString();

		if(string.length < length)
			return filler.repeat(length - string.length) + string;
		else
			return string;
	};

	function attachMediaStream(element, stream)
	{
		element.src = URL.createObjectURL(stream);
		element.load();
		element.play();
	};

	window.CallView = CallView;
})();