diff --git a/controller/cuda_grid_controller/config.py b/controller/cuda_grid_controller/config.py index a322c60..4fe60ff 100644 --- a/controller/cuda_grid_controller/config.py +++ b/controller/cuda_grid_controller/config.py @@ -101,6 +101,10 @@ class InstanceCfg(BaseModel): default="streamselect@main_cam", description="ZMQ target streamselect filter для dynamic main camera (single layout)", ) + mpp_main_filter_target: str = Field( + default="streamselect@mpp_main", + description="ZMQ target streamselect filter для dynamic main в main_plus_preview layout", + ) layout_map: dict[str, int] = Field( default_factory=lambda: {"quad": 0, "single": 1, "main_plus_preview": 2}, description="layout name → streamselect map index (соответствует pipeline filter_complex)", diff --git a/controller/cuda_grid_controller/dispatch.py b/controller/cuda_grid_controller/dispatch.py index 8d58890..81d386f 100644 --- a/controller/cuda_grid_controller/dispatch.py +++ b/controller/cuda_grid_controller/dispatch.py @@ -196,6 +196,20 @@ class CommandDispatcher: except (TimeoutError, Exception) as e: log.warning("dispatch.main_cam_fail", instance=instance, error=str(e)) + async def set_mpp_main(self, instance: str, cam_index: int) -> None: + """Switch mpp main camera через streamselect@mpp_main map.""" + inst = self._find_instance(instance) + if inst is None: + return + client = self._client(inst) + try: + reply = await client.send_command( + inst.mpp_main_filter_target, "map", str(cam_index) + ) + log.info("dispatch.mpp_main_set", instance=instance, cam_index=cam_index, ffmpeg_reply=reply) + except (TimeoutError, Exception) as e: + log.warning("dispatch.mpp_main_fail", instance=instance, error=str(e)) + async def _reload_icon(self, instance: str, icon_name: str) -> None: """Invalidate cached icon atlas в filter — used by DynamicRenderer.""" inst = self._find_instance(instance) diff --git a/controller/cuda_grid_controller/frigate_bridge.py b/controller/cuda_grid_controller/frigate_bridge.py index bb035d7..77ab32b 100644 --- a/controller/cuda_grid_controller/frigate_bridge.py +++ b/controller/cuda_grid_controller/frigate_bridge.py @@ -213,13 +213,19 @@ class FrigateBridge: ] active.sort(key=lambda m: -m.priority) + # Auto-layout logic v2: + # 0 active → quad (overview) + # 1 active → single, main_cam = тот один + # 2+ active → main_plus_preview, main_cam = highest priority active if not active: - # Idle — quad target_layout = "quad" target_main_cam = 0 - else: + elif len(active) == 1: target_layout = "single" target_main_cam = active[0].main_cam_index + else: + target_layout = "main_plus_preview" + target_main_cam = active[0].main_cam_index # highest priority prev = self._auto_state.get(instance, (None, None)) if prev == (target_layout, target_main_cam): @@ -227,8 +233,9 @@ class FrigateBridge: log.info("auto_layout.change", instance=instance, from_state=prev, to_layout=target_layout, to_main=target_main_cam, active=[m.frigate_camera for m in active]) - # Set main_cam first (so single layout shows correct cam at switch moment) + # Set both main_cam streamselects (single и mpp независимые) await self.dispatcher.set_main_cam(instance, target_main_cam) + await self.dispatcher.set_mpp_main(instance, target_main_cam) await self.dispatcher.handle(instance, "layout.set", target_layout) self._auto_state[instance] = (target_layout, target_main_cam)