Objetivos de aprendizado
Depois de ler este artigo, você vai conseguir:
- Entender como o Range, o 206 e o Content-Range permitem avançar no vídeo
- Testar com curl se o seu storage ou servidor aceita range requests
- Corrigir os erros comuns: moov no fim do MP4, 200 em vez de 206 e cache
Nesta página
Você sobe um MP4 de 800 MB para o storage, coloca a URL numa tag <video> e toca. Funciona. Então alguém arrasta a barra para o minuto 40 e o vídeo trava, recomeça do zero ou baixa tudo antes de tocar. Quase sempre o problema não é o arquivo: é que o servidor não está respondendo a range requests.
Os detalhes de protocolo abaixo seguem a RFC 9110 e a documentação da MDN.
O que é uma range request?#
É uma requisição HTTP que pede só um pedaço do arquivo, em bytes. O cliente manda o cabeçalho Range, e o servidor devolve apenas aquele trecho:
GET /videos/aula.mp4 HTTP/1.1
Host: exemplo.com
Range: bytes=1048576-2097151HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: video/mp4
Content-Length: 1048576
Content-Range: bytes 1048576-2097151/838860800Os quatro elementos que importam:
Range: bytes=inicio-fimpede do byteinicioaofim, ambos inclusos.bytes=0-1023são os primeiros 1024 bytes.bytes=1000-vai até o fim.bytes=-500pede os últimos 500 bytes.206 Partial Contentindica que o corpo é só uma parte do arquivo.Content-Range: bytes 1048576-2097151/838860800diz qual trecho veio e o tamanho total.Accept-Ranges: bytesanuncia que o servidor aceita ranges. Se o cabeçalho não aparece, o cliente não pode contar com esse suporte.
Se o servidor ignora o Range, ele responde 200 OK com o arquivo inteiro. O navegador aceita, mas perde a capacidade de avançar sem baixar tudo.
Por que vídeo e áudio dependem disso?#
A tag <video> (e <audio>) não baixa o arquivo de uma vez para só depois tocar. O navegador pede o início, descobre a duração e o formato, e toca enquanto baixa. Quando o usuário avança, ele calcula o byte correspondente e dispara uma nova requisição com Range.
O atributo preload controla quanto é baixado antes do play: none não baixa nada, metadata baixa só o necessário (duração e dimensões) e auto permite baixar o arquivo todo. É uma dica, e o navegador pode ignorá-la. Para listas com vários vídeos, preload="metadata" evita baixar dezenas de arquivos que ninguém vai tocar:
<video controls preload="metadata" width="720" src="https://exemplo.com/videos/aula.mp4"></video>Sem suporte a ranges, você tem o cenário do começo: barra de progresso que não responde, início lento e muito mais banda consumida que o necessário.
Como testar se o servidor aceita ranges?#
Peça os primeiros bytes com curl e olhe só os cabeçalhos:
curl -s -o /dev/null -D - -H "Range: bytes=0-99" https://exemplo.com/videos/aula.mp4Uma resposta correta tem este formato:
HTTP/2 206
accept-ranges: bytes
content-length: 100
content-range: bytes 0-99/838860800
content-type: video/mp4Confira três coisas: o status é 206 (não 200), o content-length é 100 e o content-range traz o total. Se vier 200 com o content-length do arquivo inteiro, o servidor ignorou o Range.
A API do S3 também trabalha com ranges: o GetObject aceita o parâmetro Range e devolve só o trecho. Em Python, com boto3:
import os
import boto3
s3 = boto3.client(
"s3",
endpoint_url=os.environ.get("S3_ENDPOINT"), # omita no AWS S3
aws_access_key_id=os.environ["S3_ACCESS_KEY"],
aws_secret_access_key=os.environ["S3_SECRET_KEY"],
)
r = s3.get_object(Bucket="meu-bucket", Key="videos/aula.mp4", Range="bytes=0-99")
print(r["ResponseMetadata"]["HTTPStatusCode"]) # 206
print(r["ContentRange"]) # bytes 0-99/838860800
print(len(r["Body"].read())) # 100Como servir vídeo de um bucket privado?#
Se o arquivo não é público, gere uma URL pré-assinada e use-a no src. Por padrão, a URL pré-assinada do S3 assina só o cabeçalho host, e o Range fica fora da assinatura, então o navegador pode pedir qualquer trecho com a mesma URL:
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "meu-bucket", "Key": "videos/aula.mp4"},
ExpiresIn=6 * 3600,
)Escolha o ExpiresIn pensando na duração da reprodução, não só no carregamento da página: se a URL expira no meio do vídeo, o próximo avanço falha com 403. Gere a URL quando o usuário abrir o player, não no build do site.
Como servir ranges no seu próprio servidor?#
Se o arquivo está no disco da sua aplicação (ou passa por um proxy seu), você precisa implementar o Range você mesmo. Este servidor Node.js não usa dependências e cobre os formatos inicio-fim, inicio- e -sufixo:
import { createReadStream, statSync } from "node:fs";
import { createServer } from "node:http";
const FILE = "video.mp4";
createServer((req, res) => {
const { size } = statSync(FILE);
const base = { "Content-Type": "video/mp4", "Accept-Ranges": "bytes" };
const m = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range ?? "");
if (!m || (m[1] === "" && m[2] === "")) {
res.writeHead(200, { ...base, "Content-Length": size });
return createReadStream(FILE).pipe(res);
}
let start, end;
if (m[1] === "") {
start = Math.max(size - Number(m[2]), 0); // bytes=-500: últimos 500
end = size - 1;
} else {
start = Number(m[1]);
end = m[2] === "" ? size - 1 : Math.min(Number(m[2]), size - 1);
}
if (start >= size || start > end) {
res.writeHead(416, { "Content-Range": `bytes */${size}` });
return res.end();
}
res.writeHead(206, {
...base,
"Content-Range": `bytes ${start}-${end}/${size}`,
"Content-Length": end - start + 1,
});
createReadStream(FILE, { start, end }).pipe(res);
}).listen(3000);Teste com o curl da seção anterior apontando para http://localhost:3000. O exemplo ignora ranges múltiplos (bytes=0-50, 100-150) e responde o arquivo inteiro, o que a especificação permite. Em produção, prefira servir o arquivo pelo storage ou por um servidor web que já implemente isso, em vez de manter este código.
Quais erros evitar?#
moovno fim do MP4. Num MP4, o índice do vídeo fica no átomomoov. Se ele está no fim, o navegador precisa buscá-lo antes de tocar. Mova-o para o início sem recodificar:ffmpeg -i entrada.mp4 -c copy -movflags +faststart saida.mp4. A documentação do ffmpeg avisa que esse passo faz uma segunda leitura e não funciona com saída fragmentada.- Receber
200em vez de206. Alguma camada no caminho (proxy, middleware de compressão, função serverless) está descartando oRange. Teste comcurldireto no storage e depois pela URL final para achar onde se perde. Content-Typeerrado. Um MP4 servido comoapplication/octet-streampode ser baixado em vez de tocado. Definavideo/mp4,video/webmouaudio/mpegno objeto.- Compressão em vídeo. Não aplique gzip ou brotli em MP4: o arquivo já é comprimido, o ganho é mínimo e uma camada de compressão no caminho pode desativar ou quebrar os ranges.
- URL pré-assinada curta demais. Expira no meio da reprodução e o avanço falha.
- Arquivo trocado com a mesma URL. Se o objeto muda enquanto o navegador usa trechos em cache, as partes não se encaixam. Use um nome novo por versão (por exemplo
aula-v2.mp4) em vez de sobrescrever. - Banda sem controle. Cada avanço gera mais requisições. Em storages que cobram saída, vídeo popular vira custo; veja taxas de egress antes de escolher onde hospedar.
Quando usar HLS ou DASH em vez de um MP4?#
Um MP4 com ranges resolve vídeos curtos e médios com uma qualidade só. Se você precisa trocar de resolução conforme a conexão do usuário, ou transmitir ao vivo, o caminho é HLS ou DASH: o vídeo é dividido em segmentos pequenos e uma playlist indica quais baixar. Os segmentos são arquivos comuns, e o object storage serve bem esse papel. Para a maioria dos sites, começar com MP4 e faststart basta.
Como fazer isso na Square Cloud#
O Blob Storage da Square Cloud é um object storage com API compatível com S3, e os downloads respondem a range requests com 206, então o player avança no vídeo sem baixar o arquivo inteiro. Arquivos públicos são servidos por uma CDN global na edge, e cada objeto aceita configurações como Cache-Control e a escolha entre download e exibição inline (útil quando alguém abre a URL do vídeo direto no navegador).
Para arquivos privados, o código boto3 acima funciona com o endpoint https://s3-blob.squarecloud.app, a região auto, o bucket private e as chaves da aba "Acesso S3" do painel: a URL pré-assinada SigV4 pode valer até 7 dias. Pelo painel, você também tem o link temporário de até 24 horas e o link de compartilhamento de até 30 dias, que pode ser revogado. Os arquivos podem ter até 10 GB, o download não tem cobrança de banda, e todo plano inclui uma cota de 5 GB a 250 GB.
Antes de publicar, rode o teste curl -H "Range: bytes=0-99" na URL do seu objeto e confirme o 206 e o Accept-Ranges. É o jeito mais rápido de validar o comportamento com o seu próprio arquivo.
Blob Storage
Object storage compatível com S3, sem taxa de saída
Perguntas frequentes
Preciso de um servidor de streaming para tocar vídeo no site?
Para um arquivo MP4 ou WebM comum, não. A tag video toca o arquivo por HTTP, desde que o servidor ou o storage responda a range requests. Streaming adaptativo (HLS ou DASH) só é necessário se você quer trocar de qualidade conforme a rede.
O que significa o erro 416 Range Not Satisfiable?
O servidor recebeu um Range que não cabe no arquivo, por exemplo um byte inicial maior que o tamanho do objeto. Ele responde 416 com Content-Range: bytes */tamanho. Em geral indica um arquivo que mudou ou um cálculo de offset errado no cliente.
Por que o vídeo só começa depois de baixar quase tudo?
Num MP4, os metadados ficam no átomo moov. Se ele está no fim do arquivo, o navegador precisa buscá-lo antes de tocar. Reprocesse com ffmpeg -movflags +faststart para movê-lo para o início, sem recodificar.
Funciona com URL pré-assinada?
Sim. O Range é um cabeçalho da requisição e não entra na assinatura de uma URL pré-assinada do S3. O navegador pode enviar Range normalmente, e a URL só precisa durar o tempo da reprodução.
Mais em Storage
Ver todos os 15 artigos- O que é uma URL assinada (pre-signed URL) e quando usarUma URL assinada (pre-signed URL) é um link temporário para um arquivo privado. Veja como funciona, o que ela não faz e quando usar.
- O que é armazenamento de objetos (object storage)Armazenamento de objetos guarda arquivos como objetos com chave e metadados, acessados por API HTTP. Veja como funciona, para que serve e seus limites.
- O que é S3 e armazenamento compatível com S3S3 é o armazenamento de objetos da Amazon e virou a API padrão do setor. Veja o que significa ser compatível com S3, o que muda e como conectar via boto3.
- Upload de arquivos grandes: multipart e upload retomávelO upload multipart divide um arquivo grande em partes enviadas em paralelo e reenviadas uma a uma. Veja o passo a passo em Python, como retomar e os erros comuns.