тест 1 времени

This commit is contained in:
2026-08-24 16:14:29 +03:00
parent 79c59eeeed
commit c82ec2cbfb
4 changed files with 203 additions and 34 deletions

View File

@@ -1403,6 +1403,7 @@ class VmixAgentHub:
sequence_id: str = "",
sequence_name: str = "",
button_id: str = "",
delivery_mode: str = "",
timeout: float = 4.0,
) -> dict[str, Any]:
"""Execute commands on exactly one Agent bound to this browser/match session."""
@@ -1522,49 +1523,92 @@ class VmixAgentHub:
# for this transport. Mapping and generic runtime sync keep their existing ACK
# semantics so diagnostics are still useful where exact field delivery matters.
interactive_shortcut = bool(str(sequence_id or "").strip() or str(button_id or "").strip())
use_batch = (not interactive_shortcut) and self._agent_supports_batch(agent_version) and len(prepared_commands) > 1
transport = "shortcut-no-ack" if interactive_shortcut else ("batch" if use_batch else "legacy")
delivery_mode = str(delivery_mode or "").strip().lower()
fast_timer = delivery_mode == "timer-fast"
no_ack_delivery = interactive_shortcut or fast_timer
supports_batch = self._agent_supports_batch(agent_version) and len(prepared_commands) > 1
use_batch = (not no_ack_delivery) and supports_batch
if fast_timer:
transport = "timer-batch-no-ack" if supports_batch else "timer-no-ack"
else:
transport = "shortcut-no-ack" if interactive_shortcut else ("batch" if use_batch else "legacy")
if interactive_shortcut:
# Preserve order between operator shortcuts, but do not wait behind Mapping's
# command ACK lock. A shortcut returns as soon as its frames are on the Agent socket.
if no_ack_delivery:
# BUILD103: realtime operator traffic (shortcuts + timers) bypasses Mapping's
# ACK queue. Timer pairs use ONE vmix.batch frame on Agent 1.4+ and return
# immediately after WebSocket delivery; the later Agent ACK is intentionally ignored.
# The shared realtime lock preserves exact frame order if Space and an F-key are
# pressed almost simultaneously without reintroducing the slow Mapping lock.
async with self._device_vmix_shortcut_send_lock(target_device_id):
for index, command in enumerate(prepared_commands):
function = str(command.get("Function") or "")
if fast_timer and supports_batch:
request_id = secrets.token_urlsafe(12)
delivered = await self.send(
target_device_id,
{
"type": "vmix.command",
"type": "vmix.batch",
"protocol": AGENT_PROTOCOL_VERSION,
"request_id": request_id,
"device_id": target_device_id,
"assignment_id": assignment_id,
"match_id": match_id,
"command": command,
"commands": prepared_commands,
},
)
item = {
"index": index,
"function": function,
"ok": bool(delivered),
"reason": "" if delivered else "Agent сейчас offline",
"delivery": "agent-websocket",
"ack_waited": False,
}
results.append(item)
if delivered:
# With no ACK wait the local ON AIR state is intentionally optimistic:
# success means delivery to the live Agent WebSocket, not vMix confirmation.
self._track_runtime_overlay_command(
for index, command in enumerate(prepared_commands):
function = str(command.get("Function") or "")
results.append({
"index": index,
"function": function,
"ok": bool(delivered),
"reason": "" if delivered else "Agent сейчас offline",
"delivery": "agent-websocket-batch",
"ack_waited": False,
})
if delivered:
self._track_runtime_overlay_command(
target_device_id,
command,
sequence_id=sequence_id,
sequence_name=sequence_name,
button_id=button_id,
)
else:
for index, command in enumerate(prepared_commands):
function = str(command.get("Function") or "")
request_id = secrets.token_urlsafe(12)
delivered = await self.send(
target_device_id,
command,
sequence_id=sequence_id,
sequence_name=sequence_name,
button_id=button_id,
{
"type": "vmix.command",
"protocol": AGENT_PROTOCOL_VERSION,
"request_id": request_id,
"device_id": target_device_id,
"assignment_id": assignment_id,
"match_id": match_id,
"command": command,
},
)
else:
break
item = {
"index": index,
"function": function,
"ok": bool(delivered),
"reason": "" if delivered else "Agent сейчас offline",
"delivery": "agent-websocket",
"ack_waited": False,
}
results.append(item)
if delivered:
# With no ACK wait the local ON AIR state is intentionally optimistic:
# success means delivery to the live Agent WebSocket, not vMix confirmation.
self._track_runtime_overlay_command(
target_device_id,
command,
sequence_id=sequence_id,
sequence_name=sequence_name,
button_id=button_id,
)
else:
break
else:
async with self._device_vmix_send_lock(target_device_id):
if use_batch:
@@ -1637,7 +1681,8 @@ class VmixAgentHub:
"failed": len(failed),
"results": results,
"transport": transport,
"confirmation": "not_waited" if interactive_shortcut else "ack",
# BUILD102 compatibility marker: "confirmation": "not_waited" if interactive_shortcut else "ack"
"confirmation": "not_waited" if no_ack_delivery else "ack",
"overlay_state": self._runtime_overlay_payload(target_device_id),
}
@@ -3914,6 +3959,7 @@ class RuntimeVmixSequencePayload(BaseModel):
sequence_id: str = Field(default="", max_length=160)
sequence_name: str = Field(default="", max_length=300)
button_id: str = Field(default="", max_length=160)
delivery_mode: str = Field(default="", max_length=32)
class SelectSessionDevicePayload(BaseModel):
@@ -4259,6 +4305,7 @@ def create_hockey_agent_router(
sequence_id=payload.sequence_id,
sequence_name=payload.sequence_name,
button_id=payload.button_id,
delivery_mode=payload.delivery_mode,
)
@router.get("/api/hockey/vmix/overlay-state")