Transformando chamadas telefônicas em videoconferências
A Video API da Vonage permite que você crie praticamente qualquer experiência de vídeo que desejar. Muitas vezes, um participante pode estar em uma área onde a cobertura de internet não é muito boa, seja por problemas na rede de celular ou com o provedor de internet, mas ainda assim precisa participar de uma reunião. Vamos mostrar como você pode fazer com que participantes sem vídeo liguem para uma reunião e participem dela.
Neste tutorial
A Video API da Vonage permite que você ofereça aos usuários a opção de ligar para participar de uma videoconferência ou ligar para um usuário e fazer com que ele entre diretamente na reunião. Vamos explicar como configurar e executar uma demonstração usando nossos exemplos já prontos, para que você não precise escrever nenhum código, mas também explicaremos o que o código está fazendo nos bastidores.
- Veja a demonstração - Confira a demonstração sem precisar escrever nenhum código
- Como funciona a demonstração – Lado do cliente - O que o código do lado do cliente está fazendo
- Como funciona a demonstração – O lado do servidor - O que o código do lado do servidor está fazendo
Pré-requisitos
Para concluir o tutorial, você precisa de:
- A Account da Vonage - para sua chave e seu segredo da API
Veja a demonstração
Se você quiser conferir a demonstração antes de escrevermos qualquer código, temos um servidor web de exemplo e um código em JavaScript para você testar como funciona uma videochamada básica. Todo o código é de código aberto e está disponível publicamente; assim, você pode experimentar a demonstração e, em seguida, usar o código para fazer suas próprias modificações.
Inicie o servidor Node.js
A demonstração em vídeo requer um servidor de back-end para lidar com tarefas como a criação de tokens de cliente para autorização e o gerenciamento geral de sessões. Embora você possa desenvolvê-lo em qualquer linguagem de sua preferência, temos um servidor já pronto que você pode usar para começar, disponível em Servidor de Aprendizado por Vídeo da Vonage (Node.js) no Code Hub. A partir do Documentação para desenvolvedores, clique em “Code Hub” na barra de navegação superior e, em seguida, role a página para baixo até encontrar o cartão “Vonage Video Learning Server (Node.js)”. Clique nele para abri-lo.
Você receberá uma descrição do que este projeto faz. Por enquanto, vamos clicar em “Obter código” para que possamos carregá-lo no editor online do Code Hub. Clique em “Criar um novo ambiente de desenvolvimento”. Nomeie o espaço de trabalho como “Vonage Video Demo”, já que podemos usar esse back-end para várias demonstrações. Esta demonstração exige que um número seja atribuído a ela, já que o servidor de aprendizagem suporta chamadas telefônicas via SIP. Embora não vamos usar isso nesta demonstração, vá em frente e clique em “Atribuir um número” para atribuir um número existente que você tenha da Vonage ou compre um novo para usar em demonstrações futuras.

O Code Hub criará automaticamente um aplicativo para você, incluindo a configuração das chaves pública e privada que nosso aplicativo utilizará. Assim que o espaço de trabalho for criado, você será direcionado para o editor de código, que é uma versão online do Visual Studio Code. Fique à vontade para acompanhar as partes seguintes desta demonstração para ver o código, e você pode editá-lo conforme necessário para seus próprios projetos.

Para executar o aplicativo, clique em “Exibir” na parte superior do editor e, em seguida, em “Terminal”. Isso abrirá uma linha de comando na qual podemos executar comandos. Basta digitar vcr deploy e o código será implantado. Isso levará alguns instantes, pois o sistema empacota o código e o executa nos servidores do Vonage Code Hub. É importante anotar o “endereço do host da instância” que é exibido perto do final.

Se tudo estiver funcionando corretamente, você deverá conseguir acessar o “endereço do host da instância” e ver a seguinte página:

Teste o front-end
O servidor de back-end funciona diretamente com todas as nossas demonstrações pré-criadas, incluindo esta demonstração individual. Acesse https://github.com/Vonage-Community/Video API web samples/tree/main/SIP, que é o código-fonte da parte front-end desta demonstração. Este exemplo permite que vários usuários, por meio da URL, participem de um bate-papo de voz por vídeo ou por um número de telefone, além de permitir que um anfitrião ligue para um número.
A maneira mais fácil de executar esta demonstração é clicar no botão “Abrir no Stackblitz” no arquivo README.

Isso abrirá o projeto no Stackblitz. Assim como no servidor de back-end, você pode examinar o código e modificá-lo aqui, se quiser. Para esta demonstração, tudo o que precisamos fazer é abrir o js/config.js arquivo e insira a URL da instância do Code Hub no SAMPLE_SERVER_BASE_URL variável:

Depois de salvar o arquivo, você pode atualizar a visualização da demonstração no lado direito do Stackblitz, e seu navegador deve solicitar que você autorize o uso do microfone e da câmera. Assim que você autorizar, sua imagem deve aparecer no canto inferior da barra lateral. Se você copiar a URL do Stackblitz acima do painel de demonstração e acessá-la no seu celular, em outro computador ou enviá-la a um amigo, qualquer pessoa que acessar o link deverá estar conectada à sua demonstração!
Como funciona a demonstração
Configurar uma aplicação da Vonage
Para que nosso aplicativo de vídeo funcione, precisamos de uma maneira de nosso cliente e servidor se comunicarem com os servidores da Vonage. O Code Hub configura isso para nós, mas se você estiver executando o código localmente ou quiser saber o que isso envolve, saiba que um aplicativo de vídeo é configurado exatamente como qualquer outra API. Precisamos configurar uma aplicação da Vonage para abrigar toda a configuração da nossa aplicação, além de ajudar a gerar os itens necessários para que possamos realizar a autenticação.
Dê uma passada no seu Painel do Cliente da Vonage e faça login. Depois de fazer login:
- Clique em “Applications” na seção “Compilação”.
- Clique em “Criar um novo aplicativo”.
- Dê um nome ao aplicativo, como “Demonstração básica de vídeo”.
- Clique em “Gerar chave pública e privada”, o que fará com que você baixe um arquivo chamado
private.key. Guarde esse arquivo para consultar mais tarde. - Role a página para baixo e ative a opção “Vídeo”. Por enquanto, vamos deixar esses campos em branco.
- Clique em “Gerar novo pedido” para criar o pedido.
Assim que o aplicativo for criado, anote o ID do aplicativo. Se você estiver executando o código localmente, precisaremos desse ID para configurar o backend. Se estiver usando o Code Hub, o código do servidor já tem acesso ao ID do aplicativo e à chave privada.
O lado do cliente
A parte do lado do cliente da demonstração consiste em alguns componentes diferentes — alguns elementos HTML para exibir os feeds de vídeo, código JavaScript para obter as informações de login e se comunicar com os servidores do Vonage Video, e código JavaScript para chamar o servidor de back-end a fim de realizar chamadas.
Como se trata de uma demonstração no navegador, utilizamos o SDK do JavaScript disponível em https://unpkg.com/@vonage/client-sdk-video@latest/dist/js/opentok.js, e incluir isso em uma tag script no nosso HTML em index.html.
Para adicionar pessoas a uma sala, precisamos apenas de dois elementos: um lugar para colocar o usuário atual — por exemplo, você —, que chamamos de “publisher”. Em seguida, precisamos de um lugar para colocar qualquer outra pessoa que entre na sala, à qual você irá “se inscrever”. Vamos colocá-las no elemento “subscribers”.
Vamos criar dois div elementos e atribuir a um deles um ID de publisher e o outro, um ID de subscriber. Faremos referência a esses elementos no JavaScript quando a página for acessada e quando detectarmos que outro usuário entrou na videochamada.
// index.html
<div>
<h2 class="font-black text-2xl">Your Camera</h2>
<div class="h-80 w-80" id="publisher"></div>
</div>
<div>
<h2 class="font-black text-2xl">Guests</h2>
<div class="h-80 w-80" id="subscriber"></div>
</div>
Temos, então, dois conjuntos de controles. O primeiro permite que nossa videoconferência ofereça recursos de acesso por discagem. Criaremos dois botões para ativar essa funcionalidade.
<div><h2 class="font-black text-2xl">Dial Options</h2></div>
<div>
<h3 class="font-black text-xl">Phone Conference</h3>
<p>You can start a phone conference to let people dial in directly. They can call the following number to join once you have started the conference:</p>
<p id="conference-number" class="text-center pb-4"></p>
</div>
<div>
<button id="btn-dial-conference" class="bg-blue-500 bold text-white p-4 rounded">Create Phone Conference</button>
<button id="btn-disconnect-conference" class="bg-red-500 bold text-white p-4 rounded">Disconnect Phone Conference</button>
</div>
Teremos, então, um conjunto de controles que nos permitirá ligar para um usuário. Você pode inserir um número de telefone e nosso sistema ligará para o usuário; quando ele atender a chamada, será conectado à conferência.
<div>
<h3 class="font-black text-xl">Direct Dial</h3>
<p>Directly dial a phone number and add them to the conference. They will appear as an additional guest and be automatically added to the conference call if you have already started one.</p>
</div>
<div>
<label for="phone">Number to call:</label>
<input name="phone" id="phone" type="text" placeholder="15554441234" class="border border-black p-4 w-full">
<button id="btn-dial-number" class="bg-blue-500 bold text-white p-4 rounded">Call Number</button>
</div>
Manipulação de vídeo
No JavaScript, vamos primeiro obter algumas informações sobre a própria videochamada. Para nos conectarmos à videochamada, precisamos de um ID de aplicativo, um ID de sessão e um token.
- O ID do aplicativo é um identificador que o Client SDK do cliente utiliza para fazer referência a diferentes configurações do nosso aplicativo de vídeo no lado da Vonage.
- O ID da sessão é uma sessão de vídeo específica à qual queremos nos conectar, já que uma única Application pode ter várias sessões de vídeo simultâneas ao mesmo tempo.
- O Token é um token de autenticação JWT que permite que você participe de uma sessão específica com direitos específicos.
Embora seja possível gerar o ID de sessão e o token com antecedência, na prática você os gerará conforme a necessidade. Nosso código mostra como fazer isso. Mostraremos como essas informações são criadas daqui a pouco, mas vamos obtê-las do servidor de back-end que implantamos.
// src/app.js
// ...
} else if (SAMPLE_SERVER_BASE_URL) {
// Make a GET request to get the Vonage Video Application ID, session ID, and token from the server
fetch(SAMPLE_SERVER_BASE_URL + '/session')
.then((response) => response.json())
.then((json) => {
applicationId = json.applicationId;
sessionId = json.sessionId;
token = json.token;
// Initialize a Vonage Video Session object
initializeSession();
}).catch((error) => {
handleError(error);
alert('Failed to get Vonage Video sessionId and token. Make sure you have updated the config.js file.');
});
}
Assim que tivermos todas as informações de conexão, podemos prosseguir e chamar o SDK do JavaScript do Vonage Video, que se encarrega de todo o trabalho de conexão com a Video API do Vonage no front-end. Primeiro, obtemos um objeto de sessão com OT.initSession(). Em seguida, começamos a escutar no streamCreated evento com session.on(). Isso nos permite definir uma função de retorno a ser executada quando um stream de outro publisher for criado. Nesse caso, usamos session.subscribe() para se conectar ao evento recebido e enviá-lo para o subscriber div que definimos no HTML. Também monitoramos o sessionDisconnected evento para saber quando o outro usuário se desconecta, mas, nesta demonstração, limitamo-nos apenas a registrar que percebemos que ele saiu.
Em seguida, criamos o publisher objeto com OT.initPublisher(). Indicamos a qual div ele deve ser anexado (publisher), além de algumas opções básicas de formatação. Isso conecta sua câmera e seu microfone à Video API.
Em seguida, chamamos session.connect() para se conectar à sessão, usando o token JWT de conexão que obtivemos do servidor. É só isso que basta para duas pessoas entrarem em uma sala!
// src/app.js
function initializeSession() {
const session = OT.initSession(applicationId, sessionId);
// Subscribe to a newly created stream
session.on('streamCreated', (event) => {
const subscriberOptions = {
insertMode: 'append',
width: '100%',
height: '100%'
};
session.subscribe(event.stream, 'subscriber', subscriberOptions, handleError);
});
session.on('sessionDisconnected', (event) => {
console.log('You were disconnected from the session.', event.reason);
});
// initialize the publisher
const publisherOptions = {
insertMode: 'append',
width: '100%',
height: '100%',
resolution: '1280x720'
};
const publisher = OT.initPublisher('publisher', publisherOptions, handleError);
// Connect to the session
session.connect(token, (error) => {
if (error) {
handleError(error);
} else {
// If the connection is successful, publish the publisher to the session
session.publish(publisher, handleError);
}
});
}
Atendimento de chamadas telefônicas
Todo o trabalho pesado para dar suporte às chamadas será realizado pela própria Video API e pelo nosso servidor de back-end. O código do lado do cliente apenas acessará algumas rotas no servidor de back-end para habilitar a chamada SIP, bem como para se desconectar da conferência telefônica quando terminarmos. A ativação da telefonia é feita clicando no /sip/session/dial rota no nosso servidor de back-end, que explicaremos em detalhes mais adiante.
// js/index.js
document.getElementById('btn-dial-conference').addEventListener('click', async () => {
const resp = await fetch(`${SAMPLE_SERVER_BASE_URL}/sip/session/dial`, {
method: "POST"
})
.then(res => res.json())
console.log(resp);
})
Essa mesma rota pode ser usada para ligar para um usuário específico. Basta passarmos o número de telefone digitado na interface do usuário do cliente:
// js/index.js
document.getElementById('btn-dial-number').addEventListener('click', async () => {
const msisdn = document.getElementById('phone').value;
const resp = await fetch(`${SAMPLE_SERVER_BASE_URL}/sip/session/dial`, {
method: "POST",
body: JSON.stringify({
msisdn
}),
headers: {
"Content-Type": "application/json"
}
})
.then(res => res.json())
console.log(resp);
})
Quando um usuário entra na teleconferência, ou quando nos conectamos a ele discando diretamente, um novo participante será adicionado à lista de participantes. A Video API encaminhará automaticamente o áudio da conexão SIP para todos os participantes conectados à sessão de vídeo.
Por fim, podemos encerrar qualquer um dos dois tipos de ligação acessando o /sip/session/hangup rota no nosso servidor de back-end:
// js/index.js
document.getElementById('btn-disconnect-conference').addEventListener('click', async () => {
const resp = await fetch(`${SAMPLE_SERVER_BASE_URL}/sip/session/hangup`, {
method: "POST"
})
.then(res => res.json())
console.log(resp);
})
O lado do servidor
A parte do lado do servidor de qualquer aplicativo Vonage Video é usada para lidar com a criação de sessões, a geração de tokens de autenticação e tarefas administrativas, como iniciar e interromper arquivamentos. Para esta demonstração, nosso único objetivo é criar sessões e tokens para que os usuários possam entrar na sala. Embora a API em si seja uma API REST e possa ser chamada da maneira que você preferir, recomendamos que você use a SDK do Vonage Node que cuida de toda a autenticação e das chamadas HTTP para você. Você pode instalá-lo em seu próprio aplicativo com:
npm install -s @vonage/server-sdk
O código de demonstração já vem com isso pré-instalado. Se você estiver executando o código localmente, precisará executar:
npm install
para baixar todas as dependências e, em seguida, copiar .envcopy em um novo arquivo chamado .env. Você precisará preencher as informações solicitadas em .env como o ID do aplicativo, o local da chave privada no disco e sua chave e segredo da API da Vonage.
Criação de sessão e participação na sessão
A primeira coisa que fazemos é verificar se já temos uma sessão para a sala que estamos gerando. Mantemos um dicionário na memória em roomToSessionIdDictionary, e se a sala já tiver uma sessão, simplesmente recuperamos essa sessão do dicionário. Em seguida, usamos o SDK do Vonage Video Node para criar um token de cliente chamando vonage.video.generateClientToken(), passando a ele o ID da sessão e um objeto com algumas configurações. No momento, tudo o que fazemos é definir o usuário como um moderator função para esta demonstração simples. Em seguida, retornamos o ID da aplicação, o ID da sessão e o token configurados de volta ao front-end.
Se a sessão não existir, criamos uma nova com vonage.video.createSession(). Isso acessa a API da Vonage e cria uma sessão à qual os usuários podem se conectar. Não temos nenhuma configuração específica para essa sessão, mas seria aqui que definiríamos itens como regras de arquivamento e como a sessão deve ser tratada, seja por roteamento ou ponto a ponto. Em seguida, assim como antes, criamos um token e enviamos todas essas informações de volta ao navegador.
// routes/index.js
async function createSession(response, roomName, sessionProperties = {}, role = 'moderator') {
let sessionId;
let token;
console.log(`Creating ${role} creds for ${roomName}`);
if (roomToSessionIdDictionary[roomName]) {
sessionId = roomToSessionIdDictionary[roomName];
// generate token for user
token = vonage.video.generateClientToken(sessionId, { role })
response.setHeader('Content-Type', 'application/json');
response.send({
applicationId: appId,
sessionId: sessionId,
token: token
});
} else {
try {
// Create the session
const session = await vonage.video.createSession(sessionProperties);
roomToSessionIdDictionary[roomName] = session.sessionId;
// generate token for user
token = vonage.video.generateClientToken(session.sessionId, { role });
response.setHeader('Content-Type', 'application/json');
response.send({
applicationId: appId,
sessionId: session.sessionId,
token: token
});
} catch(error) {
console.error("Error creating session: ", error);
response.status(500).send({ error: 'createSession error:' + error });
}
}
}
Conectando-se à ponte SIP
A Vonage disponibiliza uma ponte SIP para uso pela Video API. Basta ter um número de telefone contratado por meio do Painel do Cliente da Vonage. Assim, podemos usar esse número de telefone como uma interface SIP para chamadas recebidas. Também utilizaremos o Funcionalidade de conversação da Voice API da Vonage para conectar vários usuários em uma única conferência de áudio.
A primeira coisa que precisamos fazer é conectar nossa sessão de vídeo à própria teleconferência. Vamos criar um token de cliente para a conexão SIP a fim de participar da sessão de vídeo e, em seguida, fazer uma chamada para vonage.video.initiateSIPCall() com nossa configuração SIP para conectar tudo.
// routes/index.js
const { msisdn } = req.body;
const sessionId = findSessionIdForRoom(req.params.room);
const conversation = findConversationFromSessionId(sessionId);
const token = vonage.video.generateClientToken(sessionId, {
data: JSON.stringify({
sip: true,
role: 'client',
name: conversation.conversationName,
})
})
const options = {
token,
sip: {
auth: {
username: process.env.VCR_API_ACCOUNT_ID, // Your Vonage API Key
password: process.env.VCR_API_ACCOUNT_SECRET, // Your Vonage API Secret
},
uri: `sip:${process.env.CONFERENCE_NUMBER}@sip.nexmo.com;transport=tls`,
secure: false,
}
}
// ...
await vonage.video.intiateSIPCall(sessionId, options)
.then(data => {
// Update the conversation with connection data
conversation.connectionId = data.connectionId;
conversation.streamId = data.streamId;
sipConversationToSessionIdDictionary[sessionId] = conversation;
res.send(data)
})
Onde a conversa é estabelecida? Quando iniciamos a chamada SIP, isso faz com que nossa ponte SIP ligue para um número de conferência por meio da Voice API. Nosso número de conferência é configurado no Painel do Cliente para acessar o /sip/vapi/answer rota em nosso servidor de back-end. Se você estiver usando o Cloud Runtime, isso é configurado automaticamente, mas se estiver fazendo a configuração manualmente, será necessário acessar as configurações dessa aplicação e, em seguida, definir a “URL de resposta” como https://your-domain.com/sip/vapi/answer, onde your-domain é o nome de domínio no qual a demonstração está implantada.
A rota retornará um Ação de conversação da NCCO que cria uma conversa por meio da Voice API, conectando todos.
// routes/index.js
router.get('/sip/vapi/answer', async function (req, res) {
const ncco = new NCCOBuilder();
const conversation = findConversationFromSessionId(findSessionIdForRoom('session'));
// If the call is not from the SIP connector, then announce we are connecting
// to the conference call
if (!req.query['SipHeader_X-OpenTok-SessionId']) {
ncco.addAction(new Talk('Please wait while we connect you'));
}
// Call an individual user
if (req.query['SipHeader_X-learningserver-msisdn']) {
ncco.addAction(new Connect({type: 'phone', number: req.query['SipHeader_X-learningserver-msisdn']}, process.env.CONFERENCE_NUMBER));
} else {
ncco.addAction(new Conversation(conversation.conversationName, null, true, true, false, null, null, false));
}
res.send(ncco.build());
});
Nesse momento, um usuário pode ligar para o número da conferência, e a Voice API fará a conexão entre todos os participantes. Se um usuário ligar para o nosso número de conferência, a chamada será encaminhada para o /sip/vapi/answer ponto final. Adicionamos uma ação adicional que informa ao chamador que ele está sendo conectado à conferência e, em seguida, ele é conectado à mesma.
Ligar para um usuário
De modo geral, o processo para ligar para um usuário é o mesmo que para organizar uma teleconferência. A única diferença é que passamos um número para ser discado para o /sip/:room/dial rota, e adicionamos esse número como uma opção aos cabeçalhos SIP.
// routes/index.js
router.post("/sip/:room/dial", async function (req, res) {
// Set up client token and SIP options as before
// Add a header that will get passed to the Voice API
if (msisdn) {
options.sip.headers = {
"X-learningserver-msisdn": msisdn
}
}
//Initiate the call as before
await vonage.video.intiateSIPCall(sessionId, options)
.then(data => {
// Update the conversation with connection data
conversation.connectionId = data.connectionId;
conversation.streamId = data.streamId;
sipConversationToSessionIdDictionary[sessionId] = conversation;
res.send(data)
})
});
Quando a chamada SIP é iniciada, esse adicional X-learningserver-msisdn O cabeçalho é passado como parte da chamada da Voice API que nosso servidor de back-end aceita. Isso faz com que nosso código adicione uma etapa NCCO adicional para, primeiro, discar para o número de telefone solicitado por meio de um Conecte-se à ação da NCCO, e quando eles responderem, introduza-os na conversa.
// routes/index.js
router.get('/sip/vapi/answer', async function (req, res) {
// Find the conversation info as before
// If this header exists, call the user to bridge them in
if (req.query['SipHeader_X-learningserver-msisdn']) {
ncco.addAction(new Connect({type: 'phone', number: req.query['SipHeader_X-learningserver-msisdn']}, process.env.CONFERENCE_NUMBER));
} else {
ncco.addAction(new Conversation(conversation.conversationName, null, true, true, false, null, null, false));
}
res.send(ncco.build());
});
Desligando
Quando terminarmos, a interface do usuário nos oferece a opção de nos desconectarmos diretamente da chamada SIP. Isso é feito pelo /sip/:room/hangup encaminha e simplesmente desconecta a ligação SIP da sessão.
// routes/index.js
router.post("/sip/:room/hangup", async function (req, res) {
// Get the session ID
// Look up the connection from calls ID
const sessionId = findSessionIdForRoom(req.params.room)
const conversation = findConversationFromSessionId(sessionId);
await vonage.video.disconnectClient(sessionId, conversation.connectionId)
.then(data =>
res.send(data)
)
.catch(error => res.status(500).send(error));
});
Se não desligarmos manualmente, ficaremos atentos aos eventos da Voice API que nos informam quando uma conversa é encerrada. Uma conversa será encerrada automaticamente depois que todos os participantes se desconectarem. Esperamos até recebermos um completed evento, e, quando isso acontecer, nos certificamos de realizar o mesmo processo de desconexão da sessão, tal como feito acima.
// routes/index.js
// This must be all because VAPI sometimes sends events as POST no matter what
// your event URL config is set to. This is a known bug.
router.all('/sip/vapi/events', async function (req, res) {
if (req.query.status === "completed") {
const conversation = findConversationFromSessionId(findSessionIdForRoom('session'));
await vonage.video.disconnectClient(findSessionIdForRoom('session'), conversation.connectionId)
.then(data => res.send(data))
.catch(error => res.status(500).send(error));
} else {
res.send();
}
})
Conclusão
Neste tutorial, você viu o que é necessário no servidor de back-end para o tratamento de chamadas telefônicas via SIP, como criar um cliente web para que os usuários possam participar de uma sessão e se ver e ouvir uns aos outros, além de ter tido uma ideia de como é fácil usar o Vonage Code Hub e o Stack Blitz para testar rapidamente exemplos de código.