Device formats
Each AVCaptureDevice advertises an array of AVCaptureDeviceFormats — one entry per capture mode the hardware supports (1080p30, 4K60, 720p binned, spatial video, etc.). Picking the right format is how you control resolution, frame rate, HDR, multi-cam compatibility, stabilization, and a handful of front-camera effects (Center Stage, Studio Light, Portrait).
The session preset (session.sessionPreset = "photo") is a coarse alias that asks the system to pick a reasonable format. Switching the active format yourself locks the device into exact dimensions and frame rates.
The wrapper, briefly
You never construct one — obtain instances from device.formats or device.activeFormat.
Identity is stable
Each device wrapper hands out the same AVCaptureDeviceFormat instance for the same underlying format every time. So:
That lets you do formats.indexOf(device.activeFormat), compare with ===, or stash a chosen format and reuse it later. The same underlying format obtained from two different AVCaptureDevice instances is two different JS wrappers, though — only meaningful if you build two device objects on purpose.
Choosing a format
Standard pattern: filter device.formats, pick by your own rules, hand the winner to setActiveFormat.
A few common filters worth knowing:
setActiveFormat rules
- The format must come from this same device's
formatsarray. Passing a format obtained from another device throws — the check happens before any configuration lock, so a stray format won't take the camera down with it. setActiveFormatperforms the change inside its own configuration lock. For a batch of related changes (format + color space + frame rate clamp), wrap the whole block indevice.lockForConfiguration()/device.unlockForConfiguration()yourself so the camera only re-negotiates once.- Changing format while a session is running is fine; connections are re-negotiated automatically. Sample-buffer outputs may briefly drop frames during the switch.
Active color space
The active format dictates which color spaces are possible; device.activeColorSpace and device.setActiveColorSpace(value) choose among them.
setActiveColorSpace throws if the value isn't in activeFormat.supportedColorSpaces. Switching format may change which color spaces are valid; re-check after setActiveFormat.
Locking frame rate (setActiveVideoMin/MaxFrameDuration)
The active format publishes a range of supported frame rates — e.g. 1–60 fps. Use the two clamp setters to pin where in that range the camera actually runs:
device.setActiveVideoMinFrameDuration(seconds)— shorter duration ⇒ higher maximum fps. Pass1 / 60to cap at 60 fps.device.setActiveVideoMaxFrameDuration(seconds)— longer duration ⇒ lower minimum fps. Pass1 / 24to floor at 24 fps.
Both setters validate the seconds value against activeFormat.videoSupportedFrameRateRanges and throw if it falls outside every range. The getters return 0 when the device hasn't been clamped (the format's natural range is in effect).
Field gotchas
width/height/fieldOfVieware0on non-video formats (audio, metadata) — they have no video dimensions. Filter tomediaType === "vide"if you only care about cameras.videoSupportedFrameRateRangescan have multiple entries — a single format may support, e.g., 1–30 fps and 60 fps in two separate ranges. Don't assume the array has one element.videoMaxZoomFactoris the hardware ceiling.videoZoomFactorUpscaleThresholdis the point above which the system starts digital upscaling instead of staying within the sensor's optical range.isHighestPhotoQualitySupported/isHighPhotoQualitySupporteddescribe photo quality tiers — they affectphotoOutput.maxPhotoQualityPrioritization, not video.isCenterStageSupportedetc. report per-format support. The global toggle that turns these effects on system-wide is separate; on formats where the per-format flag isfalse, the system-wide toggle has no effect for this device.
