Skip to content

iOS: video output outputOrientation reports the set value, not connection state; currentResolution returns undefined #4178

Description

@wrieljsantos

What's happening?

On iOS, CameraVideoOutput.outputOrientation reports the value that was last set, not the connection's actual orientation — so it cannot be used to find out how a recording was oriented. Separately, CameraVideoOutput.currentResolution returns undefined for me even while the session is running.

1. outputOrientation is effectively write-only on a video output

ios/Hybrid Objects/Outputs/HybridCameraVideoOutput.swift:

var outputOrientation: CameraOrientation = .up {
  didSet {
    guard let connection = output.connection(with: .video) else { return }
    try? connection.setOrientation(outputOrientation)
  }
}

It is a stored property whose didSet writes to the connection. Reading it returns the stored value, never the connection state.

The preview output does implement the getter properly, which is what makes this look like an oversight rather than a deliberate choice — HybridCameraPreviewOutput.swift:

var outputOrientation: CameraOrientation {
  get { return previewLayer.connection?.orientation ?? .up }
  set { /* ... */ }
}

AVCaptureConnection.orientation already exists in this repo (AVCaptureConnection+orientation.swift), so the video output could use the same accessor.

I want to be precise about what this would and would not fix: since configure(config:) already calls connection.setOrientation(outputOrientation) with the default .up, a corrected getter would return .up — the connection genuinely is .portrait. It would make the getter honest, but it still would not tell a caller how the resulting file is oriented, which is what I was actually after (see below).

2. currentResolution returns undefined

var currentResolution: Size? {
  guard let connection = output.connection(with: .video) else { return nil }
  return connection.inputStreamResolution
}

I read it both immediately after await session.start() and again at stop time; it was undefined both times, on a running session that was recording successfully. configure(config:) uses the same output.connection(with: .video) lookup and clearly succeeds, since orientation and mirroring are applied — so I have not been able to work out the cause and am reporting it as an observation rather than a diagnosis.

This matters because it is the only way to detect a resolution substitution. Requesting targetResolution: 3024x4224 with a { fps: 120 } constraint was accepted, and the session recorded 3840x2160 instead. Without currentResolution there is no way to learn that from the API.

Context: what I was trying to do

Report the true rotation and dimensions of a finished recording. Rotation turns out to be format-dependent — the same portrait-locked app produced:

Camera Config Stored Rotation Presents
front 1080x1920 @ 30fps 1080x1920 none portrait
rear 4K @ 120fps 3840x2160 -90 portrait

Both present portrait, but only one carries a transform, so no single assumption is correct and no connection-level read reports it. I ended up deriving rotation server-side with ffprobe instead, which works fine — but a recorder-level "what did the finished file actually get" (resolution + rotation) would remove the need.

Environment

  • react-native-vision-camera 5.2.2
  • react-native-nitro-modules 0.36.5
  • react-native 0.81.5, New Architecture, bridgeless
  • Expo SDK 54 (expo 54.0.36), dev client
  • Xcode 26.6, iOS 27.0, iPhone 17 Pro Max (physical device)

Ground truth in the table above is ffprobe on the files pulled off the device.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions