https://a.storyblok.com/f/270183/58906/a44612b863/callmemaybe_1200x627.png

Adicionar chamadas SIP às sessões de vídeo WebRTC

Publicado em February 8, 2021

Tempo de leitura: 4 minutos

Estamos vivendo em uma época de videoconferências. Da escola ao trabalho, passando por eventos familiares, as videoconferências se tornaram parte do dia a dia de muitas pessoas, mas há momentos em que não é possível participar por meio de um computador. Neste tutorial, vamos explicar como permitir que os participantes entrem nas suas sessões da Video API da Vonage por telefone.

Quer pular direto para o final? Você pode encontrar todo o código-fonte deste tutorial no GitHub.

Como isso funciona?

A partir da sessão da Video API, faremos uma chamada para a Voice API. Essa chamada acionará o webhook de resposta em nosso aplicativo, que criará uma conversa de voz. Essa conversa será incorporada à sessão de vídeo como mais um fluxo.

Quando os usuários ligarem para o número da conferência, será solicitado que digitem um PIN. Se o usuário digitar o PIN correto, ele entrará na conversa por voz. A partir desse momento, o usuário poderá ouvir todos os participantes da sessão de Video e, por sua vez, poderá ouvir a voz dos demais participantes.

Assim que a sessão terminar, a ligação deve ser encerrada para evitar cobranças adicionais pela Voice API ou Video API.

Pré-requisitos

Para acompanhar este tutorial, você precisará de:

  • Uma conta da Video API da Vonage. Clique aqui para obter uma gratuitamente.

  • Opcional: Ngrok para testes locais

Criação do front-end

Nosso front-end utilizará o Express com um modelo EJS. Neste artigo, não abordaremos como criar uma sessão da Video API, mas você pode consultar o código no repositório para ver como fazemos isso. Vamos nos concentrar exclusivamente em como adicionar chamadas SIP a uma sessão já existente.

No modelo da sessão de Video, adicione as duas funções JavaScript a seguir:

const dialOut = () => {
  fetch(`/dial-out?roomId=${roomId}`)
    .then(response => response.json())
    .then((sipData) => {
      connectionId = sipData.connectionId;
    }).catch((error) => {
      alert(`There was an error dialing-out`);
    })
};
const hangUp = () => {
  fetch(`/hang-up?roomId=${roomId}`)
    .then(response => response)
    .then((data) => {
      console.log('dial-out-hang-up-complete');
    }).catch((error) => {
      alert(`There was an error hanging up`);
    })
  };

Ambas as funções estão chamando rotas em nosso backend Express. O dialOut método iniciará a conferência de voz e a adicionará como um stream na sessão de Video. A hangUp função será usada no final da sessão para desconectar a conferência de voz da sessão. Em nosso HTML, vamos adicionar dois botões para chamar essas funções.

<button onclick="dialOut()">Click here to dial-out to the Vonage Conference</button>
<button onclick="hangUp()">Click here to hang-up</button>

Via rápida para o back-end

Com nosso front-end pronto para funcionar, vamos configurar nosso back-end para lidar com a conexão à Voice API da Vonage via SIP.

Funções auxiliares

Precisaremos de algumas funções auxiliares antes de lidarmos com chamadas efetuadas e desligamentos.

/**
 * Generates a random 4 digit PIN
 */
const generatePin = () => {
  const pin = Math.floor(Math.random() * 9000) + 1000;
  if (app.get(pin)) {
    return generatePin();
  }
  return pin;
};

/**
 * Creates a Video API user token
 * @param {String} sessionId Id of the Video API session the user wishes to join
 * @param {String} sipTokenData Data associated with the SIP connection
*/
const generateToken = (sessionId, sipTokenData = '') => OT.generateToken(sessionId, {
  role: 'publisher',
  data: sipTokenData,
});

/**
 * Properties for the OT.dial API call
 * @returns {Object}
*/
const setSipOptions = () => ({
  auth: {
    username: config.voiceApiKey,
    password: config.voiceApiSecret,
  },
  secure: false
});

A generatePin função gera um PIN aleatório de 4 dígitos que usaremos para criar um PIN exclusivo para cada sessão de Video. Os participantes que ligarem para a sessão serão solicitados a fornecer esse PIN antes de serem autorizados a entrar na sessão.

A generateToken função é usada para criar um token da Video API com o SIP

A setSipOptions função cria um objeto que usaremos ao estabelecer a conexão SIP. Ele contém as informações de autenticação necessárias para participar da conferência de voz.

Responder ao front-end

Com essas funções já implementadas, vamos adicionar rotas para responder ao nosso front-end. A dial-out rota abaixo usará a Video API para se conectar a uma conferência SIP. Mais tarde, configuraremos a Voice API para que ela saiba como responder a essas chamadas.

/**
 * 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);
  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) {
      console.dir(error)
      res.status(500).send('There was an error dialing out');
    } else {
      app.set(conferenceNumber + roomId, sipCall.connectionId);
      res.json(sipCall);
    }
  });
});

/**
 * 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');
  }
});

A hang-up rota desconecta a conferência de voz da sessão da Video API. Desligar a chamada ao final de uma reunião é extremamente importante. Caso contrário, a conferência de voz permanecerá aberta e conectada à sessão de vídeo. Isso faria com que ambas continuassem gerando cobranças.

Webhooks da Voice API

Ao criar um aplicativo de voz, você precisará fornecer uma URL de resposta e uma URL de evento. Se estiver executando o aplicativo localmente, é recomendável usar o ngrok para disponibilizar um endpoint externo. Forneça sua URL do ngrok ou do Heroku com rotas /voice-answer para a URL de resposta e /voice-events para a URL de evento.

app.get('/voice-events', (req, res) => {
  res.status(200).send();
});

app.post('/voice-answer', (req, res) => {
  const { serverUrl } = config;
  const ncco = [];
  if (req.body['SipHeader_X-OpenTok-SessionId']) {
    ncco.push({
      action: 'conversation',
      name: req.body['SipHeader_X-OpenTok-SessionId'],
    });
  } else {
    ncco.push(
      {
        action: 'talk',
        text: 'Please enter a pin code to join the session'
      },
      {
        action: 'input',
        eventUrl: [`${serverUrl}/voice-dtmf`]
      }
    )
  }

  res.json(ncco);
});

app.post('/voice-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)
})

A /voice-answer rota criará uma conversa quando acionada devido à nossa ligação de saída. Quando outros participantes ligarem, eles serão solicitados a fornecer o PIN de 4 dígitos da sessão. As entradas do chamador serão encaminhadas para a /voice-dtmf rota para que possam, eventualmente, participar da sessão.

Configurando as opções

Vamos começar criando um .env arquivo. Você pode usar o .env-sample arquivo dentro do repositório como modelo. Seu conteúdo deve ser:

videoApiKey=
videoApiSecret=
voiceApiKey=
voiceApiSecret=
conferenceNumber=
serverUrl=

Para definir videoApiKey e videoApiSecret, crie um novo projeto no painel da Video API.

Project created dialog within the Vonage Video API dashboardProject created dialog within the Vonage Video API dashboard

Depois de criada, copie a chave da API e o segredo e cole-os no seu .env arquivo como videoApiKey e videoApiSecret , respectivamente.

Agora crie um aplicativo de voz e use a chave e o segredo da Voice API como voiceApiKey e voiceApiSecret. Você precisará adquirir um número e associá-lo ao seu aplicativo de voz. Use esse número como a conferenceNumber variável.

Por fim, insira a URL do ngrok ou do Heroku como `serverUrl`.

Agora você pode participar de uma sessão de vídeo, e outras pessoas podem ligar para o seu número e digitar um código PIN para participar da sessão. É importante ressaltar que você precisa desligar a ligação ao final da sessão de vídeo para evitar o consumo de créditos tanto da conta de vídeo quanto da conta de voz quando terminar.

Leitura complementar

Quer saber mais sobre o recurso de interconexão SIP da Video API? Abaixo estão alguns links que podem ser úteis.

Compartilhar:

https://a.storyblok.com/f/270183/225x225/b0360f94ad/michaeljolley.png
Michael JolleyEx-funcionários da Vonage

Michael é o construtor careca e barbudo. Com seus 20 anos de experiência em desenvolvimento de software e DevOps, esse desenvolvedor com pouco cabelo dedica seus dias a ajudar os outros a terem sucesso.