"""Reicht den HLS-Stream von MediaMTX unter demselben Ursprung durch. Ohne das läge der Player auf Port 8000 und der Stream auf 8890 — zwei Ursprünge, also CORS und eine zweite Route nach außen. Low-Latency-HLS fragt viel und oft: je Zuschauer rund zwanzig Anfragen pro Sekunde, für Bild und Ton getrennt. Deshalb hält dieses Modul einen gemeinsamen Client mit offenen Verbindungen und reicht die Daten stückweise weiter, statt jedes Segment erst vollständig in den Speicher zu holen. """ import httpx from fastapi import HTTPException, Request from fastapi.responses import Response, StreamingResponse from starlette.background import BackgroundTask from .config import MEDIAMTX_URL # Playlists ändern sich im Sekundentakt, Segmente nie NICHT_ZWISCHENSPEICHERN = {"Cache-Control": "no-store"} DURCHREICHEN = { "content-type", "content-length", "cache-control", "last-modified", "etag", } # Ein Client für alle Anfragen. Ein neuer je Segment hieße: bei fünfzehn # Zuschauern dreihundert Verbindungsaufbauten pro Sekunde. # # Der lange Timeout gehört zu Low-Latency-HLS: Der Player fragt nach einem # Teil, das es noch nicht gibt, und MediaMTX hält die Antwort zurück, bis # es fertig ist. _klient = httpx.AsyncClient( timeout=httpx.Timeout(30.0, connect=5.0), limits=httpx.Limits(max_connections=200, max_keepalive_connections=100), follow_redirects=True, ) async def schliessen(): await _klient.aclose() async def weiterleiten(request: Request, pfad: str): anfrage = _klient.build_request( "GET", f"{MEDIAMTX_URL}/{pfad}", params=dict(request.query_params) ) try: antwort = await _klient.send(anfrage, stream=True) except httpx.HTTPError: raise HTTPException(status_code=502, detail="Stream nicht erreichbar") if antwort.status_code >= 400: await antwort.aclose() if antwort.status_code == 404: raise HTTPException(status_code=404, detail="Kein Stream") raise HTTPException(status_code=antwort.status_code, detail="Stream-Fehler") kopf = {k: v for k, v in antwort.headers.items() if k.lower() in DURCHREICHEN} if pfad.endswith(".m3u8"): kopf.update(NICHT_ZWISCHENSPEICHERN) return StreamingResponse( antwort.aiter_raw(), status_code=antwort.status_code, headers=kopf, background=BackgroundTask(antwort.aclose), )