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
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
276 additions
and
84 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
}
...
...
@@ -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
}
...
...
@@ -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
}
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
}
...
...
@@ -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
)
...
...
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"
:
...
...
@@ -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
)
{
...
...
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,31 +360,21 @@ 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
;
}
_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
,
);
_stop
();
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.
///
/// Does nothing if the device has less than 2 cameras.
...
...
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
...
...
@@ -25,7 +25,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
// optional window to limit scan search
var
scanWindow
:
CGRect
?
/// Whether to return the input image with the barcode event.
/// This is static to avoid accessing `self` in the `VNDetectBarcodesRequest` callback.
private
static
var
returnImage
:
Bool
=
false
...
...
@@ -35,9 +35,17 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
var
timeoutSeconds
:
Double
=
0
var
symbologies
:[
VNBarcodeSymbology
]
=
[]
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"
:
...
...
@@ -128,7 +138,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
do
{
let
barcodeRequest
:
VNDetectBarcodesRequest
=
VNDetectBarcodesRequest
(
completionHandler
:
{
[
weak
self
]
(
request
,
error
)
in
self
?
.
imagesCurrentlyBeingProcessed
=
false
if
error
!=
nil
{
DispatchQueue
.
main
.
async
{
self
?
.
sink
?(
FlutterError
(
...
...
@@ -137,24 +147,24 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}
return
}
guard
let
results
:
[
VNBarcodeObservation
]
=
request
.
results
as?
[
VNBarcodeObservation
]
else
{
return
}
if
results
.
isEmpty
{
return
}
let
barcodes
:
[
VNBarcodeObservation
]
=
results
.
compactMap
({
barcode
in
// If there is a scan window, check if the barcode is within said scan window.
if
self
?
.
scanWindow
!=
nil
&&
cgImage
!=
nil
&&
!
(
self
?
.
isBarCodeInScanWindow
(
self
!.
scanWindow
!
,
barcode
,
cgImage
!
)
??
false
)
{
return
nil
}
return
barcode
})
DispatchQueue
.
main
.
async
{
guard
let
image
=
cgImage
else
{
self
?
.
sink
?([
...
...
@@ -163,7 +173,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
])
return
}
// The image dimensions are always provided.
// The image bytes are only non-null when `returnImage` is true.
let
imageData
:
[
String
:
Any
?]
=
[
...
...
@@ -171,7 +181,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
"width"
:
Double
(
image
.
width
),
"height"
:
Double
(
image
.
height
),
]
self
?
.
sink
?([
"name"
:
"barcode"
,
"data"
:
barcodes
.
map
({
$0
.
toMap
()
}),
...
...
@@ -179,7 +189,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
])
}
})
if
self
?
.
symbologies
.
isEmpty
==
false
{
// Add the symbologies the user wishes to support.
barcodeRequest
.
symbologies
=
self
!.
symbologies
...
...
@@ -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,52 +448,73 @@ 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
)
}
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
)
}
device
.
removeObserver
(
self
,
forKeyPath
:
#
keyPath
(
AVCaptureDevice
.
torchMode
))
registry
.
unregisterTexture
(
textureId
)
latestBuffer
=
nil
captureSession
=
nil
self
.
captureSession
=
nil
device
=
nil
}
private
func
releaseTexture
()
{
registry
.
unregisterTexture
(
textureId
)
textureId
=
nil
result
(
nil
)
}
func
analyzeImage
(
_
call
:
FlutterMethodCall
,
_
result
:
@escaping
FlutterResult
)
{
let
argReader
=
MapArgumentReader
(
call
.
arguments
as?
[
String
:
Any
])
let
symbologies
:[
VNBarcodeSymbology
]
=
argReader
.
toSymbology
()
guard
let
filePath
:
String
=
argReader
.
string
(
key
:
"filePath"
)
else
{
result
(
nil
)
return
}
let
fileUrl
=
URL
(
fileURLWithPath
:
filePath
)
guard
let
ciImage
=
CIImage
(
contentsOf
:
fileUrl
)
else
{
result
(
nil
)
return
}
let
imageRequestHandler
=
VNImageRequestHandler
(
ciImage
:
ciImage
,
orientation
:
CGImagePropertyOrientation
.
up
,
options
:
[:])
do
{
let
barcodeRequest
:
VNDetectBarcodesRequest
=
VNDetectBarcodesRequest
(
completionHandler
:
{
[]
(
request
,
error
)
in
if
error
!=
nil
{
DispatchQueue
.
main
.
async
{
result
(
FlutterError
(
...
...
@@ -492,26 +523,26 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}
return
}
guard
let
barcodes
:
[
VNBarcodeObservation
]
=
request
.
results
as?
[
VNBarcodeObservation
]
else
{
return
}
if
barcodes
.
isEmpty
{
return
}
result
([
"name"
:
"barcode"
,
"data"
:
barcodes
.
map
({
$0
.
toMap
()
}),
])
})
if
!
symbologies
.
isEmpty
{
// Add the symbologies the user wishes to support.
barcodeRequest
.
symbologies
=
symbologies
}
try
imageRequestHandler
.
perform
([
barcodeRequest
])
}
catch
let
error
{
DispatchQueue
.
main
.
async
{
...
...
@@ -521,7 +552,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}
}
}
// Observer for torch state
public
override
func
observeValue
(
forKeyPath
keyPath
:
String
?,
of
object
:
Any
?,
change
:
[
NSKeyValueChangeKey
:
Any
]?,
context
:
UnsafeMutableRawPointer
?)
{
switch
keyPath
{
...
...
@@ -584,29 +615,29 @@ class MapArgumentReader {
extension
CGImage
{
public
func
jpegData
(
compressionQuality
:
CGFloat
)
->
Data
?
{
let
mutableData
=
CFDataCreateMutable
(
nil
,
0
)
let
formatHint
:
CFString
if
#available(macOS 11.0, *)
{
formatHint
=
UTType
.
jpeg
.
identifier
as
CFString
}
else
{
formatHint
=
kUTTypeJPEG
}
guard
let
destination
=
CGImageDestinationCreateWithData
(
mutableData
!
,
formatHint
,
1
,
nil
)
else
{
return
nil
}
let
options
:
NSDictionary
=
[
kCGImageDestinationLossyCompressionQuality
:
compressionQuality
,
]
CGImageDestinationAddImage
(
destination
,
self
,
options
)
if
!
CGImageDestinationFinalize
(
destination
)
{
return
nil
}
return
mutableData
as
Data
?
}
}
...
...
@@ -615,7 +646,7 @@ extension VNBarcodeObservation {
private
func
distanceBetween
(
_
p1
:
CGPoint
,
_
p2
:
CGPoint
)
->
CGFloat
{
return
sqrt
(
pow
(
p1
.
x
-
p2
.
x
,
2
)
+
pow
(
p1
.
y
-
p2
.
y
,
2
))
}
public
func
toMap
()
->
[
String
:
Any
?]
{
return
[
"corners"
:
[
...
...
Please
register
or
login
to post a comment