class ViewportCapture {
constructor() {
this.stream = null;
this.isInitialized = false;
}
// Initialize capture stream - requires user interaction
async initialize() {
try {
this.stream = await navigator.mediaDevices.getDisplayMedia({
video: {
mediaSource: 'browser', // Hints at browser content
displaySurface: 'browser', // Specifically requests browser surface
logicalSurface: true, // Requests logical surface (tab content)
cursor: 'never', // Excludes cursor
width: { ideal: window.innerWidth }, // Match viewport dimensions
height: { ideal: window.innerHeight }, // Match viewport dimensions
frameRate: { ideal: 1, max: 5 }
},
audio: false,
preferCurrentTab: true // Non-standard but some browsers may respect it
});
this.isInitialized = true;
} catch (error) {
this.stop();
throw new Error(`Failed to initialize screen capture: ${error.message}`);
}
}
// Capture single frame from initialized stream
async captureFrame() {
if (!this.isInitialized) {
throw new Error('Screen capture not initialized. Call initialize() first.');
}
const video = document.createElement('video');
video.muted = true;
video.srcObject = this.stream;
await video.play();
// Wait for current frame
await new Promise((resolve) => {
const checkVideo = () => {
if (video.readyState >= video.HAVE_CURRENT_DATA) {
resolve();
} else {
requestAnimationFrame(checkVideo);
}
};
checkVideo();
});
// Capture single frame
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0);
// Clean up video element (stream stays alive)
video.srcObject = null;
return {
dataURL: canvas.toDataURL('image/png'),
blob: await new Promise(resolve => canvas.toBlob(resolve, 'image/png')),
canvas: canvas
};
}
stop() {
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
this.stream = null;
this.isInitialized = false;
}
}
}
// Usage Example
const capturer = new ViewportCapture();
// Step 1: User interaction required for initialization
document.getElementById('enableButton').addEventListener('click', async () => {
try {
await capturer.initialize();
document.getElementById('status').textContent = 'Screen capture ready!';
} catch (error) {
console.error('Failed to initialize capture:', error);
}
});
// Step 2: Any trigger can capture frames (clap detection, timer, etc.)
handleClap(async () => {
try {
const result = await capturer.captureFrame();
// Download the screenshot
const link = document.createElement('a');
link.download = `screenshot-${Date.now()}.png`;
link.href = result.dataURL;
link.click();
console.log('Frame captured!');
} catch (error) {
console.error('Frame capture failed:', error);
}
});
// Could also be triggered by other events:
// document.addEventListener('keydown', (e) => {
// if (e.key === ' ') capturer.captureFrame();
// });