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

25

我有一个Blob数组(二进制数据,实际上--可以以最有效的方式表达),目前我正在使用Blobs,但可能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进行视频播放。他们是如何做到的?


可能会有帮助:https://dev59.com/6WEh5IYBdhLWcg3w3muK#22001830 - Hyyan Abo Fakher
@HyyanAboFakher 刚刚浏览了一下那个链接,不幸的是里面没有提到 Blob URL。它更多地涉及编码和传输流,而不是播放机制。最终答案最终说“使用HLS进行编码并使用HLS.js” - 但我问题的关键是 HLS.js是如何工作的? (在我的情况下,我没有HLS,但我同样会分段获取视频) - stevendesu
2个回答

52

解决方案

经过一番搜索,我找到了这个难题的缺失环节:MediaSource

实际上,流程如下:

  1. 创建一个MediaSource
  2. MediaSource创建一个对象URL
  3. 将视频的src设置为该对象URL
  4. sourceopen事件中,创建一个SourceBuffer
  5. 使用SourceBuffer.appendBuffer() 将所有块添加到视频中

这样,您就可以持续添加新的视频片段而不改变对象URL。

注意事项

  • SourceBuffer对象非常挑剔编解码器。必须声明并且必须完全正确,否则无法工作
  • 您只能一次向SourceBuffer追加一个视频数据块,并且在第一个块异步处理完成之前,不能追加第二个块
  • 如果您向SourceBuffer追加过多的数据而没有调用.remove(),那么最终会耗尽内存,视频将停止播放。在我的笔记本电脑上,我在大约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 = ...进行搜索)。

2
这是同一侧录制视频播放的实现。但是如何从服务器连续地将接收到的视频块数组在客户端的视频标签中播放?请参见我的问题:https://dev59.com/PKzka4cB1Zd3GeqP94Zz - susan097
你好,我正在从AWS Kinesis流中读取视频数据,我能够使用createobjectaturl(blob)在blob中查看视频,但我需要它不断添加,就像你的解决方案一样。你如何知道你的数据编解码器?在上传端,编解码器是“video/x-matroska;codecs=avc1”,但这对于buffersource不受支持。任何帮助都将是极好的。 - escullz
1
@SachinKammar 你在使用哪种设备/浏览器?iOS不支持媒体源扩展,这是我首先想到的。 - stevendesu
1
@SachinKammar 关于控制台错误,很遗憾我没有其他的想法,除非看看出现问题的网站。也许尝试提出一个新的SO问题,并附带使用JSFiddle的示例代码进行链接。关于新客户端加入,“未找到支持的源”通常意味着您有错误的视频数据。请仔细检查您的视频服务器是否将视频数据保存在内存中,而不是在发送给一个客户端后刷新它。至于新的解决方案/方法,在HTML API方面没有任何东西。只需利用设计模式来抽象掉丑陋的部分 :) - stevendesu
1
对于其他可能遇到此问题的人:您的视频源(mp4)必须进行分段才能与MediaSource一起使用。 - Hari
显示剩余4条评论

0

添加到先前的答案中...

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

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

对于iOS,广播需要从iOS/macOS应用程序中进行,并且必须是mp4格式。视频块保存到应用程序的缓存中,然后在发送到服务器后将其删除。客户端可以连接到流,无论是在Web浏览器还是应用程序上,几乎支持任何设备。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接