
Compartilhar:
Hui Jing é Developer Advocate na Nexmo. Ela tem um amor imenso por CSS e tipografia e, de modo geral, é apaixonada por tudo que diz respeito à web.
Transmissão de videochamada com JavaScript e Vonage
Tempo de leitura: 8 minutos
Esta série de tutoriais explorará a Video API da Vonage (anteriormente TokBox OpenTok) e o que você pode criar com ela. A Video API é muito robusta e altamente personalizável, e em cada postagem mostraremos como implementar um recurso específico usando a API. Desta vez, veremos como transmitir seu bate-papo por vídeo para muitos espectadores online.
Como essa aplicação exigirá algum código do lado do servidor, vamos usar o Glitch para facilitar a configuração. Você também pode baixar o código deste projeto do Glitch e implantá-lo no seu servidor ou na plataforma de hospedagem de sua escolha (provavelmente será necessário fazer alguns ajustes de configuração de acordo com os requisitos da sua plataforma).
Não utilizaremos nenhum framework de front-end nesta série, apenas JavaScript puro, para manter o foco na própria Video API. Ao final deste tutorial, você deverá ser capaz de transmitir seu chat de vídeo ao vivo para um grande público usando HTTP Live Streaming (HLS) ou uma transmissão RTMP.

O código final deste aplicativo pode ser encontrado neste repositório do GitHub ou modificado no Glitch.
Pré-requisitos
Antes de começarmos, você precisará de um Account da Video API da Vonage, que pode ser criado gratuitamente aqui. Você também precisará do do Node.js instalado (caso você não esteja usando o Glitch).
Este tutorial dá continuidade ao primeiro post introdutório da série: Criação de um chat de vídeo básico. Se esta for a primeira vez que você usa a Video API, recomendamos fortemente que você leia esse post, pois ele aborda a configuração básica a seguir:
Criar um projeto da Video API da Vonage
Configuração no Glitch
Estrutura básica do projeto
Inicializando uma sessão
Conectando-se à sessão, assinando e publicando
Estilos básicos de layout para um bate-papo por Video
Transmissões com a Video API da Vonage
A plataforma oferece suporte a dois tipos de transmissões: transmissões de vídeo interativas ao vivo e transmissões ao vivo por streaming. Ambos os tipos de transmissão exigem o uso de uma sessão roteada (uma sessão que utiliza o Roteador de Mídia da Video API da Vonage). Esse assunto é abordado com mais detalhes na próxima seção.
Transmissões interativas de vídeo ao vivo permitem que muitos clientes publiquem e se inscrevam nos fluxos de áudio e Video uns dos outros em tempo real. As sessões roteadas podem suportar transmissões de vídeo interativas ao vivo com até 3.000 fluxos entre clientes.
Transmissões ao vivo permitem que você compartilhe uma transmissão em HTTP ao vivo (HLS) ou um transmissão RTMP com um grande número de espectadores. O stream HLS ou RTMP é um único vídeo composto pelos streams individuais publicados na sessão. Para este tutorial, esse é o tipo de transmissão que usaremos.
HTTP Live Streaming (HLS) é um protocolo de streaming de mídia que visa transmitir vídeos contínuos e de longa duração pela Internet de maneira confiável. Foi desenvolvido pela Apple e lançado em 2009.
O HLS utiliza uma CDN para a transmissão e é uma transmissão tradicional com alta latência (15–20 segundos) e sem interação. Um espectador que utilize o HLS receberá o conteúdo com uma latência de 15–20 segundos, portanto, esse formato não se presta diretamente a casos de uso interativos.
Protocolo de Mensagens em Tempo Real (RTMP) é um protocolo baseado em TCP projetado para a transmissão de áudio, Video e dados. Originalmente desenvolvido como um protocolo proprietário pela Macromedia, é hoje uma especificação aberta divulgada pela Adobe.
Embora o RTMP tenha menor latência (cerca de 5 segundos) do que o HLS, ele também não é adequado para casos de uso que exijam interatividade. Você usaria o RTMP para enviar conteúdo criado com a Video API da Vonage para plataformas de vídeo nas redes sociais, como o Facebook ou o YouTube Live.
Configuração inicial
Como vamos desenvolver um chat de vídeo básico, comece remixando o projeto do chat de vídeo básico criado no tutorial anterior. Clique no grande botão “Remix” abaixo para fazer isso. 👇

Sua estrutura de pastas deve ficar mais ou menos assim:

Conforme mencionado no início, o TokBox OpenTok agora é a Video API da Vonage. Não fizemos nenhuma alteração nos nomes dos nossos pacotes, portanto, você continuará fazendo referência ao OpenTok em seu código.
Se você tivesse feito um remix do projeto Glitch, seu server.js arquivo já deveria estar assim:
const express = require("express");
const app = express();
const OpenTok = require("opentok");
const OT = new OpenTok(process.env.API_KEY, process.env.API_SECRET);
let sessions = {};
app.use(express.static("public"));
app.get("/", (request, response) => {
response.sendFile(__dirname + "/views/landing.html");
});
app.get("/session/:room", (request, response) => {
response.sendFile(__dirname + "/views/index.html");
});
app.post("/session/:room", (request, response) => {
const roomName = request.params.room;
// Check if the session already exists
if (sessions[roomName]) {
// Generate the token
generateToken(roomName, response);
} else {
// If the session does not exist, create one
OT.createSession((error, session) => {
if (error) {
console.log("Error creating session:", error);
} else {
// Store the session in the sessions object
sessions[roomName] = session.sessionId;
// Generate the token
generateToken(roomName, response);
}
});
}
});
function generateToken(roomName, response) {
// Configure token options
const tokenOptions = {
role: "publisher",
data: `roomname=${roomName}`
};
// Generate token with the Video API Client SDK
let token = OT.generateToken(
sessions[roomName],
tokenOptions
);
// Send the required credentials back to to the client
// as a response from the fetch request
response.status(200);
response.send({
sessionId: sessions[roomName],
token: token,
apiKey: process.env.API_KEY
});
}
const listener = app.listen(process.env.PORT, () => {
console.log("Your app is listening on port " + listener.address().port);
});
Para colocar o chat por vídeo em funcionamento, acesse o .env arquivo e preencha com sua chave de API e seu segredo do projeto, que você encontra no painel de controle. Depois disso, vamos trabalhar no código do lado do cliente para fazer o chat de texto funcionar antes de revisitar o server.js arquivo novamente.
Adicione a marcação necessária
Nosso aplicativo será composto por três páginas: uma página inicial para os usuários criarem ou participarem de uma sessão, uma página de videochamada para os participantes da videochamada e uma página que exibe a transmissão ao vivo.
Precisaremos criar uma página adicional para a transmissão. Vamos adicionar um broadcast.html arquivo à views pasta clicando no botão botão “Novo Arquivo” na barra lateral esquerda. Nomeie o arquivo views/broadcast.html e cole a seguinte marcação na página.

<!DOCTYPE html>
<html lang="en">
<head>
<title>Broadcast Video Chat</title>
<meta
name="description"
content="Broadcast your video chat to a large audience with Vonage Video API in Node.js"
/>
<link
id="favicon"
rel="icon"
href="https://tokbox.com/developer/favicon.ico"
type="image/x-icon"
/>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<header>
<h1>Video broadcast</h1>
</header>
<main>
<video id="video" class="broadcast-video"></video>
</main>
<footer>
<p>
<small
>Built on <a href="https://glitch.com">Glitch</a> with the
<a href="https://tokbox.com/developer/">Vonage Video API</a>.</small
>
</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script src="/broadcast.js"></script>
</body>
</html>
Não há muita coisa acontecendo aqui; a parte principal é o video elemento que abrigará o stream HLS quando a transmissão começar.
Também adicionaremos algumas marcações relacionadas à transmissão à index.html página, como os botões para iniciar e interromper a transmissão, bem como para gerar um link HLS compartilhável.
<main>
<div id="subscriber" class="subscriber"></div>
<div id="publisher" class="publisher"></div>
<!-- Add the broadcast controls -->
<div class="broadcast">
<button id="startBroadcast">Start Broadcast</button>
<button id="stopBroadcast" class="hidden">Stop Broadcast</button>
</div>
</main> Definir o estilo dos controles de transmissão
A seguir, vamos aplicar alguns estilos à marcação recém-adicionada. Nada muito complicado aqui, apenas um pouco de posicionamento e também os estados dos botões, sobre os quais falaremos quando começarmos a trabalhar no início e na interrupção da transmissão.
/* To position the controls in the bottom-right corner */
.broadcast {
position: absolute;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
}
.broadcast a,
.broadcast button {
margin-bottom: 1em;
}
/* This is to centre the broadcast video */
.broadcast-video {
margin: auto;
}Agora, ao iniciar a sessão, sua interface deve ficar mais ou menos assim:

Este não é o estilo definitivo, mas serve por enquanto, enquanto desenvolvemos a funcionalidade de transmissão do aplicativo.
Iniciar/Interromper uma transmissão
O segredo para transmitir com a Video API da Vonage é o startBroadcast() método e o stopBroadcast() método. Esses métodos serão chamados a partir do server.js arquivo. O startBroadcast() método recebe três parâmetros: o ID da sessão, opções para a transmissão e uma função de retorno de chamada. Obteremos o ID da sessão do lado do cliente por meio de uma POST solicitação. Vamos configurar a rota para isso.
// Required to read the body of a POST request
app.use(express.json());
// Declare an object to store the broadcast information returned by the SDK
let broadcastData = {};
app.post("/broadcast/start", (request, response) => {
const sessionId = request.body.sessionId;
const broadcastOptions = {
outputs: {
hls: {},
},
};
OT.startBroadcast(sessionId, broadcastOptions, (error, broadcast) => {
if (error) {
console.log(error);
response.status(503);
response.send({ error });
}
// Assign the response from the SDK to the broadcastData object
broadcastData = broadcast;
response.status(200);
response.send({ broadcast: broadcast });
});
});
Existem outras propriedades opcionais que você poderia incluir como opções de transmissão, como resolução, layout e assim por diante, mas, por enquanto, usaremos os valores padrão. Consulte a referência da API para obter detalhes.
Vamos também configurar a rota para interromper uma transmissão. O stopBroadcast() método requer o ID da transmissão, que também obteremos do lado do cliente.
app.post("/broadcast/stop", (request, response) => {
const broadcastId = request.body.broadcastId;
OT.stopBroadcast(broadcastId, (error, broadcast) => {
if (error) console.log(error);
response.status(200);
response.send({
status: broadcast.status
});
});
});
É preciso fazer alguns ajustes no client.js arquivo para acomodar essa nova funcionalidade. No client.js arquivo, defina session uma variável global.
+ let session;
function initializeSession(apiKey, sessionId, token) {
- const session = OT.initSession(apiKey, sessionId);
+ session = OT.initSession(apiKey, sessionId);
// more code below
}Também precisamos alterar o modo de mídia da sessão para um roteado , em vez do modo de retransmissão padrão.
Antes:
app.post("/session/:room", (request, response) => {
const roomName = request.params.room;
// Check if the session already exists
if (sessions[roomName]) {
// Generate the token
generateToken(roomName, response);
} else {
// If the session does not exist, create one
- OT.createSession((error, session) => {
+ // Set the media mode to routed here
+ OT.createSession({ mediaMode: "routed" }, (error, session) => {
if (error) {
console.log("Error creating session:", error);
} else {
// Store the session in the sessions object
sessions[roomName] = session.sessionId;
// Generate the token
generateToken(roomName, response);
}
});
}
});
Também precisamos declarar uma broadcast variável para armazenar informações sobre a transmissão, que também será usada para interrompê-la. Por enquanto, vamos também registrar as respostas no console para que possamos verificar se tudo está funcionando conforme o esperado.
let broadcast;
const startBroadcastBtn = document.getElementById("startBroadcast");
startBroadcastBtn.addEventListener("click", startBroadCast, false);
function startBroadCast() {
fetch("/broadcast/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sessionId: session.sessionId })
})
.then(res => {
return res.json();
})
.then(res => {
broadcast = res.broadcast;
console.log(res);
})
.catch(handleCallback);
}
const stopBroadcastBtn = document.getElementById("stopBroadcast");
stopBroadcastBtn.addEventListener("click", stopBroadCast, false);
function stopBroadCast() {
fetch("/broadcast/stop", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ broadcastId: broadcast.id })
})
.then(res => {
return res.json();
})
.then(res => {
console.log(res);
})
.catch(handleCallback);
}
Se você abrir o console ao iniciar e encerrar a transmissão, deverá ver o seguinte:

Em teoria, poderíamos parar por aqui, pois agora temos acesso a um link HLS para transmitir o bate-papo por vídeo para um reprodutor compatível com o formato. E se você já tiver algo que suporte transmissões HLS, fique à vontade para fazer do seu jeito. O restante deste tutorial aborda uma implementação básica para que você possa ver como fica a transmissão ao vivo.
Lidar com os estados dos botões
Mas, antes disso, vamos definir alguns estilos adicionais para os estados dos botões. Se você percebeu, há um certo atraso entre o momento em que você pressiona o Iniciar Transmissão e a resposta que é registrada no console. Para melhorar a experiência do usuário, queremos fornecer alguma indicação de que a solicitação foi enviada ao servidor.
O fluxo funcionaria mais ou menos assim:

Em vez de exibir os botões “Iniciar” e “Parar”, mostramos apenas um botão relevante por vez. Além disso, depois que um botão é clicado, não queremos que os usuários cliquem nele várias vezes enquanto o processamento estiver em andamento. Vamos adicionar algumas classes CSS para lidar com os estados “oculto” e “desativado”.
/* These are for the button states */
.hidden {
display: none;
}
.disabled {
cursor: not-allowed;
opacity: 0.5;
pointer-events: none;
}Como os botões “start” e “stop” têm o mesmo fluxo, as classes CSS necessárias para as mudanças de estado seriam as mesmas para ambos os botões, apenas aplicadas de forma alternada. Podemos abstrair essas mudanças em uma função que receba a string “start” ou “stop” e selecione o botão apropriado.
// Button state while awaiting response from server
function pendingBtnState(statusString) {
const btn = document.getElementById(statusString + "Broadcast");
btn.classList.add("disabled");
btn.setAttribute("data-original", btn.textContent);
btn.textContent = "Processing…";
}
// Switch button state once server responds
function activeBtnState(statusString) {
const activeBtn =
statusString === "start"
? document.getElementById("startBroadcast")
: document.getElementById("stopBroadcast");
const inactiveBtn =
statusString === "stop"
? document.getElementById("startBroadcast")
: document.getElementById("stopBroadcast");
inactiveBtn.classList.remove("disabled");
inactiveBtn.textContent = inactiveBtn.getAttribute("data-original");
inactiveBtn.removeAttribute("data-original");
inactiveBtn.classList.add("hidden");
activeBtn.classList.remove("hidden");
}Vamos incorporar essas funções às nossas solicitações de busca para iniciar e interromper a transmissão.
function startBroadCast() {
// To put the Start button into the pending state
pendingBtnState("start");
fetch("/broadcast/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sessionId: session.sessionId })
})
.then(res => {
return res.json();
})
.then(res => {
broadcast = res.broadcast;
// To hide the Start button and show the Stop button
activeBtnState("stop");
})
.catch(handleCallback);
}
function stopBroadCast() {
// To put the Stop button into the pending state
pendingBtnState("stop");
fetch("/broadcast/stop", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ broadcastId: broadcast.id })
})
.then(res => {
return res.json();
})
.then(res => {
// To hide the Stop button and show the Start button
activeBtnState("start");
})
.catch(handleCallback);
}
Criar link HLS compartilhável
O objeto Broadcast retornado pelo SDK contém uma URL de transmissão HLS que pode ser utilizada por qualquer reprodutor de vídeo compatível com HLS. Vamos aproveitar isso para criar um link para uma página de transmissão. Já criamos um broadcast.html arquivo no início; vamos, então, direcionar nossa transmissão para essa página. Vamos configurar uma rota no server.js arquivo para isso.
app.get("/broadcast/:room", (request, response) => {
response.sendFile(__dirname + "/views/broadcast.html");
});
Vamos adicionar outra rota que verifique se a sessão a ser transmitida existe. Se existir, a resposta de sucesso incluirá a URL da transmissão e seu status.
app.get("/broadcast/hls/:room", (request, response) => {
const roomName = request.params.room;
if (sessions[roomName]) {
response.status(200);
response.send({
hls: broadcastData.broadcastUrls.hls,
status: broadcastData.status
});
} else {
response.status(204);
}
});
Em nossa index.html página, adicione o seguinte aos controles de transmissão div:
<div class="broadcast">
<!-- Add link to the Broadcast page and a means to copy to clipboard -->
<a class="hidden" id="hlsLink" target="_blank" rel="noopener noreferrer"
>Open Broadcast page</a
>
<p class="invisible" id="hlsCopyTarget"></p>
<button class="hidden" id="copyLink">Copy HLS link</button>
<button id="startBroadcast">Start Broadcast</button>
<button id="stopBroadcast" class="hidden">Stop Broadcast</button>
</div>
E um pouco mais de CSS para styles.css:
.invisible {
position: absolute;
opacity: 0;
z-index: -1;
}O resultado dessas alterações será um link que abre a página de transmissão em uma aba ou janela separada. É exibido um botão que, quando clicado, copia o link para a página de transmissão para que as pessoas possam compartilhá-lo. Precisaremos obter a URL HLS da resposta da transmissão, bem como o nome da sala (da URL), para criar o link para a página de transmissão.
const url = new URL(window.location.href);
const roomName = url.pathname.split("/")[2];
// To show/hide the HLS links when the broadcast starts/stops
function hlsLinkState(statusString) {
if (statusString === "start") {
document.getElementById("hlsLink").classList.remove("hidden");
document.getElementById("copyLink").classList.remove("hidden");
} else {
document.getElementById("hlsLink").classList.add("hidden");
document.getElementById("copyLink").classList.add("hidden");
}
}
// Create the link to the broadcast page
function composeHlsLink(link) {
hlsLinkState("start");
const hlsLinkUrl =
"https://" + location.host + "/broadcast/" + roomName + "?hls=" + link;
const hlsLink = document.getElementById("hlsLink");
const hlsCopyTarget = document.getElementById("hlsCopyTarget");
hlsLink.href = hlsLinkUrl;
hlsCopyTarget.innerHTML = hlsLinkUrl;
}Vamos adicionar essas novas funções também às solicitações de busca para iniciar/parar a transmissão:
function startBroadCast() {
pendingBtnState("start");
fetch("/broadcast/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sessionId: session.sessionId })
})
.then(res => {
return res.json();
})
.then(res => {
broadcast = res.broadcast;
activeBtnState("stop");
// Compose the link to the broadcast page
composeHlsLink(res.broadcast.broadcastUrls.hls);
})
.catch(handleCallback);
}
function stopBroadCast() {
pendingBtnState("stop");
fetch("/broadcast/stop", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ broadcastId: broadcast.id })
})
.then(res => {
return res.json();
})
.then(res => {
activeBtnState("start");
// Hide the links when the broadcast has stopped
hlsLinkState("stop");
})
.catch(handleCallback);
}
Existem vários reprodutores de vídeo capazes de reproduzir transmissões HLS e que oferecem diferentes níveis de personalização da interface do reprodutor, mas, para manter as coisas simples, este tutorial carrega o hls.js para reproduzir o stream. Crie um broadcast.js arquivo na public pasta.
const url = new URL(window.location.href);
const roomName = url.pathname.split("/")[2];
const hlsLink = url.searchParams.get("hls");
fetch("/broadcast/hls/" + roomName)
.then(res => {
return res.json();
})
.then(res => {
playStream(hlsLink);
})
.catch(error => console.error(error));
// Refer to hls.js documentation for more options
function playStream(hlsLink) {
const video = document.getElementById("video");
const videoSrc = hlsLink;
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = videoSrc;
video.addEventListener("loadedmetadata", function() {
video.play();
});
}
}
A parte final deste tutorial é uma implementação nativa da função “copiar para a área de transferência”. Fique à vontade para usar uma biblioteca como clipboard.js para uma API mais simples.
const copyLinkBtn = document.getElementById("copyLink");
copyLinkBtn.addEventListener("click", copyHlsLink, false);
function copyHlsLink() {
const hlsCopyTarget = document.getElementById("hlsCopyTarget");
const range = document.createRange();
range.selectNode(hlsCopyTarget);
window.getSelection().addRange(range);
try {
const successful = document.execCommand("copy");
const msg = successful ? "successful" : "unsuccessful";
console.log("Copy command was " + msg);
} catch (err) {
console.log("Oops, unable to copy");
}
window.getSelection().removeAllRanges();
}Depois de tudo isso, você deve finalmente obter algo assim para a página de videochamada e para a página de transmissão, respectivamente:


E agora?
O código final sobre Glitch e GitHub contém tudo o que abordamos neste longo post, mas reorganizado, de modo que o código ficou mais limpo e mais fácil de manter. Fique à vontade para remixar ou clonar o código e brincar com ele por conta própria.
Existem funcionalidades adicionais que podemos desenvolver com a Video API da Vonage, que serão abordadas em tutoriais futuros; mas, enquanto isso, você pode saber mais em nosso site de documentação completa. Se você tiver algum problema ou dúvida, entre em contato conosco na nossa Slack da Comunidade. Obrigado pela leitura!