fix:真实视频流还没接通,页面就会稳定显示“未实时获取真实值”
This commit is contained in:
@@ -17,10 +17,7 @@ JPEG_FRAME_DIR = WORKSPACE_ROOT / "RobotDataShow" / "jpeg-frames"
|
||||
# GPS 数据 JSON 文件路径
|
||||
GEOSTREAM_JSON_PATH = WORKSPACE_ROOT / "GeoStream" / "gps_latest.json"
|
||||
GEOSTREAM_STALE_SECONDS = 15
|
||||
SAMPLE_CDATA_DIR = PROJECT_ROOT / "SampleCData"
|
||||
CONFIG_DIR = PROJECT_ROOT / "config"
|
||||
PRIMARY_OMNISOCKET_CONFIG_PATH = CONFIG_DIR / "omnisocket_demo.yaml"
|
||||
LEGACY_OMNISOCKET_CONFIG_PATH = SAMPLE_CDATA_DIR / "config" / "omnisocket_demo.yaml"
|
||||
OMNISOCKET_CONFIG_PATH = PROJECT_ROOT / "config" / "omnisocket_demo.yaml"
|
||||
VIDEO_SOURCE_MODE = os.getenv("VIDEO_SOURCE_MODE", "auto").strip().lower()
|
||||
OMNISOCKET_FRAME_FRESH_SECONDS = 2.0
|
||||
|
||||
@@ -29,12 +26,6 @@ def utc_iso_now() -> str:
|
||||
return datetime.now(UTC).isoformat(timespec="seconds").replace("+00:00", "Z")
|
||||
|
||||
|
||||
def resolve_omnisocket_config_path() -> Path:
|
||||
if PRIMARY_OMNISOCKET_CONFIG_PATH.exists():
|
||||
return PRIMARY_OMNISOCKET_CONFIG_PATH
|
||||
return LEGACY_OMNISOCKET_CONFIG_PATH
|
||||
|
||||
|
||||
class OmniSocketVideoReceiver:
|
||||
def __init__(self) -> None:
|
||||
self._lock = threading.Lock()
|
||||
@@ -64,7 +55,7 @@ class OmniSocketVideoReceiver:
|
||||
try:
|
||||
from omnisocket import MSG_TYPE_BINARY, Session, VIDEO_DEFAULTS # type: ignore
|
||||
except ImportError:
|
||||
python_dir = SAMPLE_CDATA_DIR / "python"
|
||||
python_dir = WORKSPACE_ROOT / "OmniSocketGo" / "python"
|
||||
if python_dir.exists():
|
||||
sys.path.insert(0, str(python_dir))
|
||||
from omnisocket import MSG_TYPE_BINARY, Session, VIDEO_DEFAULTS # type: ignore
|
||||
@@ -75,8 +66,6 @@ class OmniSocketVideoReceiver:
|
||||
|
||||
def ensure_started(self) -> None:
|
||||
# 当第一次请求帧或状态时,再懒启动后台接收线程。
|
||||
if VIDEO_SOURCE_MODE == "sample":
|
||||
return
|
||||
if self._session_cls is None or self._binary_msg_type is None:
|
||||
return
|
||||
|
||||
@@ -96,17 +85,16 @@ class OmniSocketVideoReceiver:
|
||||
# transport + video_receiver。
|
||||
# 即使配置文件不存在,也允许回退到默认值继续运行。
|
||||
config: dict[str, Any] = {}
|
||||
config_path = resolve_omnisocket_config_path()
|
||||
if config_path.exists():
|
||||
if OMNISOCKET_CONFIG_PATH.exists():
|
||||
try:
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
|
||||
with config_path.open("r", encoding="utf-8") as file:
|
||||
with OMNISOCKET_CONFIG_PATH.open("r", encoding="utf-8") as file:
|
||||
config = yaml.safe_load(file) or {}
|
||||
except ImportError:
|
||||
# 如果当前环境没有 PyYAML,就用一个足够支撑当前 demo 配置的简化解析器。
|
||||
config = self._load_simple_yaml_config(config_path)
|
||||
# 当前配置文件结构非常简单,缺少 PyYAML 时用简化解析器兜底。
|
||||
config = self._load_simple_yaml_config(OMNISOCKET_CONFIG_PATH)
|
||||
except Exception as error: # pragma: no cover - 可选依赖
|
||||
self._last_error = f"config load failed: {error}"
|
||||
|
||||
@@ -247,7 +235,7 @@ class OmniSocketVideoReceiver:
|
||||
continue
|
||||
|
||||
with self._lock:
|
||||
# 这里只保留最新的一张 JPEG 帧,供 Web 接口直接返回给前端。
|
||||
# 缓存:这里只保留最新的一张 JPEG 帧,供 Web 接口直接返回给前端。
|
||||
self._latest_frame = jpeg_frame
|
||||
self._latest_received_at = time.time()
|
||||
self._latest_sequence = self._extract_sequence(frame)
|
||||
@@ -277,7 +265,6 @@ class OmniSocketVideoReceiver:
|
||||
def get_status(self) -> dict[str, Any]:
|
||||
self.ensure_started()
|
||||
config = self._load_config()
|
||||
config_path = resolve_omnisocket_config_path()
|
||||
transport_cfg = config.get("transport", {})
|
||||
video_cfg = config.get("video_receiver", {})
|
||||
with self._lock:
|
||||
@@ -292,7 +279,7 @@ class OmniSocketVideoReceiver:
|
||||
"frames_received": self._frames_received,
|
||||
"latest_sequence": self._latest_sequence,
|
||||
"last_error": self._last_error,
|
||||
"config_path": str(config_path),
|
||||
"config_path": str(OMNISOCKET_CONFIG_PATH),
|
||||
"server_addr": str(transport_cfg.get("server_addr", "")),
|
||||
"relay_via": str(transport_cfg.get("relay_via", "")),
|
||||
"peer_id": str(video_cfg.get("peer_id", "")),
|
||||
@@ -302,9 +289,6 @@ class OmniSocketVideoReceiver:
|
||||
|
||||
class VideoFrameService:
|
||||
def __init__(self) -> None:
|
||||
self._lock = threading.Lock()
|
||||
self._frame_paths = sorted(JPEG_FRAME_DIR.glob("*.jpg"))
|
||||
self._index = 0
|
||||
self._receiver = OmniSocketVideoReceiver()
|
||||
|
||||
def get_status(self) -> dict[str, Any]:
|
||||
@@ -323,31 +307,16 @@ class VideoFrameService:
|
||||
"receiver": receiver_status,
|
||||
}
|
||||
|
||||
# 强制实时模式时,如果还没收到真实帧,就明确告诉前端“正在等待”,
|
||||
# 不再悄悄回退到本地样例图,避免调试时误以为链路已接通。
|
||||
if VIDEO_SOURCE_MODE == "omnisocket":
|
||||
wait_detail = receiver_status["last_error"] or (
|
||||
"等待 OmniSocket 实时 JPEG 帧,"
|
||||
f"接收端 peer_id={receiver_status['peer_id']},"
|
||||
f"服务器={receiver_status['server_addr']}"
|
||||
)
|
||||
return {
|
||||
"available": False,
|
||||
"source_mode": "omnisocket-waiting",
|
||||
"frame_count": receiver_status["frames_received"],
|
||||
"fps": 30,
|
||||
"frame_dir": str(JPEG_FRAME_DIR),
|
||||
"source_detail": wait_detail,
|
||||
"receiver": receiver_status,
|
||||
}
|
||||
|
||||
wait_detail = receiver_status["last_error"] or (
|
||||
"未实时获取真实值,请检查 OmniSocket 服务、视频发送端和接收配置。"
|
||||
)
|
||||
return {
|
||||
"available": bool(self._frame_paths),
|
||||
"source_mode": "sample-jpeg-frame-loop" if self._frame_paths else "unavailable",
|
||||
"frame_count": len(self._frame_paths),
|
||||
"available": False,
|
||||
"source_mode": "omnisocket-waiting",
|
||||
"frame_count": receiver_status["frames_received"],
|
||||
"fps": 30,
|
||||
"frame_dir": str(JPEG_FRAME_DIR),
|
||||
"source_detail": receiver_status["last_error"] or "fallback to local sample frames",
|
||||
"source_detail": wait_detail,
|
||||
"receiver": receiver_status,
|
||||
}
|
||||
|
||||
@@ -357,23 +326,7 @@ class VideoFrameService:
|
||||
if receiver_frame is not None:
|
||||
return receiver_frame
|
||||
|
||||
# 强制实时模式下,如果还没收到真实帧,直接报错,
|
||||
# 这样前端就能明确知道实时链路还没接通。
|
||||
if VIDEO_SOURCE_MODE == "omnisocket":
|
||||
raise RuntimeError(
|
||||
"OmniSocket 实时 JPEG 帧暂未就绪,请检查 server_addr、peer_id、"
|
||||
"target_peer,以及视频发送端是否已经启动。"
|
||||
)
|
||||
|
||||
# 如果当前没有真实帧,就回退到本地演示 JPEG 文件,保证页面仍然可用。
|
||||
if not self._frame_paths:
|
||||
raise FileNotFoundError(f"No JPEG frames found in {JPEG_FRAME_DIR}")
|
||||
|
||||
with self._lock:
|
||||
frame_path = self._frame_paths[self._index]
|
||||
self._index = (self._index + 1) % len(self._frame_paths)
|
||||
|
||||
return frame_path.read_bytes()
|
||||
raise RuntimeError("未实时获取真实值,当前没有可用的真实 JPEG 帧。")
|
||||
|
||||
def iter_mjpeg(self, fps: float = 6.0) -> Iterator[bytes]:
|
||||
frame_interval = 1.0 / max(1.0, min(fps, 30.0))
|
||||
|
||||
Reference in New Issue
Block a user