Apresentar páginas da Web em telas anexadas secundárias

Francisco Beaufort
François Beaufort

O Chrome 66 permite que páginas da Web usem uma tela conectada secundária por meio da API Apresentação e controlem o conteúdo dela com a API Apresentação receptor.

1/2 O usuário escolhe uma tela secundária conectada
1/2. O usuário escolhe uma tela secundária conectada
2/2. Uma página da Web é apresentada automaticamente à exibição escolhida anteriormente
2/2. Uma página da Web é apresentada automaticamente à tela selecionada anteriormente

Contexto

Até agora, os desenvolvedores da Web podiam criar experiências em que um usuário veria conteúdo local no Chrome diferente do que seria visto em uma tela remota e, ao mesmo tempo, conseguia controlar essa experiência localmente. Por exemplo, gerenciar uma fila de reprodução no youtube.com enquanto os vídeos são reproduzidos na TV ou ver um rolo de slides com anotações do apresentador em um laptop enquanto uma apresentação em tela cheia é exibida em uma sessão do Hangouts.

No entanto, há cenários em que os usuários podem simplesmente querer apresentar conteúdo em uma segunda tela conectada. Por exemplo, imagine um usuário em uma sala de conferências equipada com um projetor a que está conectado por um cabo HDMI. Em vez de espelhar a apresentação em um endpoint remoto, o usuário quer apresentar os slides em tela cheia no projetor, deixando a tela do laptop disponível para anotações do apresentador e controle sobre o slide. Embora o autor do site possa apoiar isso de maneira muito rudimentar, por exemplo, abrindo uma nova janela, que o usuário precisa arrastar manualmente para a tela secundária e maximizar para a tela cheia, isso é complicado e oferece uma experiência inconsistente entre apresentações locais e remotas.

Apresentar uma página

Vou ajudar você a usar a API analisar para apresentar uma página da Web na tela secundária conectada. O resultado final está disponível em https://googlechrome.github.io/samples/presentation-api/.

Primeiro, vamos criar um novo objeto PresentationRequest que vai conter o URL que queremos apresentar na tela secundária anexada.

const presentationRequest = new PresentationRequest('receiver.html');

In this article, I won’t cover use cases where the parameter passed to
`PresentationRequest` can be an array like `['cast://foo’, 'apple://foo',
'https://example.com']` as this is not relevant there.

We can now monitor presentation display availability and toggle a "Present"
button visibility based on presentation displays availability. Note that we can
also decide to always show this button.

<aside class="caution"><b>Caution:</b> The browser may use more energy while the <code>availability</code> object is alive
and actively listening for presentation display availability changes. Please
use it with caution in order to save energy on mobile.</aside>

```js
presentationRequest.getAvailability()
  .then(availability => {
    console.log('Available presentation displays: ' + availability.value);
    availability.addEventListener('change', function() {
      console.log('> Available presentation displays: ' + availability.value);
    });
  })
  .catch(error => {
    console.log('Presentation availability not supported, ' + error.name + ': ' +
        error.message);
  });

Para mostrar uma solicitação de exibição de apresentação, é necessário um gesto do usuário, como um clique em um botão. Portanto, vamos chamar presentationRequest.start() em um clique no botão e aguardar a resolução da promessa quando o usuário selecionar uma tela de apresentação (por exemplo, uma tela secundária anexada no nosso caso de uso).

function onPresentButtonClick() {
  presentationRequest.start()
  .then(connection => {
    console.log('Connected to ' + connection.url + ', id: ' + connection.id);
  })
  .catch(error => {
    console.log(error);
  });
}

A lista apresentada ao usuário também poderá incluir endpoints remotos, como dispositivos Chromecast, se você estiver conectado a uma rede que os divulgue. Observe que telas espelhadas não estão na lista. Consulte http://crbug.com/840466.

Seletor de exibição da apresentação
Seletor de exibição de apresentação

Quando a promessa for resolvida, a página da Web no URL do objeto PresentationRequest será apresentada para a tela escolhida. Et voilà!

Agora, podemos ir além e monitorar os eventos "close" e "terminate", conforme mostrado abaixo. É possível se reconectar a um presentationConnection "fechado" com presentationRequest.reconnect(presentationId), em que presentationId é o ID do objeto presentationRequest anterior.

function onCloseButtonClick() {
  // Disconnect presentation connection but will allow reconnection.
  presentationConnection.close();
}

presentationConnection.addEventListener('close', function() {
  console.log('Connection closed.');
});


function onTerminateButtonClick() {
  // Stop presentation connection for good.
  presentationConnection.terminate();
}

presentationConnection.addEventListener('terminate', function() {
  console.log('Connection terminated.');
});

Comunicar-se com a página

Você está pensando: tudo bem, mas como faço para transmitir mensagens entre minha página do controlador (a que acabamos de criar) e a página receptora (aquela que transmitimos para o objeto PresentationRequest)?

Primeiro, vamos recuperar as conexões existentes na página do receptor com navigator.presentation.receiver.connectionList e detectar as conexões de entrada, conforme mostrado abaixo.

// Receiver page

navigator.presentation.receiver.connectionList
.then(list => {
  list.connections.map(connection => addConnection(connection));
  list.addEventListener('connectionavailable', function(event) {
    addConnection(event.connection);
  });
});

function addConnection(connection) {

  connection.addEventListener('message', function(event) {
    console.log('Message: ' + event.data);
    connection.send('Hey controller! I just received a message.');
  });

  connection.addEventListener('close', function(event) {
    console.log('Connection closed!', event.reason);
  });
}

Uma conexão que recebe uma mensagem dispara um evento "message" que você pode detectar. A mensagem pode ser uma string, um Blob, um ArrayBuffer ou uma ArrayBufferView. Para enviar, basta chamar connection.send(message) na página do controlador ou na página receptora.

// Controller page

function onSendMessageButtonClick() {
  presentationConnection.send('Hello!');
}

presentationConnection.addEventListener('message', function(event) {
  console.log('I just received ' + event.data + ' from the receiver.');
});

Teste o exemplo em https://googlechrome.github.io/samples/presentation-api/ para ter uma noção de como ele funciona. Tenho certeza de que você vai gostar tanto quanto eu.

Amostras e demonstrações

Confira o exemplo oficial do Chrome que usamos neste artigo.

Também recomendo a demonstração interativa do Photowall. Esse app da Web permite que vários controles apresentem de forma colaborativa uma apresentação de slides de fotos em uma tela de apresentação. O código está disponível em https://github.com/GoogleChromeLabs/presentation-api-samples.

Captura de tela da demonstração do Photowall
Foto de José Luis Mieza / CC BY-NC-SA 2.0

Mais uma coisa

O Chrome tem o menu "Transmitir" do navegador que os usuários podem invocar a qualquer momento ao visitar um site. Se você quiser controlar a apresentação padrão desse menu, atribua navigator.presentation.defaultRequest a um objeto presentationRequest personalizado criado anteriormente.

// Make this presentation the default one when using the "Cast" browser menu.
navigator.presentation.defaultRequest = presentationRequest;

Dicas para desenvolvedores

Para inspecionar a página receptora e depurá-la, acesse a página chrome://inspect interna, selecione "Outro" e clique no link "inspecionar" ao lado do URL apresentado no momento.

Inspecionar páginas receptoras da apresentação
Inspecionar as páginas do receptor da apresentação

Confira também a página chrome://media-router-internals interna para saber mais sobre os processos internos de descoberta/disponibilidade.

A seguir

A partir do Chrome 66, as plataformas ChromeOS, Linux e Windows serão compatíveis. O suporte para Mac será oferecido posteriormente.

Recursos