add screenshot button to webcam

This commit is contained in:
KingKDot
2026-03-09 00:30:52 -05:00
parent 6c3959d210
commit 768b93f02a
2 changed files with 38 additions and 0 deletions
+35
View File
@@ -10,6 +10,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
const clientLabel = document.getElementById("clientLabel"); const clientLabel = document.getElementById("clientLabel");
const startBtn = document.getElementById("startBtn"); const startBtn = document.getElementById("startBtn");
const stopBtn = document.getElementById("stopBtn"); const stopBtn = document.getElementById("stopBtn");
const screenshotBtn = document.getElementById("screenshotBtn");
const cameraSelect = document.getElementById("cameraSelect"); const cameraSelect = document.getElementById("cameraSelect");
const refreshCameras = document.getElementById("refreshCameras"); const refreshCameras = document.getElementById("refreshCameras");
const fpsInput = document.getElementById("fpsInput"); const fpsInput = document.getElementById("fpsInput");
@@ -30,6 +31,33 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
let h264TimestampUs = 0; let h264TimestampUs = 0;
let availableDevices = []; let availableDevices = [];
let selectedDeviceIndex = 0; let selectedDeviceIndex = 0;
let hasRenderedFrame = false;
function buildScreenshotFilename() {
const ts = new Date().toISOString().replace(/[:.]/g, "-");
return `webcam-${clientId}-${ts}.jpg`;
}
function downloadScreenshot() {
if (!hasRenderedFrame) {
setStreamState("error", "No frame available for screenshot");
return;
}
canvas.toBlob((blob) => {
if (!blob) {
setStreamState("error", "Failed to encode screenshot");
return;
}
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = buildScreenshotFilename();
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}, "image/jpeg", 0.92);
}
function selectedDeviceMaxFps() { function selectedDeviceMaxFps() {
const selected = availableDevices.find((dev) => (Number(dev.index) || 0) === selectedDeviceIndex); const selected = availableDevices.find((dev) => (Number(dev.index) || 0) === selectedDeviceIndex);
@@ -137,6 +165,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
try { try {
videoDecoder = new VideoDecoder({ videoDecoder = new VideoDecoder({
output: (frame) => { output: (frame) => {
hasRenderedFrame = true;
const width = frame.displayWidth || frame.codedWidth || canvas.width; const width = frame.displayWidth || frame.codedWidth || canvas.width;
const height = frame.displayHeight || frame.codedHeight || canvas.height; const height = frame.displayHeight || frame.codedHeight || canvas.height;
if (width > 0 && height > 0 && (canvas.width !== width || canvas.height !== height)) { if (width > 0 && height > 0 && (canvas.width !== width || canvas.height !== height)) {
@@ -190,6 +219,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
const streamLocked = streamState === "streaming" || streamState === "starting" || streamState === "stopping"; const streamLocked = streamState === "streaming" || streamState === "starting" || streamState === "stopping";
startBtn.disabled = !wsOpen || streamState === "starting" || streamState === "streaming"; startBtn.disabled = !wsOpen || streamState === "starting" || streamState === "streaming";
stopBtn.disabled = !wsOpen || (streamState !== "starting" && streamState !== "streaming"); stopBtn.disabled = !wsOpen || (streamState !== "starting" && streamState !== "streaming");
screenshotBtn.disabled = !hasRenderedFrame;
refreshCameras.disabled = !wsOpen; refreshCameras.disabled = !wsOpen;
applyFps.disabled = !wsOpen || streamLocked; applyFps.disabled = !wsOpen || streamLocked;
fpsInput.disabled = !wsOpen || streamLocked; fpsInput.disabled = !wsOpen || streamLocked;
@@ -218,6 +248,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
canvas.width = bitmap.width; canvas.width = bitmap.width;
canvas.height = bitmap.height; canvas.height = bitmap.height;
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height); ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height);
hasRenderedFrame = true;
bitmap.close(); bitmap.close();
updateViewerFps(); updateViewerFps();
setStreamState("streaming", "Streaming"); setStreamState("streaming", "Streaming");
@@ -375,5 +406,9 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
applyFpsSettings(); applyFpsSettings();
}); });
screenshotBtn.addEventListener("click", () => {
downloadScreenshot();
});
connect(); connect();
})(); })();
+3
View File
@@ -58,6 +58,9 @@
<button id="stopBtn" class="button ghost"> <button id="stopBtn" class="button ghost">
<i class="fa-solid fa-stop"></i><span>Stop</span> <i class="fa-solid fa-stop"></i><span>Stop</span>
</button> </button>
<button id="screenshotBtn" class="button ghost" title="Download current frame as JPG">
<i class="fa-solid fa-camera"></i><span>Screenshot</span>
</button>
</div> </div>
<div <div
id="streamStatus" id="streamStatus"