add screenshot button to webcam
This commit is contained in:
@@ -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();
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user