Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Vídeo e áudio em HTML: tag video, controls e autoplay

Como colocar vídeo e áudio na página com controles nativos, legenda e poster — e por que o autoplay com som é bloqueado por todo navegador.

Rodolfo Mori14 min de leitura

Para colocar um vídeo numa página, você escreve <video> com o atributo controls e o navegador desenha o player inteiro — play, barra de progresso, volume, tela cheia — sem uma linha de CSS ou de JavaScript. Para som, é <audio>, com os mesmos atributos. O trabalho de verdade começa depois: qual formato entregar, quantos bytes a página gasta antes de alguém dar play, e por que o autoplay com som simplesmente não acontece.

Os exemplos são todos da Escola Clave, uma escola de música online. A aula 1 é um vídeo de 60 segundos sobre o acorde de lá maior; o exercício de ouvido é um áudio de 30 segundos. Os arquivos foram gerados com ffmpeg, servidos por um servidor Node local e abertos no Chrome 151 — todo número desta página saiu dessa medição.

A tag entrega o aparelho; a fonte entrega a mídia

Quando você compra uma televisão, os botões de volume, play e tela cheia fazem parte do aparelho; o filme é outro item. <video> e <audio> são os aparelhos de mídia do navegador. O atributo controls pede a interface pronta, enquanto src ou <source> informa qual arquivo será reproduzido.

O nome técnico é elemento de mídia. Ele não garante que qualquer formato funcione: o navegador precisa entender o contêiner e o codec, e a rede precisa entregar os bytes de forma adequada. Antes de estilizar o player, abra a aba Network, recarregue com preload="metadata" e observe quanto foi baixado sem dar play. Depois compare com preload="auto". Essa diferença visível mostra que uma única palavra no HTML também é uma decisão de desempenho.

O player que vem pronto com controls

O elemento mínimo que funciona tem duas linhas:

html
<video controls>
  <source src="/aulas/aula-01-acordes.mp4" type="video/mp4">
</video>

controls é um atributo booleano: ele não recebe valor, a presença já liga a barra de controles. Sem ele, o vídeo aparece na página sem nenhum botão para tocar — o que só faz sentido em vídeo de fundo, com autoplay e loop. Se atributo booleano ainda soa estranho, a lição sobre tags e atributos HTML mostra a anatomia completa de um elemento.

O primeiro susto costuma ser o tamanho. Coloquei quatro elementos na mesma página, todos sem CSS:

html
<video id="a" controls></video>

<video id="b" controls preload="metadata">
  <source src="/aulas/aula-01-acordes.mp4" type="video/mp4">
</video>

<video id="c" controls preload="none" poster="/aulas/aula-01-capa.jpg">
  <source src="/aulas/aula-01-acordes.mp4" type="video/mp4">
</video>

<audio id="d" controls></audio>

E medi a caixa de cada um depois que a página terminou de carregar:

js
for (const id of ['a', 'b', 'c', 'd']) {
  const caixa = document.getElementById(id).getBoundingClientRect();
  console.log(id, `${caixa.width} x ${caixa.height}`);
}
a 300 x 150 b 1280 x 720 c 1280 x 720 d 300 x 54

O <video> vazio nasce com 300 × 150 — é o tamanho padrão que a especificação manda o navegador usar quando ainda não sabe as dimensões do arquivo. Assim que os metadados chegam, ele salta para o tamanho real do vídeo, 1280 × 720. Esse salto é o famoso “a página pulou”: o texto que estava abaixo desce de repente.

Repare no elemento c: ele tem preload="none", então nenhum byte de vídeo foi lido, e mesmo assim já mediu 1280 × 720. Quem deu o tamanho foi o poster, uma imagem comum de 1280 × 720 — o navegador usa a dimensão dela enquanto não conhece a do vídeo. O detalhe é que isso vale a partir do momento em que a capa chega: até lá, o elemento ainda é 300 × 150. Por isso a forma definitiva de matar o salto é a de baixo.

Dois arquivos, um elemento: quem escolhe a fonte

Dentro de <video> cabem vários <source>. O navegador não baixa todos: ele testa um por um, na ordem em que aparecem, e para no primeiro que sabe tocar.

html
<video controls preload="metadata" width="1280" height="720"
       poster="/aulas/aula-01-capa.jpg">
  <source src="/aulas/aula-01-acordes.webm" type="video/webm">
  <source src="/aulas/aula-01-acordes.mp4" type="video/mp4">
  <track kind="subtitles" src="/aulas/aula-01-acordes.pt.vtt"
         srclang="pt-BR" label="Português" default>
  Seu navegador não toca vídeo.
  <a href="/aulas/aula-01-acordes.mp4">Baixe a aula</a>.
</video>

Abri essa página no Chrome, li o currentSrc do elemento e, do lado do servidor, listei tudo que o navegador chegou a pedir:

js
const v = document.querySelector('video');
console.log('currentSrc  =', v.currentSrc.replace(location.origin, ''));
console.log('videoWidth  =', v.videoWidth);
console.log('videoHeight =', v.videoHeight);
console.log('duration    =', v.duration);
currentSrc = /aulas/aula-01-acordes.webm videoWidth = 1280 videoHeight = 720 duration = 60.008

arquivos que o navegador chegou a pedir: /fontes.html /aulas/aula-01-capa.jpg /aulas/aula-01-acordes.pt.vtt /aulas/aula-01-acordes.webm

O MP4 nunca foi pedido. O Chrome parou no WebM porque ele veio primeiro e o type batia com algo que ele sabe decodificar. Ou seja: a ordem dos <source> é a sua ordem de preferência, e o arquivo de baixo é só o plano B.

1º <source> type="video/webm" 2º <source> type="video/mp4" texto dentro do <video> carrega esse arquivo e ignora todo o resto não sei tocar não sei tocar sei tocar

Quem decide o “sei tocar” é o método canPlayType, e ele responde três coisas diferentes: "probably", "maybe" ou string vazia.

js
const v = document.createElement('video');
const tipos = [
  'video/mp4',
  'video/mp4; codecs="avc1.64001F, mp4a.40.2"',
  'video/webm; codecs="vp09.00.31.08, opus"',
  'video/ogg; codecs="theora"',
  'video/mpeg',
  'audio/mpeg',
  'audio/ogg; codecs="opus"',
  'audio/wav',
];
for (const t of tipos) console.log(t.padEnd(42), '->', JSON.stringify(v.canPlayType(t)));
video/mp4 -> "maybe" video/mp4; codecs="avc1.64001F, mp4a.40.2" -> "probably" video/webm; codecs="vp09.00.31.08, opus" -> "probably" video/ogg; codecs="theora" -> "" video/mpeg -> "" audio/mpeg -> "probably" audio/ogg; codecs="opus" -> "probably" audio/wav -> "maybe"

video/mp4 sozinho dá "maybe" porque MP4 é só a embalagem: o que está dentro pode ser H.264, HEVC, AV1. Quando você declara os codecs, a resposta vira "probably" — o navegador conseguiu conferir. String vazia é “não sei tocar”, e foi o que o Theora e o video/mpeg receberam. Repare no segundo: video/mpeg existe, só que é o MPEG-1/MPEG-2 dos anos 90, não o MP4 — e o Chrome não decodifica. Guarde esse detalhe, porque ele volta como bug lá embaixo.

O segundo arquivo não é uma gravação nova: o WebM sai do próprio MP4, num comando só.

bash
ffmpeg -i aula-01-acordes.mp4 -c:v libvpx-vp9 -b:v 2200k -row-mt 1 -speed 4 \
  -c:a libopus -b:a 128k aula-01-acordes.webm

ls -l aula-01-acordes.mp4 aula-01-acordes.webm
-rw-r--r--@ 1 rodolfomori wheel 17533382 Aug 22 17:18 aula-01-acordes.mp4 -rw-r--r--@ 1 rodolfomori wheel 13778636 Aug 22 17:18 aula-01-acordes.webm

Mesma duração, mesma resolução, mesmo bitrate pedido: o WebM saiu com 13,7 MB contra 17,5 MB do MP4, 21% menor. É essa diferença que paga o trabalho de manter dois arquivos — e é por isso que o WebM vem primeiro na lista.

formato codec de vídeo onde toca quando escolher
MP4 H.264 + AAC todos os navegadores atuais, TVs, apps se você só puder ter um arquivo, é este
WebM VP9 + Opus Chrome, Firefox e Edge há anos; no Safari, tarde e de forma irregular primeiro <source>, para economizar banda
MP4 AV1 navegadores recentes, com suporte irregular catálogo grande, onde 30% de banda pesa

poster, preload e os bytes que a página gasta sem ninguém dar play

preload é o atributo que decide quanto o navegador baixa antes de qualquer clique. Ele aceita três valores, e a diferença entre eles não é teoria: dá para medir.

html
<video controls width="640" preload="metadata"
       poster="/aulas/aula-01-capa.jpg">
  <source src="/aulas/aula-01-acordes.mp4" type="video/mp4">
</video>

Subi um servidor Node que conta o que sai de fato pelo socket — não o que foi prometido no Content-Length, porque o navegador aborta a conexão assim que tem o que queria:

js
import { createReadStream } from 'node:fs';

const PEDACO = 32 * 1024;

// entrega o arquivo em pedaços, com pausa entre eles, e devolve o que saiu mesmo
async function entregar(res, arquivo, inicio, fim, taxa) {
  const espera = (PEDACO / taxa) * 1000;
  const fluxo = createReadStream(arquivo, { start: inicio, end: fim, highWaterMark: PEDACO });
  let entregue = 0;
  for await (const pedaco of fluxo) {
    if (res.destroyed) break;   // o navegador desistiu: o resto nunca sai do disco
    res.write(pedaco);
    entregue += pedaco.length;  // só conta o que passou pelo socket
    await new Promise((r) => setTimeout(r, espera));
  }
  res.end();
  return entregue;
}

Sem limitar a banda, o teste não diz nada: em localhost o arquivo inteiro chega antes de o Chrome ter tempo de desistir. Com a banda travada em 500 KB/s e a página aberta sem ninguém tocar em nada, o mesmo MP4 de 17.533.382 bytes consome isto:

preload | bytes do MP4 | % do arquivo | duration -----------|-----------------|--------------|--------- none | 0 | 0.0% | NaN metadata | 196608 | 1.1% | 60 auto | 7700480 | 43.9% | 60

Três leituras saem daí:

  • none não baixa nada. Nem os metadados: duration fica NaN e o player não sabe nem quanto o vídeo dura.
  • metadata custou 196.608 bytes, 1,1% do arquivo — o suficiente para ler duração, resolução e desenhar a barra de progresso no tamanho certo.
  • auto puxou 7,7 MB e só parou quando encheu o buffer do Chrome. Repeti a medição três vezes e o número mal se mexeu (7.700.480, 7.700.480 e 7.733.248 bytes): não é ruído, é o tamanho de buffer que o Chrome persegue. Numa página com quatro aulas listadas, isso é o download de um vídeo inteiro antes de a pessoa escolher qual quer ver.

O padrão que funciona para uma lista de aulas: preload="none" mais um poster leve. A página fica com a capa certa, no tamanho certo, gastando 48 KB em vez de 7,7 MB.

Autoplay: o navegador recusa o play() com som, e diz por quê

Escreva autoplay sem mais nada, espere o evento canplay e leia paused um segundo e meio depois:

html
<video id="aula" autoplay controls width="640">
  <source src="/aulas/aula-01-acordes.mp4" type="video/mp4">
</video>
paused=true currentTime=0

O vídeo carregou, ficou parado no primeiro quadro e não avisou nada. Nenhum erro, nenhum aviso no console. É a falha mais silenciosa desta lição.

Para ver o motivo, é preciso chamar play() na mão e ler a promise que ele devolve. Este script está dentro da própria página, sem nenhum clique antes:

js
const v = document.getElementById('aula');
v.addEventListener('canplay', async () => {
  try {
    await v.play();
    console.log('tocou: paused=' + v.paused + ' muted=' + v.muted);
  } catch (e) {
    console.log(e.name + ': ' + e.message);
  }
}, { once: true });
NotAllowedError: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD

Essa é a regra, escrita pelo próprio navegador: som só começa depois que a pessoa interagiu com a página. Não é bug, não é permissão que se peça, não tem como desligar no site publicado. Todo navegador moderno faz igual, porque a alternativa era a web inteira gritando sozinha.

O que passa é mídia muda. Adicione muted e o mesmo código funciona:

html
<video id="aula" autoplay muted playsinline controls width="640">
  <source src="/aulas/aula-01-acordes.mp4" type="video/mp4">
</video>
tocou: paused=false muted=true

playsinline entra junto por causa do iPhone: sem ele, o iOS abre o vídeo em tela cheia quando o play começa, e o seu vídeo decorativo de cabeçalho vira um player ocupando o celular inteiro.

Legenda com track e um arquivo .vtt

Legenda não é imagem queimada no vídeo: é um arquivo de texto separado, no formato WebVTT. Ele começa com a palavra WEBVTT e uma linha em branco, e depois é só marcação de tempo:

text
WEBVTT

00:00:00.000 --> 00:00:04.000
Bem-vindo à aula 1 da Escola Clave.

00:00:04.000 --> 00:00:09.500
Hoje a gente monta o acorde de lá maior no violão.

00:00:09.500 --> 00:00:15.000
Dedo indicador na segunda casa da corda ré.

Você conecta esse arquivo com um <track> dentro do <video>:

html
<track kind="subtitles" src="/aulas/aula-01-acordes.pt.vtt"
       srclang="pt-BR" label="Português" default>

label é o nome que aparece no menu de legendas do player, srclang é o idioma e default liga a faixa sem ninguém pedir. Para conferir que carregou mesmo, o navegador expõe a faixa em video.textTracks:

js
const faixa = document.querySelector('video').textTracks[0];
console.log('label   =', faixa.label);
console.log('idioma  =', faixa.language);
console.log('mode    =', faixa.mode);
console.log('legendas =', faixa.cues.length);
for (const c of faixa.cues) console.log(` ${c.startTime}s -> ${c.endTime}s "${c.text}"`);
label = Português idioma = pt-BR mode = showing legendas = 3 0s -> 4s "Bem-vindo à aula 1 da Escola Clave." 4s -> 9.5s "Hoje a gente monta o acorde de lá maior no violão." 9.5s -> 15s "Dedo indicador na segunda casa da corda ré."

mode valendo showing confirma que o default pegou. Se vier disabled, o arquivo não foi encontrado ou está sendo servido com o tipo errado — o .vtt precisa sair do servidor como text/vtt.

A legenda exige planejamento e tem utilidade ampla: serve para quem não ouve, para quem está no ônibus sem fone e para quem está estudando em outro idioma. O resto do assunto está na lição de acessibilidade em HTML.

<audio>: o mesmo elemento, sem imagem

O exercício de ouvido da Escola Clave é um acorde de 30 segundos. O markup é o mesmo jogo, trocando a tag:

html
<audio id="exercicio" controls preload="metadata">
  <source src="/aulas/acorde-la-maior.ogg" type="audio/ogg; codecs=opus">
  <source src="/aulas/acorde-la-maior.mp3" type="audio/mpeg">
  <a href="/aulas/acorde-la-maior.mp3">Baixar o acorde de lá maior</a>
</audio>
js
const a = document.getElementById('exercicio');
console.log('currentSrc =', a.currentSrc.replace(location.origin, ''));
console.log('duration   =', a.duration);
console.log('paused     =', a.paused);
currentSrc = /aulas/acorde-la-maior.ogg duration = 30.0065 paused = true

ogg no disco: 437237 bytes baixado com preload=“metadata”: 109557 bytes

Duas coisas mudam em relação ao vídeo. A primeira é o tamanho: <audio controls> nasce com 300 × 54 e não cresce sozinho — não existe conteúdo visual para dar dimensão, então largura é decisão sua no CSS. A segunda é que a mesma regra de autoplay vale aqui, e sem imagem ela machuca mais: um <audio autoplay> com som não toca, e como não há player para olhar, ninguém descobre o motivo.

Repare que preload="metadata" num arquivo de 437 KB baixou 109.557 bytes — um quarto do arquivo. Rodei a mesma medição do vídeo nos três valores, e o áudio se comporta diferente:

none 0 bytes metadata 109557 bytes auto 437237 bytes

auto puxa o arquivo inteiro, e não uma fatia: 30 segundos de áudio cabem no buffer com folga. Ou seja, em áudio curto auto custa quatro vezes mais que metadata — mas são 300 KB de diferença, não os 7,5 MB do vídeo. É por isso que auto num áudio de aula é um pecado pequeno, e num vídeo de aula não é.

O bug que não aparece no console: type errado

Este é o erro que mais consome tempo, porque o navegador não reclama. Um type errado no <source>:

html
<video id="v" controls width="640">
  <source id="s" src="/aulas/aula-01-acordes.mp4" type="video/mpeg">
</video>

O arquivo existe, o caminho está certo, a extensão está certa. Só o type está errado: MP4 é video/mp4, e video/mpeg é aquele MPEG-1/2 que o canPlayType respondeu com string vazia lá em cima. Escutei os dois eventos de erro possíveis e inspecionei o elemento depois de quatro segundos:

js
const v = document.getElementById('v');
document.getElementById('s').addEventListener('error', () => console.log('error no <source>'));
v.addEventListener('error', () => console.log('error no <video>'));
setTimeout(() => {
  console.log('video.error   =', v.error);
  console.log('networkState  =', v.networkState, '(NETWORK_NO_SOURCE =', v.NETWORK_NO_SOURCE + ')');
  console.log('currentSrc    =', JSON.stringify(v.currentSrc));
}, 4000);
error no <source> video.error = null networkState = 3 (NETWORK_NO_SOURCE = 3) currentSrc = ""

Leia com atenção o que não aconteceu. O evento error disparou no <source>, não no <video>. video.error continua null — o elemento não considera que houve erro, ele considera que não havia fonte nenhuma. E o servidor confirmou o resto: o arquivo MP4 nunca chegou a ser pedido. O type reprovou a fonte antes de qualquer download.

Por isso a lição prática é dupla: escreva o type certo ou não escreva nenhum (sem type, o navegador baixa o começo do arquivo e descobre sozinho), e prenda o listener de erro no <source>, não só no <video>.

Quando o problema é o caminho, e não o tipo, o diagnóstico muda de cara:

html
<video id="v" controls width="640" src="/aulas/aula-02-pestana.mp4"></video>
js
const v = document.getElementById('v');
v.addEventListener('error', () => {
  console.log('code    =', v.error.code);
  console.log('message =', v.error.message);
});
[error] Failed to load resource: the server responded with a status of 404 (Not Found) code = 4 message = MEDIA_ELEMENT_ERROR: Format error

Aqui o error disparou no <video>, video.error.code vale 4 (MEDIA_ERR_SRC_NOT_SUPPORTED) e o console traz o Failed to load resource: 404 junto. Console vazio aponta para type; console com 404 aponta para caminho.

O servidor precisa entender Range, senão a barra não anda

Player de vídeo não baixa o arquivo do começo ao fim: ele pede pedaços. Quem dá o comando é o cabeçalho Range, e o servidor tem que responder 206 Partial Content:

bash
curl -i -H "Range: bytes=0-99" \
  http://localhost:4180/aulas/aula-01-acordes.mp4 -o /dev/null -D -
HTTP/1.1 206 Partial Content Content-Type: video/mp4 Accept-Ranges: bytes Content-Range: bytes 0-99/17533382 Content-Length: 100 Date: Sat, 22 Aug 2026 20:33:36 GMT Connection: keep-alive Keep-Alive: timeout=5

Com esse servidor, arrastar a barra funciona. Numa página com o vídeo inteiro num src direto:

html
<video id="aula" controls width="640" src="/aulas/aula-01-acordes.mp4"></video>

coloquei currentTime = 45 assim que os metadados chegaram e olhei os pedidos que bateram no servidor:

js
const v = document.getElementById('aula');
v.addEventListener('loadedmetadata', () => {
  v.currentTime = 45;
  setTimeout(() => console.log('currentTime =', v.currentTime), 3000);
}, { once: true });
currentTime = 45

Range pedidos ao servidor: bytes=0- bytes=12156928-

O segundo pedido pula direto para o byte 12.156.928, que é mais ou menos onde mora o segundo 45. Agora a mesma página, com o mesmo salto para 45, contra um servidor ingênuo — um que ignora o cabeçalho Range e sempre devolve o arquivo inteiro com 200 OK:

js
import { createServer } from 'node:http';
import { createReadStream, statSync } from 'node:fs';

const MP4 = './publico/aulas/aula-01-acordes.mp4';

createServer((req, res) => {
  if (req.url !== '/video.mp4') return res.writeHead(404).end();
  const info = statSync(MP4);
  console.log(`Range: ${req.headers.range ?? '(nenhum)'} -> 200 OK, ${info.size} bytes`);
  res.writeHead(200, { 'Content-Type': 'video/mp4', 'Content-Length': info.size });
  createReadStream(MP4).pipe(res);
}).listen(4191);
Range: bytes=0- -> 200 OK, 17533382 bytes duration = 60 currentTime depois do seek = 0

O vídeo toca do começo, a duração é lida — e a barra não anda. currentTime voltou para 0 porque o navegador pediu para saltar e recebeu o arquivo do início. Se você está servindo mídia com um servidor escrito à mão, é aqui que o “meu vídeo não avança” nasce.

Quando não vale hospedar o vídeo você mesmo

Um vídeo de 60 segundos em 720p deu 17,5 MB. Uma aula real de 20 minutos passa de 300 MB, e cada pessoa que assiste baixa isso do seu servidor, na resolução única que você gerou, sem se adaptar à conexão dela.

situação o que usar por quê
vídeo curto de fundo, sem som <video muted autoplay loop playsinline> é decoração; hospedar você mesmo é simples e barato
um punhado de vídeos curtos, públicos <video> com MP4 no seu servidor não vale a complexidade de mais nada
catálogo de aulas, várias resoluções player externo por <iframe> quem paga banda e transcodificação é o outro
aula paga, com controle de acesso streaming com URL assinada <video> sozinho não protege arquivo nenhum

Para catálogo, a conta quase sempre fecha do lado do serviço externo: ele gera as versões em 360p, 720p e 1080p, troca de resolução no meio da reprodução e paga a banda. A troca é o <video> virar um iframe com o player do YouTube ou do Vimeo — e você perde o controle sobre a aparência dos controles.

O que vem depois

O próximo passo natural da trilha de HTML é justamente o <iframe>, que incorpora player, mapa e formulário de fora do seu domínio com uma tag só. Se quiser ver onde mídia se encaixa no resto da linguagem antes de seguir, o guia completo de HTML mostra a ordem de estudo inteira, do doctype ao Open Graph.

  • html
  • video
  • audio
  • autoplay
  • legendas

Perguntas frequentes

Preciso mesmo gerar o vídeo em MP4 e em WebM?
Hoje, quase nunca. MP4 com H.264 e AAC toca em todo navegador atual, e é o formato que resolve sozinho. O WebM entra quando você quer economizar banda, porque o VP9 ou o AV1 entregam a mesma qualidade em menos bytes — e nesse caso ele vem como primeiro source, com o MP4 logo abaixo.
Dá para tirar o botão de download do player?
Dá para esconder com o atributo controlslist="nodownload", e o Chrome obedece. Não confunda com proteção: o arquivo continua na aba de rede do navegador. Se o conteúdo é pago, ele precisa de URL assinada e expirável no servidor, não de um atributo no HTML.
Por que o meu vídeo pula para o começo quando arrasto a barra?
Porque o servidor não responde a requisição parcial. O navegador pede Range e espera um 206 Partial Content; se vier 200 com o arquivo inteiro, ele não consegue saltar e o currentTime volta para zero.
Como eu tiro o poster do próprio vídeo?
Com um comando de ffmpeg: ffmpeg -ss 3 -i aula.mp4 -frames:v 1 -q:v 2 aula-capa.jpg congela o quadro do terceiro segundo e salva em JPG do tamanho exato do vídeo — no arquivo desta lição, 1280 × 720 em 67 KB. Evite o segundo 0: costuma ser quadro preto ou vinheta.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Chrome 151.0.7922.170 e Node 24.16.0 no macOS (Apple Silicon), e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — O elemento <video> — developer.mozilla.org
  2. MDN — Guia de autoplay para mídia e Web Audio — developer.mozilla.org
  3. Chrome for Developers — Autoplay policy in Chrome — developer.chrome.com
  4. W3C — WebVTT: The Web Video Text Tracks Format — w3.org

Continue por aqui