feat: consolidate on the shadcn frontend and harden playback
Frontend
- Replace the Ant Design app with the shadcn/ui app as the single frontend,
keeping the portfolio side-by-side revertible in this commit.
- Drop the abandoned mdui experiment and the AntD tree so there is one app,
one router and one design system to maintain.
Live playback
- Stop racing the encoder. lowLatencyMode + liveSyncDurationCount:1 targeted a
single 4s segment, so playback ran at up to 1.10x and the forward buffer
bottomed out at 0.1s, re-buffering every segment. Measured over 40s:
buffer min 0.1s -> 8.9s, rate 1.00 constant, 0 stalls.
- Remove the 落后 labels and the page-level edge-snapping timer that fought
hls.js over two different definitions of "latency".
- Live can no longer be paused: the control is not rendered while it plays, so
the icon can never misreport state.
- Fix autoplay: muted autoplay started silently and never unmuted, and the
gesture that restored sound also toggled playback off. One owner now.
Source ("原画") rendition
- Publish the publisher's stream remuxed losslessly via -c:v copy alongside the
transcoded ones, so a source option exists even when every ladder entry
downsizes.
- Give renditions an ASCII path slug: the HLS path is part of the URL and
MediaMTX rejects non-alphanumeric paths, so 原画 could never be served.
Backend
- Supervise MediaMTX from the backend: start it at boot and restart it on exit,
never killing an origin it did not start.
- Clear stale HLS output before restarting an encoder; an ENDLIST-less playlist
left by a dead encoder hangs hls.js forever.
- Mark JSON timestamps as UTC. utcnow().isoformat() emitted a naive string that
JS read as local time, so every viewer heartbeat looked 8 hours old in UTC+8
and the live board's active-viewer count was permanently zero.
- Rebind a viewer session when it arrives on another stream instead of failing,
and survive the StrictMode double-heartbeat race on first insert.
- Replace the Linux-only /proc and os.getloadavg metrics with psutil.
- Complete MediaMTX's cookieCheck redirect inside /hls proxying, and hold the
httpx client open for the streamed body.
Housekeeping
- Ignore RainOSS (it stores accessKeySecret), runtime output, TLS keys and
.venv; remove generated screenshots, load test scripts and stray files.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
1 parent
186e32caf1
commit
996e3fea39
70 files changed
+4844
-3070
No files matched your search
@@ -2,6 +2,7 @@ import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import psutil
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
@@ -88,33 +89,27 @@ async def update_transcode_profiles(
|
||||
|
||||
@router.get("/system-metrics")
|
||||
async def get_system_metrics(_: bool = Depends(require_auth)):
|
||||
load_1m, load_5m, load_15m = os.getloadavg()
|
||||
memory_total = 0
|
||||
memory_available = 0
|
||||
meminfo = Path("/proc/meminfo")
|
||||
if meminfo.exists():
|
||||
values = {}
|
||||
for line in meminfo.read_text(encoding="ascii").splitlines():
|
||||
key, _, value = line.partition(":")
|
||||
if value:
|
||||
values[key] = int(value.strip().split()[0]) * 1024
|
||||
memory_total = values.get("MemTotal", 0)
|
||||
memory_available = values.get("MemAvailable", 0)
|
||||
cpu_count = os.cpu_count() or 1
|
||||
|
||||
# There is no load average on Windows: os.getloadavg does not exist there, and
|
||||
# psutil's emulation reads as 0.0 until its background sampler warms up (and
|
||||
# never means quite the same thing). Report real CPU utilisation instead, so
|
||||
# the dashboard's "CPU 使用率" card is not permanently stuck at zero.
|
||||
if hasattr(os, "getloadavg"):
|
||||
load_1m, load_5m, load_15m = os.getloadavg()
|
||||
load_percent = round(min(100, load_1m / cpu_count * 100), 1)
|
||||
else:
|
||||
load_1m = load_5m = load_15m = None
|
||||
# interval=None never blocks; it measures since the previous call anywhere
|
||||
# in the process, which the supervisor's 10s metrics tick keeps fresh.
|
||||
load_percent = round(psutil.cpu_percent(), 1)
|
||||
|
||||
memory = psutil.virtual_memory()
|
||||
network = psutil.net_io_counters()
|
||||
|
||||
metrics_path = Path(settings.replay_media_dir).resolve()
|
||||
disk_path = metrics_path if metrics_path.exists() else Path.cwd()
|
||||
disk = shutil.disk_usage(disk_path)
|
||||
cpu_count = os.cpu_count() or 1
|
||||
network_in_bytes = 0
|
||||
network_out_bytes = 0
|
||||
netdev = Path("/proc/net/dev")
|
||||
if netdev.exists():
|
||||
for line in netdev.read_text(encoding="ascii").splitlines()[2:]:
|
||||
_, _, counters = line.partition(":")
|
||||
fields = counters.split()
|
||||
if len(fields) >= 9:
|
||||
network_in_bytes += int(fields[0])
|
||||
network_out_bytes += int(fields[8])
|
||||
|
||||
return {
|
||||
"cpu": {
|
||||
@@ -122,12 +117,13 @@ async def get_system_metrics(_: bool = Depends(require_auth)):
|
||||
"load_1m": load_1m,
|
||||
"load_5m": load_5m,
|
||||
"load_15m": load_15m,
|
||||
"load_percent": round(min(100, load_1m / cpu_count * 100), 1),
|
||||
"load_percent": load_percent,
|
||||
"percent": load_percent,
|
||||
},
|
||||
"memory": {
|
||||
"total": memory_total,
|
||||
"available": memory_available,
|
||||
"used_percent": round((1 - memory_available / memory_total) * 100, 1) if memory_total else 0,
|
||||
"total": memory.total,
|
||||
"available": memory.available,
|
||||
"used_percent": round((1 - memory.available / memory.total) * 100, 1) if memory.total else 0,
|
||||
},
|
||||
"disk": {
|
||||
"total": disk.total,
|
||||
@@ -135,7 +131,7 @@ async def get_system_metrics(_: bool = Depends(require_auth)):
|
||||
"used_percent": round(disk.used / disk.total * 100, 1) if disk.total else 0,
|
||||
},
|
||||
"network": {
|
||||
"bytes_recv": network_in_bytes,
|
||||
"bytes_sent": network_out_bytes,
|
||||
"bytes_recv": network.bytes_recv,
|
||||
"bytes_sent": network.bytes_sent,
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user