Skip to content

Commit b4a084c

Browse files
Merge pull request juliansteenbakker#994 from fumin65/pause_function
feat: add pause feature
2 parents bc31911 + 1d19bc7 commit b4a084c

File tree

15 files changed

+276
-84
lines changed

15 files changed

+276
-84
lines changed

android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ class MobileScanner(
273273
}
274274

275275
cameraProvider?.unbindAll()
276-
textureEntry = textureRegistry.createSurfaceTexture()
276+
textureEntry = textureEntry ?: textureRegistry.createSurfaceTexture()
277277

278278
// Preview
279279
val surfaceProvider = Preview.SurfaceProvider { request ->
@@ -405,14 +405,33 @@ class MobileScanner(
405405
}, executor)
406406

407407
}
408+
409+
/**
410+
* Pause barcode scanning.
411+
*/
412+
fun pause() {
413+
if (isPaused()) {
414+
throw AlreadyPaused()
415+
} else if (isStopped()) {
416+
throw AlreadyStopped()
417+
}
418+
419+
releaseCamera()
420+
}
421+
408422
/**
409423
* Stop barcode scanning.
410424
*/
411425
fun stop() {
412-
if (isStopped()) {
426+
if (!isPaused() && isStopped()) {
413427
throw AlreadyStopped()
414428
}
415429

430+
releaseCamera()
431+
releaseTexture()
432+
}
433+
434+
private fun releaseCamera() {
416435
if (displayListener != null) {
417436
val displayManager = activity.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
418437

@@ -430,9 +449,6 @@ class MobileScanner(
430449
// Unbind the camera use cases, the preview is a use case.
431450
// The camera will be closed when the last use case is unbound.
432451
cameraProvider?.unbindAll()
433-
cameraProvider = null
434-
camera = null
435-
preview = null
436452

437453
// Release the texture for the preview.
438454
textureEntry?.release()
@@ -444,7 +460,13 @@ class MobileScanner(
444460
lastScanned = null
445461
}
446462

463+
private fun releaseTexture() {
464+
textureEntry?.release()
465+
textureEntry = null
466+
}
467+
447468
private fun isStopped() = camera == null && preview == null
469+
private fun isPaused() = isStopped() && textureEntry != null
448470

449471
/**
450472
* Toggles the flash light on or off.

android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerExceptions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dev.steenbakker.mobile_scanner
33
class NoCamera : Exception()
44
class AlreadyStarted : Exception()
55
class AlreadyStopped : Exception()
6+
class AlreadyPaused : Exception()
67
class CameraError : Exception()
78
class ZoomWhenStopped : Exception()
89
class ZoomNotInRange : Exception()

android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class MobileScannerHandler(
118118
}
119119
})
120120
"start" -> start(call, result)
121+
"pause" -> pause(result)
121122
"stop" -> stop(result)
122123
"toggleTorch" -> toggleTorch(result)
123124
"analyzeImage" -> analyzeImage(call, result)
@@ -213,6 +214,18 @@ class MobileScannerHandler(
213214
)
214215
}
215216

217+
private fun pause(result: MethodChannel.Result) {
218+
try {
219+
mobileScanner!!.pause()
220+
result.success(null)
221+
} catch (e: Exception) {
222+
when (e) {
223+
is AlreadyPaused, is AlreadyStopped -> result.success(null)
224+
else -> throw e
225+
}
226+
}
227+
}
228+
216229
private fun stop(result: MethodChannel.Result) {
217230
try {
218231
mobileScanner!!.stop()

example/lib/barcode_scanner_controller.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class _BarcodeScannerWithControllerState
105105
children: [
106106
ToggleFlashlightButton(controller: controller),
107107
StartStopMobileScannerButton(controller: controller),
108+
PauseMobileScannerButton(controller: controller),
108109
Expanded(child: Center(child: _buildBarcode(_barcode))),
109110
SwitchCameraButton(controller: controller),
110111
AnalyzeImageFromGalleryButton(controller: controller),

example/lib/scanner_button_widgets.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,30 @@ class ToggleFlashlightButton extends StatelessWidget {
180180
);
181181
}
182182
}
183+
184+
class PauseMobileScannerButton extends StatelessWidget {
185+
const PauseMobileScannerButton({required this.controller, super.key});
186+
187+
final MobileScannerController controller;
188+
189+
@override
190+
Widget build(BuildContext context) {
191+
return ValueListenableBuilder(
192+
valueListenable: controller,
193+
builder: (context, state, child) {
194+
if (!state.isInitialized || !state.isRunning) {
195+
return const SizedBox.shrink();
196+
}
197+
198+
return IconButton(
199+
color: Colors.white,
200+
iconSize: 32.0,
201+
icon: const Icon(Icons.pause),
202+
onPressed: () async {
203+
await controller.pause();
204+
},
205+
);
206+
},
207+
);
208+
}
209+
}

ios/Classes/MobileScanner.swift

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
5858

5959
public var timeoutSeconds: Double = 0
6060

61+
private var stopped: Bool {
62+
return device == nil || captureSession == nil
63+
}
64+
65+
private var paused: Bool {
66+
return stopped && textureId != nil
67+
}
68+
6169
init(registry: FlutterTextureRegistry?, mobileScannerCallback: @escaping MobileScannerCallback, torchModeChangeCallback: @escaping TorchModeChangeCallback, zoomScaleChangeCallback: @escaping ZoomScaleChangeCallback) {
6270
self.registry = registry
6371
self.mobileScannerCallback = mobileScannerCallback
@@ -123,6 +131,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
123131

124132
/// Gets called when a new image is added to the buffer
125133
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
134+
126135
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
127136
return
128137
}
@@ -157,7 +166,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
157166
if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) {
158167
return
159168
}
160-
169+
161170
if (newScannedBarcodes?.isEmpty == false) {
162171
barcodesString = newScannedBarcodes
163172
}
@@ -178,7 +187,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
178187
barcodesString = nil
179188
scanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner()
180189
captureSession = AVCaptureSession()
181-
textureId = registry?.register(self)
190+
textureId = textureId ?? registry?.register(self)
182191

183192
// Open the camera device
184193
device = getDefaultCameraDevice(position: cameraPosition)
@@ -293,27 +302,49 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
293302
}
294303
}
295304

305+
/// Pause scanning for barcodes
306+
func pause() throws {
307+
if (paused) {
308+
throw MobileScannerError.alreadyPaused
309+
} else if (stopped) {
310+
throw MobileScannerError.alreadyStopped
311+
}
312+
releaseCamera()
313+
}
314+
296315
/// Stop scanning for barcodes
297316
func stop() throws {
298-
if (device == nil || captureSession == nil) {
317+
if (!paused && stopped) {
299318
throw MobileScannerError.alreadyStopped
300319
}
301-
302-
captureSession!.stopRunning()
303-
for input in captureSession!.inputs {
304-
captureSession!.removeInput(input)
320+
releaseCamera()
321+
releaseTexture()
322+
}
323+
324+
private func releaseCamera() {
325+
326+
guard let captureSession = captureSession else {
327+
return
328+
}
329+
330+
captureSession.stopRunning()
331+
for input in captureSession.inputs {
332+
captureSession.removeInput(input)
305333
}
306-
for output in captureSession!.outputs {
307-
captureSession!.removeOutput(output)
334+
for output in captureSession.outputs {
335+
captureSession.removeOutput(output)
308336
}
309337

310338
latestBuffer = nil
311339
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
312340
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.videoZoomFactor))
341+
self.captureSession = nil
342+
device = nil
343+
}
344+
345+
private func releaseTexture() {
313346
registry?.unregisterTexture(textureId)
314347
textureId = nil
315-
captureSession = nil
316-
device = nil
317348
scanner = nil
318349
}
319350

@@ -440,7 +471,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
440471
defaultOrientation: .portrait,
441472
position: position
442473
)
443-
474+
444475
let scanner: BarcodeScanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner()
445476

446477
scanner.process(image, completion: callback)

ios/Classes/MobileScannerError.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ enum MobileScannerError: Error {
1616
case noCamera
1717
case alreadyStarted
1818
case alreadyStopped
19+
case alreadyPaused
1920
case cameraError(_ error: Error)
2021
case zoomWhenStopped
2122
case zoomError(_ error: Error)

ios/Classes/MobileScannerPlugin.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
105105
AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) })
106106
case "start":
107107
start(call, result)
108+
case "pause":
109+
pause(result)
108110
case "stop":
109111
stop(result)
110112
case "toggleTorch":
@@ -166,6 +168,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
166168
details: nil))
167169
}
168170
}
171+
172+
/// Stops the mobileScanner without closing the texture.
173+
private func pause(_ result: @escaping FlutterResult) {
174+
do {
175+
try mobileScanner.pause()
176+
} catch {}
177+
result(nil)
178+
}
169179

170180
/// Stops the mobileScanner and closes the texture.
171181
private func stop(_ result: @escaping FlutterResult) {

lib/src/method_channel/mobile_scanner_method_channel.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
4646
}
4747

4848
int? _textureId;
49+
bool _pausing = false;
4950

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

217218
@override
218219
Future<MobileScannerViewAttributes> start(StartOptions startOptions) async {
219-
if (_textureId != null) {
220+
if (!_pausing && _textureId != null) {
220221
throw const MobileScannerException(
221222
errorCode: MobileScannerErrorCode.controllerAlreadyInitialized,
222223
errorDetails: MobileScannerErrorDetails(
@@ -281,6 +282,8 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
281282
size = Size.zero;
282283
}
283284

285+
_pausing = false;
286+
284287
return MobileScannerViewAttributes(
285288
currentTorchMode: currentTorchState,
286289
numberOfCameras: numberOfCameras,
@@ -295,10 +298,22 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
295298
}
296299

297300
_textureId = null;
301+
_pausing = false;
298302

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

306+
@override
307+
Future<void> pause() async {
308+
if (_pausing) {
309+
return;
310+
}
311+
312+
_pausing = true;
313+
314+
await methodChannel.invokeMethod<void>('pause');
315+
}
316+
302317
@override
303318
Future<void> toggleTorch() async {
304319
await methodChannel.invokeMethod<void>('toggleTorch');

0 commit comments

Comments
 (0)