diff --git a/README.md b/README.md index cf2334d..7f87da5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# burger +# SCE TV (previously burger) SCE TV diff --git a/server.py b/server.py index 8b83645..6223c0e 100644 --- a/server.py +++ b/server.py @@ -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,7 +136,8 @@ def create_ffmpeg_stream( # the below function returns 0 if the video ended on its own # 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( exit_code=exit_code, ).inc() @@ -142,9 +145,10 @@ def create_ffmpeg_stream( 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)