class StreamDashboard { constructor() { this.streams = []; this.filteredStreams = []; this.searchTimeout = null; this.refreshInterval = null; this.initializeElements(); this.bindEvents(); this.loadStreams(); this.startAutoRefresh(); } initializeElements() { this.elements = { searchInput: document.getElementById("searchInput"), totalStreams: document.getElementById("totalStreams"), onlineStreams: document.getElementById("onlineStreams"), loadingState: document.getElementById("loadingState"), errorState: document.getElementById("errorState"), streamsContainer: document.getElementById("streamsContainer"), noResults: document.getElementById("noResults"), retryBtn: document.getElementById("retryBtn"), }; } bindEvents() { this.elements.searchInput.addEventListener("input", (e) => { clearTimeout(this.searchTimeout); this.searchTimeout = setTimeout(() => { this.filterStreams(e.target.value); }, 300); }); this.elements.retryBtn.addEventListener("click", () => { this.loadStreams(); }); document.addEventListener("visibilitychange", () => { if (document.hidden) { this.stopAutoRefresh(); } else { this.startAutoRefresh(); } }); } startAutoRefresh() { this.refreshInterval = setInterval(() => { this.loadStreams(true); }, 10000); } stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } } async loadStreams(silentRefresh = false) { if (!silentRefresh) { this.showLoading(); } try { const response = await fetch("/api/streams"); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || "Failed to load streams"); } const allStreams = result.data.streams || []; this.streams = allStreams.filter( (stream) => stream.publish?.active === true, ); this.filteredStreams = [...this.streams]; this.updateStats(); this.renderStreams(); if (!silentRefresh) { this.showStreams(); } } catch { if (!silentRefresh) { this.showError(); } } } filterStreams(searchTerm) { const term = searchTerm.toLowerCase().trim(); if (!term) { this.filteredStreams = [...this.streams]; } else { this.filteredStreams = this.streams.filter( (stream) => stream.name?.toLowerCase().includes(term) || stream.url?.toLowerCase().includes(term) || stream.app?.toLowerCase().includes(term), ); } this.renderStreams(); if (this.filteredStreams.length === 0 && term) { this.showNoResults(); } else { this.showStreams(); } } updateStats() { const total = this.streams.length; this.elements.totalStreams.textContent = total; this.elements.onlineStreams.textContent = total; } renderStreams() { this.elements.streamsContainer.innerHTML = ""; for (const stream of this.filteredStreams) { const streamCard = this.createStreamCard(stream); this.elements.streamsContainer.appendChild(streamCard); } } createStreamCard(stream) { const card = document.createElement("div"); card.className = "stream-card"; const formatBytes = (bytes) => { if (!bytes) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`; }; const watchName = stream.name?.replace(/\/live\//, "/"); const watchUrl = watchName ? `${window.location.origin}/watch/${encodeURIComponent(watchName)}` : "Unknown Stream"; card.innerHTML = `

${this.escapeHtml(stream.name || "Unknown Stream")}

Clients: ${stream.clients || 0}
Bitrate: ${formatBytes((stream.kbps?.recv_30s || 0) * 1024)}/s
Video: ${stream.video?.codec || "N/A"} ${stream.video?.profile || ""}
Audio: ${stream.audio?.codec || "N/A"}
${this.escapeHtml(watchUrl)}
`; return card; } escapeHtml(text) { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } showLoading() { this.elements.loadingState.style.display = "block"; this.elements.errorState.style.display = "none"; this.elements.streamsContainer.style.display = "none"; this.elements.noResults.style.display = "none"; } showError() { this.elements.loadingState.style.display = "none"; this.elements.errorState.style.display = "block"; this.elements.streamsContainer.style.display = "none"; this.elements.noResults.style.display = "none"; } showStreams() { this.elements.loadingState.style.display = "none"; this.elements.errorState.style.display = "none"; this.elements.streamsContainer.style.display = "grid"; this.elements.noResults.style.display = "none"; } showNoResults() { this.elements.loadingState.style.display = "none"; this.elements.errorState.style.display = "none"; this.elements.streamsContainer.style.display = "none"; this.elements.noResults.style.display = "block"; } } new StreamDashboard();