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

ジョー・メドレー
Joe Medley

Media Source Extensions(MSE)を使用している場合、最終的に対処しなければならないことの一つは、バッファがいっぱいになってしまうことです。この場合、QuotaExceededError と呼ばれるものが生成されます。この記事では、その対処法についていくつか説明します。

QuotaExceededError とは

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

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

注意すべき点がいくつかあります。まず、QuotaExceededError という名前はメッセージのどこにも表示されていません。これを確認するには、エラーをキャッチできる場所にブレークポイントを設定し、ウォッチウィンドウまたはスコープ ウィンドウで調査します。以下に例を示します。

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

次に、SourceBuffer が処理できるデータの量を確認する確実な方法はありません。

他のブラウザでの動作

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

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

追加できるデータの量を教えてください。

正確な数はブラウザによって異なります。現在追加されているデータの量をクエリすることはできないため、追加しているデータの量を自分で追跡する必要があります。こちらが執筆時点で私が収集できる最高のデータです。Chrome の場合、これらの数値は上限であり、システムにメモリ負荷が発生した場合は小さくなる可能性があります。

Chrome Chromecast* Firefox Safari エッジ
動画 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 つを同時に行うこともできますが、以下の例では解像度を下げるだけです。

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

  • 新しい初期化セグメントを追加する必要があります。表現を変更するたびに、この操作を行う必要があります。新しい初期化セグメントは、その後に続くメディア セグメント用である必要があります。
  • 付加されたメディアの表示タイムスタンプは、バッファ内のデータのタイムスタンプとできるだけ一致する必要がありますが、早送りはしないでください。バッファ内のデータが重複すると、ブラウザによってはスタッタリングや短時間のストールが発生することがあります。何を追加する場合でも、プレイヘッドをオーバーラップしないでください。オーバーラップするとエラーがスローされます。
  • 移動中に再生が中断されることがあります。特定の場所を見つけ、その場所から再生を再開したくなるかもしれません。これにより、シークが完了するまで再生が中断することに注意してください。