A API Screen Capture permite que o usuário selecione uma guia, janela ou tela para capturar como um stream de mídia. Esse stream pode ser gravado ou compartilhado com outras pessoas pela rede. Esta documentação apresenta o foco condicional, um mecanismo para apps da Web controlarem se a guia ou janela capturada vai receber foco quando a captura começar ou se a página de captura vai permanecer em foco.
Suporte ao navegador
O foco condicional está disponível a partir do Chrome 109.
Contexto
Quando um app da Web começa a capturar uma guia ou uma janela, o navegador precisa decidir se a superfície capturada será mostrada em primeiro plano ou se a página de captura vai permanecer em foco. A resposta depende do motivo da chamada getDisplayMedia()
e da superfície que o usuário acaba selecionando.
Considere um app da Web de videoconferência hipotético. Ao ler track.getSettings().displaySurface
e possivelmente examinar o handle de captura, o app da Web de videoconferência pode entender o que o usuário escolheu compartilhar. Em seguida:
- Se a guia ou janela capturada puder ser controlada remotamente, mantenha a videoconferência em foco.
- Caso contrário, foque a guia ou janela capturada.
No exemplo acima, o app da Web de videoconferência manteria o foco ao compartilhar uma apresentação de slides, permitindo que o usuário folheie os slides remotamente. No entanto, se o usuário escolher compartilhar um editor de texto, o app da Web de videoconferência mudará imediatamente o foco para a guia ou janela capturada.
Como usar a API Conditional Focus
Instancie um CaptureController
e transmita-o para getDisplayMedia()
. Ao chamar setFocusBehavior()
imediatamente após a resolução da promessa retornada por getDiplayMedia()
, você pode controlar se a guia ou janela capturada vai ser focada ou não. Isso só pode ser feito se o usuário tiver compartilhado uma guia ou uma janela.
const controller = new CaptureController();
// Prompt the user to share a tab, a window or a screen.
const stream =
await navigator.mediaDevices.getDisplayMedia({ controller });
const [track] = stream.getVideoTracks();
const displaySurface = track.getSettings().displaySurface;
if (displaySurface == "browser") {
// Focus the captured tab.
controller.setFocusBehavior("focus-captured-surface");
} else if (displaySurface == "window") {
// Do not move focus to the captured window.
// Keep the capturing page focused.
controller.setFocusBehavior("focus-capturing-application");
}
Ao decidir se vai focar, é possível considerar o Capture Handle.
// Retain focus if capturing a tab dialed to example.com.
// Focus anything else.
const origin = track.getCaptureHandle().origin;
if (displaySurface == "browser" && origin == "https://example.com") {
controller.setFocusBehavior("focus-capturing-application");
} else if (displaySurface != "monitor") {
controller.setFocusBehavior("focus-captured-surface");
}
É possível até mesmo decidir se você quer focar antes de chamar getDisplayMedia()
.
// Focus the captured tab or window when capture starts.
const controller = new CaptureController();
controller.setFocusBehavior("focus-captured-surface");
// Prompt the user to share their screen.
const stream =
await navigator.mediaDevices.getDisplayMedia({ controller });
É possível chamar setFocusBehavior()
arbitrariamente várias vezes antes da resolução da promessa ou, no máximo, uma vez imediatamente após a resolução. A última invocação substitui todas as anteriores.
Mais precisamente:
- A promessa retornada getDisplayMedia()
é resolvida em uma microtarefa. Chamar setFocusBehavior()
após a conclusão da microtarefa gera um erro.
- Chamar setFocusBehavior()
mais de um segundo após o início da captura não faz nada.
Ou seja, os dois snippets a seguir vão falhar:
// Prompt the user to share their screen.
const stream =
await navigator.mediaDevices.getDisplayMedia({ controller });
// Too late, because it follows the completion of the task
// on which the getDisplayMedia() promise resolved.
// This will throw.
setTimeout(() => {
controller.setFocusBehavior("focus-captured-surface");
});
// Prompt the user to share their screen.
const stream =
await navigator.mediaDevices.getDisplayMedia({ controller });
const start = new Date();
while (new Date() - start <= 1000) {
// Idle for ≈1s.
}
// Because too much time has elapsed, the browser will have
// already decided whether to focus.
// This fails silently.
controller.setFocusBehavior("focus-captured-surface");
A chamada de setFocusBehavior()
também gera os seguintes casos:
- a faixa de vídeo do stream retornado por
getDisplayMedia()
não está "ao vivo". - depois que a promessa retornada
getDisplayMedia()
for resolvida, se o usuário tiver compartilhado uma tela (não uma guia ou janela).
Exemplo
Para testar o foco condicional, execute a demonstração no Glitch. Confira o código-fonte.
Detecção de recursos
Para verificar se o CaptureController.setFocusBehavior()
tem suporte, use:
if (
"CaptureController" in window &&
"setFocusBehavior" in CaptureController.prototype
) {
// CaptureController.setFocusBehavior() is supported.
}
Feedback
A equipe do Chrome e a comunidade de padrões da Web querem saber sobre sua experiência com o foco condicional.
Descreva o design
O foco condicional não está funcionando 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 funciona bem para compartilhar código.
Mostrar apoio
Você planeja usar o foco condicional? Seu apoio público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegadores a importância de oferecer suporte a eles.
Envie um tweet para @ChromiumDev e diga onde e como você está usando.
Links úteis
Agradecimentos
Imagem principal de Elena Taranenko.
Agradecemos a Rachel Andrew por revisar este artigo.