From 5916055130706b93c79e05f47ef709d049f0c78c Mon Sep 17 00:00:00 2001 From: "Andrew K. Choi" Date: Tue, 9 Dec 2025 20:27:25 +0900 Subject: [PATCH] rebuild --- rebuild.sh | 60 +++++++++++++++++++++++++++++++++++++ server.py | 75 +++++++++++++++++++++++++++++++++++++++------- test_send_frame.py | 2 +- 3 files changed, 125 insertions(+), 12 deletions(-) create mode 100755 rebuild.sh diff --git a/rebuild.sh b/rebuild.sh new file mode 100755 index 0000000..713b283 --- /dev/null +++ b/rebuild.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Скрипт для обновления и пересборки Docker контейнера + +set -e + +echo "==========================================" +echo "🔄 Updating Camera Server Container" +echo "==========================================" + +# Проверяем, находимся ли в корректной директории +if [ ! -f "docker-compose.yml" ]; then + echo "❌ Error: docker-compose.yml not found" + echo "Please run this script from the project root directory" + exit 1 +fi + +echo "" +echo "1️⃣ Stopping container..." +docker-compose down + +echo "" +echo "2️⃣ Rebuilding image..." +docker-compose build --no-cache + +echo "" +echo "3️⃣ Starting container..." +docker-compose up -d + +echo "" +echo "4️⃣ Waiting for container to be ready..." +sleep 3 + +echo "" +echo "5️⃣ Checking container status..." +docker-compose ps + +echo "" +echo "6️⃣ Recent logs:" +docker-compose logs --tail 20 + +echo "" +echo "==========================================" +echo "✅ Update complete!" +echo "==========================================" +echo "" +echo "Server should be running on:" +docker-compose exec camera_server python3 -c " +import os +from dotenv import load_dotenv +load_dotenv() +bind_host = os.getenv('BIND_HOST', '0.0.0.0') +public_host = os.getenv('PUBLIC_HOST', 'localhost') +port = os.getenv('PORT', '8000') +print(f' 📡 Public: http://{public_host}:{port}') +print(f' 🔗 Local: http://127.0.0.1:{port}') +" 2>/dev/null || echo " 📡 Check .env for PUBLIC_HOST and PORT" + +echo "" +echo "To view logs in real-time:" +echo " docker-compose logs -f" diff --git a/server.py b/server.py index fa6d8d3..e09538b 100644 --- a/server.py +++ b/server.py @@ -77,6 +77,7 @@ server_stats = None stats_lock = None cleanup_task = None templates = None +admin_frame_queues = None # Администраторы ADMINS = [ @@ -571,6 +572,7 @@ async def lifespan(app: FastAPI): admin_websockets = {} video_queues = {} command_queues = {} + admin_frame_queues = {} room_stats = {} server_stats = { "total_rooms": 0, @@ -1072,6 +1074,17 @@ async def client_websocket_endpoint(websocket: WebSocket, room_id: str, password print(f"[WebSocket Client] ⚠️ {client_id}: video queue is FULL, dropping frame") else: print(f"[WebSocket Client] ❌ {client_id}: video_queue not found!") + # Также пробуем переслать тот же байтовый кадр администраторам, если кто-то смотрит + try: + if admin_frame_queues is not None and client_id in admin_frame_queues: + aq = admin_frame_queues[client_id] + try: + aq.put_nowait(message) + except asyncio.QueueFull: + # Если очередь админа переполнена, пропускаем кадр + pass + except Exception: + pass elif isinstance(message, str): try: @@ -1113,6 +1126,12 @@ async def client_websocket_endpoint(websocket: WebSocket, room_id: str, password if video_processor: video_processor.stop() + # Очищаем очередь админа, чтобы фреймы перестали отправляться + try: + if admin_frame_queues is not None and client_id in admin_frame_queues: + del admin_frame_queues[client_id] + except Exception: + pass print(f"[WebSocket Client] ✓ VideoProcessor stopped for {client_id}") print(f"[WebSocket Client] ===== END OF CONNECTION =====\n") @@ -1191,26 +1210,60 @@ async def admin_websocket_endpoint(websocket: WebSocket, session_id: str): async def _stream_client_to_admin(client_id: str, admin_session_id: str): """Потоковая передача видео от клиента к администратору""" + global admin_frame_queues, admin_websockets, clients, stats_lock + if client_id not in clients or admin_session_id not in admin_websockets: return - + try: - await admin_websockets[admin_session_id].send_text(json.dumps({ - "type": "stream_started", - "client_id": client_id - })) - + ws = admin_websockets[admin_session_id] + # Уведомляем админа о старте стрима + await ws.send_text(json.dumps({"type": "stream_started", "client_id": client_id})) + with stats_lock: if client_id in clients: client = clients[client_id] - await admin_websockets[admin_session_id].send_text(json.dumps({ + await ws.send_text(json.dumps({ "type": "stream_info", "message": f"Streaming from client {client_id}", - "quality": client["video_settings"]["quality"], - "ip": client["ip_address"], - "connected_at": client["connected_at"] + "quality": client.get("video_settings", {}).get("quality"), + "ip": client.get("ip_address"), + "connected_at": client.get("connected_at") })) - + + # Подготовим очередь для пересылки байтов администраторам + if admin_frame_queues is None: + return + + if client_id not in admin_frame_queues: + # Создаём очередь для этого клиента, если её нет + admin_frame_queues[client_id] = asyncio.Queue(maxsize=128) + + aq = admin_frame_queues[client_id] + print(f"[WebSocket] Started streaming frames to admin {admin_session_id} for client {client_id}") + + # Цикл чтения кадров из очереди и отправки их админу + # Выходим только если очередь удалена (клиент отключился) или произойдёт исключение + try: + while client_id in admin_frame_queues: + try: + # Пытаемся получить кадр с таймаутом, чтобы периодически проверять наличие очереди + frame_bytes = await asyncio.wait_for(aq.get(), timeout=2.0) + try: + await ws.send_bytes(frame_bytes) + except Exception as e: + print(f"[WebSocket] Error sending bytes to admin {admin_session_id}: {e}") + break + except asyncio.TimeoutError: + # Таймаут OK — просто продолжаем ждать + continue + except asyncio.CancelledError: + break + except Exception as e: + print(f"[WebSocket] Error in admin frame loop: {e}") + finally: + print(f"[WebSocket] Stopped streaming frames to admin {admin_session_id} for client {client_id}") + except Exception as e: print(f"[WebSocket] Error streaming to admin: {e}") diff --git a/test_send_frame.py b/test_send_frame.py index a39ad62..e8d0d64 100644 --- a/test_send_frame.py +++ b/test_send_frame.py @@ -15,7 +15,7 @@ from pathlib import Path # Параметры подключения HOST = "192.168.0.112" # Измените на ваш PUBLIC_HOST из .env PORT = 8000 -ROOM_ID = "DazqDVlwdGX" # Измените на ID комнаты +ROOM_ID = "AtR2yGhr8QM" # Измените на ID комнаты PASSWORD = "1" # Измените на пароль комнаты FRAME_FILE = "test_frame.jpg" # Путь к тестовому JPEG-файлу