CameraView fix for Android: video recording and rotation#3168
CameraView fix for Android: video recording and rotation#3168zhitaop wants to merge 6 commits intoCommunityToolkit:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Android CameraManager implementation to improve repeated video recording reliability and to apply device rotation consistently across CameraX use cases (preview, image capture, and video capture).
Changes:
- Keeps
VideoCapture/Recorderalive across recording sessions by stopping disposal inCleanupVideoRecordingResources(). - Rebuilds video recording use cases on-demand in
PlatformStartVideoRecording()when they are missing (e.g., after a resolution change). - Centralizes orientation-to-surface-rotation mapping and applies it to
imageCapture,videoCapture, andcameraPreview.
| imageCapture?.TargetRotation = targetRotation; | ||
| videoCapture?.TargetRotation = targetRotation; | ||
| cameraPreview?.TargetRotation = targetRotation; |
There was a problem hiding this comment.
OnOrientationChanged sets TargetRotation on imageCapture/videoCapture/cameraPreview, but StartUseCase disposes these use cases without first setting the fields to null. If an orientation event fires between Dispose() and the subsequent re-creation, this can end up calling TargetRotation on a disposed Java object and throw. Consider setting the fields to null immediately after disposing in StartUseCase (or temporarily disabling the OrientationListener during rebuild) so the callback never observes disposed instances.
| imageCapture?.TargetRotation = targetRotation; | |
| videoCapture?.TargetRotation = targetRotation; | |
| cameraPreview?.TargetRotation = targetRotation; | |
| try | |
| { | |
| if (imageCapture is not null && imageCapture.Handle != IntPtr.Zero) | |
| { | |
| imageCapture.TargetRotation = targetRotation; | |
| } | |
| if (videoCapture is not null && videoCapture.Handle != IntPtr.Zero) | |
| { | |
| videoCapture.TargetRotation = targetRotation; | |
| } | |
| if (cameraPreview is not null && cameraPreview.Handle != IntPtr.Zero) | |
| { | |
| cameraPreview.TargetRotation = targetRotation; | |
| } | |
| } | |
| catch (ObjectDisposedException) | |
| { | |
| // Ignore orientation updates for disposed camera use cases. | |
| } |
| { | ||
| if (videoCapture is null || videoRecorder is null) | ||
| { | ||
| await StartUseCase(token); |
There was a problem hiding this comment.
PlatformStartVideoRecording can call StartUseCase(token) to recreate imageCapture/videoCapture/cameraPreview. Because target rotation is only applied from OnOrientationChanged, newly recreated use cases will keep the default TargetRotation until the device orientation changes again (which may never happen), leading to incorrect rotation after a rebuild (e.g., after a resolution change). Consider applying an initial TargetRotation immediately after rebuilding (e.g., from previewView.Display?.Rotation / current display rotation, or cache the last computed target rotation and reapply it).
| await StartUseCase(token); | |
| await StartUseCase(token); | |
| if (previewView is not null) | |
| { | |
| var display = previewView.Display; | |
| if (display is not null) | |
| { | |
| var rotation = (int)display.Rotation; | |
| if (imageCapture is not null) | |
| { | |
| imageCapture.TargetRotation = rotation; | |
| } | |
| if (videoCapture is not null) | |
| { | |
| videoCapture.TargetRotation = rotation; | |
| } | |
| if (cameraPreview is not null) | |
| { | |
| cameraPreview.TargetRotation = rotation; | |
| } | |
| } | |
| } |
| void OnOrientationChanged(int rotation) | ||
| { | ||
| if (imageCapture is not null) | ||
| { | ||
| imageCapture.TargetRotation = rotation switch | ||
| { | ||
| >= 45 and < 135 => (int)SurfaceOrientation.Rotation270, | ||
| >= 135 and < 225 => (int)SurfaceOrientation.Rotation180, | ||
| >= 225 and < 315 => (int)SurfaceOrientation.Rotation90, | ||
| _ => (int)SurfaceOrientation.Rotation0 | ||
| }; | ||
| } | ||
| var targetRotation = GetSurfaceRotation(rotation); | ||
|
|
There was a problem hiding this comment.
The OnOrientationChanged(int rotation) parameter is receiving raw orientation degrees from OrientationEventListener, not a surface rotation value. Renaming the parameter (e.g., to orientationDegrees) would avoid confusion with the computed targetRotation (surface rotation) used for TargetRotation assignments.
Description of Change
This PR fixes a bug on Android where attempting to record a video for a second time will fail:
CleanupVideoRecordingResources()no longer disposesvideoRecorderandvideoCaptureafter each recording session. This prevents the "Null Stream" error caused by trying to record with a disposed object. It also avoids the heavy overhead of rebinding the camera lifecycle every time a new video recording is started.In
PlatformStartVideoRecording(), ifvideoCaptureorvideoRecorderis null (e.g. due to a resolution change), it callsStartUseCase()to rebuild them.Improves orientation handling by applying rotation consistently to
imageCapture,videoCapture, andcameraPreview.Linked Issues
PR Checklist
approved(bug) orChampioned(feature/proposal)mainat time of PRAdditional information