javascript - 如何正确录制 MediaStream?

标签 javascript html vue.js html5-canvas web-mediarecorder

情况

我需要做以下事情:

  • <video> 获取视频在<canvas>里面玩

  • 将来自 Canvas 的流记录为 Blob

就是这样。第一部分没问题。

对于第二部分,我设法记录了一个 Blob。问题是 Blob 是空的。

View

<video id="video" controls="true" src="http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv"></video>
<canvas id="myCanvas" width="532" height="300"></canvas>

代码

// Init
console.log(MediaRecorder.isTypeSupported('video/webm')) // true
const canvas = document.querySelector("canvas")
const ctx = canvas.getContext("2d")
const video = document.querySelector("video")

// Start the video in the player
video.play()

// On play event - draw the video in the canvas
video.addEventListener('play', () => {
  function step() {
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
    requestAnimationFrame(step)
  }
  requestAnimationFrame(step);

  // Init stream and recorder
  const stream = canvas.captureStream()
  const recorder = new MediaRecorder(stream, {
    mimeType: 'video/webm',
  });

  // Get the blob data when is available
  let allChunks = [];
  recorder.ondataavailable = function(e) {
    console.log({e}) // img1
    allChunks.push(e.data);
  }

  // Start to record
  recorder.start()

  // Stop the recorder after 5s and check the result
  setTimeout(() => {
    recorder.stop()
    const fullBlob = new Blob(allChunks, { 'type' : 'video/webm' });
    const downloadUrl = window.URL.createObjectURL(fullBlob)
    console.log({fullBlob}) // img2
  }, 5000);
})

结果

这是 console.logondataavailable事件:

Enter image description here

这是 console.log Blob 的:

Enter image description here

fiddle

这是 JSFiddle。您可以在控制台中查看结果:

https://jsfiddle.net/1b7v2pen/

浏览器行为

这种行为(Blob 数据大小:0)发生在 Chrome 和 Opera 上。
在 Firefox 上它的行为略有不同。 它记录了一个非常小的视频 Blob(725 字节)。视频长度应该是 5 秒,但它只是一个黑屏。

问题

从 Canvas 记录流的正确方法是什么?
代码有问题吗?

为什么 Blob 出来是空的?

最佳答案

MediaRecorder.stop() 是一种异步方法。

stop 算法中,有一个对 requestData 的调用,它本身将排队任务以触发事件 dataavailable自上次此类事件以来的可用数据。

这意味着在您调用 MediaRecorder#stop() 之后同步获取的最后数据还不会成为您的 allChunks 数组的一部分。它们会在不久之后变为(通常在同一个事件循环中)。

因此,当您要保存从 MediaRecorder 制作的记录时,请务必始终从 MediaRecorder's onstop 构建最终的 Blob。表示 MediaRecorder 实际结束的事件确实触发了它的最后一个 dataavailable 事件,并且一切都很好。

一开始我错过了一件事,就是您请求的是跨域视频。如果没有正确的跨源请求,这样做会使您的 Canvas (和 MediaElement)受到污染,因此您的 MediaStream 将被静音。

由于您尝试请求的视频来自维基媒体,您可以简单地将其作为跨源资源请求,但对于其他资源,您必须确保服务器配置为允许这些请求。

const canvas = document.querySelector("canvas")
const ctx = canvas.getContext("2d")
const video = document.querySelector("video")

// Start the video in the player
video.play()

// On play event - draw the video in the canvas
video.addEventListener('play', () => {
  function step() {
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
    requestAnimationFrame(step)
  }
  requestAnimationFrame(step);
  
  // Init stream and recorder
  const stream = canvas.captureStream()
  const recorder = new MediaRecorder(stream, {
    mimeType: 'video/webm',
  });

  // Get the blob data when is available
  let allChunks = [];
  recorder.ondataavailable = function(e) {
    allChunks.push(e.data);
  }
  recorder.onstop = (e) => {
    const fullBlob = new Blob(allChunks, { 'type' : 'video/webm' });
    const downloadUrl = window.URL.createObjectURL(fullBlob)
    console.log({fullBlob})
    console.log({downloadUrl})
  }

  // Start to record
  recorder.start()

  // Stop the recorder after 5s and check the result
  setTimeout(() => {
    recorder.stop()
  }, 5000);
})
<!--add the 'crossorigin' attribute to your video -->
<video id="video" controls="true" src="https://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv" crossorigin="anonymous"></video>
<canvas id="myCanvas" width="532" height="300"></canvas>

此外,我不得不指出,如果您不从您的 Canvas 上做任何特殊的绘图,您可能希望直接保存视频源,或者至少,录制

关于javascript - 如何正确录制 MediaStream?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52512996/

相关文章:

javascript - 隐藏或删除 canvas html5 中的特定弧线

javascript - 使用 Shift 键选择多个 div

html - 使用最佳实践示例创建干净简单的 HTML/CSS

html - 如何使容器标题固定而不随内容滚动?

javascript - Typescript/Javascript 赋值并返回单行代码

javascript - 普通 Javascript 和 Dom 性能

html - 想要避免使用 CSS box-shadow 隐藏之前的 li 元素

vue.js - Vue-jest 找不到 babel

vue.js - Vue3 应用程序初始化链接语法与 config.globalProperties

typescript - VueJS字符串插值可以访问TS对象的私有(private)属性