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

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

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

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

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

})

【问题讨论】:

    标签: javascript google-chrome video-streaming html5-video media-source


    【解决方案1】:

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

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

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

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

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 您能否将您更改的代码包含在内,以供其他遇到相同问题的人使用?
      猜你喜欢
      • 1970-01-01
      • 2018-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-18
      • 2013-06-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多