p5.record.js
Version:
p5.js addon providing functions to record HTML canvas based sketches
100 lines (87 loc) • 2.31 kB
text/typescript
import { Zip, FileEntry } from "./Zip";
export const imageTypesMap = {
"image/png": "png",
"image/jpeg": "jpg",
"image/webp": "webp"
};
export class SequenceRecorder extends EventTarget {
#counter = 0;
#source: HTMLCanvasElement;
#images = [];
#animationFrame: number;
#timePerFrame: number;
#deltaTime: number;
#previousTimestamp: number;
#accumulator = 0;
#mimeType: keyof typeof imageTypesMap;
state = "inactive";
constructor(
source: HTMLCanvasElement,
mimeType: keyof typeof imageTypesMap,
frameRate = 60
) {
super();
this.#source = source;
this.#timePerFrame = 1000 / frameRate;
this.#mimeType = mimeType;
}
start() {
this.state = "recording";
this.dispatchEvent(new CustomEvent("start"));
this.frame();
}
stop() {
this.state = "inactive";
if(this.#animationFrame){
cancelAnimationFrame(this.#animationFrame);
}
const buffersPromise = Promise.all(this.#images.map((image) => {
return image.arrayBuffer();
}));
const zip = new Zip();
buffersPromise.then((buffers) => {
for(let i=0; i<buffers.length; i++){
const file = new FileEntry(
`capture-${String(i+1).padStart(5, "0")}.${imageTypesMap[this.#mimeType]}`,
new Uint8Array(buffers[i])
);
zip.addFile(file);
}
this.dispatchEvent(new CustomEvent("stop", {
detail: {
blob: zip.pack()
}
}));
});
}
pause() {
this.state = "paused";
if(this.#animationFrame){
cancelAnimationFrame(this.#animationFrame);
}
this.dispatchEvent(new CustomEvent("pause"));
}
resume() {
this.state = "recording";
this.dispatchEvent(new CustomEvent("resume"));
this.frame();
}
frame(timestamp?: DOMHighResTimeStamp) {
if(this.state === "recording"){
this.#deltaTime = timestamp - this.#previousTimestamp;
this.#previousTimestamp = timestamp;
this.#accumulator += this.#deltaTime || 0;
if(this.#accumulator >= this.#timePerFrame || this.#timePerFrame === Number.POSITIVE_INFINITY){
this.#accumulator = 0;
let c = this.#counter;
this.#source.toBlob((blob) => {
this.#images[c] = blob;
}, this.#mimeType);
this.#counter++;
}
if(this.#timePerFrame < Number.POSITIVE_INFINITY){
this.#animationFrame = requestAnimationFrame(this.frame.bind(this));
}
}
}
}