p-mazhnik

refactor(web): add zxing barcode reader

@@ -28,8 +28,8 @@ @@ -28,8 +28,8 @@
28 28
29 <title>example</title> 29 <title>example</title>
30 <link rel="manifest" href="manifest.json"> 30 <link rel="manifest" href="manifest.json">
31 -<!-- <script src="https://cdn.jsdelivr.net/npm/qr-scanner@1.4.1/qr-scanner.min.js"></script>-->  
32 <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> 31 <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
  32 + <script type="text/javascript" src="https://unpkg.com/@zxing/library@latest"></script>
33 </head> 33 </head>
34 <body> 34 <body>
35 <!-- This script installs service_worker.js to provide PWA functionality to 35 <!-- This script installs service_worker.js to provide PWA functionality to
  1 +library mobile_scanner_web;
  2 +
  3 +export 'src/web/base.dart';
  4 +export 'src/web/jsqr.dart';
  5 +export 'src/web/zxing.dart';
@@ -4,9 +4,8 @@ import 'dart:ui' as ui; @@ -4,9 +4,8 @@ import 'dart:ui' as ui;
4 4
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/src/enums/camera_facing.dart'; 8 import 'package:mobile_scanner/src/enums/camera_facing.dart';
8 -import 'package:mobile_scanner/src/web/base.dart';  
9 -import 'package:mobile_scanner/src/web/jsqr.dart';  
10 9
11 /// This plugin is the web implementation of mobile_scanner. 10 /// This plugin is the web implementation of mobile_scanner.
12 /// It only supports QR codes. 11 /// It only supports QR codes.
@@ -34,10 +33,10 @@ class MobileScannerWebPlugin { @@ -34,10 +33,10 @@ class MobileScannerWebPlugin {
34 // ID of the video feed 33 // ID of the video feed
35 String viewID = 'WebScanner-${DateTime.now().millisecondsSinceEpoch}'; 34 String viewID = 'WebScanner-${DateTime.now().millisecondsSinceEpoch}';
36 35
37 - final html.DivElement vidDiv = html.DivElement(); 36 + static final html.DivElement vidDiv = html.DivElement();
38 37
39 - late final WebBarcodeReaderBase _barCodeReader =  
40 - JsQrCodeReader(videoContainer: vidDiv); 38 + static WebBarcodeReaderBase barCodeReader =
  39 + ZXingBarcodeReader(videoContainer: vidDiv);
41 StreamSubscription? _barCodeStreamSubscription; 40 StreamSubscription? _barCodeStreamSubscription;
42 41
43 /// Handle incomming messages 42 /// Handle incomming messages
@@ -60,7 +59,7 @@ class MobileScannerWebPlugin { @@ -60,7 +59,7 @@ class MobileScannerWebPlugin {
60 59
61 /// Can enable or disable the flash if available 60 /// Can enable or disable the flash if available
62 Future<void> _torch(arguments) async { 61 Future<void> _torch(arguments) async {
63 - _barCodeReader.toggleTorch(enabled: arguments == 1); 62 + barCodeReader.toggleTorch(enabled: arguments == 1);
64 } 63 }
65 64
66 /// Starts the video stream and the scanner 65 /// Starts the video stream and the scanner
@@ -82,20 +81,20 @@ class MobileScannerWebPlugin { @@ -82,20 +81,20 @@ class MobileScannerWebPlugin {
82 ); 81 );
83 82
84 // Check if stream is running 83 // Check if stream is running
85 - if (_barCodeReader.isStarted) { 84 + if (barCodeReader.isStarted) {
86 return { 85 return {
87 'ViewID': viewID, 86 'ViewID': viewID,
88 - 'videoWidth': _barCodeReader.videoWidth,  
89 - 'videoHeight': _barCodeReader.videoHeight,  
90 - 'torchable': _barCodeReader.hasTorch, 87 + 'videoWidth': barCodeReader.videoWidth,
  88 + 'videoHeight': barCodeReader.videoHeight,
  89 + 'torchable': barCodeReader.hasTorch,
91 }; 90 };
92 } 91 }
93 try { 92 try {
94 - await _barCodeReader.start( 93 + await barCodeReader.start(
95 cameraFacing: cameraFacing, 94 cameraFacing: cameraFacing,
96 ); 95 );
97 96
98 - _barCodeStreamSubscription = _barCodeReader.detectBarcodeContinuously().listen((code) { 97 + _barCodeStreamSubscription = barCodeReader.detectBarcodeContinuously().listen((code) {
99 if (code != null) { 98 if (code != null) {
100 controller.add({ 99 controller.add({
101 'name': 'barcodeWeb', 100 'name': 'barcodeWeb',
@@ -109,9 +108,9 @@ class MobileScannerWebPlugin { @@ -109,9 +108,9 @@ class MobileScannerWebPlugin {
109 108
110 return { 109 return {
111 'ViewID': viewID, 110 'ViewID': viewID,
112 - 'videoWidth': _barCodeReader.videoWidth,  
113 - 'videoHeight': _barCodeReader.videoHeight,  
114 - 'torchable': _barCodeReader.hasTorch, 111 + 'videoWidth': barCodeReader.videoWidth,
  112 + 'videoHeight': barCodeReader.videoHeight,
  113 + 'torchable': barCodeReader.hasTorch,
115 }; 114 };
116 } catch (e) { 115 } catch (e) {
117 throw PlatformException(code: 'MobileScannerWeb', message: '$e'); 116 throw PlatformException(code: 'MobileScannerWeb', message: '$e');
@@ -134,7 +133,7 @@ class MobileScannerWebPlugin { @@ -134,7 +133,7 @@ class MobileScannerWebPlugin {
134 133
135 /// Stops the video feed and analyzer 134 /// Stops the video feed and analyzer
136 Future<void> cancel() async { 135 Future<void> cancel() async {
137 - _barCodeReader.stop(); 136 + barCodeReader.stop();
138 await _barCodeStreamSubscription?.cancel(); 137 await _barCodeStreamSubscription?.cancel();
139 _barCodeStreamSubscription = null; 138 _barCodeStreamSubscription = null;
140 } 139 }
1 -@JS()  
2 -library qrscanner;  
3 -  
4 -import 'package:js/js.dart';  
5 -  
6 -@JS('QrScanner')  
7 -external String scanImage(dynamic data);  
8 -  
9 -@JS()  
10 -class QrScanner {  
11 - external String get scanImage;  
12 -}  
  1 +import 'dart:async';
  2 +import 'dart:html';
  3 +import 'dart:typed_data';
  4 +
  5 +import 'package:js/js.dart';
  6 +import 'package:mobile_scanner/src/enums/camera_facing.dart';
  7 +import 'package:mobile_scanner/src/objects/barcode.dart';
  8 +import 'package:mobile_scanner/src/web/base.dart';
  9 +
  10 +@JS('Promise')
  11 +@staticInterop
  12 +class Promise<T> {}
  13 +
  14 +@JS('ZXing.BrowserMultiFormatReader')
  15 +@staticInterop
  16 +class JsZXingBrowserMultiFormatReader {
  17 + /// https://github.com/zxing-js/library/blob/1e9ccb3b6b28d75b9eef866dba196d8937eb4449/src/browser/BrowserMultiFormatReader.ts#L11
  18 + external JsZXingBrowserMultiFormatReader(
  19 + dynamic hints,
  20 + int timeBetweenScansMillis,
  21 + );
  22 +}
  23 +
  24 +@JS()
  25 +@anonymous
  26 +abstract class Result {
  27 + /// raw text encoded by the barcode
  28 + external String get text;
  29 + /// Returns raw bytes encoded by the barcode, if applicable, otherwise null
  30 + external Uint8ClampedList? get rawBytes;
  31 + /// Representing the format of the barcode that was decoded
  32 + external int? format;
  33 +}
  34 +
  35 +extension ResultExt on Result {
  36 + Barcode toBarcode() {
  37 + final rawBytes = this.rawBytes;
  38 + return Barcode(
  39 + rawValue: text,
  40 + rawBytes: rawBytes != null ? Uint8List.fromList(rawBytes) : null,
  41 + format: barcodeFormat,
  42 + );
  43 + }
  44 +
  45 + /// https://github.com/zxing-js/library/blob/1e9ccb3b6b28d75b9eef866dba196d8937eb4449/src/core/BarcodeFormat.ts#L28
  46 + BarcodeFormat get barcodeFormat {
  47 + switch (format) {
  48 + case 1:
  49 + return BarcodeFormat.aztec;
  50 + case 2:
  51 + return BarcodeFormat.codebar;
  52 + case 3:
  53 + return BarcodeFormat.code39;
  54 + case 4:
  55 + return BarcodeFormat.code128;
  56 + case 5:
  57 + return BarcodeFormat.dataMatrix;
  58 + case 6:
  59 + return BarcodeFormat.ean8;
  60 + case 7:
  61 + return BarcodeFormat.ean13;
  62 + case 8:
  63 + return BarcodeFormat.itf;
  64 + // case 9:
  65 + // return BarcodeFormat.maxicode;
  66 + case 10:
  67 + return BarcodeFormat.pdf417;
  68 + case 11:
  69 + return BarcodeFormat.qrCode;
  70 + // case 12:
  71 + // return BarcodeFormat.rss14;
  72 + // case 13:
  73 + // return BarcodeFormat.rssExp;
  74 + case 14:
  75 + return BarcodeFormat.upcA;
  76 + case 15:
  77 + return BarcodeFormat.upcE;
  78 + default:
  79 + return BarcodeFormat.unknown;
  80 + }
  81 + }
  82 +}
  83 +
  84 +@JS()
  85 +@anonymous
  86 +abstract class Exception {
  87 + external String get message;
  88 +}
  89 +
  90 +typedef BarcodeDetectionCallback = void Function(Result? result, Exception? error);
  91 +
  92 +extension JsZXingBrowserMultiFormatReaderExt
  93 + on JsZXingBrowserMultiFormatReader {
  94 + external Promise<void> decodeFromVideoElementContinuously(
  95 + VideoElement source,
  96 + BarcodeDetectionCallback callbackFn,
  97 + );
  98 +
  99 + /// Continuously decodes from video input
  100 + external void decodeContinuously(
  101 + VideoElement element,
  102 + BarcodeDetectionCallback callbackFn,
  103 + );
  104 +
  105 + external Promise<void> decodeFromStream(
  106 + MediaStream stream,
  107 + VideoElement videoSource,
  108 + BarcodeDetectionCallback callbackFn,
  109 + );
  110 +
  111 + external Promise<void> decodeFromConstraints(
  112 + dynamic constraints,
  113 + VideoElement videoSource,
  114 + BarcodeDetectionCallback callbackFn,
  115 + );
  116 +
  117 + external void stopContinuousDecode();
  118 +
  119 + external VideoElement prepareVideoElement(VideoElement videoSource);
  120 +
  121 + /// Defines what the [videoElement] src will be.
  122 + external void addVideoSource(
  123 + VideoElement videoElement,
  124 + MediaStream stream,
  125 + );
  126 +
  127 + external bool isVideoPlaying(VideoElement video);
  128 +
  129 + external void reset();
  130 +
  131 + /// The HTML video element, used to display the camera stream.
  132 + external VideoElement? videoElement;
  133 +
  134 + /// The stream output from camera.
  135 + external MediaStream? stream;
  136 +}
  137 +
  138 +class ZXingBarcodeReader extends WebBarcodeReaderBase
  139 + with InternalStreamCreation, InternalTorchDetection {
  140 + late final JsZXingBrowserMultiFormatReader _reader =
  141 + JsZXingBrowserMultiFormatReader(
  142 + null,
  143 + frameInterval.inMilliseconds,
  144 + );
  145 +
  146 + ZXingBarcodeReader({required super.videoContainer});
  147 +
  148 + @override
  149 + bool get isStarted => localMediaStream != null;
  150 +
  151 + @override
  152 + Future<void> start({
  153 + required CameraFacing cameraFacing,
  154 + }) async {
  155 + videoContainer.children = [video];
  156 +
  157 + final stream = await initMediaStream(cameraFacing);
  158 +
  159 + prepareVideoElement(video);
  160 + if (stream != null) {
  161 + await attachStreamToVideo(stream, video);
  162 + }
  163 + }
  164 +
  165 + @override
  166 + void prepareVideoElement(VideoElement videoSource) {
  167 + _reader.prepareVideoElement(videoSource);
  168 + }
  169 +
  170 + @override
  171 + Future<void> attachStreamToVideo(
  172 + MediaStream stream,
  173 + VideoElement videoSource,
  174 + ) async {
  175 + _reader.addVideoSource(videoSource, stream);
  176 + _reader.videoElement = videoSource;
  177 + _reader.stream = stream;
  178 + localMediaStream = stream;
  179 + await videoSource.play();
  180 + }
  181 +
  182 + @override
  183 + Stream<Barcode?> detectBarcodeContinuously() {
  184 + final controller = StreamController<Barcode?>();
  185 + controller.onListen = () async {
  186 + _reader.decodeContinuously(
  187 + video,
  188 + allowInterop((result, error) {
  189 + if (result != null) {
  190 + controller.add(result.toBarcode());
  191 + }
  192 + }),
  193 + );
  194 + };
  195 + controller.onCancel = () {
  196 + _reader.stopContinuousDecode();
  197 + };
  198 + return controller.stream;
  199 + }
  200 +
  201 + @override
  202 + Future<void> stop() async {
  203 + _reader.reset();
  204 + super.stop();
  205 + }
  206 +}