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
65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
import socket
|
|
import struct
|
|
import asyncio
|
|
from config import settings
|
|
|
|
|
|
def check_multicast_reachable(mcast_addr: str = "239.0.0.1", port: int = 9999, timeout: float = 2.0) -> bool:
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
sock.settimeout(timeout)
|
|
ttl = struct.pack("b", 1)
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
|
|
|
|
mreq = struct.pack("4sL", socket.inet_aton(mcast_addr), socket.INADDR_ANY)
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
|
|
|
sock.sendto(b"ping", (mcast_addr, port))
|
|
try:
|
|
data, addr = sock.recvfrom(1024)
|
|
sock.close()
|
|
return True
|
|
except socket.timeout:
|
|
sock.close()
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
class MulticastDetector:
|
|
def __init__(self):
|
|
self._multicast_available: bool | None = None
|
|
|
|
def is_available(self) -> bool:
|
|
if self._multicast_available is not None:
|
|
return self._multicast_available
|
|
|
|
if not settings.multicast_enabled:
|
|
self._multicast_available = False
|
|
return False
|
|
|
|
self._multicast_available = check_multicast_reachable()
|
|
return self._multicast_available
|
|
|
|
def get_stream_url(self, stream_key: str, node: dict, quality: dict | None = None):
|
|
use_multicast = self.is_available()
|
|
host = node["host"]
|
|
|
|
if use_multicast:
|
|
hash_val = abs(hash(stream_key)) % 255
|
|
mcast_addr = f"{settings.multicast_address_base.rsplit('.', 1)[0]}.{hash_val + 1}"
|
|
mcast_port = settings.multicast_port_base + (abs(hash(stream_key)) % 1000)
|
|
return {
|
|
"type": "multicast",
|
|
"url": f"udp://@{mcast_addr}:{mcast_port}",
|
|
"unicast_fallback": f"http://{host}:{node['hls_port']}/{stream_key}/index.m3u8",
|
|
}
|
|
|
|
return {
|
|
"type": "hls",
|
|
"url": f"http://{host}:{node['hls_port']}/{stream_key}/index.m3u8",
|
|
}
|
|
|
|
|
|
multicast_detector = MulticastDetector()
|