javascript - HTML5 DVR 不工作——从父元素中删除了 SourceBuffer

标签 javascript html html5-video mediarecorder media-source

目标

我正在尝试使用 MediaRecorderMediaSourceSourceBuffer 为 HTML5 视频元素创建基本的“DVR”。目前这只是一个概念证明。然而,由于像 HLS.js 这样的许多项目都利用了 HTML5 视频元素,我相信这将具有广泛的值(value)。

代码

这是我的代码的要点:

<html>
<head>
</head>
<body>
    <video id="src-video" src="http://localhost:8080/video/source.mp4" autoplay></video>
    <video id="dvr-video"></video>
    <input id="seekbar" type="range" min="-120" max="0" value="0" />
    <script>
    var mr; // MediaRecorder
    var ms = new MediaSource();
    var srcBuf; // SourceBuffer
    var srcUrl = URL.createObjectURL(ms);
    var srcVid = document.getElementById("src-video");
    var dvrVid = document.getElementById("dvr-video");
    var dvrData = []; // array of ArrayBuffer
    var queue = [];

    ms.addEventListener("sourceopen", sourceOpen);
    srcVid.addEventListener("playing", setupMediaRecorder);
    dvrVid.src = srcUrl;

    var seekBar = document.getElementById("seekbar");
    seekBar.addEventListener("change", function(e) {
        // Destroy the old media source and make a new one
        URL.revokeObjectURL(srcUrl);
        srcBuf = null;

        ms = new MediaSource();
        ms.addEventListener("sourceopen", sourceOpen);
        srcUrl = URL.createObjectURL(ms);

        body.removeChild(dvr);
        dvr = document.createElement("video");
        body.insertBefore(dvr, seekBar);

        dvr.src = srcUrl;
    });

    function sourceOpen()
    {
        // Create the source buffer
        if (!srcBuf)
        {
            srcBuf = src.addSourceBuffer('video/webm; codecs="opus,vp8"');
            srcBuf.mode = "sequence";
        }

        srcBuf.addEventListener('updateend', function() {
            if ( queue.length ) {
                srcBuf.appendBuffer(queue.shift());
            } else {
                dvr.play();
            }
        }, false);

        // Add all fragments in cache
        var start = dvrData.length + parseInt(seekBar.value);
        queue = [];
        for( var i = start; i < dvrData.length; i++ )
        {
            if (dvrData[i])
                queue.push(dvrData[i]);
        }
        if (queue.length)
            srcBuf.appendBuffer(queue.shift());
    }

    function setupMediaRecorder()
    {
        var stream = srcVid.captureStream()
        mr = new MediaRecorder(stream);
        mr.ondataavailable = function(e) {
            // Convert the Blob to an ArrayBuffer
            var fileReader = new FileReader();
            fileReader.onload = function() {
                // Append this ArrayBuffer to our playing video
                if (srcBuf)
                {
                    if (srcBuf.updating || queue.length)
                        queue.push(this.result);
                    else
                        srcBuf.appendBuffer(this.result);
                }
                // And to our historical array (for seeking purposes)
                dvrData.push(this.result);
                if (dvrData.length > 120) {
                    // Keep only 2 minutes of data
                    dvrData.splice(0, 1);
                }
            };
            fileReader.readAsArrayBuffer(e.data);
        };
        mr.start();
        // Record 1-second chunks
        setInterval(function() {
            mr.requestData();
        }, 1000);
    }
    </script>
</body>
</html>

结果

当页面首次加载时,“live”视频元素开始播放,1 秒后“dvr”元素开始播放 - 延迟 1 秒。所以它一开始似乎是有效的。

当我执行搜索时,dvr 元素变黑并且我在控制台中收到以下错误(行号可能与上面的代码不完全匹配):

Uncaught DOMException: Failed to execute 'appendBuffer' on 'SourceBuffer': This SourceBuffer has been removed from the parent media source.
at SourceBuffer.<anonymous> (http://localhost:8080/video/dvr.html:80:13)

查看 chrome://media-internals 了解更多详情,我看到 DVR 播放器如下:

+-------------+----------------+--------------------------------------------------------------------------------------------
| Timestamp   | Property       | Value
+-------------+----------------+--------------------------------------------------------------------------------------------
| 00:00:00 00 | origin_url     | http://localhost:8080/
| 00:00:00 00 | frame_url      | http://localhost:8080/video/dvr.html
| 00:00:00 00 | frame_title    |
| 00:00:00 00 | url            | blob:http://localhost:8080/cec4134a-4498-43c5-8321-3743761636ac
| 00:00:00 00 | info           | ChunkDemuxer: buffering by DTS
| 00:00:00 00 | pipeline_state | kStarting
| 00:00:00 03 | error          | Unexpected element ID 0xa3
| 00:00:00 03 | error          | Append: stream parsing failed. Data size=112300 append_window_start=0 append_window_end=inf
| 00:00:00 08 | pipeline_error | CHUNK_DEMUXER_ERROR_APPEND_FAILED
| 00:00:00 10 | pipeline_state | kStopping
| 00:00:00 10 | pipeline_state | kStopped
+-------------+----------------+--------------------------------------------------------------------------------------------

意外的元素 ID 0xa3 似乎是罪魁祸首。虽然出于某种原因,当页面首次加载时没有抛出这个错误(我将相同的 ArrayBuffer 附加到我的 SourceBuffer,所以如果他们没有抛出这个错误之前我不知道他们为什么现在扔它)

查找关于 WEBM 格式的 0xa3,这听起来像是指“SimpleBlock”-- https://chromium.googlesource.com/webm/libwebm/+/libwebm-1.0.0.26/webmids.hpp -- 我不知道为什么这会引发错误?

我尝试过的事情

  • 由于初始视频需要一些时间才能开始播放,我认为在设置 MediaSource 时可能存在竞争条件。在创建新的 MediaSource、SourceBuffer 等之前,我在搜索之后添加了 1 秒的延迟。这没有帮助
  • 我认为如果加载的第一个 block 不包含关键帧可能会出错,所以我将 block 大小从 1 秒增加到 10 秒
  • 我尝试过各种源文件(MKV、MOV、MP4、RTMP 流、WebRTC 流等)
  • 我试过销毁整个 video 元素并重新创建它
  • 万一 SourceBuffer 在播放时以某种方式修改了 ArrayBuffer,我尝试附加副本而不是原始对象 (srcBuf.appendBuffer( queue.shift().slice(0));)
  • 我已经尝试在 SourceBuffer 上的 segmentsequence 模式之间切换
  • 如果 DVR 元素在我创建 SourceBuffer(在我有数据之前)并进入“媒体完成”状态的瞬间播放,我尝试暂停 DVR 元素

到目前为止,我还没有成功让 DVR 正常工作。我错过了什么?

最佳答案

我知道了

经过更多的实验,我终于弄清楚了这个问题。

WEBM 文件实际上是二进制编码的 XML 文件。架构看起来像这样:

<EBML>
    <EBMLVersion>...</EBMLVersion>
    <EBMLReadVersion>...</EBMLReadVersion>
    <EBMLMaxIDLength>...</EBMLMaxIDLength>
    <EBMLMaxSizeLength>...</EMBLMaxSizeLength>
    <DocType>...</DocType>
    <DocTypeVersion>...</DocTypeVersion>
    <DocTypeReadVersion>...</DocTypeReadVersion>
</EBML>
<Segment>
    <SeekHead>
        <Seek>...</Seek>
        <Seek>...</Seek>
        <Seek>...</Seek>
    </SeekHead>
    <Void></Void>
    <Info>...</Info>
    <Tracks>
        <TrackEntry>
            <Video>
                <Colour>
                    <MatrixCoeffciient>...</MatrixCoefficients>
                    ...
                </Colour>
            </Video>
            <Audio>
                ...
            </Audio>
        </TrackEntry>
    </Tracks>
    <Cues>
        <CuePoint>
            <CueTrackPositions>...</CueTrackPositions>
        </CuePoint>
    </Cues>
    <Cluster>
        <Timecode>...</Timecode>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        ...
    </Cluster>
    <Cluster>
        <Timecode>...</Timecode>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        <SimpleBlock>...</SimpleBlock>
        ...
    </Cluster>
    ...
</Segment>

我读取数据的方式是,第一个 block 包含所有 header 信息(EBML 数据、段数据、轨道、时间码等),所有后面的 block 只是一个流 <Cluster><SimpleBlock>标签(偶尔有一个奇怪的 <Timecode>)

最终我要做的是构建一个基本的多路分解器来解析 EBML 文件并提取 header 信息。然后,每当我执行搜索时,此 header 信息都会在任何视频数据之前注入(inject)缓冲区。

我的建议

不客气。 MSE 很糟糕,这是一场 72 小时的噩梦。免得您头疼。

关于javascript - HTML5 DVR 不工作——从父元素中删除了 SourceBuffer,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50491065/

相关文章:

html - Azure WebMatrix(图片未在网站上加载)

html - 为什么 HTML5 视频在新的 Windows 版本的 Safari 中不起作用?

javascript - RequireJS:回调函数中依赖项和参数的不同计数

javascript - 忽略重音字符的 string.search()?

javascript - Angular 单元测试 : How to unit test . map()?

html - 在悬停时使用比例尺时模糊背景图像(svg)的问题

javascript - 模板代码创建指数数量的元素

android - <video> 标签不工作 android 模拟器

javascript - 在Electron应用程序中最有效的流视频方法? (getUserMedia?)

javascript - 新手 html/Javascript 问题。 react 迟钝