javascript HTML5 视频:使用 Blob URL 流式传输视频

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/50333767/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 09:02:55  来源:igfitidea点击:

HTML5 Video: Streaming Video with Blob URLs

javascripthtmlvideohtml5-videoblob

提问by stevendesu

I have an array of Blobs (binary data, really -- I can express it however is most efficient. I'm using Blobs for now but maybe a Uint8Arrayor something would be better). Each Blob contains 1 second of audio/video data. Every second a new Blob is generated and appended to my array. So the code roughly looks like so:

我有一个 Blob 数组(二进制数据,真的——我可以表达它,但它是最有效的。我现在使用 Blob,但也许一个Uint8Array或其他东西会更好)。每个 Blob 包含 1 秒的音频/视频数据。每秒都会生成一个新的 Blob 并将其附加到我的数组中。所以代码大致如下:

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

My goal is to stream this audio/video data to an HTML5 element. I know that a Blob URL can be generated and played like so:

我的目标是将此音频/视频数据流式传输到 HTML5 元素。我知道可以像这样生成和播放 Blob URL:

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

Of course this only plays the first 1 second of video. I also assume I can trivially concatenate all of the Blobs currently in my array somehow to play more than one second:

当然,这只播放视频的前 1 秒。我还假设我可以以某种方式简单地连接当前在我的阵列中的所有 Blob,以播放超过一秒钟:

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

However this will still eventually run out of data. As Blobs are immutable, I don't know how to keep appending data as it's received.

但是,这最终仍将耗尽数据。由于 Blob 是不可变的,我不知道如何在收到数据时继续追加数据。

I'm certain this should be possible because YouTube and many other video streaming services utilize Blob URLs for video playback. How do theydo it?

我确信这应该是可能的,因为 YouTube 和许多其他视频流服务使用 Blob URL 进行视频播放。他们是怎么做的?

回答by stevendesu

Solution

解决方案

After some significant Googling I managed to find the missing piece to the puzzle: MediaSource

经过一些重要的谷歌搜索后,我设法找到了拼图的缺失部分:MediaSource

Effectively the process goes like this:

这个过程实际上是这样的:

  1. Create a MediaSource
  2. Create an object URL from the MediaSource
  3. Set the video's srcto the object URL
  4. On the sourceopenevent, create a SourceBuffer
  5. Use SourceBuffer.appendBuffer()to add all of your chunks to the video
  1. 创建一个 MediaSource
  2. MediaSource
  3. 将视频设置src为对象 URL
  4. sourceopen活动中,创建一个SourceBuffer
  5. 使用SourceBuffer.appendBuffer()您的所有块添加到视频

This way you can keep adding new bits of video without changing the object URL.

通过这种方式,您可以在不更改对象 URL 的情况下继续添加新的视频位。

Caveats

注意事项

  • The SourceBufferobject is verypicky about codecs. These have to be declared, and must be exact, or it won't work
  • You can only append one blob of video data to the SourceBufferat a time, and you can't append a second blob until the first one has finished (asynchronously) processing
  • If you append too much data to the SourceBufferwithout calling .remove()then you'll eventually run out of RAM and the video will stop playing. I hit this limit around 1 hour on my laptop
  • SourceBuffer对象对编解码器非常挑剔。这些必须声明,并且必须准确,否则将不起作用
  • 您一次只能向 追加一个视频数据块SourceBuffer,并且在第一个块完成(异步)处理之前不能追加第二块
  • 如果您在SourceBuffer没有调用的情况下将太多数据附加到,.remove()那么您最终会耗尽 RAM 并且视频将停止播放。我在笔记本电脑上达到了这个限制大约 1 小时

Example Code

示例代码

Depending on your setup, some of this may be unnecessary (particularly the part where we build a queue of video data before we have a SourceBufferthen slowly append our queue using updateend). If you are able to wait until the SourceBufferhas been created to start grabbing video data, your code will look much nicer.

根据您的设置,其中一些可能是不必要的(特别是在我们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>

As an added bonus this automatically gives you DVR functionality for live streams, because you're retaining 20 minutes of video data in your buffer (you can seek by simply using video.currentTime = ...)

作为额外的好处,这会自动为您提供实时流的 DVR 功能,因为您在缓冲区中保留了 20 分钟的视频数据(您只需使用 即可查找video.currentTime = ...