Julian Steenbakker
Committed by GitHub

Merge pull request #410 from p-mazhnik/pavel/web-format

feat(web): support formats and detectionTimeout arguments
@@ -5,7 +5,9 @@ import 'dart:ui' as ui; @@ -5,7 +5,9 @@ import 'dart:ui' as ui;
5 import 'package:flutter/services.dart'; 5 import 'package:flutter/services.dart';
6 import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 6 import 'package:flutter_web_plugins/flutter_web_plugins.dart';
7 import 'package:mobile_scanner/mobile_scanner_web.dart'; 7 import 'package:mobile_scanner/mobile_scanner_web.dart';
  8 +import 'package:mobile_scanner/src/barcode_utility.dart';
8 import 'package:mobile_scanner/src/enums/camera_facing.dart'; 9 import 'package:mobile_scanner/src/enums/camera_facing.dart';
  10 +import 'package:mobile_scanner/src/objects/barcode.dart';
9 11
10 /// This plugin is the web implementation of mobile_scanner. 12 /// This plugin is the web implementation of mobile_scanner.
11 /// It only supports QR codes. 13 /// It only supports QR codes.
@@ -102,8 +104,23 @@ class MobileScannerWebPlugin { @@ -102,8 +104,23 @@ class MobileScannerWebPlugin {
102 }; 104 };
103 } 105 }
104 try { 106 try {
  107 + List<BarcodeFormat>? formats;
  108 + if (arguments.containsKey('formats')) {
  109 + formats = (arguments['formats'] as List)
  110 + .cast<int>()
  111 + .map((e) => toFormat(e))
  112 + .toList();
  113 + }
  114 + final Duration? detectionTimeout;
  115 + if (arguments.containsKey('timeout')) {
  116 + detectionTimeout = Duration(milliseconds: arguments['timeout'] as int);
  117 + } else {
  118 + detectionTimeout = null;
  119 + }
105 await barCodeReader.start( 120 await barCodeReader.start(
106 cameraFacing: cameraFacing, 121 cameraFacing: cameraFacing,
  122 + formats: formats,
  123 + detectionTimeout: detectionTimeout,
107 ); 124 );
108 125
109 _barCodeStreamSubscription = 126 _barCodeStreamSubscription =
@@ -114,6 +131,7 @@ class MobileScannerWebPlugin { @@ -114,6 +131,7 @@ class MobileScannerWebPlugin {
114 'data': { 131 'data': {
115 'rawValue': code.rawValue, 132 'rawValue': code.rawValue,
116 'rawBytes': code.rawBytes, 133 'rawBytes': code.rawBytes,
  134 + 'format': code.format.rawValue,
117 }, 135 },
118 }); 136 });
119 } 137 }
@@ -136,10 +136,10 @@ class MobileScannerController { @@ -136,10 +136,10 @@ class MobileScannerController {
136 } */ 136 } */
137 137
138 if (formats != null) { 138 if (formats != null) {
139 - if (Platform.isAndroid) {  
140 - arguments['formats'] = formats!.map((e) => e.index).toList();  
141 - } else if (Platform.isIOS || Platform.isMacOS) { 139 + if (kIsWeb || Platform.isIOS || Platform.isMacOS) {
142 arguments['formats'] = formats!.map((e) => e.rawValue).toList(); 140 arguments['formats'] = formats!.map((e) => e.rawValue).toList();
  141 + } else if (Platform.isAndroid) {
  142 + arguments['formats'] = formats!.map((e) => e.index).toList();
143 } 143 }
144 } 144 }
145 arguments['returnImage'] = true; 145 arguments['returnImage'] = true;
@@ -363,10 +363,12 @@ class MobileScannerController { @@ -363,10 +363,12 @@ class MobileScannerController {
363 _barcodesController.add( 363 _barcodesController.add(
364 BarcodeCapture( 364 BarcodeCapture(
365 barcodes: [ 365 barcodes: [
  366 + if (barcode != null)
366 Barcode( 367 Barcode(
367 - rawValue: barcode?['rawValue'] as String?,  
368 - rawBytes: barcode?['rawBytes'] as Uint8List?,  
369 - ) 368 + rawValue: barcode['rawValue'] as String?,
  369 + rawBytes: barcode['rawBytes'] as Uint8List?,
  370 + format: toFormat(barcode['format'] as int),
  371 + ),
370 ], 372 ],
371 ), 373 ),
372 ); 374 );
@@ -9,12 +9,11 @@ import 'package:mobile_scanner/src/web/media.dart'; @@ -9,12 +9,11 @@ import 'package:mobile_scanner/src/web/media.dart';
9 9
10 abstract class WebBarcodeReaderBase { 10 abstract class WebBarcodeReaderBase {
11 /// Timer used to capture frames to be analyzed 11 /// Timer used to capture frames to be analyzed
12 - final Duration frameInterval; 12 + Duration frameInterval = const Duration(milliseconds: 200);
13 final html.DivElement videoContainer; 13 final html.DivElement videoContainer;
14 14
15 - const WebBarcodeReaderBase({ 15 + WebBarcodeReaderBase({
16 required this.videoContainer, 16 required this.videoContainer,
17 - this.frameInterval = const Duration(milliseconds: 200),  
18 }); 17 });
19 18
20 bool get isStarted; 19 bool get isStarted;
@@ -25,6 +24,8 @@ abstract class WebBarcodeReaderBase { @@ -25,6 +24,8 @@ abstract class WebBarcodeReaderBase {
25 /// Starts streaming video 24 /// Starts streaming video
26 Future<void> start({ 25 Future<void> start({
27 required CameraFacing cameraFacing, 26 required CameraFacing cameraFacing,
  27 + List<BarcodeFormat>? formats,
  28 + Duration? detectionTimeout,
28 }); 29 });
29 30
30 /// Starts scanning QR codes or barcodes 31 /// Starts scanning QR codes or barcodes
@@ -158,3 +159,14 @@ class ImageCapture { @@ -158,3 +159,14 @@ class ImageCapture {
158 extension ImageCaptureExt on ImageCapture { 159 extension ImageCaptureExt on ImageCapture {
159 external Promise<PhotoCapabilities> getPhotoCapabilities(); 160 external Promise<PhotoCapabilities> getPhotoCapabilities();
160 } 161 }
  162 +
  163 +@JS('Map')
  164 +@staticInterop
  165 +class JsMap {
  166 + external factory JsMap();
  167 +}
  168 +
  169 +extension JsMapExt on JsMap {
  170 + external void set(dynamic key, dynamic value);
  171 + external dynamic get(dynamic key);
  172 +}
@@ -35,9 +35,15 @@ class JsQrCodeReader extends WebBarcodeReaderBase @@ -35,9 +35,15 @@ class JsQrCodeReader extends WebBarcodeReaderBase
35 @override 35 @override
36 Future<void> start({ 36 Future<void> start({
37 required CameraFacing cameraFacing, 37 required CameraFacing cameraFacing,
  38 + List<BarcodeFormat>? formats,
  39 + Duration? detectionTimeout,
38 }) async { 40 }) async {
39 videoContainer.children = [video]; 41 videoContainer.children = [video];
40 42
  43 + if (detectionTimeout != null) {
  44 + frameInterval = detectionTimeout;
  45 + }
  46 +
41 final stream = await initMediaStream(cameraFacing); 47 final stream = await initMediaStream(cameraFacing);
42 48
43 prepareVideoElement(video); 49 prepareVideoElement(video);
@@ -43,12 +43,14 @@ extension ResultExt on Result { @@ -43,12 +43,14 @@ extension ResultExt on Result {
43 /// https://github.com/zxing-js/library/blob/1e9ccb3b6b28d75b9eef866dba196d8937eb4449/src/core/BarcodeFormat.ts#L28 43 /// https://github.com/zxing-js/library/blob/1e9ccb3b6b28d75b9eef866dba196d8937eb4449/src/core/BarcodeFormat.ts#L28
44 BarcodeFormat get barcodeFormat { 44 BarcodeFormat get barcodeFormat {
45 switch (format) { 45 switch (format) {
46 - case 1: 46 + case 0:
47 return BarcodeFormat.aztec; 47 return BarcodeFormat.aztec;
48 - case 2: 48 + case 1:
49 return BarcodeFormat.codebar; 49 return BarcodeFormat.codebar;
50 - case 3: 50 + case 2:
51 return BarcodeFormat.code39; 51 return BarcodeFormat.code39;
  52 + case 3:
  53 + return BarcodeFormat.code93;
52 case 4: 54 case 4:
53 return BarcodeFormat.code128; 55 return BarcodeFormat.code128;
54 case 5: 56 case 5:
@@ -79,6 +81,42 @@ extension ResultExt on Result { @@ -79,6 +81,42 @@ extension ResultExt on Result {
79 } 81 }
80 } 82 }
81 83
  84 +extension ZXingBarcodeFormat on BarcodeFormat {
  85 + int get zxingBarcodeFormat {
  86 + switch (this) {
  87 + case BarcodeFormat.aztec:
  88 + return 0;
  89 + case BarcodeFormat.codebar:
  90 + return 1;
  91 + case BarcodeFormat.code39:
  92 + return 2;
  93 + case BarcodeFormat.code93:
  94 + return 3;
  95 + case BarcodeFormat.code128:
  96 + return 4;
  97 + case BarcodeFormat.dataMatrix:
  98 + return 5;
  99 + case BarcodeFormat.ean8:
  100 + return 6;
  101 + case BarcodeFormat.ean13:
  102 + return 7;
  103 + case BarcodeFormat.itf:
  104 + return 8;
  105 + case BarcodeFormat.pdf417:
  106 + return 10;
  107 + case BarcodeFormat.qrCode:
  108 + return 11;
  109 + case BarcodeFormat.upcA:
  110 + return 14;
  111 + case BarcodeFormat.upcE:
  112 + return 15;
  113 + case BarcodeFormat.unknown:
  114 + case BarcodeFormat.all:
  115 + return -1;
  116 + }
  117 + }
  118 +}
  119 +
82 typedef BarcodeDetectionCallback = void Function( 120 typedef BarcodeDetectionCallback = void Function(
83 Result? result, 121 Result? result,
84 dynamic error, 122 dynamic error,
@@ -136,11 +174,7 @@ extension JsZXingBrowserMultiFormatReaderExt @@ -136,11 +174,7 @@ extension JsZXingBrowserMultiFormatReaderExt
136 /// <script type="text/javascript" src="https://unpkg.com/@zxing/library@0.19.1"></script> 174 /// <script type="text/javascript" src="https://unpkg.com/@zxing/library@0.19.1"></script>
137 class ZXingBarcodeReader extends WebBarcodeReaderBase 175 class ZXingBarcodeReader extends WebBarcodeReaderBase
138 with InternalStreamCreation, InternalTorchDetection { 176 with InternalStreamCreation, InternalTorchDetection {
139 - late final JsZXingBrowserMultiFormatReader _reader =  
140 - JsZXingBrowserMultiFormatReader(  
141 - null,  
142 - frameInterval.inMilliseconds,  
143 - ); 177 + JsZXingBrowserMultiFormatReader? _reader;
144 178
145 ZXingBarcodeReader({required super.videoContainer}); 179 ZXingBarcodeReader({required super.videoContainer});
146 180
@@ -150,7 +184,27 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase @@ -150,7 +184,27 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase
150 @override 184 @override
151 Future<void> start({ 185 Future<void> start({
152 required CameraFacing cameraFacing, 186 required CameraFacing cameraFacing,
  187 + List<BarcodeFormat>? formats,
  188 + Duration? detectionTimeout,
153 }) async { 189 }) async {
  190 + final JsMap? hints;
  191 + if (formats != null && !formats.contains(BarcodeFormat.all)) {
  192 + hints = JsMap();
  193 + final zxingFormats =
  194 + formats.map((e) => e.zxingBarcodeFormat).where((e) => e > 0).toList();
  195 + // set hint DecodeHintType.POSSIBLE_FORMATS
  196 + // https://github.com/zxing-js/library/blob/1e9ccb3b6b28d75b9eef866dba196d8937eb4449/src/core/DecodeHintType.ts#L28
  197 + hints.set(2, zxingFormats);
  198 + } else {
  199 + hints = null;
  200 + }
  201 + if (detectionTimeout != null) {
  202 + frameInterval = detectionTimeout;
  203 + }
  204 + _reader = JsZXingBrowserMultiFormatReader(
  205 + hints,
  206 + frameInterval.inMilliseconds,
  207 + );
154 videoContainer.children = [video]; 208 videoContainer.children = [video];
155 209
156 final stream = await initMediaStream(cameraFacing); 210 final stream = await initMediaStream(cameraFacing);
@@ -163,7 +217,7 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase @@ -163,7 +217,7 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase
163 217
164 @override 218 @override
165 void prepareVideoElement(VideoElement videoSource) { 219 void prepareVideoElement(VideoElement videoSource) {
166 - _reader.prepareVideoElement(videoSource); 220 + _reader?.prepareVideoElement(videoSource);
167 } 221 }
168 222
169 @override 223 @override
@@ -171,9 +225,9 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase @@ -171,9 +225,9 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase
171 MediaStream stream, 225 MediaStream stream,
172 VideoElement videoSource, 226 VideoElement videoSource,
173 ) async { 227 ) async {
174 - _reader.addVideoSource(videoSource, stream);  
175 - _reader.videoElement = videoSource;  
176 - _reader.stream = stream; 228 + _reader?.addVideoSource(videoSource, stream);
  229 + _reader?.videoElement = videoSource;
  230 + _reader?.stream = stream;
177 localMediaStream = stream; 231 localMediaStream = stream;
178 await videoSource.play(); 232 await videoSource.play();
179 } 233 }
@@ -182,7 +236,7 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase @@ -182,7 +236,7 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase
182 Stream<Barcode?> detectBarcodeContinuously() { 236 Stream<Barcode?> detectBarcodeContinuously() {
183 final controller = StreamController<Barcode?>(); 237 final controller = StreamController<Barcode?>();
184 controller.onListen = () async { 238 controller.onListen = () async {
185 - _reader.decodeContinuously( 239 + _reader?.decodeContinuously(
186 video, 240 video,
187 allowInterop((result, error) { 241 allowInterop((result, error) {
188 if (result != null) { 242 if (result != null) {
@@ -192,14 +246,14 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase @@ -192,14 +246,14 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase
192 ); 246 );
193 }; 247 };
194 controller.onCancel = () { 248 controller.onCancel = () {
195 - _reader.stopContinuousDecode(); 249 + _reader?.stopContinuousDecode();
196 }; 250 };
197 return controller.stream; 251 return controller.stream;
198 } 252 }
199 253
200 @override 254 @override
201 Future<void> stop() async { 255 Future<void> stop() async {
202 - _reader.reset(); 256 + _reader?.reset();
203 super.stop(); 257 super.stop();
204 } 258 }
205 } 259 }