Capturar um stream de vídeo de qualquer elemento

Francisco Beaufort
François Beaufort
Elad alon
Alon Elad

Com a API Screen Capture, você pode capturar toda a guia atual. A API Element Capture permite capturar e gravar um elemento HTML específico. Ela transforma a captura de toda a guia em uma subárvore específica do DOM, capturando somente descendentes diretos do elemento de destino. Em outras palavras, ela recorta e remove conteúdos ocultos e ocultos.

Por que usar a captura de elementos?

Considerar os requisitos de um aplicativo de videoconferência pode ajudar você a entender onde a captura de elementos é útil. Se você tiver um aplicativo de videoconferência que permite incorporar aplicativos de terceiros a um iframe, às vezes poderá capturar esse iframe como um vídeo e transmiti-lo aos participantes remotos.

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

Chamar getDisplayMedia() e permitir que o usuário escolha a guia atual transmitirá toda a guia atual. É provável que isso transmita o vídeo das pessoas de volta a elas. Para cortá-la, use a Captura de região.

No entanto, e se o apresentador usar o aplicativo de videoconferência e alguns conteúdos, como uma lista suspensa, forem desenhados sobre o conteúdo pretendido para captura?

Captura de tela de uma lista suspensa cobrindo o conteúdo destinado à captura.
Uma lista suspensa aparece na parte de cima do conteúdo destinado à captura.

A captura de região não ajudaria nisso. 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 na parte superior do conteúdo recebido por François.

O fato de que a captura de região captura partes de elementos dessa maneira (conhecida como conteúdo oculto) cria vários problemas:

  • O conteúdo pode impedir a visualização do conteúdo que o usuário pretendia compartilhar.
  • A exclusão do conteúdo pode ser particular (por exemplo, notificações de chat).
  • A inclusão de conteúdo pode ser confusa. Por exemplo, um novo layout do aplicativo poderia trazer brevemente os próprios vídeos dos participantes remotos para o destino capturado.

A API Element Capture resolve todos esses problemas ao permitir que você direcione o elemento que quer compartilhar.

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

Como usar a captura de elementos?

A captureTarget é um elemento da página que tem 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ê deriva um RestrictionTarget de captureTarget. Depois de restringir a faixa de vídeo usando esse RestrictionTarget, os frames dessa faixa de vídeo agora consistem apenas nos pixels que fazem parte de captureTarget e nos descendentes diretos do DOM.

Se o captureTarget mudar de tamanho, forma ou local, a faixa de vídeo vai acompanhar o vídeo, sem precisar de entradas adicionais de nenhum dos apps da Web. A ocultação de conteúdo que aparece, desaparece ou se move, da mesma forma, não requer tratamento especial.

Revise essas etapas novamente:

Para começar, permita 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 o 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 conferir se RestrictionTarget.fromElement() é compatível, 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 tivesse sido desenhada sozinha, independentemente do restante do DOM. Todos os descendentes de captureTarget também são capturados. Os irmãos de captureTarget são eliminados da captura. Como resultado, todos os frames entregues na faixa aparecem como se tivessem sido cortados nos contornos de captureTarget, e todo conteúdo oculto e oculto é 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 for possível garantir que todos os frames de vídeo subsequentes sejam restritos a captureTarget.

Se falhar, a promessa vai ser rejeitada. Uma chamada malsucedida para restrictTo() vai ocorrer por um dos seguintes motivos:

  • Se o restrictionTarget tiver sido criado em uma guia diferente da capturada. Ao usar o botão "Compartilhar esta guia", os usuários podem alterar a guia capturada a qualquer momento.
  • Se o restrictionTarget for derivado de um elemento que não existe mais.
  • Se a faixa tiver clones. Consulte o problema 1509418 (link em inglês).
  • Se a faixa atual não for uma faixa de vídeo de captura automática.
  • Se o elemento do qual restrictionTarget foi derivado não estiver qualificado para restrição.

Considerações sobre a captura automática

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

O método restrictTo() é exposto em qualquer faixa de vídeo de captura de guia, e não apenas na autocaptura. No entanto, a captura de elementos só está ativada para a autocaptura no momento. Portanto, é aconselhável verificar se o usuário selecionou a guia atual, antes de tentar restringir a faixa. Isso pode ser feito com o Capture Handle. Também é possível pedir ao navegador para incentivar o usuário a fazer a captura 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 destino de captura parcialmente transparente, a remoção do Canal Alfa tem algumas consequências possíveis:

  • As cores podem mudar. Elementos de destino parcialmente transparentes sobre um fundo claro podem parecer mais escuros quando o canal alfa é removido. Já os elementos desenhados sobre um fundo escuro podem parecer mais claros.
  • As cores que eram invisíveis ou imperceptíveis para o usuário quando o canal alfa foi definido para o máximo aparecem depois que ele é removido. Por exemplo, isso poderia levar a regiões pretas inesperadas nos frames capturados, se as seções transparentes tivessem o código RGBA rgba(0, 0, 0, 0).
Captura de tela do resultado de um destino de captura transparente não retangular.
O stream de vídeo de destino de captura transparente não retangular (à direita) é um retângulo preto no plano de fundo que contém um círculo azul opaco.

Destinos de captura não qualificados

Sempre é possível começar a 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. A lógica geral é que a restrição se aplica somente a um elemento que compreende uma área retangular única, coesa, bidimensional, cujos pixels podem ser determinados logicamente isoladamente 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 o próprio contexto de pilha. Para garantir isso, especifique a propriedade CSS de isolamento, definindo-a como isolate.

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

O elemento de destino pode alternar entre estar qualificado e não para restrição a qualquer momento, por exemplo, se o app mudar as propriedades CSS. Cabe ao app usar destinos de captura razoáveis e evitar alterações inesperadas nas propriedades. Se o elemento de destino não estiver qualificado, novos frames não serão emitidos na faixa até que ele volte a se qualificar 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 no computador, que permite aos desenvolvedores ativar o recurso para os visitantes dos sites coletarem dados de usuários reais. Consulte Primeiros passos com testes de origem para saber mais sobre esses testes.

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 de captura de elementos.

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

Demonstração

Você pode brincar com a captura de elementos executando 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 mais sobre suas experiências com o Element Capture.

Fale sobre o design

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

  • Registre um problema específico no repositório do GitHub (link em inglês) ou adicione suas ideias 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?

  • Informe um bug em https://new.crbug.com. Lembre-se de incluir o máximo de detalhes possível e instruções simples de reprodução. O Glitch funciona muito bem para compartilhar repetições rápidas e fáceis.

Agradecimentos

Foto de Paul Skorupskas no Unsplash (links em inglês)