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

<!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/
Добавить комментарий