fix docker port
This commit is contained in:
@@ -91,8 +91,10 @@ POSTGRES_PORT=45432
|
|||||||
# DEMO_ORCHESTRATOR_RTSP_PORT=18554
|
# DEMO_ORCHESTRATOR_RTSP_PORT=18554
|
||||||
# 联调台 HLS 预览(MediaMTX 8888 映射到宿主机端口;API 反代给浏览器)
|
# 联调台 HLS 预览(MediaMTX 8888 映射到宿主机端口;API 反代给浏览器)
|
||||||
# DEMO_HLS_PREVIEW_PORT=18888
|
# DEMO_HLS_PREVIEW_PORT=18888
|
||||||
# API 在 Docker 内时设为 host.docker.internal
|
# API 在 Docker 内访问宿主机 HLS:host.docker.internal(docker -p 会自动绑定 127.0.0.1)
|
||||||
# DEMO_HLS_PREVIEW_HOST=127.0.0.1
|
# DEMO_HLS_PREVIEW_HOST=127.0.0.1
|
||||||
|
# 可选:显式指定 docker -p 绑定地址(一般留空)
|
||||||
|
# DEMO_HLS_PREVIEW_BIND_HOST=127.0.0.1
|
||||||
# Docker 内 API 访问宿主机假流时写入站点 JSON 的主机名(默认 host.docker.internal)
|
# Docker 内 API 访问宿主机假流时写入站点 JSON 的主机名(默认 host.docker.internal)
|
||||||
# DOCKER_DEMO_ORCHESTRATOR_RTSP_JSON_HOST=host.docker.internal
|
# DOCKER_DEMO_ORCHESTRATOR_RTSP_JSON_HOST=host.docker.internal
|
||||||
# 链路 2 simulated-start / fake_rtsp_from_file 起 MediaMTX 容器用
|
# 链路 2 simulated-start / fake_rtsp_from_file 起 MediaMTX 容器用
|
||||||
|
|||||||
@@ -36,7 +36,11 @@ from app.schemas import (
|
|||||||
build_consumption_summary,
|
build_consumption_summary,
|
||||||
)
|
)
|
||||||
from app.services.recording_live import accept_live_recording
|
from app.services.recording_live import accept_live_recording
|
||||||
from app.services.hls_preview import HlsPreviewManager, fetch_hls_upstream
|
from app.services.hls_preview import (
|
||||||
|
HlsPreviewManager,
|
||||||
|
docker_publish_bind_host,
|
||||||
|
fetch_hls_upstream,
|
||||||
|
)
|
||||||
from app.services.rtsp_preview import capture_rtsp_jpeg_frame
|
from app.services.rtsp_preview import capture_rtsp_jpeg_frame
|
||||||
from app.services.surgery_pipeline import SurgeryPipeline
|
from app.services.surgery_pipeline import SurgeryPipeline
|
||||||
from app.services.video.backend_resolver import BackendResolver
|
from app.services.video.backend_resolver import BackendResolver
|
||||||
@@ -213,6 +217,10 @@ async def hls_preview_ensure(
|
|||||||
)
|
)
|
||||||
hls_host = (settings.demo_hls_preview_host or "127.0.0.1").strip() or "127.0.0.1"
|
hls_host = (settings.demo_hls_preview_host or "127.0.0.1").strip() or "127.0.0.1"
|
||||||
hls_port = int(settings.demo_hls_preview_port)
|
hls_port = int(settings.demo_hls_preview_port)
|
||||||
|
bind_host = docker_publish_bind_host(
|
||||||
|
hls_host,
|
||||||
|
explicit_bind=(settings.demo_hls_preview_bind_host or "").strip(),
|
||||||
|
)
|
||||||
|
|
||||||
resolver = BackendResolver(settings, hikvision_runtime=None)
|
resolver = BackendResolver(settings, hikvision_runtime=None)
|
||||||
sources: dict[str, str] = {}
|
sources: dict[str, str] = {}
|
||||||
@@ -238,6 +246,7 @@ async def hls_preview_ensure(
|
|||||||
sources=sources,
|
sources=sources,
|
||||||
hls_host=hls_host,
|
hls_host=hls_host,
|
||||||
hls_port=hls_port,
|
hls_port=hls_port,
|
||||||
|
bind_host=bind_host,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -190,7 +190,13 @@ class Settings(BaseSettings):
|
|||||||
demo_hls_preview_port: int = Field(default=18888, ge=1, le=65535)
|
demo_hls_preview_port: int = Field(default=18888, ge=1, le=65535)
|
||||||
demo_hls_preview_host: str = Field(
|
demo_hls_preview_host: str = Field(
|
||||||
default="127.0.0.1",
|
default="127.0.0.1",
|
||||||
description="MediaMTX HLS 监听地址;API 反代目标。容器内 API 可设为 host.docker.internal。",
|
description="API 反代访问的 MediaMTX HLS 地址;容器内 API 可设为 host.docker.internal。",
|
||||||
|
)
|
||||||
|
demo_hls_preview_bind_host: str = Field(
|
||||||
|
default="",
|
||||||
|
description=(
|
||||||
|
"docker run -p 绑定到宿主机的地址;留空时 host.docker.internal/localhost 自动用 127.0.0.1。"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
#: 视觉管线记账默认 ``doctor_id``(TSV 未提供医生列时的回退);语音确认仍为独立常量 ``voice``。
|
#: 视觉管线记账默认 ``doctor_id``(TSV 未提供医生列时的回退);语音确认仍为独立常量 ``voice``。
|
||||||
|
|||||||
@@ -22,6 +22,20 @@ from app.services.synthetic_rtsp import MEDIAMTX_IMAGE
|
|||||||
CONTAINER_NAME_PREFIX = "orm-hls-preview-"
|
CONTAINER_NAME_PREFIX = "orm-hls-preview-"
|
||||||
_MEDIAMTX_HLS_INTERNAL_PORT = 8888
|
_MEDIAMTX_HLS_INTERNAL_PORT = 8888
|
||||||
_PATH_SAFE = re.compile(r"[^a-zA-Z0-9._-]+")
|
_PATH_SAFE = re.compile(r"[^a-zA-Z0-9._-]+")
|
||||||
|
# docker -p 只接受 IP/0.0.0.0,不能写 host.docker.internal
|
||||||
|
_DOCKER_PUBLISH_ALIAS_HOSTS = frozenset(
|
||||||
|
{"host.docker.internal", "host.containers.internal", "localhost"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def docker_publish_bind_host(hls_host: str, *, explicit_bind: str = "") -> str:
|
||||||
|
"""``docker run -p <bind>:port`` 用的宿主机地址;与 API 反代用的 ``hls_host`` 可不同。"""
|
||||||
|
if (explicit_bind or "").strip():
|
||||||
|
return explicit_bind.strip()
|
||||||
|
h = (hls_host or "").strip().lower()
|
||||||
|
if h in _DOCKER_PUBLISH_ALIAS_HOSTS:
|
||||||
|
return "127.0.0.1"
|
||||||
|
return (hls_host or "").strip() or "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
def sanitize_mediamtx_path(name: str) -> str:
|
def sanitize_mediamtx_path(name: str) -> str:
|
||||||
@@ -107,6 +121,7 @@ class HlsPreviewManager:
|
|||||||
sources: dict[str, str],
|
sources: dict[str, str],
|
||||||
hls_host: str,
|
hls_host: str,
|
||||||
hls_port: int,
|
hls_port: int,
|
||||||
|
bind_host: str | None = None,
|
||||||
ready_timeout_sec: float = 30.0,
|
ready_timeout_sec: float = 30.0,
|
||||||
) -> HlsPreviewSession:
|
) -> HlsPreviewSession:
|
||||||
"""链路 1:独立 MediaMTX 容器按路径拉取各 RTSP。"""
|
"""链路 1:独立 MediaMTX 容器按路径拉取各 RTSP。"""
|
||||||
@@ -129,6 +144,7 @@ class HlsPreviewManager:
|
|||||||
cfg = work / "mediamtx.yml"
|
cfg = work / "mediamtx.yml"
|
||||||
_write_mediamtx_config(cfg, mtx_paths)
|
_write_mediamtx_config(cfg, mtx_paths)
|
||||||
|
|
||||||
|
publish_host = docker_publish_bind_host(hls_host, explicit_bind=bind_host or "")
|
||||||
container = CONTAINER_NAME_PREFIX + uuid.uuid4().hex[:12]
|
container = CONTAINER_NAME_PREFIX + uuid.uuid4().hex[:12]
|
||||||
cmd = [
|
cmd = [
|
||||||
"docker",
|
"docker",
|
||||||
@@ -139,7 +155,7 @@ class HlsPreviewManager:
|
|||||||
"-v",
|
"-v",
|
||||||
f"{cfg.resolve()}:/mediamtx.yml:ro",
|
f"{cfg.resolve()}:/mediamtx.yml:ro",
|
||||||
"-p",
|
"-p",
|
||||||
f"{hls_host}:{hls_port}:{_MEDIAMTX_HLS_INTERNAL_PORT}",
|
f"{publish_host}:{hls_port}:{_MEDIAMTX_HLS_INTERNAL_PORT}",
|
||||||
MEDIAMTX_IMAGE,
|
MEDIAMTX_IMAGE,
|
||||||
]
|
]
|
||||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
r = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||||
@@ -164,9 +180,11 @@ class HlsPreviewManager:
|
|||||||
)
|
)
|
||||||
cls._active = sess
|
cls._active = sess
|
||||||
logger.info(
|
logger.info(
|
||||||
"HLS preview (pull) started container={} cameras={} hls=http://{}:{}/",
|
"HLS preview (pull) started container={} cameras={} publish={}:{} upstream=http://{}:{}/",
|
||||||
container,
|
container,
|
||||||
list(path_by_camera),
|
list(path_by_camera),
|
||||||
|
publish_host,
|
||||||
|
hls_port,
|
||||||
hls_host,
|
hls_host,
|
||||||
hls_port,
|
hls_port,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ from fastapi.testclient import TestClient
|
|||||||
|
|
||||||
from app.api import router as api_router
|
from app.api import router as api_router
|
||||||
from app.config import Settings
|
from app.config import Settings
|
||||||
from app.services.hls_preview import HlsPreviewManager, HlsPreviewSession
|
from app.services.hls_preview import (
|
||||||
|
HlsPreviewManager,
|
||||||
|
HlsPreviewSession,
|
||||||
|
docker_publish_bind_host,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -28,6 +32,12 @@ def hls_client(tmp_path) -> TestClient:
|
|||||||
HlsPreviewManager.stop()
|
HlsPreviewManager.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def test_docker_publish_bind_host_maps_docker_internal() -> None:
|
||||||
|
assert docker_publish_bind_host("host.docker.internal") == "127.0.0.1"
|
||||||
|
assert docker_publish_bind_host("127.0.0.1") == "127.0.0.1"
|
||||||
|
assert docker_publish_bind_host("host.docker.internal", explicit_bind="0.0.0.0") == "0.0.0.0"
|
||||||
|
|
||||||
|
|
||||||
def test_hls_preview_ensure_pull(hls_client: TestClient) -> None:
|
def test_hls_preview_ensure_pull(hls_client: TestClient) -> None:
|
||||||
fake_sess = HlsPreviewSession(
|
fake_sess = HlsPreviewSession(
|
||||||
hls_host="127.0.0.1",
|
hls_host="127.0.0.1",
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ SDK **不作为构建期依赖**:将厂商提供的 Linux x86_64 动态库挂
|
|||||||
| **链路 2**(模拟实时) | 不使用 HLS/RTSP 预览;联调台在各路槽位用本地 `<video>` 预览所选文件 |
|
| **链路 2**(模拟实时) | 不使用 HLS/RTSP 预览;联调台在各路槽位用本地 `<video>` 预览所选文件 |
|
||||||
|
|
||||||
- 播放地址形如:`GET /internal/demo/hls-preview/{camera_id}/index.m3u8`(playlist 内分片 URL 会重写为 API 路径)。
|
- 播放地址形如:`GET /internal/demo/hls-preview/{camera_id}/index.m3u8`(playlist 内分片 URL 会重写为 API 路径)。
|
||||||
- **API 在 Docker 内**时须设 `DEMO_HLS_PREVIEW_HOST=host.docker.internal`(compose 已默认),且宿主机需有 **docker** CLI 供拉流模式起 MediaMTX。
|
- **API 在 Docker 内**时设 `DEMO_HLS_PREVIEW_HOST=host.docker.internal`(compose 已默认):API 经该主机名访问宿主机上的 HLS;`docker run -p` 会自动绑定 `127.0.0.1:18888`(不能写 `host.docker.internal` 作 `-p` 地址)。
|
||||||
- 环境变量:`DEMO_HLS_PREVIEW_PORT`(默认 18888)、`DEMO_HLS_PREVIEW_HOST`(本机默认 `127.0.0.1`)。
|
- 环境变量:`DEMO_HLS_PREVIEW_PORT`(默认 18888)、`DEMO_HLS_PREVIEW_HOST`(反代上游)、可选 `DEMO_HLS_PREVIEW_BIND_HOST`(显式 `-p` 绑定,一般留空)。
|
||||||
|
|
||||||
单帧 JPEG 预览仍保留:`GET /internal/demo/rtsp-preview/{camera_id}/frame.jpg`(不依赖 MediaMTX)。
|
单帧 JPEG 预览仍保留:`GET /internal/demo/rtsp-preview/{camera_id}/frame.jpg`(不依赖 MediaMTX)。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user