Files
go-service/cmd/server/web/js/app.ts

273 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// app.js (оркестратор)
import {
fetchHealth,
fetchHistory
} from "./data.js";
import {
state,
addSignals,
resetBufferTracking,
updatePeakHolder,
updateSignalPeakHolder,
setPeakHolderListener
} from "./state.js";
import {
initDOM,
setServerTime,
setStatus,
setMeta,
setBars,
setSignal,
setAudioIndicator,
DOM,
updateAllChannels
} from "./ui.js";
import { initAccordion } from "./accordion.js";
import { drawChart } from "./chart.js";
import { initResize } from "./layout.js";
import { initCamera } from "./camera.js";
import { audioEngine } from "./audio.js";
let lastChart: number = 0;
let connectionLost: boolean = false;
let consecutiveErrors: number = 0;
const MAX_ERRORS: number = 3;
let pollTimer: number | null = null;
let lastSignalLevel: number = 0;
// ================= INIT =================
window.addEventListener("DOMContentLoaded", () => {
initDOM();
initAccordion();
initResize();
initCamera();
// Инициализация звука при первом клике
document.addEventListener('click', () => {
audioEngine.updateLevel(0);
}, { once: true });
setPeakHolderListener(() => {
if (!connectionLost) {
setBars(state.peakValue);
setSignal(state.peakSignalLevel);
const signalLevel = state.peakSignalLevel;
if (signalLevel !== lastSignalLevel) {
lastSignalLevel = signalLevel;
audioEngine.updateLevel(signalLevel);
setAudioIndicator(signalLevel > 0, signalLevel);
}
}
});
});
document.addEventListener('DOMContentLoaded', () => {
const closeBtn = document.getElementById('camCloseBtn');
const toggleBtn = document.getElementById('camToggle');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
if (toggleBtn) {
toggleBtn.click();
}
});
}
});
document.addEventListener("keydown", (e: KeyboardEvent) => {
const leftPanel = document.getElementById("leftPanel");
const rightPanel = document.getElementById("rightPanel");
switch(e.key.toLowerCase()) {
case 'c':
const camBtn = document.getElementById('camToggle');
if (camBtn) camBtn.click();
break;
case 'f':
if (leftPanel) {
leftPanel.style.width = "100%";
if (rightPanel) rightPanel.style.display = "none";
}
break;
case 'escape':
if (rightPanel) rightPanel.style.display = "flex";
if (leftPanel && rightPanel) {
const total = window.innerWidth;
const leftWidth = total * 0.6;
leftPanel.style.flex = `0 0 ${leftWidth}px`;
rightPanel.style.flex = `0 0 ${total - leftWidth}px`;
}
break;
case 'm':
if (lastSignalLevel > 0) {
audioEngine.updateLevel(0);
setAudioIndicator(false);
console.log('[Audio] Звук отключён (M)');
window._mutedLevel = lastSignalLevel;
lastSignalLevel = 0;
} else {
const level = window._mutedLevel || state.peakSignalLevel;
if (level > 0) {
audioEngine.updateLevel(level);
lastSignalLevel = level;
setAudioIndicator(true, level);
console.log('[Audio] Звук включён (M)');
}
}
break;
}
});
// ================= UPDATE =================
async function update(): Promise<void> {
try {
const [health, history] = await Promise.all([
fetchHealth(),
fetchHistory()
]);
consecutiveErrors = 0;
if (health.server_time) {
setServerTime(health.server_time);
}
if (connectionLost) {
console.log("[app] Соединение восстановлено");
}
const isOnline = health.pipe_alive && health.has_data;
if (isOnline) {
setStatus("На связи");
connectionLost = false;
} else {
setStatus("Нет связи", true);
if (!connectionLost) resetBufferTracking();
connectionLost = true;
if (lastSignalLevel > 0) {
lastSignalLevel = 0;
audioEngine.updateLevel(0);
setAudioIndicator(false);
}
}
const arr = Array.isArray(history.bytes) ? history.bytes : [];
const raw = arr[arr.length - 1] ?? 0;
// Обновляем историю сигналов в state
if (arr.length > 0 && isOnline) {
const bufferSize = health.stats?.filled ?? 0;
addSignals(arr, bufferSize);
}
// ===== ПАРСИНГ ДАННЫХ =====
// Биты 0-5: значение (0-63)
// Биты 6-7: уровень сигнала (0-3)
const value = raw & 0x3F; // 0-63
const level = (raw >> 6) & 0x3; // 0-3
const percent = (value / 63) * 100; // 0-100%
// ===== ОБНОВЛЯЕМ КАНАЛЫ =====
// Передаем сырые данные для отображения в каналах
updateAllChannels(arr);
if (isOnline) {
// ===== Обновляем Peak Holder (проценты) ====
const displayValue = updatePeakHolder(percent);
setBars(displayValue);
// Обновляем уровень сигнала с Peak Holder
const displaySignalLevel = updateSignalPeakHolder(level);
setSignal(displaySignalLevel);
// Обновляем звук
const currentSignalLevel = state.peakSignalLevel;
if (currentSignalLevel !== lastSignalLevel) {
lastSignalLevel = currentSignalLevel;
audioEngine.updateLevel(currentSignalLevel);
setAudioIndicator(currentSignalLevel > 0, currentSignalLevel);
}
} else {
setBars(0, true);
setSignal(0, true);
if (lastSignalLevel > 0) {
lastSignalLevel = 0;
audioEngine.updateLevel(0);
setAudioIndicator(false);
}
}
if (DOM.lastByte) {
DOM.lastByte.textContent = isOnline ? value.toString() : "--";
}
const uptime = Math.round(health.uptime_sec || 0);
const filled = health.stats?.filled ?? 0;
const Bps = Math.round(health.stats?.bytes_per_sec ?? 0);
setMeta(`Работа: ${uptime}s | Буфер: ${filled} | Приём: ${Bps} B/s`);
if (DOM.history) {
DOM.history.innerHTML = arr
.map((b, i) => `[${i}] ${(b & 0xFF).toString(2).padStart(8, "0")} = ${b & 0xFF}`)
.join("<br>");
}
const now = performance.now();
if (now - lastChart > 100 && DOM.canvas) {
drawChart(DOM.canvas, state.signalHistory);
lastChart = now;
}
} catch (e) {
consecutiveErrors++;
if (consecutiveErrors === 1) console.warn("[app] Сервер недоступен, ждём...");
setStatus("СЕРВЕР НЕДОСТУПЕН", true);
setServerTime("--:--:--");
setBars(0, true);
setSignal(0, true);
resetBufferTracking();
if (lastSignalLevel > 0) {
lastSignalLevel = 0;
audioEngine.updateLevel(0);
setAudioIndicator(false);
}
if (DOM.lastByte) DOM.lastByte.textContent = "--";
if (consecutiveErrors >= MAX_ERRORS && pollTimer) {
clearInterval(pollTimer);
}
}
}
function startPolling(): void {
if (pollTimer) clearInterval(pollTimer);
if (consecutiveErrors > 0) update();
pollTimer = window.setInterval(update, 500);
}
window.addEventListener("focus", () => {
if (consecutiveErrors >= MAX_ERRORS) {
consecutiveErrors = 0;
startPolling();
}
});
window.addEventListener("beforeunload", () => {
audioEngine.dispose();
});
declare global {
interface Window {
_mutedLevel?: number;
}
}
startPolling();
update();