180 lines
4.9 KiB
TypeScript
180 lines
4.9 KiB
TypeScript
// camera.ts
|
|
import { updateResizerVisibility } 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;
|
|
|
|
interface StreamResponse {
|
|
cam: string;
|
|
available: boolean;
|
|
source: string;
|
|
}
|
|
|
|
export function initCamera(): void {
|
|
rightPanel = document.getElementById("rightPanel");
|
|
camImage = document.getElementById("camImage") as HTMLImageElement;
|
|
crosshair = document.getElementById("crosshair");
|
|
|
|
// Проверяем доступность потока
|
|
checkStreamAvailability();
|
|
|
|
const btn = document.getElementById("camToggle");
|
|
|
|
const streamUrl = "/api/cam";
|
|
|
|
function open(): void {
|
|
if (isOpen) return;
|
|
|
|
isOpen = true;
|
|
|
|
if (rightPanel) {
|
|
rightPanel.classList.remove("hidden");
|
|
}
|
|
|
|
// Показываем перекрестие
|
|
if (crosshair) {
|
|
crosshair.style.display = "block";
|
|
}
|
|
|
|
if (camImage) {
|
|
camImage.src = streamUrl;
|
|
}
|
|
|
|
if (btn) {
|
|
const textSpan = btn.querySelector('.cam-btn-text');
|
|
if (textSpan) textSpan.textContent = 'Скрыть';
|
|
}
|
|
|
|
console.log("[camera] opened");
|
|
|
|
updateResizerVisibility();
|
|
|
|
// Запускаем мониторинг потока
|
|
startStreamMonitoring();
|
|
}
|
|
|
|
function close(): void {
|
|
if (!isOpen) return;
|
|
|
|
isOpen = false;
|
|
|
|
// Скрываем перекрестие
|
|
if (crosshair) {
|
|
crosshair.style.display = "none";
|
|
}
|
|
|
|
// остановка stream
|
|
if (camImage) {
|
|
camImage.src = "";
|
|
}
|
|
|
|
if (rightPanel) {
|
|
rightPanel.classList.add("hidden");
|
|
}
|
|
|
|
if (btn) {
|
|
const textSpan = btn.querySelector('.cam-btn-text');
|
|
if (textSpan) textSpan.textContent = 'Камера';
|
|
}
|
|
|
|
console.log("[camera] closed");
|
|
|
|
updateResizerVisibility();
|
|
|
|
// Останавливаем мониторинг
|
|
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);
|
|
|
|
// Проверка доступности камеры
|
|
async function checkStreamAvailability(): Promise<void> {
|
|
try {
|
|
const response = await fetch('/api/stream');
|
|
const data: StreamResponse = await response.json();
|
|
|
|
if (data.available && data.cam) {
|
|
console.log("[camera] stream available:", data.source);
|
|
// Автозапуск если камера доступна
|
|
if (!isOpen) {
|
|
open();
|
|
}
|
|
} else {
|
|
console.log("[camera] stream not available");
|
|
if (crosshair) {
|
|
crosshair.style.display = 'none';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log("[camera] check failed:", error);
|
|
}
|
|
}
|
|
|
|
// Мониторинг состояния потока
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Экспортируем для возможности ручного управления
|
|
export function showCrosshair(show: boolean = true): void {
|
|
const crosshairElem = document.getElementById("crosshair");
|
|
if (crosshairElem) {
|
|
crosshairElem.style.display = show ? "block" : "none";
|
|
}
|
|
}
|
|
|
|
export function setCrosshairOpacity(opacity: string | number): void {
|
|
const crosshairElem = document.getElementById("crosshair");
|
|
if (crosshairElem) {
|
|
crosshairElem.style.opacity = String(opacity);
|
|
}
|
|
}
|