HTML5视频:使用Blob网址流式传输视频

时间:2018-05-14 15:16:07

标签: javascript html5 video html5-video blob

我有一个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以某种方式播放超过一秒:

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

然而,这仍将最终耗尽数据。由于Blob是不可变的,我不知道如何继续附加数据。

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

2 个答案:

答案 0 :(得分:5)

解决方案

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

有效的过程如下:

  1. 创建MediaSource
  2. MediaSource
  3. 创建对象网址
  4. 将视频的src设置为对象网址
  5. sourceopen事件中,创建SourceBuffer
  6. 使用SourceBuffer.appendBuffer()将所有块添加到视频中
  7. 通过这种方式,您可以在不更改对象URL的情况下不断添加新视频。

    注意事项

    • SourceBuffer对象 非常 挑剔编解码器。这些必须被宣布,并且必须准确,或者它不会起作用
    • 您一次只能向SourceBuffer添加一个视频数据blob,并且在第一个blob完成(异步)处理之前,您无法追加第二个blob
    • 如果您在未调用SourceBuffer的情况下向.remove()添加过多数据,那么您最终将耗尽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 = ...进行搜索)

答案 1 :(得分:0)

添加到上一个答案...

确保在 sourceBuffer.mode = 'sequence' 事件处理程序中添加 MediaSource.onopen 以确保根据接收顺序附加数据。默认值是段,它会缓冲直到加载下一个“预期”时间帧。

此外,请确保您没有发送任何带有 data.size === 0 的数据包,并通过清除广播端的堆栈来确保存在“堆栈”,除非您想将其记录为一个完整的视频,在这种情况下,只需确保广播视频的大小足够小,并且您的互联网速度很快。分辨率越小越低,您就越有可能与客户端保持实时连接,即视频通话。

对于 iOS,广播需要从 iOS/macOS 应用程序制作,并且是 mp4 格式。视频块会保存到应用程序的缓存中,然后在发送到服务器后将其删除。客户端几乎可以在任何设备上使用网络浏览器或应用连接到流。