A cota de armazenamento em buffer foi excedida

Joe Medley
Joe Medley

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.

Erro no console de cota.

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.

Janela de observação de cota.

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ção abort() 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.