Autoplay de vídeo bloqueado no HTML: mudo e com pausa
Vídeo de fundo não toca sozinho? O navegador bloqueia autoplay com som. Veja o teste que prova, o HTML com muted e playsinline e o botão de pausa da WCAG.
O vídeo da capa funciona no computador de quem fez o site e, no navegador do cliente, fica parado no primeiro quadro. Quase sempre o motivo é o mesmo: ele tenta começar com som, e o navegador recusa. Chrome, Firefox e Safari só deixam um vídeo tocar sozinho quando ele está mudo ou quando a pessoa já interagiu com o site.
Este post trata do vídeo decorativo de fundo, aquele que roda atrás do título de uma landing page: por que ele não toca, como escrever o HTML que toca em qualquer navegador e como deixar a pessoa parar o movimento. Para vídeo com fala, que precisa de controles e legenda, o caminho é outro e está em vídeo HTML5 com controles e legenda.
O que o navegador permite
A política de autoplay do Chrome diz que autoplay mudo é sempre permitido. Com som, só quando a pessoa já clicou ou tocou na página, quando o índice de engajamento com mídia daquele site passou de um limite (no desktop) ou quando o site foi instalado como app. O guia de autoplay da MDN descreve regras parecidas nos outros navegadores.
Na prática, um visitante novo vindo do Google nunca cumpre essas condições. Se o vídeo depende de som para começar, ele não começa.
O teste que mostra a diferença
Para ver isso sem depender da sorte, uma página com dois vídeos, um com faixa de áudio e outro com muted, chama play() nos dois ao carregar. O play() devolve uma promessa, e a recusa chega como erro:
async function tenta(id) {
const v = document.getElementById(id);
try { await v.play(); res[id] = "tocando"; }
catch (e) { res[id] = e.name; }
}
Rodando a página no Chrome 156 com Puppeteer, primeiro com a regra padrão do desktop (--autoplay-policy=document-user-activation-required) e depois liberando tudo, o resultado foi:
document-user-activation-required {"com":"NotAllowedError","mudo":"tocando"}
após clique: tocando
no-user-gesture-required {"mudo":"tocando","com":"tocando"}
O vídeo com som é recusado com NotAllowedError, o mudo toca, e depois de um clique na página o vídeo com som também toca. A última linha, com a regra liberada, lembra o que acontece na máquina de quem desenvolve: depois de muito uso do próprio site, o índice de engajamento passa do limite e o som é permitido. Para um visitante novo, isso não vale.
O vídeo de fundo que toca
O exemplo é a capa de uma academia, com o horário das aulas por cima de um vídeo de treino. O vídeo é decoração: a informação está no texto, não nele.
<!doctype html>
<html lang="pt-BR">
<meta charset="utf-8">
<style>
.capa { position: relative; min-height: 60vh; overflow: hidden; }
.capa video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.capa .texto { position: relative; padding: 2rem; color: #fff; background: rgb(0 0 0 / .55); max-width: 32rem; }
.capa button { position: absolute; right: 1rem; bottom: 1rem; }
</style>
<section class="capa">
<video id="video-capa" muted loop playsinline preload="metadata"
poster="capa.jpg" aria-hidden="true">
<source src="fundo.mp4" type="video/mp4">
</video>
<div class="texto">
<h1>Aulas de segunda a sábado, das 6h às 22h</h1>
<p>Primeira aula experimental grátis.</p>
</div>
<button type="button" id="alternar">Pausar vídeo</button>
</section>
<script>
const video = document.getElementById("video-capa");
const botao = document.getElementById("alternar");
const menosMovimento = matchMedia("(prefers-reduced-motion: reduce)");
function mostrar(tocando) {
botao.textContent = tocando ? "Pausar vídeo" : "Tocar vídeo";
}
async function tocar() {
try {
await video.play();
mostrar(true);
} catch (erro) {
// NotAllowedError: o navegador recusou; fica o poster e o botão
mostrar(false);
}
}
botao.addEventListener("click", () => {
if (video.paused) tocar();
else { video.pause(); mostrar(false); }
});
if (menosMovimento.matches) mostrar(false);
else tocar();
</script>
</html>
Os detalhes que fazem diferença:
mutedé o que libera o autoplay. Sem ele, oplay()é recusado como no teste.playsinlinefaz o vídeo tocar dentro da página no iPhone, em vez de abrir em tela cheia (veja o elemento video na MDN).postermostra uma imagem enquanto o vídeo carrega e quando ele não toca, então a capa nunca fica preta.- O
play()pelo script, em vez do atributoautoplay, permite não tocar para quem pediu menos movimento e tratar a recusa. aria-hidden="true"no vídeo, porque ele não tem informação; o texto por cima é o que o leitor de tela lê.
Também vale tirar a faixa de áudio do arquivo, para que ninguém ouça som se alguém remover o muted sem querer:
ffmpeg -i treino.mp4 -an -c:v libx264 -pix_fmt yuv420p -movflags +faststart fundo.mp4
O mesmo teste com Puppeteer, simulando com emulateMediaFeatures as duas preferências de movimento, confirmou o comportamento da capa:
no-preference ao abrir: {"paused":false,"botao":"Pausar vídeo"}
no-preference após clicar: {"paused":true,"botao":"Tocar vídeo"}
reduce ao abrir: {"paused":true,"botao":"Tocar vídeo"}
reduce após clicar: {"paused":false,"botao":"Pausar vídeo"}
Sem preferência, o vídeo toca e o botão pausa. Com prefers-reduced-motion: reduce, a página abre com o vídeo parado no poster e só toca se a pessoa pedir.
Por que o botão de pausa não é opcional
O critério 2.2.2 da WCAG (Pausar, parar, ocultar), de nível A, pede um jeito de pausar qualquer conteúdo em movimento que comece sozinho, dure mais de 5 segundos e apareça junto com outro conteúdo. Um vídeo em loop atrás do título se encaixa nisso. Movimento contínuo atrapalha quem tem dificuldade de atenção e pode causar enjoo em quem tem distúrbios vestibulares; é para essas pessoas que existe a preferência do sistema lida por prefers-reduced-motion. Mais sobre ela em animação que respeita quem pede menos movimento.
Se em algum momento o som for indispensável, o critério 1.4.2 (Controle de áudio) exige que qualquer áudio tocado sozinho por mais de 3 segundos possa ser pausado ou ter o volume controlado à parte. Como o navegador já bloqueia esse caso para visitantes novos, o mais simples é nunca depender dele.
Antes de publicar
Abra a página numa janela anônima, que não tem histórico de interação com o site, e no celular com o modo de economia de bateria ligado. Se o vídeo não carregar em algum navegador, confira o tipo do arquivo servido, como em MIME de vídeo no servidor. O HTML e os scripts de teste deste post rodaram no Chrome 156; em Safari e Firefox o comportamento segue as regras descritas na MDN, mas não foi testado aqui.
Referência: https://msdn.microsoft.com/pt-br/library/hh924820(v=vs.85).aspx