Using a original javascript
at MediaRecorder-examples/record-canvas-to-video.js
Software requirements
- Firefox 45. This is a Firefox technical demo. So it might not work on your browser, if it doesn't implement what we're demoing. At the time of writing (January 2016), you need to download either Firefox Developer Edition or Firefox Nightly.
window.onload = function () {
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var width = canvas.width;
var height = canvas.height;
var capturing = false;
video.width = width;
video.height = height;
// We need the 2D context to individually manipulate pixel data
var ctx = canvas.getContext('2d');
// Start with a black background
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, width, height);
// Since we're continuously accessing and overwriting the pixels
// object, we'll request it once and reuse it across calls to draw()
// for best performance (we don't need to create ImageData objects
// on every frame)
var pixels = ctx.getImageData(0, 0, width, height);
var data = pixels.data;
var numPixels = data.length;
var stream = canvas.captureStream(15);
var recorder = new MediaRecorder(stream);
recorder.addEventListener('dataavailable', finishCapturing);
startCapturing();
recorder.start();
setTimeout(function() {
recorder.stop();
}, 2000);
function startCapturing() {
capturing = true;
draw();
}
function finishCapturing(e) {
capturing = false;
var videoData = [ e.data ];
var blob = new Blob(videoData, { 'type': 'video/webm' });
var videoURL = URL.createObjectURL(blob);
video.src = videoURL;
video.play();
}
function draw() {
// We don't want to render again if we're not capturing
if(capturing) {
requestAnimationFrame(draw);
}
drawWhiteNoise();
}
function drawWhiteNoise() {
var offset = 0;
for(var i = 0; i < numPixels; i++) {
var grey = Math.round(Math.random() * 255);
// The data array has pixel values in RGBA order
// (Red, Green, Blue and Alpha for transparency)
// We will make R, G and B have the same value ('grey'),
// then skip the Alpha value by increasing the offset,
// as we're happy with the opaque value we set when painting
// the background black at the beginning
data[offset++] = grey;
data[offset++] = grey;
data[offset++] = grey;
offset++; // skip the alpha component
}
// And tell the context to draw the updated pixels in the canvas
ctx.putImageData(pixels, 0, 0);
}
};
produces errors at chromium 55
Uncaught (in promise) DOMException: The play() request was interrupted by a new load request.
Failed to load resource: the server responded with a status of 416 (Requested Range Not Satisfiable)
though returns expected result at firefox 52.
Adjusting javascript
for use at chromium by pushing Blob
at dataavailable
event of MediaRecorder
to an array, then concatenating blobs at stop
event
window.onload = function () {
var blobs = [];
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var width = canvas.width;
var height = canvas.height;
var capturing = false;
video.width = width;
video.height = height;
// We need the 2D context to individually manipulate pixel data
var ctx = canvas.getContext('2d');
// Start with a black background
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, width, height);
// Since we're continuously accessing and overwriting the pixels
// object, we'll request it once and reuse it across calls to draw()
// for best performance (we don't need to create ImageData objects
// on every frame)
var pixels = ctx.getImageData(0, 0, width, height);
var data = pixels.data;
var numPixels = data.length;
var stream = canvas.captureStream(15);
var recorder = new MediaRecorder(stream);
recorder.addEventListener('dataavailable', finishCapturing);
recorder.addEventListener('stop', function(e) {
video.oncanplay = video.play;
video.src = URL.createObjectURL(new Blob(blobs, {type:"video/webm"}));
});
startCapturing();
recorder.start();
setTimeout(function() {
capturing = false;
recorder.stop();
}, 2000);
function startCapturing() {
capturing = true;
draw();
}
function finishCapturing(e) {
blobs.push(e.data);
}
function draw() {
// We don't want to render again if we're not capturing
if(capturing) {
requestAnimationFrame(draw);
}
drawWhiteNoise();
}
function drawWhiteNoise() {
var offset = 0;
for(var i = 0; i < numPixels; i++) {
var grey = Math.round(Math.random() * 255);
// The data array has pixel values in RGBA order
// (Red, Green, Blue and Alpha for transparency)
// We will make R, G and B have the same value ('grey'),
// then skip the Alpha value by increasing the offset,
// as we're happy with the opaque value we set when painting
// the background black at the beginning
data[offset++] = grey;
data[offset++] = grey;
data[offset++] = grey;
offset++; // skip the alpha component
}
// And tell the context to draw the updated pixels in the canvas
ctx.putImageData(pixels, 0, 0);
}
};
renders the recorded stream similarly to firefox.
However, the adjustments made to play video at both firefox and chromium render with apparent minimal, though noticeable delay between the concatenated blobs.
How can we render the same visual playback of canvas.captureStream()
recorded using MediaRecorder()
at <video>
element?
You're driving the animation from the main JS thread here, so it's possible other main thread JS activities - like the ondataavailable
callback firing - could disrupt timing enough to be noticeable.
Try omitting the (60) framerate from the canvas.captureStream()
call.
MDN says: "If not set, a new frame will be captured each time the canvas changes; if set to 0, one single frame will be captured."
This should hopefully make the output more impervious to such interruptions, at the cost of shortening its length marginally.
You can also specify a timeslice with the start method, e.g. recorder.start(2000)
to limit when the dataavailable
event is fires to avoid interruptions.