115 lines
3.9 KiB
Python
115 lines
3.9 KiB
Python
import os
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import FileResponse
|
|
from starlette.responses import RedirectResponse
|
|
from starlette.background import BackgroundTask
|
|
import httpx
|
|
import asyncio
|
|
import time
|
|
import psutil
|
|
from database import init_db
|
|
from services.transcoder import transcode_mgr
|
|
from routes import streams_router, nodes_router, auth_router
|
|
from routes.settings_route import router as settings_router
|
|
from config import settings
|
|
from services.load_balancer import lb
|
|
from services.node_store import load_nodes
|
|
|
|
|
|
async def monitor_origin():
|
|
previous = psutil.net_io_counters()
|
|
previous_time = time.monotonic()
|
|
while True:
|
|
await asyncio.sleep(10)
|
|
current = psutil.net_io_counters()
|
|
current_time = time.monotonic()
|
|
elapsed = current_time - previous_time
|
|
network_in = ((current.bytes_recv - previous.bytes_recv) * 8 / elapsed / 1e6) if elapsed > 0 else None
|
|
network_out = ((current.bytes_sent - previous.bytes_sent) * 8 / elapsed / 1e6) if elapsed > 0 else None
|
|
media_available = False
|
|
viewer_count = None
|
|
try:
|
|
async with httpx.AsyncClient(timeout=3) as client:
|
|
response = await client.get(f"http://127.0.0.1:{settings.media_origin_api_port}/v3/paths/list")
|
|
response.raise_for_status()
|
|
payload = response.json()
|
|
viewer_count = sum(item.get("readers", 0) for item in payload.get("items", []))
|
|
media_available = True
|
|
except Exception:
|
|
pass
|
|
lb.report_health("default", {
|
|
"cpu_percent": psutil.cpu_percent(),
|
|
"memory_percent": psutil.virtual_memory().percent,
|
|
"bandwidth_mbps": network_in + network_out if network_in is not None and network_out is not None else None,
|
|
"network_in_mbps": network_in,
|
|
"network_out_mbps": network_out,
|
|
"active_connections": len(psutil.net_connections(kind="tcp")),
|
|
"hls_readers": viewer_count,
|
|
"viewer_count": viewer_count,
|
|
"frame_drop_indicators": None,
|
|
"packet_loss_indicators": None,
|
|
"media_metrics_available": False,
|
|
})
|
|
previous = current
|
|
previous_time = current_time
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
load_nodes()
|
|
await init_db()
|
|
origin_monitor = asyncio.create_task(monitor_origin())
|
|
yield
|
|
origin_monitor.cancel()
|
|
transcode_mgr.stop_all()
|
|
|
|
|
|
app = FastAPI(title="ProgramCore", lifespan=lifespan)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.include_router(streams_router)
|
|
app.include_router(nodes_router)
|
|
app.include_router(auth_router)
|
|
app.include_router(settings_router)
|
|
|
|
|
|
@app.get("/api/health")
|
|
async def health():
|
|
from services.multicast_detector import multicast_detector
|
|
return {
|
|
"status": "ok",
|
|
"multicast_available": multicast_detector.is_available(),
|
|
}
|
|
|
|
|
|
@app.get("/hls/{path:path}")
|
|
async def serve_hls(path: str):
|
|
hls_root = "/tmp/hls"
|
|
fs_path = os.path.join(hls_root, path)
|
|
if os.path.isfile(fs_path):
|
|
return FileResponse(fs_path)
|
|
|
|
target = f"http://127.0.0.1:{settings.media_origin_hls_port}/{path}"
|
|
try:
|
|
async with httpx.AsyncClient(timeout=httpx.Timeout(10)) as client:
|
|
req = client.build_request("GET", target)
|
|
resp = await client.send(req, stream=True)
|
|
from starlette.responses import StreamingResponse
|
|
return StreamingResponse(
|
|
resp.aiter_bytes(),
|
|
status_code=resp.status_code,
|
|
headers=dict(resp.headers),
|
|
media_type=resp.headers.get("content-type"),
|
|
)
|
|
except Exception:
|
|
raise HTTPException(status_code=404)
|