如何使用 MediaRecorder 作为 MediaSource

And*_*dré 13 html javascript webrtc

作为学习 WebRTC 的练习,我试图展示本地网络摄像头并并排显示网络摄像头的延迟播放。为了实现这一点,我试图将记录的 blob 传递给 BufferSource 并使用相应的 MediaSource 作为视频元素的源。

// the ondataavailable callback for the MediaRecorder
async function handleDataAvailable(event) {
  // console.log("handleDataAvailable", event);
  if (event.data && event.data.size > 0) {
    recordedBlobs.push(event.data);
  }

  if (recordedBlobs.length > 5) {
    if (recordedBlobs.length === 5)
      console.log("buffered enough for delayed playback");
    if (!updatingBuffer) {
      updatingBuffer = true;
      const bufferedBlob = recordedBlobs.shift();
      const bufferedAsArrayBuffer = await bufferedBlob.arrayBuffer();
      if (!sourceBuffer.updating) {
        console.log("appending to buffer");
        sourceBuffer.appendBuffer(bufferedAsArrayBuffer);
      } else {
        console.warn("Buffer still updating... ");
        recordedBlobs.unshift(bufferedBlob);
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)
// connecting the media source to the video element
recordedVideo.src = null;
recordedVideo.srcObject = null;
recordedVideo.src = window.URL.createObjectURL(mediaSource);
recordedVideo.controls = true;
try {
  await recordedVideo.play();
} catch (e) {
  console.error(`Play failed: ${e}`);
}
Run Code Online (Sandbox Code Playgroud)

所有代码:https : //jsfiddle.net/43rm7258/1/

当我在 Chromium 78 中运行它时,我NotSupportedError: Failed to load because no supported source was found.play视频元素的元素中得到一个。

我不知道我做错了什么或此时如何继续。

这是关于类似的事情,但对我没有帮助:MediaSource随机停止视频

这个例子是我的起点:https : //webrtc.github.io/samples/src/content/getusermedia/record/

ste*_*esu 27

总之

让它在 Firefox 和 Chrome 中工作很容易:你只需要在你的编解码器列表中添加一个音频编解码器! video/webm;codecs=opus,vp8

让它在 Safari 中工作要复杂得多。MediaRecorder 是一项“实验性”功能,必须在开发人员选项下手动启用。启用后,Safari 缺少isTypeSupported方法,因此您需要处理它。最后,无论您从 MediaRecorder 请求什么,Safari 都会始终为您提供 MP4 文件 - 无法像 WEBM 那样流式传输。这意味着您需要在 JavaScript 中执行转换以动态转换视频容器格式

如果 Chrome 可以工作,Android 应该可以工作

iOS 不支持媒体源扩展,因此SourceBuffer在 iOS 上没有定义,整个解决方案将不起作用

原帖

查看您发布的 JSFiddle,在我们开始之前的一个快速修复:

  • 您引用了一个errorMsgElement从未定义的变量。您应该<div>使用适当的 ID 向页面添加一个,然后创建const errorMsgElement = document.querySelector(...)一行来捕获它

现在在使用 Media Source Extensions 和 MediaRecorder 时需要注意的是,每个浏览器的支持将大不相同。尽管这是 HTML5 规范的“标准化”部分,但它在跨平台上并不是很一致。根据我的经验,让 MediaRecorder 在 Firefox 中工作并不需要太多努力,让它在 Chrome 中工作有点困难,让它在 Safari 中工作几乎是不可能的,而让它在 iOS 上工作实际上不是你可以做的事情。

我在每个浏览器的基础上进行了调试并记录了我的步骤,以便您在调试媒体问题时可以了解一些可用的工具

火狐

当我在 Firefox 中检查您的 JSFiddle 时,我在控制台中看到以下错误:

NotSupportedError:无法录制音轨:video/webm;codecs=vp8 表示不支持的编解码器

我记得 VP8 / VP9 是 Google 大力推动的,因此可能无法在 Firefox 中运行,所以我尝试对您的代码进行一些小调整。我, options)从您对new MediaRecorder(). 这告诉浏览器使用它想要的任何编解码器,所以你可能会在每个浏览器中得到不同的输出(但它至少应该在每个浏览器中工作

这在 Firefox 中有效,所以我检查了 Chrome。

铬合金

这次我遇到了一个新错误:

(index):409 Uncaught (in promise) DOMException: Failed to execute 'appendBuffer' on 'SourceBuffer': 此 SourceBuffer 已从父媒体源中删除。在 MediaRecorder.handleDataAvailable ( https://fiddle.jshell.net/43rm7258/1/show/:409:22 )

所以我在浏览器中访问 c​​hrome://media-internals/ 看到了这个:

音频流编解码器 opus 与 SourceBuffer 编解码器不匹配。

在您的代码中,您指定的是视频编解码器(VP9 或 VP8)而不是音频编解码器,因此 MediaRecorder 让浏览器选择它想要的任何音频编解码器。看起来 Chrome 的 MediaRecorder 默认选择“opus”作为音频编解码器,但 Chrome 的 SourceBuffer 默认选择其他内容。这是微不足道的固定。我更新了你的两行设置options.mimeType如下:

  • options = { mimeType: "video/webm;codecs=opus, vp9" };
  • options = { mimeType: "video/webm;codecs=opus, vp8" };

由于您使用相同的options对象来声明 MediaRecorder 和 SourceBuffer,将音频编解码器添加到列表意味着 SourceBuffer 现在使用有效的音频编解码器声明并且视频播放

为了更好的衡量,我在 Firefox 上测试了新代码(使用音频编解码器)。这有效!所以我们只是通过将音频编解码器添加到options列表中(并将其保留在用于声明 MediaRecorder 的参数中)来实现 2 对 2

看起来 VP8 和 opus 在 Firefox 中工作,但不是默认值(尽管与 Chrome 不同,MediaRecorder 和 SourceBuffer 的默认值是相同的,这就是删除options参数完全有效的原因)

苹果浏览器

这次我们遇到了一个我们可能无法解决的错误:

未处理的承诺拒绝:ReferenceError:找不到变量:MediaRecorder

我做的第一件事是谷歌“Safari MediaRecorder”,它打开了这篇文章。我想我会试一试,所以我看了一下。果然:

Safari 属性

我点击它以启用 MediaRecorder 并在控制台中遇到以下内容:

未处理的承诺拒绝:TypeError:MediaRecorder.isTypeSupported 不是函数。(在“MediaRecorder.isTypeSupported(options.mimeType)”中,“MediaRecorder.isTypeSupported”未定义)

所以Safari没有这个isTypeSupported方法。不用担心,我们只会说“如果这个方法不存在,假设它是 Safari 并相应地设置类型”

  if (MediaRecorder.isTypeSupported) {
    options = { mimeType: "video/webm;codecs=vp9" };
    if (!MediaRecorder.isTypeSupported(options.mimeType)) {
      console.error(`${options.mimeType} is not Supported`);
      errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
      options = { mimeType: "video/webm;codecs=vp8" };
      if (!MediaRecorder.isTypeSupported(options.mimeType)) {
        console.error(`${options.mimeType} is not Supported`);
        errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
        options = { mimeType: "video/webm" };
        if (!MediaRecorder.isTypeSupported(options.mimeType)) {
          console.error(`${options.mimeType} is not Supported`);
          errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
          options = { mimeType: "" };
        }
      }
    }
  } else {
    options = { mimeType: "" };
  }
Run Code Online (Sandbox Code Playgroud)

现在我只需要找到 Safari 支持的 mimeType。一些轻微的谷歌搜索表明支持 H.264,所以我试过:

options = { mimeType: "video/webm;codecs=h264" };
Run Code Online (Sandbox Code Playgroud)

这成功地给了我MediaRecorder started,但在addSourceBuffer出现新错误的行中失败了:

NotSupportedError:不支持该操作。

我将继续尝试并诊断如何在 Safari 中使用它,但现在我至少已经解决了 Firefox 和 Chrome

更新 1

我继续在 Safari 上工作。不幸的是,Safari 缺乏 Chrome 和 Firefox 的工具来深入挖掘媒体内部结构,因此涉及到很多猜测。

我之前发现我们在尝试调用addSourceBuffer. 所以我创建了一个一次性页面来尝试在不同情况下调用这个方法:

  • 也许play在视频上调用之前添加一个源缓冲区
  • 可能在媒体源附加到视频元素之前添加源缓冲区
  • 也许添加具有不同编解码器的源缓冲区
  • 等等

我发现问题仍然是编解码器,并且关于不允许“操作”的错误消息有点误导。这是不允许的参数。简单地提供“h264”适用于 MediaRecorder,但 SourceBuffer 需要我传递编解码器参数

我尝试的第一件事是前往MDN 示例页面并复制他们在那里使用的编解码器:'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'. 这给出了相同的“不允许操作”错误。挖掘到这些编解码器参数的含义(像这到底是什么42E01E,即使意思?)。虽然我希望我有一个更好的答案,但在谷歌搜索时,我偶然发现了这篇 StackOverflow 帖子,其中提到了'video/mp4; codecs="avc1.64000d,mp4a.40.2"'在 Safari 上使用。我试了一下,控制台错误消失了!

尽管控制台错误现在消失了,但我仍然没有看到任何视频。所以还有工作要做。

更新 2

在 Safari 中的调试器中进行进一步调查(在流程的每个步骤放置多个断点并检查变量)发现handleDataAvailableSafari 中从未调用过。看起来在 Firefox 和 Chrome 中mediaRecorder.start(100)会正确地遵循规范并ondatavailable每 100 毫秒调用一次,但 Safari 会忽略该参数并将所有内容缓冲到一个巨大的 Blob 中。mediaRecorder.stop()手动调用导致调用ondataavailable之前记录的所有内容

我尝试使用每 100 毫秒setInterval调用mediaRecorder.requestData()一次,但未requestData在 Safari 中定义(很像如何isTypeSupported未定义)。这让我有些束手无策。

接下来,我尝试清理整个 MediaRecorder 对象并每 100 毫秒创建一个新对象,但这在await bufferedBlob.arrayBuffer(). 我仍在调查为什么那个失败了

更新 3

我记得关于 MP4 格式的一件事是,播放任何内容都需要“moov”原子。这就是为什么您不能下载 MP4 文件的中间部分并播放它的原因。您需要下载整个文件。所以我想知道我选择 MP4 的事实是否是我没有定期更新的原因。

我尝试更改video/mp4为几个不同的值并得到不同的结果:

  • video/webm -- 不支持操作
  • video/x-m4v-- 表现得像 MP4,我只在.stop()被调用时获取数据
  • video/3gpp -- 表现得像 MP4
  • video/flv -- 不支持操作
  • video/mpeg -- 表现得像 MP4

一切都像 MP4 一样,让我检查了实际传递给handleDataAvailable. 那时我注意到了这一点:

在此处输入图片说明

无论我选择哪种视频格式,Safari 总是给我一个 MP4!

突然我想起了为什么 Safari 是一场噩梦,以及为什么我在精神上将它归类为“该死的几乎不可能”。为了将多个 MP4 拼接在一起,需要一个 JavaScript 转换器

那时我才想起来,这正是我之前所做的。一年多前,我与 MediaRecorder 和 SourceBuffer 合作,尝试创建一个 JavaScript RTMP 播放器。播放器完成后,我想添加对 DVR 的支持(寻找已经流式传输的视频部分),我通过使用 MediaRecorder 并在 1 秒视频 blob 的内存中保留一个环形缓冲区来做到这一点。在 Safari 上,我通过我编码的多路复用器运行这些视频 blob,将它们从 MP4 转换为 ISO-BMFF,这样我就可以将它们连接在一起。

我希望我能与你分享代码,但它全部归我的老雇主所有——所以此时我已经失去了解决方案。我知道有人遇到了使用 emscripten 将 FFMPEG 编译为 JavaScript 的麻烦,因此您可以利用这一点。