-
Notifications
You must be signed in to change notification settings - Fork 5
Update README #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Update README #109
Changes from all commits
f4e2a3d
9664188
c327efe
416c182
e1033ca
6dc18cc
e9c9e59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| # burger | ||
| # SCE TV (previously burger) | ||
|
|
||
| SCE TV | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,6 +60,8 @@ class UrlType(enum.Enum): | |
|
|
||
| interlude_lock = threading.Lock() | ||
|
|
||
| hls_lock = threading.Lock() | ||
|
|
||
| args = get_args() | ||
|
|
||
| # Create a cache object to store video files, initializing it with the file path specified in the command-line arguments or configuration settings. This instance is used to cache downloaded videos. | ||
|
|
@@ -88,7 +90,7 @@ def create_ffmpeg_stream( | |
| loop=False, | ||
| title=None, | ||
| thumbnail=None, | ||
| play_interlude_after=False, | ||
| play_interlude_after=True, | ||
| ): | ||
| if video_path is None: | ||
| logging.info("video_path is None. ffmpeg_stream cancelled.") | ||
|
|
@@ -134,17 +136,19 @@ def create_ffmpeg_stream( | |
| # the below function returns 0 if the video ended on its own | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [LOW] The logging message |
||
| # 137, 1 | ||
| exit_code = process.wait() | ||
| logging.info(f"process {process.pid} exited with code {exit_code}") | ||
| logging.info(f"process {process.pid} started for {video_type.value} video: {video_path}") | ||
|
|
||
| MetricsHandler.subprocess_count.labels( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] The condition for releasing |
||
| exit_code=exit_code, | ||
| ).inc() | ||
| if video_type in process_dict: | ||
| process_dict.pop(video_type) | ||
| current_video_dict.clear() | ||
|
|
||
| if exit_code == 0 and play_interlude_after and args.interlude: | ||
| if (exit_code == 0 or video_type == State.PLAYING) and play_interlude_after and args.interlude: | ||
| interlude_lock.release() | ||
|
|
||
| hls_lock.release() | ||
| logging.info(f"exiting create_ffmpeg_stream with exit code {exit_code}") | ||
| return exit_code | ||
|
|
||
|
|
||
|
|
@@ -289,47 +293,53 @@ def handle_cache_play(): | |
|
|
||
|
|
||
| def run_hls_stream(): | ||
| logging.info("Starting ffmpeg command for HLS stream.") | ||
|
|
||
| playlist_path = Path(args.hls_file_path).resolve() | ||
| # Ensure the directory that will contain the playlist exists | ||
| playlist_path = Path(args.hls_file_path) | ||
| playlist_path.parent.mkdir(parents=True, exist_ok=True) | ||
| ffmpegcommand = [ | ||
| "ffmpeg", | ||
| "-i", | ||
| args.rtmp_stream_url, | ||
| "-c:v", "copy", | ||
| "-c:a", "copy", | ||
| "-f", "hls", | ||
| "-hls_time", "4", | ||
| "-hls_list_size", "5", | ||
| "-hls_flags", "delete_segments", | ||
| f"{args.hls_file_path}/tv.m3u8" | ||
| ] | ||
| #Delay the command to allow the monitor thread to start | ||
| command = [ | ||
| "sh", "-c", | ||
| f"sleep 2 && {' '.join(ffmpegcommand)}", | ||
| ] | ||
| logging.info(f"Running command: {' '.join(command)}") | ||
| process = subprocess.Popen( | ||
| command, | ||
| stdout=subprocess.DEVNULL, | ||
| stdin=subprocess.DEVNULL, | ||
| stderr=subprocess.PIPE, | ||
| ) | ||
|
|
||
| logging.info(f"HLS stream started with PID {process.pid}") | ||
|
|
||
| def _monitor_hls_process(p): | ||
| logging.info(f"Monitoring HLS process with PID {p.pid}") | ||
| exit_code = p.wait() | ||
| if exit_code != 0: | ||
| error_output = p.stderr.read().decode(errors="replace") | ||
| logging.error(f"HLS ffmpeg process exited with code {exit_code}. Error output:\n{error_output}") | ||
|
|
||
| threading.Thread(target=_monitor_hls_process, args=(process,), daemon=True).start() | ||
| while True: | ||
| ffmpeg_cmd = [ | ||
| "ffmpeg", | ||
| "-i", args.rtmp_stream_url, | ||
| "-c:v", "copy", | ||
| "-c:a", "copy", | ||
| "-f", "hls", | ||
| "-hls_time", "4", | ||
| "-hls_list_size", "5", | ||
| "-hls_flags", "delete_segments", | ||
| f"{args.hls_file_path}/tv.m3u8", | ||
| ] | ||
| proc = subprocess.Popen( | ||
| ffmpeg_cmd, | ||
| stdout=subprocess.DEVNULL, | ||
| stdin=subprocess.DEVNULL, | ||
| stderr=subprocess.PIPE, | ||
| ) | ||
| logging.info(f"HLS worker: started FFmpeg PID {proc.pid}") | ||
| # start the logging thread (non-blocking) | ||
| threading.Thread( | ||
| target=_monitor_ffmpeg, args=(proc,), daemon=True | ||
| ).start() | ||
| # wait until a playback thread ends | ||
| hls_lock.acquire() | ||
| # rotate: kill, clean, loop | ||
| logging.info("HLS worker: rotate signal, killing FFmpeg & cleaning dir") | ||
| kill_child_processes(proc.pid) | ||
| _clean_hls_dir() | ||
|
|
||
| def _monitor_ffmpeg(proc: subprocess.Popen): | ||
| """Block until proc dies and log exit / stderr.""" | ||
| exit_code = proc.wait() | ||
| if exit_code == 0: | ||
| logging.info(f"HLS ffmpeg exited cleanly (code 0)") | ||
| else: | ||
| err = proc.stderr.read().decode(errors="replace") | ||
| logging.error( | ||
| f"HLS ffmpeg exited with code {exit_code}\n---- STDERR ----\n{err}" | ||
| ) | ||
|
|
||
| def _clean_hls_dir(): | ||
| hls_dir = Path(args.hls_file_path) | ||
| for f in hls_dir.glob("*.ts"): | ||
| f.unlink(missing_ok=True) | ||
|
|
||
| @app.get("/state") | ||
| async def state(): | ||
|
|
@@ -374,11 +384,11 @@ async def play_file(file_path: str = "cache", title: str = None, thumbnail: str | |
| except Exception as e: | ||
| logging.exception(e) | ||
| raise HTTPException(status_code=500, detail="check logs") | ||
| finally: | ||
| # Start streaming video | ||
| # Once video is finished playing (or stopped early), restart interlude | ||
| if args.interlude: | ||
| interlude_lock.release() | ||
| # finally: | ||
| # # Start streaming video | ||
| # # Once video is finished playing (or stopped early), restart interlude | ||
| # if args.interlude: | ||
| # interlude_lock.release() | ||
|
|
||
|
|
||
| @app.post("/play") | ||
|
|
@@ -465,6 +475,7 @@ async def stop(): | |
| # Check if there is a video playing to stop | ||
| if State.PLAYING in process_dict: | ||
| # Stop the video playing subprocess | ||
| hls_lock.release() | ||
| stop_video_by_type(State.PLAYING) | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[CRITICAL] The HLS stream rotation logic in
run_hls_streamhas a critical flaw regarding thehls_lockinitialization and usage.hls_lockis initialized as unlocked. Whenrun_hls_streamstarts, it immediately starts an FFmpeg process for HLS, then acquires thehls_lock(which succeeds immediately as it's unlocked), kills the FFmpeg process it just started, cleans the directory, starts another FFmpeg process, and then blocks indefinitely on the secondhls_lock.acquire()call because nohls_lock.release()has occurred yet. This leads to an immediate, unnecessary rotation of the HLS stream on startup, followed by indefinite blocking of the HLS rotation mechanism until the first externalhls_lock.release()signal is received from another thread (e.g., when a video starts or stops).