I'm trying to record a HTML canvas animation using canvas.captureStream.
However, I don't want to record the canvas live.
Instead, I want to record on a per-frame basis, only once the canvas has been fully drawn.
In order to record on a per-frame basis, I provide a framerate of 0 by using canvas.captureStream(0). This should make it so a frame is only captured when stream.requestFrame() is called.
However, I have no idea what to do from here to access the stream, or save it as a video file. Ideally I could use MediaRecorder, but MediaRecorder.ondataavailable is not synchronized with requestFrame(). This means it records everything, ignoring requestFrame() entirely.
Below is an example illustrating my issue:
let stream;
let recording;
let downloadButton;
let chunks = [];
let recorder;
let interval;
function setup() {
canvas = document.getElementById("canvas");
recording = document.getElementById("recording");
downloadButton = document.getElementById("downloadButton");
ctx = canvas.getContext("2d");
stream = canvas.captureStream(0);
recorder = new MediaRecorder(stream);
recorder.ondataavailable = function(event) {
if (event.data && event.data.size > 0) {
chunks.push(event.data);
}
}
recorder.onstop = function() {
let recordedBlob = new Blob(chunks, { type: "video/webm" });
recording.src = URL.createObjectURL(recordedBlob);
downloadButton.href = recording.src;
downloadButton.download = "RecordedVideo.webm";
}
}
function toggleAnimation() {
if (interval) {
stream.getTracks().forEach(track => track.stop());
recorder.stop();
window.clearInterval(interval);
interval = undefined;
} else {
// I know this could be animated a lot faster, but this is just an example
// Imagine something like raytracing, where it takes much longer to render a frame
interval = window.setInterval(function() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.fillRect(canvas.width / 2 + (Math.random() * 50), canvas.height / 2 + (Math.random() * 50), 32, 32);
ctx.fillStyle = "black";
ctx.font = "16px Arial";
ctx.fillText("How can I get this to capture frames", 16, 16);
ctx.fillText("only when requestFrame() is called?", 16, 32);
// This ideally would capture only the moving frames
stream.getVideoTracks()[0].requestFrame();
}, 500); // Example delay of 0.5 seconds between frames
recorder.start();
}
}
<body onload="setup();">
<input type="button" value="Play and Record Animation / Stop and Save Recording" onclick="toggleAnimation();">
<a id="downloadButton" class="button">Download Video</a>
<div>
<canvas id="canvas" width="320" height="240" style="border: 1px solid black;"></canvas>
<video id="recording" width="320" height="240" controls></video>
</div>
</body>
Although the delay in this example could be avoided by lowering the time delay, imagine a situation such as ray-tracing where there would be a much more significant lag between frames. This is the type of issue I am trying to get around.
Any help would be greatly appreciated!