fix(hls): forward MediaMTX LL-HLS session query in proxy

MediaMTX v1.18+ requires ?session= on sub-playlists; the API proxy was
dropping query params and causing 401 on video1_stream.m3u8 fetches.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-22 17:35:25 +08:00
parent e7510342ba
commit 3de34fb79c
3 changed files with 46 additions and 3 deletions

View File

@@ -337,7 +337,7 @@ async def hls_preview_proxy(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="unknown camera") from exc
def _fetch() -> tuple[bytes, str]:
return fetch_hls_upstream(upstream)
return fetch_hls_upstream(upstream, query=request.url.query)
try:
body, ctype = await asyncio.to_thread(_fetch)

View File

@@ -400,8 +400,18 @@ class HlsPreviewManager:
return f"{base}/{sub}" if sub else base
def fetch_hls_upstream(url: str) -> tuple[bytes, str]:
req = Request(url, headers={"User-Agent": "operation-room-monitor-hls-proxy/1.0"})
def append_upstream_query(upstream_url: str, query: str) -> str:
q = (query or "").strip().lstrip("?")
if not q:
return upstream_url
base = upstream_url.rstrip("/")
sep = "&" if "?" in base else "?"
return f"{base}{sep}{q}"
def fetch_hls_upstream(url: str, *, query: str = "") -> tuple[bytes, str]:
target = append_upstream_query(url, query)
req = Request(target, headers={"User-Agent": "operation-room-monitor-hls-proxy/1.0"})
with urlopen(req, timeout=15) as resp: # noqa: S310
body = resp.read()
ctype = resp.headers.get_content_type() or "application/octet-stream"

View File

@@ -14,6 +14,7 @@ from app.services.hls_preview import (
HlsPreviewManager,
HlsPreviewSession,
_mediamtx_config_lines,
append_upstream_query,
attached_upstream_connect_candidates,
docker_publish_bind_host,
normalize_upstream_base,
@@ -75,6 +76,38 @@ def test_hls_preview_ensure_pull(hls_client: TestClient) -> None:
assert "index.m3u8" in body["cameras"][0]["playlist_url"]
def test_append_upstream_query() -> None:
assert (
append_upstream_query("http://mediamtx-hls:8888/or-cam-01/video1_stream.m3u8", "session=abc")
== "http://mediamtx-hls:8888/or-cam-01/video1_stream.m3u8?session=abc"
)
assert append_upstream_query("http://host/a.m3u8?foo=1", "session=abc") == (
"http://host/a.m3u8?foo=1&session=abc"
)
def test_hls_proxy_forwards_session_query(hls_client: TestClient) -> None:
HlsPreviewManager._active = HlsPreviewSession(
upstream_base="http://127.0.0.1:18888",
path_by_camera={"or-cam-01": "or-cam-01"},
mode="attached",
)
captured: list[str] = []
def _fake_fetch(url: str, *, query: str = "") -> tuple[bytes, str]:
captured.append(append_upstream_query(url, query))
return (b"#EXTM3U\n", "application/vnd.apple.mpegurl")
with patch("app.api.fetch_hls_upstream", side_effect=_fake_fetch):
r = hls_client.get(
"/internal/demo/hls-preview/or-cam-01/video1_stream.m3u8?session=abc-123"
)
assert r.status_code == 200
assert captured == [
"http://127.0.0.1:18888/or-cam-01/video1_stream.m3u8?session=abc-123"
]
def test_hls_proxy_rewrites_playlist(hls_client: TestClient) -> None:
HlsPreviewManager._active = HlsPreviewSession(
upstream_base="http://127.0.0.1:18888",