javascript - 使用 captureStream 和 mediaRecorder 进行 Canvas 记录

标签 javascript html5-canvas mediarecorder mediastream

如何从多个 Canvas 录制流? 即,当我将一个 Canvas 更改为另一个 Canvas 时,它必须记录事件 Canvas 继续到第一个。

我是这样做的:

stream = canvas.captureStream();
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start(10);

function handleDataAvailable(event) {
  recordedBlobs.push(event.data);
}

但是当添加另一个流时,只记录了第一部分。我正在将记录的数据推送到全局数组。

最佳答案

在当前的实现中,您无法切换 MediaRecorder 流的录制轨道

当您尝试这样做时,Firefox 会将您带到控制台

MediaRecorder does not support recording multiple tracks of the same type at this time.

虽然 Chrome 保持静音并记录黑帧而不是第二条轨道......

var canvases = Array.prototype.slice.call(document.querySelectorAll('canvas')),
  recordingStream,
  current = 0,
  chunks = [],
  recorder,
  switchInterval;


function startRecording() {

  // first gather both canvases streams & extract the videoTracks
  let streams = canvases.map((c) => {
    return c.captureStream(30)
  });
  let tracks = streams.map((s) => {
    return s.getVideoTracks()[0]
  });
  // create a new MediaStream with both tracks in it
  // we don't use addTrack because of https://bugzilla.mozilla.org/show_bug.cgi?id=1296531
  recordingStream = 'MediaStream' in window && new MediaStream(tracks) || new webkitMediaStream(tracks);

  // init the MediaRecorder
  recorder = new MediaRecorder(recordingStream);
  recorder.ondataavailable = saveChunks;
  recorder.onstop = exportVideo;
  recorder.onerror = (e) => {
    console.log(e.name)
  };
  recorder.start();

  stopRec.disabled = false;
  // switch the canvas to be recorder every 200ms
  switchInterval = setInterval(switchStream, 200);

}


// switch mute one of the tracks, then the other
function switchStream() {
  current = +!current;
  var tracks = recordingStream.getVideoTracks();
  tracks[current].enabled = true;
  // commented because it seems FF doesn't support canvasTrack's method yet
  // doesn't work in chrome even when there anyway
  //  tracks[current].requestFrame(); 
  tracks[+!current].enabled = false;
}

function saveChunks(evt) {
  // store our video's chunks
  if (evt.data.size > 0) {
    chunks.push(evt.data);
  }

}

stopRec.onclick = function stopRecording() {
  if (recorder.state !== 'recording') {
    this.disabled = true;
    return;
  }
  // stop everything
  recorder.stop(); // this will trigger exportVideo
  clearInterval(switchInterval);
  stopCanvasAnim();
  a.style.display = b.style.display = 'none';
  this.parentNode.innerHTML = "";
}


function exportVideo() {
  //  we've got everything
  vid.src = URL.createObjectURL(new Blob(chunks));
}



var stopCanvasAnim = (function initCanvasDrawing() {
  // some fancy drawings

  var aCtx = canvases[0].getContext('2d'),
    bCtx = canvases[1].getContext('2d');

  var objects = [],
    w = canvases[0].width,
    h = canvases[0].height;
  aCtx.fillStyle = bCtx.fillStyle = 'ivory';

  for (var i = 0; i < 100; i++) {
    objects.push({
      angle: Math.random() * 360,
      x: 100 + (Math.random() * w / 2),
      y: 100 + (Math.random() * h / 2),
      radius: 10 + (Math.random() * 40),
      speed: 1 + Math.random() * 20
    });
  }
  var stop = false;
  var draw = function() {

    aCtx.fillRect(0, 0, w, h);
    bCtx.fillRect(0, 0, w, h);
    for (var n = 0; n < 100; n++) {
      var entity = objects[n],
        velY = Math.cos(entity.angle * Math.PI / 180) * entity.speed,
        velX = Math.sin(entity.angle * Math.PI / 180) * entity.speed;

      entity.x += velX;
      entity.y -= velY;

      aCtx.drawImage(imgA, entity.x, entity.y, entity.radius, entity.radius);
      bCtx.drawImage(imgB, entity.x, entity.y, entity.radius, entity.radius);

      entity.angle++;
    }
    if (!stop) {
      requestAnimationFrame(draw);
    }
  }


  var imgA = new Image();
  var imgB = new Image();
  imgA.onload = function() {
    draw();
    startRecording();
  };
  imgA.crossOrigin = imgB.crossOrigin = 'anonymous';
  imgA.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
  imgB.src = "https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png";

  return function() {
    stop = true;
  };
})();
<p>
  <button id="stopRec" disabled>stop recording</button>
</p>
<canvas id="a"></canvas>
<canvas id="b"></canvas>
<video id="vid" controls></video>

请注意,当前有一个 open issue on the w3c github project mediacapture-record关于这个。


但是,这个问题有一个简单的解决方法:

  • 使用另一个屏幕外 [hidden] * offscreen (chrome bug 已在最新的 58 canary 中修复) Canvas ,仅用于记录器,
  • 在上面绘制所需 Canvas 的框架。

这样,没问题;-)
同样的解决方法也可用于在同一 MediaRecorder 上保存不同的视频。

var canvases = document.querySelectorAll('canvas'),
  recordingCtx,
  current = 0,
  chunks = [],
  recorder,
  switchInterval;

// draw one of our canvas on a third one
function recordingAnim() {
  recordingCtx.drawImage(canvases[current], 0, 0);
  // if recorder is stopped, stop the animation
  if (!recorder || recorder.state === 'recording') {
    requestAnimationFrame(recordingAnim);
  }
}

function startRecording() {

  var recordingCanvas = canvases[0].cloneNode();
  recordingCtx = recordingCanvas.getContext('2d');
  recordingCanvas.id = "";
  // chrome forces us to display the canvas in doc so it can be recorded,
  // This bug has been fixed in chrome 58.0.3014.0
  recordingCtx.canvas.style.height = 0;
  document.body.appendChild(recordingCtx.canvas);

  // draw one of the canvases on our recording one
  recordingAnim();

  // init the MediaRecorder
  recorder = new MediaRecorder(recordingCtx.canvas.captureStream(30));
  recorder.ondataavailable = saveChunks;
  recorder.onstop = exportVideo;
  recorder.start();

  stopRec.onclick = stopRecording;
  // switch the canvas to be recorder every 200ms
  switchInterval = setInterval(switchStream, 200);

}

function saveChunks(evt) {
  // store our final video's chunks
  if (evt.data.size > 0) {
    chunks.push(evt.data);
  }

}

function stopRecording() {
    // stop everything, this will trigger recorder.onstop
    recorder.stop();
    clearInterval(switchInterval);
    stopCanvasAnim();
    a.style.display = b.style.display = 'none';
    this.parentNode.innerHTML = "";
    recordingCtx.canvas.parentNode.removeChild(recordingCtx.canvas)
  }
  // when we've got everything

function exportVideo() {
  vid.src = URL.createObjectURL(new Blob(chunks));
}

// switch between 1 and 0
function switchStream() {
    current = +!current;
  }
  // some fancy drawings
var stopCanvasAnim = (function initCanvasDrawing() {

  var aCtx = canvases[0].getContext('2d'),
    bCtx = canvases[1].getContext('2d');

  var objects = [],
    w = canvases[0].width,
    h = canvases[0].height;
  aCtx.fillStyle = bCtx.fillStyle = 'ivory';
  // taken from http://stackoverflow.com/a/23486828/3702797
  for (var i = 0; i < 100; i++) {
    objects.push({
      angle: Math.random() * 360,
      x: 100 + (Math.random() * w / 2),
      y: 100 + (Math.random() * h / 2),
      radius: 10 + (Math.random() * 40),
      speed: 1 + Math.random() * 20
    });
  }
  var stop = false;
  var draw = function() {

    aCtx.fillRect(0, 0, w, h);
    bCtx.fillRect(0, 0, w, h);
    for (var n = 0; n < 100; n++) {
      var entity = objects[n],
        velY = Math.cos(entity.angle * Math.PI / 180) * entity.speed,
        velX = Math.sin(entity.angle * Math.PI / 180) * entity.speed;

      entity.x += velX;
      entity.y -= velY;

      aCtx.drawImage(imgA, entity.x, entity.y, entity.radius, entity.radius);
      bCtx.drawImage(imgB, entity.x, entity.y, entity.radius, entity.radius);

      entity.angle++;
    }
    if (!stop) {
      requestAnimationFrame(draw);
    }
  }


  var imgA = new Image();
  var imgB = new Image();
  imgA.onload = function() {
    draw();
    startRecording();
  };
  imgA.crossOrigin = imgB.crossOrigin = 'anonymous';
  imgA.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
  imgB.src = "https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png";

  return function() {
    stop = true;
  };
})();
<p>
  <button id="stopRec">stop recording</button>
</p>
<canvas id="a"></canvas>
<canvas id="b"></canvas>
<video id="vid" controls></video>

关于javascript - 使用 captureStream 和 mediaRecorder 进行 Canvas 记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39874867/

相关文章:

javascript - AWS API Gateway Websocket Api - 向所有连接的客户端广播消息

javascript - 限制为每秒两次键盘输入

android - 安卓录音机

android - 如何在 TextureView 上使用相机预览录制视频

javascript - 完成 html 视频后转到其他页面

javascript - JS 目录的 link_to anchor

javascript - 如何在 HTML5 Canvas 中禁用形状抗锯齿? (imageSmoothingEnabled 和像素化不起作用)

Android:如何使用 MediaRecorder 类生成 .WAV 文件?

Javascript 函数未在 jquery onready 之外定义

javascript - HTML 5 - 如何缩放 Canvas 背景图像