diff --git a/app/main.py b/app/main.py index 77df950..0ef9ecd 100644 --- a/app/main.py +++ b/app/main.py @@ -7,14 +7,18 @@ import logging from contextlib import asynccontextmanager from typing import AsyncGenerator +import httpx import aiomqtt -from fastapi import FastAPI, Request +from fastapi import FastAPI, Request, Response from fastapi.responses import HTMLResponse, StreamingResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from .config import settings +# HTTP client for proxying to go2rtc +http_client = httpx.AsyncClient(timeout=10.0) + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -139,6 +143,41 @@ async def events() -> StreamingResponse: ) +@app.post("/api/webrtc") +async def webrtc_proxy(request: Request, src: str): + """Proxy WebRTC offers to go2rtc to avoid CORS issues""" + try: + body = await request.body() + response = await http_client.post( + f"http://{settings.GO2RTC_HOST}/api/webrtc?src={src}", + content=body, + headers={"Content-Type": "application/sdp"} + ) + return Response( + content=response.content, + status_code=response.status_code, + media_type="application/sdp" + ) + except Exception as e: + logger.error(f"WebRTC proxy error for {src}: {e}") + return Response(content=str(e), status_code=500) + + +@app.get("/api/streams") +async def streams_proxy(): + """Proxy streams list from go2rtc""" + try: + response = await http_client.get(f"http://{settings.GO2RTC_HOST}/api/streams") + return Response( + content=response.content, + status_code=response.status_code, + media_type="application/json" + ) + except Exception as e: + logger.error(f"Streams proxy error: {e}") + return Response(content=str(e), status_code=500) + + @app.get("/health") async def health(): """Health check endpoint""" diff --git a/app/templates/viewer.html b/app/templates/viewer.html index d440238..a551b25 100644 --- a/app/templates/viewer.html +++ b/app/templates/viewer.html @@ -273,8 +273,8 @@ const offer = await pc.createOffer(); await pc.setLocalDescription(offer); - // Send offer to go2rtc - const response = await fetch(`http://${GO2RTC_HOST}/api/webrtc?src=${cameraId}`, { + // Send offer to go2rtc via proxy + const response = await fetch(`/api/webrtc?src=${cameraId}`, { method: 'POST', headers: { 'Content-Type': 'application/sdp' }, body: offer.sdp @@ -351,7 +351,7 @@ await pc.setLocalDescription(offer); try { - const response = await fetch(`http://${GO2RTC_HOST}/api/webrtc?src=${cameraId}`, { + const response = await fetch(`/api/webrtc?src=${cameraId}`, { method: 'POST', headers: { 'Content-Type': 'application/sdp' }, body: offer.sdp diff --git a/requirements.txt b/requirements.txt index 4d66922..8d1673d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ fastapi>=0.109.0 uvicorn[standard]>=0.27.0 aiomqtt>=2.0.0 +httpx>=0.26.0 jinja2>=3.1.0 pydantic-settings>=2.0.0 python-multipart>=0.0.6