Статьи

Создание приложения Audio Calling с помощью Ionic и PhoneRTC

В этом уроке я собираюсь создать приложение для аудиозвонков с помощью Ionic . Мы будем реализовывать его с помощью PhoneRTC , плагина Cordova, который позволяет использовать WebRTC на устройствах Android и iOS. В этом уроке я покажу только развертывание на платформе Android, потому что я лично использую Linux. Только развертывание приложения будет другим.

Настроить

Я собираюсь предположить, что вы уже использовали Cordova для разработки гибридных мобильных приложений. Если нет, я предлагаю вам прочитать Руководство по платформе Cordova . В нем есть вся информация, необходимая для того, чтобы самостоятельно настроить, как работать с Cordova.

Как только вы закончите настройку SDK для вашей платформы. Теперь вы можете установить Ionic и Cordova. Сделайте это, выполнив следующую команду в терминале:

npm install -g cordova ionic 

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

Мы будем использовать bower для установки интерфейсных зависимостей, поэтому, если он не установлен, выполните следующую команду:

 npm install -g bower 

Когда все будет готово, создайте новый проект Ionic.

 ionic start koler blank 

Если разбить приведенную выше команду, ionic — это инструмент командной строки, start — команда для запуска новых проектов, koler — имя приложения, а blank — один из шаблонов начальных проектов, предоставляемых Ionic. blank шаблон упрощает работу.

Ниже приведены внешние ссылки для этого проекта:

  • random.js — генерирует случайный идентификатор для каждого пользователя, использующего приложение.
  • socket.ioпередает информацию о сети между узлами.
  • angular socket.io — оболочка angular.js для socket.io.

Вот команда для установки выше.

 cd koler bower install random socket.io angular-socket-io 

Сборка приложения

Вы можете найти код для полного приложения, которое мы будем строить на Github .

Откройте файл index.html в каталоге www проекта и добавьте следующее прямо перед тегом script для ссылки на файл cordova.js . Это позволяет нам использовать только что установленные библиотеки.

 <script src="lib/sio-client/socket.io.js"></script> <script src="lib/angular-socket-io/socket.js"></script> <script src="lib/random/lib/random.min.js"></script> 

Пока вы находитесь внутри каталога www, создайте папку с шаблонами . Здесь мы будем хранить шаблоны HTML, которые будут использоваться для представлений. Первым из этих представлений является call.html . Добавьте следующий код в этот файл.

 <ion-header-bar class="bar-stable"> <h1 class="title">Koler</h1> </ion-header-bar> <ion-content class="padding" ng-controller="CallController"> <div class="card"> <div class="item item-text-wrap"> Your User ID: <strong>{{ id }}</strong> </div> </div> <div class="list"> <label class="item item-input"> <span class="input-label">Peer ID</span> <input type="text" ng-model="peer_id"> </label> </div> <button class="button button-positive button-block" ng-click="startCall()"> Call </button> </ion-content> 

Разбить это. Область заголовка создается с помощью директивы ion-header-bar . Эта угловая директива встроена в Ionic и специально используется для добавления фиксированной строки заголовка над основным контентом.

 <ion-header-bar class="bar-stable"> <h1 class="title">Koler</h1> </ion-header-bar> 

Далее идет обертка для основного контента. Атрибут ng-controller используется, чтобы указать, какой контроллер использует это представление.

 <ion-content class="padding" ng-controller="CallController"> </ion-content> 

Внутри основного контента находится карта для отображения случайного идентификатора текущего пользователя.

 <div class="card"> <div class="item item-text-wrap"> Your User ID: <strong>{{ id }}</strong> </div> </div> 

Ниже приведена форма для вызова пользователя с определенным идентификатором. Обратите внимание на значение атрибута ng-model текстового поля. Вы можете получить текущее значение этого поля из контроллера, используя его имя. В этом случае это peer_id . Под текстовым полем находится кнопка для инициирования вызова. Функция startCall вызывается при нажатии кнопки.

 <div class="list"> <label class="item item-input"> <span class="input-label">Peer ID</span> <input type="text" ng-model="peer_id"> </label> </div> <button class="button button-positive button-block" ng-click="startCall()"> Call </button> 

Теперь мы можем начать кодировать CallController . Внутри каталога js создайте папку контроллеров , где будут храниться контроллеры. Создайте файл CallController.js внутри и добавьте следующий код:

 (function(){ angular.module('starter') .controller('CallController', ['$scope', '$state', '$timeout', '$ionicModal', 'SocketService', CallController]); function CallController($scope, $state, $timeout, $ionicModal, SocketService){ ... } })(); 

Приведенный выше код создает новый контроллер для модуля стартера. Затем мы вводим в него следующее:

  • $scope — доступ к текущей области.
  • $state — доступ к текущему состоянию.
  • $timeout — используется для выполнения функции через указанное количество миллисекунд.
  • $ionicModal — включает использование модального компонента Ionic внутри контроллера.
  • SocketService — Сервис для использования Socket.io.

Внутри контроллера сгенерируйте случайное целое число от 10 000 до 99 999 и назначьте его в качестве идентификатора для текущего пользователя. Сохраните его в переменной $scope чтобы к нему можно было получить доступ из представления.

 var r = new Random(); var id = r.integer(10000, 99999); $scope.id = id; 

Создать объект для хранения текущего контакта. Это пользователь, которому пытается позвонить текущий пользователь.

 $scope.contact = {}; 

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

 $scope.callInProgress = false; $scope.callIgnored = false; $scope.callEnded = false; 

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

 SocketService.emit('login', {'id': id}); 

Создать модал для звонков. Это будет показано пользователю, когда вызов начат, выполняется, завершен или проигнорирован.

 $ionicModal.fromTemplateUrl('templates/call-modal.html', { scope: $scope, animation: 'slide-in-up' }).then(function(modal){ $scope.call_modal = modal; }); 

Создать функцию для вызова. Эта функция будет вызываться, когда пользователь инициирует или отвечает на вызов. Он принимает 2 аргумента, isInitiator который является логическим значением, которое позволяет phoneRTC определять пользователя, который инициировал вызов. И peer_id который используется для указания идентификатора пользователя, к которому подключается текущий пользователь. Я буду ссылаться на текущего пользователя или пользователя, который звонит в качестве инициатора , и на пользователя, который является партнером или принимает вызовы в качестве присоединяющегося, до конца учебника.

 function call(isInitiator, peer_id){ var config = { isInitiator: isInitiator, stun: { host: 'stun:stun.l.google.com:19302' }, turn: { host: 'turn:numb.viagenie.ca', username: 'webrtc@live.com', password: 'muazkh' }, streams: { audio: true, video: false } }; var session = new cordova.plugins.phonertc.Session(config); session.on('sendMessage', function(data){ SocketService.emit('sendMessage', { 'id': id, 'peer_id': $scope.peer_id, 'type': 'phonertc_handshake', 'data': JSON.stringify(data) }); }); session.on('disconnect', function(){ SocketService.emit('sendMessage', { 'id': id, 'peer_id': $scope.peer_id, 'type': 'ignore' }); $scope.call_modal.hide(); }); session.call(); $scope.contact = session; } 

Разбивка вышеприведенного кода. Во-первых, это настройка телефона phone RTC STUN и TURN . Это позволяет установить одноранговое соединение, если какие-либо устройства находятся за брандмауэром или NAT. Вы можете использовать ту же конфигурацию сервера STUN и TURN, которую я использовал, или выбрать из этого списка серверов STUN . Вы можете настроить свой собственный сервер, но я не буду рассказывать об этом в этом уроке.

Наконец, это streams , это позволяет вам установить, будете ли вы захватывать только звук устройства, камеру или и то, и другое. Эта конфигурация затем передается в качестве аргумента сеансу phoneRTC. Этот сеанс будет представлять связь между двумя узлами.

 var config = { isInitiator: isInitiator, stun: { host: 'stun:stun.l.google.com:19302' }, turn: { host: 'turn:numb.viagenie.ca', username: 'webrtc@live.com', password: 'muazkh' }, streams: { audio: true, video: false } }; var session = new cordova.plugins.phonertc.Session(config); 

Создайте обработчик событий, когда сообщение отправляется в текущем сеансе. Это событие вызывается, когда пользователь отправляет сообщение через Socket.io. Когда это событие вызывается, вы должны ответить сообщением phonertc_handshake и передать те же данные, которые были переданы в этом сообщении. Данные преобразуются в строку JSON с помощью JSON.stringify . Другие данные, такие как идентификатор однорангового узла и идентификатор текущего пользователя, передаются так, чтобы сервер сигнализации знал, откуда пришло сообщение и куда оно направляется.

Зачем вам нужно передавать те же данные? Вот как работает WebRTC. Сетевая информация должна обмениваться и проверяться между каждым узлом, чтобы можно было установить соединение. Если вы хотите углубиться в эту тему, я рекомендую прочитать статью « Начало работы с WebRTC» на веб-сайте HTML5Rocks .

 session.on('sendMessage', function(data){ SocketService.emit('sendMessage', { 'id': id, 'peer_id': $scope.peer_id, 'type': 'phonertc_handshake', 'data': JSON.stringify(data) }); }); 

Когда текущий сеанс отключается, отправьте сообщение другому партнеру о том, что вызов игнорируется, а затем скрыте модальный вызов.

 session.on('disconnect', function(){ SocketService.emit('sendMessage', { 'id': id, 'peer_id': $scope.peer_id, 'type': 'ignore' }); $scope.call_modal.hide(); }); 

Наконец, вы делаете вызов и устанавливаете текущий сеанс для переменной контакта в текущей области. Это позволяет вам получать сообщения, отключать или закрывать одноранговое соединение позже.

 session.call(); $scope.contact = session; 

Чтобы инициировать вызов, начните с установки переменной isCalling в значение true . Это будет контролировать то, что показано в модальном вызове. Затем сообщите партнеру, что вы пытаетесь вызвать его, отправив сообщение, содержащее идентификатор текущего пользователя, идентификатор партнера и тип сообщения. Наконец, показать модальный вызов. Приведенная ниже функция вызывается при нажатии кнопки «call» в представлении call.html .

Добавьте этот код в ваш файл контроллера:

 $scope.startCall = function(){ $scope.isCalling = true; $scope.callIgnored = false; $scope.callEnded = false; SocketService.emit('sendMessage', { 'id': id, 'peer_id': $scope.peer_id, type: 'call'}); $scope.call_modal.show(); } 

Модальный вызов содержит следующее. Добавьте этот код в новый файл templates / call-modal.html :

 <ion-modal-view> <div class="bar bar-header item-input-inset bar-calm"> <button class="button button-icon icon ion-ios-arrow-left" ng-click="closeModal()"></button> Call {{ peer_id }} </div> <ion-content class="padding has-header"> <div class="calling-container" ng-if="isCalling &amp;&amp; !callInProgress &amp;&amp; !callIgnored &amp;&amp; !callEnded"> <p>Calling to <span class="balanced">{{ peer_id }}</span>...</p> <button class="button button-assertive" ng-click="ignore()"> Nevermind </button> </div> <div class="calling-container" ng-if="!isCalling &amp;&amp; !callInProgress &amp;&amp; !callIgnored &amp;&amp; !callEnded"> <p><span class="balanced">{{ peer_id }}</span> is calling you</p> <button class="button button-positive" ng-click="answer()"> Answer </button> <button class="button button-assertive" ng-click="ignore()"> Ignore </button> </div> <div class="calling-container" ng-if="callInProgress &amp;&amp; !callIgnored &amp;&amp; !callEnded"> <p>Call in progress...</p> <button class="button button-assertive" ng-click="end()"> End </button> </div> <div ng-if="callIgnored &amp;&amp; !callEnded &amp;&amp; !callInProgress"> <p>Call Ignored</p> <button class="button button-positive button-block" ng-click="closeModal()">Go back</button> </div> <div ng-if="callEnded &amp;&amp; !callIgnored &amp;&amp; !callInProgress"> <p>Call Ended</p> <button class="button button-positive button-block" ng-click="closeModal()">Go back</button> </div> </ion-content> </ion-modal-view> 

Взлом вышеприведенного кода. Заголовок содержит кнопку, которая позволяет пользователю закрыть модальное окно, и заголовок, который сообщает текущему пользователю идентификатор партнера.

 <div class="bar bar-header item-input-inset bar-calm"> <button class="button button-icon icon ion-ios-arrow-left" ng-click="closeModal()"></button> Call {{ peer_id }} </div> 

Это сообщение, которое покажет, является ли текущий пользователь инициатором. Также имеется кнопка для отмены звонка, если столяр не отвечает.

 <div class="calling-container" ng-if="isCalling &amp;&amp; !callInProgress &amp;&amp; !callIgnored &amp;&amp; !callEnded"> <p>Calling to <span class="balanced">{{ peer_id }}</span>...</p> <button class="button button-assertive" ng-click="ignore()"> Nevermind </button> </div> 

Когда присоединяющийся отвечает на вызов, вызов устанавливается, и он покажет следующее. Это позволяет любому из пользователей завершить вызов.

 <div class="calling-container" ng-if="callInProgress &amp;&amp; !callIgnored &amp;&amp; !callEnded"> <p>Call in progress...</p> <button class="button button-assertive" ng-click="end()"> End </button> </div> 

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

 <div ng-if="callIgnored &amp;&amp; !callEnded &amp;&amp; !callInProgress"> <p>Call Ignored</p> <button class="button button-positive button-block" ng-click="closeModal()">Go back</button> </div> <div ng-if="callEnded &amp;&amp; !callIgnored &amp;&amp; !callInProgress"> <p>Call Ended</p> <button class="button button-positive button-block" ng-click="closeModal()">Go back</button> </div> 

Возвращаясь к файлу CallController.js , вот код для закрытия модального режима .

 $scope.closeModal = function(){ $scope.call_modal.hide(); }; 

Когда инициатор игнорирует вызов, соединение с присоединяемым устройством отключается. Если присоединяющийся является тем, кто проигнорировал вызов, то вы должны отправить сообщение инициатору, чтобы сообщить им, что вызов был проигнорирован.

 $scope.ignore = function(){ if(JSON.stringify($scope.contact) === '{}'){ $scope.contact.disconnect(); }else{ SocketService.emit('sendMessage', { 'id': id, 'peer_id': $scope.peer_id, 'type': 'ignore' }); $scope.call_modal.hide(); } }; 

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

 $scope.end = function(){ $scope.contact.close(); $scope.contact = {}; SocketService.emit('sendMessage', { 'id': id, 'peer_id': $scope.peer_id, 'type': 'end' }); $scope.callInProgress = false; $scope.callEnded = true; $scope.call_modal.hide(); }; 

Когда на одноранговый вызов ответит вызов, установите выполняемый вызов и установите соединение с инициатором, вызвав метод call . Поскольку пользователь, отвечающий на вызов, всегда является присоединяющимся, вы должны передать false для параметра isInitiator . Далее отправьте сообщение инициатору, чтобы столяр ответил на звонок. Перед отправкой сообщения вы должны указать время заполнения не менее 1,5 секунд, чтобы установить время для установления соединения между двумя узлами. Поскольку, как только инициатор получает ответное сообщение, пользовательский интерфейс немедленно обновляется. Мы не хотим, чтобы инициатор начал разговор, если соединение еще не установлено.

 $scope.answer = function(){ if($scope.callInProgress){ return; } $scope.callInProgress = true; call(false, $scope.peer_id); setTimeout(function(){ SocketService.emit('sendMessage', { 'id': id, 'peer_id': $scope.peer_id, 'type': 'answer' }); }, 1500); }; 

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

 function onMessageReceive(message){ switch(message.type){ case 'answer': $scope.$apply(function(){ $scope.callInProgress = true; }); call(true, message.id); break; case 'ignore': $scope.callInProgress = false; $scope.callIgnored = true; $scope.callEnded = false; break; case 'phonertc_handshake': $scope.contact.receiveMessage(JSON.parse(message.data)); break; case 'call': $scope.isCalling = false; $scope.callIgnored = false; $scope.callEnded = false; $scope.call_modal.show(); $scope.peer_id = message.id; $scope.current_modal = 'call_modal'; break; case 'end': $scope.callInProgress = false; $scope.callEnded = true; $scope.callIgnored = false; break; } } 

answer сообщение может получить только инициатор. Как было показано ранее, это сообщение отправляется сразу после call метода call , который пытается установить соединение между соединителем и инициатором. Первое, что он делает, — устанавливает текущий вызов, чтобы пользовательский интерфейс обновлялся. Затем соединение устанавливается с помощью call метода call . message.id содержит идентификатор участника.

 case 'answer': $scope.$apply(function(){ $scope.callInProgress = true; }); call(true, message.id); break; 

При ignore установите для переменной callIgnored в области значение true чтобы пользовательский интерфейс обновлялся.

 case 'ignore': $scope.callInProgress = false; $scope.callIgnored = true; $scope.callEnded = false; break; 

Далее идет phonertc_handshake . Это сообщение отправляется каждый раз, когда call метод call . Его единственной целью является получение информации о сети, которая передается узлом. Это важно для установления связи между пирами.

 case 'phonertc_handshake': $scope.contact.receiveMessage(JSON.parse(message.data)); break; 

Когда получено сообщение о call , сбросьте переменные области для управления пользовательским интерфейсом на false , покажите модальный вызов, а затем установите идентификатор узла в качестве переменной области. Идентификатор узла — это идентификатор участника. Таким образом, это конкретное сообщение может быть получено только столяром.

 case 'call': $scope.isCalling = false; $scope.callIgnored = false; $scope.callEnded = false; $scope.call_modal.show(); $scope.current_modal = 'call_modal'; $scope.peer_id = message.id; break; 

После получения сообщения об end вызова обновите пользовательский интерфейс, установив для callEnded значение true .

 case 'end': $scope.callInProgress = false; $scope.callEnded = true; $scope.callIgnored = false; break; 

Метод onMessageReceive вызывается при каждом получении сообщения, добавьте этот код ниже последней функции.

 SocketService.on('messageReceived', onMessageReceive); 

Наконец, после уничтожения текущей области прослушиватель удаляется. Это происходит, когда пользователь переходит на новую страницу или закрывает приложение.

 $scope.$on('$destroy', function(){ SocketService.removeListener('messageReceived', onMessageReceive); }); 

Создайте папку служб в каталоге js , создайте файл SocketService.js и добавьте следующий код.

 (function(){ angular.module('starter') .service('SocketService', ['socketFactory', SocketService]); function SocketService(socketFactory){ return socketFactory({ ioSocket: io.connect('http://yourserver.com:4000') }); } })(); 

Приведенный выше код используется для подключения к серверу Socket.io. Это инициализирует Socket.io, чтобы его можно было использовать как сервис внутри Angular. При этом используется библиотека angular-socket-io, которая была установлена ​​ранее.

В следующей части этого руководства мы рассмотрим создание сервера, который управляет вызовами между пользователями. Если у вас есть какие-либо вопросы на данный момент, пожалуйста, дайте мне знать.