Add backend/app/main.py
This commit is contained in:
parent
0ad8b220c1
commit
8de0ac50d3
1 changed files with 127 additions and 0 deletions
127
backend/app/main.py
Normal file
127
backend/app/main.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""Main FastAPI application for Deltacast SDI Recorder."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
from pathlib import Path
|
||||
|
||||
from .config import Settings
|
||||
from .recorders.recorder import RecorderManager
|
||||
from .recorders.scte35 import SCTE35Manager
|
||||
from .utils.hls import HLSPreviewManager
|
||||
from .api.routes import router
|
||||
from .api import routes as routes_module # to set module-level globals
|
||||
from .api.websocket import ConnectionManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
settings = Settings()
|
||||
|
||||
# Global manager instances
|
||||
recorder_manager: RecorderManager | None = None
|
||||
scte35_manager: SCTE35Manager | None = None
|
||||
hls_manager: HLSPreviewManager | None = None
|
||||
connection_manager = ConnectionManager()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Initialize and cleanup application resources."""
|
||||
global recorder_manager, scte35_manager, hls_manager
|
||||
|
||||
# Startup
|
||||
logger.info("Starting Deltacast SDI Recorder...")
|
||||
|
||||
recorder_manager = RecorderManager(settings)
|
||||
recorder_manager.initialize()
|
||||
|
||||
scte35_manager = SCTE35Manager()
|
||||
|
||||
hls_manager = HLSPreviewManager(settings)
|
||||
|
||||
# Inject managers into routes module
|
||||
routes_module.recorder_manager = recorder_manager
|
||||
routes_module.scte35_manager = scte35_manager
|
||||
|
||||
# Start background task for status broadcasting
|
||||
status_task = asyncio.create_task(broadcast_port_status())
|
||||
|
||||
logger.info("Deltacast SDI Recorder started successfully")
|
||||
|
||||
yield # Application runs here
|
||||
|
||||
# Shutdown
|
||||
logger.info("Shutting down Deltacast SDI Recorder...")
|
||||
status_task.cancel()
|
||||
try:
|
||||
await status_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
# Stop all recorders and previews
|
||||
for port_index in range(settings.deltacast_port_count):
|
||||
await recorder_manager.stop_recording(port_index)
|
||||
await hls_manager.stop_preview(port_index)
|
||||
|
||||
logger.info("Shutdown complete")
|
||||
|
||||
|
||||
async def broadcast_port_status():
|
||||
"""Background task: broadcast port status to all WebSocket clients every 1 second."""
|
||||
while True:
|
||||
try:
|
||||
if recorder_manager is not None:
|
||||
statuses = recorder_manager.get_all_status()
|
||||
data = {
|
||||
"type": "port_status",
|
||||
"ports": [s.model_dump() for s in statuses]
|
||||
}
|
||||
await connection_manager.broadcast(data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error broadcasting port status: {e}")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Deltacast SDI Recorder",
|
||||
description="Web GUI for Deltacast SDI capture card recording",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan
|
||||
)
|
||||
|
||||
# CORS - allow all origins for local network use
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Include API router
|
||||
app.include_router(router)
|
||||
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
"""WebSocket endpoint for real-time port status updates."""
|
||||
await connection_manager.connect(websocket)
|
||||
try:
|
||||
while True:
|
||||
# Keep connection alive, receive any messages
|
||||
# (not used but required to detect disconnect)
|
||||
data = await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
connection_manager.disconnect(websocket)
|
||||
|
||||
|
||||
@app.get("/hls/{filename}")
|
||||
async def serve_hls(filename: str):
|
||||
"""Serve HLS segment and playlist files."""
|
||||
hls_path = Path("/tmp/hls") / filename
|
||||
if not hls_path.exists():
|
||||
raise HTTPException(status_code=404, detail="HLS file not found")
|
||||
return FileResponse(str(hls_path))
|
||||
Loading…
Reference in a new issue