p-mazhnik

feat(web): torch support

@@ -82,11 +82,12 @@ class MobileScannerWebPlugin { @@ -82,11 +82,12 @@ class MobileScannerWebPlugin {
82 82
83 // Check if stream is running 83 // Check if stream is running
84 if (barCodeReader.isStarted) { 84 if (barCodeReader.isStarted) {
  85 + final hasTorch = await barCodeReader.hasTorch();
85 return { 86 return {
86 'ViewID': viewID, 87 'ViewID': viewID,
87 'videoWidth': barCodeReader.videoWidth, 88 'videoWidth': barCodeReader.videoWidth,
88 'videoHeight': barCodeReader.videoHeight, 89 'videoHeight': barCodeReader.videoHeight,
89 - 'torchable': barCodeReader.hasTorch, 90 + 'torchable': hasTorch,
90 }; 91 };
91 } 92 }
92 try { 93 try {
@@ -106,12 +107,17 @@ class MobileScannerWebPlugin { @@ -106,12 +107,17 @@ class MobileScannerWebPlugin {
106 }); 107 });
107 } 108 }
108 }); 109 });
  110 + final hasTorch = await barCodeReader.hasTorch();
  111 +
  112 + if (hasTorch && arguments.containsKey('torch')) {
  113 + barCodeReader.toggleTorch(enabled: arguments['torch'] as bool);
  114 + }
109 115
110 return { 116 return {
111 'ViewID': viewID, 117 'ViewID': viewID,
112 'videoWidth': barCodeReader.videoWidth, 118 'videoWidth': barCodeReader.videoWidth,
113 'videoHeight': barCodeReader.videoHeight, 119 'videoHeight': barCodeReader.videoHeight,
114 - 'torchable': barCodeReader.hasTorch, 120 + 'torchable': hasTorch,
115 }; 121 };
116 } catch (e) { 122 } catch (e) {
117 throw PlatformException(code: 'MobileScannerWeb', message: '$e'); 123 throw PlatformException(code: 'MobileScannerWeb', message: '$e');
1 -import 'dart:html'; 1 +import 'dart:html' as html;
2 2
3 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
  4 +import 'package:js/js.dart';
  5 +import 'package:js/js_util.dart';
4 import 'package:mobile_scanner/src/enums/camera_facing.dart'; 6 import 'package:mobile_scanner/src/enums/camera_facing.dart';
5 import 'package:mobile_scanner/src/objects/barcode.dart'; 7 import 'package:mobile_scanner/src/objects/barcode.dart';
6 import 'package:mobile_scanner/src/web/media.dart'; 8 import 'package:mobile_scanner/src/web/media.dart';
@@ -8,7 +10,7 @@ import 'package:mobile_scanner/src/web/media.dart'; @@ -8,7 +10,7 @@ import 'package:mobile_scanner/src/web/media.dart';
8 abstract class WebBarcodeReaderBase { 10 abstract class WebBarcodeReaderBase {
9 /// Timer used to capture frames to be analyzed 11 /// Timer used to capture frames to be analyzed
10 final Duration frameInterval; 12 final Duration frameInterval;
11 - final DivElement videoContainer; 13 + final html.DivElement videoContainer;
12 14
13 const WebBarcodeReaderBase({ 15 const WebBarcodeReaderBase({
14 required this.videoContainer, 16 required this.videoContainer,
@@ -35,24 +37,24 @@ abstract class WebBarcodeReaderBase { @@ -35,24 +37,24 @@ abstract class WebBarcodeReaderBase {
35 Future<void> toggleTorch({required bool enabled}); 37 Future<void> toggleTorch({required bool enabled});
36 38
37 /// Determine whether device has flash 39 /// Determine whether device has flash
38 - bool get hasTorch; 40 + Future<bool> hasTorch();
39 } 41 }
40 42
41 mixin InternalStreamCreation on WebBarcodeReaderBase { 43 mixin InternalStreamCreation on WebBarcodeReaderBase {
42 /// The video stream. 44 /// The video stream.
43 /// Will be initialized later to see which camera needs to be used. 45 /// Will be initialized later to see which camera needs to be used.
44 - MediaStream? localMediaStream;  
45 - final VideoElement video = VideoElement(); 46 + html.MediaStream? localMediaStream;
  47 + final html.VideoElement video = html.VideoElement();
46 48
47 @override 49 @override
48 int get videoWidth => video.videoWidth; 50 int get videoWidth => video.videoWidth;
49 @override 51 @override
50 int get videoHeight => video.videoHeight; 52 int get videoHeight => video.videoHeight;
51 53
52 - Future<MediaStream?> initMediaStream(CameraFacing cameraFacing) async { 54 + Future<html.MediaStream?> initMediaStream(CameraFacing cameraFacing) async {
53 // Check if browser supports multiple camera's and set if supported 55 // Check if browser supports multiple camera's and set if supported
54 final Map? capabilities = 56 final Map? capabilities =
55 - window.navigator.mediaDevices?.getSupportedConstraints(); 57 + html.window.navigator.mediaDevices?.getSupportedConstraints();
56 final Map<String, dynamic> constraints; 58 final Map<String, dynamic> constraints;
57 if (capabilities != null && capabilities['facingMode'] as bool) { 59 if (capabilities != null && capabilities['facingMode'] as bool) {
58 constraints = { 60 constraints = {
@@ -65,15 +67,15 @@ mixin InternalStreamCreation on WebBarcodeReaderBase { @@ -65,15 +67,15 @@ mixin InternalStreamCreation on WebBarcodeReaderBase {
65 constraints = {'video': true}; 67 constraints = {'video': true};
66 } 68 }
67 final stream = 69 final stream =
68 - await window.navigator.mediaDevices?.getUserMedia(constraints); 70 + await html.window.navigator.mediaDevices?.getUserMedia(constraints);
69 return stream; 71 return stream;
70 } 72 }
71 73
72 - void prepareVideoElement(VideoElement videoSource); 74 + void prepareVideoElement(html.VideoElement videoSource);
73 75
74 Future<void> attachStreamToVideo( 76 Future<void> attachStreamToVideo(
75 - MediaStream stream,  
76 - VideoElement videoSource, 77 + html.MediaStream stream,
  78 + html.VideoElement videoSource,
77 ); 79 );
78 80
79 @override 81 @override
@@ -97,18 +99,26 @@ mixin InternalStreamCreation on WebBarcodeReaderBase { @@ -97,18 +99,26 @@ mixin InternalStreamCreation on WebBarcodeReaderBase {
97 /// Mixin for libraries that don't have built-in torch support 99 /// Mixin for libraries that don't have built-in torch support
98 mixin InternalTorchDetection on InternalStreamCreation { 100 mixin InternalTorchDetection on InternalStreamCreation {
99 @override 101 @override
100 - bool get hasTorch {  
101 - // TODO: fix flash light. See https://github.com/dart-lang/sdk/issues/48533  
102 - // final track = _localStream?.getVideoTracks();  
103 - // if (track != null) {  
104 - // final imageCapture = html.ImageCapture(track.first);  
105 - // final photoCapabilities = await imageCapture.getPhotoCapabilities();  
106 - // } 102 + Future<bool> hasTorch() async {
  103 + try {
  104 + final track = localMediaStream?.getVideoTracks();
  105 + if (track != null) {
  106 + final imageCapture = ImageCapture(track.first);
  107 + final photoCapabilities = await promiseToFuture<PhotoCapabilities>(
  108 + imageCapture.getPhotoCapabilities(),
  109 + );
  110 + return photoCapabilities.fillLightMode != null;
  111 + }
  112 + } catch (e) {
  113 + // ImageCapture is not supported by some browsers:
  114 + // https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture#browser_compatibility
  115 + }
107 return false; 116 return false;
108 } 117 }
109 118
110 @override 119 @override
111 Future<void> toggleTorch({required bool enabled}) async { 120 Future<void> toggleTorch({required bool enabled}) async {
  121 + final hasTorch = await this.hasTorch();
112 if (hasTorch) { 122 if (hasTorch) {
113 final track = localMediaStream?.getVideoTracks(); 123 final track = localMediaStream?.getVideoTracks();
114 await track?.first.applyConstraints({ 124 await track?.first.applyConstraints({
@@ -119,3 +129,25 @@ mixin InternalTorchDetection on InternalStreamCreation { @@ -119,3 +129,25 @@ mixin InternalTorchDetection on InternalStreamCreation {
119 } 129 }
120 } 130 }
121 } 131 }
  132 +
  133 +@JS('Promise')
  134 +@staticInterop
  135 +class Promise<T> {}
  136 +
  137 +@JS()
  138 +@anonymous
  139 +class PhotoCapabilities {
  140 + /// Returns an array of available fill light options. Options include auto, off, or flash.
  141 + external List<String>? get fillLightMode;
  142 +}
  143 +
  144 +@JS('ImageCapture')
  145 +@staticInterop
  146 +class ImageCapture {
  147 + /// MediaStreamTrack
  148 + external factory ImageCapture(dynamic track);
  149 +}
  150 +
  151 +extension ImageCaptureExt on ImageCapture {
  152 + external Promise<PhotoCapabilities> getPhotoCapabilities();
  153 +}
@@ -7,10 +7,6 @@ import 'package:mobile_scanner/src/enums/camera_facing.dart'; @@ -7,10 +7,6 @@ import 'package:mobile_scanner/src/enums/camera_facing.dart';
7 import 'package:mobile_scanner/src/objects/barcode.dart'; 7 import 'package:mobile_scanner/src/objects/barcode.dart';
8 import 'package:mobile_scanner/src/web/base.dart'; 8 import 'package:mobile_scanner/src/web/base.dart';
9 9
10 -@JS('Promise')  
11 -@staticInterop  
12 -class Promise<T> {}  
13 -  
14 @JS('ZXing.BrowserMultiFormatReader') 10 @JS('ZXing.BrowserMultiFormatReader')
15 @staticInterop 11 @staticInterop
16 class JsZXingBrowserMultiFormatReader { 12 class JsZXingBrowserMultiFormatReader {