Превышение квоты буферизации

Джо Медли
Joe Medley

Если вы работаете с расширениями медиа-источников (MSE), вам в конечном итоге придется иметь дело с переполненным буфером. Когда это произойдет, вы получите так называемую QuotaExceededError . В этой статье я расскажу о некоторых способах борьбы с этим.

Что такое QuotaExceededError?

По сути, QuotaExceededError — это то, что вы получаете, если пытаетесь добавить слишком много данных в объект SourceBuffer . (Добавление дополнительных объектов SourceBuffer к родительскому элементу MediaSource также может вызвать эту ошибку. Это выходит за рамки этой статьи.) Если в SourceBuffer слишком много данных, вызов SourceBuffer.appendBuffer() приведет к появлению следующего сообщения в окне консоли Chrome. .

Ошибка консоли квот.

По этому поводу следует отметить несколько вещей. Во-первых, обратите внимание, что имя QuotaExceededError нигде в сообщении не встречается. Чтобы убедиться в этом, установите точку останова в том месте, где вы можете обнаружить ошибку и просмотреть ее в окне наблюдения или области видимости. Я показал это ниже.

Окно просмотра квот.

Во-вторых, не существует однозначного способа узнать, какой объем данных может обработать SourceBuffer .

Поведение в других браузерах

На момент написания Safari не выдавал QuotaExceededError во многих своих сборках. Вместо этого он удаляет кадры с помощью двухэтапного алгоритма, останавливаясь, если достаточно места для обработки appendBuffer() . Во-первых, он освобождает кадры от 0 до 30 секунд до текущего времени отрезками по 30 секунд. Затем он освобождает кадры 30-секундными фрагментами от продолжительности назад до 30 секунд после currentTime . Подробнее об этом можно прочитать в наборе изменений Webkit от 2014 года .

К счастью, наряду с Chrome, Edge и Firefox выдают эту ошибку. Если вы используете другой браузер, вам придется провести собственное тестирование. Хотя, вероятно, это не то, что вы бы создали для реального медиаплеера, тест Франсуа Бофорта на ограничение исходного буфера, по крайней мере, позволяет вам наблюдать за поведением.

Какой объем данных я могу добавить?

Точное число варьируется от браузера к браузеру. Поскольку вы не можете запросить объем добавленных в данный момент данных, вам придется отслеживать, сколько вы добавляете самостоятельно. Что касается того, что смотреть, вот лучшие данные, которые я смог собрать на момент написания. Для Chrome эти цифры являются верхними пределами, что означает, что они могут быть меньше, когда система испытывает нехватку памяти.

Хром Хромкаст* Firefox Сафари Край
Видео 150 МБ 30 МБ 100 МБ 290 МБ Неизвестный
Аудио 12 МБ 2 МБ 15 МБ 14 МБ Неизвестный
  • Или другое устройство Chrome с ограниченным объемом памяти.

Так что мне делать?

Поскольку объем поддерживаемых данных сильно различается, и вы не можете найти объем данных в SourceBuffer , вы должны получить его косвенно, обработав QuotaExceededError . Теперь давайте рассмотрим несколько способов сделать это.

Существует несколько подходов к работе с QuotaExceededError . На самом деле лучше всего сочетать один или несколько подходов. Ваш подход должен заключаться в том, чтобы основывать работу на том, сколько вы извлекаете и пытаетесь добавить за пределами HTMLMediaElement.currentTime и корректируете этот размер на основе QuotaExceededError . Также использование какого-либо манифеста, например файла mpd (MPEG-DASH) или файла m3u8 (HLS), может помочь вам отслеживать данные, которые вы добавляете в буфер.

Теперь давайте рассмотрим несколько подходов к работе с QuotaExceededError .

  • Удалите ненужные данные и добавьте заново.
  • Добавляйте более мелкие фрагменты.
  • Уменьшите разрешение воспроизведения.

Хотя их можно использовать в комбинации, я буду рассматривать их по одному.

Удалить ненужные данные и добавить заново

На самом деле это должно называться: «Удалите данные, которые с наименьшей вероятностью будут использованы в ближайшее время, а затем повторите попытку добавления данных, которые могут быть использованы в ближайшее время». Это было слишком длинное название. Вам просто нужно запомнить, что я на самом деле имею в виду.

Удаление последних данных не так просто, как вызов SourceBuffer.remove() . Чтобы удалить данные из SourceBuffer , его флаг обновления должен быть ложным. Если это не так, вызовите SourceBuffer.abort() перед удалением каких-либо данных.

При вызове SourceBuffer.remove() следует учитывать несколько вещей.

  • Это может отрицательно повлиять на воспроизведение. Например, если вы хотите, чтобы видео воспроизводилось повторно или зацикливалось в ближайшее время, возможно, вам не захочется удалять начало видео. Аналогично, если вы или пользователь выполняет поиск части видео, из которой вы удалили данные, вам придется добавить эти данные снова, чтобы удовлетворить этот поиск.
  • Удалите как можно консервативнее. Остерегайтесь удаления воспроизводимой в данный момент группы кадров, начиная с ключевого кадра в currentTime или до него, поскольку это может привести к остановке воспроизведения. Веб-приложению может потребоваться проанализировать такую ​​информацию из байтового потока, если она недоступна в манифесте. Медиа-манифест или знание приложения об интервалах ключевых кадров в мультимедиа могут помочь вашему приложению выбрать диапазоны удаления, чтобы предотвратить удаление воспроизводимого в данный момент мультимедиа. Что бы вы ни удаляли, не удаляйте воспроизводимую в данный момент группу изображений или даже первые несколько последующих. Как правило, не удаляйте данные после текущего времени, если вы не уверены, что носитель больше не нужен. Если вы удалите близко к курсору воспроизведения, это может привести к остановке.
  • Safari 9 и Safari 10 неправильно реализуют SourceBuffer.abort() . Фактически, они выдают ошибки, которые останавливают воспроизведение. К счастью, здесь и здесь есть открытые трекеры ошибок. А пока вам придется как-то обойти это. Shaka Player делает это , отключая пустую функцию abort() в этих версиях Safari.

Добавить более мелкие фрагменты

Я показал процедуру ниже. Это может сработать не во всех случаях, но у него есть то преимущество, что размер меньших фрагментов можно настроить в соответствии с вашими потребностями. Это также не требует возврата в сеть, что может повлечь за собой дополнительные затраты на передачу данных для некоторых пользователей.

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);

Уменьшите разрешение воспроизведения

Это похоже на удаление последних данных и их повторное добавление. Фактически, эти два действия можно выполнить вместе, хотя в приведенном ниже примере показано только снижение разрешения.

При использовании этой техники следует учитывать несколько моментов:

  • Вы должны добавить новый сегмент инициализации. Вы должны делать это каждый раз, когда меняете представления. Новый сегмент инициализации должен быть предназначен для следующих за ним медиа-сегментов.
  • Временная метка представления добавленного мультимедиа должна как можно ближе совпадать с временной меткой данных в буфере, но не забегать вперед. Перекрытие буферизованных данных может вызвать зависание или кратковременную остановку, в зависимости от браузера. Независимо от того, что вы добавляете, не перекрывайте точку воспроизведения, так как это приведет к ошибкам.
  • Поиск может прервать воспроизведение. У вас может возникнуть соблазн найти определенное место и возобновить воспроизведение оттуда. Имейте в виду, что это приведет к остановке воспроизведения до завершения поиска.