Skip to content

Conversation

@leszko
Copy link
Collaborator

@leszko leszko commented Nov 27, 2025

Env variables

RECORDING_ENABLED

  • Default: true
  • Description: Enable/disable recording. When false, recording is disabled.

RECORDING_MAX_LENGTH

  • Default: 1h
  • Description: Maximum total recording length (sum of all segments). Recording stops when reached.
  • Format: Supports 1h, 30m, 120s, or plain seconds (e.g., 3600)

RECORDING_STARTUP_CLEANUP_ENABLED

  • Default: true
  • Description: Enable/disable cleanup of recording files

Assumptions / Comments

  • The recording stream is sourced from the same place we send frames to the frontend.
    • This means that if a frame is dropped, it will also be missing from the recording (sorry, Ryan).
    • The main benefit is that we can piggy-back on our existing PTS calculation mechanism, and aiortc.contrib.media.MediaRecorder.
  • Temporary recordings are stored in the OS temp directory.
  • Both stream pause/resume and exporting a recording cause the backend to segment/clip the stream. The final recording is a concatenation of these segments when downloaded. This is mainly due to how pause handling works and the behavior of MediaRecorder, which needs to finalize the stream before exporting.
  • The pause/resume feature adds significant complexity to recording logic. On the backend we keep sending the same frame while paused, but we don't want repeated identical frames in the final recording.
    Recording input may be tricky because of the pause/resume (we don't pause input stream!)
  • We use ffmpeg (as Python library) for mp4 segments concatenation; I experimented with using AV library, but it happened to be surprisingly difficult (a lot of corner cases, etc.), so I ended up with ffmpeg. AV is still an option, but required more work and maintenance. One other option is to use the moviepy library
  • MAX_FPS is decreased to 30 because 60 FPS won't work with MediaRecorder, because MediaRecorder calculates PTS/DTS on its own and if we start delivering frames at 60FPS, it fails because PTS/DTS are duplicated

Demo with comments

  1. Paused video is visible in the recording
  2. When the recording is downloaded the second time, then it has the length of the whole video, but the Start Time is from the last recording time. So depending on the player you may see a blank screen at the beginning (you see in Quick Time, but VLC or ffplay respects the Start Time and starts from where the video started). e.g.:
  • Play stream, after 00:10:00, you download the recording; stream keeps playing
  • After another 00:10:00, you click download again,
  • The results stream has Duration 00:20:00 and Start Time 00:10:00
  • QuickTime does not respect Start Time, so you'll see first 00:10:00 blank black screen
demo_recording.mp4

fix #56

@leszko leszko force-pushed the rafal/recording branch 4 times, most recently from 468393a to d953d15 Compare November 28, 2025 14:02
@leszko leszko marked this pull request as ready for review November 28, 2025 14:25
@leszko leszko requested a review from yondonfu November 28, 2025 14:59
pyproject.toml Outdated
dependencies = [
"aiortc>=1.13.0",
"fastapi>=0.116.1",
"ffmpeg-python>=0.2.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm that this does not require the user to install a system copy of ffmpeg separately?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do need the FFmpeg binary installed separately, since ffmpeg-python is just a wrapper. I’ve previously tried replacing it with other libraries, but it wasn’t very straightforward. That said, we only use FFmpeg for MP4 concatenation, so replacing it should be doable.

I’ll try again to remove ffmpeg-python and evaluate alternatives. Some options that avoid concatenation entirely (mentioning them here, since maybe we'll like them more and it'd solve the ffmpeg-python issue):

  1. Return multiple MP4 files (listed in the UI or zipped on the backend if the stream was paused).
  2. Rethink the “pause” feature, since this issue only affects that path.
  3. Adopt a WYSIWYG approach: the recording matches exactly what the user sees, including pause, so some frames will be repeated.
  4. Revisit recording delivery: instead of enforcing timeline timing, record frames and let the user choose the FPS when constructing the final video.

Copy link
Contributor

@yondonfu yondonfu Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to do 3 in this PR if it means an easy way to avoid requiring system ffmpeg installation and then take a separate pass at improving how we handle recording (4 feels promising).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to do 3 in this PR if it means an easy way to avoid requiring system ffmpeg installation and then take a separate pass at improving how we handle recording (4 feels promising).

Ok, let me do some time-boxed exercise (a few hours max) and if getting rid of ffmpeg is not trivial, then I'll just do 3.

Copy link
Collaborator Author

@leszko leszko Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've spent some time exploring MP4 concatenation and ultimately decided to go with Option 3.

Some notes:

  • MP4 concatenation turned out to be significantly more complex than I initially expected.
  • I do have a working pure-Python concatenation branch, but the Claude-generated implementation is very long and complex (>1k lines) and still doesn't handle certain edge cases (e.g., varying FPS, long mp4 files).
  • My suggestion is to revisit this if a real need arises. For now, maintaining AI-generated MP4 concatenation code seems unnecessary.

See the PR Description and the attached demo to see how it works now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see an attached demo anywhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, added now with the comment. PTAL.

@leszko leszko force-pushed the rafal/recording branch 8 times, most recently from 4134dca to 648ee01 Compare January 20, 2026 14:35
Signed-off-by: Rafal Leszko <rafal@livepeer.org>
Signed-off-by: Rafal Leszko <rafal@livepeer.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for recording

3 participants