186 lines
4.3 KiB
JavaScript
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";
|
|
}
|
|
} |