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ê precisará lidar é um buffer cheio. Quando isso ocorre, você recebe o que chamamos de QuotaExceededError. Neste artigo, vou abordar algumas 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á algumas coisas a serem observadas sobre isso. Primeiro, observe que o nome QuotaExceededError não aparece em lugar nenhum da mensagem. Para conferir isso, defina um ponto de interrupção em um local em que você possa detectar 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 media player real, o teste de limite de buffer de origem de François Beaufort pelo menos permite observar o comportamento.

Quantos dados posso anexar?

O número exato varia de acordo com o navegador. Como não é possível consultar o valor dos dados anexados, é necessário acompanhar quanto você 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 eles podem ser menores quando o sistema encontra pressão de 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 conferir 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, o uso de um manifesto, 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.

Remover dados desnecessários e anexar novamente

Na verdade, esse deveria ser chamado de "Remover dados com menos probabilidade de uso em breve e, em seguida, tentar novamente o anexo de dados com maior probabilidade de uso 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 precisa ser falsa. Caso contrário, chame SourceBuffer.abort() antes de remover os dados.

Há alguns pontos a serem considerados 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 logo, 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 você removeu dados, será necessário anexar esses dados novamente para satisfazer a pesquisa.
  • Remova o máximo possível. Cuidado ao remover o grupo de frames que está sendo reproduzido atualmente, começando no frame-chave em ou antes de currentTime, porque isso pode causar a interrupção da reprodução. Essas informações podem precisar ser analisadas fora do fluxo de bytes 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 além do horário atual, a menos que você tenha certeza de que a mídia não é mais necessária. Se você remover o elemento perto do indicador de reprodução, poderá causar uma falha.
  • 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ê vai precisar contornar esse problema 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 blocos menores pode ser ajustado de acordo com 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.

Há alguns pontos a considerar ao usar essa técnica:

  • É necessário anexar um novo segmento de inicialização. Você precisa fazer isso sempre que mudar 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 uma falha ou uma breve interrupção, 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. Isso vai causar a interrupção da reprodução até que a busca seja concluída.