// camera.ts import { updateResizerVisibility, openRightPanel, closeRightPanel } from './layout.js'; let rightPanel: HTMLElement | null = null; let camImage: HTMLImageElement | null = null; let crosshair: HTMLElement | null = null; let isOpen: boolean = false; let streamCheckInterval: number | null = null; let resizer: HTMLElement | null = null; export function initCamera(): void { rightPanel = document.getElementById("rightPanel"); camImage = document.getElementById("camImage") as HTMLImageElement; crosshair = document.getElementById("crosshair"); resizer = document.getElementById("dragBar"); const btn = document.getElementById("camToggle"); const streamUrl = "/api/cam"; function open(): void { if (isOpen) return; isOpen = true; // Открываем правую панель через layout openRightPanel(); // Показываем перекрестие if (crosshair) { crosshair.style.display = "block"; crosshair.style.opacity = "0.5"; } if (camImage) { camImage.src = streamUrl; } if (btn) { const textSpan = btn.querySelector('.cam-btn-text'); if (textSpan) { textSpan.textContent = 'Скрыть'; } else { btn.innerHTML = '📷 Скрыть'; } } console.log("[camera] opened"); // Запускаем мониторинг потока startStreamMonitoring(); } function close(): void { if (!isOpen) return; isOpen = false; // Скрываем перекрестие if (crosshair) { crosshair.style.display = "none"; } // Останавливаем поток if (camImage) { camImage.src = ""; } // Закрываем правую панель через layout closeRightPanel(); if (btn) { const textSpan = btn.querySelector('.cam-btn-text'); if (textSpan) { textSpan.textContent = 'Камера'; } else { btn.innerHTML = '📷 Камера'; } } console.log("[camera] closed"); // Останавливаем мониторинг stopStreamMonitoring(); } function toggle(): void { if (isOpen) { close(); } else { open(); } } if (camImage) { camImage.onload = () => { console.log("[camera] stream started"); if (crosshair) { crosshair.style.opacity = '1'; } }; camImage.onerror = (e) => { console.error("[camera] stream error", e); if (crosshair) { crosshair.style.opacity = '0.3'; } }; } btn?.addEventListener("click", toggle); // Мониторинг состояния потока function startStreamMonitoring(): void { stopStreamMonitoring(); streamCheckInterval = window.setInterval(() => { if (isOpen && camImage) { if (!camImage.complete || camImage.naturalWidth === 0) { if (crosshair) { crosshair.style.opacity = '0.3'; } } else { if (crosshair) { crosshair.style.opacity = '1'; } } } }, 2000); } function stopStreamMonitoring(): void { if (streamCheckInterval) { clearInterval(streamCheckInterval); streamCheckInterval = null; } } } // ============================================ // ПУБЛИЧНЫЕ ФУНКЦИИ (используются в других модулях) // ============================================ /** * Показать/скрыть перекрестие на видео * @param show - показывать или скрыть */ // noinspection JSUnusedGlobalSymbols export function showCrosshair(show: boolean = true): void { const crosshairElem = document.getElementById("crosshair"); if (crosshairElem) { crosshairElem.style.display = show ? "block" : "none"; } } /** * Установить прозрачность перекрестия * @param opacity - значение прозрачности (0-1) */ // noinspection JSUnusedGlobalSymbols export function setCrosshairOpacity(opacity: string | number): void { const crosshairElem = document.getElementById("crosshair"); if (crosshairElem) { crosshairElem.style.opacity = String(opacity); } }