@@ -96,13 +96,28 @@ class Camera
96
96
* Holds all of the camera features/settings and will be used to update the request builder when
97
97
* one changes.
98
98
*/
99
- private final CameraFeatures cameraFeatures ;
99
+ private CameraFeatures cameraFeatures ;
100
+
101
+ private String imageFormatGroup ;
102
+
103
+ /**
104
+ * Takes an input/output surface and orients the recording correctly. This is needed because
105
+ * switching cameras while recording causes the wrong orientation.
106
+ */
107
+ private VideoRenderer videoRenderer ;
108
+
109
+ /**
110
+ * Whether or not the camera aligns with the initial way the camera was facing if the camera was
111
+ * flipped.
112
+ */
113
+ private int initialCameraFacing ;
100
114
101
115
private final SurfaceTextureEntry flutterTexture ;
116
+ private final ResolutionPreset resolutionPreset ;
102
117
private final boolean enableAudio ;
103
118
private final Context applicationContext ;
104
119
private final DartMessenger dartMessenger ;
105
- private final CameraProperties cameraProperties ;
120
+ private CameraProperties cameraProperties ;
106
121
private final CameraFeatureFactory cameraFeatureFactory ;
107
122
private final Activity activity ;
108
123
/** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */
@@ -192,6 +207,7 @@ public Camera(
192
207
this .applicationContext = activity .getApplicationContext ();
193
208
this .cameraProperties = cameraProperties ;
194
209
this .cameraFeatureFactory = cameraFeatureFactory ;
210
+ this .resolutionPreset = resolutionPreset ;
195
211
this .cameraFeatures =
196
212
CameraFeatures .init (
197
213
cameraFeatureFactory , cameraProperties , activity , dartMessenger , resolutionPreset );
@@ -232,6 +248,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {
232
248
if (mediaRecorder != null ) {
233
249
mediaRecorder .release ();
234
250
}
251
+ closeRenderer ();
235
252
236
253
final PlatformChannel .DeviceOrientation lockedOrientation =
237
254
cameraFeatures .getSensorOrientation ().getLockedCaptureOrientation ();
@@ -259,6 +276,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {
259
276
260
277
@ SuppressLint ("MissingPermission" )
261
278
public void open (String imageFormatGroup ) throws CameraAccessException {
279
+ this .imageFormatGroup = imageFormatGroup ;
262
280
final ResolutionFeature resolutionFeature = cameraFeatures .getResolution ();
263
281
264
282
if (!resolutionFeature .checkIsSupported ()) {
@@ -303,14 +321,17 @@ public void onOpened(@NonNull CameraDevice device) {
303
321
cameraDevice = new DefaultCameraDeviceWrapper (device );
304
322
try {
305
323
startPreview ();
324
+ if (!recordingVideo ) // only send initialization if we werent already recording and switching cameras
306
325
dartMessenger .sendCameraInitializedEvent (
307
- resolutionFeature .getPreviewSize ().getWidth (),
308
- resolutionFeature .getPreviewSize ().getHeight (),
309
- cameraFeatures .getExposureLock ().getValue (),
310
- cameraFeatures .getAutoFocus ().getValue (),
311
- cameraFeatures .getExposurePoint ().checkIsSupported (),
312
- cameraFeatures .getFocusPoint ().checkIsSupported ());
313
- } catch (CameraAccessException e ) {
326
+ resolutionFeature .getPreviewSize ().getWidth (),
327
+ resolutionFeature .getPreviewSize ().getHeight (),
328
+ cameraFeatures .getExposureLock ().getValue (),
329
+ cameraFeatures .getAutoFocus ().getValue (),
330
+ cameraFeatures .getExposurePoint ().checkIsSupported (),
331
+ cameraFeatures .getFocusPoint ().checkIsSupported ());
332
+
333
+ } catch (Exception e ) {
334
+ Log .i (TAG , "open | onOpened error: " + e .getMessage ());
314
335
dartMessenger .sendCameraErrorEvent (e .getMessage ());
315
336
close ();
316
337
}
@@ -320,7 +341,8 @@ public void onOpened(@NonNull CameraDevice device) {
320
341
public void onClosed (@ NonNull CameraDevice camera ) {
321
342
Log .i (TAG , "open | onClosed" );
322
343
323
- // Prevents calls to methods that would otherwise result in IllegalStateException exceptions.
344
+ // Prevents calls to methods that would otherwise result in IllegalStateException
345
+ // exceptions.
324
346
cameraDevice = null ;
325
347
closeCaptureSession ();
326
348
dartMessenger .sendCameraClosingEvent ();
@@ -735,7 +757,7 @@ public void startVideoRecording(
735
757
if (imageStreamChannel != null ) {
736
758
setStreamHandler (imageStreamChannel );
737
759
}
738
-
760
+ initialCameraFacing = cameraProperties . getLensFacing ();
739
761
recordingVideo = true ;
740
762
try {
741
763
startCapture (true , imageStreamChannel != null );
@@ -747,6 +769,13 @@ public void startVideoRecording(
747
769
}
748
770
}
749
771
772
+ private void closeRenderer () {
773
+ if (videoRenderer != null ) {
774
+ videoRenderer .close ();
775
+ videoRenderer = null ;
776
+ }
777
+ }
778
+
750
779
public void stopVideoRecording (@ NonNull final Result result ) {
751
780
if (!recordingVideo ) {
752
781
result .success (null );
@@ -757,6 +786,7 @@ public void stopVideoRecording(@NonNull final Result result) {
757
786
cameraFeatureFactory .createAutoFocusFeature (cameraProperties , false ));
758
787
recordingVideo = false ;
759
788
try {
789
+ closeRenderer ();
760
790
captureSession .abortCaptures ();
761
791
mediaRecorder .stop ();
762
792
} catch (CameraAccessException | IllegalStateException e ) {
@@ -765,7 +795,7 @@ public void stopVideoRecording(@NonNull final Result result) {
765
795
mediaRecorder .reset ();
766
796
try {
767
797
startPreview ();
768
- } catch (CameraAccessException | IllegalStateException e ) {
798
+ } catch (CameraAccessException | IllegalStateException | InterruptedException e ) {
769
799
result .error ("videoRecordingFailed" , e .getMessage (), null );
770
800
return ;
771
801
}
@@ -1049,13 +1079,50 @@ public void resumePreview() {
1049
1079
null , (code , message ) -> dartMessenger .sendCameraErrorEvent (message ));
1050
1080
}
1051
1081
1052
- public void startPreview () throws CameraAccessException {
1082
+ public void startPreview () throws CameraAccessException , InterruptedException {
1083
+ // If recording is already in progress, the camera is being flipped, so send it through the VideoRenderer to keep the correct orientation.
1084
+ if (recordingVideo ) {
1085
+ startPreviewWithVideoRendererStream ();
1086
+ } else {
1087
+ startRegularPreview ();
1088
+ }
1089
+ }
1090
+
1091
+ private void startRegularPreview () throws CameraAccessException {
1053
1092
if (pictureImageReader == null || pictureImageReader .getSurface () == null ) return ;
1054
1093
Log .i (TAG , "startPreview" );
1055
-
1056
1094
createCaptureSession (CameraDevice .TEMPLATE_PREVIEW , pictureImageReader .getSurface ());
1057
1095
}
1058
1096
1097
+ private void startPreviewWithVideoRendererStream ()
1098
+ throws CameraAccessException , InterruptedException {
1099
+ if (videoRenderer == null ) return ;
1100
+
1101
+ // get rotation for rendered video
1102
+ final PlatformChannel .DeviceOrientation lockedOrientation =
1103
+ cameraFeatures .getSensorOrientation ().getLockedCaptureOrientation ();
1104
+ DeviceOrientationManager orientationManager =
1105
+ cameraFeatures .getSensorOrientation ().getDeviceOrientationManager ();
1106
+
1107
+ int rotation = 0 ;
1108
+ if (orientationManager != null ) {
1109
+ rotation =
1110
+ lockedOrientation == null
1111
+ ? orientationManager .getVideoOrientation ()
1112
+ : orientationManager .getVideoOrientation (lockedOrientation );
1113
+ }
1114
+
1115
+ if (cameraProperties .getLensFacing () != initialCameraFacing ) {
1116
+
1117
+ // If the new camera is facing the opposite way than the initial recording,
1118
+ // the rotation should be flipped 180 degrees.
1119
+ rotation = (rotation + 180 ) % 360 ;
1120
+ }
1121
+ videoRenderer .setRotation (rotation );
1122
+
1123
+ createCaptureSession (CameraDevice .TEMPLATE_RECORD , videoRenderer .getInputSurface ());
1124
+ }
1125
+
1059
1126
public void startPreviewWithImageStream (EventChannel imageStreamChannel )
1060
1127
throws CameraAccessException {
1061
1128
setStreamHandler (imageStreamChannel );
@@ -1179,17 +1246,7 @@ private void closeCaptureSession() {
1179
1246
public void close () {
1180
1247
Log .i (TAG , "close" );
1181
1248
1182
- if (cameraDevice != null ) {
1183
- cameraDevice .close ();
1184
- cameraDevice = null ;
1185
-
1186
- // Closing the CameraDevice without closing the CameraCaptureSession is recommended
1187
- // for quickly closing the camera:
1188
- // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close()
1189
- captureSession = null ;
1190
- } else {
1191
- closeCaptureSession ();
1192
- }
1249
+ stopAndReleaseCamera ();
1193
1250
1194
1251
if (pictureImageReader != null ) {
1195
1252
pictureImageReader .close ();
@@ -1208,6 +1265,75 @@ public void close() {
1208
1265
stopBackgroundThread ();
1209
1266
}
1210
1267
1268
+ private void stopAndReleaseCamera () {
1269
+ if (cameraDevice != null ) {
1270
+ cameraDevice .close ();
1271
+ cameraDevice = null ;
1272
+
1273
+ // Closing the CameraDevice without closing the CameraCaptureSession is recommended
1274
+ // for quickly closing the camera:
1275
+ // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close()
1276
+ captureSession = null ;
1277
+ } else {
1278
+ closeCaptureSession ();
1279
+ }
1280
+ }
1281
+
1282
+ private void prepareVideoRenderer () {
1283
+ if (videoRenderer != null ) return ;
1284
+ final ResolutionFeature resolutionFeature = cameraFeatures .getResolution ();
1285
+
1286
+ // handle videoRenderer errors
1287
+ Thread .UncaughtExceptionHandler videoRendererUncaughtExceptionHandler =
1288
+ new Thread .UncaughtExceptionHandler () {
1289
+ @ Override
1290
+ public void uncaughtException (Thread thread , Throwable ex ) {
1291
+ dartMessenger .sendCameraErrorEvent (
1292
+ "Failed to process frames after camera was flipped." );
1293
+ }
1294
+ };
1295
+
1296
+ videoRenderer =
1297
+ new VideoRenderer (
1298
+ mediaRecorder .getSurface (),
1299
+ resolutionFeature .getCaptureSize ().getWidth (),
1300
+ resolutionFeature .getCaptureSize ().getHeight (),
1301
+ videoRendererUncaughtExceptionHandler );
1302
+ }
1303
+
1304
+ public void setDescriptionWhileRecording (
1305
+ @ NonNull final Result result , CameraProperties properties ) {
1306
+
1307
+ if (!recordingVideo ) {
1308
+ result .error ("setDescriptionWhileRecordingFailed" , "Device was not recording" , null );
1309
+ return ;
1310
+ }
1311
+
1312
+ // See VideoRenderer.java requires API 26 to switch camera while recording
1313
+ if (android .os .Build .VERSION .SDK_INT < android .os .Build .VERSION_CODES .O ) {
1314
+ result .error (
1315
+ "setDescriptionWhileRecordingFailed" ,
1316
+ "Device does not support switching the camera while recording" ,
1317
+ null );
1318
+ return ;
1319
+ }
1320
+
1321
+ stopAndReleaseCamera ();
1322
+ prepareVideoRenderer ();
1323
+ cameraProperties = properties ;
1324
+ cameraFeatures =
1325
+ CameraFeatures .init (
1326
+ cameraFeatureFactory , cameraProperties , activity , dartMessenger , resolutionPreset );
1327
+ cameraFeatures .setAutoFocus (
1328
+ cameraFeatureFactory .createAutoFocusFeature (cameraProperties , true ));
1329
+ try {
1330
+ open (imageFormatGroup );
1331
+ } catch (CameraAccessException e ) {
1332
+ result .error ("setDescriptionWhileRecordingFailed" , e .getMessage (), null );
1333
+ }
1334
+ result .success (null );
1335
+ }
1336
+
1211
1337
public void dispose () {
1212
1338
Log .i (TAG , "dispose" );
1213
1339
0 commit comments