Chamada de vídeo no navegador no PeerJS. Início rápido

Saúdo todos os leitores de Habr. Este ano, tive a oportunidade de escrever um módulo de comunicação por vídeo para um portal de treinamento para telefonar via comunicação por vídeo diretamente no site do professor e do aluno. Não foi necessário resolver uma tarefa tão precoce. Após uma breve pesquisa, descobri que existem duas maneiras: Flash e WebRTC . O WebRTC, em sua forma pura, acabou sendo complicado e, em geral, é natural, pois a tarefa da comunicação por vídeo não é simples. Mas então me deparei com o PeerJS , que é um invólucro para o WebRTC. Neste artigo, mostrarei como organizar rapidamente o discador do navegador.

Para repetir o exemplo, será necessário o acesso à sua página de teste através do protocolo https (já que a página solicitará acesso à câmera e ao microfone, e sem um protocolo seguro, o navegador simplesmente apresentará um erro)

O layout inicial terá a seguinte aparência:

<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Peer</title> <script src="https://unpkg.com/peerjs@1.0.0/dist/peerjs.min.js"></script> </head> <body> <p><h3> ID: </h3><span id=myid ></span></p> <input id=otherPeerId type=text placeholder="otherPeerId" > <button onclick="callToNode(document.getElementById('otherPeerId').value)"></button> <br> <video id=myVideo muted="muted" width="400px" height="auto" ></video> <div id=callinfo ></div> <video id=remVideo width="400px" height="auto" ></video> </body> 

Na seção principal, conectamos o PeerJS remotamente. Também é possível baixar o script e conectá-lo localmente.

input id = otherPeerId - projetado para entrar no banquete daquele a quem ligaremos (você pode tomá-lo como índice ou como número de telefone).

Duas tags de vídeo foram projetadas para exibir seu próprio vídeo e para o vídeo do interlocutor, respectivamente.

Agora, um pouco sobre a tecnologia WebRTC e como a chamada é feita. O WebRTC faz uma chamada de cliente para cliente diretamente, sem o envolvimento do servidor, portanto, na primeira etapa, os navegadores devem se encontrar. Para fazer isso, um WebRTC clássico requer um servidor de sinal, ou seja, um servidor que informa a um navegador os parâmetros de outro navegador e, no WebRTC, você mesmo deve organizar esse servidor. No entanto, os desenvolvedores do PeerJS fornecem seu próprio servidor de sinal. Tudo o que você precisa fazer é passar o peerID para o interlocutor em potencial, ou seja, o índice exclusivo recebido no sistema PeerJS. Em um rascunho de trabalho, organizei-o assim:

  1. Depois de carregar a página, um objeto Peer é criado
  2. Seu peerID é gravado no banco de dados mysql
  3. Quando o botão Ligar é pressionado, o peerID do interlocutor é retirado do banco de dados e usado para estabelecer uma conexão

No caso de teste atual, inseriremos o peerID do interlocutor no campo de texto otherPeerId

Então, vamos começar a escrever código.

1. Crie o objeto de mesmo nível principal

 var peer = new Peer(); 

2. Na abertura do banquete, receberemos o cobiçado peerID, que deve ser transferido para o parceiro para que ele possa entrar em contato conosco

 peer.on('open', function(peerID) { document.getElementById('myid').innerHTML=peerID; }); 

3. Para receber uma chamada, desligamos o manipulador do evento de chamada

 var peercall; peer.on('call', function(call) { // Answer the call, providing our mediaStream peercall=call; document.getElementById('callinfo').innerHTML="  <button onclick='callanswer()' ></button><button onclick='callcancel()' ></button>"; }); 

Com uma chamada recebida, obtemos um objeto de chamada , que salvamos na variável global peercall . Também no bloco de informações, uma notificação sobre uma chamada recebida e 2 botões será exibida: Aceitar e Rejeitar

4. Escrevemos uma função para o botão Aceitar

 function callanswer() { navigator.mediaDevices.getUserMedia ({ audio: true, video: true }).then(function(mediaStream) { var video = document.getElementById('myVideo'); peercall.answer(mediaStream); //         //peercall.on ('close', onCallClose); //  -  video.srcObject = mediaStream; //      (  ) document.getElementById('callinfo').innerHTML=" ... <button onclick='callclose()' > </button>"; //,   ,     video.onloadedmetadata = function(e) {// ,    video.play(); }; setTimeout(function() { //        document.getElementById('remVideo').srcObject = peercall.remoteStream; document.getElementById('remVideo').onloadedmetadata= function(e) { //       document.getElementById('remVideo').play(); }; },1500); }).catch(function(err) { console.log(err.name + ": " + err.message); }); } 

navigator.mediaDevices.getUserMedia - solicita acesso à câmera e ao microfone. Nos dados do objeto que é passado para esse método {audio: true, video: true}, você pode solicitar acesso apenas à câmera ou ao microfone. Comentários adicionais foram adicionados diretamente ao código.

setTimeout foi adicionado empiricamente: o vídeo do parceiro não começou a ser reproduzido, mas funcionou com um tempo limite.

5. A função de discagem pelo botão Ligar

 function callToNode(peerId) { // navigator.mediaDevices.getUserMedia ({ audio: true, video: true }).then(function(mediaStream) { var video = document.getElementById('myVideo'); peercall = peer.call(peerId,mediaStream); //,  peerId-    mediaStream peercall.on('stream', function (stream) { // ,   setTimeout(function() { document.getElementById('remVideo').srcObject = peercall.remoteStream; document.getElementById('remVideo').onloadedmetadata= function(e) { document.getElementById('remVideo').play(); }; },1500); }); // peercall.on('close', onCallClose); video.srcObject = mediaStream; video.onloadedmetadata = function(e) { video.play(); }; }).catch(function(err) { console.log(err.name + ": " + err.message); }); } 

Como no parágrafo anterior, solicitamos seu fluxo de mídia. Depois de chamarmos a função de chamada do objeto de mesmo nível, que nos retornará o objeto de chamada, salve-o em peercall. Processamos o evento de fluxo para descobrir o que eles responderam e colocamos o fluxo de entrada no objeto de vídeo correspondente

Isso é tudo, mas ...

Se os dois chamadores estiverem atrás da NAT, a chamada não será atendida. (Por quê? Leia aqui habr.com/en/company/yandex/blog/419951 )
Para superar esse obstáculo, é necessário especificar o servidor TURN ao criar o objeto de mesmo nível (a questão de onde obtê-lo não era a mais fácil. Tivemos que criar nosso próprio: VPS no Ubuntu 16.04. Instalação com o comando
 apt install coturn 
)

Então a criação da festa será mais ou menos assim:

 var callOptions={'iceServers': [ {url: 'stun:95.xxx.xx.x9:3479', username: "user", credential: "xxxxxxxxxx"}, { url: "turn:95.xxx.xx.x9:3478", username: "user", credential: "xxxxxxxx"}] }; peer= new Peer({config: callOptions}); 

Finalmente, todo o código:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Peer</title> <script src="https://unpkg.com/peerjs@1.0.0/dist/peerjs.min.js"></script> </head> <body> <p><h3> ID: </h3><span id=myid ></span></p> <input id=otherPeerId type=text placeholder="otherPeerId" > <button onclick="callToNode(document.getElementById('otherPeerId').value)"></button> <br> <video id=myVideo muted="muted" width="400px" height="auto" ></video> <div id=callinfo ></div> <video id=remVideo width="400px" height="auto" ></video> <script> var callOptions={'iceServers': [ {url: 'stun:95.xxx.xx.x9:3479', username: "user", credential: "xxxxxxxxxx"}, { url: "turn:95.xxx.xx.x9:3478", username: "user", credential: "xxxxxxxx"}] }; peer= new Peer({config: callOptions}); peer.on('open', function(peerID) { document.getElementById('myid').innerHTML=peerID; }); var peercall; peer.on('call', function(call) { // Answer the call, providing our mediaStream peercall=call; document.getElementById('callinfo').innerHTML="  <button onclick='callanswer()' ></button><button onclick='callcancel()' ></button>"; }); function callanswer() { navigator.mediaDevices.getUserMedia ({ audio: true, video: true }).then(function(mediaStream) { var video = document.getElementById('myVideo'); peercall.answer(mediaStream); //         //peercall.on ('close', onCallClose); //  -  video.srcObject = mediaStream; //      (  ) document.getElementById('callinfo').innerHTML=" ... <button onclick='callclose()' > </button>"; //,   ,     video.onloadedmetadata = function(e) {// ,    video.play(); }; setTimeout(function() { //        document.getElementById('remVideo').srcObject = peercall.remoteStream; document.getElementById('remVideo').onloadedmetadata= function(e) { //       document.getElementById('remVideo').play(); }; },1500); }).catch(function(err) { console.log(err.name + ": " + err.message); }); } function callToNode(peerId) { // navigator.mediaDevices.getUserMedia ({ audio: true, video: true }).then(function(mediaStream) { var video = document.getElementById('myVideo'); peercall = peer.call(peerId,mediaStream); peercall.on('stream', function (stream) { // ,   setTimeout(function() { document.getElementById('remVideo').srcObject = peercall.remoteStream; document.getElementById('remVideo').onloadedmetadata= function(e) { document.getElementById('remVideo').play(); }; },1500); }); // peercall.on('close', onCallClose); video.srcObject = mediaStream; video.onloadedmetadata = function(e) { video.play(); }; }).catch(function(err) { console.log(err.name + ": " + err.message); }); } </script> </body> 

Esta solução foi testada com sucesso no Windows7 e Ubuntu 18.04 no Chrome, Opera, Firefox. O Chrome também funciona no Android e MacOS, mas não no iPhone e iPad.

Source: https://habr.com/ru/post/pt468705/


All Articles