バッファリングの割り当てを超過する

Joe Medley
Joe Medley

Media Source Extensions(MSE)を使用している場合、最終的にはバッファの過剰な状態に対処する必要があります。この場合、QuotaExceededError と呼ばれるものが返されます。この記事では、この問題に対処する方法について説明します。

QuotaExceededError とは何ですか?

基本的に、SourceBuffer オブジェクトにデータが多すぎると、QuotaExceededError が返されます。(親 MediaSource 要素に SourceBuffer オブジェクトを追加すると、このエラーがスローされる可能性があります。これはこの記事の対象外です)。SourceBuffer にデータが多すぎる場合、SourceBuffer.appendBuffer() を呼び出すと、Chrome コンソール ウィンドウに次のメッセージが表示されます。

割り当てコンソール エラー。

ただし、注意すべき点がいくつかあります。まず、メッセージのどこにも QuotaExceededError という名前がないことを確認します。エラーを検出できる場所にブレークポイントを設定し、ウォッチウィンドウまたはスコープ ウィンドウでエラーを確認します。その手順を以下に示します。

割り当て監視ウィンドウ。

2 つ目は、SourceBuffer が処理できるデータ量を特定する確実な方法がないことです。

他のブラウザでの動作

執筆時点では、Safari の多くのビルドで QuotaExceededError がスローされません。代わりに、2 段階のアルゴリズムを使用してフレームを削除し、appendBuffer() を処理するのに十分なスペースがある場合は停止します。まず、現在の時刻の 0 ~ 30 秒前から 30 秒のチャンクでフレームを解放します。次に、currentTime の 30 秒後に近い時間から、時間の逆方向に 30 秒単位でフレームを解放します。詳しくは、2014 年の WebKit の変更セットをご覧ください。

幸い、Chrome だけでなく、Edge と Firefox でもこのエラーがスローされます。別のブラウザを使用している場合は、ご自身でテストする必要があります。実際のメディア プレーヤー用にビルドするものではないかもしれませんが、François Beaufort のソース バッファの上限テストでは、少なくとも動作を確認できます。

追加できるデータの量はどれくらいですか?

正確な数はブラウザによって異なります。現在アペンドされているデータの量をクエリすることはできないため、アペンドするデータの量を自分で追跡する必要があります。注意すべき点としては、この記事の執筆時点で収集できる最良のデータは次のとおりです。Chrome では、これらの数値は上限です。つまり、システムでメモリ不足が発生した場合は、この数値より小さくなる可能性があります。

Chrome Chromecast* Firefox Safari Edge
動画 150MB 30MB 100 MB 290MB 不明
音声 12MB 2MB 15 MB 14MB 不明
  • または、メモリ容量が限られている他の Chrome デバイス。

どうすればよいですか?

サポートされるデータの量は非常に大きく、SourceBuffer でデータの量を確認できないため、QuotaExceededError を処理して間接的に取得する必要があります。その方法をいくつか見てみましょう。

QuotaExceededError に対処する方法はいくつかあります。実際には、1 つ以上のアプローチを組み合わせるのが最善です。フェッチして HTMLMediaElement.currentTime を超えるデータを追加しようとする量に基づいて処理を行い、そのサイズを QuotaExceededError に基づいて調整することをおすすめします。また、mpd ファイル(MPEG-DASH)や m3u8 ファイル(HLS)などのマニフェストを使用すると、バッファに追加するデータを追跡できます。

次に、QuotaExceededError に対処するためのいくつかのアプローチを見てみましょう。

  • 不要なデータを削除して、再度追加します。
  • 小さいフラグメントを追加します。
  • 再生解像度を下げる。

これらは組み合わせて使用することもできますが、ここでは 1 つずつ説明します。

不要なデータを削除して再度追加する

正確には、「近い将来使用される可能性の低いデータを削除してから、近い将来使用される可能性の高いデータの追加を再試行する」という名前にすべきです。タイトルが長すぎます。 私が本当に言いたいことを覚えておいてください。

最近のデータの削除は、SourceBuffer.remove() を呼び出すだけでは簡単ではありません。SourceBuffer からデータを削除するには、更新フラグを false にする必要があります。有効でない場合は、データを削除する前に SourceBuffer.abort() を呼び出します。

SourceBuffer.remove() を呼び出す際には、いくつか注意すべき点があります。

  • これにより、再生に悪影響が生じる可能性があります。たとえば、動画をすぐに再生またはループさせたい場合は、動画の冒頭を削除しないことをおすすめします。同様に、ユーザーまたはクリエイターが、データを削除した動画の部分にシークした場合は、そのシークを満たすために、そのデータを再度追加する必要があります。
  • 削除はできるだけ控えめに行ってください。currentTime 以前のキーフレームで現在再生中のフレーム グループを削除すると、再生が停止する可能性があるため、注意してください。このような情報は、マニフェストで利用できない場合は、ウェブアプリによってバイトストリームから解析される必要があります。メディア マニフェストまたはメディア内のキーフレーム間隔に関するアプリの知識は、現在再生中のメディアが削除されないように、アプリが削除範囲を選択する際に役立ちます。削除する写真は、現在再生中のグループや、その最初の数枚は削除しないでください。一般的に、メディアが不要になったことが確実な場合を除き、現在時刻以降は削除しないでください。プレイヘッドの近くで取り外すと、ストールが発生することがあります。
  • Safari 9 と Safari 10 では SourceBuffer.abort() が正しく実装されていません。実際には、エラーをスローして再生を停止します。幸い、こちらこちらにオープンなバグトラッカーがあります。それまでは、何らかの回避策を講じる必要があります。Shaka Player では、これらのバージョンの Safari で空の abort() 関数をスタブ化することでこれを実現しています

小さいフラグメントを追加する

手順は次のとおりです。これはすべてのケースで機能するとは限りません。ただし、小さなチャンクのサイズをニーズに合わせて調整できるという利点があります。また、ネットワークに戻る必要がないため、一部のユーザーで追加のデータ通信料が発生することはありません。

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

再生解像度を下げる

これは、最近のデータの削除と再追加に似ています。実際には、この 2 つを一緒に行うこともできますが、以下の例は解像度を下げるだけを示しています。

この手法を使用する場合は、次の点に注意してください。

  • 新しい初期化セグメントを追加する必要があります。これは、表現を変更するたびに行う必要があります。新しい初期化セグメントは、続くメディア セグメント用にする必要があります。
  • 追加されたメディアのプレゼンテーション タイムスタンプは、バッファ内のデータのタイムスタンプとできるだけ一致させる必要がありますが、先に進まないようにする必要があります。バッファに格納されたデータを重ねると、ブラウザによっては、途切れや短時間の停止が発生することがあります。追加する内容にかかわらず、再生ヘッドを重ねないようにしてください。重ねるとエラーが発生します。
  • シークすると再生が中断される場合があります。特定の場所まで移動して、そこから再生を再開したくなるかもしれませんが、シークが完了するまで、再生が中断されます。