Skip to content

feat: gracefully stop and restart recordings during display resize#158

Open
hiroTamada wants to merge 3 commits intomainfrom
htamada/graceful-recording-resize
Open

feat: gracefully stop and restart recordings during display resize#158
hiroTamada wants to merge 3 commits intomainfrom
htamada/graceful-recording-resize

Conversation

@hiroTamada
Copy link
Contributor

@hiroTamada hiroTamada commented Feb 23, 2026

Summary

  • Instead of returning 409 when recordings are active during a PATCH /display resize, the endpoint now gracefully stops active recordings, performs the resize, then restarts them with the same recorder ID and params.
  • Pre-resize recording segments are preserved by renaming the finalized file (e.g., <id>-before-resize-<timestamp>.mp4) before the new recording begins.
  • Live view/replay sessions still block the resize with a 409 as before.

Changes

server/lib/recorder/ffmpeg.go

  • Added Params() and OutputPath() accessors to FFmpegRecorder (thread-safe).

server/cmd/api/api/display.go

  • Separated the live-view check (still returns 409) from the recording check.
  • Added stopActiveRecordings() — stops each active FFmpegRecorder, waits for shutdown, deregisters it, and captures its ID/params/outputPath for restart. Tolerates finalization errors as long as the process actually stopped.
  • Added restartRecordings() — renames the old segment file, creates a new recorder via the factory with the same ID and params, registers and starts it. Uses defer with context.WithoutCancel so recordings are restarted even if the resize itself fails.

server/lib/recorder/ffmeg_test.go

  • Added tests for Params() and OutputPath().

server/cmd/api/api/display_test.go (new)

  • TestStopActiveRecordings — 4 sub-tests: stops/deregisters active recording, handles multiple recordings, skips idle recorders, handles empty manager.
  • TestRestartRecordings — 2 sub-tests: renames old file and starts new recording, handles missing old file.
  • TestStopAndRestartRecordings_RoundTrip — full stop→restart cycle verifying the same recorder ID is reused.

Test plan

  • go test ./lib/recorder/ — all pass (including new accessor tests)
  • go test ./cmd/api/api/ — all pass (including 7 new display tests)
  • E2E: start a recording, call PATCH /display with new resolution, verify recording restarts at new resolution and pre-resize segment is preserved on disk

Made with Cursor


Note

Medium Risk
Touches display-resize and recording lifecycle orchestration; failures could interrupt recordings or overwrite files despite best-effort renaming and restart logic.

Overview
PATCH /display now blocks only on active live view/replay sessions, but will otherwise gracefully stop any active FFmpeg recordings, perform the resize, then restart those recordings (same recorder ID/params) afterward.

To support this, FFmpegRecorder gains thread-safe Params() and OutputPath() accessors (with deep-copy of params), and the API adds stop/restart helpers that preserve pre-resize segments by renaming the finalized .mp4 to *-before-resize-<timestamp>.mp4 before starting a new segment. New unit tests cover the new recorder accessors and the stop→restart behavior end-to-end.

Written by Cursor Bugbot for commit 0820c4a. This will update automatically on new commits. Configure here.

Instead of refusing display resize requests (409) when recordings are
active, PatchDisplay now stops active recordings, performs the resize,
then restarts them with the same ID and params. The pre-resize segment
is preserved by renaming it (e.g. `<id>-before-resize-<ts>.mp4`).

Live view sessions still block the resize as before.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Don't block recording restart when rename of old file fails (rename is
  best-effort to preserve the pre-resize segment)
- Deep copy pointer fields in FFmpegRecordingParams.clone() so Params()
  callers cannot mutate recorder internals

Co-authored-by: Cursor <cursoragent@cursor.com>
if !ok {
log.Warn("cannot capture params from non-FFmpeg recorder, skipping", "id", id)
continue
}
Copy link

Choose a reason for hiding this comment

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

Non-FFmpeg recordings bypass resize safety

High Severity

stopActiveRecordings silently skips active recorders that aren’t *recorder.FFmpegRecorder. This allows PATCH /display to proceed while a non-FFmpeg recording is still running, which regresses the prior “block while recording” behavior and can lead to active recording sessions being resized mid-capture.

Fix in Cursor Fix in Web

If DeregisterRecorder fails, the old recorder remains registered.
Appending to the stopped list would cause RegisterRecorder to fail
with a duplicate ID during restart.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

}

log.Info("recording restarted after resize", "id", info.id)
}
Copy link

Choose a reason for hiding this comment

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

Restart may not use original output path

Medium Severity

restartRecordings captures outputPath for rename purposes but recreates the recorder via s.factory(info.id, info.params), which derives its own outputPath. If the original recorder’s outputPath didn’t match the factory’s derived path (custom naming, different extension, etc.), the restarted recording won’t continue at the same path and the “preserve then continue” segmentation behavior becomes inconsistent.

Fix in Cursor Fix in Web

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.

1 participant