@@ -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,16 @@ 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 (CameraAccessException | InterruptedException e ) {
314
334
dartMessenger .sendCameraErrorEvent (e .getMessage ());
315
335
close ();
316
336
}
@@ -320,7 +340,8 @@ public void onOpened(@NonNull CameraDevice device) {
320
340
public void onClosed (@ NonNull CameraDevice camera ) {
321
341
Log .i (TAG , "open | onClosed" );
322
342
323
- // Prevents calls to methods that would otherwise result in IllegalStateException exceptions.
343
+ // Prevents calls to methods that would otherwise result in IllegalStateException
344
+ // exceptions.
324
345
cameraDevice = null ;
325
346
closeCaptureSession ();
326
347
dartMessenger .sendCameraClosingEvent ();
@@ -735,7 +756,7 @@ public void startVideoRecording(
735
756
if (imageStreamChannel != null ) {
736
757
setStreamHandler (imageStreamChannel );
737
758
}
738
-
759
+ initialCameraFacing = cameraProperties . getLensFacing ();
739
760
recordingVideo = true ;
740
761
try {
741
762
startCapture (true , imageStreamChannel != null );
@@ -747,6 +768,13 @@ public void startVideoRecording(
747
768
}
748
769
}
749
770
771
+ private void closeRenderer () {
772
+ if (videoRenderer != null ) {
773
+ videoRenderer .close ();
774
+ videoRenderer = null ;
775
+ }
776
+ }
777
+
750
778
public void stopVideoRecording (@ NonNull final Result result ) {
751
779
if (!recordingVideo ) {
752
780
result .success (null );
@@ -757,6 +785,7 @@ public void stopVideoRecording(@NonNull final Result result) {
757
785
cameraFeatureFactory .createAutoFocusFeature (cameraProperties , false ));
758
786
recordingVideo = false ;
759
787
try {
788
+ closeRenderer ();
760
789
captureSession .abortCaptures ();
761
790
mediaRecorder .stop ();
762
791
} catch (CameraAccessException | IllegalStateException e ) {
@@ -765,7 +794,7 @@ public void stopVideoRecording(@NonNull final Result result) {
765
794
mediaRecorder .reset ();
766
795
try {
767
796
startPreview ();
768
- } catch (CameraAccessException | IllegalStateException e ) {
797
+ } catch (CameraAccessException | IllegalStateException | InterruptedException e ) {
769
798
result .error ("videoRecordingFailed" , e .getMessage (), null );
770
799
return ;
771
800
}
@@ -1049,13 +1078,50 @@ public void resumePreview() {
1049
1078
null , (code , message ) -> dartMessenger .sendCameraErrorEvent (message ));
1050
1079
}
1051
1080
1052
- public void startPreview () throws CameraAccessException {
1081
+ public void startPreview () throws CameraAccessException , InterruptedException {
1082
+ // If recording is already in progress, the camera is being flipped, so send it through the VideoRenderer to keep the correct orientation.
1083
+ if (recordingVideo ) {
1084
+ startPreviewWithVideoRendererStream ();
1085
+ } else {
1086
+ startRegularPreview ();
1087
+ }
1088
+ }
1089
+
1090
+ private void startRegularPreview () throws CameraAccessException {
1053
1091
if (pictureImageReader == null || pictureImageReader .getSurface () == null ) return ;
1054
1092
Log .i (TAG , "startPreview" );
1055
-
1056
1093
createCaptureSession (CameraDevice .TEMPLATE_PREVIEW , pictureImageReader .getSurface ());
1057
1094
}
1058
1095
1096
+ private void startPreviewWithVideoRendererStream ()
1097
+ throws CameraAccessException , InterruptedException {
1098
+ if (videoRenderer == null ) return ;
1099
+
1100
+ // get rotation for rendered video
1101
+ final PlatformChannel .DeviceOrientation lockedOrientation =
1102
+ cameraFeatures .getSensorOrientation ().getLockedCaptureOrientation ();
1103
+ DeviceOrientationManager orientationManager =
1104
+ cameraFeatures .getSensorOrientation ().getDeviceOrientationManager ();
1105
+
1106
+ int rotation = 0 ;
1107
+ if (orientationManager != null ) {
1108
+ rotation =
1109
+ lockedOrientation == null
1110
+ ? orientationManager .getVideoOrientation ()
1111
+ : orientationManager .getVideoOrientation (lockedOrientation );
1112
+ }
1113
+
1114
+ if (cameraProperties .getLensFacing () != initialCameraFacing ) {
1115
+
1116
+ // If the new camera is facing the opposite way than the initial recording,
1117
+ // the rotation should be flipped 180 degrees.
1118
+ rotation = (rotation + 180 ) % 360 ;
1119
+ }
1120
+ videoRenderer .setRotation (rotation );
1121
+
1122
+ createCaptureSession (CameraDevice .TEMPLATE_RECORD , videoRenderer .getInputSurface ());
1123
+ }
1124
+
1059
1125
public void startPreviewWithImageStream (EventChannel imageStreamChannel )
1060
1126
throws CameraAccessException {
1061
1127
setStreamHandler (imageStreamChannel );
@@ -1179,17 +1245,7 @@ private void closeCaptureSession() {
1179
1245
public void close () {
1180
1246
Log .i (TAG , "close" );
1181
1247
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
- }
1248
+ stopAndReleaseCamera ();
1193
1249
1194
1250
if (pictureImageReader != null ) {
1195
1251
pictureImageReader .close ();
@@ -1208,6 +1264,75 @@ public void close() {
1208
1264
stopBackgroundThread ();
1209
1265
}
1210
1266
1267
+ private void stopAndReleaseCamera () {
1268
+ if (cameraDevice != null ) {
1269
+ cameraDevice .close ();
1270
+ cameraDevice = null ;
1271
+
1272
+ // Closing the CameraDevice without closing the CameraCaptureSession is recommended
1273
+ // for quickly closing the camera:
1274
+ // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close()
1275
+ captureSession = null ;
1276
+ } else {
1277
+ closeCaptureSession ();
1278
+ }
1279
+ }
1280
+
1281
+ private void prepareVideoRenderer () {
1282
+ if (videoRenderer != null ) return ;
1283
+ final ResolutionFeature resolutionFeature = cameraFeatures .getResolution ();
1284
+
1285
+ // handle videoRenderer errors
1286
+ Thread .UncaughtExceptionHandler videoRendererUncaughtExceptionHandler =
1287
+ new Thread .UncaughtExceptionHandler () {
1288
+ @ Override
1289
+ public void uncaughtException (Thread thread , Throwable ex ) {
1290
+ dartMessenger .sendCameraErrorEvent (
1291
+ "Failed to process frames after camera was flipped." );
1292
+ }
1293
+ };
1294
+
1295
+ videoRenderer =
1296
+ new VideoRenderer (
1297
+ mediaRecorder .getSurface (),
1298
+ resolutionFeature .getCaptureSize ().getWidth (),
1299
+ resolutionFeature .getCaptureSize ().getHeight (),
1300
+ videoRendererUncaughtExceptionHandler );
1301
+ }
1302
+
1303
+ public void setDescriptionWhileRecording (
1304
+ @ NonNull final Result result , CameraProperties properties ) {
1305
+
1306
+ if (!recordingVideo ) {
1307
+ result .error ("setDescriptionWhileRecordingFailed" , "Device was not recording" , null );
1308
+ return ;
1309
+ }
1310
+
1311
+ // See VideoRenderer.java requires API 26 to switch camera while recording
1312
+ if (android .os .Build .VERSION .SDK_INT < android .os .Build .VERSION_CODES .O ) {
1313
+ result .error (
1314
+ "setDescriptionWhileRecordingFailed" ,
1315
+ "Device does not support switching the camera while recording" ,
1316
+ null );
1317
+ return ;
1318
+ }
1319
+
1320
+ stopAndReleaseCamera ();
1321
+ prepareVideoRenderer ();
1322
+ cameraProperties = properties ;
1323
+ cameraFeatures =
1324
+ CameraFeatures .init (
1325
+ cameraFeatureFactory , cameraProperties , activity , dartMessenger , resolutionPreset );
1326
+ cameraFeatures .setAutoFocus (
1327
+ cameraFeatureFactory .createAutoFocusFeature (cameraProperties , true ));
1328
+ try {
1329
+ open (imageFormatGroup );
1330
+ } catch (CameraAccessException e ) {
1331
+ result .error ("setDescriptionWhileRecordingFailed" , e .getMessage (), null );
1332
+ }
1333
+ result .success (null );
1334
+ }
1335
+
1211
1336
public void dispose () {
1212
1337
Log .i (TAG , "dispose" );
1213
1338
0 commit comments