Criar uma sala de espera para telemedicina com o Twilio Video

July 31, 2020
Escrito por
Revisado por
Diane Phan
Twilion
Liz Moy
Twilion

Criar uma sala de espera para telemedicina com o Twilio Video

A telemedicina está mudando rapidamente o setor da saúde. Tirando a preocupação com a COVID-19, poder ver o rosto de um médico sem ter de se deslocar até ele é um divisor de águas.

Twilio Video permite que os desenvolvedores criem soluções de telemedicina em conformidade com a HIPAA que estejam totalmente sob seu controle e sob medida para as necessidades de seus pacientes. Hoje, mostrarei como você pode criar um app de telemedicina com o Twilio Video para exibir uma "sala de espera virtual" onde o paciente pode ficar até que o médico chegue. Por uma questão de simplificação, vamos construir o front-end no JavaScript básico, sem uma estrutura.

Em nível geral, seguiremos estas etapas:

  • Configurar um back-end para controle de acesso ao aplicativo
  • Configurar o scaffolding do front-end básico
  • Criar a experiência do prestador
  • Criar a experiência do paciente

Captura de tela de um aplicativo de telemedicina. O texto diz 'Bem-vindo ao Owl Hospital Telemedicine' e há 2 pessoas sorridentes e reais conversando por vídeo.

Para os que são impacientes e que desejam ir diretamente para o código, todo o projeto está no GitHub.

Pré-requisitos

Configurar o ambiente

Execute npm init --yes e inicie um novo projeto Node.js na linha de comando.

Crie um arquivo .env na raiz do projeto, onde armazenaremos suas credenciais de conta da Twilio.

Vá até o console da Twilio e copie o account SID (SID da conta) no arquivo .env.

Você também precisará de algumas credenciais específicas de vídeo. Essas credenciais permitem que você gere um access token (token de acesso), que informa aos servidores da Twilio que seus usuários finais têm o direito de se conectar a esse aplicativo de vídeo vinculado à sua conta.

Na página Programmable Video Tools (Ferramentas do Programmable Video), gere uma API key (chave de API) e um secret (segredo). Copie-os também no arquivo .env.

Você terá um arquivo semelhante a este:

TWILIO_API_SECRET="xxxxxx"
TWILIO_API_KEY="SKxxxxxx"
TWILIO_ACCOUNT_SID="ACxxxxx"

Se você está usando o controle de versão para este projeto, adicione o arquivo ".env" ao arquivo ".gitignore" para não enviar suas credenciais acidentalmente para o GitHub.

Não é possível armazenar credenciais no front-end, pois isso as expõe a agentes mal-intencionados. Portanto, nosso app precisará de um back-end para gerar o access token (token de acesso) ao vídeo.

Criar o back-end

Usaremos o Express para alimentar nosso servidor. Na linha de comando, execute npm install express para adicionar o Express como uma dependência do projeto. Quando estiver nele, execute npm install dotenv para instalar a biblioteca que usaremos para ler nossas credenciais do arquivo .env. Para obter mais informações sobre como trabalhar com variáveis de ambiente no Node.js, consulte este post do blog.  A última dependência de que precisaremos é o SDK da Twilio, portanto execute o npm install twilio também.

Na raiz do projeto, crie um arquivo chamado server.js onde ficará o código do servidor. Copie nele o código a seguir:

require("dotenv").config();
const http = require("http");
const express = require("express");
const path = require("path");
const app = express();

const AccessToken = require("twilio").jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;

const ROOM_NAME = "telemedicineAppointment";

// Max. period that a Participant is allowed to be in a Room (currently 14400 seconds or 4 hours)
const MAX_ALLOWED_SESSION_DURATION = 14400;

app.get("/token", function (request, response) {
 const identity = request.query.identity;

 // Create an access token which we will sign and return to the client,
 // containing the grant we just created.

 const token = new AccessToken(
   process.env.TWILIO_ACCOUNT_SID,
   process.env.TWILIO_API_KEY,
   process.env.TWILIO_API_SECRET,
   { ttl: MAX_ALLOWED_SESSION_DURATION }
 );

 // Assign the generated identity to the token.
 token.identity = identity;

 // Grant the access token Twilio Video capabilities.
 const grant = new VideoGrant({ room: ROOM_NAME });
 token.addGrant(grant);

 // Serialize the token to a JWT string and include it in a JSON response.
 response.send({
   identity: identity,
   token: token.toJwt(),
 });
});

http.createServer(app).listen(1337, () => {
 console.log("express server listening on port 1337");
});

Aqui, adicionamos o código de texto clichê para executar um servidor Express. Adicionamos uma rota que leva uma string identity e gera um access token (token de acesso) ao Twilio Video.

Como a rota do token faz uma solicitação GET, podemos testá-la diretamente do navegador.

No diretório de projetos, execute o seguinte comando para iniciar o servidor:

node server.js

Carregue http://localhost:1337/token?identity=tilde no navegador. Você verá uma resposta semelhante à seguinte:

{
        "identity": "tilde",
        "token": "<YOUR_TOKEN_HERE>"
}

Bom trabalho! 

Criar o front-end

Vamos adicionar um front-end para que o paciente e o prestador possam realizar videoconferências e se verem. Qual é o produto mínimo viável para a telemedicina com uma sala de espera virtual?

  • O paciente e o prestador precisam poder se conectar e desconectar de áudio/vídeo
  • O paciente precisa poder interagir com uma experiência de "sala de espera" quando ele já está na sala, mas o prestador ainda não
  • O app não deve mostrar a sala de espera quando o prestador e o paciente já estão na sala
  • A opção de mostrar ou não a sala de espera deve permanecer no estado correto da página, mesmo que o prestador ou o paciente desconecte-se e retorne

Crie uma pasta public na raiz do seu projeto, que é onde ficará o front-end.

Adicione os seguintes arquivos vazios na pasta public:

  • patient.html: a página pela qual o paciente entrará na chamada
  • provider.html: a página pela qual o prestador entrará na chamada
  • index.js: o JavaScript que é compartilhado entre as duas páginas ficará aqui
  • index.css: para dar um toque de estilo

Adicionar a página do prestador

Vamos trabalhar na página do prestador primeiro, pois ela é um pouco mais simples.

Copie o código a seguir em provider.html:

<!DOCTYPE html>
<html>
 <head>
   <title>Owl Hospital Telemedicine App</title>
   <link rel="stylesheet" href="index.css" />
 </head>

 <body>
   <h1>🦉 Welcome to Owl Hospital Telemedicine 🦉</h1>
   <h3>Thanks for caring for our patients <3</h3>
 </body>
 <button id="join-button">Join Room</button>
 <button id="leave-button" class="hidden">Leave Room</button>
 <div id="local-media-container"></div>
</html>

Para atender a esta página, precisamos de um pouco de lógica no lado do servidor.

Em server.js, adicione o código a seguir:

const providerPath = path.join(__dirname, "./public/provider.html");
app.use("/provider", express.static(providerPath));

const patientPath = path.join(__dirname, "./public/patient.html");
app.use("/patient", express.static(patientPath));

// serving up some fierce CSS lewks
app.use(express.static(__dirname + "/public"));

Esse código expõe as URLs das páginas do paciente e do prestador e também permite que nosso app acesse os arquivos na pasta public como ativos estáticos para que possamos aplicar o arquivo CSS.

Vá até http://localhost:1337/provider no navegador e você verá algo parecido com isto:

Captura de tela do front-end de um aplicativo de telemedicina. O texto grande diz &#x27;Bem-vindo ao Owl Hospital Telemedicine&#x27;. O texto pequeno diz &#x27;Obrigado por cuidar de nossos pacientes <3&#x27;. Há 2 botões, &#x27;Entrar na sala&#x27; e &#x27;Sair da sala&#x27;. A página não tem estilização.

Observe que estamos codificando os nomes do paciente, do prestador e da sala aqui apenas para fins de simplificação. Em um app de telemedicina com produção pronta que se expandiria para lidar com vários agendamentos simultâneos, essas páginas estariam protegidas por um fluxo de autenticação e você extrairia os nomes dos usuários de seu banco de dados em vez de codificá-los. Você também precisaria de algum tipo de lógica do lado do servidor para gerar nomes de sala exclusivos para cada agendamento diferente.

De qualquer forma. Vamos deixar isso um pouco menos feio?

Abra o arquivo public/index.css e adicione o código a seguir:

* {
 background: #252d26;
 color: #a1ceb6;
 font-family: "Gill Sans", sans-serif;
}

button {
 background: #6a7272;
 font-size: 20px;
 border-radius: 5px;
}

button:hover {
 background: #694d3c;
}

.hidden {
 display: none;
}

Se você recarregar a página, ela terá a seguinte aparência:

Captura de tela de um aplicativo do Twilio Video. O texto grande diz &#x27;Bem-vindo ao Owl Hospital Telemedicine&#x27;. O texto pequeno diz &#x27;Obrigado por cuidar de nossos pacientes&#x27;. Há um botão que diz &#x27;Entrar na sala&#x27;. Além disso, ele tem uma paleta de cores verde/marrom.

Não é incrível como uma pequena mudança nas fontes e cores faz uma enorme diferença na aparência do app? As cores inspiradas na floresta combinaram perfeitamente com um hospital cujo tema é de coruja.

Esse CSS também nos permite exibir e ocultar elementos aplicando a classe hidden ao arquivo HTML.

Nossa página de prestador ainda não faz nada, então vamos corrigir isso.

Entrar em uma chamada de vídeo

Abra o arquivo public/index.js e adicione o código a seguir (explicarei o que está acontecendo em seguida):

let room;

const joinRoom = async (event, identity) => {
 const response = await fetch(`/token?identity=${identity}`);
 const jsonResponse = await response.json();
 const token = jsonResponse.token;

 const Video = Twilio.Video;

 const localTracks = await Video.createLocalTracks({
   audio: true,
   video: { width: 640 },
 });
 try {
   room = await Video.connect(token, {
     name: "telemedicineAppointment",
     tracks: localTracks,
   });
 } catch (error) {
   console.log(error);
 }

 // display your own video element in DOM
 // localParticipants are handled differently
 // you don't need to fetch your own video/audio streams from the server
 const localMediaContainer = document.getElementById("local-media-container");
 localTracks.forEach((localTrack) => {
   localMediaContainer.appendChild(localTrack.attach());
 });

 // display video/audio of other participants who have already joined
 room.participants.forEach(onParticipantConnected);

 // subscribe to new participant joining event so we can display their video/audio
 room.on("participantConnected", onParticipantConnected);

 room.on("participantDisconnected", onParticipantDisconnected);

 toggleButtons();

 event.preventDefault();
};

// when a participant disconnects, remove their video and audio from the DOM.
const onParticipantDisconnected = (participant) => {
 const participantDiv = document.getElementById(participant.sid);
 participantDiv.parentNode.removeChild(participantDiv);
};

const onParticipantConnected = (participant) => {
 const participantDiv = document.createElement("div");
 participantDiv.id = participant.sid;

 // when a remote participant joins, add their audio and video to the DOM
 const trackSubscribed = (track) => {
   participantDiv.appendChild(track.attach());
 };
 participant.on("trackSubscribed", trackSubscribed);

 participant.tracks.forEach((publication) => {
   if (publication.isSubscribed) {
     trackSubscribed(publication.track);
   }
 });

 document.body.appendChild(participantDiv);

 const trackUnsubscribed = (track) => {
   track.detach().forEach((element) => element.remove());
 };

 participant.on("trackUnsubscribed", trackUnsubscribed);
};

const onLeaveButtonClick = (event) => {
 room.localParticipant.tracks.forEach((publication) => {
   const track = publication.track;
   // stop releases the media element from the browser control
   // which is useful to turn off the camera light, etc.
   track.stop();
   const elements = track.detach();
   elements.forEach((element) => element.remove());
 });
 room.disconnect();

 toggleButtons();
};

const toggleButtons = () => {
 document.getElementById("leave-button").classList.toggle("hidden");
 document.getElementById("join-button").classList.toggle("hidden");
};

O que está acontecendo aqui?

Existem alguns conceitos que você precisa saber para entender as APIs do Twilio Video:

  • Uma room (sala) é um espaço virtual no qual os usuários finais se comunicam.
  • participant (participante) é um usuário que entrou ou entrará em uma sala.
  • As Tracks (Faixas) são informações compartilhadas entre os participantes. Existem diferentes tipos de faixas, como áudio, vídeo ou dados.
  • As tracks (faixas) podem ser local (locais) ou remote (remotas), pois esses tipos de dados precisam ser tratados de maneiras diferentes. Você não iria querer que o vídeo de um usuário fizesse uma viagem de ida e volta no servidor ao exibi-lo no navegador dele.
  • As informações sobre as tracks (faixas) são compartilhadas entre os participantes usando um subscription model (modelo de assinatura).

Primeiro, buscamos o access token (token de acesso) em nosso servidor. Em seguida, nos conectamos a uma sala ao chamar o método connect.

Usamos as APIs do navegador para capturar áudio e vídeo locais e, em seguida, passar essas informações para a sala que estamos criando.

Após o usuário se conectar a uma sala, precisamos attach (anexar) suas tracks (faixas) locais de áudio e vídeo, o que significa transformá-las em elementos HTML de midia com o SDK do Twilio Video. Feito isso, podemos anexá-las ao DOM.

Ainda não terminamos. Se outros participantes já estiverem na sala, precisaremos assinar e anexar suas faixas de vídeo e áudio. Além disso, devemos configurar ouvintes de eventos para que façam o mesmo para os futuros participantes que também participarão da chamada.

Por fim, precisaremos limpar e remover elementos e assinaturas quando um participante sair da sala. É apenas uma questão de boas maneiras e o garbage collector vai agradecer por isso.

O método toggleButtons (alternar botões) é uma função auxiliar para exibir ou ocultar os botões Join Room (Entrar na sala) e Leave Room (Sair da sala), assim o usuário não se atrapalha ao escolher qual é o correto.

Em seguida, modificaremos o HTML do prestador para aproveitar esse código que acabamos de escrever.

Na parte inferior de public/provider.html, adicione as linhas a seguir:

 <script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
 <script src="./index.js"></script>
 <script>
   const joinButton = document.getElementById("join-button");
   joinButton.addEventListener("click", async (event) => {
     await joinRoom(event, "provider");
   });

   const leaveButton = document.getElementById("leave-button");
   leaveButton.addEventListener("click", onLeaveButtonClick);
 </script>
</html>

Aqui importamos o SDK do video do lado do cliente da Twilio e o arquivo JavaScript que acabamos de escrever. Em seguida, anexamos listeners aos botões para fazer a coisa certa quando o prestador entrar e sair.

Faça um teste: navegue até http://localhost:1337/provider em seu navegador novamente e clique no botão Join Room (Entrar na sala):

Captura de tela de um aplicativo Twilio Video que diz &#x27;Bem-vindo ao Owl Hospital Telemedicine&#x27;. O texto pequeno diz &#x27;Obrigado por cuidar de nossos pacientes&#x27;. Há um botão que diz &#x27;Sair da sala&#x27; e uma pessoa não binária no bate-papo com vídeo, com cabelo verde, fazendo uma careta.

Eu me pareço com um médico? Gostaria de ter um estetoscópio ou algo do tipo para ficar ainda mais real.

Estamos chegando lá! O código para a experiência do paciente virá em seguida.

Criar a experiência da sala de espera virtual

Abra o arquivo public/patient.html e adicione nele o código a seguir:

<!DOCTYPE html>
<html>
 <head>
   <title>Owl Hospital Telemedicine App</title>
   <link rel="stylesheet" href="index.css" />
 </head>
 <body>
   <h1>🦉 Welcome to Owl Hospital Telemedicine 🦉</h1>
 </body>
 <button id="join-button">Join Room</button>
 <button id="leave-button" class="hidden">Leave Room</button>
 <div id="waiting-room" class="hidden">
   <p>Thanks! Your provider will be with you shortly.</p>
   <p>In the meantime enjoy this soothing campfire.</p>
   <iframe
     width="640"
     height="315"
     src="https://www.youtube.com/embed/E77jmtut1Zc"
     frameborder="0"
     allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
     allowfullscreen
   ></iframe>
 </div>
 <div id="local-media-container"></div>
 <script src="./index.js"></script>
 <script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>

É muito semelhante ao HTML do prestador, exceto que temos um div oculto que contém a experiência da sala de espera.

Para manter o tema de coruja, escolhi um relaxante vídeo de fogueira para brincar com o paciente. Se você administra um hospital com estilo punk rock, pode exibir a música "Waiting Room" da banda Fugazi.

Em seguida, adicionaremos o JavaScript embutido para:

  • Permitir que o paciente entre e saia da sala de chamada de vídeo
  • Verificar se o prestador está na sala quando o paciente entrar, para que possamos exibir a sala de espera (ou não)
  • Assinar futuros participantes para que possamos exibir ou ocultar a sala de espera adequadamente
  • Interromper o vídeo da sala de espera caso ele esteja sendo reproduzido (isso pode causar distração!)

Adicione o seguinte código à parte inferior do arquivo public/patient.html:

<script>
   const providerIdentity = "provider";

   async function onJoinButtonClick(event) {
     await joinRoom(event, "patient");

     // is there a doctor in the house??
     // if not, show the waiting room
     if (!isProviderPresent(room.participants)) {
       showWaitingRoom();
     }

     // if the provider joins, hide the waiting room
     room.on("participantConnected", (participant) => {
       if (participant.identity === providerIdentity) {
         hideWaitingRoom();
       }
     });

     // hide the waiting room if the patient disconnects
     room.on("disconnected", () => {
       hideWaitingRoom();
     });
     event.preventDefault();
   }

   const isProviderPresent = (participantMap) => {
     for (const participant of participantMap.values()) {
       if (participant.identity === providerIdentity) {
         return true;
       }
     }
     return false;
   };

   const hideWaitingRoom = () => {
     const waitingRoom = document.getElementById("waiting-room");
     // check that the waiting room is visible, before hiding
     // just to avoid weird state bugs
     if (!waitingRoom.classList.contains("hidden")) {
       waitingRoom.classList.toggle("hidden");
       stopWaitingRoomVideo();
     }
   };

   const showWaitingRoom = () => {
     const waitingRoom = document.getElementById("waiting-room");
     // check that the waiting room is hidden, before showing
     // just to avoid weird state bugs
     if (waitingRoom.classList.contains("hidden")) {
       waitingRoom.classList.toggle("hidden");
     }
   };

   const stopWaitingRoomVideo = () => {
     const iframe = document.querySelector("iframe");
     const video = document.querySelector("video");
     if (iframe !== null) {
       const iframeSrc = iframe.src;
       iframe.src = iframeSrc;
     }
     if (video !== null) {
       video.pause();
     }
   };

   const button = document.getElementById("join-button");
   button.addEventListener("click", onJoinButtonClick);

   const leaveButton = document.getElementById("leave-button");
   leaveButton.addEventListener("click", onLeaveButtonClick);
 </script>
</html>

Faça um teste: navegue até http://localhost:1337/patient e clique no botão Join Room (Entrar na sala).

Captura de tela de um aplicativo de telemedicina do Twilio Video. Há uma pessoa não binária no bate-papo com vídeo, que tem um termômetro na boca e uma expressão preocupada. O texto na página diz &#x27;Obrigado! Seu provedor atenderá você em breve. Enquanto isso, desfrute desta fogueira relaxante.&#x27; Há um vídeo integrado do YouTube de uma fogueira.

Sou melhor em fazer um cosplay de um paciente do que um de médico.

Vá até http://localhost:1337/provider entre como prestador em outra guia ou navegador e voilà! A sala de espera desaparecerá e o vídeo será interrompido.

Captura de tela de um aplicativo de telemedicina. O texto diz &#x27;Bem-vindo ao Owl Hospital Telemedicine&#x27; e há 2 pessoas sorridentes e reais conversando por vídeo.

Conclusão: criar um app de telemedicina com uma sala de espera

Vamos revisar o que aprendemos hoje:

  • Como criar um token de acesso ao Twilio Video com Node.js e Express
  • Como mostrar os elementos de áudio e vídeo de um participante local em uma página
  • Como mostrar os elementos de áudio e vídeo de um participante remoto em uma página
  • Como mostrar e ocultar elementos na página quando os participantes entram e saem de uma sala de chamada de vídeo

Essa experiência de sala de espera certamente é bem básica. Há tantos complementos disponíveis para tornar a telemedicina realmente inovadora e incrível, como:

  • Se o prestador estiver atrasado, poderá enviar uma mensagem de texto ao paciente
  • Em vez do vídeo da sala de espera, pedir ao paciente para preencher um formulário sobre sua saúde
  • Lembretes de pré-agendamento enviados por SMS ou e-mail
  • A possibilidade de envio de um link para um terceiro para que ele possa participar do chat por vídeo facilmente, é um recurso excelente para tradutores, pais, cuidadores, etc.
  • Gravar e transcrever o áudio de visitas
  • Integração com um software de gráficos de terceiros para lembrar o paciente de executar tarefas de acompanhamento

Os casos de uso de telemedicina nunca foram tão urgentes. Mal posso esperar para ver o que você vai criar! Se você é um desenvolvedor que trabalha com apps da área da saúde, eu adoraria ouvir o que você tem a dizer para mim. Deixe seus comentários a seguir ou me encontre no Twitter.

Este artigo foi traduzido do original "Building a telemedicine waiting room with Twilio Video". Enquanto melhoramos nossos processos de tradução, adoraríamos receber seus comentários em help@twilio.com - contribuições valiosas podem render brindes da Twilio.