Skip to content

Instantly share code, notes, and snippets.

@RezSat
Last active April 24, 2026 20:47
Show Gist options
  • Select an option

  • Save RezSat/81ae6c6bce82ae3ba45a0c27ccb2d464 to your computer and use it in GitHub Desktop.

Select an option

Save RezSat/81ae6c6bce82ae3ba45a0c27ccb2d464 to your computer and use it in GitHub Desktop.
High-fidelity 60FPS canvas recording utility.
/**
* @file capture.js
* @description High-fidelity 60FPS canvas recording utility.
* @author Yehan Wasura (RezSat)a
* @contact yehanwasura@duck.com
* * Technical Specs:
* - Output: VP9/WebM High-Bitrate (50Mbps - Try lowering the bitrate if output feels stuck)
* - Auto-injecting UI, frame-synced counter, and variable duration.
*/
/**
* KNOWN ISSUE: Video "Sticking" or Playback Freezing
* High bitrates (e.g., 50Mbps) at 60FPS can overwhelm the browser's encoder,
* causing it to skip I-Frames/Cues. This results in WebM files that "stuck"
* in VLC unless manually seeked.
* * WHAT TO DO:
* - Reduce bitrate to ~20-30Mbps (stable threshold for most hardware).
* - Use mediaRecorder.start(1000) to flush data chunks frequently.
*/
let mediaRecorder;
let recordedChunks = [];
let isRecording = false;
let recordTimer;
let secondsRemaining = 0;
const FPS = 60;
const setupUI = () => {
const container = document.createElement('div');
container.id = 'rezsat-capture-ui';
container.style.cssText = `
position: fixed;
bottom: 30px;
left: 30px;
display: flex;
align-items: center;
gap: 20px;
z-index: 999999; /* Max priority */
font-family: 'Courier New', monospace;
pointer-events: none;
`;
const btn = document.createElement('button');
btn.id = 'rec-btn';
btn.innerHTML = '<span style="color:#ff4444">●</span> RECORD';
btn.style.cssText = `
background: rgba(0, 0, 0, 0.5);
border: 2px solid white;
color: white;
padding: 10px 22px;
cursor: pointer;
font-weight: 900;
letter-spacing: 1px;
pointer-events: auto;
text-transform: uppercase;
/* The "Ghost Stroke" technique: */
box-shadow: 0 0 0 2px black, 0 4px 15px rgba(0,0,0,0.4);
text-shadow: 1px 1px 0px black, -1px -1px 0px black, 1px -1px 0px black, -1px 1px 0px black;
transition: transform 0.1s;
`;
const counter = document.createElement('span');
counter.id = 'rec-counter';
counter.style.cssText = `
display: none;
font-size: 1.3rem;
font-weight: bold;
color: white;
text-shadow: 2px 2px 0px black, -1px -1px 0px black;
font-variant-numeric: tabular-nums;
`;
container.appendChild(btn);
container.appendChild(counter);
document.body.appendChild(container);
btn.onmouseover = () => btn.style.transform = "scale(1.05)";
btn.onmouseout = () => btn.style.transform = "scale(1)";
btn.onclick = () => {
if (!isRecording) {
const duration = prompt("Set recording length (seconds):", "10");
if (duration === null) return;
const seconds = parseFloat(duration);
if (!isNaN(seconds) && seconds > 0) {
startRecording(seconds);
} else {
alert("Enter a valid number.");
}
}
};
};
if (document.readyState === 'complete') setupUI();
else window.addEventListener('load', setupUI);
function startRecording(durationSeconds) {
const canvas = document.getElementById("defaultCanvas0");
if (!canvas) return;
const stream = canvas.captureStream(FPS);
recordedChunks = [];
// EXTREME BITRATE & PRO CODECS
// Using 50Mbps for near-lossless quality on complex fluid textures (Try lowering the rate if feels stucked)
let options = {
mimeType: "video/webm;codecs=vp9",
videoBitsPerSecond: 50000000
};
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
options.mimeType = "video/webm;codecs=h264";
}
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = (e) => {
if (e.data && e.data.size > 0) recordedChunks.push(e.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: "video/webm" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `fluid-sim-${new Date().getTime()}.webm`;
a.click();
document.getElementById('rec-counter').style.display = 'none';
document.getElementById('rec-btn').style.display = 'block';
};
mediaRecorder.start();
isRecording = true;
secondsRemaining = durationSeconds;
const counterEl = document.getElementById('rec-counter');
const btnEl = document.getElementById('rec-btn');
btnEl.style.display = 'none';
counterEl.style.display = 'inline';
counterEl.innerText = `[ RECORDING: ${secondsRemaining.toFixed(1)}s ]`;
const startTime = Date.now();
recordTimer = setInterval(() => {
const elapsed = (Date.now() - startTime) / 1000;
const remaining = Math.max(0, durationSeconds - elapsed);
counterEl.innerText = `[ RECORDING: ${remaining.toFixed(1)}s ]`;
if (remaining <= 0) {
clearInterval(recordTimer);
stopRecording();
}
}, 100);
}
function stopRecording() {
if (mediaRecorder && isRecording) {
mediaRecorder.stop();
isRecording = false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment