feat(02-03): remove MPVEngine from main.py, add beats cache and shows static route
- Remove MPVEngine import, engine module-level var, and lifespan start/stop logic
- Remove broadcast_loop task (no server-side position polling)
- Add app.state.beats = {} dict (filepath -> beat data cache) in lifespan startup
- Mount /shows as StaticFiles so browser <audio src=/shows/file.mp3> works
- Simplified lifespan: registry + show_store + beats init only
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import asyncio
|
|
||||||
import os
|
import os
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -8,36 +7,27 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
|
|
||||||
from lightsync.devices.registry import DeviceRegistry
|
from lightsync.devices.registry import DeviceRegistry
|
||||||
from lightsync.store.show_store import ShowStore
|
from lightsync.store.show_store import ShowStore
|
||||||
from lightsync.audio.engine import MPVEngine
|
|
||||||
|
|
||||||
# Module-level containers populated in lifespan
|
# Module-level containers populated in lifespan
|
||||||
registry: DeviceRegistry | None = None
|
registry: DeviceRegistry | None = None
|
||||||
show_store: ShowStore | None = None
|
show_store: ShowStore | None = None
|
||||||
engine: MPVEngine | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
global registry, show_store, engine
|
global registry, show_store
|
||||||
# Startup
|
# Startup
|
||||||
registry = DeviceRegistry(Path("devices.json"))
|
registry = DeviceRegistry(Path("devices.json"))
|
||||||
await registry.load()
|
await registry.load()
|
||||||
show_store = ShowStore(Path("shows"))
|
show_store = ShowStore(Path("shows"))
|
||||||
show_store.ensure_dir()
|
show_store.ensure_dir()
|
||||||
ao = os.environ.get("MPV_AO", "null")
|
|
||||||
engine = MPVEngine(ao=ao)
|
# Beat analysis cache: filepath -> {"tempo": float, "beats": [float, ...]}
|
||||||
engine.start()
|
app.state.beats = {}
|
||||||
app.state.engine = engine
|
|
||||||
from lightsync.api.ws import broadcast_loop
|
|
||||||
task = asyncio.create_task(broadcast_loop(lambda: engine))
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
# Shutdown
|
# Shutdown
|
||||||
task.cancel()
|
|
||||||
try:
|
|
||||||
await task
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
engine.stop()
|
|
||||||
await registry.save()
|
await registry.save()
|
||||||
|
|
||||||
|
|
||||||
@@ -51,6 +41,11 @@ def create_app() -> FastAPI:
|
|||||||
app.include_router(ws.router)
|
app.include_router(ws.router)
|
||||||
app.include_router(audio.router, prefix="/api/audio")
|
app.include_router(audio.router, prefix="/api/audio")
|
||||||
|
|
||||||
|
# Serve uploaded audio files so <audio src=...> can load them
|
||||||
|
shows_dir = Path("/app/shows")
|
||||||
|
shows_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
app.mount("/shows", StaticFiles(directory=str(shows_dir)), name="shows")
|
||||||
|
|
||||||
# Static file serving — no-cache headers to prevent stale JS/CSS
|
# Static file serving — no-cache headers to prevent stale JS/CSS
|
||||||
frontend_dir = Path(__file__).parent / "frontend"
|
frontend_dir = Path(__file__).parent / "frontend"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user