HTML5 视频:使用 Blob URL 的流式视频

Posted

技术标签:

【中文标题】HTML5 视频:使用 Blob URL 的流式视频【英文标题】:HTML5 Video: Streaming Video with Blob URLs 【发布时间】:2018-10-24 07:37:00 【问题描述】:

我有一个 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 URL 进行视频播放。 他们是怎么做到的?

【问题讨论】:

可能会有所帮助:***.com/questions/21921790/… @HyyanAboFakher 刚刚浏览了那个链接,不幸的是那里没有提到 Blob URL。它更多的是关于编码和传输流,而不是播放机制。最终答案最终说“在 HLS 中编码并使用 HLS.js”——但我的问题的症结是 HLS.js 是如何工作的?(在我的情况下,我没有 HLS,但是我同样会分块获取视频) 【参考方案1】:

解决方案

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

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

    创建一个MediaSourceMediaSource 创建一个对象 URL 将视频的 src 设置为对象 URL 在sourceopen 事件中,创建SourceBuffer 使用SourceBuffer.appendBuffer() 将所有块添加到视频中

这样您就可以在不更改对象 URL 的情况下继续添加新的视频片段。

注意事项

SourceBuffer 对象对编解码器非常很挑剔。这些必须声明,并且必须准确,否则将不起作用 您一次只能将一个视频数据块附加到 SourceBuffer,并且在第一个 blob 完成(异步)处理之前,您不能附加第二个 blob 如果您在没有调用.remove() 的情况下向SourceBuffer 添加了太多数据,那么您最终会耗尽内存并且视频将停止播放。我在笔记本电脑上大约 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 = ... 来查找)

【讨论】:

在参考链接中看不懂,请您详细说明代码! 嗯,这是在同一侧播放录制的视频块的实现。但是如何从服务器端到客户端连续播放视频标签中接收到的视频blob数组的缝隙。请查看我的问题:***.com/questions/51096770/… @SachinKammar 您使用的是什么设备/浏览器? ios 不支持媒体源扩展,所以这是首先想到的。 @SachinKammar 关于控制台错误,不幸的是,如果不查看发生问题的站点,我没有很多其他想法。也许尝试就该问题提出一个新的 SO 问题并将 JSFiddle 与示例代码链接起来。关于新客户加入,“未找到支持的来源”通常意味着您的视频数据不正确。仔细检查您的视频服务器是否将视频数据保存在内存中,而不是在将其发送到一个客户端后将其刷新。至于新的解决方案/方法,HTML API 方面没有任何内容。只需利用设计模式来抽象出丑陋的部分:) 对于可能遇到此问题的其他人:您的视频源 (mp4) 必须分段才能与 MediaSource 一起使用。【参考方案2】:

添加到上一个答案...

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

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

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

【讨论】:

以上是关于HTML5 视频:使用 Blob URL 的流式视频的主要内容,如果未能解决你的问题,请参考以下文章

将 getUserMedia 流式传输到 Icecast 服务器?

当 url 是 blob url 时,html5 下载属性不起作用

Safari:无法从 blob url 动态加载视频

Safari:无法从 blob url 动态加载视频

使用 HTML5 视频标签从 Google Drive 流式传输视频

使用 PHP Html5 流式传输视频