Webcam animations & camera regions
Camera regions mark segments of the timeline where the webcam changes state: fullscreen, hidden, or a custom PiP layout. Each boundary can have its own animated transition, rendered by the compositor at export.
Three camera states
Outside any defined region the webcam renders using the global PiP layout from the editor properties panel. Regions override that global state for their duration.
Fullscreen
The webcam fills the whole canvas for the region's duration. For talking-head segments where you replace the screen recording with the speaker.
Hidden
The webcam disappears for the region's duration, even if PiP is on globally. For segments where the webcam would be distracting.
Custom
A PiP overlay with layout and style that override the global defaults. Each custom region gets its own position, size, aspect ratio, corner radius, border, shadow, and flip.
CameraRegionData structure
CameraRegionData {
id: UUID
startSeconds: Double
endSeconds: Double
type: .fullscreen | .hidden | .custom
// Custom layout overrides (nil = use global defaults)
customLayout: CameraLayout? // position, size
customCameraAspect: CameraAspect? // original, 1:1, 4:3, 16:9, 9:16
customCornerRadius: CGFloat?
customShadow: CGFloat?
customBorderWidth: CGFloat?
customBorderColor: CodableColor?
customMirrored: Bool?
// Transitions
entryTransition: CameraTransitionType?
entryTransitionDuration:Double? // seconds
exitTransition: CameraTransitionType?
exitTransitionDuration: Double?
}Fade, scale & slide
Every region boundary can have its own transition type and duration. Entry and exit are set separately, so a region can fade in and slide out.
Fade
Opacity goes from 0 to 1 on entry and 1 to 0 on exit. Simple and works at any speed.
Scale
Scales from nothing to full size on entry, shrinks back on exit. For fullscreen-to-PiP transitions, the region morphs between both rects instead of just scaling from center.
Slide
Slides in from below the canvas on entry, slides back down on exit. The distance is the full camera height so it starts and ends off-screen.
None
Hard cut with no animation. For fast-paced content or when the edit around it already provides context.
Progress computation
// elapsed = time since region start // remaining = time until region end if elapsed < entryDuration: progress = smoothstep(elapsed / entryDuration) // 0 → 1 elif remaining < exitDuration: progress = smoothstep(remaining / exitDuration) // 1 → 0 else: progress = 1.0 // hold smoothstep(t) = t³ × (6t² − 15t + 10)
Effect applied per transition type
Fade: context.setAlpha(progress) Scale (PiP): scale at camera center: context.scaleBy(x: progress, y: progress) Scale (Fullscreen ↔ PiP): rect = lerp(fullscreenRect, pipRect, progress) cornerRadius = lerp(0, pipCornerRadius, progress) Slide: slideY = cameraRect.height × (1 − progress) context.translateBy(x: 0, y: −slideY)
How transitions are composed
All transition logic runs in CameraVideoCompositor, a custom AVVideoCompositing called once per output frame. No pre-processing pass; everything is computed and drawn inline.
Independent Entry & Exit Durations
Entry and exit transitions can use different types and different durations on the same region. For example, a fast fade-in followed by a slow slide-out.
Smoothstep Easing on All Transitions
All progress values go through smoothstep (t³(6t²−15t+10)) for ease-in-out with zero velocity at both ends.
Hold Phase Between Transitions
The region is fully active between the end of its entry transition and the start of its exit transition. During the hold phase the progress value is 1.0 and no interpolation is applied.
Per-region Styling During Transitions
Corner radius, border width, and other properties animate along with position and opacity. For scale transitions, corner radius interpolates proportionally with the scale factor.
Frame rendering order
Each exported frame goes through a fixed five-stage pipeline in CameraVideoCompositor.renderFrame(). Camera regions and their transitions only affect stage 5 — the webcam layer — leaving background, video, and cursor unmodified.
Per-frame Region Lookup
CameraVideoCompositor evaluates the region list on every frame. It binary-searches the active region for the current composition timestamp and computes the transition progress before any drawing begins.
Rendering Order
Each frame is composited in a fixed order: background first, then the screen video (with zoom applied), then the cursor overlay, and finally the webcam. Camera regions only affect the webcam layer.
Fullscreen → PiP Scale Morph
At a fullscreen/PiP boundary with a scale transition, the compositor interpolates the camera rect from fullscreen bounds to PiP rect. Position and size change together.
Aspect Ratio Preservation
The webcam is always aspect-filled into its destination rect. During transitions that rect is the interpolated intermediate, so aspect fill stays correct throughout.
Per-frame pipeline
renderFrame(at compositionTime):
1. drawBackground() // solid color / gradient / image
2. drawScreenVideo() // source video cropped by zoom rect
3. drawCursorOverlay() // cursor + click highlights
4. lookupActiveRegion(at time) // binary search through regions
5. drawWebcam():
if region == .hidden: skip (with exit transition if leaving)
if region == .fullscreen: fill canvas (with entry/exit transitions)
if region == .custom: render PiP at custom layout
if no region: render PiP at global layoutCamera track in the editor
Camera regions live on a dedicated track in the timeline. The preview updates live, so you can scrub through transitions before exporting.
Dedicated Camera Track
Sits below the video and audio tracks.
Add Regions by Double-clicking
Double-click empty space to insert a fullscreen region. It extends to the next region or the end of the video.
Drag to Move and Resize
Drag to reposition, drag the edges to resize. Regions can't overlap their neighbors.
Edit Popover
Right-click (or click edit) to open a popover where you change the region type, set transitions and durations, and override styling for custom regions.