diff --git a/app/api/voice_ws.py b/app/api/voice_ws.py new file mode 100644 index 0000000..4989a1a --- /dev/null +++ b/app/api/voice_ws.py @@ -0,0 +1,39 @@ +from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query +from app.realtime.voice_hub import voice_hub + +router = APIRouter() + +@router.websocket("/ws/voice") +async def voice_ws( + ws: WebSocket, + room_id: str = Query(...), + username: str = Query(...) +): + await voice_hub.connect(room_id, username, ws) + + # уведомим остальных + await voice_hub.broadcast( + room_id, + {"type": "join", "user": username} + ) + + try: + while True: + msg = await ws.receive_json() + + if msg["type"] == "signal": + await voice_hub.send_to( + room_id, + msg["to"], + { + "type": "signal", + "from": username, + "data": msg["data"] + } + ) + except WebSocketDisconnect: + voice_hub.disconnect(room_id, username) + await voice_hub.broadcast( + room_id, + {"type": "leave", "user": username} + ) diff --git a/app/realtime/voice_hub.py b/app/realtime/voice_hub.py new file mode 100644 index 0000000..563d405 --- /dev/null +++ b/app/realtime/voice_hub.py @@ -0,0 +1,30 @@ +from typing import Dict +from fastapi import WebSocket + +class VoiceHub: + def __init__(self): + # room_id -> username -> websocket + self.rooms: Dict[str, Dict[str, WebSocket]] = {} + + async def connect(self, room_id: str, username: str, ws: WebSocket): + await ws.accept() + self.rooms.setdefault(room_id, {})[username] = ws + + def disconnect(self, room_id: str, username: str): + room = self.rooms.get(room_id) + if not room: + return + room.pop(username, None) + if not room: + self.rooms.pop(room_id, None) + + async def send_to(self, room_id: str, to_user: str, payload: dict): + ws = self.rooms.get(room_id, {}).get(to_user) + if ws: + await ws.send_json(payload) + + async def broadcast(self, room_id: str, payload: dict): + for ws in self.rooms.get(room_id, {}).values(): + await ws.send_json(payload) + +voice_hub = VoiceHub() diff --git a/main.py b/main.py index ac9904c..3a5c151 100644 --- a/main.py +++ b/main.py @@ -3,14 +3,12 @@ import os from fastapi import FastAPI from fastapi.staticfiles import StaticFiles import httpx -from app.api import admin_daily_quests, inventory, news, users, skins, capes, meta, server, store, pranks, marketplace, bonuses, case, promo +from app.api import admin_daily_quests, inventory, news, users, skins, capes, meta, server, store, pranks, marketplace, marketplace_ws, bonuses, case, promo, voice_ws from fastapi.middleware.cors import CORSMiddleware from app.core.config import CAPES_DIR, CAPES_STORE_DIR, SKINS_DIR from app.services.promo import PromoService from app.webhooks import telegram -from app.db.database import users_collection -from app.api import marketplace_ws from app.db.database import users_collection, sessions_collection @@ -75,6 +73,7 @@ app.include_router(news.router) app.include_router(telegram.router) app.include_router(admin_daily_quests.router) app.include_router(promo.router) +app.include_router(voice_ws.router) # Монтируем статику app.mount("/skins", StaticFiles(directory=str(SKINS_DIR)), name="skins")