Pro controls: manual exposure + Cinematic video (#206) - #223
Open
darioalessandro wants to merge 6 commits into
Open
Pro controls: manual exposure + Cinematic video (#206)#223darioalessandro wants to merge 6 commits into
darioalessandro wants to merge 6 commits into
Conversation
Adds ExposurePolicy (pure clamp/recording-cap/unsupported decisions), SetExposure=33 + ExposureState on the wire, supports_manual_exposure capability, one-owner apply in CaptureEngine re-applied on device swap and format change, coordinator gate pinned by loopback tests, and a hardware probe in CaptureIntegrationTests. No UI yet. Design: Docs/pro-controls.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
📊 Test Coverage Report✅ 914/928 tests passed · ⏭️ 14 skipped
🎥 Capture & session core
📁 Full report — 106 files
Generated from |
- CinematicPolicy + SetCinematic=34 + CinematicState on the wire, gated on supports_cinematic_video; engine owns isCinematicVideoCaptureEnabled/ simulatedAperture in one apply function (iOS 26 availability-guarded), Cinematic tap-to-focus routing, mode/recording locks per policy. - Manual-exposure lens hop: entering Manual on a virtual device swaps to activePrimaryConstituent at runtime only when the device refuses .custom. - Monitor UI: PRO tray tile -> ProControlsPanel (Auto/Manual segmented, detented shutter/ISO/aperture dials, echo-driven values, VoiceOver adjustable), camera-side readout chip; behind ENABLE_PRO_CONTROLS. - IAP "10" Pro Controls mirroring tap-to-focus, 15-locale strings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
darioalessandro
marked this pull request as ready for review
August 24, 2026 06:02
The PRO panel is included for every user; the only gate left is the capability gate (button appears only when the camera offers the feature). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onstituent activePrimaryConstituent is nil until the virtual device is used in a running session (AVCaptureDevice.h), and the first capabilities exchange fires before the session starts. Every modern iPhone opens on a virtual device, so the camera advertised supports_manual_exposure = false and the PRO tray tile never appeared. The lens choice is now one function (manualExposureLensLocked) used by both the capability and the hop; the tray predicate moved into MonitorTray so it is unit-tested. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K5uHRuiiLu5FEBiHZkBWo3
…essage A single camera lands on the multicam director (MULTICAM_FOR_SINGLE_CAMERA), whose tray rendered nothing for .proControls — so the PRO tile only ever existed on the classic 1:1 monitor nobody reaches. The director now offers the tile for the FOCUSED camera (like torch and zoom): CameraLink carries that camera's echoed exposure/Cinematic truth, RigTray lists the tile when the focused lane advertised a capability, and MulticamController sends SetExposure/SetCinematic only to a camera that advertised them. The rig's photo/video mode is pushed to cameras (SyncMonitorSettings, incl. late joiners) so Cinematic is not refused as photo-mode. ProControlsPanel takes plain values so both monitors share it. Debug console: TrafficEntry carries a reflective MessageDump of the message; tapping a command row unfolds its fields, so a capabilities message shows supportsManualExposure / exposure ranges on the device. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K5uHRuiiLu5FEBiHZkBWo3
… zoom and pro The PRO tile → panel → dials path was two taps too deep. Shutter, ISO, Cinematic and Aperture are now first-class tray tiles (with the camera's current value on each); SHUTTER / ISO / APERTURE open a slider in the zoom pill's slot, Cinematic toggles in place like HDR. The slider IS the zoom control: the ruler, relative drag, scroll wheel, pending-value echo and VoiceOver adjustable element are extracted from ZoomPill into RulerPill, and the log-track math from ZoomScale into LogTrack (ZoomScale wraps it; ZoomScaleTests unchanged and green). ZoomPill and ProSliderPill are thin configurations. Slider sends are throttled with the zoom throttle. Both the 1:1 monitor and the multicam director get the same tiles and slider. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K5uHRuiiLu5FEBiHZkBWo3
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.
Closes #206 — a user asked for the controls a photographer expects from a real camera: shutter speed, ISO, and aperture. This PR adds them to the remote, free for everyone. Design notes live in
Docs/pro-controls.md.What you get
Tray tiles, one tap deep
Open the tray (grid glyph, top-right capsule) and the controls are right there, next to timer, aspect and quality — no sub-menu:
1/125,400) and lights up while Manual is on.f/2.8).If the camera can't do any of this (a Mac's webcam, an older phone, an older app version on the other side), the tiles simply aren't there — the remote never offers a control the camera would ignore. This works on the screen you actually land on: a single camera opens the multicam director, and its tray carries the same tiles, driving the focused camera exactly like torch and zoom.
Sliders that work like zoom
Tap SHUTTER, ISO or APERTURE and the tray closes; that control's slider appears where the zoom pill sits — the same control, not a look-alike: a log-spaced ruler over the camera's range, photographic detents you can feel (1/8000 … 1/125 … 1s; ISO in ⅓-stops; f/1.4 … f/16), relative drag so a small correction stays small, scroll wheel on the Mac, VoiceOver-adjustable. The ruler shows the value you're dragging to and settles on what the camera confirms.
Dragging the shutter locks the shutter and keeps ISO where auto had it; dragging ISO does the reverse. So your first drag engages Manual from a correctly exposed frame instead of a black one. AUTO on the pill hands exposure back to the camera and closes it; × just closes it.
Two protections happen quietly:
The camera phone shows a small chip at the top of its screen (
M 1/125 · ISO 400) so whoever is holding it sees it's under manual control.Cinematic video — simulated aperture (iOS 26)
Turning the CINEMATIC tile on applies Apple's Cinematic effect — shallow depth of field with subject tracking — to both the live preview and the recorded clip, and the APERTURE slider (f/1.4 … f/16) sets how shallow. Tap-to-focus on the remote becomes "track this subject" while the effect is on.
The aperture is set before a take: once recording starts the CINEMATIC and APERTURE tiles dim, because Apple rejects changes mid-clip — we don't pretend otherwise. Leaving video mode turns the effect off.
Debug console: tap a message to see what's in it
The in-app traffic console (ant icon, debug builds) listed message names only. Tapping a command row now unfolds its fields — nested state indented, arrays with counts, shutter durations also shown as fractions — so you can read straight off the device whether a
CameraCapabilitiesRespcarriedsupportsManualExposure: trueand what exposure range the phone reported. It's reflection-based, so every command, including future ones, is inspectable without writing a describer.Nothing to buy
Earlier drafts gated this behind an in-app purchase; that's gone. No new product, no store changes, no unlock screen.
How it's built
SetExposureandSetCinematic, each answered with the camera's actual state. Capabilities gainsupports_manual_exposureandsupports_cinematic_video. Old app versions ignore what they don't know.RulerPill, and its log-track mathLogTrack—ZoomScalewraps it (its tests are unchanged and green),ZoomPillandProSliderPillare thin configurations of the same view. Slider sends are throttled with the zoom throttle.ExposurePolicy,CinematicPolicy): range clamping, the recording cap, aperture lock, video-mode-only Cinematic. No AVFoundation types cross that boundary, so every rule has a table test.activePrimaryConstituent, which Apple documents as nil until the session is running — and capabilities are exchanged before that. The check now reads the constituent lens list, and the same function picks the hop lens, so advertising and behavior can't disagree.MULTICAM_FOR_SINGLE_CAMERA), whose tray rendered nothing for it. The panel now takes plain values, so both screens share it; the director gets its own per-camera routing and capability gate.What I checked
.customexposure support, so the PRO tile stays hidden — the gate works end to end.Still needs a real iPhone
CameraCapabilitiesResprow in the debug console and checksupportsManualExposure/supportsCinematicVideo; on the camera phone the🌗 EXPOSURE PROBElines should show the virtual cameracustom=falseand its lensescustom=true.🤖 Generated with Claude Code
https://claude.ai/code/session_01K5uHRuiiLu5FEBiHZkBWo3