import asyncio from contextlib import asynccontextmanager from datetime import datetime from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from sqlalchemy import select from .routers import auth, rooms, tracks, websocket, messages from .database import async_session from .models.room import Room from .services.sync import manager async def periodic_sync(): """Send sync updates to all rooms every 10 seconds""" while True: await asyncio.sleep(10) # Get all active rooms for room_id in list(manager.active_connections.keys()): try: async with async_session() as db: result = await db.execute(select(Room).where(Room.id == room_id)) room = result.scalar_one_or_none() if not room or not room.is_playing: continue # Calculate current position current_position = room.playback_position or 0 if room.playback_started_at: elapsed = (datetime.utcnow() - room.playback_started_at).total_seconds() * 1000 current_position = int((room.playback_position or 0) + elapsed) track_url = None if room.current_track_id: track_url = f"/api/tracks/{room.current_track_id}/stream" await manager.broadcast_to_room( room_id, { "type": "sync_state", "is_playing": room.is_playing, "position": current_position, "current_track_id": str(room.current_track_id) if room.current_track_id else None, "track_url": track_url, "server_time": datetime.utcnow().isoformat(), }, ) except Exception: pass @asynccontextmanager async def lifespan(app: FastAPI): # Start background sync task sync_task = asyncio.create_task(periodic_sync()) yield # Cleanup sync_task.cancel() try: await sync_task except asyncio.CancelledError: pass app = FastAPI(title="EnigFM", description="Listen to music together with friends", lifespan=lifespan) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Routers app.include_router(auth.router) app.include_router(rooms.router) app.include_router(tracks.router) app.include_router(messages.router) app.include_router(websocket.router) @app.get("/") async def root(): return {"message": "EnigFM API", "version": "1.0.0"} @app.get("/health") async def health(): return {"status": "ok"}