добавление креста метки на видео
This commit is contained in:
@@ -341,6 +341,70 @@ h1 {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background: black;
|
background: black;
|
||||||
}
|
}
|
||||||
|
/* ===== CAMERA OVERLAY ===== */
|
||||||
|
.cam-frame-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
background: black;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cam-frame {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Перекрестие (crosshair) */
|
||||||
|
.cam-crosshair {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Вертикальная линия */
|
||||||
|
.crosshair-v {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background: #00ff88;
|
||||||
|
box-shadow: 0 0 4px rgba(0, 255, 136, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Горизонтальная линия */
|
||||||
|
.crosshair-h {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background: #00ff88;
|
||||||
|
box-shadow: 0 0 4px rgba(0, 255, 136, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Центральный квадрат */
|
||||||
|
.crosshair-box {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border: 2px solid #00ff88;
|
||||||
|
box-shadow:
|
||||||
|
0 0 10px rgba(0, 255, 136, 0.3),
|
||||||
|
inset 0 0 10px rgba(0, 255, 136, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== MOBILE ===== */
|
/* ===== MOBILE ===== */
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
|
|||||||
@@ -86,7 +86,19 @@
|
|||||||
<div class="cam-header">
|
<div class="cam-header">
|
||||||
<span>Видео с камеры</span>
|
<span>Видео с камеры</span>
|
||||||
</div>
|
</div>
|
||||||
<img id="camImage" src="" alt="Camera" class="cam-frame">
|
<div class="cam-frame-wrapper">
|
||||||
|
<img id="camImage" src="" alt="Camera" class="cam-frame">
|
||||||
|
|
||||||
|
<!-- ПЕРЕКРЕСТИЕ -->
|
||||||
|
<div class="cam-crosshair" id="crosshair">
|
||||||
|
<!-- Основные линии -->
|
||||||
|
<div class="crosshair-v"></div>
|
||||||
|
<div class="crosshair-h"></div>
|
||||||
|
|
||||||
|
<!-- Центральный квадрат -->
|
||||||
|
<div class="crosshair-box"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ import { updateResizerVisibility } from './layout.js';
|
|||||||
|
|
||||||
let rightPanel;
|
let rightPanel;
|
||||||
let camImage;
|
let camImage;
|
||||||
let camClose;
|
let crosshair;
|
||||||
|
|
||||||
let isOpen = false;
|
let isOpen = false;
|
||||||
|
let streamCheckInterval = null;
|
||||||
|
|
||||||
export function initCamera() {
|
export function initCamera() {
|
||||||
rightPanel = document.getElementById("rightPanel");
|
rightPanel = document.getElementById("rightPanel");
|
||||||
camImage = document.getElementById("camImage");
|
camImage = document.getElementById("camImage");
|
||||||
camClose = document.getElementById("camClose");
|
crosshair = document.getElementById("crosshair");
|
||||||
|
|
||||||
|
// Проверяем доступность потока
|
||||||
|
checkStreamAvailability();
|
||||||
|
|
||||||
const btn = document.getElementById("camToggle");
|
const btn = document.getElementById("camToggle");
|
||||||
|
|
||||||
@@ -23,6 +26,11 @@ export function initCamera() {
|
|||||||
|
|
||||||
rightPanel.classList.remove("hidden");
|
rightPanel.classList.remove("hidden");
|
||||||
|
|
||||||
|
// Показываем перекрестие
|
||||||
|
if (crosshair) {
|
||||||
|
crosshair.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
camImage.src = streamUrl;
|
camImage.src = streamUrl;
|
||||||
|
|
||||||
if (btn) {
|
if (btn) {
|
||||||
@@ -32,6 +40,9 @@ export function initCamera() {
|
|||||||
console.log("[camera] opened");
|
console.log("[camera] opened");
|
||||||
|
|
||||||
updateResizerVisibility();
|
updateResizerVisibility();
|
||||||
|
|
||||||
|
// Запускаем мониторинг потока
|
||||||
|
startStreamMonitoring();
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
@@ -39,6 +50,11 @@ export function initCamera() {
|
|||||||
|
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
|
|
||||||
|
// Скрываем перекрестие
|
||||||
|
if (crosshair) {
|
||||||
|
crosshair.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
// остановка stream
|
// остановка stream
|
||||||
camImage.src = "";
|
camImage.src = "";
|
||||||
|
|
||||||
@@ -51,6 +67,9 @@ export function initCamera() {
|
|||||||
console.log("[camera] closed");
|
console.log("[camera] closed");
|
||||||
|
|
||||||
updateResizerVisibility();
|
updateResizerVisibility();
|
||||||
|
|
||||||
|
// Останавливаем мониторинг
|
||||||
|
stopStreamMonitoring();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
@@ -63,14 +82,80 @@ export function initCamera() {
|
|||||||
|
|
||||||
camImage.onload = () => {
|
camImage.onload = () => {
|
||||||
console.log("[camera] stream started");
|
console.log("[camera] stream started");
|
||||||
|
if (crosshair) {
|
||||||
|
crosshair.style.opacity = '1';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
camImage.onerror = (e) => {
|
camImage.onerror = (e) => {
|
||||||
console.error("[camera] stream error", e);
|
console.error("[camera] stream error", e);
|
||||||
|
if (crosshair) {
|
||||||
|
crosshair.style.opacity = '0.3';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
btn?.addEventListener("click", toggle);
|
btn?.addEventListener("click", toggle);
|
||||||
|
|
||||||
// автозапуск
|
// Проверка доступности камеры
|
||||||
open();
|
async function checkStreamAvailability() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/stream');
|
||||||
|
const data = 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() {
|
||||||
|
stopStreamMonitoring();
|
||||||
|
streamCheckInterval = 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() {
|
||||||
|
if (streamCheckInterval) {
|
||||||
|
clearInterval(streamCheckInterval);
|
||||||
|
streamCheckInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Экспортируем для возможности ручного управления
|
||||||
|
export function showCrosshair(show = true) {
|
||||||
|
const crosshair = document.getElementById("crosshair");
|
||||||
|
if (crosshair) {
|
||||||
|
crosshair.style.display = show ? "block" : "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCrosshairOpacity(opacity) {
|
||||||
|
const crosshair = document.getElementById("crosshair");
|
||||||
|
if (crosshair) {
|
||||||
|
crosshair.style.opacity = opacity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user