deltacast-sdi-recorder/backend/app/main.py

127 lines
3.8 KiB
Python

"""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))