
Compartilhar:
Ben é um desenvolvedor que mudou de carreira, tendo atuado anteriormente por uma década nas áreas de educação de adultos, organização comunitária e gestão de organizações sem fins lucrativos. Ele trabalhou como representante de desenvolvedores na Vonage. Escreve regularmente sobre a interseção entre desenvolvimento comunitário e tecnologia. Natural do sul da Califórnia e morador de longa data da cidade de Nova York, Ben reside atualmente perto de Tel Aviv, em Israel.
Gravar uma chamada em Ruby usando a Voice API da Vonage com WebSockets
Tempo de leitura: 10 minutos
A Voice API da Vonage com WebSockets saiu recentemente da fase beta e passou a estar disponível para o público em geral. O WebSockets permite criar uma comunicação bidirecional por meio de uma única conexão TCP persistente. Com o WebSockets, você não precisa lidar com várias solicitações e respostas HTTP. Uma única conexão WebSocket permite a comunicação contínua de dados de texto e binários, mantendo apenas uma única conexão aberta.
Embora os WebSockets possam simplificar o ciclo de solicitações e respostas HTTP, trata-se de um paradigma diferente para a criação de Applications. Felizmente, as linguagens de programação mais utilizadas contam com ferramentas para WebSockets que podem ajudar a reduzir um pouco a complexidade do processo.
Neste tutorial, vamos criar um pequeno servidor web para trabalhar com WebSockets em Ruby. O servidor irá processar chamadas de voz recebidas, conexões WebSocket e renderizar HTML. Usaremos o Rack como nossa interface web e o Thin como nosso servidor web. Este tutorial não exige nenhum conhecimento prévio sobre o uso de WebSockets, mas pressupõe alguma experiência básica com servidores web em Ruby.
Resumo: Se você quiser pular essa parte e simplesmente rodar o aplicativo, pode encontrar uma versão totalmente funcional no GitHub.
Pré-requisitos
Este tutorial requer que o Ruby v2.7 ou superior esteja instalado no seu computador. Além disso, várias gems são utilizadas no aplicativo. Cada uma delas está listada no Gemfile que criaremos mais adiante e serão instaladas executando bundle install na linha de comando:
Agora podemos seguir em frente e começar a implementação do nosso aplicativo.
Account da API da Vonage
Este tutorial também utiliza um número de telefone virtual. Para adquirir um, acesse Numbers > Comprar Numbers e procure um que atenda às suas necessidades.
Nossa última etapa na configuração da nossa conta da API é criar um aplicativo da Voice API. Vamos vincular o número de telefone virtual que provisionamos a esse aplicativo e definir as URLs dos webhooks.
No Painel de Controle da API da Vonage, acesse Seus aplicativos e clique em Criar um novo aplicativo. Isso exibirá a seguinte página:
Dashboard Create Application
As áreas principais nas quais você deve se concentrar ao criar seu aplicativo estão destacadas em roxo:
Nome: Você pode dar ao seu aplicativo o nome que quiser.
Chave pública e privada: Isso irá gerar um par de chaves pública e privada para autenticação. Um arquivo com a chave privada será baixado para o seu computador. Nosso aplicativo lida apenas com chamadas de voz recebidas, portanto, não precisamos fazer nada com ele.
Recursos: Cada application pode oferecer vários recursos. Para nossos objetivos, precisamos apenas ativar Voice.
Depois de definir as opções, você pode clicar no botão botão “Gerar novo pedido” para concluir.
Agora que seu aplicativo foi criado, vamos vinculá-lo ao seu número de telefone recém-ativado e definir as URLs dos webhooks.
Assim como antes, acesse Seus aplicativos no Painel, clique nas reticências ao lado do nome do seu aplicativo e clique na Editar .
No âmbito das seção “Recursos” da página, você verá as seguintes opções:
Application Webhook URL settings
Precisamos preencher o URL do evento e a URL de resposta. O primeiro é para onde a Vonage enviará todos os dados do ciclo de vida da chamada de voz. O segundo é para onde a Vonage enviará cada nova chamada de voz assim que ela for iniciada. As URLs fornecidas aqui devem ser acessíveis externamente para que a Vonage possa acessá-las. Em outras palavras, usar localhost não funciona. Uma opção popular de desenvolvimento é o ngrok, e você pode seguir nosso tutorial sobre como trabalhar com ele.
Certifique-se de que tanto a URL do evento quanto a URL da resposta terminem com /webhooks/event e /webhooks/answer, respectivamente.
Também precisamos vincular nosso número de telefone da Vonage a este aplicativo. Para isso, acesse Numbers > Seus números e clicar no ícone de lápis ao lado do seu número. Em seguida, você pode selecionar seu novo aplicativo entre as opções para vincular o número de telefone a ele. Depois de clicar em Salvar, isso significará que todas as chamadas recebidas nesse número serão encaminhadas para o seu aplicativo.
Criação da estrutura de pastas
Agora que nosso Account e nossas configurações da API da Vonage estão prontos, vamos criar a estrutura de pastas para nosso aplicativo. No final, ela ficará assim:
.
+-- recordings/
+-- views/
| +-- index.html.erb
+-- app.rb
+-- GemfileA pasta raiz do nosso aplicativo conterá app.rb, que será nosso servidor web responsável por lidar com todas as chamadas de voz recebidas e conexões WebSocket. Ela também incluirá a pasta Gemfile, onde definiremos nossas dependências. Haverá mais duas pastas: recordings/ e views/. A recordings/ pasta será onde a gravação da chamada telefônica proveniente da conexão WebSockets da Voice API será salva. A views/ pasta é onde manteremos a única visualização do aplicativo.
Definindo as dependências
Dentro do Gemfile adicionar as seguintes gems que usaremos no aplicativo:
source 'https://rubygems.org'
gem 'wavefile'
gem 'faye-websocket'
gem 'json'
gem 'rack'
gem 'thin'Cada gema terá uma função específica:
Wavefile: Usaremos essa gem para converter os dados de áudio brutos em um arquivo WAV
Faye: Vamos usar essa gem para gerenciar a conexão WebSockets
JSON: Essa gem será usada para converter a instrução de chamada que enviamos de volta à Voice API da Vonage para o formato JSON
Rack: Usaremos o Rack como nossa estrutura de web
Thin: Essa joia nos oferece nosso servidor web com base no Rack
Agora você pode executar bundle install a partir da linha de comando para disponibilizar todas essas gems para sua aplicação.
Montagem do servidor
Dependências e definição de variáveis
Agora estamos prontos para montar nosso servidor web. A primeira coisa que faremos ao montar o servidor é adicionar vários require e include no início do arquivo para incorporar a funcionalidade das gems mencionadas acima em nosso aplicativo:
require 'rack'
require 'erb'
require 'faye/websocket'
require 'json'
require "wavefile"
include Rack
include WaveFileNeste ponto, também definiremos uma variável constante que corresponderá à URL acessível externamente para nossas solicitações de conexão WebSockets. Usaremos essa URL nas instruções que enviaremos de volta à Voice API da Vonage quando recebermos uma nova chamada:
EXTERNAL_WS_URL = 'ws://example.com/cable'Substitua o example.com no trecho acima pela sua URL acessível externamente.
Métodos auxiliares
Nosso aplicativo utilizará dois métodos auxiliares. Podemos criá-los agora e adicioná-los após a declaração das variáveis constantes.
O primeiro método, #create_wav_file, ajudará a realizar o processo de conversão dos dados de áudio binários recebidos pelo WebSocket em um arquivo WAV. Ele utilizará funcionalidades da gem Wavefile para criar o arquivo WAV e também retornará o nome do arquivo para ser usado posteriormente no aplicativo:
def create_wav_file(data, file_name)
buffer = Buffer.new(data, Format.new(:mono, :pcm_16, 16000))
puts "Audio Buffer Created..."
writer = Writer.new(file_name, Format.new(:mono, :pcm_16, 16000))
puts "New Audio File Created..."
puts "Writing to the Buffer..."
writer.write(buffer)
puts "Closing Buffer Writing..."
writer.close
puts "WAV File Created..."
file_name
endO método acima especifica que o áudio recebido provém de uma mono fonte, em vez de uma stereo fonte. A diferença é que há uma única trilha de áudio, definida por uma matriz plana de dados binários, e não por uma matriz de matrizes de dados. O áudio é definido como pcm_16, o que significa que a fonte é PCM linear de 16 bits. Por fim, a fonte é definida como 16000, o que significa que a taxa de amostragem é de 16 kHz. O novo arquivo WAV é criado com as mesmas configurações de áudio dos dados binários de origem.
O segundo método, #erb, é um método curto que usaremos para renderizar arquivos de modelo ERB para o usuário:
def erb(template)
path = File.expand_path("#{template}")
ERB.new(File.read(path)).result(binding)
endO restante do nosso código consistirá em uma série de map instruções que conectam rotas de URL a ações específicas, encapsuladas dentro de Rack::Handler middleware.
Definindo as rotas
As rotas do nosso aplicativo precisam ser definidas dentro do middleware do Rack, que faz a ligação entre o Rack e o Thin. Também usaremos o Rack::Static middleware para servir o arquivo de áudio estático na visualização. Inicializaremos o manipulador Faye WebSocket aqui também:
Rack::Handler::Thin.run(Rack::Builder.new {
Faye::WebSocket.load_adapter('thin')
use(Rack::Static, urls: ["/recording"], root: 'recording')Há quatro rotas que precisamos criar map declarações: /cable, /, /webhooks/answer, e /webhooks/answer. Vamos fazer isso agora.
A primeira rota será responsável pela conexão WebSockets:
map('/cable') do
run(->env{
if Faye::WebSocket.websocket?(env)
puts "WebSockets connection opened..."
@call_data = []
ws = Faye::WebSocket.new(env)
ws.on :message do |event|
if event.data.is_a?(Array)
@call_data.append(event.data.pack('c*').unpack('s*'))
else
puts event.data
end
end
ws.on :close do |event|
puts 'WebSocket connection closed...'
create_wav_file(@call_data.flatten, 'recording/recording.wav')
end
ws.rack_response
end
})
end
No código acima, verificamos se a solicitação de conexão é uma solicitação WebSocket. Se for, instanciamos uma nova instância de Faye::WebSocket. Existem dois tipos possíveis de dados enviados a um WebSocket: texto ou dados binários. Estes últimos são sempre enviados na forma de inteiros do tamanho de um byte em uma matriz.
Assim, podemos verificar se o event.data é um objeto de matriz ou não. Se for uma matriz, sabemos que se trata dos dados de áudio binários que usaremos para criar nosso arquivo WAV. Se não for, então são atualizações de status da Voice API da Vonage. Nesse caso, podemos registrá-las no console.
Uma observação importante a ser considerada: a gem Faye WebSockets converte os dados binários em inteiros do tamanho de um byte, conforme mencionamos acima. Isso, no entanto, significa que ela os converte em inteiros de exatamente 1 byte ou 8 bits. A Voice API da Vonage envia os dados binários de áudio em inteiros de 16 bits, um padrão comum para a fala humana. Isso significa que nosso aplicativo precisa converter os 8 bits de dados binários em 16 bits. Utilizamos os métodos da biblioteca padrão do Ruby #pack e #unpack ao anexá-los à nossa @call_data variável de instância. Essa é uma etapa necessária para produzir um áudio compreensível.
A próxima rota atenderá a index visualização. Ela verificará se há um arquivo e, caso haja, o passará para a visualização com variáveis de instância:
map('/') do
if File.exist?('recording.wav')
@call_status = 'Audio Loaded!'
@file = 'recording.wav'
end
run(->env{
[200, { 'Content-Type' => 'text/html'}, [erb("views/index.html.erb")]]})
end
A /webhooks/answer rota retornará à Voice API da Vonage instruções especializadas chamadas de NCCO (Nexmo Call Control Object) , informando à API o que fazer com a chamada que acabou de receber. A instrução que enviamos de volta no formato JSON indicará à Voice API que queremos abrir uma conexão WebSocket e fornecerá a ela a URL do WebSocket para iniciar a conexão. Também informamos à Voice API que queremos que ela transmita ao chamador uma breve mensagem, informando que a chamada será transmitida em tempo real em breve:
map('/webhooks/answer') do
run(->env{
ncco = [
{
"action": "talk",
"text": "You will be streaming momentarily."
},
{
"action": "connect",
"endpoint": [
{
"type": "websocket",
"uri": "#{EXTERNAL_WS_URL}",
"content-type": "audio/l16;rate=16000",
}
]
}
].to_json
[200, { 'Content-Type' => 'application/json' }, [ncco]]
})
end
A rota final que criaremos irá lidar com os dados do ciclo de vida do evento da chamada que a Voice API envia para nosso aplicativo. Não queremos fazer nada com esses dados, exceto confirmar à API que os recebemos com um 200 código de status:
map('/webhooks/event') do
run(->env{
[200, { 'Content-Type' => 'text/html'}, ['']]
})
end
Por fim, fechamos o Rack::Builder bloco que abrimos logo no início, especificando uma porta na qual nossa aplicação será executada:
}, Port: 9292)O último elemento que precisamos criar antes de podermos executar nosso aplicativo é nossa visualização.
Criação da visualização
O aplicativo possui apenas uma única visualização. É possível acessar essa visualização acessando a URL raiz do aplicativo, ou seja, localhost:9292 ou 127.0.0.1:9292. A visualização apresentará o áudio a ser reproduzido com um <audio> elemento HTML:
<html>
<head>
<title>Ruby Vonage WebSockets Demo</title>
</head>
<body>
<h1>Vonage WebSockets + Ruby == ♥</h1>
<p>Welcome to the Vonage WebSockets demo in Ruby</p>
<h2>Your Audio To Playback</h2>
<p>Once you have finished your call, your audio will be available to playback from here.</p>
<div id="audio-status">
<%= @call_status %>
<br />
<% if @file %>
<audio
controls
src="recording/<%= @file %>">
Your browser does not support the
<code>audio</code> element.
</audio>
<% end %>
</div>
</body>
</html>
A visualização utiliza as variáveis de instância que criamos na rota para determinar se deve ou não exibir o <audio> elemento.
Agora estamos prontos para executar o aplicativo!
Executando o aplicativo
O aplicativo já está pronto para ser executado. Para executá-lo, digite o seguinte na linha de comando, na pasta raiz do aplicativo:
Lembre-se também de verificar se o seu servidor web está acessível externamente usando o ngrok ou outra ferramenta semelhante.
Nesse momento, ligue para o seu aplicativo usando o seu número de telefone virtual da Vonage. Quando terminar a ligação, você pode desligar. Se acessar o aplicativo pelo navegador da web, você verá agora um reprodutor de áudio e poderá reproduzir a gravação. Parabéns!
Leitura complementar
Este tutorial demonstrou as funcionalidades básicas para começar a usar o recurso WebSockets da Voice API da Vonage em Ruby. Há muito mais que você pode fazer com esse recurso. Para saber mais sobre o recurso WebSockets da Voice API da Vonage, confira o seguinte:
Compartilhar:
Ben é um desenvolvedor que mudou de carreira, tendo atuado anteriormente por uma década nas áreas de educação de adultos, organização comunitária e gestão de organizações sem fins lucrativos. Ele trabalhou como representante de desenvolvedores na Vonage. Escreve regularmente sobre a interseção entre desenvolvimento comunitário e tecnologia. Natural do sul da Califórnia e morador de longa data da cidade de Nova York, Ben reside atualmente perto de Tel Aviv, em Israel.