controller: auto-layout v2 — mpp при 2+ active с dynamic main
Logic update в FrigateBridge._update_auto_layout: 0 active → quad 1 active → single, main_cam = active 2+ active → main_plus_preview, mpp_main = highest priority active Dispatcher.set_mpp_main — ZMQ streamselect@mpp_main map <index> Config.mpp_main_filter_target = "streamselect@mpp_main" При каждом auto-layout change controller отправляет 3 ZMQ: streamselect@main_cam map N (single layout main) streamselect@mpp_main map N (mpp layout main, может быть тот же N) streamselect@layout map L (final layout selector) Preview cells в mpp остаются fixed mapping (cell1=cam1/front_yard, cell2=cam2/gate_lpr, cell3=cam3/back_yard). Если main_cam = cam1/2/3 — preview slot этой cam visible duplicate. Acceptable v2 trade-off (user warned). Live verified: 4+ active cameras → mpp, gate_lpr в main slot (priority=10 highest). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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)",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user