p-mazhnik

refactor: abstract web scanner

@@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; @@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
6 import 'package:flutter/services.dart'; 6 import 'package:flutter/services.dart';
7 import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 7 import 'package:flutter_web_plugins/flutter_web_plugins.dart';
8 import 'package:mobile_scanner/src/enums/camera_facing.dart'; 8 import 'package:mobile_scanner/src/enums/camera_facing.dart';
  9 +import 'package:mobile_scanner/src/web/base.dart';
9 import 'package:mobile_scanner/src/web/jsqr.dart'; 10 import 'package:mobile_scanner/src/web/jsqr.dart';
10 import 'package:mobile_scanner/src/web/media.dart'; 11 import 'package:mobile_scanner/src/web/media.dart';
11 12
@@ -42,8 +43,8 @@ class MobileScannerWebPlugin { @@ -42,8 +43,8 @@ class MobileScannerWebPlugin {
42 // Determine wether device has flas 43 // Determine wether device has flas
43 bool hasFlash = false; 44 bool hasFlash = false;
44 45
45 - // Timer used to capture frames to be analyzed  
46 - Timer? _frameInterval; 46 + final WebBarcodeReaderBase _barCodeReader = JsQrCodeReader();
  47 + StreamSubscription? _barCodeStreamSubscription;
47 48
48 html.DivElement vidDiv = html.DivElement(); 49 html.DivElement vidDiv = html.DivElement();
49 50
@@ -135,13 +136,14 @@ class MobileScannerWebPlugin { @@ -135,13 +136,14 @@ class MobileScannerWebPlugin {
135 // required to tell iOS safari we don't want fullscreen 136 // required to tell iOS safari we don't want fullscreen
136 video.setAttribute('playsinline', 'true'); 137 video.setAttribute('playsinline', 'true');
137 138
138 - await video.play();  
139 -  
140 - // Then capture a frame to be analyzed every 200 miliseconds  
141 - _frameInterval =  
142 - Timer.periodic(const Duration(milliseconds: 200), (timer) {  
143 - _captureFrame(); 139 + _barCodeStreamSubscription =
  140 + _barCodeReader.detectBarcodeContinuously(video).listen((code) {
  141 + if (_localStream == null) return;
  142 + if (code != null) {
  143 + controller.add({'name': 'barcodeWeb', 'data': code});
  144 + }
144 }); 145 });
  146 + await video.play();
145 147
146 return { 148 return {
147 'ViewID': viewID, 149 'ViewID': viewID,
@@ -183,27 +185,7 @@ class MobileScannerWebPlugin { @@ -183,27 +185,7 @@ class MobileScannerWebPlugin {
183 185
184 video.srcObject = null; 186 video.srcObject = null;
185 _localStream = null; 187 _localStream = null;
186 - _frameInterval?.cancel();  
187 - _frameInterval = null;  
188 - }  
189 -  
190 - /// Captures a frame and analyzes it for QR codes  
191 - Future<dynamic> _captureFrame() async {  
192 - if (_localStream == null) return null;  
193 - final canvas =  
194 - html.CanvasElement(width: video.videoWidth, height: video.videoHeight);  
195 - final ctx = canvas.context2D;  
196 -  
197 - ctx.drawImage(video, 0, 0);  
198 - final imgData = ctx.getImageData(0, 0, canvas.width!, canvas.height!);  
199 -  
200 - final code = jsQR(imgData.data, canvas.width, canvas.height);  
201 - if (code != null) {  
202 - controller.add({  
203 - 'name': 'barcodeWeb',  
204 - 'data': code.data,  
205 - 'binaryData': code.binaryData,  
206 - });  
207 - } 188 + await _barCodeStreamSubscription?.cancel();
  189 + _barCodeStreamSubscription = null;
208 } 190 }
209 } 191 }
  1 +import 'dart:html';
  2 +
  3 +abstract class WebBarcodeReaderBase {
  4 + Stream<String?> detectBarcodeContinuously(VideoElement video);
  5 +}
1 @JS() 1 @JS()
2 library jsqr; 2 library jsqr;
3 3
  4 +import 'dart:async';
  5 +import 'dart:html';
4 import 'dart:typed_data'; 6 import 'dart:typed_data';
5 7
6 import 'package:js/js.dart'; 8 import 'package:js/js.dart';
  9 +import 'package:mobile_scanner/src/web/base.dart';
7 10
8 @JS('jsQR') 11 @JS('jsQR')
9 external Code? jsQR(dynamic data, int? width, int? height); 12 external Code? jsQR(dynamic data, int? width, int? height);
@@ -14,3 +17,28 @@ class Code { @@ -14,3 +17,28 @@ class Code {
14 17
15 external Uint8ClampedList get binaryData; 18 external Uint8ClampedList get binaryData;
16 } 19 }
  20 +
  21 +
  22 +class JsQrCodeReader extends WebBarcodeReaderBase {
  23 + // Timer used to capture frames to be analyzed
  24 + final frameInterval = const Duration(milliseconds: 200);
  25 +
  26 + @override
  27 + Stream<String?> detectBarcodeContinuously(VideoElement video) async* {
  28 + yield* Stream.periodic(frameInterval, (_) {
  29 + return _captureFrame(video);
  30 + }).asyncMap((event) async => (await event)?.data);
  31 + }
  32 +
  33 + /// Captures a frame and analyzes it for QR codes
  34 + Future<Code?> _captureFrame(VideoElement video) async {
  35 + final canvas = CanvasElement(width: video.videoWidth, height: video.videoHeight);
  36 + final ctx = canvas.context2D;
  37 +
  38 + ctx.drawImage(video, 0, 0);
  39 + final imgData = ctx.getImageData(0, 0, canvas.width!, canvas.height!);
  40 +
  41 + final code = jsQR(imgData.data, canvas.width, canvas.height);
  42 + return code;
  43 + }
  44 +}