
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.
Transmita uma videochamada com a Video API da Vonage
Tempo de leitura: 6 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 um público que não está participando do bate-papo.
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, seu aplicativo de chat por vídeo também deverá oferecer uma opção para simplesmente assistir à transmissão do chat por vídeo.

O código final desta aplicação 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
Criação de tokens e funções
Todo usuário que se conecta a uma sessão precisa ser autenticado por meio de um token. A cada token é atribuída uma função, que determina o que o cliente pode fazer enquanto estiver conectado. Existem três funções disponíveis: Assinante, Editor e Moderador. Usaremos apenas os dois primeiros neste tutorial.
Um emissor pode se conectar a sessões, transmitir fluxos de áudio e Video para a sessão e se inscrever nas sessões de outros clientes. Um assinante pode se conectar a sessões e se inscrever nas sessões de outros clientes, mas não pode publicar na sessão.

Para este tutorial, forneceremos aos participantes tokens de editor, enquanto os espectadores receberão tokens de assinante.
Mais informações sobre tokens podem ser encontradas na documentação.
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, faremos algumas adições ao projeto para disponibilizar uma interface para os espectadores.
Adicione a marcação necessária
Nosso aplicativo será composto por três páginas: uma página inicial para que os usuários criem ou participem de uma sessão, bem como para selecionar se desejam ser espectadores ou participantes, e as duas páginas de videochamada para cada função, respectivamente.
Precisaremos criar uma página adicional para o visualizador. Vamos adicionar um viewer.html arquivo à views pasta clicando no botão botão “Novo Arquivo” na barra lateral esquerda. Nomeie o arquivo views/viewer.html e cole a seguinte marcação na página. Essa página é quase exatamente igual ao index.html arquivo, exceto que não possui um div editor.

<!DOCTYPE html>
<html lang="en">
<head>
<title>Stream your video chat</title>
<meta
name="description"
content="Stream a basic audio-video chat 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>Viewer</h1>
</header>
<main>
<div id="subscriber" class="subscriber"></div>
</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://static.opentok.com/v2/js/opentok.min.js"></script>
<script src="/viewer.js"></script>
</body>
</html>
O viewer.html e o index.html arquivo utilizará arquivos de script diferentes, pois sua implementação é ligeiramente diferente devido às respectivas funções dos tokens, conforme explicado na seção acima.
A seguir, faremos algumas modificações no formulário da landing.html página para incluir uma opção que permita aos usuários selecionar suas funções por meio de botões de opção. Se eles selecionarem “Espectador”, serão direcionados para a página que exibe a transmissão do bate-papo por Video. Se selecionarem Participante, aparecerá outro campo de texto para o nome de usuário, que será usado para identificar sua transmissão.
<form id="registration" class="registration">
<label>
<span>Room</span>
<input
type="text"
name="room-name"
placeholder="Enter room name"
required
/>
</label>
<!-- Add the user type radio buttons -->
<p>Select your role:</p>
<fieldset id="userRoles">
<label>
<input type="radio" name="user-type" value="viewer" checked />
<span>Viewer</span>
</label>
<label>
<input type="radio" name="user-type" value="participant" />
<span>Participant</span>
</label>
</fieldset>
<!-- Add the user name input field and label -->
<label id="userName" class="hidden">
<span>User name</span>
<input type="text" name="user-name" placeholder="Enter your name" />
</label>
<button>Enter</button>
</form>
Definir o estilo do formulário da página de destino
Vamos complementar os estilos existentes para acomodar o novo fieldset e os botões de opção.
fieldset {
border: 0;
display: flex;
justify-content: space-between;
margin-bottom: 1em;
}
fieldset label {
padding: 0.25em 0em;
cursor: pointer;
}
.hidden {
display: none;
}
Reestruturar o JavaScript do lado do cliente
Vamos trabalhar na landing.html página primeiro. Para mostrar ou ocultar o campo do nome de usuário de forma condicional, podemos adicionar um ouvinte de evento que verifique o valor do botão de opção selecionado e altere os estilos de acordo com isso.
const userRoles = document.getElementById("userRoles");
const userName = document.getElementById("userName");
const userNameField = document.querySelector('[name="user-name"]');
userRoles.addEventListener(
"click",
event => {
if (event.target.value === "participant") {
userName.classList.remove("hidden");
userNameField.required = true;
} else {
userName.classList.add("hidden");
userNameField.required = false;
}
},
false
);
Também precisamos modificar a lógica para direcionar nossos usuários às páginas corretas, dependendo de qual visualizador eles escolheram visualizador ou participante. Os espectadores serão direcionados para /session/viewer/ROOM_NAME , enquanto os participantes serão direcionados para /session/participant/ROOM_NAME?username=USER_NAME. Estamos utilizando a string de consulta na URL para passar o nome de usuário ao servidor.
const form = document.getElementById("registration");
form.addEventListener("submit", event => {
event.preventDefault();
// Check the selected option and redirect accordingly
const isViewer = form.elements["user-type"].value === "viewer";
if (isViewer) {
location.href = `/session/viewer/${form.elements["room-name"].value}`;
} else {
location.href = `/session/participant/${form.elements["room-name"].value}?username=${form.elements["user-name"].value}`;
}
});
A seguir, vamos criar o viewer.js arquivo para a viewer.html página. Da mesma forma que fizemos para o viewer.html, clique em “Novo Arquivo” novamente, mas, desta vez, adicione os arquivos de JavaScript à public pasta.
A pasta do seu projeto deve estar assim agora:

O viewer.js arquivo é um pouco mais curto que o client.js arquivo, pois não inclui a criação de um editor. Estamos fazendo uma POST solicitação para /session/viewer/ROOM_NAME e recebendo os dados de resposta necessários para nos conectarmos a uma sessão.
fetch(location.pathname, { method: "POST" })
.then(res => {
return res.json();
})
.then(res => {
const apiKey = res.apiKey;
const sessionId = res.sessionId;
const token = res.token;
initializeSession(apiKey, sessionId, token);
})
.catch(handleCallback);
function initializeSession(apiKey, sessionId, token) {
// Create a session object with the sessionId
const session = OT.initSession(apiKey, sessionId);
// Connect to the session
session.connect(token, error => handleCallback(error));
// Subscribe to a newly created stream
session.on("streamCreated", event => {
session.subscribe(
event.stream,
"subscriber",
{
insertMode: "append",
width: "100%",
height: "100%",
name: event.stream.name
},
handleCallback
);
});
}
// Callback handler
function handleCallback(error) {
if (error) {
console.log("error: " + error.message);
} else {
console.log("callback success");
}
}
Precisamos fazer alguns pequenos ajustes no client.js arquivo também, pois queremos nomear o stream de cada participante com o nome de usuário que ele digitou na página inicial.
fetch(location.pathname, { method: "POST" })
.then(res => {
return res.json();
})
.then(res => {
const apiKey = res.apiKey;
const sessionId = res.sessionId;
const token = res.token;
// Declare the stream name and pass it to the initializeSession() function
const streamName = res.streamName;
initializeSession(apiKey, sessionId, token, streamName);
})
.catch(handleCallback);
A initializeSession() função agora aceitará mais um parâmetro para streamName e será usada no initPublisher() método e no subscribe() método. Ambos os métodos aceitam um argumento opcional de propriedades, o que nos permite passar opções de personalização para os fluxos.
// Create a publisher
const publisher = OT.initPublisher(
"publisher",
{
insertMode: "append",
width: "100%",
height: "100%",
name: streamName
},
handleCallback
);
// Subscribe to a newly created stream
session.on("streamCreated", event => {
session.subscribe(
event.stream,
"subscriber",
{
insertMode: "append",
width: "100%",
height: "100%",
name: event.stream.name
},
handleCallback
);
});
Seus arquivos finais client.js ficarão assim:
fetch(location.pathname, { method: "POST" })
.then(res => {
return res.json();
})
.then(res => {
const apiKey = res.apiKey;
const sessionId = res.sessionId;
const token = res.token;
const streamName = res.streamName;
initializeSession(apiKey, sessionId, token, streamName);
})
.catch(handleCallback);
function initializeSession(apiKey, sessionId, token, streamName) {
// Create a session object with the sessionId
const session = OT.initSession(apiKey, sessionId);
// Create a publisher
const publisher = OT.initPublisher(
"publisher",
{
insertMode: "append",
width: "100%",
height: "100%",
name: streamName
},
handleCallback
);
// Connect to the session
session.connect(token, error => {
// If the connection is successful, initialize the publisher and publish to the session
if (error) {
handleCallback(error);
} else {
session.publish(publisher, handleCallback);
}
});
// Subscribe to a newly created stream
session.on("streamCreated", event => {
session.subscribe(
event.stream,
"subscriber",
{
insertMode: "append",
width: "100%",
height: "100%",
name: event.stream.name
},
handleCallback
);
});
}
// Callback handler
function handleCallback(error) {
if (error) {
console.log("error: " + error.message);
} else {
console.log("callback success");
}
}
Tratamento de rotas no lado do servidor
A última etapa antes de tudo se encaixar é o server.js arquivo, onde as rotas são definidas. Precisaremos configurar as rotas para atender o página do (viewer.html) e também a página (index.html), respectivamente.
app.get("/session/participant/:room", (request, response) => {
response.sendFile(__dirname + "/views/index.html");
});
app.get("/session/viewer/:room", (request, response) => {
response.sendFile(__dirname + "/views/viewer.html");
});
Em vez da função remixada generateToken() , usaremos duas funções diferentes para as duas funções.
function generatePublisherToken(roomName, streamName, response) {
// Configure token options
const tokenOptions = {
role: "publisher",
data: `roomname=${roomName}?streamname=${streamName}`
};
// Generate token with the OpenTok 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,
streamName: streamName
});
}
function generateSubscriberToken(roomName, response) {
// Configure token options
const tokenOptions = {
role: "subscriber",
data: `roomname=${roomName}`
};
// Generate token with the OpenTok 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
});
}Para os espectadores, assim que a página do Viewer for carregada, o nome da sala será enviado ao servidor por meio de uma POST solicitação. Isso será processado pela seguinte rota:
app.post("/session/viewer/:room", (request, response) => {
const roomName = request.params.room;
// Check if the session already exists
if (sessions[roomName]) {
// Generate the token
generateSubscriberToken(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
generateSubscriberToken(roomName, response);
}
});
}
});
Da mesma forma, para os participantes, assim que a página do Participante for carregada, o nome da sala e o nome de usuário serão enviados ao servidor por meio de uma POST solicitação, e a rota correspondente é tratada da seguinte forma:
// Middleware to read the body of the request
app.use(express.json());
app.post("/session/participant/:room", (request, response) => {
const roomName = request.params.room;
const streamName = request.body.username;
// Check if the session already exists
if (sessions[roomName]) {
// Generate the token
generatePublisherToken(roomName, streamName, 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
generatePublisherToken(roomName, streamName, response);
}
});
}
});
E, com isso, os espectadores poderão assistir às transmissões dos participantes em uma única página, enquanto os participantes estiverem conversando por Video entre si.


Não deixe de conferir o código final em Glitch ou no GitHub e fique à vontade para remixar ou clonar o código e brincar com ele por conta própria.
E agora?
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!