fix
This commit is contained in:
@@ -90,7 +90,8 @@ POSTGRES_PORT=45432
|
||||
# DEMO_ORCHESTRATOR_ENABLED=false
|
||||
# DEMO_ORCHESTRATOR_RTSP_PORT=18554
|
||||
# 联调台 HLS(Compose:先 docker compose up -d mediamtx-hls api)
|
||||
# DEMO_HLS_PREVIEW_UPSTREAM=http://127.0.0.1:18888
|
||||
# Compose 内 api 反代侧车:DEMO_HLS_PREVIEW_UPSTREAM=http://mediamtx-hls:8888
|
||||
# 本机 uvicorn 直连 published 端口:DEMO_HLS_PREVIEW_UPSTREAM=http://127.0.0.1:18888
|
||||
# 本机 uvicorn 且无 mediamtx-hls 侧车时改为:DEMO_HLS_PREVIEW_UPSTREAM=ephemeral
|
||||
# DEMO_HLS_PREVIEW_PORT=18888
|
||||
# Docker 内 API 访问宿主机假流时写入站点 JSON 的主机名(默认 host.docker.internal)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
import pathlib
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import (
|
||||
@@ -260,7 +261,13 @@ async def hls_preview_ensure(
|
||||
detail += (
|
||||
"。当前 API 可能仍是旧版本:请执行 "
|
||||
"`docker compose up -d --build api mediamtx-hls`,并确认 "
|
||||
"DEMO_HLS_PREVIEW_UPSTREAM=http://127.0.0.1:18888(勿把 host.docker.internal 用于 docker -p)。"
|
||||
"DEMO_HLS_PREVIEW_UPSTREAM=http://mediamtx-hls:8888(勿把 host.docker.internal 用于 docker -p)。"
|
||||
)
|
||||
elif "127.0.0.1" in str(exc) and pathlib.Path("/.dockerenv").is_file():
|
||||
detail += (
|
||||
"。API 在 Docker 桥接网内时勿用 127.0.0.1 访问 published 端口;"
|
||||
"请设 DEMO_HLS_PREVIEW_UPSTREAM=http://mediamtx-hls:8888 并 "
|
||||
"`docker compose up -d --build api mediamtx-hls`。"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
|
||||
@@ -191,7 +191,8 @@ class Settings(BaseSettings):
|
||||
demo_hls_preview_upstream: str = Field(
|
||||
default="http://127.0.0.1:18888",
|
||||
description=(
|
||||
"MediaMTX HLS 基址(Compose 侧车默认 127.0.0.1:18888)。"
|
||||
"MediaMTX HLS 基址。Compose 内 api 用 http://mediamtx-hls:8888;"
|
||||
"本机 uvicorn 用 http://127.0.0.1:18888。"
|
||||
"设为 ephemeral 则改回临时 docker run(本机开发)。"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -28,9 +28,7 @@ from app.services.synthetic_rtsp import MEDIAMTX_IMAGE
|
||||
CONTAINER_NAME_PREFIX = "orm-hls-preview-"
|
||||
_MEDIAMTX_HLS_INTERNAL_PORT = 8888
|
||||
_PATH_SAFE = re.compile(r"[^a-zA-Z0-9._-]+")
|
||||
_DOCKER_PUBLISH_ALIAS_HOSTS = frozenset(
|
||||
{"host.docker.internal", "host.containers.internal", "localhost"}
|
||||
)
|
||||
_DOCKER_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost", "::1"})
|
||||
|
||||
|
||||
def normalize_upstream_base(url: str) -> str:
|
||||
@@ -55,7 +53,10 @@ def upstream_host_port(upstream_base: str) -> tuple[str, int]:
|
||||
def docker_publish_bind_host(hls_host: str) -> str:
|
||||
"""本机临时容器 ``docker run -p`` 的绑定地址(勿用主机名)。"""
|
||||
h = (hls_host or "").strip().lower()
|
||||
if h in _DOCKER_PUBLISH_ALIAS_HOSTS:
|
||||
if h in _DOCKER_LOOPBACK_HOSTS or h in (
|
||||
"host.docker.internal",
|
||||
"host.containers.internal",
|
||||
):
|
||||
return "127.0.0.1"
|
||||
return (hls_host or "").strip() or "127.0.0.1"
|
||||
|
||||
@@ -90,6 +91,39 @@ def _wait_tcp(host: str, port: int, *, timeout: float) -> None:
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
def attached_upstream_connect_candidates(
|
||||
upstream_base: str,
|
||||
*,
|
||||
internal_service_host: str = "mediamtx-hls",
|
||||
) -> list[tuple[str, int]]:
|
||||
"""Bridge 网络内 API 无法经 127.0.0.1 访问 published 端口,需回退到 Compose 服务名。"""
|
||||
host, port = upstream_host_port(upstream_base)
|
||||
candidates: list[tuple[str, int]] = [(host, port)]
|
||||
if Path("/.dockerenv").is_file() and host.lower() in _DOCKER_LOOPBACK_HOSTS:
|
||||
internal = (internal_service_host.strip() or "mediamtx-hls", _MEDIAMTX_HLS_INTERNAL_PORT)
|
||||
if internal not in candidates:
|
||||
candidates.append(internal)
|
||||
return candidates
|
||||
|
||||
|
||||
def _wait_tcp_any(candidates: list[tuple[str, int]], *, timeout: float) -> tuple[str, int]:
|
||||
if not candidates:
|
||||
raise RuntimeError("no TCP candidates for HLS upstream")
|
||||
deadline = time.monotonic() + max(1.0, timeout)
|
||||
errors: list[str] = []
|
||||
for host, port in candidates:
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
break
|
||||
try:
|
||||
_wait_tcp(host, port, timeout=remaining)
|
||||
return host, port
|
||||
except RuntimeError as exc:
|
||||
errors.append(f"{host}:{port} ({exc})")
|
||||
tried = ", ".join(errors) if errors else ", ".join(f"{h}:{p}" for h, p in candidates)
|
||||
raise RuntimeError(f"等待 MediaMTX HLS 超时({timeout:g}s),已尝试: {tried}")
|
||||
|
||||
|
||||
def _write_mediamtx_config(path: Path, paths: dict[str, str]) -> None:
|
||||
lines = [
|
||||
"# Generated for OR demo HLS preview",
|
||||
@@ -241,13 +275,21 @@ class HlsPreviewManager:
|
||||
f"failed to restart {container_name} (is mediamtx-hls running in compose?): {err}"
|
||||
)
|
||||
|
||||
host, port = upstream_host_port(upstream_base)
|
||||
candidates = attached_upstream_connect_candidates(upstream_base)
|
||||
try:
|
||||
_wait_tcp(host, port, timeout=ready_timeout_sec)
|
||||
except Exception:
|
||||
host, port = _wait_tcp_any(candidates, timeout=ready_timeout_sec)
|
||||
except RuntimeError as exc:
|
||||
raise RuntimeError(
|
||||
f"HLS preview not reachable at {upstream_base} after restarting {container_name}"
|
||||
) from None
|
||||
) from exc
|
||||
reachable_base = normalize_upstream_base(f"http://{host}:{port}")
|
||||
if reachable_base != upstream_base:
|
||||
logger.info(
|
||||
"HLS preview (attached) using {} instead of configured {}",
|
||||
reachable_base,
|
||||
upstream_base,
|
||||
)
|
||||
upstream_base = reachable_base
|
||||
|
||||
sess = HlsPreviewSession(
|
||||
upstream_base=upstream_base,
|
||||
|
||||
@@ -115,8 +115,8 @@ services:
|
||||
DEMO_ORCHESTRATOR_ENABLED: ${DEMO_ORCHESTRATOR_ENABLED:-false}
|
||||
DEMO_ORCHESTRATOR_RTSP_PORT: ${DEMO_ORCHESTRATOR_RTSP_PORT:-18554}
|
||||
DEMO_ORCHESTRATOR_RTSP_JSON_HOST: ${DOCKER_DEMO_ORCHESTRATOR_RTSP_JSON_HOST:-host.docker.internal}
|
||||
# api 使用 network:host,经宿主机端口访问 mediamtx-hls(勿用 mediamtx-hls 主机名)
|
||||
DEMO_HLS_PREVIEW_UPSTREAM: ${DEMO_HLS_PREVIEW_UPSTREAM:-http://127.0.0.1:18888}
|
||||
# api 在 Compose 桥接网内反代 mediamtx-hls:8888;宿主机调试 HLS 仍用 127.0.0.1:18888
|
||||
DEMO_HLS_PREVIEW_UPSTREAM: ${DEMO_HLS_PREVIEW_UPSTREAM:-http://mediamtx-hls:8888}
|
||||
DEMO_HLS_PREVIEW_CONFIG_DIR: ${DEMO_HLS_PREVIEW_CONFIG_DIR:-/hls-preview-config}
|
||||
DEMO_HLS_PREVIEW_CONTAINER_NAME: ${DEMO_HLS_PREVIEW_CONTAINER_NAME:-orm-mediamtx-hls}
|
||||
MEDIAMTX_DOCKER_IMAGE: ${MEDIAMTX_DOCKER_IMAGE:-m.daocloud.io/docker.io/bluenviron/mediamtx:latest}
|
||||
|
||||
@@ -13,6 +13,7 @@ from app.config import Settings
|
||||
from app.services.hls_preview import (
|
||||
HlsPreviewManager,
|
||||
HlsPreviewSession,
|
||||
attached_upstream_connect_candidates,
|
||||
docker_publish_bind_host,
|
||||
normalize_upstream_base,
|
||||
resolve_ephemeral_upstream_host,
|
||||
@@ -92,6 +93,20 @@ def test_hls_proxy_rewrites_playlist(hls_client: TestClient) -> None:
|
||||
assert "127.0.0.1:18888" not in text
|
||||
|
||||
|
||||
def test_attached_upstream_connect_candidates_local() -> None:
|
||||
with patch("app.services.hls_preview.Path") as path_cls:
|
||||
path_cls.return_value.is_file.return_value = False
|
||||
got = attached_upstream_connect_candidates("http://127.0.0.1:18888")
|
||||
assert got == [("127.0.0.1", 18888)]
|
||||
|
||||
|
||||
def test_attached_upstream_connect_candidates_docker_bridge() -> None:
|
||||
with patch("app.services.hls_preview.Path") as path_cls:
|
||||
path_cls.return_value.is_file.return_value = True
|
||||
got = attached_upstream_connect_candidates("http://127.0.0.1:18888")
|
||||
assert got == [("127.0.0.1", 18888), ("mediamtx-hls", 8888)]
|
||||
|
||||
|
||||
def test_resolve_ephemeral_upstream_host_local() -> None:
|
||||
with patch("app.services.hls_preview.Path") as path_cls:
|
||||
path_cls.return_value.is_file.return_value = False
|
||||
|
||||
@@ -63,7 +63,7 @@ SDK **不作为构建期依赖**:将厂商提供的 Linux x86_64 动态库挂
|
||||
|
||||
| 部署方式 | 行为 |
|
||||
|----------|------|
|
||||
| **Docker Compose(推荐)** | 固定服务 `mediamtx-hls`(宿主机 `127.0.0.1:18888`);`hls-preview-init` 种子共享卷后 MediaMTX 直接读 `/config/mediamtx.yml`(官方镜像是 scratch,勿用 `/bin/sh` entrypoint);`ensure` 写共享 `mediamtx.yml` 并 `docker restart orm-mediamtx-hls`;`DEMO_HLS_PREVIEW_UPSTREAM=http://127.0.0.1:18888` |
|
||||
| **Docker Compose(推荐)** | 固定服务 `mediamtx-hls`(宿主机 `127.0.0.1:18888`,容器内 `mediamtx-hls:8888`);`hls-preview-init` 种子共享卷后 MediaMTX 直接读 `/config/mediamtx.yml`(官方镜像是 scratch,勿用 `/bin/sh` entrypoint);`ensure` 写共享 `mediamtx.yml` 并 `docker restart orm-mediamtx-hls`;`DEMO_HLS_PREVIEW_UPSTREAM=http://mediamtx-hls:8888` |
|
||||
| **本机 uvicorn** | 未设 upstream 时按需 `docker run` 临时 MediaMTX(端口默认 `127.0.0.1:18888`) |
|
||||
|
||||
| 链路 | 行为 |
|
||||
|
||||
Reference in New Issue
Block a user