Se você estiver trabalhando com extensões de origem de mídia (MSE, na sigla em inglês), uma coisa que
você terá que lidar é um buffer cheio. Quando isso ocorrer, você
receberá o que é chamado de QuotaExceededError
. Neste artigo, abordarei algumas
das maneiras de lidar com isso.
O que é o QuotaExceededError?
Basicamente, QuotaExceededError
é o que você recebe se tentar adicionar muitos dados
ao objeto SourceBuffer
. Adicionar mais objetos SourceBuffer
a um elemento
MediaSource
pai também pode gerar esse erro. Isso está fora do escopo deste
artigo.) Se o SourceBuffer
tiver muitos dados, a chamada
SourceBuffer.appendBuffer()
vai acionar a seguinte mensagem na janela do console
do Chrome.
Há alguns pontos a serem observados sobre isso. Primeiro, observe que o nome
QuotaExceededError
não aparece em lugar nenhum da mensagem. Para isso, defina um
ponto de interrupção em um local em que você possa capturar o erro e examiná-lo na
janela de observação ou de escopo. Mostrei isso abaixo.
Em segundo lugar, não há uma maneira definitiva de descobrir quantos dados o SourceBuffer
pode processar.
Comportamento em outros navegadores
No momento em que este artigo foi escrito, o Safari não gerava uma QuotaExceededError
em muitos
dos builds. Em vez disso, ele remove frames usando um algoritmo de duas etapas, parando se
houver espaço suficiente para processar o appendBuffer()
. Primeiro, ele libera frames de
entre 0 e 30 segundos antes do tempo atual em blocos de 30 segundos. Em seguida, ele
libera frames em blocos de 30 segundos da duração para trás, até chegar a 30
segundos após currentTime
. Leia mais sobre isso em uma mudança de conjunto do WebKit de 2014.
Felizmente, o Chrome, o Edge e o Firefox geram esse erro. Se você estiver usando outro navegador, vai precisar fazer seus próprios testes. Embora provavelmente não seja o que você criaria para um player de mídia real, o teste de limite de buffer de origem de François Beaufort permite que você observe o comportamento.
Quantos dados posso anexar?
O número exato varia de acordo com o navegador. Como não é possível consultar o valor de dados anexados no momento, você vai precisar acompanhar quanto está anexando. Quanto ao que observar, aqui estão os melhores dados que posso coletar no momento da escrita. Para o Chrome, esses números são limites máximos, o que significa que podem ser menores quando o sistema tem pressão sobre a memória.
Chrome | Chromecast* | Firefox | Safari | Edge | |
---|---|---|---|---|---|
Vídeo | 150MB | 30MB | 100 MB | 290MB | Desconhecido |
Áudio | 12MB | 2MB | 15 MB | 14MB | Desconhecido |
- Ou outro dispositivo Chrome com memória limitada.
O que devo fazer?
Como a quantidade de dados compatíveis varia muito e você não consegue encontrar a
quantidade de dados em um SourceBuffer
, é necessário recebê-los indiretamente processando o
QuotaExceededError
. Agora vamos ver algumas maneiras de fazer isso.
Há várias abordagens para lidar com QuotaExceededError
. Na realidade, a
combinação de uma ou mais abordagens é a melhor. Sua abordagem precisa ser baseada
no quanto você está buscando e tentando anexar além de
HTMLMediaElement.currentTime
e ajustar esse tamanho com base no
QuotaExceededError
. Além disso, usar um manifesto de algum tipo, como um arquivo mpd
(MPEG-DASH) ou um arquivo m3u8
(HLS), pode ajudar a acompanhar os dados que você está anexando ao buffer.
Agora, vamos analisar várias abordagens para lidar com o
QuotaExceededError
.
- Remova os dados desnecessários e adicione novamente.
- Anexe fragmentos menores.
- Diminuir a resolução de reprodução.
Embora eles possam ser usados em conjunto, vou falar sobre eles um por vez.
Remova os dados desnecessários e anexe novamente
Essa regra deve ser chamada de "Remover os dados menos prováveis de serem usados em breve" e, em seguida, repetir a anexação dos dados que provavelmente serão usados em breve". O título é muito longo. Você só precisa se lembrar do que eu realmente quero dizer.
Remover dados recentes não é tão simples quanto chamar SourceBuffer.remove()
. Para
remover dados do SourceBuffer
, a flag de atualização dele precisa ser falsa. Se não for, chame SourceBuffer.abort()
antes de remover dados.
É preciso considerar alguns pontos ao chamar SourceBuffer.remove()
.
- Isso pode ter um impacto negativo na reprodução. Por exemplo, se você quiser que o vídeo seja reproduzido novamente ou em loop em breve, não remova o início do vídeo. Da mesma forma, se você ou o usuário procurar uma parte do vídeo em que os dados foram removidos, será necessário anexar esses dados novamente para satisfazer a pesquisa.
- Remova o máximo possível. Tenha cuidado ao remover o grupo
de frames em exibição no momento, começando no frame-chave em
currentTime
ou antes dele, porque isso pode causar a interrupção da reprodução. Essas informações podem precisar ser analisadas fora do bytestream pelo app da Web se não estiverem disponíveis no manifesto. Um manifesto de mídia ou o conhecimento do app sobre intervalos de keyframe na mídia pode ajudar a orientar a escolha de intervalos de remoção do app para evitar a remoção da mídia em reprodução. Não remova o grupo de fotos que está sendo reproduzido ou mesmo as primeiras fotos após ele. Em geral, não remova após o horário atual, a menos que você tenha certeza de que a mídia não é mais necessária. Remover perto do marcador pode causar uma paralisação. - O Safari 9 e o Safari 10 não implementam
SourceBuffer.abort()
corretamente. Na verdade, eles geram erros que interrompem a reprodução. Felizmente, há rastreadores de bugs abertos aqui e aqui. Enquanto isso, você terá que contornar isso de alguma forma. O Shaka Player faz isso substituindo uma funçãoabort()
vazia nessas versões do Safari.
Anexar fragmentos menores
Mostrei o procedimento abaixo. Isso pode não funcionar em todos os casos, mas tem a vantagem de que o tamanho dos pedaços menores pode ser ajustado para atender às suas necessidades. Além disso, não é necessário voltar à rede, o que pode gerar custos de dados adicionais para alguns usuários.
const pieces = new Uint8Array([data]);
(function appendFragments(pieces) {
if (sourceBuffer.updating) {
return;
}
pieces.forEach(piece => {
try {
sourceBuffer.appendBuffer(piece);
}
catch e {
if (e.name !== 'QuotaExceededError') {
throw e;
}
// Reduction schedule: 80%, 60%, 40%, 20%, 16%, 12%, 8%, 4%, fail.
const reduction = pieces[0].byteLength * 0.8;
if (reduction / data.byteLength < 0.04) {
throw new Error('MediaSource threw QuotaExceededError too many times');
}
const newPieces = [
pieces[0].slice(0, reduction),
pieces[0].slice(reduction, pieces[0].byteLength)
];
pieces.splice(0, 1, newPieces[0], newPieces[1]);
appendBuffer(pieces);
}
});
})(pieces);
Diminuir a resolução de reprodução
Isso é semelhante à remoção de dados recentes e adição novamente. Na verdade, as duas podem ser feitas juntas, embora o exemplo abaixo mostre apenas a redução da resolução.
Lembre-se de alguns pontos ao usar essa técnica:
- É preciso anexar um novo segmento de inicialização. Faça isso sempre que alterar as representações. O novo segmento de inicialização precisa ser para os segmentos de mídia seguintes.
- O carimbo de data/hora da apresentação da mídia anexada precisa corresponder ao carimbo de data/hora dos dados no buffer o mais próximo possível, mas não pular para frente. A sobreposição dos dados em buffer pode causar falhas ou interrupções rápidas, dependendo do navegador. Não importa o que você anexar, não sobreponha o indicador de reprodução, porque isso gera erros.
- A busca pode interromper a reprodução. Você pode querer procurar um local específico e retomar a reprodução a partir dele. Esteja ciente de que isso causará a interrupção da reprodução até que a busca seja concluída.