Сегодня я расскажу вам о работе sip-телефонии, а именно о том, как я организовывал звуковой сеанс между мобильными рациями (или ИКН) о которых вы слышали ранее из других статей нашей компании и web-клиентом через webRTC с использованием sipML5 в качестве библиотеки и asterisk 11 в качестве АТС.
Всем кому небезразлична данная тема — добро пожаловать под кат.
Немного предыстории
К нам в отдел поступило оперативное задание разработать sip-клиент, который работал бы на asterisk, и как минимум на google chrome (как мобильной, так и десктопной его версии). С этого и закрутилось.
В системе локального позиционирования RealTrac существуют мобильные устройства типа рация, или, как мы их называем, ИКН (интерком носимый), который обеспечивает использование как дуплексной связи, так и широковещательной полудуплексной связи с другими устройствами. Эти устройства взаимодействуют с коммуникационным сервером INCPd, основная задача которого – обработка пакетов протокола INCP, посредством которого обеспечивается обмен информацией с устройствами системы RealTrac. В том числе, данный сервер обрабатывает поступающий голосовой трафик, и перепаковывает его в sip-пакеты, для дальнейшей работы со сторонним софтом.
В качестве коммутатора выступает asterisk.
Инструментарий
SipML5 был выбран нами неслучайно. Во-первых, данная библиотека хорошо подходит для интеграции с asterisk. Во-вторых, мы уже имели опыт работы с подобными системами, однако, из-за того, что наша компания поддерживала Debian 7 в качестве ОС для нашего программного обеспечения имелись определенные сложности. В дистрибутиве Debian 7 доступен только asterisk 1.8 в то время как webRTC без танцев с бубном, в виде webrtc2sip, работает только с 11 версии asterisk, что нас несильно устраивало, так как это могло бы стать полигоном проблем для будущих инсталляций.
С релизом Debian 8 было решено продолжить движение в направлении данного функционала.
В качестве инструментария создания ui-части выступал React.js, так как он удобен для отображения различных динамических объектов, имеющих большое количество внутренних состояний.
Небольшое введение по sipML5
В основе клиента используется две основные сущности, это SIPml.Stack и SIPml.Session.
SIPml.Stack является управляющим потоком данных. Данный объект будет выступать в качестве основного при создании сессий с сервером.
SIPml.Session является потоком sip-сеанса с сервером. Используется непосредственно для передачи данных между сервером и клиентской частью.
Алгоритм работы с sipML5:
Инициализация библиотеки:
SIPml.init(readyCallback, errorCallback);
Создание Stack
Stack является ключевым источником данных для работы клиента.
RtlsSip.stack = new SIPml.Stack({
realm: Data.property.realm,
impi: Data.property.impi,
impu: Data.property.impu,
password: Data.property.password,
display_name: Data.property.display_name,
websocket_proxy_url: Data.property.websocket_proxy_url,
events_listener: { events: '*', listener: eventsListener },
sip_headers: [
{ name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.0.0' },
{ name: 'Organization', value: 'RTLS' }
]
})
При создании stack необходимо указывать большое количество конфигурационных данных, часть из которых является опциональной. Полную информацию по stack можно посмотреть здесь
После создания stack-объекта необходимо его запустить:
RtlsSip.stack.start();
Важно отметить, что функция start() является асинхронной, поэтому, перед тем как начать совершать звонки, необходимо дождаться события запуска stack.
Полный перечень событий представлен тут много, и перечислять их все не имеет смысла.
После события создания stack необходимо создать сессию регистрации:
RtlsSip.stack.newSession('register', {
events_listener: { events: '*', listener: registerListeners} });
RtlsSip.sessions.register();
В ходе этой операции происходит sip запрос для входа пользователя в систему.
После выполнения данных действий мы готовы совершать или принимать звонки от пользователей.
Прием звонков
Входящий звонок в sipML5 инициализирует событие ”i_new_call” объекта SIPml.Stack.
В общем случае обработчик ответа на звонок выглядит следующим образом:
var eventCallback = function(е){
RtlsSip.callSession = e.newSession;
RtlsSip.callSession.events_listener({events: '*', listener: RtlsSip.sessionEventListener})
RtlsSip.callSession.accept(
{audio_remote: document.getElementById('audio_remote'),
events_listener: { events: '*', listener: RtlsSip.sessionEventListener}})
}
e.newSession — содержит в себе дополнительный объект сеанса связи, для события “i_new_call” это будет объект SIPml.Session.Call.
Функция accept() необходима для принятия звонка. В качестве параметра принимает объект SIPml.Session.Configuration
Инициализация звонков
Инициализация звонка сводится к созданию новой сессии типа SIPml.Session.Call
RtlsSip.callSession = RtlsSip.stack.newSession('call-audio', {
audio_remote: document.querySelector('#audio_remote'),
events_listener: { events: '*', listener: RtlsSip.sessionEventListener }
});
После инициализации можно приступать к вызову абонента, через его идентификатор, номер или url (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000').:
RtlsSip.callSession.call(number);
Управление звонком
Управление звонком это методы класса SIPml.Session.Call
Основные из них:
? .hangup() — завершение звонка
? .hold() / .resume() — удержание/возобновление звонка
? .mute(media, mute) — отключение входящего звука, где media — тип контента для отключения; mute — boolean — активация/деактивация mute.
RtlsSip.callSession.mute('audio', true);
Есть также множество других функций, информацию о которых вы найдете здесь.
Схема приложения:
Схема приложения имеет достаточно простую архитектуру. В качестве идеологии однонаправленность данных и отсутствия взаимных зависимостей между элементами.
Интеграция в систему
Настройки для asterisk сервера я здесь описывать не буду, так как их несложно найти на просторах сети.
Для абонентов типа web-client был выделен собственный список номеров. Номера из пула устанавливаются диспетчерам при создании аккаунта пользователя.
Диспетчер может совершать звонки как в полудуплексном, так и в дуплексном режиме, а также связываться с другими диспетчерами в случае необходимости.
Устройство по умолчанию звонит в полудуплексном режиме. Диспетчер, которому звонит устройство выбирается в зависимости от геосегмента в котором находится устройство в текущий момент времени.
Все это позволяет добиться максимального комфорта в использовании системы.
Итоги:
На данный момент, эта технология проходит у нас процесс интеграции и тестирования. Уверен, что в процессе работы найдется немало помех и проблем.
Вполне возможно, что в будущем мы попробуем решение на основании других технологий, предоставляющих тот же функционал.
by Sinires
Комментарии (15)
Emily_Rose
09.06.2016 16:4511.13 это очень давно.
https://issues.asterisk.org/jira/browse/ASTERISK-24146 Эту лично я чинил, тестируйте, должно ломаться на 11.13, почти все на это уже продакшине натыкаються.
https://issues.asterisk.org/jira/browse/ASTERISK-25265 Это входящие в фоксе лечит.
Но это кончно проблемы в chan_sip, а на pjsip а в 11 только chan_sip.sinires
09.06.2016 17:41Действительно давно, но debian не шибко стремится обновлять пакеты. В случае необходимости всегда можно собрать из исходников, благо манов полно.
Большое спасибо за подводные камни =)
EminH
10.06.2016 11:22Заранее извиняюсь за простой вопрос, объясните а Html5 клиент должен где то логиниться?
Можно ли сделать просто звонилку с браузера на Sip (или может есть уже такой виджет)?
я не хочу ничего интегрировать, просто хочу кнопку чтобы посетитель сайта мог кликнуть и позвонить мне на sip:user@myvoipserver.tldEmily_Rose
10.06.2016 11:45Можно. «клиент должен где то логиниться?» это в телефонии называется регистрация, делается это для того чтоб сервер знал какой юзер за какими апйи: портом и т.д. для входящих звонков к этому юзеру. Вы же собираетесь делать клик ту колл, там просто нужно достаточно посылать инвайт. У Voximplant вроде как что то есть, zingaya… погуглите в эту сторону, уже не помню.
EminH
10.06.2016 11:53zingaya
они просят регистрироваться и платить поминутно, то есть там явно привязка к серверам.
Voximplant посмотрю
sinires
10.06.2016 12:30Имхо, если sip-сервер сконфигурирован, то проблем быть не должно.
Наверно (не уверен) можно настроить пул гостевых пользователей, а даже если нельзя всегда можно вшить регистрацию «внутри» логики, и регистрировать пользователя в момент нажатия кнопки/перехода на страницу.Emily_Rose
10.06.2016 12:33Зачем их регистрировать?
sinires
10.06.2016 12:40Имелось ввиду подключение к sip-серверу
Mendel
10.06.2016 13:34+1Насколько я понял изначальный вопрос, то задача в том, чтобы зарегистрироваться у любого провайдера у которого «звонки с сипа на сип бесплатно» (большинство вроде как) и который принимает звонки и извне, и разместить на сайте клиента который без всяких логино-паролей (которые не хочется светить в паблик если они не от гостевого пользователя на своем сервере), без поднятий астерисков и т.п.
Я вот если честно не уверен что это возможно, недостаточно в теме, но вроде как все хтмл2сип вариации используют относительно нестандартные транспорты типа того же вебсокета, и могут не поддерживаться софтом провайдера. (могут и поддерживаться, но кто ж знает как повезет?)
Emily_Rose
Я вот чесно не понимаю, почему люди выбираю sipML5, а не sip.js? и да, какая версия 11 астериска в дебиан 8 репозитории?
sinires
Философский вопрос =)
# apt-cache madison asterisk
asterisk | 1:11.13.1~dfsg-2+b1 | http://ftp.debian.org/debian/ jessie/main amd64 Packages
asterisk | 1:11.13.1~dfsg-2+b1 | http://httpredir.debian.org/debian/ jessie/main amd64 Packages
asterisk | 1:11.13.1~dfsg-2+b1 | http://ftp.ru.debian.org/debian/ jessie/main amd64 Packages
asterisk | 1:11.13.1~dfsg-2 | http://ftp.debian.org/debian/ jessie/main Sources
asterisk | 1:11.13.1~dfsg-2 | http://ftp.ru.debian.org/debian/ jessie/main Sources
Ovoshlook
Не филосовский
sipML5 как библиотека довольно криво реализует SIP стэк клиента
Но ради справеливости стоит сказать что sip.js это всего лишь форк jssip.
А вот jssip очень удачный клиент и со стороны sip и со стороны websocket
sinires
Спасибо, глянем повнимательнее в его сторону =)