feat: 站点 JSON、语音终端 WebSocket 指派与客户端联调
- 用 OR_SITE_CONFIG_JSON_FILE 统一术间配置(video_rtsp_urls + voice_or_room_bindings) - VoiceTerminalHub:assignment、WS 推送与 HTTP 查询;开录/停录后 notify - 一键联调 orchestrate-and-start 与 /client/surgeries/start 共用指派逻辑,修复 demo 路径不发 WS - 语音桌面端:SIGINT 退出、shutdown 清理、仅 WS 指派、固定 pending 轮询间隔、界面仅保留录音时长 - 新增/调整契约与绑定测试,文档与示例配置同步 Made-with: Cursor
This commit is contained in:
@@ -13,10 +13,15 @@ from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, s
|
||||
from loguru import logger
|
||||
|
||||
from app.config import settings
|
||||
from app.dependencies import get_surgery_pipeline
|
||||
from app.dependencies import get_surgery_pipeline, get_voice_terminal_hub
|
||||
from app.schemas import SurgeryApiResponse, SurgeryStartRequest
|
||||
from app.services.synthetic_rtsp import StreamSpec, SyntheticRtspManager, write_rtsp_url_json_file
|
||||
from app.or_site_config import merge_video_rtsp_urls_into_file
|
||||
from app.services.synthetic_rtsp import StreamSpec, SyntheticRtspManager
|
||||
from app.services.surgery_pipeline import SurgeryPipeline
|
||||
from app.services.voice_terminal_hub import (
|
||||
VoiceTerminalHub,
|
||||
assign_voice_terminal_after_recording_started,
|
||||
)
|
||||
from app.surgery_errors import SurgeryPipelineError
|
||||
|
||||
router = APIRouter(prefix="/internal/demo", tags=["demo"])
|
||||
@@ -39,7 +44,8 @@ def _orchestrate_write_rtsp_host() -> str:
|
||||
summary="一键联调:上传 1–4 路视频并开录",
|
||||
description=(
|
||||
"仅当 DEMO_ORCHESTRATOR_ENABLED=true。保存一路或多路视频、启动 MediaMTX+ffmpeg、"
|
||||
"将 RTSP 映射写入 VIDEO_RTSP_URLS_JSON_FILE,再执行与 /client/surgeries/start 相同的开录逻辑。"
|
||||
"将 RTSP 映射合并写入 OR_SITE_CONFIG_JSON_FILE 的 video_rtsp_urls,再执行与 /client/surgeries/start 相同的开录逻辑"
|
||||
"(含按 voice_or_room_bindings 解析并 WebSocket 推送语音终端指派)。"
|
||||
),
|
||||
)
|
||||
async def orchestrate_and_start(
|
||||
@@ -58,6 +64,7 @@ async def orchestrate_and_start(
|
||||
rtsp_path_4: Annotated[str, Form()] = "demo4",
|
||||
candidate_consumables_json: Annotated[str, Form()] = "[]",
|
||||
pipeline: SurgeryPipeline = Depends(get_surgery_pipeline),
|
||||
voice_hub: VoiceTerminalHub = Depends(get_voice_terminal_hub),
|
||||
) -> SurgeryApiResponse:
|
||||
logger.info(
|
||||
"demo orchestrate-and-start: surgery_id={} cameras={} rpaths={}",
|
||||
@@ -70,12 +77,13 @@ async def orchestrate_and_start(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Demo orchestrator disabled (set DEMO_ORCHESTRATOR_ENABLED=true).",
|
||||
)
|
||||
path_raw = (settings.video_rtsp_urls_json_file or "").strip()
|
||||
path_raw = (settings.or_site_config_json_file or "").strip()
|
||||
if not path_raw:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=(
|
||||
"VIDEO_RTSP_URLS_JSON_FILE must be set to a writable path; "
|
||||
"OR_SITE_CONFIG_JSON_FILE must be set to a writable path "
|
||||
"(strict site JSON with video_rtsp_urls + voice_or_room_bindings); "
|
||||
"in Docker, bind-mount a host file to this path."
|
||||
),
|
||||
)
|
||||
@@ -195,7 +203,7 @@ async def orchestrate_and_start(
|
||||
try:
|
||||
|
||||
def _write() -> None:
|
||||
write_rtsp_url_json_file(
|
||||
merge_video_rtsp_urls_into_file(
|
||||
json_path,
|
||||
url_map_host,
|
||||
replace_host=host_for_json,
|
||||
@@ -224,6 +232,13 @@ async def orchestrate_and_start(
|
||||
detail={"code": exc.code, "message": exc.message, "surgery_id": body.surgery_id},
|
||||
) from exc
|
||||
|
||||
await assign_voice_terminal_after_recording_started(
|
||||
voice_hub,
|
||||
surgery_id=body.surgery_id,
|
||||
camera_ids=list(body.camera_ids),
|
||||
set_voice_terminal_id=pipeline.set_voice_terminal_id,
|
||||
)
|
||||
|
||||
return SurgeryApiResponse(
|
||||
surgery_id=body.surgery_id,
|
||||
status="accepted",
|
||||
|
||||
Reference in New Issue
Block a user