Showing
11 changed files
with
174 additions
and
32 deletions
| @@ -19,7 +19,6 @@ import androidx.camera.core.ExperimentalGetImage | @@ -19,7 +19,6 @@ import androidx.camera.core.ExperimentalGetImage | ||
| 19 | import androidx.camera.core.ImageAnalysis | 19 | import androidx.camera.core.ImageAnalysis |
| 20 | import androidx.camera.core.ImageProxy | 20 | import androidx.camera.core.ImageProxy |
| 21 | import androidx.camera.core.Preview | 21 | import androidx.camera.core.Preview |
| 22 | -import androidx.camera.core.resolutionselector.AspectRatioStrategy | ||
| 23 | import androidx.camera.core.resolutionselector.ResolutionSelector | 22 | import androidx.camera.core.resolutionselector.ResolutionSelector |
| 24 | import androidx.camera.core.resolutionselector.ResolutionStrategy | 23 | import androidx.camera.core.resolutionselector.ResolutionStrategy |
| 25 | import androidx.camera.lifecycle.ProcessCameraProvider | 24 | import androidx.camera.lifecycle.ProcessCameraProvider |
| @@ -259,7 +258,7 @@ class MobileScanner( | @@ -259,7 +258,7 @@ class MobileScanner( | ||
| 259 | } | 258 | } |
| 260 | 259 | ||
| 261 | cameraProvider?.unbindAll() | 260 | cameraProvider?.unbindAll() |
| 262 | - textureEntry = textureRegistry.createSurfaceTexture() | 261 | + textureEntry = textureEntry ?: textureRegistry.createSurfaceTexture() |
| 263 | 262 | ||
| 264 | // Preview | 263 | // Preview |
| 265 | val surfaceProvider = Preview.SurfaceProvider { request -> | 264 | val surfaceProvider = Preview.SurfaceProvider { request -> |
| @@ -380,14 +379,33 @@ class MobileScanner( | @@ -380,14 +379,33 @@ class MobileScanner( | ||
| 380 | }, executor) | 379 | }, executor) |
| 381 | 380 | ||
| 382 | } | 381 | } |
| 382 | + | ||
| 383 | + /** | ||
| 384 | + * Pause barcode scanning. | ||
| 385 | + */ | ||
| 386 | + fun pause() { | ||
| 387 | + if (isPaused()) { | ||
| 388 | + throw AlreadyPaused() | ||
| 389 | + } else if (isStopped()) { | ||
| 390 | + throw AlreadyStopped() | ||
| 391 | + } | ||
| 392 | + | ||
| 393 | + releaseCamera() | ||
| 394 | + } | ||
| 395 | + | ||
| 383 | /** | 396 | /** |
| 384 | * Stop barcode scanning. | 397 | * Stop barcode scanning. |
| 385 | */ | 398 | */ |
| 386 | fun stop() { | 399 | fun stop() { |
| 387 | - if (isStopped()) { | 400 | + if (!isPaused() && isStopped()) { |
| 388 | throw AlreadyStopped() | 401 | throw AlreadyStopped() |
| 389 | } | 402 | } |
| 390 | 403 | ||
| 404 | + releaseCamera() | ||
| 405 | + releaseTexture() | ||
| 406 | + } | ||
| 407 | + | ||
| 408 | + private fun releaseCamera() { | ||
| 391 | if (displayListener != null) { | 409 | if (displayListener != null) { |
| 392 | val displayManager = activity.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager | 410 | val displayManager = activity.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager |
| 393 | 411 | ||
| @@ -398,15 +416,19 @@ class MobileScanner( | @@ -398,15 +416,19 @@ class MobileScanner( | ||
| 398 | val owner = activity as LifecycleOwner | 416 | val owner = activity as LifecycleOwner |
| 399 | camera?.cameraInfo?.torchState?.removeObservers(owner) | 417 | camera?.cameraInfo?.torchState?.removeObservers(owner) |
| 400 | cameraProvider?.unbindAll() | 418 | cameraProvider?.unbindAll() |
| 401 | - textureEntry?.release() | ||
| 402 | 419 | ||
| 403 | camera = null | 420 | camera = null |
| 404 | preview = null | 421 | preview = null |
| 405 | - textureEntry = null | ||
| 406 | cameraProvider = null | 422 | cameraProvider = null |
| 407 | } | 423 | } |
| 408 | 424 | ||
| 425 | + private fun releaseTexture() { | ||
| 426 | + textureEntry?.release() | ||
| 427 | + textureEntry = null | ||
| 428 | + } | ||
| 429 | + | ||
| 409 | private fun isStopped() = camera == null && preview == null | 430 | private fun isStopped() = camera == null && preview == null |
| 431 | + private fun isPaused() = isStopped() && textureEntry != null | ||
| 410 | 432 | ||
| 411 | /** | 433 | /** |
| 412 | * Toggles the flash light on or off. | 434 | * Toggles the flash light on or off. |
| @@ -3,6 +3,7 @@ package dev.steenbakker.mobile_scanner | @@ -3,6 +3,7 @@ package dev.steenbakker.mobile_scanner | ||
| 3 | class NoCamera : Exception() | 3 | class NoCamera : Exception() |
| 4 | class AlreadyStarted : Exception() | 4 | class AlreadyStarted : Exception() |
| 5 | class AlreadyStopped : Exception() | 5 | class AlreadyStopped : Exception() |
| 6 | +class AlreadyPaused : Exception() | ||
| 6 | class CameraError : Exception() | 7 | class CameraError : Exception() |
| 7 | class ZoomWhenStopped : Exception() | 8 | class ZoomWhenStopped : Exception() |
| 8 | class ZoomNotInRange : Exception() | 9 | class ZoomNotInRange : Exception() |
| @@ -122,6 +122,7 @@ class MobileScannerHandler( | @@ -122,6 +122,7 @@ class MobileScannerHandler( | ||
| 122 | }) | 122 | }) |
| 123 | "start" -> start(call, result) | 123 | "start" -> start(call, result) |
| 124 | "torch" -> toggleTorch(call, result) | 124 | "torch" -> toggleTorch(call, result) |
| 125 | + "pause" -> pause(result) | ||
| 125 | "stop" -> stop(result) | 126 | "stop" -> stop(result) |
| 126 | "analyzeImage" -> analyzeImage(call, result) | 127 | "analyzeImage" -> analyzeImage(call, result) |
| 127 | "setScale" -> setScale(call, result) | 128 | "setScale" -> setScale(call, result) |
| @@ -227,6 +228,18 @@ class MobileScannerHandler( | @@ -227,6 +228,18 @@ class MobileScannerHandler( | ||
| 227 | ) | 228 | ) |
| 228 | } | 229 | } |
| 229 | 230 | ||
| 231 | + private fun pause(result: MethodChannel.Result) { | ||
| 232 | + try { | ||
| 233 | + mobileScanner!!.pause() | ||
| 234 | + result.success(null) | ||
| 235 | + } catch (e: Exception) { | ||
| 236 | + when (e) { | ||
| 237 | + is AlreadyPaused, is AlreadyStopped -> result.success(null) | ||
| 238 | + else -> throw e | ||
| 239 | + } | ||
| 240 | + } | ||
| 241 | + } | ||
| 242 | + | ||
| 230 | private fun stop(result: MethodChannel.Result) { | 243 | private fun stop(result: MethodChannel.Result) { |
| 231 | try { | 244 | try { |
| 232 | mobileScanner!!.stop() | 245 | mobileScanner!!.stop() |
| @@ -106,6 +106,7 @@ class _BarcodeScannerWithControllerState | @@ -106,6 +106,7 @@ class _BarcodeScannerWithControllerState | ||
| 106 | children: [ | 106 | children: [ |
| 107 | ToggleFlashlightButton(controller: controller), | 107 | ToggleFlashlightButton(controller: controller), |
| 108 | StartStopMobileScannerButton(controller: controller), | 108 | StartStopMobileScannerButton(controller: controller), |
| 109 | + PauseMobileScannerButton(controller: controller), | ||
| 109 | Expanded(child: Center(child: _buildBarcode(_barcode))), | 110 | Expanded(child: Center(child: _buildBarcode(_barcode))), |
| 110 | SwitchCameraButton(controller: controller), | 111 | SwitchCameraButton(controller: controller), |
| 111 | AnalyzeImageFromGalleryButton(controller: controller), | 112 | AnalyzeImageFromGalleryButton(controller: controller), |
| @@ -166,3 +166,30 @@ class ToggleFlashlightButton extends StatelessWidget { | @@ -166,3 +166,30 @@ class ToggleFlashlightButton extends StatelessWidget { | ||
| 166 | ); | 166 | ); |
| 167 | } | 167 | } |
| 168 | } | 168 | } |
| 169 | + | ||
| 170 | +class PauseMobileScannerButton extends StatelessWidget { | ||
| 171 | + const PauseMobileScannerButton({required this.controller, super.key}); | ||
| 172 | + | ||
| 173 | + final MobileScannerController controller; | ||
| 174 | + | ||
| 175 | + @override | ||
| 176 | + Widget build(BuildContext context) { | ||
| 177 | + return ValueListenableBuilder( | ||
| 178 | + valueListenable: controller, | ||
| 179 | + builder: (context, state, child) { | ||
| 180 | + if (!state.isInitialized || !state.isRunning) { | ||
| 181 | + return const SizedBox.shrink(); | ||
| 182 | + } | ||
| 183 | + | ||
| 184 | + return IconButton( | ||
| 185 | + color: Colors.white, | ||
| 186 | + iconSize: 32.0, | ||
| 187 | + icon: const Icon(Icons.pause), | ||
| 188 | + onPressed: () async { | ||
| 189 | + await controller.pause(); | ||
| 190 | + }, | ||
| 191 | + ); | ||
| 192 | + }, | ||
| 193 | + ); | ||
| 194 | + } | ||
| 195 | +} |
| @@ -60,6 +60,14 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -60,6 +60,14 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 60 | private var imagesCurrentlyBeingProcessed = false | 60 | private var imagesCurrentlyBeingProcessed = false |
| 61 | 61 | ||
| 62 | public var timeoutSeconds: Double = 0 | 62 | public var timeoutSeconds: Double = 0 |
| 63 | + | ||
| 64 | + private var stopped: Bool { | ||
| 65 | + return device == nil || captureSession == nil | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + private var paused: Bool { | ||
| 69 | + return stopped && textureId != nil | ||
| 70 | + } | ||
| 63 | 71 | ||
| 64 | init(registry: FlutterTextureRegistry?, mobileScannerCallback: @escaping MobileScannerCallback, torchModeChangeCallback: @escaping TorchModeChangeCallback, zoomScaleChangeCallback: @escaping ZoomScaleChangeCallback) { | 72 | init(registry: FlutterTextureRegistry?, mobileScannerCallback: @escaping MobileScannerCallback, torchModeChangeCallback: @escaping TorchModeChangeCallback, zoomScaleChangeCallback: @escaping ZoomScaleChangeCallback) { |
| 65 | self.registry = registry | 73 | self.registry = registry |
| @@ -126,6 +134,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -126,6 +134,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 126 | 134 | ||
| 127 | /// Gets called when a new image is added to the buffer | 135 | /// Gets called when a new image is added to the buffer |
| 128 | public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { | 136 | public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { |
| 137 | + | ||
| 129 | guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { | 138 | guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { |
| 130 | print("Failed to get image buffer from sample buffer.") | 139 | print("Failed to get image buffer from sample buffer.") |
| 131 | return | 140 | return |
| @@ -180,7 +189,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -180,7 +189,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 180 | barcodesString = nil | 189 | barcodesString = nil |
| 181 | scanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner() | 190 | scanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner() |
| 182 | captureSession = AVCaptureSession() | 191 | captureSession = AVCaptureSession() |
| 183 | - textureId = registry?.register(self) | 192 | + textureId = textureId ?? registry?.register(self) |
| 184 | 193 | ||
| 185 | // Open the camera device | 194 | // Open the camera device |
| 186 | device = getDefaultCameraDevice(position: cameraPosition) | 195 | device = getDefaultCameraDevice(position: cameraPosition) |
| @@ -300,28 +309,50 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | @@ -300,28 +309,50 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega | ||
| 300 | completion(MobileScannerStartParameters()) | 309 | completion(MobileScannerStartParameters()) |
| 301 | } | 310 | } |
| 302 | } | 311 | } |
| 312 | + | ||
| 313 | + /// Pause scanning for barcodes | ||
| 314 | + func pause() throws { | ||
| 315 | + if (paused) { | ||
| 316 | + throw MobileScannerError.alreadyPaused | ||
| 317 | + } else if (stopped) { | ||
| 318 | + throw MobileScannerError.alreadyStopped | ||
| 319 | + } | ||
| 320 | + releaseCamera() | ||
| 321 | + } | ||
| 303 | 322 | ||
| 304 | /// Stop scanning for barcodes | 323 | /// Stop scanning for barcodes |
| 305 | func stop() throws { | 324 | func stop() throws { |
| 306 | - if (device == nil || captureSession == nil) { | 325 | + if (!paused && stopped) { |
| 307 | throw MobileScannerError.alreadyStopped | 326 | throw MobileScannerError.alreadyStopped |
| 308 | } | 327 | } |
| 328 | + releaseCamera() | ||
| 329 | + releaseTexture() | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + private func releaseCamera() { | ||
| 333 | + | ||
| 334 | + guard let captureSession = captureSession else { | ||
| 335 | + return | ||
| 336 | + } | ||
| 309 | 337 | ||
| 310 | - captureSession!.stopRunning() | ||
| 311 | - for input in captureSession!.inputs { | ||
| 312 | - captureSession!.removeInput(input) | 338 | + captureSession.stopRunning() |
| 339 | + for input in captureSession.inputs { | ||
| 340 | + captureSession.removeInput(input) | ||
| 313 | } | 341 | } |
| 314 | - for output in captureSession!.outputs { | ||
| 315 | - captureSession!.removeOutput(output) | 342 | + for output in captureSession.outputs { |
| 343 | + captureSession.removeOutput(output) | ||
| 316 | } | 344 | } |
| 317 | 345 | ||
| 318 | latestBuffer = nil | 346 | latestBuffer = nil |
| 319 | device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode)) | 347 | device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode)) |
| 320 | device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.videoZoomFactor)) | 348 | device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.videoZoomFactor)) |
| 349 | + self.captureSession = nil | ||
| 350 | + device = nil | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + private func releaseTexture() { | ||
| 321 | registry?.unregisterTexture(textureId) | 354 | registry?.unregisterTexture(textureId) |
| 322 | textureId = nil | 355 | textureId = nil |
| 323 | - captureSession = nil | ||
| 324 | - device = nil | ||
| 325 | } | 356 | } |
| 326 | 357 | ||
| 327 | /// Set the torch mode. | 358 | /// Set the torch mode. |
| @@ -10,6 +10,7 @@ enum MobileScannerError: Error { | @@ -10,6 +10,7 @@ enum MobileScannerError: Error { | ||
| 10 | case noCamera | 10 | case noCamera |
| 11 | case alreadyStarted | 11 | case alreadyStarted |
| 12 | case alreadyStopped | 12 | case alreadyStopped |
| 13 | + case alreadyPaused | ||
| 13 | case cameraError(_ error: Error) | 14 | case cameraError(_ error: Error) |
| 14 | case zoomWhenStopped | 15 | case zoomWhenStopped |
| 15 | case zoomError(_ error: Error) | 16 | case zoomError(_ error: Error) |
| @@ -78,6 +78,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { | @@ -78,6 +78,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 78 | AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) }) | 78 | AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) }) |
| 79 | case "start": | 79 | case "start": |
| 80 | start(call, result) | 80 | start(call, result) |
| 81 | + case "pause": | ||
| 82 | + pause(result) | ||
| 81 | case "stop": | 83 | case "stop": |
| 82 | stop(result) | 84 | stop(result) |
| 83 | case "torch": | 85 | case "torch": |
| @@ -146,6 +148,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { | @@ -146,6 +148,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { | ||
| 146 | details: nil)) | 148 | details: nil)) |
| 147 | } | 149 | } |
| 148 | } | 150 | } |
| 151 | + | ||
| 152 | + /// Stops the mobileScanner without closing the texture. | ||
| 153 | + private func pause(_ result: @escaping FlutterResult) { | ||
| 154 | + do { | ||
| 155 | + try mobileScanner.pause() | ||
| 156 | + } catch {} | ||
| 157 | + result(nil) | ||
| 158 | + } | ||
| 149 | 159 | ||
| 150 | /// Stops the mobileScanner and closes the texture. | 160 | /// Stops the mobileScanner and closes the texture. |
| 151 | private func stop(_ result: @escaping FlutterResult) { | 161 | private func stop(_ result: @escaping FlutterResult) { |
| @@ -38,6 +38,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | @@ -38,6 +38,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | ||
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | int? _textureId; | 40 | int? _textureId; |
| 41 | + bool _pausing = false; | ||
| 41 | 42 | ||
| 42 | /// Parse a [BarcodeCapture] from the given [event]. | 43 | /// Parse a [BarcodeCapture] from the given [event]. |
| 43 | BarcodeCapture? _parseBarcode(Map<Object?, Object?>? event) { | 44 | BarcodeCapture? _parseBarcode(Map<Object?, Object?>? event) { |
| @@ -206,7 +207,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | @@ -206,7 +207,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | ||
| 206 | 207 | ||
| 207 | @override | 208 | @override |
| 208 | Future<MobileScannerViewAttributes> start(StartOptions startOptions) async { | 209 | Future<MobileScannerViewAttributes> start(StartOptions startOptions) async { |
| 209 | - if (_textureId != null) { | 210 | + if (!_pausing && _textureId != null) { |
| 210 | throw const MobileScannerException( | 211 | throw const MobileScannerException( |
| 211 | errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, | 212 | errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, |
| 212 | errorDetails: MobileScannerErrorDetails( | 213 | errorDetails: MobileScannerErrorDetails( |
| @@ -274,6 +275,8 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | @@ -274,6 +275,8 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | ||
| 274 | size = Size(width, height); | 275 | size = Size(width, height); |
| 275 | } | 276 | } |
| 276 | 277 | ||
| 278 | + _pausing = false; | ||
| 279 | + | ||
| 277 | return MobileScannerViewAttributes( | 280 | return MobileScannerViewAttributes( |
| 278 | hasTorch: hasTorch, | 281 | hasTorch: hasTorch, |
| 279 | numberOfCameras: numberOfCameras, | 282 | numberOfCameras: numberOfCameras, |
| @@ -288,10 +291,23 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | @@ -288,10 +291,23 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { | ||
| 288 | } | 291 | } |
| 289 | 292 | ||
| 290 | _textureId = null; | 293 | _textureId = null; |
| 294 | + _pausing = false; | ||
| 291 | 295 | ||
| 292 | await methodChannel.invokeMethod<void>('stop'); | 296 | await methodChannel.invokeMethod<void>('stop'); |
| 293 | } | 297 | } |
| 294 | 298 | ||
| 299 | + | ||
| 300 | + @override | ||
| 301 | + Future<void> pause() async { | ||
| 302 | + if (_pausing) { | ||
| 303 | + return; | ||
| 304 | + } | ||
| 305 | + | ||
| 306 | + _pausing = true; | ||
| 307 | + | ||
| 308 | + await methodChannel.invokeMethod<void>('pause'); | ||
| 309 | + } | ||
| 310 | + | ||
| 295 | @override | 311 | @override |
| 296 | Future<void> updateScanWindow(Rect? window) async { | 312 | Future<void> updateScanWindow(Rect? window) async { |
| 297 | if (_textureId == null) { | 313 | if (_textureId == null) { |
| @@ -166,6 +166,25 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> { | @@ -166,6 +166,25 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> { | ||
| 166 | } | 166 | } |
| 167 | } | 167 | } |
| 168 | 168 | ||
| 169 | + void _stop() { | ||
| 170 | + // Do nothing if not initialized or already stopped. | ||
| 171 | + // On the web, the permission popup triggers a lifecycle change from resumed to inactive, | ||
| 172 | + // due to the permission popup gaining focus. | ||
| 173 | + // This would 'stop' the camera while it is not ready yet. | ||
| 174 | + if (!value.isInitialized || !value.isRunning || _isDisposed) { | ||
| 175 | + return; | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + _disposeListeners(); | ||
| 179 | + | ||
| 180 | + // After the camera stopped, set the torch state to off, | ||
| 181 | + // as the torch state callback is never called when the camera is stopped. | ||
| 182 | + value = value.copyWith( | ||
| 183 | + isRunning: false, | ||
| 184 | + torchState: TorchState.off, | ||
| 185 | + ); | ||
| 186 | + } | ||
| 187 | + | ||
| 169 | /// Analyze an image file. | 188 | /// Analyze an image file. |
| 170 | /// | 189 | /// |
| 171 | /// The [path] points to a file on the device. | 190 | /// The [path] points to a file on the device. |
| @@ -301,26 +320,21 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> { | @@ -301,26 +320,21 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> { | ||
| 301 | /// | 320 | /// |
| 302 | /// Does nothing if the camera is already stopped. | 321 | /// Does nothing if the camera is already stopped. |
| 303 | Future<void> stop() async { | 322 | Future<void> stop() async { |
| 304 | - // Do nothing if not initialized or already stopped. | ||
| 305 | - // On the web, the permission popup triggers a lifecycle change from resumed to inactive, | ||
| 306 | - // due to the permission popup gaining focus. | ||
| 307 | - // This would 'stop' the camera while it is not ready yet. | ||
| 308 | - if (!value.isInitialized || !value.isRunning || _isDisposed) { | ||
| 309 | - return; | ||
| 310 | - } | ||
| 311 | - | ||
| 312 | - _disposeListeners(); | ||
| 313 | - | ||
| 314 | - // After the camera stopped, set the torch state to off, | ||
| 315 | - // as the torch state callback is never called when the camera is stopped. | ||
| 316 | - value = value.copyWith( | ||
| 317 | - isRunning: false, | ||
| 318 | - torchState: TorchState.off, | ||
| 319 | - ); | ||
| 320 | - | 323 | + _stop(); |
| 321 | await MobileScannerPlatform.instance.stop(); | 324 | await MobileScannerPlatform.instance.stop(); |
| 322 | } | 325 | } |
| 323 | 326 | ||
| 327 | + /// Pause the camera. | ||
| 328 | + /// | ||
| 329 | + /// This method stops to update camera frame and scan barcodes. | ||
| 330 | + /// After calling this method, the camera can be restarted using [start]. | ||
| 331 | + /// | ||
| 332 | + /// Does nothing if the camera is already paused or stopped. | ||
| 333 | + Future<void> pause() async { | ||
| 334 | + _stop(); | ||
| 335 | + await MobileScannerPlatform.instance.pause(); | ||
| 336 | + } | ||
| 337 | + | ||
| 324 | /// Switch between the front and back camera. | 338 | /// Switch between the front and back camera. |
| 325 | /// | 339 | /// |
| 326 | /// Does nothing if the device has less than 2 cameras. | 340 | /// Does nothing if the device has less than 2 cameras. |
| @@ -95,6 +95,12 @@ abstract class MobileScannerPlatform extends PlatformInterface { | @@ -95,6 +95,12 @@ abstract class MobileScannerPlatform extends PlatformInterface { | ||
| 95 | throw UnimplementedError('stop() has not been implemented.'); | 95 | throw UnimplementedError('stop() has not been implemented.'); |
| 96 | } | 96 | } |
| 97 | 97 | ||
| 98 | + /// Pause the camera. | ||
| 99 | + Future<void> pause() { | ||
| 100 | + throw UnimplementedError('pause() has not been implemented.'); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + | ||
| 98 | /// Update the scan window to the given [window] rectangle. | 104 | /// Update the scan window to the given [window] rectangle. |
| 99 | /// | 105 | /// |
| 100 | /// Any barcodes that do not intersect with the given [window] will be ignored. | 106 | /// Any barcodes that do not intersect with the given [window] will be ignored. |
-
Please register or login to post a comment