rebuild
This commit is contained in:
60
rebuild.sh
Executable file
60
rebuild.sh
Executable file
@@ -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"
|
||||||
75
server.py
75
server.py
@@ -77,6 +77,7 @@ server_stats = None
|
|||||||
stats_lock = None
|
stats_lock = None
|
||||||
cleanup_task = None
|
cleanup_task = None
|
||||||
templates = None
|
templates = None
|
||||||
|
admin_frame_queues = None
|
||||||
|
|
||||||
# Администраторы
|
# Администраторы
|
||||||
ADMINS = [
|
ADMINS = [
|
||||||
@@ -571,6 +572,7 @@ async def lifespan(app: FastAPI):
|
|||||||
admin_websockets = {}
|
admin_websockets = {}
|
||||||
video_queues = {}
|
video_queues = {}
|
||||||
command_queues = {}
|
command_queues = {}
|
||||||
|
admin_frame_queues = {}
|
||||||
room_stats = {}
|
room_stats = {}
|
||||||
server_stats = {
|
server_stats = {
|
||||||
"total_rooms": 0,
|
"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")
|
print(f"[WebSocket Client] ⚠️ {client_id}: video queue is FULL, dropping frame")
|
||||||
else:
|
else:
|
||||||
print(f"[WebSocket Client] ❌ {client_id}: video_queue not found!")
|
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):
|
elif isinstance(message, str):
|
||||||
try:
|
try:
|
||||||
@@ -1113,6 +1126,12 @@ async def client_websocket_endpoint(websocket: WebSocket, room_id: str, password
|
|||||||
|
|
||||||
if video_processor:
|
if video_processor:
|
||||||
video_processor.stop()
|
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] ✓ VideoProcessor stopped for {client_id}")
|
||||||
print(f"[WebSocket Client] ===== END OF CONNECTION =====\n")
|
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):
|
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:
|
if client_id not in clients or admin_session_id not in admin_websockets:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await admin_websockets[admin_session_id].send_text(json.dumps({
|
ws = admin_websockets[admin_session_id]
|
||||||
"type": "stream_started",
|
# Уведомляем админа о старте стрима
|
||||||
"client_id": client_id
|
await ws.send_text(json.dumps({"type": "stream_started", "client_id": client_id}))
|
||||||
}))
|
|
||||||
|
|
||||||
with stats_lock:
|
with stats_lock:
|
||||||
if client_id in clients:
|
if client_id in clients:
|
||||||
client = clients[client_id]
|
client = clients[client_id]
|
||||||
await admin_websockets[admin_session_id].send_text(json.dumps({
|
await ws.send_text(json.dumps({
|
||||||
"type": "stream_info",
|
"type": "stream_info",
|
||||||
"message": f"Streaming from client {client_id}",
|
"message": f"Streaming from client {client_id}",
|
||||||
"quality": client["video_settings"]["quality"],
|
"quality": client.get("video_settings", {}).get("quality"),
|
||||||
"ip": client["ip_address"],
|
"ip": client.get("ip_address"),
|
||||||
"connected_at": client["connected_at"]
|
"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:
|
except Exception as e:
|
||||||
print(f"[WebSocket] Error streaming to admin: {e}")
|
print(f"[WebSocket] Error streaming to admin: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from pathlib import Path
|
|||||||
# Параметры подключения
|
# Параметры подключения
|
||||||
HOST = "192.168.0.112" # Измените на ваш PUBLIC_HOST из .env
|
HOST = "192.168.0.112" # Измените на ваш PUBLIC_HOST из .env
|
||||||
PORT = 8000
|
PORT = 8000
|
||||||
ROOM_ID = "DazqDVlwdGX" # Измените на ID комнаты
|
ROOM_ID = "AtR2yGhr8QM" # Измените на ID комнаты
|
||||||
PASSWORD = "1" # Измените на пароль комнаты
|
PASSWORD = "1" # Измените на пароль комнаты
|
||||||
FRAME_FILE = "test_frame.jpg" # Путь к тестовому JPEG-файлу
|
FRAME_FILE = "test_frame.jpg" # Путь к тестовому JPEG-файлу
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user