O compartilhamento de guias, janelas e telas já é possível na plataforma da Web com a API Screen Capture. Quando um app da Web chamar getDisplayMedia()
, o Chrome solicitará que o usuário compartilhe uma guia, janela ou tela com o app como um vídeo MediaStreamTrack
.
Muitos apps da Web que usam o getDisplayMedia()
mostram ao usuário uma prévia em vídeo da superfície capturada. Por exemplo, os apps de videoconferência geralmente transmitem esse vídeo para usuários remotos e o renderizam para uma HTMLVideoElement
local. Assim, o usuário local tem sempre uma prévia do que está compartilhando.
Esta documentação apresenta a nova API Captured Surface Control (link em inglês) no Chrome, que permite que o app da Web role uma guia capturada, além de ler e gravar o nível de zoom de uma guia capturada.
Por que usar o Captured Surface Control?
Todos os apps de videoconferência têm a mesma desvantagem: se o usuário quiser interagir com uma guia ou janela capturada, precisará alternar para essa plataforma, removendo-o do app de videoconferência. Isso apresenta alguns desafios:
- O usuário não poderá ver o app capturado e os vídeos de usuários remotos ao mesmo tempo, a menos que use o modo Picture-in-picture ou janelas lado a lado separadas para a guia de videoconferência e a guia compartilhada. Em uma tela menor, isso pode ser difícil.
- O usuário fica sobrecarregado pela necessidade de alternar entre o app de videoconferência e a plataforma capturada.
- o usuário perder o acesso aos controles expostos pelo app de videoconferência enquanto estiver fora dele; por exemplo, um app de chat incorporado, reações com emojis, notificações sobre pedidos de participação de usuários, controles multimídia e de layout e outros recursos úteis de videoconferência.
- O apresentador não pode delegar o controle aos participantes remotos. Isso nos leva a uma situação muito familiar, em que usuários remotos pedem para o apresentador mudar o slide, rolar um pouco para cima e para baixo ou ajustar o nível de zoom.
A API Captured Surface Control resolve esses problemas.
Como usar o Captured Surface Control?
O uso do Controle de superfície capturada requer algumas etapas, como capturar explicitamente uma guia do navegador e receber permissão do usuário antes de rolar e aplicar zoom à guia capturada.
Capturar uma guia do navegador
Comece solicitando que o usuário escolha uma plataforma para compartilhar usando getDisplayMedia()
e, no processo, associe um objeto CaptureController
à sessão de captura. Vamos usar esse objeto para controlar a superfície capturada em breve.
const controller = new CaptureController();
const stream = await navigator.mediaDevices.getDisplayMedia({ controller });
Em seguida, produza uma visualização local da superfície capturada na forma de um elemento <video>
:
const previewTile = document.querySelector('video');
previewTile.srcObject = stream;
Se o usuário optar por compartilhar uma janela ou tela, isso está fora do escopo no momento. No entanto, se ele optar por compartilhar uma guia, poderemos prosseguir.
const [track] = stream.getVideoTracks();
if (track.getSettings().displaySurface !== 'browser') {
// Bail out early if the user didn't pick a tab.
return;
}
Comando de permissão
A primeira invocação de sendWheel()
ou setZoomLevel()
em um determinado objeto CaptureController
produz uma solicitação de permissão. Se o usuário conceder permissão, outras invocações desses métodos no objeto CaptureController
serão permitidas. Se o usuário negar a permissão, a promessa retornada vai ser rejeitada.
Os objetos CaptureController
são associados exclusivamente a uma capture-session específica, não podem ser associados a outra sessão de captura e não sobrevivem à navegação da página em que são definidos. No entanto, as sessões de captura sobrevivem à navegação da página capturada.
É necessário fazer um gesto para mostrar uma solicitação de permissão ao usuário. Apenas as chamadas sendWheel()
e setZoomLevel()
exigem um gesto do usuário e somente se a solicitação precisar ser mostrada. Se o usuário clicar em um botão para aumentar ou diminuir o zoom no app da Web, esse gesto do usuário será determinado. No entanto, se o app quiser oferecer controle de rolagem primeiro, os desenvolvedores precisam ter em mente que rolar não constitui um gesto do usuário. Uma possibilidade é oferecer primeiro ao usuário uma opção para "começar a rolar" , conforme o exemplo a seguir:
const startScrollingButton = document.querySelector('button');
startScrollingButton.addEventListener('click', async () => {
try {
const noOpWheelAction = {};
await controller.sendWheel(noOpWheelAction);
// The user approved the permission prompt.
// You can now scroll and zoom the captured tab as shown later in the article.
} catch (error) {
return; // Permission denied. Bail.
}
});
Rolagem
Usando sendWheel()
, um app de captura pode exibir eventos de roda com a magnitude escolhida nas coordenadas escolhidas na janela de visualização de uma guia. O evento é indistinguível do app capturado da interação direta do usuário.
Supondo que o app de captura use um elemento <video>
chamado "previewTile"
, o código abaixo mostra como redirecionar eventos de roda de envio para a guia capturada:
const previewTile = document.querySelector('video');
previewTile.addEventListener('wheel', async (event) => {
// Translate the offsets into coordinates which sendWheel() can understand.
// The implementation of this translation is explained further below.
const [x, y] = translateCoordinates(event.offsetX, event.offsetY);
const [wheelDeltaX, wheelDeltaY] = [-event.deltaX, -event.deltaY];
try {
// Relay the user's action to the captured tab.
await controller.sendWheel({ x, y, wheelDeltaX, wheelDeltaY });
} catch (error) {
// Inspect the error.
// ...
}
});
O método sendWheel()
usa um dicionário com dois conjuntos de valores:
x
ey
: as coordenadas em que o evento da roda será entregue.wheelDeltaX
ewheelDeltaY
: as magnitudes dos rolamentos, em pixels, para rolagens horizontais e verticais, respectivamente. Esses valores são invertidos em comparação com o evento original da roda.
Uma possível implementação de translateCoordinates()
é:
function translateCoordinates(offsetX, offsetY) {
const previewDimensions = previewTile.getBoundingClientRect();
const trackSettings = previewTile.srcObject.getVideoTracks()[0].getSettings();
const x = trackSettings.width * offsetX / previewDimensions.width;
const y = trackSettings.height * offsetY / previewDimensions.height;
return [Math.floor(x), Math.floor(y)];
}
Observe que há três tamanhos diferentes em jogo no código anterior:
- O tamanho do elemento
<video>
. - O tamanho dos frames capturados (representados aqui como
trackSettings.width
etrackSettings.height
). - O tamanho da guia.
O tamanho do elemento <video>
está totalmente dentro do domínio do app de captura e é desconhecido para o navegador. O tamanho da guia está totalmente dentro do domínio do navegador e é desconhecido para o aplicativo da web.
O app da Web usa translateCoordinates()
para traduzir os deslocamentos relativos ao elemento <video>
em coordenadas dentro do espaço de coordenadas da faixa de vídeo. Da mesma forma, o navegador fará a conversão entre o tamanho dos frames capturados e o tamanho da guia e fornecerá o evento de rolagem em um deslocamento correspondente à expectativa do app da Web.
A promessa retornada por sendWheel()
pode ser rejeitada nos seguintes casos:
- Se a sessão de captura ainda não tiver sido iniciada ou já tiver sido interrompida, incluindo a interrupção assíncrona enquanto a ação
sendWheel()
é processada pelo navegador. - Se o usuário não concedeu permissão ao app para usar
sendWheel()
. - Se o app de captura tentar entregar um evento de rolagem em coordenadas que estejam fora de
[trackSettings.width, trackSettings.height]
. Esses valores podem mudar de forma assíncrona, por isso é uma boa ideia capturar o erro e ignorá-lo. Normalmente, o0, 0
não estaria fora dos limites, então é seguro usá-lo para solicitar a permissão do usuário.
Zoom
A interação com o nível de zoom da guia capturada é feita pelas seguintes plataformas do CaptureController
:
getSupportedZoomLevels()
retorna uma lista de níveis de zoom compatíveis com o navegador, representada como porcentagens do "nível de zoom padrão", definido como 100%. Essa lista está aumentando monotonicamente e contém o valor 100.getZoomLevel()
retorna o nível de zoom atual da guia.setZoomLevel()
define o nível de zoom da guia como qualquer valor inteiro presente emgetSupportedZoomLevels()
e retorna uma promessa quando é concluído. O nível de zoom não é redefinido ao final da sessão de captura.- O
oncapturedzoomlevelchange
permite que você ouça as mudanças no nível de zoom de uma guia capturada, já que os usuários podem mudar o nível pelo app de captura ou pela interação direta com a guia capturada.
As chamadas para setZoomLevel()
são controladas por permissão. chamadas para os outros métodos de zoom somente leitura são "sem custo financeiro", assim como a escuta de eventos.
O exemplo a seguir mostra como aumentar o nível de zoom de uma guia capturada em uma sessão de captura atual:
const zoomIncreaseButton = document.getElementById('zoomInButton');
zoomIncreaseButton.addEventListener('click', async (event) => {
const levels = CaptureController.getSupportedZoomLevels();
const index = levels.indexOf(controller.getZoomLevel());
const newZoomLevel = levels[Math.min(index + 1, levels.length - 1)];
try {
await controller.setZoomLevel(newZoomLevel);
} catch (error) {
// Inspect the error.
// ...
}
});
O exemplo a seguir mostra como reagir às mudanças de nível de zoom de uma guia capturada:
controller.addEventListener('capturedzoomlevelchange', (event) => {
const zoomLevel = controller.getZoomLevel();
document.querySelector('#zoomLevelLabel').textContent = `${zoomLevel}%`;
});
Detecção de recursos
Para verificar se há suporte para o envio de eventos da roda, use:
if (!!window.CaptureController?.prototype.sendWheel) {
// CaptureController sendWheel() is supported.
}
Para verificar se o controle de zoom é compatível, use:
if (!!window.CaptureController?.prototype.setZoomLevel) {
// CaptureController setZoomLevel() is supported.
}
Ativar controle de superfície capturada
A API Captured Surface Control está disponível no Chrome para computadores com a flag "Captured Surface Control" e pode ser ativada em chrome://flags/#captured-surface-control
.
Esse recurso também está iniciando um teste de origem com o Chrome 122 para computadores. Com ele, os desenvolvedores podem ativar o recurso para que os visitantes dos sites coletem dados de usuários reais. Consulte Introdução aos testes de origem para mais informações sobre eles e como eles funcionam.
Segurança e privacidade
A política de permissão "captured-surface-control"
permite gerenciar como o app de captura e os iframes incorporados de terceiros têm acesso ao controle da superfície capturada. Para entender os prós e contras da segurança, consulte a seção Considerações sobre privacidade e segurança na explicação sobre o Captured Surface Control.
Demonstração
Você pode testar o Captured Surface Control executando a demonstração no Glitch. Não se esqueça de conferir o código-fonte.
Mudanças em relação às versões anteriores do Chrome
Confira algumas das principais diferenças comportamentais do controle de superfície capturada que você precisa conhecer:
- No Chrome 124 e versões anteriores:
- A permissão, se concedida, tem o escopo definido para a sessão de captura associada a essa
CaptureController
, não para a origem da captura.
- A permissão, se concedida, tem o escopo definido para a sessão de captura associada a essa
- No Chrome 122:
getZoomLevel()
retorna uma promessa com o nível de zoom atual da guia.sendWheel()
vai retornar uma promessa rejeitada com a mensagem de erro"No permission."
se o usuário não tiver concedido permissão de uso ao app. O tipo de erro é"NotAllowedError"
no Chrome 123 e versões mais recentes.oncapturedzoomlevelchange
não está disponível. É possível preencher esse recurso com o polyfill usandosetInterval()
.
Feedback
A equipe do Chrome e a comunidade de padrões da Web querem saber sobre suas experiências com o Captured Surface Control.
Conte-nos sobre o design
Alguma coisa na Captura de superfície capturada não funciona como esperado? Ou faltam métodos ou propriedades que você precisa para implementar sua ideia? Tem uma pergunta ou comentário sobre o modelo de segurança? Registre um problema de especificação no repositório do GitHub (link em inglês) ou adicione sua opinião a um problema.
Problemas com a implementação?
Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente das especificações? Registre um bug em https://new.crbug.com. Certifique-se de incluir o máximo de detalhes possível, bem como instruções para reproduzi-los. O Glitch (link em inglês) funciona muito bem para compartilhar bugs reproduzíveis.