Compartilhamento de tela com JavaScript e Twilio Programmable Video

July 15, 2020
Escrito por
Revisado por

Compartilhamento de tela em JavaScript e Twilio Programmable Video

A API Twilio Programmable Video permite criar aplicativos personalizados de chat por vídeo com base no padrão WebRTC. Neste artigo, vou mostrar como adicionar uma opção de compartilhamento de tela a um aplicativo Programmable Video para navegador criado em JavaScript.

Demonstração de compartilhamento de tela

Requisitos do tutorial

Neste tutorial, vamos adicionar um recurso de compartilhamento de tela ao aplicativo de chat por vídeo criado com JavaScript e Python em um tutorial introdutório anterior. Para executar esse aplicativo no computador, você precisa do seguinte:

  • Python 3.6 ou mais recente. Se o seu sistema operacional não fornece um interpretador de Python, acesse python.org para fazer download de um instalador.
  • Uma conta da Twilio gratuita ou paga. Se você é novo na Twilio, obtenha uma conta gratuita agora mesmo! Com este link, você terá US$ 10 quando fizer o upgrade.
  • Um navegador compatível com a biblioteca JavaScript da API Twilio Programmable Video (veja abaixo uma lista). Este requisito também se aplica aos usuários que você pretende convidar para utilizar o aplicativo depois de criado.

Como a funcionalidade principal de vídeo e áudio do projeto é fornecida pela API Twilio Programmable Video, teremos que usar um destes navegadores compatíveis:

  • Android: Chrome e Firefox
  • iOS: Safari
  • Linux: Chrome e Firefox
  • MacOS: Chrome, Firefox, Safari e Edge
  • Windows: Chrome, Firefox e Edge

Embora a lista de navegadores que oferece suporte a chamadas de vídeo seja razoavelmente longa e todos eles exibam faixas de compartilhamento de tela, apenas um subconjunto deles é capaz de iniciar uma sessão de compartilhamento de tela. Especificamente falando, nenhum navegador móvel pode fazer isso; e no desktop, vamos precisar das seguintes versões:

  • Chrome 72 ou posteriores
  • Firefox 66 ou posteriores
  • Safari 12.2 ou posteriores

Consulte a documentação da API Programmable Video para ver a lista mais recente de navegadores compatíveis e, especificamente, a página Screen Capture (Captura de tela) para saber quais versões dos navegadores são compatíveis com esse recurso.

Instalar e executar o aplicativo do tutorial

Vamos começar pela configuração do aplicativo de exemplo. Esse aplicativo está disponível no GitHub. Se você tiver o cliente git instalado, poderá baixar o aplicativo da seguinte forma:

$ git clone https://github.com/miguelgrinberg/flask-twilio-video

O branch master neste repositório já inclui todo o código necessário para suporte ao recurso de compartilhamento de tela. Se você pretende fazer a codificação acompanhando este tutorial, mude para o branch only-video-sharing usando o seguinte comando:

$ git checkout only-video-sharing

Se você não tiver o cliente git instalado, também poderá baixar o aplicativo completo no formato de um arquivo zip. Se você pretende codificar acompanhando o tutorial, só precisa da parte de chamada de vídeo.

Criar um ambiente virtual Python

Depois de baixar e configurar o código, vamos criar um ambiente virtual para instalar as dependências do Python.

Se você está usando um sistema Unix ou MacOS, abra um terminal, mude para o diretório do projeto e digite os seguintes comandos:

$ python -m venv venv
$ source venv/bin/activate
(venv) $ pip install -r requirements.txt

Se você está seguindo o tutorial no Windows, digite os seguintes comandos em uma janela do prompt de comando:

$ python -m venv venv
$ venv\Scripts\activate
(venv) $ pip install -r requirements.txt

O último comando usa o pip, o instalador de pacotes do Python, para instalar os pacotes do Python utilizados por este aplicativo. Os pacotes são os seguintes:

  • biblioteca auxiliar para Python da Twilio, para o funcionamento com as APIs da Twilio.
  • A estrutura do Flask, para criação do aplicativo da Web
  • Python-dotenv, para importação do conteúdo do arquivo .env como variáveis de ambiente
  • Pyngrok, para exposição temporária da versão de desenvolvimento do aplicativo na Internet

Configurar a conta da Twilio

O aplicativo precisa ser autenticado no serviço da Twilio com o uso das credenciais associadas à sua conta. Especificamente falando, você vai precisar do Account SID (SID da conta), do key SID (SID de uma chave) de API e do Key secret (segredo da chave) de API correspondente. Se você não está familiarizado com a forma de obter essas credenciais, sugiro rever as instruções na seção "Setting up your Twilio account" (Configurar a conta da Twilio) do tutorial sobre compartilhamento de vídeo.

O aplicativo inclui um arquivo chamado .env.template que contém as três variáveis de configuração necessárias. Faça uma cópia desse arquivo com o nome .env (ponto env) e edite-o da seguinte forma:

TWILIO_ACCOUNT_SID="<enter your Twilio account SID here>"
TWILIO_API_KEY_SID="<enter your Twilio API key here>"
TWILIO_API_KEY_SECRET="<enter your Twilio API secret here>"

Executar o aplicativo

Agora, o aplicativo deve estar pronto para ser executado. Verifique se o ambiente virtual está ativado e use o seguinte comando para iniciar o servidor Web:

(venv) $ flask run
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 274-913-316

O aplicativo está em execução, mas só pode receber conexões locais originadas no mesmo computador. Para atribuir um URL público temporário, que nos permitirá estabelecer a conexão de um telefone ou de outro computador, vamos usar o ngrok, que já está instalado como parte do ambiente virtual Python. Para iniciar o ngrok, abra uma segunda janela de terminal, ative o ambiente virtual (source venv/bin/activate ou venv\Scripts\activate, conforme o sistema operacional) e digite o seguinte comando:

(venv) $ ngrok http 5000

Agora, o segundo terminal vai mostrar algo parecido com esta tela:

captura de tela do ngrok

O ngrok atribuirá um URL público ao servidor. Encontre os valores listados nas chaves "Forwarding" para ver do que se trata. Vamos usar o URL que começa com https://, pois muitos navegadores não permitem que sites não criptografados acessem a câmera e o microfone. No exemplo acima, o URL público é https://bbf1b72b.ngrok.io. O seu será parecido com este, mas o primeiro componente do domínio será diferente sempre que você executar o ngrok.

Com o servidor Flask e o ngrok em execução no computador, você pode utilizar o URL público https:// do ngrok para se conectar ao servidor de uma fonte externa, como outro computador ou smartphone.

Se houver algum aspecto do aplicativo que gostaria de entender melhor, você poderá acessar o primeiro tutorial para ter todas as respostas de que precisa.

Introdução à API getDisplayMedia

Para capturar um fluxo de vídeo da tela de um usuário, vamos usar a API getDisplayMedia do navegador. Supondo que a sala esteja armazenada em uma variável room, o seguinte trecho de código iniciará uma sessão de compartilhamento de tela e a publicará na sala:

        navigator.mediaDevices.getDisplayMedia().then(stream => {
            screenTrack = new Twilio.Video.LocalVideoTrack(stream.getTracks()[0]);
            room.localParticipant.publishTrack(screenTrack);
        }).catch(() => {
            alert('Could not share the screen.')
        });

A chamada getDisplayMedia() solicita que o usuário selecione o que ele gostaria de compartilhar. A implementação dessa seleção é fornecida pelo navegador. Veja como ela se parece no Chrome:

Seleção de compartilhamento de tela do Chrome

Aqui, o usuário pode optar por compartilhar uma tela inteira, uma única janela ou até mesmo uma guia do navegador. Depois que a seleção é feita, a track (faixa) de vídeo é criada e publicada na chamada. Nesse momento, todos os outros participantes receberão o evento trackSubscribed, que é o mesmo evento que alerta o aplicativo quando a faixa de vídeo de um participante é publicada.

Para parar de compartilhar uma tela, precisamos cancelar a publicação da faixa na chamada e interromper a faixa de vídeo. Podemos fazer isso com o seguinte código:

        room.localParticipant.unpublishTrack(screenTrack);
        screenTrack.stop();
        screenTrack = null;

Para saber mais sobre compartilhamento de tela com a API Twilio Programmable Video, consulte a documentação.

Aprimoramentos de layout

Antes de integrarmos o compartilhamento de tela ao aplicativo, precisamos fazer algumas alterações no layout da página. Uma delas é adicionar um botão "Share Screen" (Compartilhar tela), que será colocado ao lado do botão "Join call" (Ingressar na chamada).

Botão Compartilhar tela

O layout atual pressupõe que cada participante terá uma única faixa de vídeo, apresentada com o nome abaixo dela. Quando um participante adiciona uma faixa de compartilhamento de tela, o nome abrange as duas faixas. Para evidenciar que um participante está compartilhando a tela, vamos adicionar uma cor de fundo ao elemento <div> que mostra o nome. Ele terá a seguinte aparência para cada participante que está compartilhando somente a câmera:

Exemplo de participante

Quando o participante começa a compartilhar a tela junto com o vídeo, o nome é centralizado nas duas faixas. A cor de fundo ajuda a indicar quem é o proprietário da faixa de tela:

Exemplo de participante com compartilhamento de tela

Vamos fazer estas alterações. Primeiro, vamos adicionar o botão de compartilhamento de tela à página HTML de base. Esta é a versão atualizada do arquivo *templates/index.html*, por isso você pode substituir todo o código pelo seguinte:

<!doctype html>
<html>
    <head>
        <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
    </head>
    <body>
        <h1>Flask & Twilio Video Conference</h1>
        <form>
            <label for="username">Name: </label>
            <input type="text" name="username" id="username">
            <button id="join_leave">Join call</button>
            <button id="share_screen" disabled>Share screen</button>
        </form>
        <p id="count">Disconnected.</p>
        <div id="container" class="container">
            <div id="local" class="participant"><div></div><div class="label">Me</div></div>
            <!-- more participants will be added dynamically here -->
        </div>

        <script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
        <script src="{{ url_for('static', filename='app.js') }}"></script>
    </body>
</html>

Estamos adicionando o botão de compartilhamento de tela em estado desativado, porque você precisa estar em uma chamada para usar esse recurso.

Além do botão, adicionei uma classe label ao elemento <div> que contém o nome do participante. Isso facilitará a adição da cor de fundo no arquivo CSS.

O arquivo static/styles.css atualizado é mostrado abaixo, com a nova cor de fundo do rótulo e algumas pequenas limpezas no código:

.container {
    margin-top: 20px;
    width: 100%;
    display: flex;
    flex-wrap: wrap;
}
.participant {
    margin-bottom: 5px;
    margin-right: 5px;
}
.participant div {
    text-align: center;
}
.participant div video {
    width: 240px;
    height: 180px;
    background-color: #ccc;
    border: 1px solid black;
}
.participant .label {
    background-color: #ddd;
}

A classe label também precisa ser adicionada a cada participante na função participantConnected() do arquivo static/app.js:

function participantConnected(participant) {
    // ...
    var labelDiv = document.createElement('div');
    labelDiv.setAttribute('class', 'label');
    labelDiv.innerHTML = participant.identity;
    participantDiv.appendChild(labelDiv);
    // ...

Iniciar uma sessão de compartilhamento de tela

Agora, estamos prontos para implementar o compartilhamento de tela no arquivo app.js. No início do arquivo, adicione uma instância do novo botão e uma nova variável que armazenará a faixa local de compartilhamento de tela:

const shareScreen = document.getElementById('share_screen');
var screenTrack;

Depois, na parte inferior, associe um manipulador ao evento click desse botão:

shareScreen.addEventListener('click', shareScreenHandler);

Na sequência, em qualquer lugar do arquivo, podemos adicionar nosso manipulador de compartilhamento de tela:

function shareScreenHandler() {
    event.preventDefault();
    if (!screenTrack) {
        navigator.mediaDevices.getDisplayMedia().then(stream => {
            screenTrack = new Twilio.Video.LocalVideoTrack(stream.getTracks()[0]);
            room.localParticipant.publishTrack(screenTrack);
            shareScreen.innerHTML = 'Stop sharing';
            screenTrack.mediaStreamTrack.onended = () => { shareScreenHandler() };
        }).catch(() => {
            alert('Could not share the screen.');
        });
    }
    else {
        room.localParticipant.unpublishTrack(screenTrack);
        screenTrack.stop();
        screenTrack = null;
        shareScreen.innerHTML = 'Share screen';
    }
};

Estamos usando a variável screenTrack não somente para armazenar a track (faixa) de vídeo, mas também para saber se o compartilhamento de tela está ativado ou não. Quando essa variável tem um

valor falso, sabemos que o compartilhamento de tela não está ativado, então iniciamos uma nova sessão usando a técnica acima. Também alteramos o rótulo do botão para "Stop sharing" (Parar de compartilhar).

Além disso, definimos o evento onended na faixa de compartilhamento de tela. Alguns navegadores fornecem a própria interface do usuário para encerrar uma sessão de compartilhamento de tela. O Chrome exibe este widget flutuante, por exemplo:

Pop-up de compartilhamento de tela no Chrome

Clicar no botão "Hide" (Ocultar) para interromper o fluxo encerrará o compartilhamento de tela. No entanto, o aplicativo e a API Twilio Video não saberão que o compartilhamento de tela foi encerrado e continuarão mostrando a faixa para todos os participantes com uma imagem congelada ou preta. O evento onended é uma forma de receber um retorno de chamada quando o usuário encerra o fluxo desse modo. Tudo o que precisamos fazer é enviar o retorno de chamada para a função de manipulador fazer a devida limpeza.

As últimas alterações cuidam do estado do botão de compartilhamento de tela, que começa desativado. Assim que o participante se conecta a uma chamada, podemos ativar esse botão e desativá-lo novamente quando ele se desconecta:

function connectButtonHandler(event) {
    event.preventDefault();
    if (!connected) {
        // ...
        connect(username).then(() => {
            // ...
            shareScreen.disabled = false;
        }).catch(() => {
        // ...
       });
    }
   else {
        disconnect();
        # ...
        shareScreen.innerHTML = 'Share screen';
        shareScreen.disabled = true;
    }
};

Quando uma sessão de compartilhamento de tela é iniciada, atualizamos o rótulo para "Stop sharing" (Parar de compartilhar). Agora, precisamos redefinir isso quando um participante se desconecta.

Com essas alterações, concluímos um recurso básico de compartilhamento de tela. Execute o aplicativo junto com o ngrok, conecte-se a uma chamada de vídeo com pelo menos duas janelas de navegador diferentes (no mesmo dispositivo ou em dispositivos diferentes) e tente compartilhar a tela entre dois dispositivos!

Adicionar um recurso de tela cheia

No compartilhamento de vídeo, as faixas de vídeo grandes são bastante preocupantes. Porém, ao compartilhar a tela, mostrar uma pequena faixa de vídeo em miniatura torna o texto mais ilegível. Para deixar o compartilhamento de tela mais útil, podemos adicionar um recurso de zoom que coloca qualquer faixa de vídeo em tela cheia com um simples clique:

Demonstração em tela cheia

Para aplicar zoom a uma faixa de vídeo, vamos atribuir a ela uma nova classe CSS chamada participantZoomed e, ao mesmo tempo, vamos atribuir uma classe participantHidden a todas as outras faixas. Este é o arquivo static/styles.css atualizado com essas novas classes:

.container {
    margin-top: 20px;
    width: 100%;
    display: flex;
    flex-wrap: wrap;
}
.participant {
    margin-bottom: 5px;
    margin-right: 5px;
}
.participant div {
    text-align: center;
}
.participant div video {
    background-color: #ccc;
    border: 1px solid black;
}
.participant div video:not(.participantZoomed) {
    width: 240px;
    height: 180px;
}
.participant .label {
    background-color: #ddd;
}
.participantZoomed {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
}
.participantHidden {
    display: none;
}

Em seguida, precisamos adicionar manipuladores de evento click em todas as faixas. Para a faixa de vídeo local, modifique a função addLocalVideo():

function addLocalVideo() {
    Twilio.Video.createLocalVideoTrack().then(track => {
        var video = document.getElementById('local').firstChild;
        var trackElement = track.attach();
        trackElement.addEventListener('click', () => { zoomTrack(trackElement); });
        video.appendChild(trackElement);
    });
};

Para as faixas de vídeo de outros participantes, podemos adicionar o manipulador na função trackSubscribed():

function trackSubscribed(div, track) {
    var trackElement = track.attach();
    trackElement.addEventListener('click', () => { zoomTrack(trackElement); });
    div.appendChild(trackElement);
};

O manipulador zoomTrack() é mostrado abaixo:

function zoomTrack(trackElement) {
    if (!trackElement.classList.contains('participantZoomed')) {
        // zoom in
        container.childNodes.forEach(participant => {
            if (participant.className == 'participant') {
                participant.childNodes[0].childNodes.forEach(track => {
                    if (track === trackElement) {
                        track.classList.add('participantZoomed')
                    }
                    else {
                        track.classList.add('participantHidden')
                    }
                });
                participant.childNodes[1].classList.add('participantHidden');
            }
        });
    }
    else {
        // zoom out
        container.childNodes.forEach(participant => {
            if (participant.className == 'participant') {
                participant.childNodes[0].childNodes.forEach(track => {
                    if (track === trackElement) {
                        track.classList.remove('participantZoomed');
                    }
                    else {
                        track.classList.remove('participantHidden');
                    }
                });
                participant.childNodes[1].classList.remove('participantHidden');
            }
        });
    }
};

O procedimento para aumentar o zoom se repete em todos os elementos do div container, que são os participantes da chamada. Para cada participante, ele se repete nas respectivas faixas, aplicando participantZoomed à faixa selecionada e participantHidden a todas as demais. A classe oculta também é aplicada ao elemento <div> que contém o nome do participante. O processo de diminuir o zoom aplica o mesmo processo, mas ao contrário.

Uma complicação decorrente dessas alterações é quando uma faixa ampliada tem sua publicação cancelada na chamada. Nesse caso, precisamos executar o procedimento de diminuir o zoom antes da exibição da faixa. Podemos fazer isso no manipulador trackUnsubscribed():

function trackUnsubscribed(track) {
    track.detach().forEach(element => {
        if (element.classList.contains('participantZoomed')) {
            zoomTrack(element);
        }
        element.remove()
    });
};

E, assim, o recurso de compartilhamento de tela está concluído!

Conclusão

Espero que, seguindo este tutorial, você consiga adicionar o compartilhamento de tela ao seu aplicativo Twilio Programmable Video.

Caso você precise de suporte para compartilhamento de tela em outros navegadores além do Chrome, Firefox e Safari, ou talvez em versões mais antigas deles, meu colega Phil Nash escreveu alguns tutoriais que podem ajudar:

Adoraria ver os aplicativos interessantes de chat por vídeo que você criou!

Este artigo foi traduzido do original "Screen Sharing with JavaScript and Twilio Programmable 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.