web-dev-qa-db-ja.com

HTML5ビデオ:Blob URLを使用したスト​​リーミングビデオ

私はBlobの配列(バイナリデータ、本当に-表現できますが、最も効率的です。私は今のところBlobを使用していますが、おそらくUint8Arrayまたは何かが良いでしょう)。各Blobには、1秒のオーディオ/ビデオデータが含まれます。毎秒、新しいBlobが生成され、配列に追加されます。したがって、コードはおおよそ次のようになります。

var arrayOfBlobs = [];
setInterval(function() {
    arrayOfBlobs.append(nextChunk());
}, 1000);

私の目標は、このオーディオ/ビデオデータをHTML5要素にストリーミングすることです。 Blob URLは次のように生成および再生できることを知っています。

var src = URL.createObjectURL(arrayOfBlobs[0]);
var video = document.getElementsByTagName("video")[0];
video.src = src;

もちろん、これはビデオの最初の1秒間しか再生しません。また、配列に現在含まれているすべてのBlobをどうにかして連結して、1秒以上再生できると仮定します。

// Something like this (untested)
var concatenatedBlob = new Blob(arrayOfBlobs);
var src = ...

ただし、これでも最終的にはデータが不足します。 Blobは不変なので、受信したデータを追加し続ける方法がわかりません。

YouTubeや他の多くの動画ストリーミングサービスは動画再生にBlob URLを利用しているので、これは可能だと確信しています。 theyどうやって行うのですか?

4
stevendesu

解決

いくつかの重要なグーグルの後、私はパズルに欠けている部分を見つけることができました: MediaSource

効果的にプロセスは次のようになります。

  1. MediaSourceを作成します
  2. MediaSourceからオブジェクトURLを作成します
  3. 動画のsrcをオブジェクトのURLに設定します
  4. sourceopenイベントで、SourceBufferを作成します
  5. SourceBuffer.appendBuffer()を使用して、すべてのチャンクをビデオに追加します

このようにして、オブジェクトURLを変更せずに新しいビデオを追加し続けることができます。

注意事項

  • SourceBufferオブジェクトは、コーデックについてveryうるさいです。これらは宣言する必要があり、正確でなければなりません。そうでないと機能しません。
  • 一度にSourceBufferに追加できるのは、ビデオデータのblobを1つだけです。最初のblobが(非同期に)処理を完了するまで、2番目のblobを追加することはできません。
  • .remove()を呼び出さずにSourceBufferに過剰なデータを追加すると、最終的にRAM)が不足し、ビデオの再生が停止します。私のラップトップでこの制限は約1時間

コード例

設定によっては、この一部が不要になる場合があります(特に、SourceBufferを取得する前にビデオデータのキューを作成し、updateendを使用してゆっくりとキューに追加する部分)。 SourceBufferが作成されてビデオデータの取得を開始するまで待つことができる場合、コードはより見栄えがよくなります。

<html>
<head>
</head>
<body>
    <video id="video"></video>
    <script>
        // As before, I'm regularly grabbing blobs of video data
        // The implementation of "nextChunk" could be various things:
        //   - reading from a MediaRecorder
        //   - reading from an XMLHttpRequest
        //   - reading from a local webcam
        //   - generating the files on the fly in JavaScript
        //   - etc
        var arrayOfBlobs = [];
        setInterval(function() {
            arrayOfBlobs.append(nextChunk());
            // NEW: Try to flush our queue of video data to the video element
            appendToSourceBuffer();
        }, 1000);

        // 1. Create a `MediaSource`
        var mediaSource = new MediaSource();

        // 2. Create an object URL from the `MediaSource`
        var url = URL.createObjectURL(mediaSource);

        // 3. Set the video's `src` to the object URL
        var video = document.getElementById("video");
        video.src = url;

        // 4. On the `sourceopen` event, create a `SourceBuffer`
        var sourceBuffer = null;
        mediaSource.addEventListener("sourceopen", function()
        {
            // NOTE: Browsers are VERY picky about the codec being EXACTLY
            // right here. Make sure you know which codecs you're using!
            sourceBuffer = mediaSource.addSourceBuffer("video/webm; codecs=\"opus,vp8\"");

            // If we requested any video data prior to setting up the SourceBuffer,
            // we want to make sure we only append one blob at a time
            sourceBuffer.addEventListener("updateend", appendToSourceBuffer);
        });

        // 5. Use `SourceBuffer.appendBuffer()` to add all of your chunks to the video
        function appendToSourceBuffer()
        {
            if (
                mediaSource.readyState === "open" &&
                sourceBuffer &&
                sourceBuffer.updating === false
            )
            {
                sourceBuffer.appendBuffer(arrayOfBlobs.shift());
            }

            // Limit the total buffer size to 20 minutes
            // This way we don't run out of RAM
            if (
                video.buffered.length &&
                video.buffered.end(0) - video.buffered.start(0) > 1200
            )
            {
                sourceBuffer.remove(0, video.buffered.end(0) - 1200)
            }
        }
    </script>
</body>
</html>

追加のボーナスとして、これにより、ライブストリームにDVR機能が自動的に提供されます。これは、バッファに20分のビデオデータを保持しているためです(video.currentTime = ...

18
stevendesu