Files
2026-07-22 01:12:45 +02:00

186 lines
4.3 KiB
JavaScript

const HEALTH_CONFIG = {
warningThreshold: 60,
dangerThreshold: 30,
staleWarningMs: 2500,
staleDangerMs: 5000,
bitrateLow: 1000,
bitrateGood: 1001,
autoSceneSwitch: false,
sceneLow: "Low",
sceneOffline: "Offline",
enableAlerts: false
};
// ================= STATE =================
let lastHealthState = {
live1: "UNKNOWN",
live2: "UNKNOWN"
};
// ================= MAIN ENTRY =================
function evaluateStreams(status) {
if (!status?.streams) return;
const results = {};
for (const key of Object.keys(status.streams)) {
const stream = status.streams[key];
const health = calculateHealth(stream);
results[key] = health;
handleStateChange(key, health, status);
updateHealthUI(key, health);
}
return results;
}
// ================= HEALTH CALC =================
function calculateHealth(stream) {
const now = Date.now();
const bitrate = stream.bitrate || 0;
const staleness = now - (stream.time || now);
let score = 100;
// 🔴 STALENESS (most important)
if (staleness > HEALTH_CONFIG.staleDangerMs) score -= 60;
else if (staleness > HEALTH_CONFIG.staleWarningMs) score -= 30;
// 📉 BITRATE
if (bitrate === 0) score -= 50;
else if (bitrate < HEALTH_CONFIG.bitrateLow) score -= 30;
else if (bitrate < HEALTH_CONFIG.bitrateGood) score -= 10;
// clamp
score = Math.max(0, Math.min(100, score));
return {
score,
bitrate,
staleness,
status: getStatus(score, bitrate, staleness)
};
}
// ================= STATUS =================
function getStatus(score, bitrate, staleness) {
if (bitrate === 0 && staleness > HEALTH_CONFIG.staleDangerMs) {
return "DOWN";
}
if (score < HEALTH_CONFIG.dangerThreshold) {
return "DANGER";
}
if (score < HEALTH_CONFIG.warningThreshold) {
return "WEAK";
}
return "GOOD";
}
// ================= STATE HANDLER =================
function handleStateChange(streamName, health, status) {
const prev = lastHealthState[streamName];
const current = health.status;
if (prev === current) return;
lastHealthState[streamName] = current;
console.log(`[HEALTH] ${streamName}: ${prev}${current}`);
// 🚨 ALERTS
if (HEALTH_CONFIG.enableAlerts) {
sendAlert(streamName, health, status);
}
// 🎛 AUTO SCENE SWITCH
if (HEALTH_CONFIG.autoSceneSwitch) {
handleAutoScene(streamName, health, status);
}
}
// ================= AUTO SCENE =================
function handleAutoScene(streamName, health, status) {
if (streamName !== status.activeStream) return;
if (health.status === "DOWN") {
setSceneSafe(HEALTH_CONFIG.sceneOffline);
}
if (health.status === "DANGER") {
setSceneSafe(HEALTH_CONFIG.sceneLow);
}
}
// ================= SAFE SCENE SWITCH =================
function setSceneSafe(scene) {
fetch("https://api.anjuma-crew.de/scene", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ scene })
}).catch(console.error);
}
// ================= ALERT SYSTEM =================
function sendAlert(streamName, health, status) {
const msg = {
stream: streamName,
status: health.status,
score: health.score,
bitrate: health.bitrate,
staleness: health.staleness
};
console.warn("[ALERT]", msg);
// 👉 optional webhook
/*
fetch("YOUR_WEBHOOK_URL", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(msg)
});
*/
}
// ================= UI =================
function updateHealthUI(streamName, health) {
const el = document.getElementById(`health-${streamName}`);
if (el) {
el.innerText = health.score;
el.style.color =
health.status === "GOOD" ? "green" :
health.status === "WEAK" ? "orange" :
"red";
}
const label = document.getElementById(`status-${streamName}`);
if (label) {
label.innerText = health.status;
}
const staleEl = document.getElementById(`stale-${streamName}`);
if (staleEl) {
staleEl.innerText = Math.round(health.staleness / 1000) + "s";
}
}