Last active
April 24, 2026 20:47
-
-
Save RezSat/81ae6c6bce82ae3ba45a0c27ccb2d464 to your computer and use it in GitHub Desktop.
High-fidelity 60FPS canvas recording utility.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * @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