Julian Steenbakker

bug: fix start() and stop() crash when called multiple times on iOS and macOS. R…

…emove analyze mode on Android and iOS.
include: package:flutter_lints/flutter.yaml
linter:
rules:
unawaited_futures: true
\ No newline at end of file
... ...
... ... @@ -38,8 +38,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
private var camera: Camera? = null
private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
@AnalyzeMode
private var analyzeMode: Int = AnalyzeMode.NONE
// @AnalyzeMode
// private var analyzeMode: Int = AnalyzeMode.NONE
@ExperimentalGetImage
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) {
... ... @@ -47,8 +47,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
"state" -> checkPermission(result)
"request" -> requestPermission(result)
"start" -> start(call, result)
"torch" -> switchTorch(call, result)
"analyze" -> switchAnalyzeMode(call, result)
"torch" -> toggleTorch(call, result)
// "analyze" -> switchAnalyzeMode(call, result)
"stop" -> stop(result)
else -> result.notImplemented()
}
... ... @@ -92,8 +92,8 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
@ExperimentalGetImage
val analyzer = ImageAnalysis.Analyzer { imageProxy -> // YUV_420_888 format
when (analyzeMode) {
AnalyzeMode.BARCODE -> {
// when (analyzeMode) {
// AnalyzeMode.BARCODE -> {
val mediaImage = imageProxy.image ?: return@Analyzer
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
... ... @@ -106,9 +106,9 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
}
.addOnFailureListener { e -> Log.e(TAG, e.message, e) }
.addOnCompleteListener { imageProxy.close() }
}
else -> imageProxy.close()
}
// }
// else -> imageProxy.close()
// }
}
... ... @@ -116,6 +116,10 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
@ExperimentalGetImage
private fun start(call: MethodCall, result: MethodChannel.Result) {
if (camera != null) {
result.error(TAG, "Called start() while already started!", null)
return
}
val facing: Int = call.argument<Int>("facing") ?: 0
val ratio: Int? = call.argument<Int>("ratio")
... ... @@ -186,24 +190,32 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
}, executor)
}
private fun switchTorch(call: MethodCall, result: MethodChannel.Result) {
val state = call.arguments == 1
camera!!.cameraControl.enableTorch(state)
result.success(null)
private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
if (camera == null) {
result.error(TAG,"Called toggleTorch() while stopped!", null)
return
}
private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
analyzeMode = call.arguments as Int
camera!!.cameraControl.enableTorch(call.arguments == 1)
result.success(null)
}
// private fun switchAnalyzeMode(call: MethodCall, result: MethodChannel.Result) {
// analyzeMode = call.arguments as Int
// result.success(null)
// }
private fun stop(result: MethodChannel.Result) {
if (camera == null) {
result.error(TAG,"Called stop() while already stopped!", null)
return
}
val owner = activity as LifecycleOwner
camera!!.cameraInfo.torchState.removeObservers(owner)
cameraProvider!!.unbindAll()
textureEntry!!.release()
analyzeMode = AnalyzeMode.NONE
// analyzeMode = AnalyzeMode.NONE
camera = null
textureEntry = null
cameraProvider = null
... ...
... ... @@ -23,7 +23,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
var latestBuffer: CVImageBuffer!
var analyzeMode: Int = 0
// var analyzeMode: Int = 0
var analyzing: Bool = false
var position = AVCaptureDevice.Position.back
... ... @@ -53,9 +53,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
case "start":
start(call, result)
case "torch":
switchTorch(call, result)
case "analyze":
switchAnalyzeMode(call, result)
toggleTorch(call, result)
// case "analyze":
// switchAnalyzeMode(call, result)
case "stop":
stop(result)
default:
... ... @@ -89,10 +89,10 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
registry.textureFrameAvailable(textureId)
switch analyzeMode {
case 1: // barcode
// switch analyzeMode {
// case 1: // barcode
if analyzing {
break
return
}
analyzing = true
let buffer = CMSampleBufferGetImageBuffer(sampleBuffer)
... ... @@ -112,9 +112,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
analyzing = false
}
default: // none
break
}
// default: // none
// break
// }
}
func imageOrientation(
... ... @@ -154,6 +154,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device != nil) {
result(FlutterError(code: "MobileScanner",
message: "Called start() while already started!",
details: nil))
return
}
textureId = registry.register(self)
captureSession = AVCaptureSession()
... ... @@ -219,7 +226,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
result(answer)
}
func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "Called toggleTorch() while stopped!",
details: nil))
return
}
do {
try device.lockForConfiguration()
device.torchMode = call.arguments as! Int == 1 ? .on : .off
... ... @@ -230,12 +243,18 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
}
func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
analyzeMode = call.arguments as! Int
result(nil)
}
// func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
// analyzeMode = call.arguments as! Int
// result(nil)
// }
func stop(_ result: FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "Called stop() while already stopped!",
details: nil))
return
}
captureSession.stopRunning()
for input in captureSession.inputs {
captureSession.removeInput(input)
... ... @@ -246,7 +265,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
registry.unregisterTexture(textureId)
analyzeMode = 0
// analyzeMode = 0
latestBuffer = nil
captureSession = nil
device = nil
... ...
... ... @@ -27,7 +27,7 @@ enum TorchState {
on,
}
enum AnalyzeMode { none, barcode }
// enum AnalyzeMode { none, barcode }
class MobileScannerController {
MethodChannel methodChannel =
... ... @@ -62,8 +62,8 @@ class MobileScannerController {
// Sets analyze mode and barcode stream
barcodesController = StreamController.broadcast(
onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
// onListen: () => setAnalyzeMode(AnalyzeMode.barcode.index),
// onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
);
start();
... ... @@ -94,20 +94,22 @@ class MobileScannerController {
}
}
void setAnalyzeMode(int mode) {
if (hashCode != _controllerHashcode) {
return;
}
methodChannel.invokeMethod('analyze', mode);
}
// TODO: Add more analyzers like text analyzer
// void setAnalyzeMode(int mode) {
// if (hashCode != _controllerHashcode) {
// return;
// }
// methodChannel.invokeMethod('analyze', mode);
// }
// List<BarcodeFormats>? formats = _defaultBarcodeFormats,
/// Start barcode scanning. This will first check if the required permissions
/// are set.
Future<void> start() async {
ensure('startAsync');
setAnalyzeMode(AnalyzeMode.barcode.index);
// setAnalyzeMode(AnalyzeMode.barcode.index);
// Check authorization status
MobileScannerState state =
MobileScannerState.values[await methodChannel.invokeMethod('state')];
... ... @@ -132,8 +134,15 @@ class MobileScannerController {
if (torchEnabled != null) arguments['torch'] = torchEnabled;
// Start the camera with arguments
final Map<String, dynamic>? startResult = await methodChannel
Map<String, dynamic>? startResult = {};
try {
startResult = await methodChannel
.invokeMapMethod<String, dynamic>('start', arguments);
} on PlatformException catch (error) {
debugPrint('${error.code}: ${error.message}');
// setAnalyzeMode(AnalyzeMode.none.index);
return;
}
if (startResult == null) {
throw PlatformException(code: 'INITIALIZATION ERROR');
... ... @@ -146,17 +155,32 @@ class MobileScannerController {
hasTorch: hasTorch);
}
Future<void> stop() async => await methodChannel.invokeMethod('stop');
Future<void> stop() async {
try {
await methodChannel.invokeMethod('stop');
} on PlatformException catch (error) {
debugPrint('${error.code}: ${error.message}');
}
}
/// Switches the torch on or off.
///
/// Only works if torch is available.
void toggleTorch() {
Future<void> toggleTorch() async {
ensure('toggleTorch');
if (!hasTorch) return;
if (!hasTorch) {
debugPrint('Device has no torch/flash.');
return;
}
TorchState state =
torchState.value == TorchState.off ? TorchState.on : TorchState.off;
methodChannel.invokeMethod('torch', state.index);
try {
await methodChannel.invokeMethod('torch', state.index);
} on PlatformException catch (error) {
debugPrint('${error.code}: ${error.message}');
}
}
/// Switches the torch on or off.
... ... @@ -164,10 +188,15 @@ class MobileScannerController {
/// Only works if torch is available.
Future<void> switchCamera() async {
ensure('switchCamera');
await stop();
try {
await methodChannel.invokeMethod('stop');
} on PlatformException catch (error) {
debugPrint('${error.code}: camera is stopped! Please start before switching camera.');
return;
}
facing =
facing == CameraFacing.back ? CameraFacing.front : CameraFacing.back;
start();
await start();
}
/// Disposes the controller and closes all listeners.
... ...
... ... @@ -22,7 +22,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
var latestBuffer: CVImageBuffer!
var analyzeMode: Int = 0
// var analyzeMode: Int = 0
var analyzing: Bool = false
var position = AVCaptureDevice.Position.back
... ... @@ -52,9 +52,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
case "start":
start(call, result)
case "torch":
switchTorch(call, result)
case "analyze":
switchAnalyzeMode(call, result)
toggleTorch(call, result)
// case "analyze":
// switchAnalyzeMode(call, result)
case "stop":
stop(result)
default:
... ... @@ -92,14 +92,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
registry.textureFrameAvailable(textureId)
switch analyzeMode {
case 1: // barcode
// switch analyzeMode {
// case 1: // barcode
// Limit the analyzer because the texture output will freeze otherwise
if i / 10 == 1 {
i = 0
} else {
break
return
}
let imageRequestHandler = VNImageRequestHandler(
cvPixelBuffer: latestBuffer,
... ... @@ -129,9 +129,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
print(error)
}
default: // none
break
}
// default: // none
// break
// }
}
func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
... ... @@ -159,6 +159,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}
func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device != nil) {
result(FlutterError(code: "MobileScanner",
message: "Called start() while already started!",
details: nil))
return
}
textureId = registry.register(self)
captureSession = AVCaptureSession()
... ... @@ -222,7 +229,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
result(answer)
}
func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "Called toggleTorch() while stopped!",
details: nil))
return
}
do {
try device.lockForConfiguration()
device.torchMode = call.arguments as! Int == 1 ? .on : .off
... ... @@ -233,12 +246,18 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}
}
func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
analyzeMode = call.arguments as! Int
result(nil)
}
// func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
// analyzeMode = call.arguments as! Int
// result(nil)
// }
func stop(_ result: FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
message: "Called stop() while already stopped!",
details: nil))
return
}
captureSession.stopRunning()
for input in captureSession.inputs {
captureSession.removeInput(input)
... ... @@ -249,7 +268,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
registry.unregisterTexture(textureId)
analyzeMode = 0
// analyzeMode = 0
latestBuffer = nil
captureSession = nil
device = nil
... ...