Toggle navigation
Toggle navigation
This project
Loading...
Sign in
flutter_package
/
mobile_scanner
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
Julian Steenbakker
2025-01-12 15:05:13 +0100
Browse Files
Options
Browse Files
Download
Plain Diff
Committed by
GitHub
2025-01-12 15:05:13 +0100
Commit
b4a084cf38bc7ad27423b3fb1bd8f6bbbb6b09b0
b4a084cf
2 parents
bc319115
1d19bc74
Merge pull request #994 from fumin65/pause_function
feat: add pause feature
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
239 additions
and
47 deletions
android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt
android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerExceptions.kt
android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt
example/lib/barcode_scanner_controller.dart
example/lib/scanner_button_widgets.dart
ios/Classes/MobileScanner.swift
ios/Classes/MobileScannerError.swift
ios/Classes/MobileScannerPlugin.swift
lib/src/method_channel/mobile_scanner_method_channel.dart
lib/src/mobile_scanner_controller.dart
lib/src/mobile_scanner_platform_interface.dart
lib/src/web/barcode_reader.dart
lib/src/web/mobile_scanner_web.dart
lib/src/web/zxing/zxing_barcode_reader.dart
macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift
android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt
View file @
b4a084c
...
...
@@ -273,7 +273,7 @@ class MobileScanner(
}
cameraProvider?.unbindAll()
textureEntry = textureRegistry.createSurfaceTexture()
textureEntry = texture
Entry ?: texture
Registry.createSurfaceTexture()
// Preview
val surfaceProvider = Preview.SurfaceProvider { request ->
...
...
@@ -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
...
...
@@ -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()
...
...
@@ -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.
...
...
android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerExceptions.kt
View file @
b4a084c
...
...
@@ -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()
\ No newline at end of file
...
...
android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt
View file @
b4a084c
...
...
@@ -118,6 +118,7 @@ class MobileScannerHandler(
}
})
"start" -> start(call, result)
"pause" -> pause(result)
"stop" -> stop(result)
"toggleTorch" -> toggleTorch(result)
"analyzeImage" -> analyzeImage(call, result)
...
...
@@ -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()
...
...
example/lib/barcode_scanner_controller.dart
View file @
b4a084c
...
...
@@ -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
),
...
...
example/lib/scanner_button_widgets.dart
View file @
b4a084c
...
...
@@ -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
();
},
);
},
);
}
}
...
...
ios/Classes/MobileScanner.swift
View file @
b4a084c
...
...
@@ -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
...
...
@@ -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
}
...
...
@@ -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
)
...
...
@@ -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
}
releaseCamera
()
releaseTexture
()
}
private
func
releaseCamera
()
{
guard
let
captureSession
=
captureSession
else
{
return
}
captureSession
!.
stopRunning
()
for
input
in
captureSession
!.
inputs
{
captureSession
!.
removeInput
(
input
)
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
}
...
...
ios/Classes/MobileScannerError.swift
View file @
b4a084c
...
...
@@ -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
)
...
...
ios/Classes/MobileScannerPlugin.swift
View file @
b4a084c
...
...
@@ -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"
:
...
...
@@ -167,6 +169,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
}
}
/// 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
)
{
do
{
...
...
lib/src/method_channel/mobile_scanner_method_channel.dart
View file @
b4a084c
...
...
@@ -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
)
{
...
...
@@ -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
(
...
...
@@ -281,6 +282,8 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
size
=
Size
.
zero
;
}
_pausing
=
false
;
return
MobileScannerViewAttributes
(
currentTorchMode:
currentTorchState
,
numberOfCameras:
numberOfCameras
,
...
...
@@ -295,11 +298,23 @@ 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'
);
}
...
...
lib/src/mobile_scanner_controller.dart
View file @
b4a084c
...
...
@@ -183,6 +183,30 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
}
}
void
_stop
()
{
// Do nothing if not initialized or already stopped.
// On the web, the permission popup triggers a lifecycle change from resumed to inactive,
// due to the permission popup gaining focus.
// This would 'stop' the camera while it is not ready yet.
if
(!
value
.
isInitialized
||
!
value
.
isRunning
||
_isDisposed
)
{
return
;
}
_disposeListeners
();
final
TorchState
oldTorchState
=
value
.
torchState
;
// After the camera stopped, set the torch state to off,
// as the torch state callback is never called when the camera is stopped.
// If the device does not have a torch, do not report "off".
value
=
value
.
copyWith
(
isRunning:
false
,
torchState:
oldTorchState
==
TorchState
.
unavailable
?
TorchState
.
unavailable
:
TorchState
.
off
,
);
}
/// Analyze an image file.
///
/// The [path] points to a file on the device.
...
...
@@ -336,29 +360,19 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
///
/// Does nothing if the camera is already stopped.
Future
<
void
>
stop
()
async
{
// Do nothing if not initialized or already stopped.
// On the web, the permission popup triggers a lifecycle change from resumed to inactive,
// due to the permission popup gaining focus.
// This would 'stop' the camera while it is not ready yet.
if
(!
value
.
isInitialized
||
!
value
.
isRunning
||
_isDisposed
)
{
return
;
_stop
();
await
MobileScannerPlatform
.
instance
.
stop
();
}
_disposeListeners
();
final
TorchState
oldTorchState
=
value
.
torchState
;
// After the camera stopped, set the torch state to off,
// as the torch state callback is never called when the camera is stopped.
// If the device does not have a torch, do not report "off".
value
=
value
.
copyWith
(
isRunning:
false
,
torchState:
oldTorchState
==
TorchState
.
unavailable
?
TorchState
.
unavailable
:
TorchState
.
off
,
);
await
MobileScannerPlatform
.
instance
.
stop
();
/// Pause the camera.
///
/// This method stops to update camera frame and scan barcodes.
/// After calling this method, the camera can be restarted using [start].
///
/// Does nothing if the camera is already paused or stopped.
Future
<
void
>
pause
()
async
{
_stop
();
await
MobileScannerPlatform
.
instance
.
pause
();
}
/// Switch between the front and back camera.
...
...
lib/src/mobile_scanner_platform_interface.dart
View file @
b4a084c
...
...
@@ -97,6 +97,11 @@ abstract class MobileScannerPlatform extends PlatformInterface {
throw
UnimplementedError
(
'stop() has not been implemented.'
);
}
/// Pause the camera.
Future
<
void
>
pause
()
{
throw
UnimplementedError
(
'pause() has not been implemented.'
);
}
/// Toggle the torch on the active camera on or off.
Future
<
void
>
toggleTorch
()
{
throw
UnimplementedError
(
'toggleTorch() has not been implemented.'
);
...
...
lib/src/web/barcode_reader.dart
View file @
b4a084c
...
...
@@ -128,6 +128,11 @@ abstract class BarcodeReader {
throw
UnimplementedError
(
'start() has not been implemented.'
);
}
/// Pause the barcode reader.
Future
<
void
>
pause
()
{
throw
UnimplementedError
(
'pause() has not been implemented.'
);
}
/// Stop the barcode reader and dispose of the video stream.
Future
<
void
>
stop
()
{
throw
UnimplementedError
(
'stop() has not been implemented.'
);
...
...
lib/src/web/mobile_scanner_web.dart
View file @
b4a084c
...
...
@@ -271,6 +271,11 @@ class MobileScannerWeb extends MobileScannerPlatform {
);
}
// If the previous state is a pause, reset scanner.
if
(
_barcodesSubscription
!=
null
&&
_barcodesSubscription
!.
isPaused
)
{
await
stop
();
}
_barcodeReader
=
ZXingBarcodeReader
();
await
_barcodeReader
?.
maybeLoadLibrary
(
...
...
@@ -358,6 +363,12 @@ class MobileScannerWeb extends MobileScannerPlatform {
}
@override
Future
<
void
>
pause
()
async
{
_barcodesSubscription
?.
pause
();
await
_barcodeReader
?.
pause
();
}
@override
Future
<
void
>
stop
()
async
{
// Ensure the barcode scanner is stopped, by cancelling the subscription.
await
_barcodesSubscription
?.
cancel
();
...
...
lib/src/web/zxing/zxing_barcode_reader.dart
View file @
b4a084c
...
...
@@ -169,6 +169,11 @@ final class ZXingBarcodeReader extends BarcodeReader {
}
@override
Future
<
void
>
pause
()
async
{
_reader
?.
videoElement
?.
pause
();
}
@override
Future
<
void
>
stop
()
async
{
_onMediaTrackSettingsChanged
=
null
;
_reader
?.
stopContinuousDecode
.
callAsFunction
(
_reader
);
...
...
macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift
View file @
b4a084c
...
...
@@ -38,6 +38,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
var
position
=
AVCaptureDevice
.
Position
.
back
private
var
stopped
:
Bool
{
return
device
==
nil
||
captureSession
==
nil
}
private
var
paused
:
Bool
{
return
stopped
&&
textureId
!=
nil
}
public
static
func
register
(
with
registrar
:
FlutterPluginRegistrar
)
{
let
instance
=
MobileScannerPlugin
(
registrar
.
textures
)
let
method
=
FlutterMethodChannel
(
name
:
...
...
@@ -67,6 +75,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
setScale
(
call
,
result
)
case
"resetScale"
:
resetScale
(
call
,
result
)
case
"pause"
:
pause
(
result
)
case
"stop"
:
stop
(
result
)
case
"updateScanWindow"
:
...
...
@@ -276,7 +286,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
return
}
textureId
=
registry
.
register
(
self
)
textureId
=
textureId
??
registry
.
register
(
self
)
captureSession
=
AVCaptureSession
()
let
argReader
=
MapArgumentReader
(
call
.
arguments
as?
[
String
:
Any
])
...
...
@@ -438,28 +448,49 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
result
(
nil
)
}
func
pause
(
_
result
:
FlutterResult
)
{
if
(
paused
||
stopped
)
{
result
(
nil
)
return
}
releaseCamera
()
}
func
stop
(
_
result
:
FlutterResult
)
{
if
(
device
==
nil
||
captureSession
==
nil
)
{
if
(
!
paused
&&
stopped
)
{
result
(
nil
)
return
}
captureSession
!.
stopRunning
()
for
input
in
captureSession
!.
inputs
{
captureSession
!.
removeInput
(
input
)
releaseCamera
()
releaseTexture
()
result
(
nil
)
}
for
output
in
captureSession
!.
outputs
{
captureSession
!.
removeOutput
(
output
)
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
)
}
device
.
removeObserver
(
self
,
forKeyPath
:
#
keyPath
(
AVCaptureDevice
.
torchMode
))
registry
.
unregisterTexture
(
textureId
)
latestBuffer
=
nil
captureSession
=
nil
self
.
captureSession
=
nil
device
=
nil
textureId
=
nil
}
result
(
nil
)
private
func
releaseTexture
()
{
registry
.
unregisterTexture
(
textureId
)
textureId
=
nil
}
func
analyzeImage
(
_
call
:
FlutterMethodCall
,
_
result
:
@escaping
FlutterResult
)
{
...
...
Please
register
or
login
to post a comment