Всем снова привет! На этих выходных я выступал на DevFest Siberia 2016 с проектом IoT домофона. Это была невероятно атмосферная конференция. Но разговор не о ней. Во время доклада я пообещал, что отдельно расскажу на Хабре, как организовать аналог технологии Knock Knock из Google DUO в домофоне.
Небольшая справка от Google:
«Когда вы звоните контакту в Duo, абонент может увидеть ваше видео, если вы входите в число его контактов. Вы увидите абонента только после того, как он ответит на вызов».
В этом туториале мы будем делать код для домофона, так что одна сторона будет уметь звонить, вторая сторона будет уметь принимать звонки. Странно, если бы вы звонили на домофон, это как звонки на таксофон.
Чтобы сделать полноценный видео чат, достаточно просто объединить код обоих примеров на одной странице.
Вызывающая сторона
Давайте сделаем немного html+css, чтобы было красиво. Что выйдет в итоге, вы можете посмотреть на КДПВ статьи.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>Intercom</title>
<script type="text/javascript" src="//cdn.voximplant.com/edge/voximplant.min.js"></script>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<!-- В этом блоке мы сначала покажем свое видео, а потом и если нам ответят, удаленное видео -->
<div class="it_remote_wrapper">
<div class="it_remote_video"></div>
</div>
<!-- В этом блоке будет наше видео, когда нам ответят-->
<div class="it_local_video"></div>
<!-- Отмена звонка -->
<div class="it_exit_link">
<i class="material-icons">call_end</i>
</div>
<!-- Полупрозрачный баннер в шапке -->
<div class="it_connecting">
Соединяем <br> с квартирой
<br>
ждите
</div>
<!-- Звонок гудка -->
<audio src="/assets/31800_81339-lq.mp3" id="beeptone" loop autoplay></audio>
</body>
</html>
Принимающая сторона
<div class="mdl-layout mdl-js-layout">
<main class="mdl-layout__content">
<h1>Please, wait incoming call.</h1>
</main>
<div class="callpopup">
<div class="it_remote_wrapper">
<div class="it_remote_video"></div>
</div>
<div class="it_local_video"></div>
<div class="it_exit_link">
<i class="material-icons">call_end</i>
</div>
<div class="it_start_link">
<i class="material-icons">call</i>
</div>
<div class="it_connecting">
Incoming call
</div>
</div>
О верстке особо нечего сказать. Это дело вкуса и предпочтений.
Теперь добавим сценарий на Voximplant.
VoxEngine.forwardCallToUserDirect(function(call1,call2){
call2.addEventListener(CallEvents.InfoReceived,function(e){
call1.sendMessage(e.body);
});
},true);
Этот код позволяет провести звонок в режиме P2P и передать между телефонами собственную сигнализацию о начале звонка. Тут стоит рассказать о том, как вообще будет проходить звонок и чем этот сценарий отличается от обычного вызова.
В целом, схема такая:
- Павел А вызывает Павла Б
- Павел Б принимает звонок, но не отсылает свои медиа потоки
- Павел Б видет Павла А и на вид решает, стоит ли отвечать
- Павел Б решает ответить и посылает свои медиа потоки Павлу А
- … Renegotiation ...
- PROFIT!
Главное отличие — в применении renegotiation на клиенте.
Давайте напишем код клиентов.
Первая часть — инициализация WebSDK и логин. Для двух сторон она будет идентична.
var vox = VoxImplant.getInstance();
vox.init({micRequired: true,
videoSupport:true,
progressTone:true
});
vox.addEventListener(VoxImplant.Events.SDKReady, function(){
vox.connect();
});
var call;
vox.addEventListener(VoxImplant.Events.ConnectionEstablished,function(){
vox.showLocalVideo(true);
vox.login(settings.users[0].name+"@"+settings.app_name+"."+settings.account_name+".voximplant.com",settings.users[0].pass);
vox.addEventListener(VoxImplant.Events.AuthResult,function(e){
});
});
Дальше заставим код вызывающей стороны показать собственное видео на весь экран.
vox.showLocalVideo(true);
var localvideo = document.querySelector('#voximplantlocalvideo');
document.querySelector('.it_remote_video').appendChild(localvideo);
localvideo.style.height = "100%";
localvideo.play(); //очень важно вызвать play после перемещения, иначе боль и баги
А когда нам по собственной сигнализации (об этом позже) придет информация о принятии звонка, покажем там видео принимающей стороны, а своё видео уберем в угол.
call.addEventListener(VoxImplant.CallEvents.MessageReceived,function(e){
if(e.text=="CONNECTED"){
document.querySelector('.it_local_video').style.display = "block";
document.querySelector('.it_local_video').appendChild(localvideo);
document.querySelector('.it_connecting').style.display = "none";
localvideo.style.height = "140px";
localvideo.style.marginLeft = "-40px";
localvideo.play();
var remotevideo = document.getElementById(call.getVideoElementId());
document.querySelector('.it_remote_video').appendChild(remotevideo);
remotevideo.style.height = "100%";
remotevideo.removeAttribute("height");
remotevideo.removeAttribute("width");
remotevideo.play();
}
});
Ну и заставим звонить второй стороне.
call = vox.call(settings.users[1].name,true);
Теперь вторая сторона.
Инициализацию вставим от первого клиента.
Теперь нам нужно хитро принять вызов. Мы не будем передавать звук и видео.
Теперь напишем код приема звонка.С точки зрения SDK это будет автоответ.
vox.addEventListener(VoxImplant.Events.IncomingCall,function(e){
e.call.sendAudio(false);
document.querySelector('.callpopup').style.display = 'block';
call = e.call;
call.answer();
call.addEventListener(VoxImplant.CallEvents.Connected,function () {
call.mutePlayback();
var remotevideo = document.getElementById(call.getVideoElementId());
document.querySelector('.it_remote_video').appendChild(remotevideo);
remotevideo.style.height = "100%";
remotevideo.removeAttribute("height");
remotevideo.removeAttribute("width");
remotevideo.play();
});
call.addEventListener(VoxImplant.CallEvents.Disconnected,cancelCall);
call.addEventListener(VoxImplant.CallEvents.Disconnected,Failed);
})
Обратите внимание, что мы глушим звук от вызывающей стороны. Чуть позже это поможет нам быстрее начать разговор.
Теперь мы увидим вызывающую сторону. Осталось описать кнопки «Ответить» и «Отбой».
Начнем с «Отбоя»:
function cancelCall() {
if(typeof call!="undefined")
call.hangup();
document.querySelector('.it_remote_video').removeChild(document.querySelector('.it_remote_video').childNodes[0]);
document.querySelector('.it_connecting').style.display = "block";
document.querySelector('.it_exit_link').style.marginLeft = '-110px';
document.querySelector('.it_start_link').style.display = 'block';
document.querySelector('.callpopup').style.display = 'none';
document.querySelector('.it_local_video').style.display = 'none';
}
Кнопка «Ответить» удивительна. В её обработчике содержится ровно половина магии для работы примера. Как только пользователь нажмет её, мы сделаем подключение аудио и видео потока к sdk и выполним renegotiation.
function answerCall(){
document.querySelector('.it_exit_link').style.marginLeft = '-40px';
document.querySelector('.it_start_link').style.display = 'none';
vox.showLocalVideo(true);
var localvideo = document.querySelector('#voximplantlocalvideo');
document.querySelector('.it_local_video').appendChild(localvideo);
document.querySelector('.it_local_video').style.display = 'block';
document.querySelector('.it_connecting').style.display = "none";
localvideo.style.height = "140px";
localvideo.style.marginLeft = "-40px";
localvideo.play();
call.unmutePlayback();
call.sendAudio(true);
call.sendVideo(true);
call.sendMessage('CONNECTED');
}
Ссылка на репозитарий с готовым проектом → github.com/voximplant/demo_callstats.io
Для того чтобы развернуть его, выполните:
npm install
npm start
В процессе вам потребуется ввести ваш api_key и account_id
Автор: Voximplant