поправлены удаления и надеюсь заробатаю статусы для кнопок которые вызывают титры из веб-интерфейса
This commit is contained in:
@@ -242,6 +242,8 @@ class VmixAgentHub:
|
||||
self._auto_refresh_task: asyncio.Task[None] | None = None
|
||||
self._auto_refresh_next: dict[tuple[str, str], float] = {}
|
||||
self._mapping_value_cache: dict[tuple[str, str, int, str, str, str, str], str] = {}
|
||||
# BUILD83: server-side mirror of ACK-confirmed runtime Overlay state.
|
||||
self._runtime_overlay_state: dict[str, dict[str, dict[str, str]]] = {}
|
||||
self._auto_refresh_last_error = ""
|
||||
|
||||
@staticmethod
|
||||
@@ -251,6 +253,86 @@ class VmixAgentHub:
|
||||
raise ValueError("Некорректный device_id")
|
||||
return device_id
|
||||
|
||||
@staticmethod
|
||||
def _runtime_overlay_command(function_name: Any) -> tuple[str, str] | None:
|
||||
text = re.sub(r"\s+", "", str(function_name or ""))
|
||||
if text.lower() == "overlayinputalloff":
|
||||
return ("all", "off")
|
||||
match = re.search(r"OverlayInput([1-4])(In|Out|Off)?", text, flags=re.IGNORECASE)
|
||||
if not match:
|
||||
return None
|
||||
raw = str(match.group(2) or "toggle").lower()
|
||||
action = "in" if raw == "in" else "out" if raw == "out" else "off" if raw == "off" else "toggle"
|
||||
return str(match.group(1)), action
|
||||
|
||||
def _track_runtime_overlay_command(
|
||||
self,
|
||||
device_id: str,
|
||||
command: dict[str, Any],
|
||||
*,
|
||||
sequence_id: str = "",
|
||||
sequence_name: str = "",
|
||||
button_id: str = "",
|
||||
) -> None:
|
||||
parsed = self._runtime_overlay_command(command.get("Function") or command.get("function"))
|
||||
if parsed is None:
|
||||
return
|
||||
layer, action = parsed
|
||||
device_state = self._runtime_overlay_state.setdefault(device_id, {})
|
||||
if layer == "all":
|
||||
device_state.clear()
|
||||
return
|
||||
input_ref = str(command.get("Input") or command.get("input") or "").strip()
|
||||
if action == "in":
|
||||
device_state[layer] = {
|
||||
"input": input_ref,
|
||||
"sequence_id": str(sequence_id or ""),
|
||||
"sequence_name": str(sequence_name or ""),
|
||||
"button_id": str(button_id or ""),
|
||||
"updated_at": _utcnow().isoformat(),
|
||||
}
|
||||
return
|
||||
if action in {"out", "off"}:
|
||||
device_state.pop(layer, None)
|
||||
return
|
||||
current = device_state.get(layer)
|
||||
if current and (not input_ref or str(current.get("input") or "") == input_ref):
|
||||
device_state.pop(layer, None)
|
||||
else:
|
||||
device_state[layer] = {
|
||||
"input": input_ref,
|
||||
"sequence_id": str(sequence_id or ""),
|
||||
"sequence_name": str(sequence_name or ""),
|
||||
"button_id": str(button_id or ""),
|
||||
"updated_at": _utcnow().isoformat(),
|
||||
}
|
||||
|
||||
def _runtime_overlay_payload(self, device_id: str) -> dict[str, Any]:
|
||||
source = self._runtime_overlay_state.get(device_id, {})
|
||||
overlays = {str(layer): dict(value) for layer, value in source.items() if str(layer) in {"1", "2", "3", "4"}}
|
||||
return {"device_id": device_id, "overlays": overlays}
|
||||
|
||||
def runtime_overlay_state_for_user(self, user: HockeyUser, *, device_id: str = "") -> dict[str, Any]:
|
||||
requested = self.normalise_device_id(device_id) if str(device_id or "").strip() else ""
|
||||
with self.database.session() as session:
|
||||
if requested:
|
||||
device = session.scalar(select(VmixDevice).where(and_(VmixDevice.device_uuid == requested, VmixDevice.wfl_user_id == user.id)))
|
||||
if device is None:
|
||||
raise HTTPException(status_code=404, detail="Agent не принадлежит текущему аккаунту")
|
||||
else:
|
||||
rows = list(session.scalars(
|
||||
select(VmixDevice)
|
||||
.where(and_(VmixDevice.wfl_user_id == user.id, VmixDevice.is_active_for_account.is_(True)))
|
||||
.order_by(desc(VmixDevice.last_seen_at))
|
||||
))
|
||||
if not rows:
|
||||
return {"device_id": "", "overlays": {}}
|
||||
device = rows[0]
|
||||
result = self._runtime_overlay_payload(device.device_uuid)
|
||||
result["online"] = device.device_uuid in self._live
|
||||
result["vmix_connected"] = bool(device.vmix_connected)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _mapping_source_code(data_key: Any) -> str:
|
||||
key = str(data_key or "").strip()
|
||||
@@ -558,6 +640,8 @@ class VmixAgentHub:
|
||||
row.last_seen_at = now
|
||||
if "connected" in vmix:
|
||||
row.vmix_connected = bool(vmix.get("connected"))
|
||||
if not row.vmix_connected:
|
||||
self._runtime_overlay_state.pop(device_id, None)
|
||||
if vmix.get("version") is not None:
|
||||
row.vmix_version = str(vmix.get("version") or "")[:64]
|
||||
if vmix.get("url") is not None:
|
||||
@@ -1107,6 +1191,9 @@ class VmixAgentHub:
|
||||
*,
|
||||
device_id: str = "",
|
||||
session_token: str = "",
|
||||
sequence_id: str = "",
|
||||
sequence_name: str = "",
|
||||
button_id: str = "",
|
||||
timeout: float = 4.0,
|
||||
) -> dict[str, Any]:
|
||||
"""Execute commands on exactly one Agent bound to this browser/match session."""
|
||||
@@ -1239,6 +1326,13 @@ class VmixAgentHub:
|
||||
"results": results,
|
||||
},
|
||||
)
|
||||
self._track_runtime_overlay_command(
|
||||
target_device_id,
|
||||
command,
|
||||
sequence_id=sequence_id,
|
||||
sequence_name=sequence_name,
|
||||
button_id=button_id,
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"device_id": target_device_id,
|
||||
@@ -1247,6 +1341,7 @@ class VmixAgentHub:
|
||||
"session_token": session_token,
|
||||
"applied": len(results),
|
||||
"results": results,
|
||||
"overlay_state": self._runtime_overlay_payload(target_device_id),
|
||||
}
|
||||
|
||||
async def admin_test_mapping_value(
|
||||
@@ -2959,6 +3054,9 @@ class RuntimeVmixSequencePayload(BaseModel):
|
||||
commands: list[RuntimeVmixCommandPayload] = Field(default_factory=list, min_length=1, max_length=80)
|
||||
device_id: str = Field(default="", max_length=128)
|
||||
session_token: str = Field(default="", max_length=128)
|
||||
sequence_id: str = Field(default="", max_length=160)
|
||||
sequence_name: str = Field(default="", max_length=300)
|
||||
button_id: str = Field(default="", max_length=160)
|
||||
|
||||
|
||||
class SelectSessionDevicePayload(BaseModel):
|
||||
@@ -3215,8 +3313,18 @@ def create_hockey_agent_router(
|
||||
commands,
|
||||
device_id=payload.device_id,
|
||||
session_token=payload.session_token,
|
||||
sequence_id=payload.sequence_id,
|
||||
sequence_name=payload.sequence_name,
|
||||
button_id=payload.button_id,
|
||||
)
|
||||
|
||||
@router.get("/api/hockey/vmix/overlay-state")
|
||||
async def runtime_vmix_overlay_state(
|
||||
device_id: str = Query(default="", max_length=128),
|
||||
user: HockeyUser = Depends(auth_dependency),
|
||||
) -> dict[str, Any]:
|
||||
return hub.runtime_overlay_state_for_user(user, device_id=device_id)
|
||||
|
||||
@router.post("/api/hockey/admin/vmix-mapping/apply-all-active", dependencies=admin)
|
||||
async def admin_apply_all_active_mappings() -> dict[str, Any]:
|
||||
return await hub.apply_mapping_to_all_active_devices(reason="ui_language_changed")
|
||||
|
||||
Reference in New Issue
Block a user