feat: expand livestream management platform

This commit is contained in:
admin committed 2026-08-19 22:24:01 +08:00
1 parent 383c066d86
commit ed62d42a63
12 files changed
+223 -24

No files matched your search

+13 -2
View File
@@ -14,6 +14,17 @@ class StreamService:
def __init__(self, db: AsyncSession):
self.db = db
@staticmethod
def effective_renditions(stream: Stream) -> list:
if stream.renditions_json:
try:
renditions = json.loads(stream.renditions_json)
if isinstance(renditions, list) and renditions:
return renditions
except (TypeError, ValueError, json.JSONDecodeError):
pass
return settings.transcode_profiles.get(stream.transcode_profile, settings.qualities)
async def create_stream(self, title: str, description: str | None = None,
cover_url: str | None = None,
scheduled_at: str | None = None,
@@ -95,7 +106,7 @@ class StreamService:
started_at=datetime.utcnow(),
)
rtmp_url = media_service.get_rtmp_url(stream_key)
transcode_mgr.start_transcode(stream_key, rtmp_url, stream.transcode_profile)
transcode_mgr.start_transcode(stream_key, rtmp_url, self.effective_renditions(stream))
async def set_stream_ended(self, stream_key: str):
stream = await self.get_stream_by_key(stream_key)
@@ -112,7 +123,7 @@ class StreamService:
stream = await self.get_stream_by_key(stream_key)
if not stream:
return {"lines": [], "qualities": [], "multicast_available": False, "selected_node": None}
qualities = settings.transcode_profiles.get(stream.transcode_profile, settings.qualities)
qualities = self.effective_renditions(stream)
best_node = lb.select_best_node(client_ip)
if not best_node:
best_node = lb.get_enabled_nodes()[0] if lb.get_enabled_nodes() else None
+12 -7
View File
@@ -6,33 +6,38 @@ from config import settings
class TranscodeManager:
_processes: dict[str, list[subprocess.Popen]] = {}
def start_transcode(self, stream_key: str, input_url: str, profile: str = "standard"):
def start_transcode(self, stream_key: str, input_url: str, qualities: list[dict]):
if stream_key in self._processes:
return
procs = []
qualities = settings.transcode_profiles.get(profile, settings.qualities)
segment_seconds = max(1, settings.hls_segment_seconds)
keyframe_frames = segment_seconds * 30
for quality in qualities:
label = quality["label"]
width = quality["width"]
height = quality["height"]
fps = quality.get("fps", 30)
codec = quality.get("codec", "libx264")
bitrate = quality["bitrate"]
crf = quality.get("crf", 23)
preset = quality.get("preset", "ultrafast")
audio_bitrate = quality.get("audio_bitrate", "128k")
keyframe_seconds = quality.get("keyframe_seconds", segment_seconds)
keyframe_frames = keyframe_seconds * fps
hls_dir = f"/tmp/hls/{stream_key}/{label}"
os.makedirs(hls_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y",
"-i", input_url,
"-c:v", "libx264", "-preset", "ultrafast",
"-b:v", bitrate,
"-c:v", codec, "-preset", preset,
"-r", str(fps), "-b:v", bitrate, "-crf", str(crf),
"-s", f"{width}x{height}",
"-sc_threshold", "0",
"-g", str(keyframe_frames),
"-keyint_min", str(keyframe_frames),
"-force_key_frames", f"expr:gte(t,n_forced*{segment_seconds})",
"-force_key_frames", f"expr:gte(t,n_forced*{keyframe_seconds})",
"-copyts", "-start_at_zero",
"-c:a", "aac", "-b:a", "128k",
"-c:a", "aac", "-b:a", audio_bitrate,
"-f", "hls",
"-hls_time", str(segment_seconds),
"-hls_list_size", "6",