Files
god_eye_android/signaling_server.py
2025-10-06 09:40:51 +09:00

270 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
GodEye Signaling Server
Сервер для обмена SDP и ICE кандидатами между устройством и оператором
"""
import asyncio
import websockets
import json
import logging
from datetime import datetime
import uuid
# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class SignalingServer:
def __init__(self):
self.clients = {} # client_id -> websocket
self.sessions = {} # session_id -> {device: ws, operator: ws}
async def register_client(self, websocket, path):
"""Регистрация нового клиента"""
client_id = str(uuid.uuid4())
self.clients[client_id] = websocket
logger.info(f"🔗 Клиент подключен: {client_id}")
try:
# Отправляем ID клиенту
await websocket.send(json.dumps({
"type": "client_registered",
"client_id": client_id,
"timestamp": datetime.now().isoformat()
}))
async for message in websocket:
await self.handle_message(client_id, message)
except websockets.exceptions.ConnectionClosed:
logger.info(f"🔌 Клиент отключен: {client_id}")
finally:
# Очистка при отключении
await self.cleanup_client(client_id)
async def handle_message(self, client_id, message):
"""Обработка сообщений от клиентов"""
try:
data = json.loads(message)
message_type = data.get("type")
logger.info(f"📨 Сообщение от {client_id}: {message_type}")
if message_type == "create_session":
await self.create_session(client_id, data)
elif message_type == "join_session":
await self.join_session(client_id, data)
elif message_type == "offer":
await self.relay_offer(client_id, data)
elif message_type == "answer":
await self.relay_answer(client_id, data)
elif message_type == "ice_candidate":
await self.relay_ice_candidate(client_id, data)
elif message_type == "hangup":
await self.handle_hangup(client_id, data)
else:
logger.warning(f"⚠️ Неизвестный тип сообщения: {message_type}")
except json.JSONDecodeError:
logger.error(f"❌ Ошибка парсинга JSON от {client_id}")
except Exception as e:
logger.error(f"❌ Ошибка обработки сообщения: {e}")
async def create_session(self, client_id, data):
"""Создание новой сессии (устройство)"""
session_id = str(uuid.uuid4())
device_info = data.get("device_info", {})
self.sessions[session_id] = {
"device": self.clients[client_id],
"operator": None,
"device_info": device_info,
"created_at": datetime.now().isoformat()
}
# Отправляем подтверждение устройству
await self.clients[client_id].send(json.dumps({
"type": "session_created",
"session_id": session_id,
"timestamp": datetime.now().isoformat()
}))
logger.info(f"📱 Сессия создана: {session_id} для устройства {device_info.get('model', 'Unknown')}")
async def join_session(self, client_id, data):
"""Подключение к сессии (оператор)"""
session_id = data.get("session_id")
if session_id not in self.sessions:
await self.clients[client_id].send(json.dumps({
"type": "error",
"message": "Session not found"
}))
return
session = self.sessions[session_id]
if session["operator"] is not None:
await self.clients[client_id].send(json.dumps({
"type": "error",
"message": "Session already has an operator"
}))
return
# Подключаем оператора
session["operator"] = self.clients[client_id]
# Уведомляем оператора
await self.clients[client_id].send(json.dumps({
"type": "session_joined",
"session_id": session_id,
"device_info": session["device_info"],
"timestamp": datetime.now().isoformat()
}))
# Уведомляем устройство
await session["device"].send(json.dumps({
"type": "operator_joined",
"session_id": session_id,
"timestamp": datetime.now().isoformat()
}))
logger.info(f"👤 Оператор подключен к сессии: {session_id}")
async def relay_offer(self, client_id, data):
"""Передача SDP offer"""
session_id = data.get("session_id")
session = self.sessions.get(session_id)
if not session or session["operator"] is None:
return
# Передаем offer оператору
await session["operator"].send(json.dumps({
"type": "offer",
"session_id": session_id,
"sdp": data.get("sdp"),
"timestamp": datetime.now().isoformat()
}))
logger.info(f"📞 SDP Offer передан в сессии: {session_id}")
async def relay_answer(self, client_id, data):
"""Передача SDP answer"""
session_id = data.get("session_id")
session = self.sessions.get(session_id)
if not session:
return
# Передаем answer устройству
await session["device"].send(json.dumps({
"type": "answer",
"session_id": session_id,
"sdp": data.get("sdp"),
"timestamp": datetime.now().isoformat()
}))
logger.info(f"📞 SDP Answer передан в сессии: {session_id}")
async def relay_ice_candidate(self, client_id, data):
"""Передача ICE кандидатов"""
session_id = data.get("session_id")
session = self.sessions.get(session_id)
if not session:
return
# Определяем кому передавать (устройству или оператору)
if session["device"] == self.clients[client_id]:
# От устройства к оператору
if session["operator"]:
await session["operator"].send(json.dumps({
"type": "ice_candidate",
"session_id": session_id,
"candidate": data.get("candidate"),
"timestamp": datetime.now().isoformat()
}))
else:
# От оператора к устройству
await session["device"].send(json.dumps({
"type": "ice_candidate",
"session_id": session_id,
"candidate": data.get("candidate"),
"timestamp": datetime.now().isoformat()
}))
logger.info(f"🧊 ICE candidate передан в сессии: {session_id}")
async def handle_hangup(self, client_id, data):
"""Завершение сессии"""
session_id = data.get("session_id")
session = self.sessions.get(session_id)
if not session:
return
# Уведомляем обе стороны
if session["device"]:
await session["device"].send(json.dumps({
"type": "hangup",
"session_id": session_id,
"timestamp": datetime.now().isoformat()
}))
if session["operator"]:
await session["operator"].send(json.dumps({
"type": "hangup",
"session_id": session_id,
"timestamp": datetime.now().isoformat()
}))
# Удаляем сессию
del self.sessions[session_id]
logger.info(f"📴 Сессия завершена: {session_id}")
async def cleanup_client(self, client_id):
"""Очистка при отключении клиента"""
if client_id in self.clients:
del self.clients[client_id]
# Удаляем сессии с этим клиентом
sessions_to_remove = []
for session_id, session in self.sessions.items():
if (session["device"] == self.clients.get(client_id) or
session["operator"] == self.clients.get(client_id)):
sessions_to_remove.append(session_id)
for session_id in sessions_to_remove:
del self.sessions[session_id]
logger.info(f"🧹 Сессия {session_id} удалена из-за отключения клиента")
def main():
"""Запуск сигналинг сервера"""
server = SignalingServer()
print("🚀 GodEye Signaling Server")
print("==========================")
print("📡 Сервер запущен на ws://localhost:8765")
print("💡 Для остановки нажмите Ctrl+C")
print()
# Исправляем проблему с event loop
async def run_server():
start_server = websockets.serve(
server.register_client,
"0.0.0.0", # Слушаем на всех интерфейсах
8765
)
await start_server
await asyncio.Future() # Запускаем вечно
try:
asyncio.run(run_server())
except KeyboardInterrupt:
print("\n🛑 Сервер остановлен")
if __name__ == "__main__":
main()