代码之家  ›  专栏  ›  技术社区  ›  stevendesu

html5 dvr不工作-已从父元素中删除sourcebuffer

  •  4
  • stevendesu  · 技术社区  · 8 年前

    目标

    我试图通过使用 MediaRecorder , MediaSource SourceBuffer . 目前这只是一个概念的证明。然而,由于像hls.js这样的许多项目利用了html5视频元素,我相信这将具有广泛的传播价值。

    代码

    我的代码要点如下:

    <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
    +-------------+----------------+--------------------------------------------------------------------------------------------
    

    这个 Unexpected element ID 0xa3 似乎是罪魁祸首。尽管出于某种原因,在第一次加载页面时不会引发此错误(我正在附加相同的 ArrayBuffer S到我的 源缓冲区 ,所以如果他们在我不知道为什么之前没有抛出这个错误)

    向上看 0xa3 对于WebM格式,这听起来像是指“simpleBlock”-- https://chromium.googlesource.com/webm/libwebm/+/libwebm-1.0.0.26/webmids.hpp --我不知道为什么会出错?

    我试过的东西

    • 由于开始播放初始视频需要一段时间,我认为在设置MediaSource时可能存在竞争条件。在创建新的MediaSource、SourceBuffer等之前,我在查找后添加了1秒钟的延迟。这没有帮助
    • 我认为如果加载的第一个块不包含关键帧,可能会有错误,所以我将块大小从1秒增加到了10秒。
    • 我尝试过各种源文件(mkv、mov、mp4、rtmp stream、webrtc stream等)
    • 我试图摧毁整个 video 元素并重新创建它
    • 以防 源缓冲区 以某种方式修改 阵列缓冲器 s播放时,我尝试添加副本而不是原始对象( srcBuf.appendBuffer(queue.shift().slice(0)); )
    • 我试过在 segment sequence 上的模式 源缓冲区
    • 如果dvr元素在我创建一个 源缓冲区 (在我有数据之前)并进入“媒体完成”状态时,我尝试暂停dvr元素。

    到目前为止,我还没有运气让dvr正常工作。我错过了什么?

    1 回复  |  直到 8 年前
        1
  •  3
  •   stevendesu    8 年前

    我明白了

    经过更多的试验,我终于解决了这个问题。

    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>
    

    在我读取数据的过程中,第一个块包含所有的头信息(ebml数据、段数据、轨迹、时间代码等),而所有后面的块只是一个流 <Cluster> <SimpleBlock> 标签(带有奇数 <Timecode> 偶尔)

    最后,我要做的是构建一个基本的解释器来解析ebml文件并提取头信息。然后每当我执行搜索时,在任何视频数据之前都会将这个头信息注入缓冲区。

    我的建议

    别麻烦了。MSE很糟糕,这是一个72小时的噩梦。别让自己头疼。

    推荐文章