Skip to content

Commit

Permalink
Merge pull request #994 from fumin65/pause_function
Browse files Browse the repository at this point in the history
feat: add pause feature
  • Loading branch information
juliansteenbakker authored Jan 12, 2025
2 parents bc31911 + 1d19bc7 commit b4a084c
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ class MobileScanner(
}

cameraProvider?.unbindAll()
textureEntry = textureRegistry.createSurfaceTexture()
textureEntry = textureEntry ?: textureRegistry.createSurfaceTexture()

// Preview
val surfaceProvider = Preview.SurfaceProvider { request ->
Expand Down Expand Up @@ -405,14 +405,33 @@ class MobileScanner(
}, executor)

}

/**
* Pause barcode scanning.
*/
fun pause() {
if (isPaused()) {
throw AlreadyPaused()
} else if (isStopped()) {
throw AlreadyStopped()
}

releaseCamera()
}

/**
* Stop barcode scanning.
*/
fun stop() {
if (isStopped()) {
if (!isPaused() && isStopped()) {
throw AlreadyStopped()
}

releaseCamera()
releaseTexture()
}

private fun releaseCamera() {
if (displayListener != null) {
val displayManager = activity.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager

Expand All @@ -430,9 +449,6 @@ class MobileScanner(
// Unbind the camera use cases, the preview is a use case.
// The camera will be closed when the last use case is unbound.
cameraProvider?.unbindAll()
cameraProvider = null
camera = null
preview = null

// Release the texture for the preview.
textureEntry?.release()
Expand All @@ -444,7 +460,13 @@ class MobileScanner(
lastScanned = null
}

private fun releaseTexture() {
textureEntry?.release()
textureEntry = null
}

private fun isStopped() = camera == null && preview == null
private fun isPaused() = isStopped() && textureEntry != null

/**
* Toggles the flash light on or off.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dev.steenbakker.mobile_scanner
class NoCamera : Exception()
class AlreadyStarted : Exception()
class AlreadyStopped : Exception()
class AlreadyPaused : Exception()
class CameraError : Exception()
class ZoomWhenStopped : Exception()
class ZoomNotInRange : Exception()
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class MobileScannerHandler(
}
})
"start" -> start(call, result)
"pause" -> pause(result)
"stop" -> stop(result)
"toggleTorch" -> toggleTorch(result)
"analyzeImage" -> analyzeImage(call, result)
Expand Down Expand Up @@ -213,6 +214,18 @@ class MobileScannerHandler(
)
}

private fun pause(result: MethodChannel.Result) {
try {
mobileScanner!!.pause()
result.success(null)
} catch (e: Exception) {
when (e) {
is AlreadyPaused, is AlreadyStopped -> result.success(null)
else -> throw e
}
}
}

private fun stop(result: MethodChannel.Result) {
try {
mobileScanner!!.stop()
Expand Down
1 change: 1 addition & 0 deletions example/lib/barcode_scanner_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class _BarcodeScannerWithControllerState
children: [
ToggleFlashlightButton(controller: controller),
StartStopMobileScannerButton(controller: controller),
PauseMobileScannerButton(controller: controller),
Expanded(child: Center(child: _buildBarcode(_barcode))),
SwitchCameraButton(controller: controller),
AnalyzeImageFromGalleryButton(controller: controller),
Expand Down
27 changes: 27 additions & 0 deletions example/lib/scanner_button_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,30 @@ class ToggleFlashlightButton extends StatelessWidget {
);
}
}

class PauseMobileScannerButton extends StatelessWidget {
const PauseMobileScannerButton({required this.controller, super.key});

final MobileScannerController controller;

@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller,
builder: (context, state, child) {
if (!state.isInitialized || !state.isRunning) {
return const SizedBox.shrink();
}

return IconButton(
color: Colors.white,
iconSize: 32.0,
icon: const Icon(Icons.pause),
onPressed: () async {
await controller.pause();
},
);
},
);
}
}
55 changes: 43 additions & 12 deletions ios/Classes/MobileScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega

public var timeoutSeconds: Double = 0

private var stopped: Bool {
return device == nil || captureSession == nil
}

private var paused: Bool {
return stopped && textureId != nil
}

init(registry: FlutterTextureRegistry?, mobileScannerCallback: @escaping MobileScannerCallback, torchModeChangeCallback: @escaping TorchModeChangeCallback, zoomScaleChangeCallback: @escaping ZoomScaleChangeCallback) {
self.registry = registry
self.mobileScannerCallback = mobileScannerCallback
Expand Down Expand Up @@ -123,6 +131,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega

/// Gets called when a new image is added to the buffer
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
Expand Down Expand Up @@ -157,7 +166,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) {
return
}

if (newScannedBarcodes?.isEmpty == false) {
barcodesString = newScannedBarcodes
}
Expand All @@ -178,7 +187,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
barcodesString = nil
scanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner()
captureSession = AVCaptureSession()
textureId = registry?.register(self)
textureId = textureId ?? registry?.register(self)

// Open the camera device
device = getDefaultCameraDevice(position: cameraPosition)
Expand Down Expand Up @@ -293,27 +302,49 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
}
}

/// Pause scanning for barcodes
func pause() throws {
if (paused) {
throw MobileScannerError.alreadyPaused
} else if (stopped) {
throw MobileScannerError.alreadyStopped
}
releaseCamera()
}

/// Stop scanning for barcodes
func stop() throws {
if (device == nil || captureSession == nil) {
if (!paused && stopped) {
throw MobileScannerError.alreadyStopped
}

captureSession!.stopRunning()
for input in captureSession!.inputs {
captureSession!.removeInput(input)
releaseCamera()
releaseTexture()
}

private func releaseCamera() {

guard let captureSession = captureSession else {
return
}

captureSession.stopRunning()
for input in captureSession.inputs {
captureSession.removeInput(input)
}
for output in captureSession!.outputs {
captureSession!.removeOutput(output)
for output in captureSession.outputs {
captureSession.removeOutput(output)
}

latestBuffer = nil
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.videoZoomFactor))
self.captureSession = nil
device = nil
}

private func releaseTexture() {
registry?.unregisterTexture(textureId)
textureId = nil
captureSession = nil
device = nil
scanner = nil
}

Expand Down Expand Up @@ -440,7 +471,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
defaultOrientation: .portrait,
position: position
)

let scanner: BarcodeScanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner()

scanner.process(image, completion: callback)
Expand Down
1 change: 1 addition & 0 deletions ios/Classes/MobileScannerError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum MobileScannerError: Error {
case noCamera
case alreadyStarted
case alreadyStopped
case alreadyPaused
case cameraError(_ error: Error)
case zoomWhenStopped
case zoomError(_ error: Error)
Expand Down
10 changes: 10 additions & 0 deletions ios/Classes/MobileScannerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) })
case "start":
start(call, result)
case "pause":
pause(result)
case "stop":
stop(result)
case "toggleTorch":
Expand Down Expand Up @@ -166,6 +168,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
details: nil))
}
}

/// Stops the mobileScanner without closing the texture.
private func pause(_ result: @escaping FlutterResult) {
do {
try mobileScanner.pause()
} catch {}
result(nil)
}

/// Stops the mobileScanner and closes the texture.
private func stop(_ result: @escaping FlutterResult) {
Expand Down
17 changes: 16 additions & 1 deletion lib/src/method_channel/mobile_scanner_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
}

int? _textureId;
bool _pausing = false;

/// Parse a [BarcodeCapture] from the given [event].
BarcodeCapture? _parseBarcode(Map<Object?, Object?>? event) {
Expand Down Expand Up @@ -216,7 +217,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {

@override
Future<MobileScannerViewAttributes> start(StartOptions startOptions) async {
if (_textureId != null) {
if (!_pausing && _textureId != null) {
throw const MobileScannerException(
errorCode: MobileScannerErrorCode.controllerAlreadyInitialized,
errorDetails: MobileScannerErrorDetails(
Expand Down Expand Up @@ -281,6 +282,8 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
size = Size.zero;
}

_pausing = false;

return MobileScannerViewAttributes(
currentTorchMode: currentTorchState,
numberOfCameras: numberOfCameras,
Expand All @@ -295,10 +298,22 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
}

_textureId = null;
_pausing = false;

await methodChannel.invokeMethod<void>('stop');
}

@override
Future<void> pause() async {
if (_pausing) {
return;
}

_pausing = true;

await methodChannel.invokeMethod<void>('pause');
}

@override
Future<void> toggleTorch() async {
await methodChannel.invokeMethod<void>('toggleTorch');
Expand Down
Loading

0 comments on commit b4a084c

Please sign in to comment.