camera: emit previewSize from the first sample buffer's real dims

`device.activeFormat.formatDescription` reports the device's
*selected* format, but on macOS the session-preset remap means the
data output sometimes delivers a different resolution than what
the active format claims. The mismatch surfaced as a stretched
preview on macOS: camera_thumb's SizedBox was sized for a 4:3
buffer, the FittedBox(cover) was given a 16:9 texture, so the
texture stretched to fill the wrongly-shaped box.

PreviewSink now snapshots `CVPixelBufferGet{Width,Height}` from
the first sample buffer that arrives and forwards it via a
callback. CameraInstance hooks the callback to emit the existing
`previewSizeChanged` event (same shape the Android backend uses).
The Dart controller writes it into `value.previewSize`,
camera_thumb's SizedBox snaps to the real buffer aspect, and
FittedBox(cover) crops cleanly without stretching.

Identical wire shape and event name as the Android equivalent —
no Dart changes needed.
This commit is contained in:
agra
2026-05-13 19:20:00 +03:00
parent 8ab672c12a
commit de1a9fd25e
2 changed files with 35 additions and 0 deletions

View File

@@ -132,6 +132,23 @@ final class CameraInstance {
try configureSession(forDeviceUniqueID: cameraId, replacing: false)
// The active-format dims from `device.activeFormat` don't
// always match the buffer the data output actually delivers
// (notably on macOS, where the session preset gets remapped
// mid-pipeline). Forward the real first-frame size to Dart
// so camera_thumb's SizedBox aspect matches the texture's
// actual aspect without this the FittedBox(cover) stretches
// a 16:9 buffer that's been sized as 4:3.
sink.onFirstFrameSize = { [weak self] size in
self?.emit([
"event": "previewSizeChanged",
"previewSize": [
"width": size.width,
"height": size.height,
],
])
}
orientation.start { [weak self] next in
guard let self = self else { return }
self.sessionQueue.async { self.applyOrientationFollowDevice(next) }