import type { CapturedEventEvidence } from "./event-evidence-capture.js"; export async function captureMediaStreamFrame( stream: MediaStream, now: () => number = Date.now, ): Promise { const track = stream.getVideoTracks()[0]; if (!track || track.readyState === "ended") { throw new Error("screen-share video track is unavailable"); } const video = document.createElement("video"); video.muted = true; video.playsInline = true; video.srcObject = stream; try { await Promise.race([ video.play(), new Promise((_, reject) => setTimeout(() => reject(new Error("screen frame timed out")), 2_000), ), ]); if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) { await Promise.race([ new Promise((resolve) => video.addEventListener("loadeddata", () => resolve(), { once: true }), ), new Promise((_, reject) => setTimeout(() => reject(new Error("screen frame timed out")), 2_000), ), ]); } const settings = track.getSettings(); const width = video.videoWidth || settings.width || 0; const height = video.videoHeight || settings.height || 0; if (!width || !height) throw new Error("screen frame has no dimensions"); const canvas = document.createElement("canvas"); const scale = Math.min(1, 1280 / width); canvas.width = Math.max(1, Math.round(width * scale)); canvas.height = Math.max(1, Math.round(height * scale)); const context = canvas.getContext("2d"); if (!context) throw new Error("screen frame canvas is unavailable"); const capturedAt = now(); context.drawImage(video, 0, 0, canvas.width, canvas.height); const blob = await canvasToJpeg(canvas); return { blob, capturedAt, width: canvas.width, height: canvas.height, source: "screen-share", syncQuality: "exact", }; } finally { video.pause(); video.srcObject = null; } } function canvasToJpeg(canvas: HTMLCanvasElement): Promise { return new Promise((resolve, reject) => { canvas.toBlob( (blob) => blob ? resolve(blob) : reject(new Error("screen frame JPEG encoding failed")), "image/jpeg", 0.82, ); }); }