feat: wire HLS manager into routes, add preview start/stop endpoints
This commit is contained in:
parent
36a1e4a80e
commit
b936647abb
1 changed files with 70 additions and 102 deletions
|
|
@ -8,6 +8,7 @@ from pydantic import BaseModel, Field
|
||||||
from ..models import RecorderConfig, PortStatus, SCTE35Marker
|
from ..models import RecorderConfig, PortStatus, SCTE35Marker
|
||||||
from ..recorders.recorder import RecorderManager
|
from ..recorders.recorder import RecorderManager
|
||||||
from ..recorders.scte35 import SCTE35Manager
|
from ..recorders.scte35 import SCTE35Manager
|
||||||
|
from ..utils.hls import HLSPreviewManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -31,22 +32,27 @@ router = APIRouter(prefix="/api")
|
||||||
# These will be set by main.py during startup
|
# These will be set by main.py during startup
|
||||||
recorder_manager: RecorderManager | None = None
|
recorder_manager: RecorderManager | None = None
|
||||||
scte35_manager: SCTE35Manager | None = None
|
scte35_manager: SCTE35Manager | None = None
|
||||||
|
hls_manager: HLSPreviewManager | None = None
|
||||||
|
|
||||||
|
|
||||||
def get_recorder_manager() -> RecorderManager:
|
def get_recorder_manager() -> RecorderManager:
|
||||||
"""Dependency to get the RecorderManager instance."""
|
|
||||||
if recorder_manager is None:
|
if recorder_manager is None:
|
||||||
raise HTTPException(status_code=503, detail="Recorder not initialized")
|
raise HTTPException(status_code=503, detail="Recorder not initialized")
|
||||||
return recorder_manager
|
return recorder_manager
|
||||||
|
|
||||||
|
|
||||||
def get_scte35_manager() -> SCTE35Manager:
|
def get_scte35_manager() -> SCTE35Manager:
|
||||||
"""Dependency to get the SCTE35Manager instance."""
|
|
||||||
if scte35_manager is None:
|
if scte35_manager is None:
|
||||||
raise HTTPException(status_code=503, detail="SCTE35 manager not initialized")
|
raise HTTPException(status_code=503, detail="SCTE35 manager not initialized")
|
||||||
return scte35_manager
|
return scte35_manager
|
||||||
|
|
||||||
|
|
||||||
|
def get_hls_manager() -> HLSPreviewManager:
|
||||||
|
if hls_manager is None:
|
||||||
|
raise HTTPException(status_code=503, detail="HLS manager not initialized")
|
||||||
|
return hls_manager
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Health and Status Endpoints
|
# Health and Status Endpoints
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -54,16 +60,9 @@ def get_scte35_manager() -> SCTE35Manager:
|
||||||
|
|
||||||
@router.get("/health")
|
@router.get("/health")
|
||||||
async def health(manager: RecorderManager = Depends(get_recorder_manager)) -> dict[str, Any]:
|
async def health(manager: RecorderManager = Depends(get_recorder_manager)) -> dict[str, Any]:
|
||||||
"""
|
|
||||||
Health check endpoint.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict with status, port count, and recording count
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
all_status = manager.get_all_status()
|
all_status = manager.get_all_status()
|
||||||
recording_count = sum(1 for status in all_status if status.is_recording)
|
recording_count = sum(1 for status in all_status if status.is_recording)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"ports": len(all_status),
|
"ports": len(all_status),
|
||||||
|
|
@ -81,12 +80,6 @@ async def health(manager: RecorderManager = Depends(get_recorder_manager)) -> di
|
||||||
|
|
||||||
@router.get("/ports", response_model=list[PortStatus])
|
@router.get("/ports", response_model=list[PortStatus])
|
||||||
async def list_ports(manager: RecorderManager = Depends(get_recorder_manager)) -> list[PortStatus]:
|
async def list_ports(manager: RecorderManager = Depends(get_recorder_manager)) -> list[PortStatus]:
|
||||||
"""
|
|
||||||
Get status of all recorder ports.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of PortStatus objects for all ports
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
return manager.get_all_status()
|
return manager.get_all_status()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -99,22 +92,9 @@ async def get_port(
|
||||||
port_index: int,
|
port_index: int,
|
||||||
manager: RecorderManager = Depends(get_recorder_manager),
|
manager: RecorderManager = Depends(get_recorder_manager),
|
||||||
) -> PortStatus:
|
) -> PortStatus:
|
||||||
"""
|
|
||||||
Get status of a specific recorder port.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
port_index: 0-based port index
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PortStatus for the specified port
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException(404): If port index is invalid
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
return manager.get_status(port_index)
|
return manager.get_status(port_index)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(f"Invalid port index {port_index}: {e}")
|
|
||||||
raise HTTPException(status_code=404, detail=f"Port {port_index} not found")
|
raise HTTPException(status_code=404, detail=f"Port {port_index} not found")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting port {port_index}: {e}")
|
logger.error(f"Error getting port {port_index}: {e}")
|
||||||
|
|
@ -132,32 +112,19 @@ async def start_recording(
|
||||||
config: RecorderConfig,
|
config: RecorderConfig,
|
||||||
manager: RecorderManager = Depends(get_recorder_manager),
|
manager: RecorderManager = Depends(get_recorder_manager),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
|
||||||
Start recording on a specific port.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
port_index: 0-based port index
|
|
||||||
config: RecorderConfig with codec and output settings
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Message confirmation with port number
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException(400): If port is already recording or config is invalid
|
|
||||||
HTTPException(404): If port index is invalid
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
await manager.start_recording(port_index, config)
|
await manager.start_recording(port_index, config)
|
||||||
|
# Auto-start HLS preview if enabled in config
|
||||||
|
if config.preview_enabled and hls_manager is not None:
|
||||||
|
try:
|
||||||
|
await hls_manager.start_preview(port_index)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"HLS preview failed to start for port {port_index}: {e}")
|
||||||
logger.info(f"Started recording on port {port_index}")
|
logger.info(f"Started recording on port {port_index}")
|
||||||
return {
|
return {"message": "Recording started", "port": port_index}
|
||||||
"message": "Recording started",
|
|
||||||
"port": port_index,
|
|
||||||
}
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(f"Invalid port {port_index}: {e}")
|
|
||||||
raise HTTPException(status_code=404, detail=f"Port {port_index} not found")
|
raise HTTPException(status_code=404, detail=f"Port {port_index} not found")
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
logger.warning(f"Cannot start recording on port {port_index}: {e}")
|
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error starting recording on port {port_index}: {e}")
|
logger.error(f"Error starting recording on port {port_index}: {e}")
|
||||||
|
|
@ -169,33 +136,71 @@ async def stop_recording(
|
||||||
port_index: int,
|
port_index: int,
|
||||||
manager: RecorderManager = Depends(get_recorder_manager),
|
manager: RecorderManager = Depends(get_recorder_manager),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
|
||||||
Stop recording on a specific port.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
port_index: 0-based port index
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Message confirmation with port number
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException(404): If port index is invalid
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
await manager.stop_recording(port_index)
|
await manager.stop_recording(port_index)
|
||||||
|
# Stop HLS preview when recording stops
|
||||||
|
if hls_manager is not None:
|
||||||
|
try:
|
||||||
|
await hls_manager.stop_preview(port_index)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"HLS preview failed to stop for port {port_index}: {e}")
|
||||||
logger.info(f"Stopped recording on port {port_index}")
|
logger.info(f"Stopped recording on port {port_index}")
|
||||||
return {
|
return {"message": "Recording stopped", "port": port_index}
|
||||||
"message": "Recording stopped",
|
|
||||||
"port": port_index,
|
|
||||||
}
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(f"Invalid port {port_index}: {e}")
|
|
||||||
raise HTTPException(status_code=404, detail=f"Port {port_index} not found")
|
raise HTTPException(status_code=404, detail=f"Port {port_index} not found")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping recording on port {port_index}: {e}")
|
logger.error(f"Error stopping recording on port {port_index}: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
raise HTTPException(status_code=500, detail="Internal server error")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# HLS Preview Endpoints
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/ports/{port_index}/preview/start")
|
||||||
|
async def start_preview(
|
||||||
|
port_index: int,
|
||||||
|
manager: HLSPreviewManager = Depends(get_hls_manager),
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Manually start HLS preview for a port (independent of recording)."""
|
||||||
|
try:
|
||||||
|
await manager.start_preview(port_index)
|
||||||
|
playlist_url = f"/hls/port_{port_index}.m3u8"
|
||||||
|
return {"message": "HLS preview started", "port": port_index, "playlist_url": playlist_url}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error starting HLS preview on port {port_index}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/ports/{port_index}/preview/stop")
|
||||||
|
async def stop_preview(
|
||||||
|
port_index: int,
|
||||||
|
manager: HLSPreviewManager = Depends(get_hls_manager),
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Manually stop HLS preview for a port."""
|
||||||
|
try:
|
||||||
|
await manager.stop_preview(port_index)
|
||||||
|
return {"message": "HLS preview stopped", "port": port_index}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping HLS preview on port {port_index}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/ports/{port_index}/preview/status")
|
||||||
|
async def preview_status(
|
||||||
|
port_index: int,
|
||||||
|
manager: HLSPreviewManager = Depends(get_hls_manager),
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Get HLS preview status for a port."""
|
||||||
|
active = manager.is_previewing(port_index)
|
||||||
|
return {
|
||||||
|
"port": port_index,
|
||||||
|
"active": active,
|
||||||
|
"playlist_url": f"/hls/port_{port_index}.m3u8" if active else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# SCTE35 Ad Break Endpoints
|
# SCTE35 Ad Break Endpoints
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -206,18 +211,6 @@ async def inject_scte35_marker(
|
||||||
request: SCTE35InjectionRequest,
|
request: SCTE35InjectionRequest,
|
||||||
manager: SCTE35Manager = Depends(get_scte35_manager),
|
manager: SCTE35Manager = Depends(get_scte35_manager),
|
||||||
) -> SCTE35Marker:
|
) -> SCTE35Marker:
|
||||||
"""
|
|
||||||
Inject a SCTE35 ad break marker globally (all ports).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: SCTE35InjectionRequest
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
SCTE35Marker with timestamp and metadata
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException(400): If parameters are invalid
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
marker = await manager.inject_marker(
|
marker = await manager.inject_marker(
|
||||||
event_id=request.event_id,
|
event_id=request.event_id,
|
||||||
|
|
@ -228,12 +221,9 @@ async def inject_scte35_marker(
|
||||||
port_index=None,
|
port_index=None,
|
||||||
srt_destination_url=request.srt_destination_url,
|
srt_destination_url=request.srt_destination_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Injected global SCTE35 marker event_id={request.event_id}")
|
logger.info(f"Injected global SCTE35 marker event_id={request.event_id}")
|
||||||
return marker
|
return marker
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(f"Invalid SCTE35 parameters: {e}")
|
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error injecting SCTE35 marker: {e}")
|
logger.error(f"Error injecting SCTE35 marker: {e}")
|
||||||
|
|
@ -246,16 +236,6 @@ async def inject_scte35_marker_for_port(
|
||||||
request: SCTE35InjectionRequest,
|
request: SCTE35InjectionRequest,
|
||||||
manager: SCTE35Manager = Depends(get_scte35_manager),
|
manager: SCTE35Manager = Depends(get_scte35_manager),
|
||||||
) -> SCTE35Marker:
|
) -> SCTE35Marker:
|
||||||
"""
|
|
||||||
Inject a SCTE35 ad break marker on a specific port's SRT output(s).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
port_index: 0-based port index
|
|
||||||
request: SCTE35InjectionRequest
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
SCTE35Marker with timestamp, port_index, and metadata
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
marker = await manager.inject_marker(
|
marker = await manager.inject_marker(
|
||||||
event_id=request.event_id,
|
event_id=request.event_id,
|
||||||
|
|
@ -266,12 +246,9 @@ async def inject_scte35_marker_for_port(
|
||||||
port_index=port_index,
|
port_index=port_index,
|
||||||
srt_destination_url=request.srt_destination_url,
|
srt_destination_url=request.srt_destination_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Injected SCTE35 marker event_id={request.event_id} on port {port_index}")
|
logger.info(f"Injected SCTE35 marker event_id={request.event_id} on port {port_index}")
|
||||||
return marker
|
return marker
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(f"Invalid SCTE35 parameters: {e}")
|
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error injecting SCTE35 marker on port {port_index}: {e}")
|
logger.error(f"Error injecting SCTE35 marker on port {port_index}: {e}")
|
||||||
|
|
@ -282,12 +259,6 @@ async def inject_scte35_marker_for_port(
|
||||||
async def get_scte35_history(
|
async def get_scte35_history(
|
||||||
manager: SCTE35Manager = Depends(get_scte35_manager),
|
manager: SCTE35Manager = Depends(get_scte35_manager),
|
||||||
) -> list[SCTE35Marker]:
|
) -> list[SCTE35Marker]:
|
||||||
"""
|
|
||||||
Get history of injected SCTE35 markers.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of SCTE35Marker objects in chronological order
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
return manager.get_marker_history()
|
return manager.get_marker_history()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -300,9 +271,6 @@ async def get_scte35_history_for_port(
|
||||||
port_index: int,
|
port_index: int,
|
||||||
manager: SCTE35Manager = Depends(get_scte35_manager),
|
manager: SCTE35Manager = Depends(get_scte35_manager),
|
||||||
) -> list[SCTE35Marker]:
|
) -> list[SCTE35Marker]:
|
||||||
"""
|
|
||||||
Get SCTE35 marker history for a specific port.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
return [m for m in manager.get_marker_history() if m.port_index == port_index]
|
return [m for m in manager.get_marker_history() if m.port_index == port_index]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue