当我向 MSE 缓冲区添加小块时,Chrome 停止播放视频

Posted

技术标签:

【中文标题】当我向 MSE 缓冲区添加小块时,Chrome 停止播放视频【英文标题】:Chrome stops playing video when I add small pieces to the MSE buffer 【发布时间】:2020-11-15 16:10:24 【问题描述】:

我正在尝试基于 https://developers.google.com/web/updates/2016/03/mse-sourcebuffer 的媒体源扩展

    通过 XMLHttpRequest 下载视频 (link) 我通过 Blob 和 FileReader 将它分成 20 块(1 块大约 3 秒) 创建 MediaSource 并向其添加缓冲区 我将这些片段添加到缓冲区中

我不是一次全部添加,而是一个接一个地添加,即视频中缓冲区为空时,即发生等待事件。

    下载文件并拆分后,我立即添加第一块进行初始化并设置2.duration 视频开始播放,一切正常 然后,当播放器中没有新数据可以播放时(等待事件之后),我添加一个新片段 视频又开始播放了,来了canplay、playing、canplaythrough、timeupdate、timeupdate…… 循环良好地重复 3 次 当播放器时间线达到 10.61 秒时,视频卡住

视频在添加另一片10.61秒后卡住,虽然缓冲区中有新数据(2.5秒),但由于某种原因,添加数据后canplay事件没有发生。 Chrome 期待一些东西,尽管它似乎有新数据 - 继续播放它们。没有错误、没有视频、没有缓冲区、没有 MSE

是什么阻止 chrome 播放下一首曲子尚不清楚,它只是停止,标准***转动并等待数据(?)

有三种方法可以让视频重新开始播放:

video.currentTime = video.currentTime //愚蠢地将当前位置设置为相同 手动拉动时间线,无论在哪里稍微后退或前进 添加新作品

然后视频将继续播放,但它已经停止了 31.9 秒(已经更长)

当视频没有停止时:

如果你分成更少的部分,例如 5 个 如果您在 onupdateend 事件之后立即添加新块

我无法理解视频停止的神奇时间戳:10.61 和 31.9 秒

在 Firefox 中,一切都播放到最后,这样的 bug 只在类似 chrome 的浏览器中出现(在 chrome 和 opera 中检查)

这是一个示例代码,你可以在那里看到 10 秒。秤上有加载数据,但没有播放 https://jsfiddle.net/1w4hyrke/1/

function playVideo(video) 
  var NUM_CHUNKS = 20;
  var sourceBuffer;
  var segments = [];
  var mimeCodec = 'video/mp4; codecs="avc1.4d401f,mp4a.40.2"';
  var mediaSource = new MediaSource();


  video.src = URL.createObjectURL(mediaSource);
  video.play();

  function addBuffer() 

    if (!segments.length) return console.error("segments empty");
    if (sourceBuffer.updating) return console.error("buffer updating");

    var segment = segments.shift();
    console.info("appendBuffer", segments.length, mediaSource.duration);
    sourceBuffer.appendBuffer(segment);
  

  mediaSource.addEventListener('sourceopen', () => 
    sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
    //sourceBuffer.mode = 'segments';

    sourceBuffer.addEventListener('error', (e) => 
      console.error("sourceBuffer error", segments);
    );

    var xhr = new XMLHttpRequest();
    xhr.responseType = 'arraybuffer';

    sourceBuffer.onupdateend = () => 
      console.info("onupdateend");
      //addBuffer();
    ;

    $(video).on("waiting abort canplay canplaythrough durationchange emptied encrypted  ended error interruptbegin interruptend loadeddata loadedmetadata loadstart mozaudioavailable pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange", (e) => 

      var vbuf = video.buffered;
      var vstart = vbuf && vbuf.length > 0 ? vbuf.start(0) : "";
      var vend = vbuf && vbuf.length > 0 ? vbuf.end(0) : "";
      var videoDelta = vend ? (vend - video.currentTime).toFixed(2) : "-";
      var videoLen = vend ? (vend - vstart).toFixed(2) : "-";
      var quality = video.getVideoPlaybackQuality();
      let warn = ["timeupdate", "progress"].indexOf(e.type) === -1;

      console[!warn ? "log" : "error"].call(console, "VIDEO EVENT", e.type,
                                            `currentTime: $video.currentTime, videoDelta: $videoDelta, videoBuffLen: $videoLen, mediaSource: $mediaSource.duration`,
                                           );

      if (e.type === "waiting") 
        addBuffer();
      

    );
    
    xhr.onload = () => 
      let buf = xhr.response;
      var uInt8Array = new Uint8Array(buf);
      var file = new Blob([uInt8Array], 
        type: 'video/mp4'
      );

      var i = 0;

      function readChunk() 
        var reader = new FileReader();

        reader.onload = function(e) 
          console.log('Appending chunk: ' + i);

          segments.push(new Uint8Array(e.target.result));

          if (i === 0) addBuffer(); //init first chunk          

          if (i === NUM_CHUNKS - 1) 

            sourceBuffer.addEventListener('updateend', function() 
              if (!sourceBuffer.updating && mediaSource.readyState === 'open') 
                //mediaSource.endOfStream();
              
            );

           else 
            readChunk(++i);
          
        ;

        var chunkSize = Math.ceil(file.size / NUM_CHUNKS);
        var startByte = chunkSize * i;
        var chunk = file.slice(startByte, startByte + chunkSize);
        reader.readAsArrayBuffer(chunk);
      

      readChunk();
    ;

    xhr.open("GET", "https://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4");
    xhr.send();

  );

  mediaSource.addEventListener('sourceended', () => 
    console.log("mediaSource ended");
  );
  mediaSource.addEventListener('sourceclose', () => 
    console.log("mediaSource closed");
  );

  mediaSource.addEventListener('error', () => 
    console.error("mediaSource error");
  );

  mediaSource.addEventListener('abort', () => 
    console.error("mediaSource abort");
  );



$(function() 
  var video = $('#video')[0];
  playVideo(video);

)

【问题讨论】:

【参考方案1】:

您的错误是监听来自video 的“等待”事件。事件的定义是:

由于暂时缺少数据而停止播放时触发。

使用它是个坏主意,因为这意味着播放器可以随时暂停(在 Chrome 中,它似乎可能在“等待”事件之后发生),这也会导致口吃

解决方案是在onupdateend 事件中执行addBuffer(),我看到您评论了这一行,也许您确实发现了问题? 如果您不想在缓冲区准备就绪时将数据包推送到 SourceBuffer 中,您还可以使用 setTimeoutsetInterval 进行异步调用。

以下是在 Firefox 和 Chrome 上运行的代码示例: https://jsfiddle.net/thomasjammet/bvm8ueoj/

我删除了不必要的 FileReader 部分(您可以直接将 UInt8Array 数据包推送到 SourceBuffer 实例)。

【讨论】:

【参考方案2】:

根据我的经验,视频停止播放的大部分错误都是由视频片段乱序引起的。我认为 MediaSource 期望这些段是单调递增的。因此,我将代码从视频缓冲区的异步发送/接收更改为同步。

【讨论】:

您能否将您更改的代码包含在内,以供其他遇到相同问题的人使用?

以上是关于当我向 MSE 缓冲区添加小块时,Chrome 停止播放视频的主要内容,如果未能解决你的问题,请参考以下文章

React Native:执行 UI 块时抛出异常

使用 JAX 进行梯度累积

我添加视频标题(chrome)时固定背景不起作用

UISlider 划分范围

请停用以开发者模式运行的扩展程序 提示怎么去掉

Chrome提示:"请停用以开发者模式运行的扩展程序"的解决办法