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 de toda a guia em uma captura de uma subárvore DOM específica, capturando apenas descendentes diretos do elemento de destino. Em outras palavras, ele corta e remove o conteúdo oclusor e ocluído.

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 permita 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 videoconferência com François.

Chamar getDisplayMedia() e deixar o usuário escolher a guia atual transmitiria toda a guia atual. Isso provavelmente vai transmitir o vídeo das pessoas de volta para elas. É possível cortar essa parte 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 deve ser capturado?

Captura de tela de uma lista suspensa cobrindo o conteúdo que seria capturado.
Uma lista suspensa aparece na parte de cima do conteúdo que você quer capturar.

A captura de região não ajudaria 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 de elementos dessa forma (conhecido como conteúdo de oclusão) cria vários problemas:

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

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

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

Como faço para usar a captura de elementos?

O captureTarget é um Element na sua página que contém o conteúdo que o usuário quer capturar. Você quer que o web app de videoconferência capture captureTarget e compartilhe com participantes remotos. Assim, você deriva um RestrictionTarget de captureTarget. Depois de restringir a faixa de vídeo usando esse RestrictionTarget, os frames dela consistem apenas nos pixels que fazem parte de captureTarget e seus descendentes diretos do DOM.

Se captureTarget mudar de tamanho, forma ou local, a faixa de vídeo vai acompanhar sem exigir nenhuma entrada adicional do web app. O conteúdo oclusivo que aparece, desaparece ou se move também não exige tratamento especial.

Revise estas etapas:

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. Depois que 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 RestrictionTarget.fromElement() é compatível, use:

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

Derivar um RestrictionTarget

Foque no Element 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, ele será rejeitado se você tiver criado um número irracional 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 só, independente do restante do DOM. Todos os descendentes de captureTarget também são capturados, e 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 oclusivo e ocluído é 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 serão restritos a captureTarget.

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

  • Se o restrictionTarget foi gerado em uma guia diferente da que está sendo capturada. Ao usar 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 música tiver cópias. Consulte o problema 1509418.
  • Se a faixa atual não for de um vídeo gravado por você.
  • Se o elemento de que 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 tela, não apenas para autocaptura. No entanto, a captura de elementos está ativada apenas para autocaptura no momento. 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 destino de captura parcialmente transparente, a remoção do canal alfa poderá ter algumas consequências:

  • As cores podem mudar. Elementos de destino parcialmente transparentes desenhados sobre um fundo claro podem parecer mais escuros quando o canal alfa é removido, e aqueles desenhados sobre um fundo escuro podem parecer mais claros.
  • As cores que estavam invisíveis ou imperceptíveis para o usuário quando o canal alfa estava no máximo aparecem quando ele é 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 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 inelegíveis

É 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 apenas a um elemento que compreende uma área única, coesa, bidimensional e retangular, cujos pixels podem ser determinados logicamente isolados de qualquer elemento pai ou irmão.

Para garantir que o elemento esteja qualificado para restrição, é importante que ele forme seu próprio contexto de empilhamento. Para garantir isso, especifique a propriedade CSS isolation, definindo-a como isolate.

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

O elemento de destino pode alternar entre qualificado e não qualificado para restrição a qualquer momento, por exemplo, se o app mudar as propriedades de CSS. É responsabilidade do app usar metas de captura razoáveis e evitar mudar as propriedades de forma inesperada. Se o elemento de destino ficar inelegível, novos frames não serão emitidos na faixa até que o elemento de destino se torne novamente qualificado para restrição.

Suporte ao navegador

A captura de elementos está disponível apenas para computadores a partir do Chrome 132.

Segurança e privacidade

Para entender as compensações de segurança, confira 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 guias capturadas.

Demonstração

Para testar a captura de elementos, execute a demonstração.

Feedback

A equipe do Chrome e a comunidade de padrões da Web querem saber sobre suas experiências com a captura de elementos.

Fale sobre o design

Há algo na captura de elementos que não funciona como esperado? Ou há métodos ou propriedades ausentes que você precisa implementar sua ideia? Tem uma dúvida ou um comentário sobre o modelo de segurança?

  • Registre um problema de especificação no repositório do GitHub 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?

  • Registre um bug em https://new.crbug.com. Inclua o máximo de detalhes possível e instruções simples para reproduzir o problema.

Agradecimentos

Foto de Paul Skorupskas no Unsplash