Ativar comunicação em tempo real com WebRTC

1. Antes de começar

Este codelab ensina como criar um app para fazer vídeos, tirar snapshots com a webcam e compartilhá-los ponto a ponto com o WebRTC. Você também aprenderá a usar as principais APIs WebRTC e configurar um servidor de mensagens com o Node.js.

Prerequisites

  • Conhecimento básico de HTML, CSS e JavaScript

O que você vai criar

  • Faça vídeos com a webcam.
  • Assista o vídeo por streaming com RTCPeerConnection.
  • Transmitir dados com RTCDataChannel.
  • Configure um serviço de sinalização para trocar mensagens.
  • Combinar conexão e sinal de peering.
  • Tire uma foto e use um canal de dados para compartilhá-la.

Pré-requisitos

  • Chrome 47 ou superior
  • Web Server for Chrome ou um servidor da Web de sua escolha
  • Um editor de texto de sua escolha
  • Node.js

2. Como conseguir o exemplo de código

Fazer o download do código

  1. Se você estiver familiarizado com o Git, execute este comando para clonar o código deste codelab no GitHub:
git clone https://github.com/googlecodelabs/webrtc-web

Se preferir, clique neste link para fazer o download de um arquivo ZIP com o código:

  1. Abra o arquivo ZIP transferido por download para descompactar uma pasta do projeto chamada webrtc-web-master, que contém uma pasta para cada etapa deste codelab e todos os recursos necessários.

Todo o código é executado no diretório work.

As pastas step-nn contêm uma versão concluída para cada etapa deste codelab. Eles estão ali para referência.

Instalar e verificar o servidor da Web

Embora você possa usar seu próprio servidor da Web, este codelab funciona bem com o servidor da Web para Chrome.

  1. Se você não tiver um servidor da Web para o Chrome, clique neste link para instalá-lo pela Chrome Web Store:

d0a4649b4920cf3.png

  1. Clique em Usar no Chrome, que instala o servidor da Web para o Chrome e abre automaticamente os apps do Google em uma nova guia.
  2. Clique em Servidor da Web:

27fce4494f641883.png

Uma caixa de diálogo será exibida. Com ela, você pode configurar seu servidor da Web local:

a300381a486b9e22.png

  1. Clique em Escolher pasta.
  2. Selecione a pasta work que você criou.

Em URLs de servidores da Web, você verá o URL de onde pode ver seu trabalho em andamento.

Google Chrome

  1. Em Opções (pode exigir reinicialização), marque a caixa de seleção Mostrar index.html automaticamente.
  2. Alterne Web Server: Started duas vezes para parar e reiniciar o servidor.

f23cafb3993dfac1.png

  1. Clique no URL em URLs do servidor da Web para ver seu trabalho no navegador da Web.

Você verá uma página como esta, que corresponde a work/index.html:

18a705cb6ccc5181.png

Obviamente, esse aplicativo ainda não está fazendo nada de interessante. Ele é apenas um esqueleto mínimo para garantir que o servidor da Web funcione corretamente. Você adicionará funcionalidades e recursos de layout nas etapas subsequentes.

3. Fazer streaming de vídeo com a webcam

Há uma versão completa desta etapa na pasta step-01.

Adicionar um traço de HTML

Copie este código e cole-o no arquivo index.html no diretório work para adicionar um elemento video e um script:

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <video autoplay playsinline></video>

  <script src="js/main.js"></script>

</body>

</html>

Adicionar um gesto de pinça do JavaScript

Copie este código e cole-o no arquivo main.js na pasta js:

'use strict';

// In this codelab, you  only stream video, not audio (video: true).
const mediaStreamConstraints = {
  video: true,
};

// The video element where the stream is displayed
const localVideo = document.querySelector('video');

// The local stream that's displayed on the video
let localStream;

// Handle success and add the MediaStream to the video element
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

// Handle error and log a message to the console with the error message
function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

// Initialize media stream
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);

Testar

Abra o arquivo index.html no navegador e você verá algo parecido com isso, mas com a visualização da webcam, é claro:

9297048e43ed0f3d.png

Como funciona

Após a chamada de getUserMedia(), o navegador solicita permissão para acessar sua câmera se ela for a primeira solicitação de acesso à câmera para a origem atual.

Se for bem-sucedido, um MediaStream será retornado, que um elemento media poderá usar por meio do atributo srcObject:

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);


}
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

O argumento constraints permite que você especifique qual mídia será recebida. Neste exemplo, a mídia é somente de vídeo porque o áudio está desativado por padrão:

const mediaStreamConstraints = {
  video: true,
};

Você pode usar restrições para requisitos adicionais, como resolução de vídeo:

const hdConstraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  }
}

A especificação MediaTrackConstraints lista todos os possíveis tipos de restrição, embora nem todas as opções sejam compatíveis com todos os navegadores. Se a resolução solicitada não for compatível com a câmera selecionada no momento, o getUserMedia() será rejeitado com um OverconstrainedError e você precisará permitir o acesso à câmera.

Se getUserMedia() for bem-sucedido, o stream de vídeo da webcam será definido como a origem do elemento de vídeo:

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Pontuação de pontos

  • O objeto localStream transmitido para getUserMedia() está no escopo global, por isso é possível inspecioná-lo no console do navegador. Abra o console, digite stream, e pressione Enter (Return no Mac). Para ver o console no Chrome, pressione Control+Shift+J (ou Command+Option+J no Mac).
  • O que localStream.getVideoTracks() retorna?
  • Chame o método localStream.getVideoTracks()[0].stop().
  • Observe o objeto de restrições. O que acontece quando você a muda para {audio: true, video: true}?
  • Qual é o tamanho do elemento de vídeo? Como você pode conseguir o tamanho natural do vídeo em JavaScript, em vez do tamanho da tela? Use as Ferramentas para desenvolvedores do Google Chrome para verificar.
  • Adicione filtros CSS ao elemento de vídeo da seguinte forma:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Adicione filtros SVG da seguinte forma:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Dicas

Prática recomendada

Verifique se o elemento de vídeo não transborda o contêiner. Este codelab adicionou width e max-width para definir um tamanho preferido e um tamanho máximo para o vídeo. O navegador calcula a altura automaticamente.

video {
  max-width: 100%;
  width: 320px;
}

4. Transmitir vídeos com a API RTCPeerConnection

Há uma versão completa desta etapa na pasta step-2.

Adicionar elementos de vídeo e botões de controle

No arquivo index.html, substitua o único elemento video por dois elementos video e três button:

<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>


<div>
  <button id="startButton">Start</button>
  <button id="callButton">Call</button>
  <button id="hangupButton">Hang Up</button>
</div>

Um elemento exibe o stream de getUserMedia(), e o outro mostra o mesmo vídeo transmitido pelo RTCPeerconnection. Em um app real, um elemento video exibe o stream local, e o outro exibe o stream remoto.

Adicionar o paliativo adaptador.js

Copie este elemento de script e cole-o acima do elemento de script para main.js:

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

Seu arquivo index.html ficará assim:

<!DOCTYPE html>
<html>

<head>
  <title>Real-time communication with WebRTC</title>
  <link rel="stylesheet" href="css/main.css" />
</head>

<body>
  <h1>Real-time communication with WebRTC</h1>

  <video id="localVideo" autoplay playsinline></video>
  <video id="remoteVideo" autoplay playsinline></video>

  <div>
    <button id="startButton">Start</button>
    <button id="callButton">Call</button>
    <button id="hangupButton">Hang Up</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
</body>
</html>

Instalar o código RTCPeerConnection

Substitua main.js pela versão na pasta step-02.

Fazer a chamada

  1. Abra o arquivo index.html.
  2. Clique em Iniciar para fazer o vídeo com a webcam.
  3. Clique em Ligar para fazer a conexão de peering

Você verá o mesmo vídeo com a webcam nos dois elementos video.

  1. Acesse o console do navegador para ver a geração de registros WebRTC.

Como funciona

Esta etapa é muito útil.

O WebRTC usa a API RTCPeerConnection para configurar uma conexão para fazer streaming de vídeo entre clientes WebRTC, conhecidos como apps semelhantes.

Neste exemplo, os dois objetos RTCPeerConnection estão na mesma página: pc1 e pc2.

A configuração da chamada entre apps semelhantes ao WebRTC envolve três tarefas:

  1. Crie um RTCPeerConnection para cada fim da chamada e, em cada extremidade, adicione o stream local de getUserMedia().
  2. Receber e compartilhar informações de rede.

Os possíveis endpoints de conexão são conhecidos como candidatos ICE.

  1. Veja e compartilhe descrições locais e remotas.

Os metadados sobre mídia local estão no formato Session Descrição Protocol (SDP).

Imagine que Alice e Beto usem o RTCPeerConnection para configurar um chat por vídeo.

Primeiro, Alice e Beto trocam informações sobre a rede. A expressão encontrar candidatos refere-se ao processo de encontrar interfaces e portas de rede usando o framework ICE.

  1. A Alice cria um objeto RTCPeerConnection com um gerenciador onicecandidate (addEventListener('icecandidate')).

Isso corresponde ao seguinte código de main.js:

let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Alice chama getUserMedia() e adiciona o fluxo transmitido a ele:
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
  then(gotLocalMediaStream).
  catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
  1. O gerenciador onicecandidate da primeira etapa é chamado quando os candidatos de rede são disponibilizados.
  2. Alice envia dados de candidato serializados para Beto.

Em um app real, esse processo, conhecido como sinalização, ocorre por um serviço de mensagens. Você aprenderá a fazer isso em uma etapa posterior. É claro que, nesta etapa, os dois objetos RTCPeerConnection estão na mesma página e podem se comunicar diretamente sem a necessidade de mensagens externas.

  1. Quando Beto recebe uma mensagem de Alice, ele chama o addIceCandidate() para adicionar o candidato à descrição remota do colega:
function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}

Os apps semelhantes também precisam descobrir e trocar informações de mídia de áudio e vídeo locais e remotas, como recursos de resolução e codec. A troca para informações de configuração de mídia prossegue com a troca de blobs de metadados, conhecidos como oferta e resposta, usando o formato SDP.

  1. A Alice executa o método RTCPeerConnection createOffer().

A promessa retornada fornece uma descrição da sessão local de RTCSessionDescription: Alice:

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. Se for bem-sucedida, Alice definirá a descrição local usando setLocalDescription() e enviará a descrição da sessão a Bob pelo canal de sinalização.
  2. Beto define a descrição que Alice enviou para ele como a descrição remota com setRemoteDescription().
  3. Bob executa o método createAnswer() da RTCPeerConnection e transmite a descrição remota que ele recebeu da Alice para que uma sessão local seja gerada e seja compatível com a dela.
  4. A promessa createAnswer() transmite um RTCSessionDescription, que Bob define como a descrição local e envia para a Alice.
  5. Quando Alice recebe a descrição da sessão de Beto, ela a define como descrição remota com setRemoteDescription().
// Logs offer creation and sets peer connection session descriptions
function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

// Logs answer to offer creation and sets peer-connection session descriptions
function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}

Pontuação de pontos

  1. Acesse chrome://webrtc-internals.

Esta página fornece estatísticas do WebRTC e dados de depuração. Você pode ver uma lista completa de URLs do Chrome em chrome://about.

  1. Definir o estilo da página com CSS:
  2. Coloque os vídeos lado a lado.
  3. Use uma largura maior para os botões.
  4. Verifique se o layout funciona em dispositivos móveis.
  5. No Console das Ferramentas para desenvolvedores do Chrome, confira localStream, localPeerConnection e remotePeerConnection.
  6. No console, consulte localPeerConnectionpc1.localDescription.

Qual é a aparência do formato SDP?

Dicas

  • Para mais informações sobre o shim do adaptador.js, consulte o repositório do GitHub (em inglês) do adapter.js.
  • Confira o AppRTC e o código dele (link em inglês), o app canônico do projeto WebRTC para chamadas WebRTC. O tempo de configuração da chamada é inferior a 500 ms.

Prática recomendada

Para preparar o código para uso futuro, use as novas APIs baseadas em promessas e ative a compatibilidade com navegadores que não são compatíveis com adapter.js.

5. Usar um canal de dados para trocar informações

Há uma versão completa desta etapa na pasta step-03.

Atualizar seu HTML

Nesta etapa, você usará os canais de dados WebRTC para enviar texto entre dois elementos textarea na mesma página. Isso não é muito útil, mas demonstra como o WebRTC pode ser usado para compartilhar dados e fazer streaming de vídeos.

Remova os elementos video e button do index.html, e substitua-os pelo seguinte HTML:

<textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>

<div id="buttons">
  <button id="startButton">Start</button>
  <button id="sendButton">Send</button>
  <button id="closeButton">Stop</button>
</div>

Um textarea serve para inserir texto, enquanto o outro é para exibir o texto conforme transmitido entre apps semelhantes.

Seu arquivo index.html ficará assim:

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
  <textarea id="dataChannelReceive" disabled></textarea>

  <div id="buttons">
    <button id="startButton">Start</button>
    <button id="sendButton">Send</button>
    <button id="closeButton">Stop</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Atualizar o JavaScript

  1. Substitua main.js pelo conteúdo do step-03/js/main.js.
  1. Tente fazer streaming de dados entre apps semelhantes:
  2. Abra o index.html
  3. Clique em Iniciar para configurar a conexão de peering.
  4. Digite um texto no textarea à esquerda.
  5. Clique em Enviar para transferir o texto usando um canal de dados WebRTC.

Como funciona

Esse código usa RTCPeerConnection e RTCDataChannel para permitir a troca de mensagens de texto.

Grande parte do código nesta etapa é igual ao do exemplo RTCPeerConnection. As funções sendData() e createConnection() têm a maior parte do novo código:

function createConnection() {
  dataChannelSend.placeholder = '';
  var servers = null;
  pcConstraint = null;
  dataConstraint = null;
  trace('Using SCTP based data channels');
  // For SCTP, reliable and ordered delivery is true by default.
  // Add localConnection to global scope to make it visible
  // from the browser console.
  window.localConnection = localConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created local peer connection object localConnection');

  sendChannel = localConnection.createDataChannel('sendDataChannel',
      dataConstraint);
  trace('Created send data channel');

  localConnection.onicecandidate = iceCallback1;
  sendChannel.onopen = onSendChannelStateChange;
  sendChannel.onclose = onSendChannelStateChange;

  // Add remoteConnection to global scope to make it visible
  // from the browser console.
  window.remoteConnection = remoteConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created remote peer connection object remoteConnection');

  remoteConnection.onicecandidate = iceCallback2;
  remoteConnection.ondatachannel = receiveChannelCallback;

  localConnection.createOffer().then(
    gotDescription1,
    onCreateSessionDescriptionError
  );
  startButton.disabled = true;
  closeButton.disabled = false;
}

function sendData() {
  var data = dataChannelSend.value;
  sendChannel.send(data);
  trace('Sent Data: ' + data);
}

A sintaxe da RTCDataChannel é deliberadamente semelhante à WebSocket com um método send() e um evento message.

Observe o uso de dataConstraint. Os canais de dados podem ser configurados para ativar diferentes tipos de compartilhamento de dados, como priorizar a entrega confiável, em vez do desempenho.

Pontuação de pontos

  1. Com o SCTP, o protocolo usado pelos canais de dados WebRTC, a entrega confiável e ordenada de dados é ativada por padrão. Quando o RTCDataChannel precisa fornecer uma entrega confiável de dados e quando o desempenho pode ser mais importante, mesmo que isso signifique perder alguns dados?
  2. Use CSS para melhorar o layout da página e adicionar um atributo de marcador ao textarea do dataChannelReceive.
  3. Teste a página em um dispositivo móvel.

Saiba mais

6. Configurar um serviço de sinalização para trocar mensagens

Você aprendeu a trocar dados entre pares na mesma página, mas como fazer isso em diferentes máquinas? Primeiro, você precisa configurar um canal de sinalização para trocar mensagens de metadados.

Há uma versão completa desta etapa na pasta step-04.

Sobre o aplicativo

O WebRTC usa uma API JavaScript do lado do cliente, mas para uso real, também é necessário um servidor de sinalização (mensagens), além de servidores STUN e turn. Saiba mais neste link.

Nesta etapa, você criará um servidor de sinalização Node.js simples usando o módulo Node.js e a biblioteca JavaScript para mensagens.

Neste exemplo, o servidor (o app Node.js) é implementado em index.js, e o cliente que é executado nele (o app da Web) é implementado em index.html.

O app Node.js nesta etapa tem duas tarefas.

Primeiro, ele funciona como um redirecionamento de mensagem:

socket.on('message', function (message) {
  log('Got message: ', message);
  socket.broadcast.emit('message', message);
});

Em segundo lugar, as salas de chat por vídeo WebRTC são gerenciadas:

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

Seu app WebRTC simples permite que no máximo dois apps semelhantes compartilhem uma sala.

HTML e JavaScript

  1. Atualize o index.html para que ele fique assim:
<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <script src="/socket.io/socket.io.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

Nesta etapa, você não verá nada na página. Todos os registros são feitos no console do navegador. Para ver o console no Chrome, pressione Control+Shift+J (ou Command+Option+J no Mac).

  1. Substitua js/main.js pelo seguinte:
'use strict';

var isInitiator;

window.room = prompt("Enter room name:");

var socket = io.connect();

if (room !== "") {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

socket.on('created', function(room, clientId) {
  isInitiator = true;
});

socket.on('full', function(room) {
  console.log('Message from client: Room ' + room + ' is full :^(');
});

socket.on('ipaddr', function(ipaddr) {
  console.log('Message from client: Server IP address is ' + ipaddr);
});

socket.on('joined', function(room, clientId) {
  isInitiator = false;
});

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

Configurar o arquivo Socket.IO para ser executado em Node.js

No arquivo HTML, talvez você tenha visto que está usando um arquivo Socket.IO:

<script src="/socket.io/socket.io.js"></script>
  1. No nível superior do diretório work, crie um arquivo chamado package.json com o seguinte conteúdo:
{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

Este é um manifesto do app que informa ao Gerenciador de pacotes de nós (npm) qual projeto

dependências a serem instaladas.

  1. Para instalar dependências, como /socket.io/socket.io.js, execute o seguinte no terminal de linha de comando no seu diretório work:
npm install

Você verá um registro de instalação que termina com a seguinte mensagem:

3ab06b7bcc7664b9.png.

Como você pode ver, npm instalou as dependências definidas em package.json.

  1. Crie um novo arquivo index.js no nível superior do diretório work (não no diretório js) e adicione o seguinte código:
'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(8080);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {

  // Convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('message', function(message) {
    log('Client said: ', message);
    // For a real app, would be room-only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;

    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });

});
  1. No terminal de linha de comando, execute o seguinte comando no diretório work:
node index.js
  1. No navegador, acesse http://localhost:8080.

Toda vez que você navegar até esse URL, receberá uma solicitação para inserir um nome para a sala.

Para entrar na mesma sala, digite o mesmo nome de sala todas as vezes, como foo.

  1. Abra uma nova guia, acesse http://localhost:8080 novamente e digite o mesmo nome de sala.
  2. Abra outra guia, navegue até http://localhost:8080 novamente e digite o mesmo nome de sala.
  3. Verifique o console em cada uma das guias.

Você verá a geração de registros do JavaScript.

Pontuação de pontos

  • Quais mecanismos alternativos de troca de mensagens são possíveis? Que problemas você pode encontrar usando o WebSocket puro?
  • Que problemas podem estar envolvidos no escalonamento deste app? Você pode desenvolver um método para testar milhares ou milhões de solicitações simultâneas de salas?
  • Este app usa uma solicitação JavaScript para encontrar um nome de sala. Descubra como conseguir o nome da sala no URL. Por exemplo, http://localhost:8080/foo fornece o nome da sala foo.

Saiba mais

7. Combinar conexão e sinal de peering

Há uma versão completa desta etapa na pasta step-05.

Substituir HTML e JavaScript

  1. Substitua os conteúdos de index.html pelo seguinte:
<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <div id="videos">
    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>
  1. Substitua js/main.js pelo conteúdo do step-05/js/main.js.

Executar o servidor Node.js

Se você não está seguindo este codelab do diretório work, talvez seja necessário instalar as dependências da pasta step-05 ou da pasta de trabalho atual.

  1. Execute o seguinte comando no seu diretório de trabalho:
npm install
  1. Depois de instalado, se o servidor Node.js não estiver em execução, execute-o com o seguinte comando no diretório work:
node index.js

Verifique se você está usando a versão do index.js da etapa anterior que implementa Socket.IO. Para mais informações sobre E/S de nó e soquete, consulte a seção Configurar um serviço de sinalização para trocar mensagens.

  1. No navegador, acesse http://localhost:8080.
  2. Abra uma nova guia e navegue até http://localhost:8080 novamente.

Um elemento video exibe o stream local de getUserMedia(), e o outro mostra o vídeo remoto transmitido por RTCPeerconnection.

  1. Veja a geração de registros no console do navegador.

Pontuação ****** em pontos

  • Este app é compatível com apenas um chat por vídeo. Como você pode mudar o design para que mais de uma pessoa compartilhe a mesma sala de chat por vídeo?
  • O exemplo tem o nome da sala foo codificado. Qual é a melhor maneira de ativar outros nomes de sala?
  • Como os usuários compartilhariam o nome da sala? Tente criar uma alternativa ao compartilhamento de nomes de salas.
  • Como você pode alterar o app?

Dicas

  • Veja estatísticas do WebRTC e depure dados em chrome://webrtc-internals.
  • Use o Solucionador de problemas de WebRTC para verificar o ambiente local e teste a câmera e o microfone.
  • Se você tiver problemas estranhos com o armazenamento em cache, tente o seguinte:
  1. Pressione Control e clique em Atualizar esta página.
  2. Reinicie o navegador.
  3. Execute npm cache clean na linha de comando.

8. Tirar uma foto e compartilhá-la usando um canal de dados

Há uma versão completa desta etapa na pasta step-06.

Como funciona

Anteriormente, você aprendeu a trocar mensagens de texto usando RTCDataChannel. Esta etapa possibilita o compartilhamento de arquivos inteiros. Neste exemplo, as fotos são capturadas com getUserMedia().

As partes principais desta etapa são as seguintes:

  1. Estabelecer um canal de dados.

Nesta etapa, você não adiciona nenhum fluxo de mídia à conexão de peering.

  1. Capture o stream de vídeo com a webcam usando o getUserMedia():
var video = document.getElementById('video');

function grabWebCamVideo() {
  console.log('Getting user media (video) ...');
  navigator.mediaDevices.getUserMedia({
    video: true
  })
  .then(gotStream)
  .catch(function(e) {
    alert('getUserMedia() error: ' + e.name);
  });
}
  1. Clique em Ajustar para ver um resumo (um frame de vídeo) do stream de vídeo e exibi-lo em um elemento canvas:
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');

function snapPhoto() {
  photoContext.drawImage(video, 0, 0, photo.width, photo.height);
  show(photo, sendBtn);
}
  1. Clique em Enviar para converter a imagem em bytes e enviá-la por um canal de dados:
function sendPhoto() {
  // Split the data-channel message in chunks of this byte length.
  var CHUNK_LEN = 64000;
  var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
    len = img.data.byteLength,
    n = len / CHUNK_LEN | 0;

  console.log('Sending a total of ' + len + ' byte(s)');
  dataChannel.send(len);

  // Split the photo and send in chunks of approximately 64KB.
  for (var i = 0; i < n; i++) {
    var start = i * CHUNK_LEN,
      end = (i + 1) * CHUNK_LEN;
    console.log(start + ' - ' + (end - 1));
    dataChannel.send(img.data.subarray(start, end));
  }

  // Send the reminder, if applicable.
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}

O lado de recebimento converte bytes de mensagens de canais de dados em uma imagem e exibe a imagem para o usuário:

function receiveDataChromeFactory() {
  var buf, count;

  return function onmessage(event) {
    if (typeof event.data === 'string') {
      buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
      count = 0;
      console.log('Expecting a total of ' + buf.byteLength + ' bytes');
      return;
    }

    var data = new Uint8ClampedArray(event.data);
    buf.set(data, count);

    count += data.byteLength;
    console.log('count: ' + count);

    if (count === buf.byteLength) {
      // we're done: all data chunks have been received
      console.log('Done. Rendering photo.');
      renderPhoto(buf);
    }
  };
}

function renderPhoto(data) {
  var canvas = document.createElement('canvas');
  canvas.width = photoContextW;
  canvas.height = photoContextH;
  canvas.classList.add('incomingPhoto');
  // The trail is the element that holds the incoming images.
  trail.insertBefore(canvas, trail.firstChild);

  var context = canvas.getContext('2d');
  var img = context.createImageData(photoContextW, photoContextH);
  img.data.set(data);
  context.putImageData(img, 0, 0);
}

Acessar o código

  1. Substitua o conteúdo da sua pasta work pelo conteúdo do step-06.

Seu arquivo index.html em work terá esta aparência agora**:**

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <h2>
    <span>Room URL: </span><span id="url">...</span>
  </h2>

  <div id="videoCanvas">
    <video id="camera" autoplay></video>
    <canvas id="photo"></canvas>
  </div>

  <div id="buttons">
    <button id="snap">Snap</button><span> then </span><button id="send">Send</button>
    <span> or </span>
    <button id="snapAndSend">Snap &amp; Send</button>
  </div>

  <div id="incoming">
    <h2>Incoming photos</h2>
    <div id="trail"></div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>
  1. Se você não está seguindo este codelab do diretório work, talvez seja necessário instalar as dependências da pasta step-06 ou da pasta de trabalho atual. Basta executar o seguinte comando no seu diretório de trabalho:
npm install
  1. Depois de instalado, se o servidor Node.js não estiver em execução, execute-o com o comando a seguir no diretório work:
node index.js
    Make sure that you're using the version of `index.js` that implements Socket.IO and 

lembre-se de reiniciar o servidor Node.js se fizer alterações.

Para mais informações sobre o Node e o Socket.IO, consulte a seção Configurar um sinal

para trocar mensagens.

  1. Se necessário, clique em Permitir para que o aplicativo use a webcam.

O app cria um código de sala aleatório e o adiciona ao URL.

  1. Abra o URL na barra de endereço em uma nova guia ou janela do navegador.
  2. Clique em Snap & Send e veja as Entrada de fotos na outra guia, na parte inferior da página.

O app transfere fotos entre guias.

Você verá algo parecido com:

911b40f36ba6ba8.png

Pontuação de pontos

Como mudar o código para permitir o compartilhamento de qualquer tipo de arquivo?

Saiba mais

9. Parabéns

Você criou um app de streaming de vídeo e troca de dados em tempo real.

Saiba mais