Javascript-клиент для звонков через voximplant

от автора


Вступление

Некоторое время назад появилась необходимость интегрировать программный продукт с сервисом voximplant.

Поскольку еще ни разу не приходилось с ним работать, наверное, как и многие, начал искать готовые рабочие примеры интеграции. И каково же было мое удивление, когда оказалось, что информации не так уж и много. А примеров качественного кода и вовсе не было найдено.

После оценки стартовых позиций, было принято волевое решение писать код интеграции с воксимплантом самому. За основу был взят код из хабрапоста: нагромождать текст процессом написания кода считаю нецелесообразным, поэтому перейду сразу к описанию результата.

Способы использования и код

В основном, код заточен под два способа использования:

  1. звонки с сайта на контактный номер;
  2. звонки пользователям(например, с панели администратора).

Конечно же, это совершенно одна и та же задача. Единственное, в первом случае стоит инициализировать клиент для звонков только тогда, когда пользователь непосредственно решил позвонить(поскольку в этот момент появится диалоговое окно браузера о подтверждении доступа к микрофону). Во втором же случае можно клиент инициализировать сразу, поскольку предполагается частое совершение звонков.

js код клиента

/**  * @constructor  * @param {String} aLogin_str Логин для приложения voximplant. Обязательный параметр  * @param {String} aPassword_str Пароль для приложения voximplant. Обязательный параметр  * @param {Object} aOptOptions_obj Набор необязательных параметров, которые принимаются для метода VoxImplant.getInstance().init(). Список параметров тут: http://voximplant.com/docs/references/websdk/VoxImplant.Config.html  *   * */ function VoximaplantClient(aLogin_str, aPassword_str, aOptOptions_obj) {     var lLogin_str = aLogin_str;     var lPassword_str = aPassword_str;     var lOptions_obj = {         micRequired: true,         progressTone: true,         progressToneCountry: "RU",         showDebugInfo: false     };     if (aOptOptions_obj && aOptOptions_obj instanceof Object) {         for (var i in aOptOptions_obj) {             lOptions_obj[i] = aOptOptions_obj[i];         }     }      /**      * Статус, указывающий, что VoximaplantClient еще не выполнял метод init()      */     this.NOT_INTIALIZED = 0; 	 	/**      * Статус, указывающий, что VoximaplantClient уже выполнил метод init()      */     this.INTIALIZED = 1; 	 	/**      * Статус, указывающий, что у пользователя доступен микрофон. Если микрофон не доступен, статус будет INTIALIZED      */     this.MIC_VERIFIED = 2; 	 	/**      * Статус, указывающий, что клиент подключился к серверу voximplant. Если по каким-то причинам подключиться не удалось, статус будет: MIC_VERIFIED      */     this.CONNECTED = 3; 	 	/**      * Статус, указывающий, что клиент успешно авторизовался на сервере voximplant.  	 * Если по каким-то причинам авторизоваться не удалось, статус будет: CONNECTED 	 * Единственный статус, при котором возможно совершать звонки      */     this.LOGGED_IN = 4; 	 	/**      * Статус, указывающий, что у клиента подключился к серверу voximplant, но по каким-то причинам подключение разорвалось.      */     this.CONNECTION_CLOSED = 5;          /**      * При начале обработки звонка воксимплантом, обрабатывается эта функция, если ей присвоили значение      */     this.onCallingStartedEvent = null;          /**      * При завершении звонка, обрабатывается эта функция, если ей присвоили значение      */     this.onCallingCompletedEvent = null;          /**      * При окончании работы функции VoximplantClient::init(), обрабатывается эта функция, если ей присвоили значение      */     this.onInitializationCompletedEvent = null;     var self = this;     var tryToExecuteInitializationCompletedEventHandler = function() {         if (self.onInitializationCompletedEvent && typeof self.onInitializationCompletedEvent === 'function') {             self.onInitializationCompletedEvent();         }     };      var lStatus_int = this.NOT_INTIALIZED;     var lInstance_vx_obj = null;     var lCall_vx_obj = null;          this.init = function () {         if (lStatus_int < this.INTIALIZED) {             lInstance_vx_obj = VoxImplant.getInstance();             var self = this;             lInstance_vx_obj.addEventListener(VoxImplant.Events.SDKReady, function() {                 lStatus_int = self.INTIALIZED;                 lInstance_vx_obj.addEventListener(VoxImplant.Events.MicAccessResult, function(e) {                     if (e.result) {                         lStatus_int = self.MIC_VERIFIED;                     } else {                         tryToExecuteInitializationCompletedEventHandler();                     }                 });                 if (!lInstance_vx_obj.connected()) {                     lInstance_vx_obj.addEventListener(VoxImplant.Events.ConnectionEstablished, function() {                         lStatus_int = self.CONNECTED;                         lInstance_vx_obj.addEventListener(VoxImplant.Events.AuthResult, function(e) {                             if (e.result) {                                 lStatus_int = self.LOGGED_IN;                             }                             tryToExecuteInitializationCompletedEventHandler();                         });                         lInstance_vx_obj.login(lLogin_str, lPassword_str);                     });                     lInstance_vx_obj.addEventListener(VoxImplant.Events.ConnectionClosed, function() {                         lStatus_int = self.CONNECTION_CLOSED;                     });                     lInstance_vx_obj.addEventListener(VoxImplant.Events.ConnectionFailed, function() {                         lStatus_int = self.CONNECTION_CLOSED;                         tryToExecuteInitializationCompletedEventHandler();                     });                     lInstance_vx_obj.connect();                 }             });             lInstance_vx_obj.init(lOptions_obj);         }     };          /**      * Получить текущий статус VoximplantClient.  	 * Доступные статусы: NOT_INTIALIZED, INTIALIZED, MIC_VERIFIED, CONNECTED, LOGGED_IN, CONNECTION_CLOSED 	 * @return {Integer}      */     this.getStatus = function() {         return lStatus_int;     };          /**      * Проверяем, разрешен ли звонок 	 * @return {Boolean}      */     this.isCallingAllowed = function() {         return lStatus_int === self.LOGGED_IN;     };          /**      * Звоним на номер 	 *  	 * @param{String} aNumber телефонный номер на который звоним      */     this.callToNumber = function(aNumber) {         if (!this.isCallingAllowed() || lCall_vx_obj !== null) {             return;         }         aNumber = aNumber.replace(/[^0-9]/g, '');         lCall_vx_obj = lInstance_vx_obj.call(aNumber);         var self = this;         var closeCallFunc = function() {             lCall_vx_obj.removeEventListener(VoxImplant.CallEvents.Failed, closeCallFunc);             lCall_vx_obj.removeEventListener(VoxImplant.CallEvents.Disconnected, closeCallFunc);             if (lCall_vx_obj.state() !== 'ENDED') {                 lCall_vx_obj.hangup();             }             lCall_vx_obj = null;              if (self.onCallingCompletedEvent && typeof self.onCallingCompletedEvent === 'function') {                 self.onCallingCompletedEvent();             }         };         lCall_vx_obj.addEventListener(VoxImplant.CallEvents.Failed, closeCallFunc);         lCall_vx_obj.addEventListener(VoxImplant.CallEvents.Disconnected, closeCallFunc);          if (this.onCallingStartedEvent && typeof this.onCallingStartedEvent === 'function') {             this.onCallingStartedEvent();         }     };          /**      * Завершить звонок      */     this.hangUp = function() {         if (!this.isCallingAllowed() || lCall_vx_obj === null) {             return;         }         lCall_vx_obj.hangup();     }; } 

Так как первый вариант использования более комплексный, реализуем его. Для начала создадим такую html-форму для звонков:

код html-формы

<!DOCTYPE html> <html lang="en"> 	<head> 		<meta charset="utf-8"> 		<meta http-equiv="X-UA-Compatible" content="IE=edge"> 		<meta name="viewport" content="width=device-width, initial-scale=1"> 		<title>Voximplant test page</title> 		<link href="css/bootstrap.min.css" rel="stylesheet"> 	</head> 	<body> 		<div class="container"> 			<div class="row" style="margin-top:20px;"> 				<div class="col-xs-4 col-sm-4 col-md-3 text-right"> 					Позвонить на номер: 				</div> 				<div class="col-xs-4 col-sm-4 col-md-3"> 					<input type="password" class="form-control" id="phone_num"/> 				</div> 				<div class="col-xs-4 col-sm-4 col-md-3"> 					<button type="button" class="btn btn-success" id="call_to_num"><span class="glyphicon glyphicon-earphone"></span></button> 				</div> 			</div> 			<div class="row" style="margin-top:20px;"> 				<div class="col-md-12" id="log"> 				</div> 			</div> 		</div> 	</body> </html> 

Дальше подключим несколько скриптов: скрипт WebSDK voximplant’а, библиотека jquery, наш voximplantclient, а также скрипт, демонстрирующий работу с voximplantclient.

<script src="//cdn.voximplant.com/voximplant.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="js/voximplantclient.js"></script> <script> 	var voximplantClient = new VoximaplantClient('xxxxx', 'xxxxx'); 	voximplantClient.onInitializationCompletedEvent = function () { 		$('#log').append('<p class="bg-warning">Инициализиация завершена</p>'); 		callToNumber(); 	}; 	voximplantClient.onCallingStartedEvent = function () { 		$('#call_to_num').removeClass('btn-success btn-warning').addClass('btn-danger'); 		$('#log').append('<p class="bg-danger">Звонок начался</p>'); 	}; 	voximplantClient.onCallingCompletedEvent = function () { 		$('#call_to_num').removeClass('btn-danger btn-warning').addClass('btn-success'); 		$('#log').append('<p class="bg-success">Звонок завершен</p>'); 	}; 	 	function callToNumber() { 		if (voximplantClient.isCallingAllowed()) { 			voximplantClient.callToNumber($('#phone_num').val()); 		} else { 			var voximplantStatus = voximplantClient.getStatus(); 			if (voximplantStatus === voximplantClient.NOT_INTIALIZED) { 				voximplantClient.init(); 				$('#call_to_num').removeClass('btn-success btn-danger').addClass('btn-warning'); 				$('#log').append('<p class="bg-warning">Начата инициализация</p>'); 			} else { 				switch (voximplantStatus) { 					case voximplantClient.INTIALIZED: 						msg_text = 'Не удается сделать звонок. Нет доступа к микрофону'; 						break; 					case voximplantClient.MIC_VERIFIED: 						msg_text = 'Не удается сделать звонок. Нет соединения с сервером voximplant'; 						break; 					case voximplantClient.CONNECTED: 						msg_text = 'Не удается сделать звонок. Не удалось авторизоваться на сервере voximplant'; 						break; 					case voximplantClient.CONNECTION_CLOSED: 						msg_text = 'Не удается сделать звонок. Соединение с сервером voximplant закрыто. Пожалуйста, перезайдите в панель администратора и попробуйте снова.'; 						break; 				} 				alert(msg_text); 			} 		} 	} 	 	$(document).ready(function () { 		$('#call_to_num').click(function () { 			if ($(this).hasClass('btn-success')) { 				callToNumber(); 			} else if ($(this).hasClass('btn-danger')) { 				voximplantClient.hangUp(); 			} 		}); 	}); </script> 

По-моему код получился настолько самодокументируемым и понятным, что комментировать здесь нечего. В итоге получаем:

Достоинства и недостатки

К достоинствам следует отнести:

  • Одновременно можно совершать лишь один звонок;
  • Номер для дозвона можно менять во время работы на сайте;
  • Присутствуют обработчики событий на начало звонка и на окончание звонка;
  • Простой набор интерфейсов;
  • Код написан в ООП-стиле, что сводит к минимуму количество переменных в глобальном пространстве видимости.

Также и имеются недостатки:

  • Неточное описание состояний. Например, когда после инициализации объект находится в состоянии MIC_VERIFIED, на самом деле это означает, что была произведена попытка соединения с сервером voximplant, но соединение не удалось. Да, пришлось пожертвовать неточностью в замен простоты и времени разработки;
  • Клиент имеет функционал только для звонков;
  • Неточность вызова обработчика onCallingStartedEvent. Когда срабатывает данный обработчик, означает, что начался звонок написанным классом, а не начался звонок непосредственно на телефон дозвона;

Для поставленных задач класс всецело оправдал себя. Конечно же, если этих возможностей мало — всегда можно расширить функционал. Поэтому, всем желающим код доступен на гитхабе.

Думаю, кому-нибудь да будет полезно.

ссылка на оригинал статьи http://habrahabr.ru/post/271055/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *