Backend: FastAPI + SQLAlchemy + SQLite Frontend: React + Vite + Ant Design (serif Anthropic theme) Media server: MediaMTX with edge node support Load balancing: round-robin / weighted / least-conn Multicast detection with automatic fallback Transcoding support via FFmpeg (configurable quality) hls.js player with line & quality switching Admin panel with password auth Windows compatible scripts
51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
import json
|
|
import asyncio
|
|
import subprocess
|
|
from config import settings
|
|
|
|
|
|
class LoadBalancer:
|
|
def __init__(self):
|
|
self._rr_index = 0
|
|
self._conn_counts: dict[str, int] = {n["id"]: 0 for n in settings.edge_nodes}
|
|
|
|
def get_enabled_nodes(self):
|
|
return [n for n in settings.edge_nodes if n.get("enabled", True)]
|
|
|
|
def select_nodes(self, strategy: str | None = None):
|
|
nodes = self.get_enabled_nodes()
|
|
strategy = strategy or settings.load_balance_strategy
|
|
|
|
if strategy == "round-robin":
|
|
if not nodes:
|
|
return []
|
|
chosen = nodes[self._rr_index % len(nodes)]
|
|
self._rr_index += 1
|
|
return [chosen]
|
|
elif strategy == "weighted":
|
|
total = sum(n["weight"] for n in nodes)
|
|
if total == 0:
|
|
return nodes
|
|
import random
|
|
r = random.uniform(0, total)
|
|
cumulative = 0
|
|
for n in nodes:
|
|
cumulative += n["weight"]
|
|
if r <= cumulative:
|
|
return [n]
|
|
return [nodes[-1]]
|
|
elif strategy == "least-conn":
|
|
sorted_nodes = sorted(nodes, key=lambda n: self._conn_counts.get(n["id"], 0))
|
|
return [sorted_nodes[0]]
|
|
return nodes
|
|
|
|
def increment_connection(self, node_id: str):
|
|
self._conn_counts[node_id] = self._conn_counts.get(node_id, 0) + 1
|
|
|
|
def decrement_connection(self, node_id: str):
|
|
if self._conn_counts.get(node_id, 0) > 0:
|
|
self._conn_counts[node_id] -= 1
|
|
|
|
|
|
lb = LoadBalancer()
|