Skip to content

Commit

Permalink
Merge pull request #1328 from navaronbracke/more_facing_directions
Browse files Browse the repository at this point in the history
feat: handle more facing directions
  • Loading branch information
juliansteenbakker authored Feb 17, 2025
2 parents 63095d6 + 4f847df commit b862ab6
Show file tree
Hide file tree
Showing 17 changed files with 215 additions and 87 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
## NEXT

**BREAKING CHANGES:**

* The initial state of the `MobileScannerState` camera facing direction is changed to `CameraFacing.unknown`.

Improvements:
* [Android] Turn off logging for CameraX, except for the `Log.ERROR` logging level.
* Added `CameraFacing.external` and `CameraFacing.unknown` enum values.

## 7.0.0-beta.6

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ExperimentalLensFacing
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
Expand Down Expand Up @@ -278,6 +279,17 @@ class MobileScanner(
}
}

@ExperimentalLensFacing
private fun getCameraLensFacing(camera: Camera?): Int? {
return when(camera?.cameraInfo?.lensFacing) {
CameraSelector.LENS_FACING_BACK -> 1
CameraSelector.LENS_FACING_FRONT -> 0
CameraSelector.LENS_FACING_EXTERNAL -> 2
CameraSelector.LENS_FACING_UNKNOWN -> null
else -> null
}
}

private fun rotateBitmap(bitmap: Bitmap, degrees: Float): Bitmap {
val matrix = Matrix()
matrix.postRotate(degrees)
Expand Down Expand Up @@ -318,6 +330,7 @@ class MobileScanner(
/**
* Start barcode scanning by initializing the camera and barcode scanner.
*/
@ExperimentalLensFacing
@ExperimentalGetImage
fun start(
barcodeScannerOptions: BarcodeScannerOptions?,
Expand All @@ -343,6 +356,7 @@ class MobileScanner(
// TODO: resume here for seamless transition
// if (isPaused) {
// resumeCamera()
// val cameraDirection = getCameraLensFacing(camera)
// mobileScannerStartedCallback(
// MobileScannerStartParameters(
// if (portrait) width else height,
Expand All @@ -352,7 +366,8 @@ class MobileScanner(
// surfaceProducer!!.handlesCropAndRotation(),
// currentTorchState,
// surfaceProducer!!.id(),
// numberOfCameras ?: 0
// numberOfCameras ?: 0,
// cameraDirection
// )
// )
// return
Expand Down Expand Up @@ -465,6 +480,7 @@ class MobileScanner(
val height = resolution.height.toDouble()
val sensorRotationDegrees = camera?.cameraInfo?.sensorRotationDegrees ?: 0
val portrait = sensorRotationDegrees % 180 == 0
val cameraDirection = getCameraLensFacing(camera)

// Start with 'unavailable' torch state.
var currentTorchState: Int = -1
Expand All @@ -488,7 +504,8 @@ class MobileScanner(
surfaceProducer!!.handlesCropAndRotation(),
currentTorchState,
surfaceProducer!!.id(),
numberOfCameras ?: 0
numberOfCameras ?: 0,
cameraDirection,
)
)
}, executor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.os.Looper
import android.util.Size
import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ExperimentalLensFacing
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import dev.steenbakker.mobile_scanner.objects.BarcodeFormats
import dev.steenbakker.mobile_scanner.objects.DetectionSpeed
Expand Down Expand Up @@ -114,6 +115,7 @@ class MobileScannerHandler(
}
}

@ExperimentalLensFacing
@ExperimentalGetImage
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
Expand Down Expand Up @@ -146,6 +148,7 @@ class MobileScannerHandler(
}
}

@ExperimentalLensFacing
@ExperimentalGetImage
private fun start(call: MethodCall, result: MethodChannel.Result) {
val torch: Boolean = call.argument<Boolean>("torch") ?: false
Expand Down Expand Up @@ -191,7 +194,8 @@ class MobileScannerHandler(
"isPreviewPreTransformed" to it.isPreviewPreTransformed,
"sensorOrientation" to it.sensorOrientation,
"currentTorchState" to it.currentTorchState,
"numberOfCameras" to it.numberOfCameras
"numberOfCameras" to it.numberOfCameras,
"cameraDirection" to it.cameraDirection
))
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ class MobileScannerStartParameters(
val isPreviewPreTransformed: Boolean,
val currentTorchState: Int,
val id: Long,
val numberOfCameras: Int
val numberOfCameras: Int,
val cameraDirection: Int?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,18 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
let answer: [String : Any?]

if let device = self.device {
let cameraDirection: Int? = switch(device.position) {
case .back: 1
case .unspecified: nil
case .front: 0
@unknown default: nil
}

answer = [
"textureId": self.textureId,
"size": size,
"currentTorchState": device.hasTorch ? device.torchMode.rawValue : -1,
"cameraDirection": cameraDirection,
]
} else {
answer = [
Expand Down
4 changes: 4 additions & 0 deletions example/lib/scanner_button_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ class SwitchCameraButton extends StatelessWidget {
icon = const Icon(Icons.camera_front);
case CameraFacing.back:
icon = const Icon(Icons.camera_rear);
case CameraFacing.external:
icon = const Icon(Icons.usb);
case CameraFacing.unknown:
icon = const Icon(Icons.device_unknown);
}

return IconButton(
Expand Down
28 changes: 21 additions & 7 deletions lib/src/enums/camera_facing.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
/// The facing of a camera.
enum CameraFacing {
/// Front facing camera.
/// The camera is a front facing camera.
///
/// This type of camera always faces the user.
front(0),

/// Back facing camera.
back(1);
/// The camera is a back facing camera.
///
/// This type of camera always faces away from the user.
back(1),

/// The camera is an external camera.
///
/// For example a USB-camera.
external(2),

/// The camera facing direction is unknown.
unknown(-1);

const CameraFacing(this.rawValue);

factory CameraFacing.fromRawValue(int value) {
factory CameraFacing.fromRawValue(int? value) {
switch (value) {
case 0:
return CameraFacing.front;
return front;
case 1:
return CameraFacing.back;
return back;
case 2:
return external;
default:
throw ArgumentError.value(value, 'value', 'Invalid raw value.');
return unknown;
}
}

Expand Down
7 changes: 6 additions & 1 deletion lib/src/method_channel/mobile_scanner_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:mobile_scanner/src/enums/barcode_format.dart';
import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/enums/mobile_scanner_authorization_state.dart';
import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
import 'package:mobile_scanner/src/enums/torch_state.dart';
Expand Down Expand Up @@ -307,13 +308,16 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
);
}

final CameraFacing cameraDirection =
CameraFacing.fromRawValue(startResult['cameraDirection'] as int?);

_textureId = textureId;

if (defaultTargetPlatform == TargetPlatform.android) {
_surfaceProducerDelegate =
AndroidSurfaceProducerDelegate.fromConfiguration(
startResult,
startOptions.cameraDirection,
cameraDirection,
);
_surfaceProducerDelegate?.startListeningToDeviceOrientation(
deviceOrientationChangedStream,
Expand All @@ -337,6 +341,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
_pausing = false;

return MobileScannerViewAttributes(
cameraDirection: cameraDirection,
currentTorchMode: currentTorchState,
numberOfCameras: numberOfCameras,
size: size,
Expand Down
54 changes: 38 additions & 16 deletions lib/src/mobile_scanner_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
detectionTimeoutMs >= 0,
'The detection timeout must be greater than or equal to 0.',
),
super(MobileScannerState.uninitialized(facing));
assert(
facing != CameraFacing.unknown,
'CameraFacing.unknown is not a valid camera direction.',
),
super(const MobileScannerState.uninitialized());

/// The desired resolution for the camera.
///
Expand Down Expand Up @@ -303,15 +307,22 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
);
}

if (cameraDirection == CameraFacing.unknown) {
throw const MobileScannerException(
errorCode: MobileScannerErrorCode.genericError,
errorDetails: MobileScannerErrorDetails(
message: 'CameraFacing.unknown is not a valid camera direction.',
),
);
}

// Do nothing if the camera is already running.
if (value.isRunning) {
return;
}

final CameraFacing effectiveDirection = cameraDirection ?? facing;

final StartOptions options = StartOptions(
cameraDirection: effectiveDirection,
cameraDirection: cameraDirection ?? facing,
cameraResolution: cameraResolution,
detectionSpeed: detectionSpeed,
detectionTimeoutMs: detectionTimeoutMs,
Expand All @@ -333,7 +344,7 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
if (!_isDisposed) {
value = value.copyWith(
availableCameras: viewAttributes.numberOfCameras,
cameraDirection: effectiveDirection,
cameraDirection: viewAttributes.cameraDirection,
isInitialized: true,
isRunning: true,
size: viewAttributes.size,
Expand All @@ -351,11 +362,11 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
}

// The initialization finished with an error.
// To avoid stale values, reset the output size,
// torch state and zoom scale to the defaults.
// To avoid stale values, reset the camera direction,
// output size, torch state and zoom scale to the defaults.
if (!_isDisposed) {
value = value.copyWith(
cameraDirection: facing,
cameraDirection: CameraFacing.unknown,
isInitialized: true,
isRunning: false,
error: error,
Expand Down Expand Up @@ -392,11 +403,13 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {

/// Switch between the front and back camera.
///
/// Does nothing if the device has less than 2 cameras.
/// Does nothing if the device has less than 2 cameras,
/// or if the current camera is an external camera.
Future<void> switchCamera() async {
_throwIfNotInitialized();

final int? availableCameras = value.availableCameras;
final CameraFacing cameraDirection = value.cameraDirection;

// Do nothing if the amount of cameras is less than 2 cameras.
// If the the current platform does not provide the amount of cameras,
Expand All @@ -405,15 +418,24 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
return;
}

await stop();
// If the camera direction is not known,
// or if the camera is an external camera, do not allow switching cameras.
if (cameraDirection == CameraFacing.unknown ||
cameraDirection == CameraFacing.external) {
return;
}

final CameraFacing cameraDirection = value.cameraDirection;
await stop();

await start(
cameraDirection: cameraDirection == CameraFacing.front
? CameraFacing.back
: CameraFacing.front,
);
switch (value.cameraDirection) {
case CameraFacing.front:
return start(cameraDirection: CameraFacing.back);
case CameraFacing.back:
return start(cameraDirection: CameraFacing.front);
case CameraFacing.external:
case CameraFacing.unknown:
return;
}
}

/// Switches the flashlight on or off.
Expand Down
7 changes: 6 additions & 1 deletion lib/src/mobile_scanner_view_attributes.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import 'dart:ui';

import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/enums/torch_state.dart';

/// This class defines the attributes for the mobile scanner view.
class MobileScannerViewAttributes {
/// Construct a new [MobileScannerViewAttributes] instance.
const MobileScannerViewAttributes({
required this.cameraDirection,
required this.currentTorchMode,
this.numberOfCameras,
required this.size,
this.numberOfCameras,
});

/// The direction of the active camera.
final CameraFacing cameraDirection;

/// The current torch state of the active camera.
final TorchState currentTorchMode;

Expand Down
4 changes: 2 additions & 2 deletions lib/src/objects/mobile_scanner_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ class MobileScannerState {
});

/// Create a new [MobileScannerState] instance that is uninitialized.
const MobileScannerState.uninitialized(CameraFacing facing)
const MobileScannerState.uninitialized()
: this(
availableCameras: null,
cameraDirection: facing,
cameraDirection: CameraFacing.unknown,
isInitialized: false,
isRunning: false,
size: Size.zero,
Expand Down
Loading

0 comments on commit b862ab6

Please sign in to comment.