Make streaming actually work end-to-end#2
Open
Cfomodz wants to merge 1 commit into
Open
Conversation
The core pipeline previously streamed an FFmpeg test pattern instead of real content (TODOs in _start_headless_ffmpeg_stream), and starting any stream crashed on unparsed JSON columns and UUID-derived display/port numbers. This makes the whole flow work: login -> create stream -> start -> real page/game frames arrive at the RTMP endpoint (verified against a local RTMP sink for HTML, pygame, and test-pattern paths). Real frame capture: - New capture_html.py: attaches to headless Chrome via the DevTools screencast API (websocket) and pipes JPEG frames to FFmpeg at a fixed frame rate. Chrome now launched with --remote-allow-origins and without --disable-images. - New capture_pygame.py: runs user pygame scripts under SDL's dummy driver, intercepts display.flip()/update(), and emits frames at a fixed rate (duplicating the last frame when the game idles) so the stream timeline tracks wall-clock. - Pipe-fed FFmpeg normalizes frames to the preset resolution and adds a silent AAC track with -shortest so it exits when capture ends. - X11 path: Chrome no longer launched with --headless under Xvfb (it rendered nothing for x11grab); kiosk mode instead. - headless_streamer.py rewritten around the same capture helpers; its old HTTP screenshot endpoint doesn't exist in CDP and produced zero frames, and --virtual-time-budget froze pages. Crash and correctness fixes in stream_manager.py: - Parse audio_config/multi_stream_targets/schedule_config JSON columns when loading streams (previously str.get() AttributeError on start). - Derive X display / debug port numbers from a hash of the stream ID (int(uuid[-1]) raised ValueError for hex letters). - Add the ~10 StreamDatabase methods that routes referenced but never existed (templates get/update/delete, create_stream_from_template, platform config CRUD, get_project_streams). - stream_lock is now reentrant: updating or deleting a running stream deadlocked the whole app (update_stream held the lock and called stop_stream which re-acquired it). - _full_restart reset status first (start_streaming refused to run while status was "live", so every automatic recovery failed). - _restart_ffmpeg only restarts FFmpeg in isolation on the X11 path; pipe-fed and test-pattern pipelines restart whole, and fallback to test pattern cleans up partial pipelines (orphaned FFmpeg processes kept fighting for the RTMP endpoint). - Test pattern: fix str-int TypeError in drawtext y position, escape unsafe characters in the overlay text, pace lavfi input with -re. - Persist orientation (the create form sent it; it was dropped), widen update_stream allowed fields, fix audio update writing to a nonexistent column. - Reset stale live/error statuses at startup; persist the Flask session secret so restarts don't log users out; send subprocess output to DEVNULL/log files (unread PIPEs filled up and blocked Chrome/FFmpeg after a while, killing 24/7 streams). - Multi-target tee muxer now includes the required -map arguments. Misc: - Detect chromium/chrome binary instead of hardcoding chromium-browser (override with STREAMDROP_BROWSER). - monitor.sh: fix syntax error in process count, hardcoded /home/toor paths, and a query against a nonexistent table. - setup.sh installs fonts (drawtext and page rendering need them). - requirements: add websocket-client, drop unused selenium/opencv/numpy. - Remove committed test streams.db (already gitignored). - main.py: initialize ffmpeg_process (cleanup crashed on failed start); smart_streamer passes credentials to the HTMLStreamer fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VEoYpbVmPMBeLkoWRtdva7
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The core pipeline previously streamed an FFmpeg test pattern instead of real content, and starting any stream crashed before FFmpeg even launched. This PR makes the whole flow work: login → create stream → start → real page/game frames arrive at the RTMP endpoint — verified end-to-end against a local RTMP sink for the HTML, pygame, and test-pattern paths.
What was broken
TODOstubs pushing atestsrctest pattern instead of the page/game.headless_streamer.pytried to screenshot Chrome via a DevTools HTTP endpoint that doesn't exist (screenshots require a WebSocket CDP session), so it produced zero frames, and--virtual-time-budgetfroze pages after 5s.audio_config,multi_stream_targets) came out of SQLite as strings and got.get()called on them; display/port numbers came fromint(uuid[-1]), which raisesValueErrorfor hex letters.--headlessunder Xvfb, so nothing rendered to the display x11grab recorded.StreamDatabasemethods that didn't exist (templates, platform configs, project streams) → instantAttributeError.start_streaming()refused to run while status was still"live".livestatuses surviving restarts, multi-target tee muxer missing its required-mapargs, and a committed teststreams.db.What this PR does
Real frame capture (new):
capture_html.py— attaches to headless Chrome via the DevTools screencast API over WebSocket and pipes JPEG frames to FFmpeg at a fixed frame rate. Chrome now launches with--remote-allow-originsand without--disable-images.capture_pygame.py— runs user pygame scripts under SDL's dummy driver, interceptsdisplay.flip()/update(), and emits frames on a fixed-rate schedule (duplicating the last frame while the game idles) so the stream timeline tracks wall-clock.-shortestso it exits when capture ends (instead of streaming silence forever).headless_streamer.pyrewritten around the same capture helpers.Crash/correctness fixes in
stream_manager.py:stream_lock;_full_restartresets status first so recovery can succeed; fallback to test pattern cleans up partial pipelines (orphaned FFmpeg previously kept fighting for the RTMP endpoint);_restart_ffmpegonly bounces FFmpeg in isolation on the X11 path.create_stream_from_template, platform config CRUD,get_project_streams).orientationfrom the create form; widenupdate_streamallowed fields; audio update writes to the real column; test-pattern drawtextstr - intTypeError fixed, unsafe characters escaped, lavfi input paced with-re.live/errorstatuses reset at startup; Flask session secret persisted so restarts don't log users out; subprocess output to DEVNULL/log files instead of unread PIPEs.Misc: browser binary auto-detection (
STREAMDROP_BROWSERoverride),monitor.shfixes (syntax error, hardcoded/home/toorpaths, query against a nonexistent table), fonts added tosetup.sh(page rendering + drawtext need them),websocket-clientadded / unusedselenium/opencv/numpydropped, committed teststreams.dbremoved.How it was verified
Ran the real user flow through the web API against a local RTMP sink (
ffmpeg -listen 1):example_game.py's bouncing balls captured headlessly, pillar-boxed to 1280×720.Known limitation
Per-stream display/debug-port numbers are hash-derived (
sha1(id) % 500), so two streams have a ~1-in-500 chance of colliding. Fine for the typical few-streams-per-droplet setup; proper port allocation can follow if needed.🤖 Generated with Claude Code
https://claude.ai/code/session_01VEoYpbVmPMBeLkoWRtdva7
Generated by Claude Code