
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.
- 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 .
- 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,
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.
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.
- 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.
- 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