test 6
This commit is contained in:
@@ -4196,6 +4196,23 @@ function startCustomTooltips() {
|
||||
];
|
||||
}
|
||||
|
||||
function vmixGameCountdownControlCommands(input, selectedName, millisecondsProvider, action = "start") {
|
||||
// BUILD108: the native vMix game clock is seeded only on an explicit time change.
|
||||
// Space must never re-seed the countdown: Start/Resume only starts it and Pause/Stop
|
||||
// only pauses it. This keeps the already-running native vMix countdown continuous
|
||||
// and removes jumps caused by writing the browser value during a pause transition.
|
||||
const target = { Input: String(input || "").trim(), SelectedName: String(selectedName || "").trim() };
|
||||
if (!target.Input || !target.SelectedName) return [];
|
||||
if (action === "pause" || action === "stop") {
|
||||
return [{ Function: "PauseCountdown", ...target }];
|
||||
}
|
||||
if (action === "set") {
|
||||
const milliseconds = typeof millisecondsProvider === "function" ? millisecondsProvider() : millisecondsProvider;
|
||||
return [{ Function: "ChangeCountdown", ...target, Value: vmixCountdownValue(milliseconds) }];
|
||||
}
|
||||
return [{ Function: "StartCountdown", ...target }];
|
||||
}
|
||||
|
||||
function buildShortcutRuntimeContext(sequence = null) {
|
||||
const timers = {};
|
||||
state.config.components.filter((component) => isTimerComponent(component)).forEach((component) => {
|
||||
@@ -4754,25 +4771,50 @@ function startCustomTooltips() {
|
||||
|
||||
async function syncActiveVmixGameCountdown(component, timerState, eventName) {
|
||||
if (!component?.action_id || eventName === "timer_tick") return false;
|
||||
const explicitSeedEvent = ["timer_reset", "timer_set_time", "timer_add_time", "timer_subtract_time", "timer_restart"].includes(eventName);
|
||||
const candidateSteps = [];
|
||||
if (explicitSeedEvent) {
|
||||
// BUILD108: an explicit time edit must seed the configured vMix game clock even
|
||||
// before Space has ever been pressed. This is the moment where 20:00 / 05:00
|
||||
// (or any manual value) is transferred to vMix.
|
||||
(state.config.shortcut_sequences || []).forEach((sequence) => {
|
||||
if (sequence?.enabled === false) return;
|
||||
(sequence.steps || []).forEach((step) => {
|
||||
if (step?.enabled === false || step?.type !== "hockey_vmix_timers_start") return;
|
||||
candidateSteps.push(step);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
Array.from(state.activeHockeyVmixTimerSteps).forEach((stepId) => {
|
||||
const step = hockeyTimerSyncStepById(stepId);
|
||||
if (step) candidateSteps.push(step);
|
||||
});
|
||||
}
|
||||
|
||||
const commands = [];
|
||||
for (const stepId of Array.from(state.activeHockeyVmixTimerSteps)) {
|
||||
const step = hockeyTimerSyncStepById(stepId);
|
||||
const seenTargets = new Set();
|
||||
for (const step of candidateSteps) {
|
||||
if (!step || step.enabled === false || !step.sync_vmix_game || step.game_vmix_mode !== "countdown") continue;
|
||||
if (String(step.game_timer_action_id || "hockey_game_timer") !== String(component.action_id)) continue;
|
||||
const input = String(step.game_vmix_input || "").trim();
|
||||
const selectedName = String(step.game_vmix_selected_name || "").trim();
|
||||
if (!input || !selectedName) continue;
|
||||
const targetKey = `${input}\u0000${selectedName}`;
|
||||
if (seenTargets.has(targetKey)) continue;
|
||||
seenTargets.add(targetKey);
|
||||
const valueProvider = () => timerState.currentMs;
|
||||
if (["timer_start", "timer_restart", "timer_resume"].includes(eventName)) {
|
||||
commands.push(...vmixCountdownSyncCommands(input, selectedName, valueProvider, "start"));
|
||||
|
||||
if (["timer_start", "timer_resume"].includes(eventName)) {
|
||||
commands.push(...vmixGameCountdownControlCommands(input, selectedName, valueProvider, "start"));
|
||||
} else if (eventName === "timer_restart") {
|
||||
commands.push(...vmixGameCountdownControlCommands(input, selectedName, valueProvider, "set"));
|
||||
commands.push(...vmixGameCountdownControlCommands(input, selectedName, valueProvider, "start"));
|
||||
} else if (eventName === "timer_pause") {
|
||||
commands.push(...vmixCountdownSyncCommands(input, selectedName, valueProvider, "pause"));
|
||||
commands.push(...vmixGameCountdownControlCommands(input, selectedName, valueProvider, "pause"));
|
||||
} else if (["timer_stop", "timer_finished"].includes(eventName)) {
|
||||
commands.push(...vmixCountdownSyncCommands(input, selectedName, valueProvider, "stop"));
|
||||
commands.push(...vmixGameCountdownControlCommands(input, selectedName, valueProvider, "stop"));
|
||||
} else if (["timer_reset", "timer_set_time", "timer_add_time", "timer_subtract_time"].includes(eventName)) {
|
||||
commands.push(...vmixCountdownSyncCommands(
|
||||
input, selectedName, valueProvider, timerState.running ? "start" : "set"
|
||||
));
|
||||
commands.push(...vmixGameCountdownControlCommands(input, selectedName, valueProvider, "set"));
|
||||
}
|
||||
}
|
||||
if (!commands.length) return false;
|
||||
@@ -4805,12 +4847,13 @@ function startCustomTooltips() {
|
||||
async function syncConfiguredScoreboardCountdownsToRuntime() {
|
||||
const commands = [];
|
||||
configuredHockeyVmixGameCountdownTargets().forEach(({ input, selectedName, timerState }) => {
|
||||
commands.push(...vmixCountdownSyncCommands(
|
||||
input,
|
||||
selectedName,
|
||||
() => timerState.currentMs,
|
||||
timerState.running ? "start" : "set"
|
||||
));
|
||||
// F1 only seeds a stopped/paused game clock. If it is already running,
|
||||
// do not touch its native vMix time at all.
|
||||
if (!timerState.running) {
|
||||
commands.push(...vmixGameCountdownControlCommands(
|
||||
input, selectedName, () => timerState.currentMs, "set"
|
||||
));
|
||||
}
|
||||
});
|
||||
if (!commands.length) return { ok: true, applied: 0, requested: 0 };
|
||||
// Await only WebSocket dispatch (never vMix ACK) so the countdown reseed reaches the
|
||||
@@ -5335,11 +5378,14 @@ function startCustomTooltips() {
|
||||
setVmixTimerMirror(step.game_timer_action_id || "hockey_game_timer", step.game_vmix_input, step.game_vmix_selected_name);
|
||||
commands.push({ Function: "SetText", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: formatTimerValue(gameTimer, gameTimerState) });
|
||||
} else if (pausing) {
|
||||
commands.push(...vmixCountdownSyncCommands(
|
||||
// BUILD108 Space pause: native pause only, absolutely no time writeback.
|
||||
commands.push(...vmixGameCountdownControlCommands(
|
||||
step.game_vmix_input, step.game_vmix_selected_name, () => gameTimerState.currentMs, "pause"
|
||||
));
|
||||
} else {
|
||||
commands.push(...vmixCountdownSyncCommands(
|
||||
// BUILD108 Space start/resume: native start only. The countdown was seeded
|
||||
// when the operator set/reset the period time (or by F1 before first start).
|
||||
commands.push(...vmixGameCountdownControlCommands(
|
||||
step.game_vmix_input, step.game_vmix_selected_name, () => gameTimerState.currentMs, "start"
|
||||
));
|
||||
}
|
||||
@@ -13921,7 +13967,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
||||
<div class="shortcut-finish-action-list">${timerFinishActionRows(step)}</div>
|
||||
|
||||
<div class="shortcut-vmix-inventory-note"><span>${escapeHtml(shortcutInventoryLabel())}</span><small>Для каждого countdown теперь обязательно выбирается конкретный Text / SelectedName. Это исключает отправку времени в первый текстовый элемент по умолчанию.</small></div>
|
||||
<p class="shortcut-step-note">Режим <b>Countdown vMix</b>: веб-таймер является источником времени. Перед Start/Resume vMix выполняет <code>ChangeCountdown(время Runtime) → StartCountdown</code>; при Pause/Stop выполняет <code>PauseCountdown → ChangeCountdown(время Runtime)</code>. <code>StopCountdown</code> в обычном управлении не используется, потому что он сбрасывает countdown. Команды идут в Agent без ACK отдельными кадрами строго по порядку. Каждую секунду значение в vMix не передаётся. <b>Text mirror</b> оставлен только для совместимости со старыми титрами. Для верхнего счёта используется одна penalty-плашка: при реальном большинстве она показывает ближайшее изменение численного состава; при чистом равном обоюдном удалении сама по себе не появляется.</p>
|
||||
<p class="shortcut-step-note">Режим <b>Countdown vMix</b>: время передаётся в vMix только при явной установке/сбросе (например, 20:00 или 05:00). После этого Space не синхронизирует значение: Start/Resume отправляет только <code>StartCountdown</code>, Pause/Stop — только <code>PauseCountdown</code>. Это исключает скачки времени при паузе. <code>StopCountdown</code> в обычном управлении не используется. Команды идут в Agent без ACK. <b>Text mirror</b> оставлен только для совместимости со старыми титрами. Для верхнего счёта используется одна penalty-плашка: при реальном большинстве она показывает ближайшее изменение численного состава; при чистом равном обоюдном удалении сама по себе не появляется.</p>
|
||||
</div>`;
|
||||
} else if (step.type === "delay") {
|
||||
body.innerHTML = `<div class="shortcut-step-grid"><label>Задержка, мс<input type="number" min="0" max="10000" step="10" data-step-field="milliseconds" value="${Number(step.milliseconds) || 0}"></label></div>`;
|
||||
|
||||
Reference in New Issue
Block a user