show if users have webcams and make sorting options better for windows and webcam. Also make webcam settings sticky.
This commit is contained in:
@@ -120,6 +120,7 @@ function clientQueryParams(page = state.page) {
|
||||
os: state.filterOs || "all",
|
||||
country: state.filterCountry || "all",
|
||||
group: state.filterGroup || "all",
|
||||
webcam: state.filterWebcam || "all",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,6 +223,11 @@ function updateOsFilter(items) {
|
||||
allOption.textContent = "All OS";
|
||||
osSelect.appendChild(allOption);
|
||||
|
||||
const windowsOption = document.createElement("option");
|
||||
windowsOption.value = "windows";
|
||||
windowsOption.textContent = "Windows";
|
||||
osSelect.appendChild(windowsOption);
|
||||
|
||||
osArray.forEach((os) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = os;
|
||||
@@ -229,7 +235,7 @@ function updateOsFilter(items) {
|
||||
osSelect.appendChild(option);
|
||||
});
|
||||
|
||||
if (currentValue !== "all" && osList.has(currentValue)) {
|
||||
if (currentValue === "windows" || (currentValue !== "all" && osList.has(currentValue))) {
|
||||
osSelect.value = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2277,6 +2277,7 @@ html.nav-pre-sidebar.nav-pre-collapsed #top-nav:empty::before {
|
||||
#filter-status,
|
||||
#filter-os,
|
||||
#filter-group,
|
||||
#filter-webcam,
|
||||
#sort {
|
||||
transition:
|
||||
border-color 150ms ease,
|
||||
@@ -2287,6 +2288,7 @@ html.nav-pre-sidebar.nav-pre-collapsed #top-nav:empty::before {
|
||||
#filter-status:focus,
|
||||
#filter-os:focus,
|
||||
#filter-group:focus,
|
||||
#filter-webcam:focus,
|
||||
#sort:focus {
|
||||
border-color: rgba(100, 116, 139, 0.82) !important;
|
||||
box-shadow: none !important;
|
||||
@@ -2715,6 +2717,7 @@ textarea,
|
||||
#filter-status,
|
||||
#filter-os,
|
||||
#filter-group,
|
||||
#filter-webcam,
|
||||
#sort,
|
||||
#console-input {
|
||||
background-color: var(--ui-surface);
|
||||
@@ -2735,6 +2738,7 @@ textarea:focus,
|
||||
#filter-status:focus,
|
||||
#filter-os:focus,
|
||||
#filter-group:focus,
|
||||
#filter-webcam:focus,
|
||||
#sort:focus,
|
||||
#console-input:focus {
|
||||
border-color: var(--ui-border-strong) !important;
|
||||
@@ -2951,6 +2955,9 @@ tbody tr:hover {
|
||||
.cv-pill-admin { background: rgba(234, 179, 8, 0.12); color: #fde68a; border-color: rgba(234, 179, 8, 0.3); }
|
||||
.cv-pill-system { background: rgba(239, 68, 68, 0.12); color: #fca5a5; border-color: rgba(239, 68, 68, 0.3); }
|
||||
.cv-pill-ti { background: rgba(168, 85, 247, 0.12); color: #d8b4fe; border-color: rgba(168, 85, 247, 0.3); }
|
||||
.cv-pill-webcam { background: rgba(20, 184, 166, 0.12); color: #99f6e4; border-color: rgba(20, 184, 166, 0.3); }
|
||||
.cv-mini-pill.cv-webcam-btn { padding: 0; cursor: pointer; }
|
||||
.cv-mini-pill.cv-webcam-btn:hover { background: rgba(20, 184, 166, 0.20); border-color: rgba(20, 184, 166, 0.5); }
|
||||
.cv-pill-muted { background: rgba(245, 158, 11, 0.12); color: #fcd34d; border-color: rgba(245, 158, 11, 0.3); }
|
||||
.cv-pill-macperm.is-ok { background: rgba(16, 185, 129, 0.12); color: #86efac; border-color: rgba(16, 185, 129, 0.3); }
|
||||
.cv-pill-macperm.is-warn { background: rgba(245, 158, 11, 0.12); color: #fcd34d; border-color: rgba(245, 158, 11, 0.3); }
|
||||
@@ -3228,6 +3235,8 @@ tbody tr:hover {
|
||||
}
|
||||
.cv-icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.cv-icon-btn.cv-icon-sm { width: 26px; height: 26px; font-size: 11px; }
|
||||
.cv-icon-btn.cv-webcam-btn { color: #99f6e4; border-color: rgba(20, 184, 166, 0.3); }
|
||||
.cv-icon-btn.cv-webcam-btn:hover { background: rgba(20, 184, 166, 0.10); border-color: rgba(20, 184, 166, 0.45); }
|
||||
.cv-icon-btn.cv-icon-danger { color: var(--cv-danger); border-color: rgba(239, 68, 68, 0.25); }
|
||||
.cv-icon-btn.cv-icon-danger:hover { background: rgba(239, 68, 68, 0.10); border-color: rgba(239, 68, 68, 0.45); }
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ const sortSelect = document.getElementById("sort");
|
||||
const filterStatusSelect = document.getElementById("filter-status");
|
||||
const filterOsSelect = document.getElementById("filter-os");
|
||||
const filterGroupSelect = document.getElementById("filter-group");
|
||||
const filterWebcamSelect = document.getElementById("filter-webcam");
|
||||
const showOfflineToggle = document.getElementById("toggle-offline");
|
||||
const displayFieldsMenu = document.getElementById("display-fields-menu");
|
||||
const selectAllBtn = document.getElementById("select-all");
|
||||
@@ -56,6 +57,7 @@ const PREF_SORT_KEY = "overlord_sort";
|
||||
const PREF_FILTER_OS_KEY = "overlord_filter_os";
|
||||
const PREF_FILTER_COUNTRY_KEY = "overlord_filter_country";
|
||||
const PREF_FILTER_GROUP_KEY = "overlord_filter_group";
|
||||
const PREF_FILTER_WEBCAM_KEY = "overlord_filter_webcam";
|
||||
const PREF_DISPLAY_FIELDS_KEY = "overlord_display_fields";
|
||||
const DEFAULT_DISPLAY_FIELDS = {
|
||||
user: true,
|
||||
@@ -944,6 +946,7 @@ function initializeRenderer() {
|
||||
requestPreview,
|
||||
requestThumbnail,
|
||||
pingClient: (id) => sendCommand(id, "ping"),
|
||||
onOpenWebcam: (id) => window.open(`/webcam?clientId=${encodeURIComponent(id)}`, "_blank", "noopener"),
|
||||
onMacPermissionRequest: (id, _card, permissionKey) => requestMacPermissions(id, permissionKey),
|
||||
onMacPermissionRefresh: (id) => requestMacPermissions(id, "", { refreshOnly: true }),
|
||||
onMacPermissionApply: (id) => applyMacPermissionChanges(id),
|
||||
@@ -1057,6 +1060,14 @@ filterGroupSelect?.addEventListener("change", (e) => {
|
||||
loadWithOptions({ force: true, reorder: true });
|
||||
});
|
||||
|
||||
filterWebcamSelect?.addEventListener("change", (e) => {
|
||||
state.filterWebcam = e.target.value;
|
||||
localStorage.setItem(PREF_FILTER_WEBCAM_KEY, state.filterWebcam);
|
||||
state.page = 1;
|
||||
state.lastDigest = "";
|
||||
loadWithOptions({ force: true, reorder: true });
|
||||
});
|
||||
|
||||
import("./country-picker.js").then(({ initCountryPicker }) => {
|
||||
initCountryPicker((code) => {
|
||||
state.filterCountry = code;
|
||||
@@ -1078,7 +1089,7 @@ import("./country-picker.js").then(({ initCountryPicker }) => {
|
||||
}
|
||||
|
||||
const savedSort = localStorage.getItem(PREF_SORT_KEY);
|
||||
const validSorts = ["stable", "last_seen_desc", "host_asc", "ping_asc", "ping_desc", "country_asc", "country_desc", "group_asc", "group_desc", "admin_first"];
|
||||
const validSorts = ["stable", "last_seen_desc", "host_asc", "ping_asc", "ping_desc", "country_asc", "country_desc", "group_asc", "group_desc", "admin_first", "elevated_first"];
|
||||
if (savedSort && validSorts.includes(savedSort)) {
|
||||
state.sort = savedSort;
|
||||
if (sortSelect) sortSelect.value = savedSort;
|
||||
@@ -1100,6 +1111,12 @@ import("./country-picker.js").then(({ initCountryPicker }) => {
|
||||
state.filterGroup = savedGroup;
|
||||
if (filterGroupSelect) filterGroupSelect.value = savedGroup;
|
||||
}
|
||||
|
||||
const savedWebcam = localStorage.getItem(PREF_FILTER_WEBCAM_KEY);
|
||||
if (savedWebcam && ["all", "available", "none"].includes(savedWebcam)) {
|
||||
state.filterWebcam = savedWebcam;
|
||||
if (filterWebcamSelect) filterWebcamSelect.value = savedWebcam;
|
||||
}
|
||||
})();
|
||||
|
||||
showOfflineToggle?.addEventListener("change", (e) => {
|
||||
|
||||
@@ -321,6 +321,7 @@ export function createRenderer({
|
||||
requestPreview,
|
||||
requestThumbnail,
|
||||
pingClient,
|
||||
onOpenWebcam,
|
||||
onMacPermissionRequest,
|
||||
onMacPermissionRefresh,
|
||||
onMacPermissionApply,
|
||||
@@ -479,6 +480,12 @@ export function createRenderer({
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target.closest(".cv-webcam-btn")) {
|
||||
e.stopPropagation();
|
||||
if (onOpenWebcam) onOpenWebcam(clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target.closest(".cv-ping-btn")) {
|
||||
e.stopPropagation();
|
||||
if (pingClient) pingClient(clientId);
|
||||
@@ -655,7 +662,7 @@ export function createRenderer({
|
||||
}
|
||||
|
||||
function cardDigest(c) {
|
||||
return `${currentDisplayDigest}|${currentLayout}|${c.id}|${!!c.online}|${c.lastSeen}|${c.pingMs}|${c.host}|${c.user}|${c.os}|${c.arch}|${c.version}|${c.monitors}|${c.country}|${c.nickname}|${c.customTag}|${c.customTagNote}|${!!c.bookmarked}|${!!c.isAdmin}|${c.elevation}|${JSON.stringify(c.permissions || {})}|${c.cpu}|${c.gpu}|${c.ram}|${c.batteryPercent}|${c.batteryCharging}|${c.hwid}|${c.disconnectReason}|${c.disconnectDetail}|${c.groupId}|${c.groupName}|${c.groupColor}|${!!c.notificationsMuted}`;
|
||||
return `${currentDisplayDigest}|${currentLayout}|${c.id}|${!!c.online}|${c.lastSeen}|${c.pingMs}|${c.host}|${c.user}|${c.os}|${c.arch}|${c.version}|${c.monitors}|${c.country}|${c.nickname}|${c.customTag}|${c.customTagNote}|${!!c.bookmarked}|${!!c.isAdmin}|${c.elevation}|${JSON.stringify(c.permissions || {})}|${c.cpu}|${c.gpu}|${c.ram}|${c.batteryPercent}|${c.batteryCharging}|${!!c.webcamAvailable}|${JSON.stringify(c.webcamDevices || [])}|${c.hwid}|${c.disconnectReason}|${c.disconnectDetail}|${c.groupId}|${c.groupName}|${c.groupColor}|${!!c.notificationsMuted}`;
|
||||
}
|
||||
|
||||
function cardThumbDigest(c) {
|
||||
@@ -789,6 +796,7 @@ export function createRenderer({
|
||||
el.dataset.customTag = String(client.customTag || "");
|
||||
el.dataset.bookmarked = String(!!client.bookmarked);
|
||||
el.dataset.notificationsMuted = String(!!client.notificationsMuted);
|
||||
el.dataset.hasWebcam = String(!!client.webcamAvailable);
|
||||
el.dataset.admin = String(!!client.isAdmin);
|
||||
el.dataset.groupId = String(client.groupId || "");
|
||||
el.dataset.groupName = String(client.groupName || "");
|
||||
@@ -833,6 +841,16 @@ export function createRenderer({
|
||||
return node;
|
||||
}
|
||||
|
||||
function webcamButtonHtml(client, extraClass = "") {
|
||||
if (!client.webcamAvailable || isViewer) return "";
|
||||
return `<button type="button" class="cv-icon-btn cv-webcam-btn ${extraClass}" title="Open webcam" aria-label="Open webcam"><i class="fa-solid fa-video"></i></button>`;
|
||||
}
|
||||
|
||||
function webcamBadgeHtml(client) {
|
||||
if (!client.webcamAvailable || isViewer) return "";
|
||||
return `<button type="button" class="cv-mini-pill cv-pill-webcam cv-webcam-btn" title="Open webcam" aria-label="Open webcam"><i class="fa-solid fa-video"></i></button>`;
|
||||
}
|
||||
|
||||
function updateCard(card, client) {
|
||||
const wasChecked = card.querySelector(".client-checkbox")?.checked || false;
|
||||
const wasTagNoteExpanded = card.dataset.tagNoteExpanded === "true";
|
||||
@@ -914,6 +932,7 @@ export function createRenderer({
|
||||
${client.isAdmin ? `<span class="cv-mini-pill cv-pill-admin" title="Admin"><i class="fa-solid fa-shield-halved"></i></span>` : ""}
|
||||
${client.elevation === "system" ? `<span class="cv-mini-pill cv-pill-system" title="SYSTEM"><i class="fa-solid fa-gear"></i></span>` : ""}
|
||||
${client.elevation === "trustedinstaller" ? `<span class="cv-mini-pill cv-pill-ti" title="TrustedInstaller"><i class="fa-solid fa-lock"></i></span>` : ""}
|
||||
${webcamBadgeHtml(client)}
|
||||
${macPermissionBadgeHtml(client)}
|
||||
${client.notificationsMuted ? `<span class="cv-mini-pill cv-pill-muted" title="Notifications muted"><i class="fa-solid fa-bell-slash"></i></span>` : ""}
|
||||
</div>
|
||||
@@ -930,6 +949,7 @@ export function createRenderer({
|
||||
<div class="cv-spacer"></div>
|
||||
<div class="cv-actions">
|
||||
${isViewer ? "" : `<button class="command-btn cv-btn-primary" data-id="${escapeHtml(client.id)}"><i class="fa-solid fa-terminal"></i><span>Commands</span></button>`}
|
||||
${webcamButtonHtml(client)}
|
||||
<button class="cv-icon-btn cv-ping-btn" title="Ping" ${client.online ? "" : "disabled"}><i class="fa-solid fa-satellite-dish"></i></button>
|
||||
${isViewer ? "" : `<button class="cv-icon-btn cv-icon-danger ban-btn" title="Ban IP" data-id="${escapeHtml(client.id)}"><i class="fa-solid fa-ban"></i></button>`}
|
||||
<button class="cv-icon-btn cv-expand-btn" title="More info" aria-expanded="false"><i class="fa-solid fa-chevron-down"></i></button>
|
||||
@@ -993,6 +1013,7 @@ export function createRenderer({
|
||||
${client.isAdmin ? `<span class="cv-mini-pill cv-pill-admin" title="Admin"><i class="fa-solid fa-shield-halved"></i></span>` : ""}
|
||||
${client.elevation === "system" ? `<span class="cv-mini-pill cv-pill-system" title="SYSTEM"><i class="fa-solid fa-gear"></i></span>` : ""}
|
||||
${client.elevation === "trustedinstaller" ? `<span class="cv-mini-pill cv-pill-ti" title="TI"><i class="fa-solid fa-lock"></i></span>` : ""}
|
||||
${webcamBadgeHtml(client)}
|
||||
${macPermissionBadgeHtml(client)}
|
||||
${client.notificationsMuted ? `<span class="cv-mini-pill cv-pill-muted" title="Notifications muted"><i class="fa-solid fa-bell-slash"></i></span>` : ""}
|
||||
</span>
|
||||
@@ -1012,6 +1033,7 @@ export function createRenderer({
|
||||
<td class="cv-td-actions">
|
||||
<div class="cv-actions cv-actions-table">
|
||||
${isViewer ? "" : `<button class="command-btn cv-btn-primary cv-btn-sm" data-id="${escapeHtml(client.id)}"><i class="fa-solid fa-terminal"></i><span>Commands</span></button>`}
|
||||
${webcamButtonHtml(client, "cv-icon-sm")}
|
||||
${isViewer ? "" : `<button class="cv-icon-btn cv-icon-sm kebab-btn" title="More" data-id="${escapeHtml(client.id)}"><i class="fa-solid fa-ellipsis-vertical"></i></button>`}
|
||||
</div>
|
||||
</td>
|
||||
@@ -1054,6 +1076,7 @@ export function createRenderer({
|
||||
client.isAdmin ? `<span class="cv-mini-pill cv-pill-admin" title="Admin"><i class="fa-solid fa-shield-halved"></i></span>` : "",
|
||||
client.elevation === "system" ? `<span class="cv-mini-pill cv-pill-system" title="SYSTEM"><i class="fa-solid fa-gear"></i></span>` : "",
|
||||
client.elevation === "trustedinstaller" ? `<span class="cv-mini-pill cv-pill-ti" title="TrustedInstaller"><i class="fa-solid fa-lock"></i></span>` : "",
|
||||
webcamBadgeHtml(client),
|
||||
macPermissionBadgeHtml(client),
|
||||
client.notificationsMuted ? `<span class="cv-mini-pill cv-pill-muted" title="Notifications muted"><i class="fa-solid fa-bell-slash"></i></span>` : "",
|
||||
].join("");
|
||||
@@ -1102,6 +1125,7 @@ export function createRenderer({
|
||||
${macPermDetail ? `<div class="cv-card-perms">${macPermDetail}</div>` : ""}
|
||||
<div class="cv-card-actions">
|
||||
${isViewer ? "" : `<button class="command-btn cv-btn-primary cv-btn-flex" data-id="${escapeHtml(client.id)}"><i class="fa-solid fa-terminal"></i><span>Commands</span></button>`}
|
||||
${webcamButtonHtml(client)}
|
||||
<button class="cv-icon-btn cv-ping-btn" title="Ping" ${client.online ? "" : "disabled"}><i class="fa-solid fa-satellite-dish"></i></button>
|
||||
${isViewer ? "" : `<button class="cv-icon-btn cv-icon-danger ban-btn" title="Ban IP" data-id="${escapeHtml(client.id)}"><i class="fa-solid fa-ban"></i></button>`}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ export const state = {
|
||||
filterOs: "all",
|
||||
filterCountry: "all",
|
||||
filterGroup: "all",
|
||||
filterWebcam: "all",
|
||||
lastDigest: "",
|
||||
isLoading: false,
|
||||
pendingForce: false,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
|
||||
import { checkFeatureAccess } from "./feature-gate.js";
|
||||
import { WhepClient } from "./whep.js";
|
||||
import { P2PClient } from "./webrtc-p2p.js";
|
||||
import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-settings.js";
|
||||
|
||||
(async function () {
|
||||
const clientId = new URLSearchParams(location.search).get("clientId");
|
||||
@@ -58,16 +59,53 @@ import { P2PClient } from "./webrtc-p2p.js";
|
||||
let clientIsAdmin = false;
|
||||
let firewallWarningAcked = false;
|
||||
|
||||
const codecPrefKey = "webcamCodecPreferH264";
|
||||
let prefersH264 = typeof VideoDecoder === "function";
|
||||
let savedCameraIndex = null;
|
||||
|
||||
const storedCodecPref = localStorage.getItem(codecPrefKey);
|
||||
if (storedCodecPref === "0") {
|
||||
prefersH264 = false;
|
||||
} else if (storedCodecPref === "1") {
|
||||
prefersH264 = typeof VideoDecoder === "function";
|
||||
function setSelectValue(select, value) {
|
||||
if (!select || value === undefined || value === null) return false;
|
||||
const normalized = String(value);
|
||||
const exists = Array.from(select.options || []).some((opt) => opt.value === normalized);
|
||||
if (!exists) return false;
|
||||
select.value = normalized;
|
||||
return true;
|
||||
}
|
||||
|
||||
function applySavedCamera() {
|
||||
if (savedCameraIndex === null || savedCameraIndex === undefined) return false;
|
||||
if (!setSelectValue(cameraSelect, savedCameraIndex)) return false;
|
||||
selectedDeviceIndex = Number(cameraSelect.value) || 0;
|
||||
applyFpsInputLimits();
|
||||
return true;
|
||||
}
|
||||
|
||||
function applySharedSettings(settings) {
|
||||
if (!settings || typeof settings !== "object") return;
|
||||
if (Number.isFinite(Number(settings.camera))) {
|
||||
savedCameraIndex = Number(settings.camera);
|
||||
}
|
||||
if (fpsInput && settings.fps !== undefined) fpsInput.value = String(settings.fps);
|
||||
if (qualitySlider && settings.quality !== undefined) qualitySlider.value = String(settings.quality);
|
||||
setSelectValue(webrtcMode, settings.webrtcMode);
|
||||
if (typeof settings.preferH264 === "boolean") {
|
||||
prefersH264 = settings.preferH264 && typeof VideoDecoder === "function";
|
||||
}
|
||||
applySavedCamera();
|
||||
}
|
||||
|
||||
function readSharedSettings() {
|
||||
return {
|
||||
camera: Number(cameraSelect?.value ?? savedCameraIndex ?? 0),
|
||||
fps: Number(fpsInput?.value || 30),
|
||||
quality: Number(qualitySlider?.value || 90),
|
||||
preferH264: !!prefersH264,
|
||||
webrtcMode: getWebrtcMode(),
|
||||
};
|
||||
}
|
||||
|
||||
applySharedSettings(await loadSharedUiSettings("webcam"));
|
||||
const sharedSettingsSaver = createSharedUiSettingsSaver("webcam", readSharedSettings);
|
||||
|
||||
if (codecH264) {
|
||||
codecH264.checked = prefersH264;
|
||||
codecH264.disabled = typeof VideoDecoder !== "function";
|
||||
@@ -176,7 +214,9 @@ import { P2PClient } from "./webrtc-p2p.js";
|
||||
: (dev.name || `Camera ${idx + 1}`);
|
||||
cameraSelect.appendChild(opt);
|
||||
}
|
||||
selectedDeviceIndex = Number(selected) || 0;
|
||||
selectedDeviceIndex = savedCameraIndex !== null && savedCameraIndex !== undefined
|
||||
? Number(savedCameraIndex) || 0
|
||||
: Number(selected) || 0;
|
||||
const selectedOpt = Array.from(cameraSelect.options).find((o) => Number(o.value) === selectedDeviceIndex);
|
||||
if (selectedOpt) {
|
||||
cameraSelect.value = selectedOpt.value;
|
||||
@@ -185,6 +225,9 @@ import { P2PClient } from "./webrtc-p2p.js";
|
||||
}
|
||||
selectedDeviceIndex = Number(cameraSelect.value) || 0;
|
||||
applyFpsInputLimits();
|
||||
if (selectedDeviceIndex !== Number(selected || 0) && ws && ws.readyState === WebSocket.OPEN) {
|
||||
send("webcam_select", { index: selectedDeviceIndex });
|
||||
}
|
||||
}
|
||||
|
||||
function isH264KeyFrame(data) {
|
||||
@@ -466,6 +509,10 @@ import { P2PClient } from "./webrtc-p2p.js";
|
||||
|
||||
ws.onopen = () => {
|
||||
requestCameraList();
|
||||
if (savedCameraIndex !== null && savedCameraIndex !== undefined) {
|
||||
selectedDeviceIndex = Number(savedCameraIndex) || 0;
|
||||
send("webcam_select", { index: selectedDeviceIndex });
|
||||
}
|
||||
applyFpsSettings();
|
||||
pushQuality(qualitySlider ? qualitySlider.value : 90);
|
||||
if (desiredStreaming) {
|
||||
@@ -543,6 +590,7 @@ import { P2PClient } from "./webrtc-p2p.js";
|
||||
});
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
sharedSettingsSaver.saveNow();
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
send("webcam_stop");
|
||||
}
|
||||
@@ -555,9 +603,11 @@ import { P2PClient } from "./webrtc-p2p.js";
|
||||
|
||||
cameraSelect.addEventListener("change", () => {
|
||||
const index = Number(cameraSelect.value) || 0;
|
||||
savedCameraIndex = index;
|
||||
selectedDeviceIndex = index;
|
||||
applyFpsInputLimits();
|
||||
send("webcam_select", { index });
|
||||
sharedSettingsSaver.scheduleSave();
|
||||
});
|
||||
|
||||
fpsInput.addEventListener("input", () => {
|
||||
@@ -566,18 +616,19 @@ import { P2PClient } from "./webrtc-p2p.js";
|
||||
if (Number.isFinite(val) && val > maxFps) {
|
||||
fpsInput.value = String(maxFps);
|
||||
}
|
||||
sharedSettingsSaver.scheduleSave();
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
sharedSettingsSaver.scheduleSave();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -586,13 +637,21 @@ import { P2PClient } from "./webrtc-p2p.js";
|
||||
qualitySlider.addEventListener("input", function () {
|
||||
updateQualityLabel(qualitySlider.value);
|
||||
pushQuality(qualitySlider.value);
|
||||
sharedSettingsSaver.scheduleSave();
|
||||
});
|
||||
}
|
||||
|
||||
applyFps.addEventListener("click", () => {
|
||||
applyFpsSettings();
|
||||
sharedSettingsSaver.scheduleSave();
|
||||
});
|
||||
|
||||
if (webrtcMode) {
|
||||
webrtcMode.addEventListener("change", () => {
|
||||
sharedSettingsSaver.scheduleSave();
|
||||
});
|
||||
}
|
||||
|
||||
screenshotBtn.addEventListener("click", () => {
|
||||
downloadScreenshot();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user