camera: per-platform capture-orientation extension + macOS sensor=0

macOS preview was stretching (aspect wrong) and macOS photo capture
was rotating the landscape sensor 90° because the shared
PhotoOutput / CameraInstance code was setting
`AVCaptureConnection.videoOrientation` from the orientation snapshot
unconditionally. iOS needs that to rotate sample buffers to portrait;
macOS desktop cams are physically landscape and any rotation just
skews the result.

Moved the rotation call behind a per-platform extension on
`AVCaptureConnection`:
  - `ios/Classes/Camera/AVCaptureConnection+iOS.swift` applies the
    snapshot orientation (current behavior).
  - `macos/Classes/Camera/AVCaptureConnection+macOS.swift` is a
    no-op. macOS-flavoured photos / preview frames now flow at
    native landscape orientation.

`CaptureDevice` reports sensorOrientation=0 on macOS (was hardcoded
90 for iOS); on macOS the page's `normalizeCameraCapture` math then
collapses to identity and the saved JPEG stays the landscape the
sensor produced. iOS keeps sensorOrientation=90 (matches
camera_avfoundation's reported value and the existing capture-
transform math).

Photo and video paths now both produce upright content on macOS
(video already worked because VideoRecorder's transform table maps
the always-portraitUp macOS snapshot to `.identity`).
This commit is contained in:
agra
2026-05-13 19:07:29 +03:00
parent a6d2539722
commit 8ab672c12a
5 changed files with 62 additions and 26 deletions

View File

@@ -34,9 +34,12 @@ final class PhotoOutput {
)))
return
}
if connection.isVideoOrientationSupported {
connection.videoOrientation = orientation.avVideoOrientation
}
// Rotation handled per-platform: iOS applies the snapshot
// orientation to the connection (which rotates the captured
// JPEG); macOS is a no-op (desktop cams are physically
// landscape, any rotation skews the photo). See
// `AVCaptureConnection+iOS.swift` / `+macOS.swift`.
connection.applyUxCaptureOrientation(orientation)
// The recorded photo carries no mirror; mirroring is a
// preview-only concern.
if connection.isVideoMirroringSupported {
@@ -57,12 +60,12 @@ final class PhotoOutput {
}
let delegate = PhotoCaptureDelegate { [weak self] result in
// Reset orientation on the photo connection so a future
// capture without a snapshot defaults to portrait.
if let conn = self?.avOutput.connection(with: .video),
conn.isVideoOrientationSupported {
conn.videoOrientation = .portrait
}
// Reset orientation back to portraitUp on the photo
// connection so a follow-up shot without an explicit
// snapshot defaults cleanly. No-op on macOS (the
// extension method is empty there).
self?.avOutput.connection(with: .video)?
.applyUxCaptureOrientation(.portraitUp)
self?.inFlight = nil
DispatchQueue.main.async { completion(result) }
}