Telefonou! Como montar seu discador da Web em uma hora



Amigos, hoje eu quero falar com você sobre ligações. Para alguns, este é um tópico completamente novo. Para outros, é pura diversão para os fãs no nível de "mas devo enviar meu Skype para mim?" Para o terceiro, uma súbita necessidade de vida. A última opção é a nossa opção.


Neste artigo, mostrarei um exemplo de implementação pequeno, mas muito viável, que permitirá que você faça literalmente seu próprio discador WEB e chame um amigo diretamente do navegador, literalmente no joelho de várias dezenas de linhas de javascript.


Sobre tecnologias e protocolos




Estamos em 2019 e, para nossa satisfação, já existe uma ferramenta pronta para implementar a Reality C ommunication (RTC) para a Web, ou seja, WebRTC . Alguns anos atrás, ele estava em desenvolvimento ativo. A API ainda está sendo finalizada, mas a tecnologia se tornou o padrão de fato e é suportada nos navegadores mais populares. Neste artigo, não abordaremos a própria tecnologia; você pode ler mais no site dos desenvolvedores ou procurar artigos no hub Por exemplo, aqui .


Mas antes de começarmos, quero esclarecer alguns pontos.


  1. Primeiro, o WebRTC roda em cima de um conjunto de protocolos e, mesmo para comunicação p2p, você precisará de algum tipo de servidor através do qual seus clientes possam encontrar e fazer amizade. Nosso exemplo usará o protocolo SIP, sobre o qual você pode ler mais, por exemplo, aqui .
  2. Você precisará de um servidor com suporte para todos os itens acima - como FreeSwitch ou Asterisk.

Deixamos essas coisas fora do escopo do artigo. Assumiremos que você tem a mesma sorte que você e já configurou a telefonia VoIP à sua disposição.


Bem, a parte mais longa do artigo está por trás, vamos codificar!


Layout da página




Primeiro, precisamos de uma página com a qual ligaremos, campos para inserir um nome de usuário, senha, número de telefone e alguns botões. Na versão mais simples, será algo parecido com isto:


<div class="container"> <div class="input-group mb-6"> <div class="input-group-prepend"> <span class="input-group-text">Login</span> </div> <input id="loginText" type="text" class="form-control"> <div class="input-group-prepend"> <span class="input-group-text">Password</span> </div> <input id="passwordText" type="password" class="form-control"> <button id="loginButton" type="button" class="btn btn-primary" onclick="login()">Login</button> <button id="logOutButton" type="button" class="btn btn-primary d-none" onclick="logout()">LogOut</button> </div> <div class="input-group mb-6 d-none" id="callPanel"> <input id="callNumberText" type="text" class="form-control" placeholder="Call number"> <button id="callNumberButton" type="button" class="btn btn-success" onclick="call()">Call</button> <button id="hangUpButton" type="button" class="btn btn-danger d-none" onclick="hangUp()">Hang Up</button> </div> <audio id="localAudio" autoPlay muted></audio> <audio id="remoteAudio" autoPlay></audio> <audio id="sounds" autoPlay></audio> </div> 

Os elementos de áudio “enviam” e “recebem” som e, para beleza, através dos sons, reproduzem sons dial-up.


A interface do usuário está pronta, você não encontra falhas no UX, vamos fazê-lo funcionar.


Prendemos JSSIP




Usaremos uma biblioteca na qual tudo o que é necessário já está implementado - JSSIP . Você pode ver a documentação: tudo é descrito com bastante detalhes e há até um exemplo de implementação pronta. Ou seja, praticamente não precisamos fazer nada - apenas simplifique tudo o máximo possível e descubra o que é o quê.


Depois de inserir o login / senha (deve ser registrado no seu servidor de telefonia), é necessário efetuar login no servidor. Fazemos isso:


 socket = new JsSIP.WebSocketInterface("wss://webrtcserver:port/ws"); _ua = new JsSIP.UA( { uri: "sip:" + this.loginText.val() + "@webrtcserver", password: this.passwordText.val(), display_name: this.loginText.val(), sockets: [socket] }); 

Ao longo do caminho, você pode se inscrever nos eventos de conexão e conexão e fazer algo útil lá. Mas vamos ao evento de registro:


 his._ua.on('registered', () => { console.log("UA registered"); this.loginButton.addClass('d-none'); this.logOutButton.removeClass('d-none'); this.loginText.prop('disabled', true); this.passwordText.prop('disabled', true); $("#callPanel").removeClass('d-none'); }); 

Aqui só precisamos alterar o status dos botões: mostrar o necessário, ocultar o desnecessário. E se de repente algo deu errado com o login, cuspimos o erro no log:


 this._ua.on('registrationFailed', (data) => { console.error("UA registrationFailed", data.cause); }); 

Isso é suficiente para o login. Resta obter o órgão de barril com
this._ua.start ();


Se o servidor for especificado corretamente e seu nome de usuário / senha for aceito, um campo para inserir o telefone e o botão Ligar serão exibidos.


Para registrar, você precisa chamar this._ua.stop (), tudo é simples.


Faça uma ligação


Agora - o mais importante: você precisa ligar para o número digitado.


 this.session = this._ua.call(number, { pcConfig: { hackStripTcp: true, //   ,       rtcpMuxPolicy: 'negotiate', //   ,   multiplexing.       . iceServers: [] }, mediaConstraints: { audio: true, //    video: false }, rtcOfferConstraints: { offerToReceiveAudio: 1, //    offerToReceiveVideo: 0 } }); 

Observe: habilitamos explicitamente a multiplexação, essa configuração também deve ser ativada no seu servidor. No caso de um asterisco, isso é rtcp_mux = yes nas configurações do sip.conf.


Interações adicionais são baseadas em retornos de chamada, nos quais devemos garantir a direção do fluxo de áudio e vídeo para o elemento da página correspondente e enviar as mensagens necessárias na ordem correta para o servidor.


 //    this.session.on('progress', () => { console.log("UA session progress"); playSound("ringback.ogg", true); }); //      this.session.on('connecting', () => { console.log("UA session connecting"); playSound("ringback.ogg", true); //          ,     let peerconnection = this.session.connection; let localStream = peerconnection.getLocalStreams()[0]; // Handle local stream if (localStream) { // Clone local stream this._localClonedStream = localStream.clone(); console.log('UA set local stream'); let localAudioControl = document.getElementById("localAudio"); localAudioControl.srcObject = this._localClonedStream; } //       ,        peerconnection.addEventListener('addstream', (event) => { console.log("UA session addstream"); let remoteAudioControl = document.getElementById("remoteAudio"); remoteAudioControl.srcObject = event.stream; }); }); //   , ,    this.session.on('failed', (data) => { console.log("UA session failed"); stopSound("ringback.ogg"); playSound("rejected.mp3", false); this.callButton.removeClass('d-none'); this.hangUpButton.addClass('d-none'); }); // ,  this.session.on('ended', () => { console.log("UA session ended"); playSound("rejected.mp3", false); JsSIP.Utils.closeMediaStream(this._localClonedStream); this.callButton.removeClass('d-none'); this.hangUpButton.addClass('d-none'); }); //  ,    this.session.on('accepted', () => { console.log("UA session accepted"); stopSound("ringback.ogg"); playSound("answered.mp3", false); }); 

Em geral, tudo é bastante lógico. Durante a discagem ['progress'] - reproduza os sons da discagem. Em nosso exemplo, tocamos nosso próprio som, mas, como observou pvsur corretamente, você também pode obtê-lo do lado chamado e ouvir a resposta do autoinformer como "deixe uma mensagem após o sinal sonoro", se houver um.
Assim que eu terminar ['aceito'] - toque o som respondido. Assim que o assinante atender o telefone, obteremos o fluxo de som e o colocaremos no elemento remoteAudio ['connect' e 'addstream'].
No final da chamada, faça closeMediaStream. Você pode relaxar.


Um pouco sobre a operação


Durante o teste, duas coisas foram descobertas.


  1. No chrome, houve um atraso de vários segundos no início da discagem, o que foi muito irritante. Descobrimos pelos registros que ele foi ao servidor de gelo, o que não era necessário, uma vez que possuímos nosso próprio servidor. Portanto, na configuração do JSSIP, simplesmente os removemos e imediatamente nos tornamos mais bonitos. Consulte pcConfig.iceServers e pcConfig.hackStripTcp.
  2. Nosso asterisco possui um protocolo WSS com criptografia para SIP. Isso é exigido pelo navegador ao usar o site HTTPS. Mas o asterisco usa o WS com base nos parâmetros de contato nos quais a biblioteca JSSIP contém um descritor do WS codificado. Os desenvolvedores da biblioteca apontam ao mesmo tempo para padrões nos quais realmente não há requisitos sobre esse assunto. E colegas do aster persistentemente não querem consertar nada. Em geral, um beco sem saída. Bem, neste momento, encontramos na fonte a linha this._configuration.contact_uri = new URI (...), altere transport: 'ws' para transport: 'wss' e continuamos a aproveitar a vida.

Em geral, o exemplo está pronto, você pode atender e ligar. Não há necessidade de colocar nenhum softphone ou desenvolver o seu próprio. Não precisa se preocupar em implantar esse software nos carros clientes. Basta abrir o navegador e ligar.


Outra biblioteca permite discar números adicionais no modo de tom. Ou seja, é possível ligar, por exemplo, para a central de atendimento do banco e chegar ao item desejado no menu de voz. Para fazer isso, basta executar o seguinte comando:


 this._call.sendDTMF('. ') 

Sobre Fakapy




Houve vários pontos que realmente me deixaram nervoso.


Deixei esta parte fora do escopo do artigo, mas, além das chamadas realizadas, também é necessário aceitar as chamadas recebidas. E por algum tempo tive que me agachar com uma ligação recebida, que veio e depois interrompeu. Tudo foi decidido pela configuração rtcpMuxPolicy mencionada acima e permitindo a multiplexação no asterisco, mas éramos estúpidos por algum tempo.


E ainda há problemas com a discagem - quando uma chamada e uma chamada são feitas na mesma máquina. Não me lembro exatamente, mas a conexão foi estabelecida com sucesso, também não houve erros e nenhum som :) O tempo estava acabando, então pontuamos nesse efeito especial. Mas lembre-se de que testar as chamadas recebidas é melhor em um carro separado.


Conclusão


Por fim, quero observar que testamos o pacote JSSIP + Asterisk em nossa central de atendimento, tudo funciona bem, pelo menos no chrome, o que nos convém completamente. O principal é permitir ao navegador acessar dispositivos de mídia e registrar usuários no servidor de discagem.


Você pode ver o exemplo finalizado no github .


Links úteis


Sobre o webrct
Sobre o SIP: tyts , tyts
Sobre o Asterisk
Biblioteca Jssip

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


All Articles