Capturar um stream de vídeo de qualquer elemento

François Beaufort
François Beaufort

Com a API Screen Capture, é possível capturar toda a guia atual. A API Element Capture permite capturar e gravar um elemento HTML específico. Ele transforma uma captura da guia inteira em uma captura de uma subárvore DOM específica, capturando apenas os descendentes diretos do elemento de destino. Em outras palavras, ele corta e remove o conteúdo que está obstruindo e o que está obstruído.

Por que usar o Element Capture?

Considerar os requisitos de um aplicativo de videoconferência pode ajudar você a entender onde o Element Capture é útil. Se você tiver um aplicativo de videoconferência que permite incorporar aplicativos de terceiros em um iframe, talvez queira capturar esse iframe como um vídeo e transmiti-lo para participantes remotos.

Captura de tela de uma videoconferência no Chrome.
Elad usa um aplicativo de terceiros em uma chamada de videoconferência com François.

Chamar getDisplayMedia() e permitir que o usuário escolha a guia atual transmitiria toda a guia atual. Isso provavelmente vai transmitir o vídeo de volta para a pessoa. Você pode cortar isso usando a captura de região.

No entanto, e se o apresentador interagir com o aplicativo de videoconferência e algum conteúdo, como uma lista suspensa, aparecer sobre o conteúdo que será gravado?

Captura de tela de uma lista suspensa que cobre o conteúdo destinado à captura.
Uma lista suspensa aparece acima do conteúdo destinado à captura.

O recurso "Captura de região" não ajuda nesse caso. Parte da lista suspensa pode ficar visível nas telas dos participantes remotos.

Captura de tela de uma lista suspensa.
A lista suspensa de Elad aparece acima do conteúdo recebido por François.

O fato de a captura de região capturar partes dos elementos dessa maneira (conhecido como conteúdo obstruído) cria vários problemas:

  • O conteúdo obstruído pode impedir a visualização do conteúdo que o usuário pretendia compartilhar.
  • O conteúdo oculto pode ser particular (por exemplo, notificações de chat).
  • O conteúdo obstruído pode ser confuso. Por exemplo, um novo layout do aplicativo pode mostrar brevemente os vídeos dos participantes remotos sobre o alvo capturado.

A API Element Capture resolve todos esses problemas, permitindo que você segmente o elemento que quer compartilhar.

Captura de tela do elemento de destino sem a lista suspensa visível.
François não consegue acessar a lista suspensa de Elad.

Como usar a captura de elementos?

O captureTarget é um elemento na sua página que contém o conteúdo que o usuário quer capturar. Você quer que o app da Web de videoconferência capture captureTarget e compartilhe com participantes remotos. Então você extrai um RestrictionTarget de captureTarget. Depois de restringir a faixa de vídeo usando RestrictionTarget, os frames dessa faixa agora consistem apenas nos pixels que fazem parte de captureTarget e dos descendentes diretos do DOM.

Se o captureTarget mudar de tamanho, forma ou localização, a faixa de vídeo vai acompanhar, sem exigir nenhuma entrada adicional de nenhum app da Web. O conteúdo obstruído que aparece, desaparece ou se move também não requer tratamento especial.

Siga estas etapas novamente:

Comece permitindo que o usuário capture a guia atual.

// Ask the user for permission to start capturing the current tab.
const stream = await navigator.mediaDevices.getDisplayMedia({
 preferCurrentTab: true,
});
const [track] = stream.getVideoTracks();

Defina um RestrictionTarget chamando RestrictionTarget.fromElement() com um elemento de sua escolha como entrada.

// Associate captureTarget with a new RestrictionTarget
const captureTarget = document.querySelector("#captureTarget");
const restrictionTarget = await RestrictionTarget.fromElement(captureTarget);

Em seguida, chame restrictTo() na faixa de vídeo com RestrictionTarget como entrada. Quando a última promessa for resolvida, todos os frames subsequentes serão restritos.

// Start restricting the self-capture video track using the RestrictionTarget.
await track.restrictTo(restrictionTarget);

// Enjoy! Transmit remotely.

Análise detalhada

Detecção de recursos

Para verificar se o RestrictionTarget.fromElement() tem suporte, use:

if ("RestrictionTarget" in self && "fromElement" in RestrictionTarget) {
  // Deriving a restriction target is supported.
}

Derivar um RestrictionTarget

Concentre-se no elemento chamado captureTarget. Para derivar um RestrictionTarget dele, chame RestrictionTarget.fromElement(captureTarget). A promessa retornada será resolvida com um novo objeto RestrictionTarget se for bem-sucedida. Caso contrário, ela será rejeitada se você tiver criado um número excessivo de objetos RestrictionTarget.

const captureTarget = document.querySelector("#captureTarget");
const restrictionTarget = await RestrictionTarget.fromElement(captureTarget);

Ao contrário de um elemento, um objeto RestrictionTarget é serializável. Ele pode ser transmitido para outro documento usando Window.postMessage(), por exemplo.

Restrito

Ao capturar uma guia, a faixa de vídeo expõe restrictTo(). Ao capturar a guia atual, é válido chamar restrictTo() com null ou qualquer RestrictionTarget derivado de um elemento na guia atual.

As chamadas para restrictTo(restrictionTarget) transformam a faixa de vídeo em uma captura de captureTarget, como se ela fosse desenhada por si mesma, independentemente do resto do DOM. Todos os descendentes de captureTarget também são capturados. Os irmãos de captureTarget são eliminados da captura. O resultado é que todos os frames entregues na faixa aparecem como se tivessem sido cortados nos contornos de captureTarget, e todo o conteúdo ocluído e que causa obstrução é removido.

// Start restricting the self-capture video track using the RestrictionTarget.
await track.restrictTo(restrictionTarget);

As chamadas para restrictTo(null) revertem a faixa ao estado original.

// Stop restricting.
await track.restrictTo(null);

Se a chamada para restrictTo() for bem-sucedida, a promessa retornada será resolvida quando puder ser garantida que todos os frames de vídeo subsequentes serão restritos a captureTarget.

Se não for bem-sucedida, a promessa será rejeitada. Uma chamada para restrictTo() sem sucesso será por um dos seguintes motivos:

  • Se o restrictionTarget foi criado em uma guia diferente da que está sendo capturada. Usando o botão "Compartilhar esta guia", os usuários podem mudar a guia capturada a qualquer momento.
  • Se o restrictionTarget foi derivado de um elemento que não existe mais.
  • Se a faixa tiver clones. Consulte o problema 1509418.
  • Se a faixa atual não for uma faixa de vídeo de autocaptura.
  • Se o elemento de onde restrictionTarget foi derivado não estiver qualificado para restrição.

Considerações sobre a autocaptura

Quando um app chama getDisplayMedia() e o usuário escolhe capturar a própria guia do app, chamamos isso de "autocaptura".

O método restrictTo() é exposto em qualquer faixa de vídeo de captura de guia, não apenas para autocaptura. No entanto, por enquanto, a captura de elementos só está ativada para autocaptura. Portanto, é recomendável verificar se o usuário selecionou a guia atual antes de tentar restringir a faixa. Isso pode ser feito usando o Capture Handle. Também é possível pedir ao navegador para incentivar o usuário a fazer a autocaptura usando preferCurrentTab.

Transparência

Os frames de vídeo que o app recebe por getDisplayMedia() não incluem um canal alfa. Se um app definir um alvo de captura parcialmente transparente, a remoção do canal Alfa terá algumas consequências possíveis:

  • As cores podem mudar. Elementos de destino parcialmente transparentes desenhados sobre um plano de fundo claro podem ficar mais escuros quando o canal Alfa é removido, e aqueles desenhados sobre um plano de fundo escuro podem ficar mais claros.
  • Cores que eram invisíveis ou imperceptíveis para o usuário quando o canal Alfa estava definido no máximo apareciam quando o canal Alfa era removido. Por exemplo, isso pode levar a regiões pretas inesperadas nos frames capturados, se as seções transparentes tiverem o código RGBA rgba(0, 0, 0, 0).
Captura de tela do resultado de um destino de captura transparente que não é retangular.
O fluxo de vídeo de destino de captura transparente não retangular (à direita) é um retângulo de fundo preto que contém um círculo azul opaco.

Destinos de captura não qualificados

É sempre possível restringir uma faixa a qualquer destino de captura válido. No entanto, os frames não serão produzidos em determinadas condições, por exemplo, se o elemento ou um ancestral for display:none. O raciocínio geral é que a restrição se aplica apenas a um elemento que compreende uma única área retangular, coesa e bidimensional, cujos pixels podem ser determinados logicamente de forma isolada de qualquer elemento pai ou irmão.

Uma consideração importante para garantir que o elemento seja qualificado para restrição é que ele precisa formar seu próprio contexto de empilhamento. Para garantir isso, especifique a propriedade CSS isolation como isolate.

<div id="captureTarget" style="isolation: isolate;"></iframe>

O elemento de destino pode alternar entre estar qualificado e não qualificado para restrição em qualquer momento arbitrário, por exemplo, se o app mudar as propriedades do CSS. Cabe ao app usar metas de captura razoáveis e evitar mudanças inesperadas nas propriedades. Se o elemento de destino se tornar inelegível, novos frames simplesmente não serão emitidos na faixa até que o elemento de destino se torne novamente qualificado para restrição.

Como ativar a captura de elementos

A API Element Capture está disponível no Chrome para computador por trás da flag Element Capture e pode ser ativada em chrome://flags/#element-capture.

Esse recurso também está entrando em um teste de origem do Chrome 121 para computadores, que permite que os desenvolvedores ativem o recurso para visitantes dos sites para coletar dados de usuários reais. Consulte Começar a usar os testes de origem para mais informações.

Segurança e privacidade

Para entender as compensações de segurança, consulte a seção Considerações sobre privacidade e segurança da especificação do Element Capture.

O navegador Chrome desenha uma borda azul ao redor das bordas das guias capturadas.

Demonstração

Para testar a captura de elementos, execute a demonstração no Glitch. Confira o código-fonte.

Feedback

A equipe do Chrome e a comunidade de padrões da Web querem saber sobre sua experiência com o Element Capture.

Descreva o design

Há algo na captura de região que não funciona como esperado? Ou há métodos ou propriedades que faltam para implementar sua ideia? Tem alguma dúvida ou comentário sobre o modelo de segurança?

  • Envie um problema de especificação no repositório do GitHub ou adicione sua opinião a um problema existente.

Problemas com a implementação?

Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente da especificação?

  • Envie um bug em https://new.crbug.com. Inclua o máximo de detalhes possível e instruções simples para reproduzir o problema. O Glitch é ótimo para compartilhar reprosagens rápidas e fáceis.

Agradecimentos

Foto de Paul Skorupskas no Unsplash