Przekroczono limit buforowania

Joe Medley
Joe Medley

Jeśli pracujesz z rozszerzeniami źródła multimediów (MSE), musisz zająć się problemem przepełnionego bufora. W takim przypadku zobaczysz QuotaExceededError. W tym artykule omawiam kilka sposobów radzenia sobie z tym problemem.

Czym jest błąd QuotaExceededError?

Ogólnie rzecz biorąc, jeśli spróbujesz dodać zbyt dużo danych do obiektu SourceBuffer, zobaczysz QuotaExceededError. Ten błąd może też spowodować dodanie większej liczby obiektów SourceBuffer do elementu nadrzędnego MediaSource. (nie jest to jednak tematem tego artykułu). Jeśli SourceBuffer zawiera zbyt dużo danych, wywołanie funkcji SourceBuffer.appendBuffer() spowoduje wyświetlenie tego komunikatu w oknie konsoli Chrome.

Błąd konsoli limitów.

Należy jednak pamiętać o kilku kwestiach. Po pierwsze zwróć uwagę, że nigdzie w wiadomości nie pojawia się nazwa QuotaExceededError. Aby to sprawdzić, ustaw punkt przerwania w miejscu, w którym możesz wykryć błąd i zbadać go w oknie zegara lub zakresu. Poniżej znajdziesz zrzut ekranu.

Okno limitu

Po drugie, nie ma jednoznacznego sposobu na ustalenie, ile danych może obsłużyć SourceBuffer.

Działanie w innych przeglądarkach

W momencie pisania tego tekstu Safari nie wyrzuca błędu QuotaExceededError w wielu swoich wersjach. Zamiast tego usuwa klatki za pomocą algorytmu dwuetapowego, który zatrzymuje się, gdy jest wystarczająco dużo miejsca na appendBuffer(). Najpierw zwalnia klatki z przedziału od 0 do 30 sekund przed bieżącym czasem w odstępach 30 sekund. Następnie usuwa klatki w klockach po 30 sekund od początku do 30 sekund przed currentTime. Więcej informacji na ten temat można znaleźć w zmianach w kodzie WebKit z 2014 roku.

Na szczęście ten błąd występuje wraz z przeglądarkami Chrome, Edge i Firefox. Jeśli używasz innej przeglądarki, musisz przeprowadzić testy samodzielnie. Chociaż prawdopodobnie nie jest to coś, co tworzysz dla rzeczywistego odtwarzacza multimediów, test limitu bufora źródła Françoisa Beauforta pozwala przynajmniej zaobserwować zachowanie.

Ile danych mogę dołączyć?

Dokładna liczba zależy od przeglądarki. Ponieważ nie możesz wysłać zapytania o ilość danych dołączonych w danym momencie, musisz śledzić, ile danych dołączasz. Jeśli chodzi o to, co warto obejrzeć, oto najlepsze dane, jakie udało mi się zebrać w momencie pisania tego artykułu. W przypadku Chrome te wartości są limitami górnymi, co oznacza, że mogą być mniejsze, gdy system odczuwa presję pamięci.

Chrome Chromecast*, Firefox Safari Edge
Wideo 150 MB 30 MB 100 MB 290 MB Nieznany
Audio 12 MB 2 MB 15 MB 14 MB Nieznany
  • lub inne urządzenie z Chrome o ograniczonej pamięci.

Co mam zrobić?

Ponieważ ilość obsługiwanych danych jest bardzo zróżnicowana i nie możesz znaleźć informacji o jej wielkości w SourceBuffer, musisz ją uzyskać pośrednio, korzystając z QuotaExceededError. Przyjrzyjmy się kilku sposobom, jak to zrobić.

Z QuotaExceededError można sobie radzić na kilka sposobów. W rzeczywistości najlepiej jest połączyć kilka metod. Twoje podejście powinno polegać na określeniu, ile danych chcesz pobrać i dołączyć do danych z HTMLMediaElement.currentTime, oraz na dostosowaniu tego rozmiaru do QuotaExceededError. Możesz też używać pliku manifestu, np. pliku mpd (MPEG-DASH) lub pliku m3u8 (HLS), aby śledzić dane dołączane do bufora.

Przyjrzyjmy się teraz kilku podejściom do QuotaExceededError.

  • Usuń niepotrzebne dane i ponownie dołącz pliki.
  • Dołącz mniejsze fragmenty.
  • Zmniejsz rozdzielczość odtwarzania.

Chociaż można ich używać w połączeniu, omówię je po kolei.

Usuwanie zbędnych danych i ponowne dołączanie

Właściwa nazwa tego zadania powinna brzmieć: „Usuń dane, które prawdopodobnie nie zostaną użyte w najbliższym czasie, a następnie spróbuj dołączyć dane, które prawdopodobnie zostaną użyte w najbliższym czasie”. Tytuł jest za długi. Musisz tylko pamiętać, co mam na myśli.

Usuwanie ostatnich danych nie jest tak proste, jak wywołanie funkcji SourceBuffer.remove(). Aby usunąć dane z tabeli SourceBuffer, flaga aktualizacji musi być ustawiona na wartość false. Jeśli nie, zadzwoń pod numer SourceBuffer.abort(), zanim usuniesz jakiekolwiek dane.

Podczas rozmowy z zespołem pomocy SourceBuffer.remove() należy pamiętać o kilku kwestiach.

  • Może to mieć negatywny wpływ na odtwarzanie. Jeśli na przykład chcesz, aby film był odtwarzany w pętli, nie musisz usuwać początku filmu. Podobnie, jeśli Ty lub użytkownik przewiniecie do części filmu, w której dane zostały usunięte, będzie trzeba ponownie dołączyć te dane, aby spełnić wymagania wyszukiwania.
  • Usuwaj filmy jak najbardziej zachowawczo. Pamiętaj, aby nie usuwać grupy klatek, która jest obecnie odtwarzana, zaczynając od klatki kluczowej lub wcześniej (currentTime), ponieważ może to spowodować zablokowanie odtwarzania. Jeśli takie informacje nie są dostępne w pliku manifestu, aplikacja internetowa może musieć wyodrębnić je z strumienia bajtów. Manifest multimediów lub wiedza aplikacji o interwałach kluczowych klatek w multimediach mogą pomóc aplikacji w określeniu zakresów usuwania, aby zapobiec usunięciu obecnie odtwarzanych multimediów. Niezależnie od tego, co usuniesz, nie usuwaj grupy zdjęć, która jest obecnie odtwarzana, ani nawet pierwszych kilku zdjęć po niej. Zazwyczaj nie usuwaj plików, które nie są już potrzebne, chyba że masz pewność, że nie są już potrzebne. Jeśli odłączysz telefon blisko suwaka odtwarzania, możesz spowodować przerwanie odtwarzania.
  • Safari 9 i Safari 10 nieprawidłowo implementują SourceBuffer.abort(). W rzeczywistości powodują one błędy, które powodują zatrzymanie odtwarzania. Na szczęście istnieją śledzące błędy tutaj i tutaj. Tymczasem będzie trzeba sobie to jakoś opracować. Shaka Player robi to, zastępując pustą funkcję abort() w tych wersjach Safari.

Dołącz mniejsze fragmenty

Poniżej przedstawiono procedurę. Nie zawsze jest to możliwe, ale zaletą jest to, że rozmiar mniejszych fragmentów można dostosować do swoich potrzeb. Nie musisz też wracać do sieci, co może wiązać się z dodatkowymi kosztami u niektórych użytkowników.

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

Zmniejsz rozdzielczość odtwarzania.

Działa to podobnie do usuwania najnowszych danych i ponownego dołączania ich. W istocie te dwie metody mogą być stosowane razem, ale w przykładzie poniżej pokazano tylko obniżenie rozdzielczości.

Korzystając z tej metody, należy pamiętać o kilku kwestiach:

  • Musisz dodać nowy segment inicjalizacji. Musisz to zrobić za każdym razem, gdy zmieniasz reprezentację. Nowy segment inicjalizacji musi być przeznaczony dla następujących segmentów mediów.
  • Sygnatury czasowe multimediów dołączonych do siebie powinny być jak najbardziej zbliżone do sygnatury czasowej danych w buforze, ale nie mogą być przesunięte do przodu. Nakładanie się danych buforowanych może powodować zacinanie lub krótkie zawieszanie się filmu, w zależności od przeglądarki. Niezależnie od tego, co dołączysz, nie nakładaj elementów na pasek odtwarzania, ponieważ może to spowodować błędy.
  • Przewijanie może przerwać odtwarzanie. Może Cię kusić, aby poszukać w konkretnym miejscu i wznowić odtwarzanie od niego. Pamiętaj, że do momentu zakończenia przesunięcia odtwarzanie zostanie przerwane.