Skip to content

Extract RecordingPipeline from CameraViewController (UI modernization PR 9)#132

Merged
darioalessandro merged 2 commits into
masterfrom
modernize/pr9-recording-pipeline
Jul 10, 2026
Merged

Extract RecordingPipeline from CameraViewController (UI modernization PR 9)#132
darioalessandro merged 2 commits into
masterfrom
modernize/pr9-recording-pipeline

Conversation

@darioalessandro

Copy link
Copy Markdown
Member

Summary

PR 9 of the UI modernization series: the recording/sample-buffer pipeline moves out of CameraViewController into a new non-UI RecordingPipeline, following the exact playbook of PR 8's CaptureEngine extraction — mechanical move, no behavior or threading changes, closure seams back to the VC, full suite green as the gate.

CameraViewController drops from 962 → 632 lines. With capture (PR 8) and recording (this PR) both out, what remains is real UI plus the frame streamers — the runway for the PR 10 SwiftUI chrome.

What moved

RecordingPipeline (407 lines, zero UIKit) now owns:

  • The asset writer and its video/audio inputs, the pixel-buffer adaptor, and the writing queue
  • The six-boolean recording state machine (isRecording, recordingWillBeStarted/Stopped, readyToRecordVideo/Audio) — moved intact, not redesigned; the redesign stays queued behind the single-owned-queue threading fix
  • Per-frame writing and cropping, including the cached even-rounded crop rect for non-16:9 aspect ratios
  • Saving/sending the finished movie (Photos library save, stop response, video resource transfer)

What stayed in the VC — and why

  • The sample-buffer delegate role: captureOutput fans out to two consumers — frame streaming (live preview, not recording) and the recording path. The VC keeps the delegate and forwards recording frames to pipeline.processFrame; the audio delegate is passed into pipeline.startRecording(audioSampleBufferDelegate:) the same way PR 8 passes it into engine.setupCamera.
  • The microphone-permission flow (prompt UI) — the pipeline is only invoked once permission is granted.
  • Timer/progress overlays, preview layer, lifecycle — unchanged.

Seams

  • sendMessage — a single actor relay for the start ack, stop response, and video resource. It captures the session ref (not the VC), carrying over PR 8's teardown-safety guarantee: an in-flight recording still reaches the actor if the VC deallocates. It also made the pipeline's no-op guards directly assertable (inject a recorder, assert nothing was sent).
  • onRecordingStarted/Stopped (timer overlay), onModeChanged (idle vs recording chrome), onError, onPhotosAccessDenied.

Tests

385/385 green (381 existing + 4 new). The loopback video round-trips — including the 3-step stop protocol and the start-ack forwarding fixed in PR 8 — passed unchanged across the move, which is the point of the safety net.

New device-free RecordingPipelineTests:

  • 4:3 video input setup caches the even-rounded crop rect (1920×1080 → 1440×1080 at x=240) and creates the adaptor — the "dimensions must be even for the codec" rounding had never been asserted anywhere
  • 16:9 needs no crop rect and no adaptor
  • Audio input setup from a bare format description
  • Stop-while-idle is a strict no-op: no state change, no messages, no callbacks

Explicitly deferred (unchanged from the queued list)

Single owned serial queue (threading fix), RotationCoordinator (iOS 17) / maxPhotoDimensions (iOS 16) migrations, frame-streaming extraction, SwiftUI chrome (PR 10+).

🤖 Generated with Claude Code

darioalessandro and others added 2 commits July 10, 2026 00:04
… PR 9)

Mechanical move, no behavior/threading changes: RecordingPipeline (non-UI)
now owns the asset writer and its inputs, the six-boolean recording state
machine, the writing queue, per-frame write/crop, and saving/sending the
finished movie. The VC stays the sample-buffer delegate (captureOutput fans
out to the frame streamers, which are live preview, not recording) and keeps
the microphone-permission flow, forwarding recording frames to the pipeline.

Seams: sendMessage (actor relay for the start ack, stop response and video
resource — captures the session ref, not the VC), onRecordingStarted/Stopped
(timer overlay), onModeChanged (idle vs recording chrome), onError,
onPhotosAccessDenied.

CameraViewController: 962 -> 632 lines. New device-free tests cover asset
writer input setup including the even-rounded 4:3 crop rect and the
stop-while-idle guard. 385/385 tests green (381 + 4 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New-code cleanups from the senior Swift review: tighten access control on
the pipeline (assetWriter/videoCropContext/writeSampleBuffer private,
pixelBufferAdaptor/cachedVideoCropRect private(set)), document that onError
fires on the writing queue, name the idle condition at both onModeChanged
call sites, and use XCTUnwrap instead of force-unwraps in the tests.

Also three pre-existing one-liners the extraction surfaced: guard the
missing-microphone case so it reaches the record-without-audio fallback
instead of crashing on audioDevice!, wire the silent startWriting() failure
to onError, and drop two DEBUG prints from production paths.

385/385 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@darioalessandro darioalessandro merged commit 2c3e5c4 into master Jul 10, 2026
1 check passed
@darioalessandro darioalessandro deleted the modernize/pr9-recording-pipeline branch July 10, 2026 07:41
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