完善节目管理与回放播放体验
This commit is contained in:
1 parent
b265518084
commit
a493ef2309
14 files changed
+446
-96
No files matched your search
@@ -16,7 +16,12 @@ class StreamService:
|
||||
|
||||
async def create_stream(self, title: str, description: str | None = None,
|
||||
cover_url: str | None = None,
|
||||
scheduled_at: str | None = None) -> Stream:
|
||||
scheduled_at: str | None = None,
|
||||
replay_file: str | None = None,
|
||||
chapters: list[dict] | None = None,
|
||||
transcode_profile: str = "standard",
|
||||
homepage_visible: bool = True,
|
||||
remote_control_enabled: bool = False) -> Stream:
|
||||
import uuid
|
||||
scheduled_datetime = None
|
||||
if scheduled_at:
|
||||
@@ -28,6 +33,11 @@ class StreamService:
|
||||
title=title,
|
||||
description=description,
|
||||
cover_url=cover_url,
|
||||
replay_file=replay_file,
|
||||
chapters_json=json.dumps(chapters or [], ensure_ascii=False),
|
||||
transcode_profile=transcode_profile,
|
||||
homepage_visible=homepage_visible,
|
||||
remote_control_enabled=remote_control_enabled,
|
||||
stream_key=uuid.uuid4().hex,
|
||||
status=StreamStatus.pending,
|
||||
scheduled_at=scheduled_datetime,
|
||||
@@ -43,6 +53,14 @@ class StreamService:
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
async def get_public_streams(self):
|
||||
result = await self.db.execute(
|
||||
select(Stream)
|
||||
.where(Stream.homepage_visible.is_(True))
|
||||
.order_by(Stream.created_at.desc())
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
async def get_stream(self, stream_id: int):
|
||||
result = await self.db.execute(
|
||||
select(Stream).where(Stream.id == stream_id)
|
||||
@@ -77,7 +95,7 @@ class StreamService:
|
||||
started_at=datetime.utcnow(),
|
||||
)
|
||||
rtmp_url = media_service.get_rtmp_url(stream_key)
|
||||
transcode_mgr.start_transcode(stream_key, rtmp_url)
|
||||
transcode_mgr.start_transcode(stream_key, rtmp_url, stream.transcode_profile)
|
||||
|
||||
async def set_stream_ended(self, stream_key: str):
|
||||
stream = await self.get_stream_by_key(stream_key)
|
||||
@@ -91,6 +109,10 @@ class StreamService:
|
||||
transcode_mgr.stop_transcode(stream_key)
|
||||
|
||||
async def get_watch_urls(self, stream_key: str, client_ip: str | None = None):
|
||||
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)
|
||||
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
|
||||
@@ -106,7 +128,7 @@ class StreamService:
|
||||
"url": f"{base_hls}/index.m3u8",
|
||||
"qualities": [],
|
||||
}
|
||||
for quality in settings.qualities:
|
||||
for quality in qualities:
|
||||
label = quality["label"]
|
||||
line_info["qualities"].append({
|
||||
"label": label,
|
||||
@@ -118,7 +140,7 @@ class StreamService:
|
||||
|
||||
return {
|
||||
"lines": lines,
|
||||
"qualities": [{"label": q["label"], "width": q["width"], "height": q["height"]} for q in settings.qualities],
|
||||
"qualities": [{"label": q["label"], "width": q["width"], "height": q["height"]} for q in qualities],
|
||||
"multicast_available": multicast_detector.is_available(),
|
||||
"selected_node": {
|
||||
"id": best_node["id"],
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import asyncio
|
||||
import subprocess
|
||||
import os
|
||||
from config import settings
|
||||
@@ -7,12 +6,15 @@ from config import settings
|
||||
class TranscodeManager:
|
||||
_processes: dict[str, list[subprocess.Popen]] = {}
|
||||
|
||||
def start_transcode(self, stream_key: str, input_url: str):
|
||||
def start_transcode(self, stream_key: str, input_url: str, profile: str = "standard"):
|
||||
if stream_key in self._processes:
|
||||
return
|
||||
|
||||
procs = []
|
||||
for quality in settings.qualities:
|
||||
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"]
|
||||
@@ -25,11 +27,16 @@ class TranscodeManager:
|
||||
"-c:v", "libx264", "-preset", "ultrafast",
|
||||
"-b:v", bitrate,
|
||||
"-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})",
|
||||
"-copyts", "-start_at_zero",
|
||||
"-c:a", "aac", "-b:a", "128k",
|
||||
"-f", "hls",
|
||||
"-hls_time", "4",
|
||||
"-hls_time", str(segment_seconds),
|
||||
"-hls_list_size", "6",
|
||||
"-hls_flags", "delete_segments",
|
||||
"-hls_flags", "delete_segments+independent_segments+program_date_time",
|
||||
"-hls_segment_filename", f"{hls_dir}/segment_%03d.ts",
|
||||
f"{hls_dir}/index.m3u8",
|
||||
]
|
||||
|
||||
Reference in new issue
Block a user