1. Introdução
O WebRTC é um projeto de código aberto para permitir a comunicação em tempo real de áudio, vídeo e dados em apps nativos e da Web.
O WebRTC tem várias APIs JavaScript. Clique nos links para ver demonstrações.
getUserMedia(): captura áudio e vídeo.MediaRecorder: gravar áudio e vídeo.RTCPeerConnection: faça streaming de áudio e vídeo entre usuários.RTCDataChannel: transmita dados entre usuários.
Onde posso usar o WebRTC?
No Firefox, Opera e no Chrome para computador e Android. O WebRTC também está disponível para apps nativos no iOS e no Android.
O que é sinalização?
O WebRTC usa o RTCPeerConnection para comunicar dados de streaming entre navegadores, mas também precisa de um mecanismo para coordenar a comunicação e enviar mensagens de controle, um processo conhecido como sinalização. Os métodos e protocolos de sinalização não são especificados pelo WebRTC. Neste codelab, você vai usar o Socket.IO para mensagens, mas há muitas alternativas.
O que são STUN e TURN?
O WebRTC foi projetado para funcionar ponto a ponto, para que os usuários possam se conectar pelo caminho mais direto possível. No entanto, o WebRTC foi criado para lidar com redes do mundo real: os aplicativos cliente precisam atravessar gateways NAT e firewalls, e as redes ponto a ponto precisam de alternativas caso a conexão direta falhe. Como parte desse processo, as APIs WebRTC usam servidores STUN para receber o endereço IP do seu computador e servidores TURN para funcionar como servidores de retransmissão caso a comunicação ponto a ponto falhe. O WebRTC no mundo real explica isso com mais detalhes.
O WebRTC é seguro?
A criptografia é obrigatória para todos os componentes do WebRTC, e as APIs JavaScript só podem ser usadas em origens seguras (HTTPS ou localhost). Os mecanismos de sinalização não são definidos pelos padrões do WebRTC. Portanto, cabe a você usar protocolos seguros.
2. Visão geral
Crie um app para gravar vídeos e tirar fotos com a webcam e compartilhe tudo ponto a ponto via WebRTC. Ao longo do processo, você vai aprender a usar as principais APIs WebRTC e configurar um servidor de mensagens usando o Node.js.
O que você vai aprender
- Gravar vídeo com a webcam
- Fazer streaming de vídeo com RTCPeerConnection
- Transmitir dados com RTCDataChannel
- Configurar um serviço de sinalização para trocar mensagens
- Combinar conexão e sinalização de peer
- Tirar uma foto e compartilhar por um canal de dados
O que é necessário
- Chrome 47 ou mais recente
- Servidor da Web para Chrome ou use seu próprio servidor da Web.
- Código de amostra
- Um editor de texto
- Conhecimento básico de HTML, CSS e JavaScript
3. Acessar o exemplo de código
Fazer o download do código
Se você conhece o git, pode fazer o download do código deste codelab no GitHub clonando-o:
git clone https://github.com/googlecodelabs/webrtc-web
Se preferir, clique no botão a seguir para fazer o download de um arquivo .zip do código:
Abra o arquivo ZIP baixado. Isso descompacta uma pasta de projeto (adaptive-web-media) que contém uma pasta para cada etapa deste codelab, além de todos os recursos necessários.
Todo o trabalho de programação será feito no diretório chamado work.
As pastas step-nn contêm uma versão finalizada para cada etapa deste codelab. Elas servem como referência.
Instalar e verificar o servidor da Web
Embora você possa usar seu próprio servidor da Web, este codelab foi projetado para funcionar bem com o Chrome Web Server. Se você ainda não tiver o app instalado, poderá instalá-lo pela Chrome Web Store.

Depois de instalar o app Servidor da Web para Chrome, clique no atalho dos apps do Chrome na barra de favoritos, na página "Nova guia" ou no Acesso rápido aos apps:

Clique no ícone do servidor da Web:

Em seguida, você verá esta caixa de diálogo, que permite configurar seu servidor da Web local:

Clique no botão ESCOLHER PASTA e selecione a pasta work que você acabou de criar. Isso permite que você veja seu trabalho em andamento no Chrome pelo URL destacado na caixa de diálogo do servidor da Web na seção URLs do servidor da Web.
Em Opções, marque a caixa ao lado de Mostrar automaticamente index.html, conforme mostrado abaixo:

Em seguida, pare e reinicie o servidor deslizando a chave rotulada como Servidor da Web: INICIADO para a esquerda e depois para a direita.

Agora, acesse o site de trabalho no navegador da Web clicando no URL do servidor da Web destacado. Você vai ver uma página como esta, que corresponde a work/index.html:

Obviamente, esse app ainda não faz nada de interessante. Por enquanto, ele é apenas um esqueleto mínimo que estamos usando para garantir que seu servidor da Web esteja funcionando corretamente. Você vai adicionar funcionalidades e recursos de layout nas próximas etapas.
4. Fazer streaming de vídeo com sua webcam
O que você vai aprender
Nesta etapa, você vai aprender a:
- Receba um stream de vídeo da sua webcam.
- Manipula a reprodução de stream.
- Use CSS e SVG para manipular vídeos.
Uma versão completa desta etapa está na pasta step-01.
Um toque de HTML...
Adicione um elemento video e um elemento script a index.html no diretório work:
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<video autoplay playsinline></video>
<script src="js/main.js"></script>
</body>
</html>
...e um pouco de JavaScript
Adicione o seguinte a main.js na pasta js:
'use strict';
// On this codelab, you will be streaming only video (video: true).
const mediaStreamConstraints = {
video: true,
};
// Video element where stream will be placed.
const localVideo = document.querySelector('video');
// Local stream that will be reproduced on the video.
let localStream;
// Handles success by adding the MediaStream to the video element.
function gotLocalMediaStream(mediaStream) {
localStream = mediaStream;
localVideo.srcObject = mediaStream;
}
// Handles error by logging a message to the console with the error message.
function handleLocalMediaStreamError(error) {
console.log('navigator.getUserMedia error: ', error);
}
// Initializes media stream.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
Faça um teste
Abra index.html no navegador. Você vai ver algo assim (com a imagem da sua webcam, é claro!):

Como funciona
Após a chamada getUserMedia(), o navegador pede permissão ao usuário para acessar a câmera (se for a primeira vez que o acesso à câmera é solicitado para a origem atual). Se for bem-sucedido, um MediaStream será retornado, que pode ser usado por um elemento de mídia com o atributo srcObject:
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
}
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
}
O argumento constraints permite especificar qual mídia receber. Neste exemplo, somente vídeo, já que o áudio é desativado por padrão:
const mediaStreamConstraints = {
video: true,
};
É possível 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 tipos de restrição possíveis, mas nem todas as opções são compatíveis com todos os navegadores. Se a resolução solicitada não for compatível com a câmera selecionada, o getUserMedia() será rejeitado com um OverconstrainedError, e o usuário não vai receber uma solicitação de permissão para acessar a câmera.
Se getUserMedia() for bem-sucedido, o fluxo de vídeo da webcam será definido como a origem do elemento de vídeo:
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
}
Pontos de bônus
- O objeto
localStreamtransmitido paragetUserMedia()está no escopo global. Portanto, é possível inspecioná-lo no console do navegador: abra o console, digite stream e pressione "Return". Para ver o console no Chrome, pressione Ctrl+Shift+J ou Command+Option+J se estiver em um Mac. - O que
localStream.getVideoTracks()retorna? - Ligue para
localStream.getVideoTracks()[0].stop(). - Analise o objeto de restrições: o que acontece quando você o muda para
{audio: true, video: true}? - Qual é o tamanho do elemento de vídeo? Como posso extrair o tamanho natural do vídeo do JavaScript, em vez do tamanho de exibição? Use o Chrome DevTools para verificar.
- Tente adicionar filtros CSS ao elemento de vídeo. Exemplo:
video {
filter: blur(4px) invert(1) opacity(0.5);
}
- Tente adicionar filtros SVG. Exemplo:
video {
filter: hue-rotate(180deg) saturate(200%);
}
O que você aprendeu
Nesta etapa, você aprendeu a:
- Receber vídeo da webcam.
- Defina restrições de mídia.
- Mexa no elemento de vídeo.
Uma versão completa desta etapa está na pasta step-01.
Dicas
- Não se esqueça do atributo
autoplayno elementovideo. Sem isso, você só vai ver um único frame. - Há muitas outras opções de restrições
getUserMedia(). Confira a demonstração em webrtc.github.io/samples/src/content/peerconnection/constraints. Como você vai ver, há muitos exemplos interessantes de WebRTC nesse site.
Prática recomendada
- Verifique se o elemento de vídeo não transborda do contêiner. Adicionamos
widthemax-widthpara definir um tamanho preferido e um tamanho máximo para o vídeo. O navegador vai calcular a altura automaticamente:
video {
max-width: 100%;
width: 320px;
}
A seguir
Você tem o vídeo, mas como fazer o streaming? Descubra na próxima etapa.
5. Fazer streaming de vídeo com RTCPeerConnection
O que você vai aprender
Nesta etapa, você vai aprender a:
- Abstraia as diferenças do navegador com o shim WebRTC, adapter.js.
- Use a API RTCPeerConnection para fazer streaming de vídeo.
- Controlar a captura e o streaming de mídia.
Uma versão completa desta etapa está na pasta step-2.
O que é RTCPeerConnection?
RTCPeerConnection é uma API para fazer chamadas WebRTC para transmitir vídeo e áudio e trocar dados.
Este exemplo configura uma conexão entre dois objetos RTCPeerConnection (conhecidos como peers) na mesma página.
Não tem muito uso prático, mas é bom para entender como o RTCPeerConnection funciona.
Adicionar elementos de vídeo e botões de controle
Em index.html, substitua o elemento de vídeo único por dois elementos de vídeo e três botões:
<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 de vídeo vai mostrar o stream de getUserMedia(), e o outro vai exibir o mesmo vídeo transmitido via RTCPeerconnection. Em uma aplicação do mundo real, um elemento de vídeo mostraria o stream local e o outro, o remoto.
Adicionar o shim adapter.js
Adicione um link à versão atual de adapter.js acima do link para main.js:
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
O arquivo Index.html vai ficar assim:
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime 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 ligação
Abra index.html, clique no botão Iniciar para receber o vídeo da sua webcam e clique em Chamada para fazer a conexão ponto a ponto. O mesmo vídeo (da sua webcam) vai aparecer nos dois elementos. Consulte o console do navegador para ver o registro do WebRTC.
Como funciona
Esta etapa faz muito...
O WebRTC usa a API RTCPeerConnection para configurar uma conexão e transmitir vídeo entre clientes WebRTC, conhecidos como peers.
Neste exemplo, os dois objetos RTCPeerConnection estão na mesma página: pc1 e pc2. Não tem muito uso prático, mas é bom para demonstrar como as APIs funcionam.
A configuração de uma chamada entre peers do WebRTC envolve três tarefas:
- Crie um RTCPeerConnection para cada extremidade da chamada e, em cada extremidade, adicione o fluxo local de
getUserMedia(). - Receber e compartilhar informações de rede: os possíveis endpoints de conexão são conhecidos como candidatos ICE.
- Receber e compartilhar descrições locais e remotas: metadados sobre mídia local no formato SDP.
Imagine que Alice e Bob queiram usar o RTCPeerConnection para configurar um chat por vídeo.
Primeiro, Alice e Bob trocam informações de rede. A expressão "encontrar candidatos" se refere ao processo de encontrar interfaces de rede e portas usando a estrutura ICE.
- Alice cria um objeto RTCPeerConnection com um manipulador
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);
- 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.');
- O manipulador
onicecandidateda etapa 1 é chamado quando os candidatos de rede ficam disponíveis. - Alice envia dados de candidatos serializados para Bob. Em um aplicativo real, esse processo (conhecido como sinalização) acontece por um serviço de mensagens. Você vai aprender a fazer isso em uma etapa posterior. Nesta etapa, os dois objetos RTCPeerConnection estão na mesma página e podem se comunicar diretamente sem a necessidade de mensagens externas.
- Quando Bob recebe uma mensagem de candidato de Alice, ele chama
addIceCandidate()para adicionar o candidato à descrição do peer remoto:
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 participantes do WebRTC também precisam descobrir e trocar informações de mídia de áudio e vídeo locais e remotas, como resolução e recursos de codec. A sinalização para troca de informações de configuração de mídia é feita com a troca de blobs de metadados, conhecidos como oferta e resposta, usando o formato do Protocolo de descrição de sessão, conhecido como SDP:
- Alice executa o método
createOffer()do RTCPeerConnection. A promessa retornada fornece uma RTCSessionDescription: a descrição da sessão local de Alice:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
- Se a operação for bem-sucedida, Alice vai definir a descrição local usando
setLocalDescription()e enviar essa descrição de sessão para Bob pelo canal de sinalização. - Bob define a descrição que Alice enviou como a descrição remota usando
setRemoteDescription(). - Bob executa o método RTCPeerConnection
createAnswer(), transmitindo a descrição remota que recebeu de Alice. Assim, uma sessão local compatível com a dela pode ser gerada. A promessacreateAnswer()transmite uma RTCSessionDescription: Bob a define como a descrição local e a envia para Alice. - Quando Alice recebe a descrição da sessão de Bob, ela a define como a 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);
}
- Ping!
Pontos de bônus
- Confira chrome://webrtc-internals. Isso fornece estatísticas do WebRTC e dados de depuração. A lista completa de URLs do Chrome está em chrome://about.
- Aplique um estilo à página com CSS:
- Coloque os vídeos lado a lado.
- Deixe os botões com a mesma largura e um texto maior.
- Verifique se o layout funciona em dispositivos móveis.
- No console do Chrome DevTools, confira
localStream,localPeerConnectioneremotePeerConnection. - No console, confira
localPeerConnectionpc1.localDescription. Como é o formato SDP?
O que você aprendeu
Nesta etapa, você aprendeu a:
- Abstraia as diferenças do navegador com o shim WebRTC, adapter.js.
- Use a API RTCPeerConnection para fazer streaming de vídeo.
- Controlar a captura e o streaming de mídia.
- Compartilhar mídia e informações de rede entre usuários para ativar uma chamada WebRTC.
Uma versão completa desta etapa está na pasta step-2.
Dicas
- Há muito para aprender nesta etapa. Para encontrar outros recursos que explicam o RTCPeerConnection com mais detalhes, consulte webrtc.org. Esta página inclui sugestões de frameworks JavaScript se você quiser usar o WebRTC, mas não quiser lidar com as APIs.
- Saiba mais sobre o shim adapter.js no repositório adapter.js do GitHub.
- Quer saber como é o melhor app de chat por vídeo do mundo? Confira o AppRTC, o app canônico do projeto WebRTC para chamadas WebRTC: app, código. O tempo de configuração da chamada é inferior a 500 ms.
Prática recomendada
- Para proteger seu código contra futuras mudanças, use as novas APIs baseadas em promessas e ative a compatibilidade com navegadores que não as oferecem usando adapter.js.
A seguir
Esta etapa mostra como usar o WebRTC para transmitir vídeo entre usuários, mas este codelab também é sobre dados.
Na próxima etapa, descubra como transmitir dados arbitrários usando RTCDataChannel.
6. Usar RTCDataChannel para trocar dados
O que você vai aprender
- Como trocar dados entre endpoints (peers) do WebRTC.
Uma versão completa desta etapa está na pasta step-03.
Atualizar seu HTML
Nesta etapa, você vai usar 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ídeo.
Remova os elementos de vídeo e botão de 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>
Uma textarea será para inserir texto, e a outra vai mostrar o texto transmitido entre os usuários.
O arquivo index.html vai ficar assim:
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime 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
Substitua main.js pelo conteúdo de step-03/js/main.js.
Teste o streaming de dados entre peers: abra index.html, pressione Start para configurar a conexão de peer, insira um texto no textarea à esquerda e clique em Send para transferir o texto usando canais 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 exemplo do 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 do RTCDataChannel é propositalmente semelhante à do WebSocket, com um método send() e um evento message.
Observe o uso de dataConstraint. Os canais de dados podem ser configurados para permitir diferentes tipos de compartilhamento de dados, por exemplo, priorizando a entrega confiável em vez do desempenho. Saiba mais sobre as opções na Mozilla Developer Network (em inglês).
Pontos de bônus
- Com o SCTP, o protocolo usado pelos canais de dados do WebRTC, a entrega de dados confiável e ordenada fica ativada por padrão. Quando o RTCDataChannel precisa fornecer entrega confiável de dados e quando o desempenho é mais importante, mesmo que isso signifique perder alguns dados?
- Use CSS para melhorar o layout da página e adicione um atributo de marcador de posição à caixa de texto "dataChannelReceive".
- Teste a página em um dispositivo móvel.
O que você aprendeu
Nesta etapa, você aprendeu a:
- Estabelecer uma conexão entre dois peers do WebRTC.
- Troque dados de texto entre os participantes.
Uma versão completa desta etapa está na pasta step-03.
Saiba mais
- Canais de dados do WebRTC (de alguns anos atrás, mas ainda vale a pena ler)
- Por que o SCTP foi selecionado para o canal de dados do WebRTC?
A seguir
Você aprendeu a trocar dados entre usuários na mesma página, mas como fazer isso entre máquinas diferentes? Primeiro, configure um canal de sinalização para trocar mensagens de metadados. Descubra como na próxima etapa.
7. Configurar um serviço de sinalização para trocar mensagens
O que você vai aprender
Nesta etapa, você vai aprender a:
- Use
npmpara instalar as dependências do projeto, conforme especificado em package.json. - Execute um servidor Node.js e use o node-static para disponibilizar arquivos estáticos.
- Configure um serviço de mensagens em Node.js usando o Socket.IO.
- Use esse recurso para criar "salas" e trocar mensagens.
Uma versão completa desta etapa está na pasta step-04.
Conceitos
Para configurar e manter uma chamada WebRTC, os clientes WebRTC (peers) precisam trocar metadados:
- Informações sobre candidatos (rede).
- Mensagens de oferta e resposta que fornecem informações sobre mídia, como resolução e codecs.
Em outras palavras, é necessário trocar metadados antes que o streaming ponto a ponto de áudio, vídeo ou dados possa ocorrer. Esse processo é chamado de transmissão de indicadores.
Nas etapas anteriores, os objetos RTCPeerConnection do remetente e do destinatário estão na mesma página. Portanto, a "transmissão de sinalização" é apenas uma questão de transmitir metadados entre objetos.
Em um aplicativo do mundo real, as RTCPeerConnections do remetente e do destinatário são executadas em páginas da Web em dispositivos diferentes, e você precisa de uma maneira para que elas comuniquem metadados.
Para isso, use um servidor de sinalização, que pode transmitir mensagens entre clientes (peers) do WebRTC. As mensagens reais são texto simples: objetos JavaScript convertidos em strings.
Pré-requisito: instale o Node.js
Para executar as próximas etapas deste codelab (pastas step-04 a step-06), é necessário executar um servidor em localhost usando o Node.js.
Faça o download e instale o Node.js neste link ou pelo gerenciador de pacotes de sua preferência.
Depois de instalado, você poderá importar as dependências necessárias para as próximas etapas (execução de npm install), além de executar um pequeno servidor localhost para executar o codelab (execução de node index.js). Esses comandos serão indicados mais tarde, quando forem necessários.
Sobre o app
O WebRTC usa uma API JavaScript do lado do cliente, mas, para uso no mundo real, também requer um servidor de sinalização (mensagens), além de servidores STUN e TURN. Saiba mais aqui.
Nesta etapa, você vai criar um servidor de sinalização Node.js simples usando o módulo Node.js do Socket.IO e a biblioteca JavaScript para mensagens. Ter experiência com Node.js e Socket.IO é útil, mas não essencial. Os componentes de mensagens são muito simples.
Neste exemplo, o servidor (aplicativo Node.js) é implementado em index.js, e o cliente que é executado nele (o app da Web) é implementado em index.html.
O aplicativo Node.js nesta etapa tem duas tarefas.
Primeiro, ele atua como um retransmissor de mensagens:
socket.on('message', function (message) {
log('Got message: ', message);
socket.broadcast.emit('message', message);
});
Em segundo lugar, ele gerencia as "salas" de chat por vídeo do WebRTC:
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);
}
Nosso aplicativo WebRTC simples permite que no máximo dois usuários compartilhem uma sala.
HTML e JavaScript
Atualize index.html para que fique assim:
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<script src="/socket.io/socket.io.js"></script>
<script src="js/main.js"></script>
</body>
</html>
Nesta etapa, nada vai aparecer na página. Todo o registro é feito no console do navegador. Para ver o console no Chrome, pressione Ctrl+Shift+J ou Command+Option+J se estiver em um Mac.
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 Socket.IO para execução no Node.js
No arquivo HTML, você pode ter notado que está usando um arquivo Socket.IO:
<script src="/socket.io/socket.io.js"></script>
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"
}
}
É um manifesto do app que informa ao Node Package Manager (npm) quais dependências do projeto instalar.
Para instalar dependências (como /socket.io/socket.io.js), execute o seguinte no terminal de linha de comando, no diretório work:
npm install
Você vai ver um registro de instalação que termina assim:

Como você pode ver, o npm instalou as dependências definidas em package.json.
Crie um 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);
}
});
}
});
});
No terminal da linha de comando, execute o seguinte comando no diretório work:
node index.js
No navegador, abra localhost:8080.
Cada vez que você abrir esse URL, será solicitado que você insira um nome de sala. Para entrar na mesma sala, escolha o mesmo nome todas as vezes, como "foo".
Abra uma nova guia e acesse localhost:8080 novamente. Escolha o mesmo nome de sala.
Abra localhost:8080 em uma terceira guia ou janela. Escolha o mesmo nome de sala novamente.
Verifique o console em cada uma das guias. Você vai encontrar o registro do JavaScript acima.
Pontos de bônus
- Quais mecanismos de mensagens alternativos podem ser possíveis? Quais problemas você pode encontrar usando o WebSocket "puro"?
- Quais problemas podem estar envolvidos no escalonamento desse aplicativo? Você pode desenvolver um método para testar milhares ou milhões de solicitações de sala simultâneas?
- Esse app usa um prompt do JavaScript para receber o nome de uma sala. Encontre uma maneira de extrair o nome da sala do URL. Por exemplo, localhost:8080/foo daria o nome do ambiente
foo.
O que você aprendeu
Nesta etapa, você aprendeu a:
- Use o npm para instalar as dependências do projeto, conforme especificado em package.json.
- Execute um servidor Node.js para veicular arquivos estáticos.
- Configurar um serviço de mensagens em Node.js usando socket.io.
- Use esse recurso para criar "salas" e trocar mensagens.
Uma versão completa desta etapa está na pasta step-04.
Saiba mais
- Repositório de exemplo de chat do Socket.io
- WebRTC no mundo real: STUN, TURN e sinalização
- O termo "sinalização" no WebRTC
A seguir
Saiba como usar a sinalização para permitir que dois usuários façam uma conexão ponto a ponto.
8. Combinar conexão e sinalização de peer
O que você vai aprender
Nesta etapa, você vai aprender a:
- Executar um serviço de sinalização WebRTC usando o Socket.IO no Node.js
- Use esse serviço para trocar metadados do WebRTC entre usuários.
Uma versão completa desta etapa está na pasta step-05.
Substituir HTML e JavaScript
Substitua o conteúdo de index.html pelo seguinte:
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="/css/main.css" />
</head>
<body>
<h1>Realtime 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>
Substitua js/main.js pelo conteúdo de step-05/js/main.js.
Executar o servidor Node.js
Se você não estiver seguindo este codelab no diretório work, talvez seja necessário instalar as dependências da pasta step-05 ou da pasta de trabalho atual. Execute o seguinte comando no diretório de trabalho:
npm install
Depois da instalação, se o servidor Node.js não estiver em execução, inicie-o chamando o seguinte comando no diretório work:
node index.js
Verifique se você está usando a versão de index.js da etapa anterior que implementa o Socket.IO. Para mais informações sobre Node e Socket IO, consulte a seção "Configurar um serviço de sinalização para trocar mensagens".
No navegador, abra localhost:8080.
Abra localhost:8080 novamente em uma nova guia ou janela. Um elemento de vídeo vai mostrar o stream local de getUserMedia(), e o outro vai mostrar o vídeo "remoto" transmitido por RTCPeerconnection.
Confira o registro no console do navegador.
Pontos de bônus
- Este aplicativo só aceita chat por vídeo individual. Como você mudaria o design para permitir que mais de uma pessoa compartilhe a mesma sala de chat por vídeo?
- O exemplo tem o nome do ambiente foo codificado. Qual seria a melhor maneira de ativar outros nomes de salas?
- Como os usuários compartilham o nome da sala? Tente criar uma alternativa para compartilhar nomes de salas.
- Como você poderia mudar o app
O que você aprendeu
Nesta etapa, você aprendeu a:
- Executar um serviço de sinalização WebRTC usando o Socket.IO no Node.js.
- Use esse serviço para trocar metadados do WebRTC entre usuários.
Uma versão completa desta etapa está na pasta step-05.
Dicas
- As estatísticas e os dados de depuração do WebRTC estão disponíveis em chrome://webrtc-internals.
- O site test.webrtc.org pode ser usado para verificar seu ambiente local e testar a câmera e o microfone.
- Se você tiver problemas estranhos com o cache, tente o seguinte:
- Faça uma atualização forçada mantendo pressionada a tecla Ctrl e clicando no botão Atualizar.
- Reinicie o navegador.
- Execute
npm cache cleanna linha de comando.
A seguir
Saiba como tirar uma foto, receber os dados da imagem e compartilhar isso entre usuários remotos.
9. Tirar uma foto e compartilhar por um canal de dados
O que você vai aprender
Nesta etapa, você vai aprender a:
- Tire uma foto e extraia os dados dela usando o elemento canvas.
- Trocar dados de imagem com um usuário remoto.
Uma versão completa desta etapa está na pasta step-06.
Como funciona
Antes, você aprendeu a trocar mensagens de texto usando o RTCDataChannel.
Essa etapa permite compartilhar arquivos inteiros. Neste exemplo, fotos capturadas com o getUserMedia().
As principais partes desta etapa são as seguintes:
- Estabelecer um canal de dados. Nesta etapa, não adicione fluxos de mídia à conexão de mesmo nível.
- Capture o stream de vídeo da webcam do usuário com
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);
});
}
- Quando o usuário clicar no botão Snap, capture um instantâneo (um frame de vídeo) do fluxo de vídeo e mostre-o 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);
}
- Quando o usuário clicar no botão Enviar, converta a imagem em bytes e envie por um canal de dados:
function sendPhoto() {
// Split 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 about 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 any
if (len % CHUNK_LEN) {
console.log('last ' + len % CHUNK_LEN + ' byte(s)');
dataChannel.send(img.data.subarray(n * CHUNK_LEN));
}
}
- O lado receptor converte os bytes da mensagem do canal de dados de volta em uma imagem e a mostra ao 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');
// trail is the element holding 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
Substitua o conteúdo da pasta work pelo conteúdo de step-06. O arquivo index.html em work vai ficar assim:
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="/css/main.css" />
</head>
<body>
<h1>Realtime 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 & 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>
Se você não estiver seguindo este codelab no 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 diretório de trabalho:
npm install
Depois da instalação, se o servidor Node.js não estiver em execução, inicie-o chamando o seguinte comando no diretório work:
node index.js
Verifique se você está usando a versão de index.js que implementa o Socket.IO e reinicie o servidor Node.js se fizer mudanças. Para mais informações sobre Node e Socket IO, consulte a seção "Configurar um serviço de sinalização para trocar mensagens".
Se necessário, clique no botão Permitir para que o app use sua webcam.
O app vai criar um ID de sala aleatório e adicioná-lo ao URL. Abra o URL da barra de endereço em uma nova guia ou janela do navegador.
Clique no botão Snap & Send e confira a área "Incoming" na outra guia, na parte de baixo da página. O app transfere fotos entre guias.
Você verá algo como:

Pontos de bônus
- Como mudar o código para permitir o compartilhamento de qualquer tipo de arquivo?
Saiba mais
- API MediaStream Image Capture: uma API para tirar fotos e controlar câmeras. Em breve, no navegador mais perto de você!
- A API MediaRecorder, para gravação de áudio e vídeo: demonstração, documentação.
O que você aprendeu
- Como tirar uma foto e extrair os dados dela usando o elemento canvas.
- Como trocar esses dados com um usuário remoto.
Uma versão completa desta etapa está na pasta step-06.
10. Parabéns
Você criou um app para fazer streaming de vídeo e troca de dados em tempo real.
O que você aprendeu
Neste codelab, você aprendeu a:
- Receber vídeo da webcam.
- Fazer streaming de vídeo com RTCPeerConnection.
- Transmita dados com RTCDataChannel.
- Configure um serviço de sinalização para trocar mensagens.
- Combine conexão e sinalização de mesmo nível.
- Tire uma foto e compartilhe por um canal de dados.
Próximas etapas
- Confira o código e a arquitetura do aplicativo de chat WebRTC canônico AppRTC: app, code.
- Teste as demonstrações ao vivo em github.com/webrtc/samples.
Saiba mais
- Uma variedade de recursos para começar a usar o WebRTC está disponível em webrtc.org.