camera: Android preview rotation stays at 0 (texture transform applies)

Flutter's `Texture` widget on Android samples the underlying
`SurfaceTexture` with its transform matrix applied (the GL
`GL_TEXTURE_EXTERNAL_OES` sampler reads
`SurfaceTexture.getTransformMatrix()` each frame), and CameraX
populates that matrix with the rotation needed for upright display.
A `RotatedBox` on top of that double-applies — manifested as the
preview being 90° CW from where it should be.

So `CameraInstance.previewRotationQuarterTurns` is hard-coded to 0
on Android. The wire field stays (iOS always emits 0 too because
AVCaptureConnection.videoOrientation pre-rotates the sample
buffers); future preview pipelines that bypass the SurfaceTexture
transform would set it to a non-zero value.
This commit is contained in:
agra
2026-05-13 17:52:33 +03:00
parent cc243b7b0a
commit f78dd4d846

View File

@@ -46,18 +46,17 @@ class CameraInstance(
val currentLens: String get() = lens
val audioClaimed: Boolean get() = enableAudio
/// Number of 90° CW rotations the Flutter Texture needs so the
/// sensor frames display upright on a portrait screen. Derived
/// from the active camera's `sensorRotationDegrees`.
val previewRotationQuarterTurns: Int get() {
val degrees = cameraProvider?.let {
val selector = if (lens == "front") CameraSelector.DEFAULT_FRONT_CAMERA
else CameraSelector.DEFAULT_BACK_CAMERA
selector.filter(it.availableCameraInfos).firstOrNull()
?.sensorRotationDegrees ?: 0
} ?: 0
return ((degrees % 360 + 360) % 360) / 90
}
/// Always 0 on Android: Flutter's `Texture` widget samples the
/// underlying `SurfaceTexture` with its transform matrix applied
/// (`GL_TEXTURE_EXTERNAL_OES` sampling reads
/// `SurfaceTexture.getTransformMatrix()`), and CameraX populates
/// that matrix with the rotation needed for upright display. Any
/// `RotatedBox` on top of that would double-apply the rotation.
/// The field stays in the wire protocol for iOS' sake (always 0
/// there too, since AVCaptureConnection pre-rotates the sample
/// buffers) and as a future hook if a non-CameraX preview path
/// ever lands.
val previewRotationQuarterTurns: Int get() = 0
/// Bind the provider against [lens]. Resolves the
/// [ProcessCameraProvider.getInstance] future on the main executor