camera: pin macOS photo connection to .landscapeRight

`AVCaptureVideoDataOutput`'s connection on macOS doesn't honor
`videoOrientation` (or its `isVideoOrientationSupported` is false) —
which is why the preview + recorded video were landscape and looked
fine even with our previously-no-op extension. `AVCapturePhotoOutput`'s
connection on macOS *does* honor it, and its default is `.portrait`
— same as iOS — so leaving it untouched rotated the captured JPEG 90°
CW relative to the landscape sensor.

The extension now sets `.landscapeRight` unconditionally (guarded by
`isVideoOrientationSupported`, so on the data output it's a no-op):
photo connection pins to landscape, JPEG EXIF orientation = 1 (no
rotation), captured image matches the preview.

Video + preview already correct → unaffected.
This commit is contained in:
agra
2026-05-13 19:40:51 +03:00
parent 8bed5435ad
commit f0a7f0b3a1

View File

@@ -2,16 +2,21 @@ import AVFoundation
/// macOS counterpart of `AVCaptureConnection+iOS.swift`.
///
/// macOS desktop cameras are physically fixed landscape applying any
/// `AVCaptureVideoOrientation` would skew the captured photo (and on
/// some macOS versions the preview's data-output buffers) by 90°.
/// The orientation snapshot from Flutter (always `portraitUp` on
/// macOS desktops don't rotate) is therefore ignored at the
/// connection layer; the recorded video's track transform is still
/// `.identity` from VideoRecorder's existing mapping, so video stays
/// landscape too.
/// macOS desktop cameras are physically fixed landscape. AVFoundation
/// initialises `connection.videoOrientation` to `.portrait` by
/// default though same as iOS which on macOS *also* rotates the
/// sensor frame 90° CW (manifested as a 90° CW selfie photo + a
/// rotated preview). So we *do* set the orientation on macOS, but
/// pin it to `.landscapeRight` regardless of the snapshot Flutter
/// passes (which is always `portraitUp` on macOS desktops don't
/// rotate). That tells AVFoundation "leave the sensor frame alone";
/// captured JPEG ends up landscape with EXIF orientation = 1 (no
/// rotation), and preview data-output buffers flow at the sensor's
/// native aspect.
extension AVCaptureConnection {
func applyUxCaptureOrientation(_ orientation: DeviceOrientationFlutter) {
// Intentionally empty.
if isVideoOrientationSupported {
videoOrientation = .landscapeRight
}
}
}