Вступление
Некоторое время назад появилась необходимость интегрировать программный продукт с сервисом voximplant.
Поскольку еще ни разу не приходилось с ним работать, наверное, как и многие, начал искать готовые рабочие примеры интеграции. И каково же было мое удивление, когда оказалось, что информации не так уж и много. А примеров качественного кода и вовсе не было найдено.
После оценки стартовых позиций, было принято волевое решение писать код интеграции с воксимплантом самому. За основу был взят код из хабрапоста: нагромождать текст процессом написания кода считаю нецелесообразным, поэтому перейду сразу к описанию результата.
Способы использования и код
В основном, код заточен под два способа использования:
- звонки с сайта на контактный номер;
- звонки пользователям(например, с панели администратора).
Конечно же, это совершенно одна и та же задача. Единственное, в первом случае стоит инициализировать клиент для звонков только тогда, когда пользователь непосредственно решил позвонить(поскольку в этот момент появится диалоговое окно браузера о подтверждении доступа к микрофону). Во втором же случае можно клиент инициализировать сразу, поскольку предполагается частое совершение звонков.
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. Когда срабатывает данный обработчик, означает, что начался звонок написанным классом, а не начался звонок непосредственно на телефон дозвона;
Для поставленных задач класс всецело оправдал себя. Конечно же, если этих возможностей мало — всегда можно расширить функционал. Поэтому, всем желающим код доступен на гитхабе.
Думаю, кому-нибудь да будет полезно.