Showing
5 changed files
with
227 additions
and
29 deletions
| @@ -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 |
lib/mobile_scanner_web.dart
0 → 100644
| @@ -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 | } |
lib/src/web/qr_scanner.dart
deleted
100644 → 0
lib/src/web/zxing.dart
0 → 100644
| 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 | +} |
-
Please register or login to post a comment