
Compartilhar:
Manik é engenheiro de software sênior. Ele gosta de trabalhar com desenvolvedores e criar APIs. Quando não está desenvolvendo APIs ou SDKs, é possível encontrá-lo palestrando em conferências e encontros.
Conectando WebRTC e PSTN com o OpenTok e o Nexmo
Tempo de leitura: 5 minutos
Na Nexmo, anunciamos recentemente o SIP Connect , que permite conectar terminais WebRTC à Voice API da Nexmo. Esse recurso possibilita que usuários da rede PSTN liguem para uma sessão de vídeo do OpenTok.
Nesta postagem, vamos criar um aplicativo web de vídeo em tempo real usando o OpenTok e conectar usuários da PSTN a ele por meio do SIP Connect e da Voice API.
Pré-requisitos
Antes de começarmos, certifique-se de que você tenha o seguinte:
Introdução
Por favor, crie um projeto da API do TokBox, pois você precisará de um apiKey e apiSecret para adicionar voz e vídeo em tempo real ao aplicativo web. Além das credenciais da TokBox, você precisará criar um aplicativo Nexmo Voice e configurar webhooks de eventos e respostas.
Não se preocupe com o que são no momento, pois vamos explicá-los à medida que formos trabalhando com eles. Por fim, você precisará adquirir um número virtual da Nexmo e encaminhar todas as chamadas recebidas nesse número para o aplicativo que você criou.
Visão geral

Código de exemplo
Para começar, clone o repositório opentok-nexmo-sip e acesse a pasta Dial-In-Conference .
No diretório do projeto, você verá um config.example.js arquivo. Por favor, copie o conteúdo desse arquivo para um novo arquivo chamado config.js.
Certifique-se de adicionar as credenciais do TokBox e do Nexmo que você gerou anteriormente ao
config.jsarquivo, pois vamos usá-las no aplicativo.
Código do lado do cliente
Nesse caso, estamos usando JavaScript para a web, mas você pode aplicar os mesmos Concepts com o OpenTok iOS, Androide Windows SDKs.
Como você pode ver abaixo, no opentok.js arquivo, localizado na public/js pasta, inicializamos uma sessão chamando o initSession método no OT objeto. Em seguida, criamos um objeto Publisher usando o initPublisher método.
Em seguida, definimos os seguintes eventos de sessão:
streamCreatedstreamDestroyedsessionConnected
Esses eventos são acionados quando um fluxo é criado, quando um fluxo é encerrado ou quando o cliente se conecta à sessão, respectivamente.
Depois de definir os ouvintes de eventos, nos conectamos à sessão passando o token e um manipulador de erros. O manipulador de erros é usado para garantir que não tenha ocorrido nenhum erro durante a tentativa de conexão com a sessão. Em nosso aplicativo, se ocorrer um erro, registramos-o no console, mas em um aplicativo de produção, devemos exibir um elemento da interface do usuário e tentar se reconectar.
const session = OT.initSession(apiKey, sessionId);
const publisher = OT.initPublisher('publisher');
session.on({
streamCreated: (event) => {
const subscriberClassName = `subscriber-${event.stream.streamId}`;
const subscriber = document.createElement('div');
subscriber.setAttribute('id', subscriberClassName);
document.getElementById('subscribers').appendChild(subscriber);
session.subscribe(event.stream, subscriberClassName);
},
streamDestroyed: (event) => {
console.log(`Stream ${event.stream.name} ended because ${event.reason}.`);
},
sessionConnected: (event) => {
session.publish(publisher);
},
});
session.connect(token, (error) => {
if (error) {
console.log('error connecting to session');
}
});
Este opentok.js arquivo é importado para a visualização localizada no views/index.ejs arquivo.
Além desse código, também criamos alguns botões que acionam solicitações de API ao servidor do aplicativo para realizar chamadas via SIP.
Neste aplicativo, essa visualização está sendo gerada pelo nosso servidor, mas você pode optar por gerá-la da maneira que preferir. Para ver o código em que essa visualização está sendo gerada, confira o seguinte link.
Código do lado do servidor
Agora que já configuramos nosso cliente, vamos dar uma olhada no código do servidor.
Você vai perceber que estamos importando express, opentok, e body-parser pacotes. Estamos usando o Express.js para nosso servidor, o SDK do OpenTok para Nodee analisador de corpo , que será usada para analisar o corpo das solicitações recebidas.
Observe que também estamos importando o
configque definimos noconfig.jsarquivo.
Em seguida, passamos a criar os seguintes endpoints:
/room/:roomId/dial-out/hang-up/nexmo-answer/nexmo-dtmf/nexmo-events
A /room/:roomId caminho é o caminho principal do nosso aplicativo. Ele renderiza a index.ejs visualização com as credenciais apropriadas do OpenTok. Quando isso ocorre, o servidor do aplicativo faz uma solicitação ao OpenTok para criar uma sessão, que então responde com um objeto de sessão contendo o sessionId.
O sessionId é então usado para gerar um token do OpenTok. Em seguida, geramos um código PIN de 4 dígitos e o associamos ao sessionId e ao nome da sala. Isso é importante porque precisaremos disso para identificar o sessionId quando o usuário PSTN ligar. Também adicionamos uma lógica para colocar os usuários na mesma sessão quando eles fizerem uma solicitação com o mesmo roomId.
/**
* When the room/:roomId request is made, either a template is rendered is served with the
* sessionid, token, pinCode, roomId, and apiKey.
*/
app.get('/room/:roomId', (req, res) => {
const { roomId } = req.params;
let pinCode;
if (app.get(roomId)) {
const sessionId = app.get(roomId);
const token = generateToken(sessionId);
pinCode = app.get(sessionId);
renderRoom(res, sessionId, token, roomId, pinCode);
} else {
pinCode = generatePin();
OT.createSession({
mediaMode: 'routed',
}, (error, session) => {
if (error) {
return res.send('There was an error').status(500);
}
const { sessionId } = session;
const token = generateToken(sessionId);
app.set(roomId, sessionId);
app.set(pinCode, sessionId);
renderRoom(res, sessionId, token, roomId, pinCode);
});
}
});
Chamadas de saída
Para fazer uma chamada para o endpoint SIP, o navegador enviará uma solicitação ao /dial-out terminal, e o servidor irá gerar um token e utilizar nossas credenciais do Nexmo (chave de API e segredo de API), juntamente com a URI SIP (sip:lvn@sip.nexmo.com) para enviar uma solicitação ao OpenTok para iniciar a chamada da sessão. Se isso for bem-sucedido, receberemos as informações de conexão por meio do callback para os participantes SIP.
/**
* When the dial-out get request is made, the dial method of the OpenTok Dial API is invoked
*/
app.get('/dial-out', (req, res) => {
const { roomId } = req.query;
const { conferenceNumber } = config;
const sipTokenData = `{"sip":true, "role":"client", "name":"'${conferenceNumber}'"}`;
const sessionId = app.get(roomId); // grabbing the sessionId from the mapping we created earlier
const token = generateToken(sessionId, sipTokenData);
const options = setSipOptions();
const sipUri = `sip:${conferenceNumber}@sip.nexmo.com;transport=tls`;
OT.dial(sessionId, token, sipUri, options, (error, sipCall) => {
if (error) {
res.status(500).send('There was an error dialing out');
} else {
app.set(conferenceNumber + roomId, sipCall.connectionId);
res.json(sipCall);
}
});
});
A ligação para a Voice API da Nexmo via SIP acionará o webhook de evento, /nexmo-events no nosso caso, para quaisquer alterações de status, ou seja, started, ringing, etc.
app.get('/nexmo-events', (req, res) => {
console.log('call event', req.query);
res.status(200).send();
});
Além da URL do evento, um webhook de resposta é chamado para que o servidor de aplicativos possa informar à Voice API o que fazer com essa chamada.
A Voice API espera que isso seja fornecido no formato de um NCCO, um objeto JSON no qual o servidor do aplicativo especifica a(s) ação(ões).
No nosso caso, o servidor de aplicativos especificará o action como um conversation e usará o sessionId como o nome do conversation. Determinamos o sessionId usando os cabeçalhos SIP adicionados pelo OpenTok ao fazer a chamada ou por meio do código PIN de quatro dígitos que o usuário digita via DTMF.
app.get('/nexmo-answer', (req, res) => {
const { serverUrl } = config;
const ncco = [];
if (req.query['SipHeader_X-OpenTok-SessionId']) {
ncco.push({
action: 'conversation',
name: req.query['SipHeader_X-OpenTok-SessionId'],
});
} else {
ncco.push(
{
action: 'talk',
text: 'Please enter a a pin code to join the session'
},
{
action: 'input',
eventUrl: [`${serverUrl}/nexmo-dtmf`]
}
)
}
res.json(ncco);
});
No código acima, temos uma instrução condicional que verifica se há cabeçalhos para obter o sessionId. Nesse caso, quando não há cabeçalhos SIP, podemos supor que se trata de um usuário PSTN ligando, de modo que podemos solicitar que ele digite um PIN. Fazemos isso definindo nosso action para talk com alguns text. O servidor do aplicativo também informa à Voice API que queremos receber o dtmf código no /nexmo-dtmf webhook.
Solicitação do PIN
Quando o /nexmo-dtmf webhook é chamado, verificamos se há o dtmf código no corpo da solicitação e consultamos o sessionId com base no mapeamento. Isso é feito para garantir que estamos conectando os usuários do WebRTC e da PSTN na mesma conversa. Isso também permite que você reutilize o mesmo número de telefone virtual com vários códigos PIN para facilitar conferências simultâneas.
app.post('/nexmo-dtmf', (req, res) => {
const { dtmf } = req.body;
let sessionId;
if (app.get(dtmf)) {
sessionId = app.get(dtmf);
}
const ncco = [
{
action: 'conversation',
name: sessionId,
}];
res.json(ncco);
});
Criamos um /hang-up ponto de extremidade onde podemos usar o forceDisconnect método no OpenTok objeto para desconectar o participante SIP.
/**
* When the hang-up get request is made, the forceDisconnect method of the OpenTok API is invoked
*/
app.get('/hang-up', (req, res) => {
const { roomId } = req.query;
const { conferenceNumber } = config;
if (app.get(roomId) + app.get(conferenceNumber + roomId)) {
const sessionId = app.get(roomId);
const connectionId = app.get(conferenceNumber + roomId);
OT.forceDisconnect(sessionId, connectionId, (error) => {
if (error) {
res.status(500).send('There was an error hanging up');
} else {
res.status(200).send('Ok');
}
});
} else {
res.status(400).send('There was an error hanging up');
}
});
Por fim, especificamos uma porta e executamos o servidor Express:
const port = process.env.PORT || '3000';
app.listen(port, () => console.log(`listening on port ${port}`));
Conclusão
Nesta postagem, abordamos como conectar uma sessão do OpenTok a usuários da PSTN usando o SIP Connect e a Voice API da Nexmo. Para ver o código completo com outros exemplos de SIP, acesse o opentok-nexmo-sip .