Criar serviços de back-end de apps WebRTC

Publicado em: 4 de novembro de 2013

O WebRTC permite a comunicação ponto a ponto. Ele requer servidores para que os clientes possam trocar metadados e coordenar a comunicação por um processo chamado sinalização. Os servidores também ajudam os clientes a lidar com conversores de endereços de rede (NATs) e firewalls.

Aqui, você vai aprender a criar um serviço de sinalização e lidar com as peculiaridades da conectividade do mundo real com servidores STUN e TURN. Você também vai entender como os apps WebRTC podem lidar com chamadas de várias partes e interagir com serviços, como VoIP e PSTN (também conhecidos como telefones).

O que é sinalização?

A sinalização é o processo de coordenação da comunicação. Para que um app WebRTC configure uma chamada, os clientes precisam trocar as seguintes informações:

  • Mensagens de controle de sessão usadas para abrir ou fechar a comunicação
  • Mensagens de erro
  • Metadados de mídia, como codecs, configurações de codec, largura de banda e tipos de mídia
  • Dados de chave usados para estabelecer conexões seguras
  • Dados de rede, como o endereço IP e a porta de um host, conforme vistos pelo mundo externo

Esse processo de sinalização precisa de uma maneira para que os clientes transmitam mensagens. Esse mecanismo não é implementado pelas APIs WebRTC. Em vez disso, você precisa criá-lo.

Por que a sinalização não é definida pelo WebRTC?

Para evitar redundância e maximizar a compatibilidade com tecnologias estabelecidas, os métodos e protocolos de sinalização não são especificados pelos padrões do WebRTC. Essa abordagem é descrita pelo Protocolo de estabelecimento de sessão JavaScript (JSEP, na sigla em inglês):

A arquitetura do JSEP também evita que um navegador precise salvar o estado, ou seja, funcionar como uma máquina de estado de sinalização. Isso seria problemático se, por exemplo, os dados de sinalização fossem perdidos sempre que uma página fosse recarregada. Em vez disso, o estado de sinalização pode ser salvo em um servidor.

Diagrama de arquitetura do JSEP

O JSEP exige a troca entre os pares de oferta e resposta, os metadados de mídia. As ofertas e respostas são comunicadas no formato Session Description Protocol (SDP), que tem esta aparência:

v=0
o=- 7614219274584779017 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:W2TGCZw2NZHuwlnf
a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=mid:audio
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe
a=rtpmap:111 opus/48000/2
...

Quer saber o que significa todo esse jargão do SDP? Confira os exemplos da Internet Engineering Task Force (IETF).

Lembre-se de que o WebRTC foi projetado para que a oferta ou a resposta possa ser ajustada antes de ser definida como a descrição local ou remota, editando os valores no texto do SDP. Por exemplo, a função preferAudioCodec() em appr.tc pode ser usada para definir o codec e a taxa de bits padrão. O SDP é um pouco difícil de manipular com JavaScript, e há discussões sobre se as versões futuras do WebRTC devem usar JSON, mas há algumas vantagens em manter o SDP.

API RTCPeerConnection e sinalização: oferta, resposta e candidato

RTCPeerConnection é a API usada por apps WebRTC para criar uma conexão entre pares e comunicar áudio e vídeo.

Para inicializar esse processo, o RTCPeerConnection tem duas tarefas:

  • Verifique as condições de mídia local, como recursos de resolução e codec. Esses são os metadados usados para o mecanismo de oferta e resposta.
  • Receber possíveis endereços de rede para o host do app, conhecidos como candidatos.

Depois que esses dados locais forem determinados, eles precisarão ser trocados por um mecanismo de sinalização com o par remoto.

Imagine que Alice está tentando ligar para Eva. Veja o mecanismo de resposta completo em todos os detalhes:

  1. Alice cria um objeto RTCPeerConnection.
  2. Alice cria uma oferta (uma descrição de sessão SDP) com o método RTCPeerConnection createOffer().
  3. Alice liga para setLocalDescription() com a oferta dela.
  4. Alice transforma a oferta em string e usa um mecanismo de sinalização para enviá-la a Eve.
  5. Eve chama setRemoteDescription() com a oferta de Alice para que o RTCPeerConnection dela saiba sobre a configuração de Alice.
  6. Eva chama createAnswer(), e o callback de sucesso para isso recebe uma descrição de sessão local: a resposta de Eva.
  7. Eva define a resposta como a descrição local chamando setLocalDescription().
  8. Em seguida, Eve usa o mecanismo de sinalização para enviar a resposta em string para Alice.
  9. Alice define a resposta de Eve como a descrição da sessão remota usando setRemoteDescription().

Alice e Eve também precisam trocar informações de rede. A expressão "encontrar candidatos" se refere ao processo de encontrar interfaces de rede e portas usando o framework ICE.

  1. Alice cria um objeto RTCPeerConnection com um gerenciador onicecandidate.
  2. O gerenciador é chamado quando os candidatos de rede ficam disponíveis.
  3. No gerenciador, Alice envia dados de candidatos em formato de string para Eve pelo canal de sinalização.
  4. Quando Eve recebe uma mensagem de candidato de Alice, ela chama addIceCandidate() para adicionar o candidato à descrição do par remoto.

O JSEP é compatível com o ICE Candidate Trickling, que permite que o autor da chamada forneça candidatos de forma incremental ao destinatário após a oferta inicial, e que o destinatário comece a agir na chamada e configure uma conexão sem esperar que todos os candidatos cheguem.

Codificar o WebRTC para sinalização

O snippet de código a seguir é um exemplo de código do W3C que resume o processo de sinalização completo. O código pressupõe a existência de algum mecanismo de sinalização, SignalingChannel. A sinalização será discutida em mais detalhes posteriormente.

// handles JSON.stringify/parse
const signaling = new SignalingChannel();
const constraints = {audio: true, video: true};
const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
const pc = new RTCPeerConnection(configuration);

// Send any ice candidates to the other peer.
pc.onicecandidate = ({candidate}) => signaling.send({candidate});

// Let the "negotiationneeded" event trigger offer generation.
pc.onnegotiationneeded = async () => {
  try {
    await pc.setLocalDescription(await pc.createOffer());
    // send the offer to the other peer
    signaling.send({desc: pc.localDescription});
  } catch (err) {
    console.error(err);
  }
};

// After remote track media arrives, show it in remote video element.
pc.ontrack = (event) => {
  // Don't set srcObject again if it is already set.
  if (remoteView.srcObject) return;
  remoteView.srcObject = event.streams[0];
};

// Call start() to initiate.
async function start() {
  try {
    // Get local stream, show it in self-view, and add it to be sent.
    const stream =
      await navigator.mediaDevices.getUserMedia(constraints);
    stream.getTracks().forEach((track) =>
      pc.addTrack(track, stream));
    selfView.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}

signaling.onmessage = async ({desc, candidate}) => {
  try {
    if (desc) {
      // If you get an offer, you need to reply with an answer.
      if (desc.type === 'offer') {
        await pc.setRemoteDescription(desc);
        const stream =
          await navigator.mediaDevices.getUserMedia(constraints);
        stream.getTracks().forEach((track) =>
          pc.addTrack(track, stream));
        await pc.setLocalDescription(await pc.createAnswer());
        signaling.send({desc: pc.localDescription});
      } else if (desc.type === 'answer') {
        await pc.setRemoteDescription(desc);
      } else {
        console.log('Unsupported SDP type.');
      }
    } else if (candidate) {
      await pc.addIceCandidate(candidate);
    }
  } catch (err) {
    console.error(err);
  }
};

Para ver os processos de oferta-resposta e troca de candidatos em ação, acesse simpl.info RTCPeerConnection e confira o registro do console para um exemplo de chat por vídeo de página única. Você pode baixar um dump completo de sinalização e estatísticas do WebRTC na página about://webrtc-internals no Google Chrome ou na página opera://webrtc-internals no Opera.

Descoberta de semelhantes

Essa é uma forma sofisticada de perguntar: "Como faço para encontrar alguém com quem conversar?"

Para ligações, você tem números de telefone e listas telefônicas. Para chat por vídeo e mensagens on-line, você precisa de sistemas de gerenciamento de identidade e presença, além de uma maneira para os usuários iniciarem sessões. Os apps WebRTC precisam de uma maneira para que os clientes sinalizem uns aos outros que querem iniciar ou participar de uma chamada.

Os mecanismos de descoberta de peering não são definidos pelo WebRTC, e não vamos entrar nas opções aqui. O processo pode ser tão simples quanto enviar um URL por e-mail ou mensagem. Para apps de chat por vídeo, como Talky, tawk.to e Browser Meeting, você convida pessoas para uma chamada compartilhando um link personalizado. O desenvolvedor Chris Ball criou um experimento intrigante de WebRTC sem servidor que permite que os participantes de chamadas WebRTC troquem metadados por qualquer serviço de mensagens que quiserem, como mensagens instantâneas, e-mail ou pombo-correio.

Como criar um serviço de sinalização?

Para reiterar, os protocolos e mecanismos de sinalização não são definidos pelos padrões do WebRTC. Seja qual for sua escolha, você precisa de um servidor intermediário para trocar mensagens de sinalização e dados do app entre clientes. Infelizmente, um app da Web não pode gritar na Internet: "Conecte-me ao meu amigo!"

Felizmente, as mensagens de sinalização são pequenas e trocadas principalmente no início de uma chamada. Em testes com appr.tc para uma sessão de chat por vídeo, um total de cerca de 30 a 45 mensagens foi processado pelo serviço de sinalização, com um tamanho total de cerca de 10 KB para todas as mensagens.

Além de não exigirem muita largura de banda, os serviços de sinalização do WebRTC não consomem muito processamento ou memória, porque só precisam retransmitir mensagens e reter uma pequena quantidade de dados de estado da sessão, como quais clientes estão conectados.

Enviar mensagens do servidor para o cliente

Browser Support

  • Chrome: 6.
  • Edge: 79.
  • Firefox: 6.
  • Safari: 5.

Source

Um serviço de mensagens para sinalização precisa ser bidirecional: do cliente para o servidor e do servidor para o cliente. A comunicação bidirecional vai contra o modelo de solicitação/resposta do cliente HTTP/servidor, mas vários hacks, como long polling, foram desenvolvidos ao longo de muitos anos para enviar dados de um serviço em execução em um servidor da Web para um app da Web em execução em um navegador.

A API EventSource permite eventos enviados pelo servidor, dados enviados de um servidor da Web para um cliente do navegador por HTTP. O EventSource foi projetado para mensagens unidirecionais, mas pode ser usado em combinação com o XHR para criar um serviço de troca de mensagens de sinalização. Um serviço de sinalização transmite uma mensagem de um autor de chamada, entregue por solicitação XHR, enviando-a por push pelo EventSource para o destinatário.

Browser Support

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 11.
  • Safari: 5.

Source

O WebSocket foi projetado para comunicação full-duplex entre cliente e servidor, o que significa que as mensagens podem fluir em ambas as direções ao mesmo tempo. Uma vantagem de um serviço de sinalização criado com WebSocket puro ou eventos enviados pelo servidor (EventSource) é que o back-end dessas APIs pode ser implementado em uma variedade de frameworks da Web comuns à maioria dos pacotes de hospedagem na Web para linguagens como PHP, Python e Ruby.

Todos os navegadores que oferecem suporte ao WebRTC também oferecem suporte ao WebSocket. O TLS precisa ser usado em todas as conexões para garantir que as mensagens não sejam interceptadas sem criptografia e também para reduzir problemas com a travessia de proxy. Para mais informações sobre WebSocket e travessia de proxy, leia o capítulo sobre WebRTC no livro High Performance Browser Networking, de Ilya Grigorik.

É possível lidar com a sinalização fazendo com que os clientes do WebRTC pesquisem um servidor de mensagens repetidamente pelo Ajax, mas isso gera muitas solicitações de rede redundantes, o que é especialmente problemático para dispositivos móveis. Mesmo depois que uma sessão é estabelecida, os pares precisam consultar as mensagens de sinalização em caso de mudanças ou encerramento da sessão por outros pares. O exemplo de app WebRTC Book usa essa opção com algumas otimizações para a frequência de sondagem.

Sinalização de escala

Embora um serviço de sinalização consuma relativamente pouca largura de banda e CPU por cliente, os servidores de sinalização de um app conhecido podem ter que lidar com muitas mensagens de locais diferentes com altos níveis de simultaneidade. Os apps WebRTC que recebem muito tráfego precisam de servidores de sinalização capazes de lidar com uma carga considerável. Não vamos entrar em detalhes aqui, mas há várias opções para mensagens de alto volume e alto desempenho, incluindo as seguintes:

  • O eXtensible Messaging and Presence Protocol (XMPP) é um protocolo desenvolvido para mensagens instantâneas que pode ser usado para sinalização. As implementações de servidor incluem ejabberd e Openfire. Os clientes JavaScript, como o Strophe.js, usam BOSH para emular o streaming bidirecional, mas o BOSH pode ser ineficiente e ter uma escalonabilidade ruim em comparação com o WebSocket.

  • Bibliotecas de código aberto, como ZeroMQ (usada pela TokBox para o serviço Rumour) e OpenMQ (NullMQ aplica conceitos do ZeroMQ a plataformas da Web usando o protocolo STOMP no WebSocket).

  • Plataformas comerciais de mensagens na nuvem que usam WebSocket (embora possam voltar para a sondagem longa), como Pusher e Kaazing.

  • Plataformas comerciais de WebRTC, como o vLine

Criar um serviço de sinalização com o Socket.io no Node

Veja a seguir o código de um app da Web que usa um serviço de sinalização criado com o Socket.io no Node. O design do Socket.io facilita a criação de um serviço para trocar mensagens, e o Socket.io é particularmente adequado para a sinalização do WebRTC devido ao conceito integrado de salas. Este exemplo foi projetado para um número relativamente pequeno de usuários, em vez de uso em produção.

O Socket.io usa o WebSocket com substitutos: sondagem longa AJAX, streaming de várias partes AJAX, Forever Iframe e sondagem JSONP. Ele foi portado para vários back-ends, mas talvez seja mais conhecido pela versão do Node usada neste exemplo.

Não há WebRTC neste exemplo. Ele foi projetado para mostrar como criar sinalização em um app da Web. Confira o registro do console para ver o que acontece quando os clientes entram em uma sala e trocam mensagens. Este codelab do WebRTC oferece instruções detalhadas sobre como integrar isso a um app de chat por vídeo completo do WebRTC.

Este é o cliente index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>WebRTC client</title>
  </head>
  <body>
    <script src='/socket.io/socket.io.js'></script>
    <script src='js/main.js'></script>
  </body>
</html>

Este é o arquivo JavaScript main.js referenciado no cliente:

const isInitiator;

room = prompt('Enter room name:');

const socket = io.connect();

if (room !== '') {
  console.log('Joining room ' + room);
  socket.emit('create or join', room);
}

socket.on('full', (room) => {
  console.log('Room ' + room + ' is full');
});

socket.on('empty', (room) => {
  isInitiator = true;
  console.log('Room ' + room + ' is empty');
});

socket.on('join', (room) => {
  console.log('Making request to join room ' + room);
  console.log('You are the initiator!');
});

socket.on('log', (array) => {
  console.log.apply(console, array);
});

Confira o app de servidor completo:

const static = require('node-static');
const http = require('http');
const file = new(static.Server)();
const app = http.createServer(function (req, res) {
  file.serve(req, res);
}).listen(2013);

const io = require('socket.io').listen(app);

io.sockets.on('connection', (socket) => {

  // Convenience function to log server messages to the client
  function log(){
    const array = ['>>> Message from server: '];
    for (const i = 0; i < arguments.length; i++) {
      array.push(arguments[i]);
    }
      socket.emit('log', array);
  }

  socket.on('message', (message) => {
    log('Got message:', message);
    // For a real app, would be room only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', (room) => {
    const numClients = io.sockets.clients(room).length;

    log('Room ' + room + ' has ' + numClients + ' client(s)');
    log('Request to create or join room ' + room);

    if (numClients === 0){
      socket.join(room);
      socket.emit('created', room);
    } else if (numClients === 1) {
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room);
    } else { // max two clients
      socket.emit('full', room);
    }
    socket.emit('emit(): client ' + socket.id +
      ' joined room ' + room);
    socket.broadcast.emit('broadcast(): client ' + socket.id +
      ' joined room ' + room);

  });

});

Para executar esse app no localhost, você precisa ter o Node.js, o Socket.IO e o node-static. Para instalar o Socket.IO e o node-static, execute npm em um terminal no diretório do app:

npm install socket.io
npm install node-static

Para iniciar o servidor, execute o seguinte comando em um terminal no diretório do app:

node server.js

No navegador, abra localhost:2013. Abra uma nova guia ou janela em qualquer navegador e abra localhost:2013 novamente. Para ver o que está acontecendo, verifique o console. No Chrome e no Opera, é possível acessar o console pelas Ferramentas para desenvolvedores do Google Chrome com Ctrl+Shift+J (ou Command+Option+J no Mac).

Seja qual for a abordagem escolhida para a sinalização, o back-end e o app cliente precisam fornecer serviços semelhantes aos deste exemplo.

Pegadinhas de indicadores

  • O RTCPeerConnection não vai começar a coletar candidatos até que setLocalDescription() seja chamado. Isso é obrigatório no rascunho do JSEP IETF.
  • Aproveite o Trickle ICE. Chame addIceCandidate() assim que os candidatos chegarem.

Servidores de sinalização prontos

Se você não quiser criar o seu, há vários servidores de sinalização WebRTC disponíveis, que usam o Socket.IO como no exemplo anterior e são integrados às bibliotecas JavaScript do cliente WebRTC:

  • webRTC.io é uma das primeiras bibliotecas de abstração para WebRTC.
  • O Signalmaster é um servidor de sinalização criado para uso com a biblioteca de cliente JavaScript SimpleWebRTC.

Se você não quiser escrever nenhum código, plataformas comerciais completas de WebRTC estão disponíveis em empresas como vLine, OpenTok e Asterisk.

Para que fique registrado, a Ericsson criou um servidor de sinalização usando PHP no Apache nos primeiros dias do WebRTC. Isso agora está um pouco obsoleto, mas vale a pena analisar o código se você estiver considerando algo semelhante.

Segurança do sinal

A criptografia é obrigatória para todos os componentes do WebRTC.

No entanto, os mecanismos de sinalização não são definidos pelos padrões do WebRTC. Portanto, cabe a você garantir a segurança da sinalização. Se um invasor conseguir sequestrar a sinalização, ele poderá interromper sessões, redirecionar conexões e gravar, alterar ou injetar conteúdo.

O fator mais importante para proteger a sinalização é usar protocolos seguros: HTTPS e WSS, como TLS. Isso garante que as mensagens não possam ser interceptadas sem criptografia. Além disso, tome cuidado para não transmitir mensagens de sinalização de modo que elas possam ser acessadas por outros autores de chamada usando o mesmo servidor de sinalização.

Lidar com NATs e firewalls

Para sinalização de metadados, os apps WebRTC usam um servidor intermediário, mas para streaming de mídia e dados reais, depois que uma sessão é estabelecida, o RTCPeerConnection tenta conectar os clientes diretamente ou ponto a ponto.

Em um mundo mais simples, cada endpoint do WebRTC teria um endereço exclusivo que poderia ser trocado com outros pares para se comunicar diretamente.

Conexão simples de peering
Um mundo sem NATs e firewalls

Na realidade, a maioria dos dispositivos fica atrás de uma ou mais camadas de NAT, alguns têm software antivírus que bloqueia determinadas portas e protocolos, e muitos estão atrás de proxies e firewalls corporativos. Um firewall e uma NAT podem ser implementados pelo mesmo dispositivo, como um roteador Wi-Fi doméstico.

Peers por trás de NATs e firewalls
O mundo real

Os apps WebRTC podem usar o framework ICE para superar as complexidades das redes do mundo real. Para ativar esse recurso, seu app precisa transmitir URLs de servidor ICE para RTCPeerConnection.

O ICE tenta encontrar o melhor caminho para conectar os pares. Ele testa todas as possibilidades em paralelo e escolhe a opção mais eficiente que funciona. O ICE primeiro tenta fazer uma conexão usando o endereço do host obtido do sistema operacional e da placa de rede de um dispositivo. Se isso falhar (o que acontecerá com dispositivos atrás de NATs), o ICE vai receber um endereço externo usando um servidor STUN e, se isso falhar, o tráfego será roteado por um servidor de redirecionamento TURN.

Em outras palavras, um servidor STUN é usado para receber um endereço de rede externo, e os servidores TURN são usados para retransmitir o tráfego se a conexão direta (ponto a ponto) falhar.

Todo servidor TURN é compatível com STUN. Um servidor TURN é um servidor STUN com funcionalidade de retransmissão integrada. O ICE também lida com as complexidades das configurações de NAT. Na realidade, o NAT hole punching pode exigir mais do que apenas um endereço IP:porta público.

Os URLs dos servidores STUN e TURN são (opcionalmente) especificados por um app WebRTC no objeto de configuração iceServers, que é o primeiro argumento do construtor RTCPeerConnection. Para appr.tc, esse valor é assim:

{
  'iceServers': [
    {
      'urls': 'stun:stun.l.google.com:19302'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=udp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=tcp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    }
  ]
}

Depois que o RTCPeerConnection tiver essas informações, a mágica do ICE vai acontecer automaticamente. O RTCPeerConnection usa o framework ICE para descobrir o melhor caminho entre os pares, trabalhando com servidores STUN e TURN conforme necessário.

STUN

Os NATs fornecem a um dispositivo um endereço IP para uso em uma rede local privada, mas esse endereço não pode ser usado externamente. Sem um endereço público, não há como os pares do WebRTC se comunicarem. Para contornar esse problema, o WebRTC usa o STUN.

Os servidores STUN estão na Internet pública e têm uma tarefa: verificar o endereço IP:porta de uma solicitação recebida (de um app em execução atrás de um NAT) e enviar esse endereço de volta como resposta. Em outras palavras, o app usa um servidor STUN para descobrir o IP:porta de uma perspectiva pública. Esse processo permite que um par do WebRTC consiga um endereço acessível publicamente para si mesmo e o transmita para outro par por um mecanismo de sinalização para configurar um link direto.

Os servidores STUN não precisam fazer ou lembrar de muita coisa, então servidores STUN com especificações relativamente baixas podem lidar com um grande número de solicitações.

A maioria das chamadas do WebRTC faz uma conexão usando STUN, mas pode ser menor para chamadas entre pares por trás de firewalls e configurações de NAT complexas.

Conexão ponto a ponto usando um servidor STUN
Como usar servidores STUN para receber endereços IP:porta públicos

TURN

O RTCPeerConnection tenta configurar a comunicação direta entre os pares por UDP. Se isso falhar, o RTCPeerConnection vai recorrer ao TCP. Se isso falhar, os servidores TURN podem ser usados como substitutos, transmitindo dados entre endpoints.

Para reiterar, o TURN é usado para retransmitir streaming de áudio, vídeo e dados entre pares, não para sinalizar dados.

Os servidores TURN têm endereços públicos, então podem ser contatados por pares, mesmo que eles estejam atrás de firewalls ou proxies. Os servidores TURN têm uma tarefa: retransmitir um stream. Ao contrário dos servidores STUN, eles consomem muita largura de banda.

Conexão ponto a ponto usando um servidor STUN
The full Monty: STUN, TURN, and signaling

Este diagrama mostra o TURN em ação. O STUN puro não funcionou, então cada par recorre ao uso de um servidor TURN.

Como implantar servidores STUN e TURN

Para testes, o Google executa um servidor STUN público, stun.l.google.com:19302, conforme usado por appr.tc. Para um serviço STUN/TURN de produção, use o rfc5766-turn-server. O código-fonte dos servidores STUN e TURN está disponível no GitHub, onde você também pode encontrar links para várias fontes de informações sobre a instalação do servidor. Uma imagem de VM para Amazon Web Services também está disponível.

Uma alternativa de servidor TURN é o restund, disponível como código-fonte e também para a AWS. É possível configurar um restund no Compute :

  1. Abra o firewall conforme necessário para tcp=443, udp/tcp=3478.
  2. Crie quatro instâncias, uma para cada IP público, com a imagem padrão do Ubuntu 12.06.
  3. Defina a configuração do firewall local (permitir QUALQUER de QUALQUER).
  4. Instale as ferramentas: shell sudo apt-get install make sudo apt-get install gcc
  5. Instale o libre em creytiv.com/re.html.
  6. Busque o restund em creytiv.com/restund.html e descompacte.
  7. wget hancke.name/restund-auth.patch e aplique com patch -p1 < restund-auth.patch.
  8. Execute make, sudo make install para libre e restund.
  9. Adapte restund.conf às suas necessidades (substitua os endereços IP e verifique se ele contém a mesma chave secreta compartilhada) e copie para /etc.
  10. Copie restund/etc/restund para /etc/init.d/.
  11. Configure o restund:
    1. Defina LD_LIBRARY_PATH.
    2. Copie restund.conf para /etc/restund.conf.
    3. Defina restund.conf para usar os 10 caracteres à direita. sem precisar de endereço IP externo.
  12. Executar restund
  13. Teste usando o cliente stund da máquina remota: ./client IP:port

WebRTC para várias partes

Você também pode dar uma olhada no padrão IETF proposto por Justin Uberti para uma API REST para acesso aos serviços TURN.

Há vários casos de uso para streaming de mídia que vão além de uma chamada individual. Por exemplo, uma videoconferência entre um grupo de colegas ou um evento público com um palestrante e centenas ou milhões de espectadores.

Um app WebRTC pode usar várias RTCPeerConnections para que cada endpoint se conecte a todos os outros endpoints em uma configuração de malha. Essa é a abordagem adotada por apps como o talky.io, que funciona muito bem para um pequeno grupo de usuários. Além disso, o consumo de processamento e largura de banda se torna excessivo, especialmente para clientes móveis.

Malha: chamada pequena com N participantes
Topologia de malha completa: todos conectados a todos

Como alternativa, um app WebRTC pode escolher um endpoint para distribuir streams para todos os outros em uma configuração em estrela. Também é possível executar um endpoint WebRTC em um servidor e criar seu próprio mecanismo de redistribuição (um app cliente de exemplo é fornecido pelo webrtc.org).

Um MediaStream de um RTCPeerConnection pode ser usado como entrada para outro. Isso permite arquiteturas mais flexíveis, porque permite que um app da Web processe o roteamento de chamadas escolhendo a qual outro par se conectar. Para ver isso em ação, consulte Retransmissão de conexão de peering de amostras do WebRTC e Várias conexões de peering de amostras do WebRTC.

Unidade de controle multiponto

Uma opção melhor para um grande número de endpoints é usar uma unidade de controle multiponto (MCU, na sigla em inglês). Esse é um servidor que funciona como uma ponte para distribuir mídia entre um grande número de participantes. As MCUs podem lidar com diferentes resoluções, codecs e frame rates em uma videoconferência, processar transcodificação, fazer encaminhamento seletivo de stream e mixar ou gravar áudio e vídeo. Para chamadas com várias partes, há uma série de problemas a serem considerados, principalmente como exibir várias entradas de vídeo e mixar áudio de várias fontes. Plataformas de nuvem, como vLine, também tentam otimizar o roteamento de tráfego.

É possível comprar um pacote completo de hardware de MCU ou criar o seu próprio.

Vista traseira do Cisco MCU5300
A parte de trás de uma MCU da Cisco

Há várias opções de software de MCU de código aberto disponíveis. Por exemplo, o Licode (anteriormente conhecido como Lynckia) produz uma MCU de código aberto para o WebRTC. O OpenTok tem o Mantis.

Além dos navegadores: VoIP, telefones e mensagens

A natureza padronizada do WebRTC possibilita estabelecer comunicação entre um app WebRTC em execução em um navegador e um dispositivo ou plataforma em execução em outra plataforma de comunicação, como um telefone ou um sistema de videoconferência.

O SIP é um protocolo de sinalização usado por sistemas de VoIP e videoconferência. Para permitir a comunicação entre um app da Web WebRTC e um cliente SIP, como um sistema de videoconferência, o WebRTC precisa de um servidor proxy para mediar a sinalização. A sinalização precisa fluir pelo gateway, mas, depois que a comunicação é estabelecida, o tráfego SRTP (vídeo e áudio) pode fluir de peering para peering.

A rede pública de telefonia comutada (PSTN) é a rede de comutação de circuitos de todos os telefones analógicos "antigos". Para chamadas entre apps da Web WebRTC e telefones, o tráfego precisa passar por um gateway PSTN. Da mesma forma, os apps da Web do WebRTC precisam de um servidor XMPP intermediário para se comunicar com endpoints Jingle, como clientes de mensagem instantânea. O Jingle foi desenvolvido pelo Google como uma extensão do XMPP para permitir voz e vídeo em serviços de mensagens. As implementações atuais do WebRTC são baseadas na biblioteca libjingle em C++, uma implementação do Jingle desenvolvida inicialmente para o Talk.

Vários apps, bibliotecas e plataformas usam a capacidade do WebRTC de se comunicar com o mundo externo. Exemplo:

  • jsSIP: biblioteca SIP de JavaScript
  • Phono: API de telefone JavaScript de código aberto criada como um plug-in
  • Twilio: voz e mensagens

Saiba mais

O livro de 350 páginas WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web fornece muitos detalhes sobre dados e caminhos de sinalização e inclui vários diagramas detalhados de topologia de rede.