Julian Steenbakker
Committed by GitHub

Merge branch 'master' into pavel/changelog-web

... ... @@ -77,33 +77,41 @@ class _BarcodeScannerWithControllerState
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.torchState,
builder: (context, state, child) {
if (state == null) {
return const Icon(
Icons.flash_off,
color: Colors.grey,
);
}
switch (state as TorchState) {
case TorchState.off:
return const Icon(
Icons.flash_off,
color: Colors.grey,
);
case TorchState.on:
return const Icon(
Icons.flash_on,
color: Colors.yellow,
);
}
},
),
iconSize: 32.0,
onPressed: () => controller.toggleTorch(),
ValueListenableBuilder(
valueListenable: controller.hasTorchState,
builder: (context, state, child) {
if (state != true) {
return const SizedBox.shrink();
}
return IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.torchState,
builder: (context, state, child) {
if (state == null) {
return const Icon(
Icons.flash_off,
color: Colors.grey,
);
}
switch (state as TorchState) {
case TorchState.off:
return const Icon(
Icons.flash_off,
color: Colors.grey,
);
case TorchState.on:
return const Icon(
Icons.flash_on,
color: Colors.yellow,
);
}
},
),
iconSize: 32.0,
onPressed: () => controller.toggleTorch(),
);
},
),
IconButton(
color: Colors.white,
... ...
... ... @@ -93,11 +93,12 @@ class MobileScannerWebPlugin {
// Check if stream is running
if (barCodeReader.isStarted) {
final hasTorch = await barCodeReader.hasTorch();
return {
'ViewID': viewID,
'videoWidth': barCodeReader.videoWidth,
'videoHeight': barCodeReader.videoHeight,
'torchable': barCodeReader.hasTorch,
'torchable': hasTorch,
};
}
try {
... ... @@ -117,12 +118,17 @@ class MobileScannerWebPlugin {
});
}
});
final hasTorch = await barCodeReader.hasTorch();
if (hasTorch && arguments.containsKey('torch')) {
barCodeReader.toggleTorch(enabled: arguments['torch'] as bool);
}
return {
'ViewID': viewID,
'videoWidth': barCodeReader.videoWidth,
'videoHeight': barCodeReader.videoHeight,
'torchable': barCodeReader.hasTorch,
'torchable': hasTorch,
};
} catch (e) {
throw PlatformException(code: 'MobileScannerWeb', message: '$e');
... ...
... ... @@ -99,19 +99,21 @@ class MobileScannerController {
bool isStarting = false;
bool? _hasTorch;
/// A notifier that provides availability of the Torch (Flash)
final ValueNotifier<bool?> hasTorchState = ValueNotifier(false);
/// Returns whether the device has a torch.
///
/// Throws an error if the controller is not initialized.
bool get hasTorch {
if (_hasTorch == null) {
final hasTorch = hasTorchState.value;
if (hasTorch == null) {
throw const MobileScannerException(
errorCode: MobileScannerErrorCode.controllerUninitialized,
);
}
return _hasTorch!;
return hasTorch;
}
/// Set the starting arguments for the camera
... ... @@ -210,8 +212,9 @@ class MobileScannerController {
);
}
_hasTorch = startResult['torchable'] as bool? ?? false;
if (_hasTorch! && torchEnabled) {
final hasTorch = startResult['torchable'] as bool? ?? false;
hasTorchState.value = hasTorch;
if (hasTorch && torchEnabled) {
torchState.value = TorchState.on;
}
... ... @@ -223,7 +226,7 @@ class MobileScannerController {
startResult['videoHeight'] as double? ?? 0,
)
: toSize(startResult['size'] as Map? ?? {}),
hasTorch: _hasTorch!,
hasTorch: hasTorch,
textureId: kIsWeb ? null : startResult['textureId'] as int?,
webId: kIsWeb ? startResult['ViewID'] as String? : null,
);
... ... @@ -244,7 +247,7 @@ class MobileScannerController {
///
/// Throws if the controller was not initialized.
Future<void> toggleTorch() async {
final hasTorch = _hasTorch;
final hasTorch = hasTorchState.value;
if (hasTorch == null) {
throw const MobileScannerException(
... ...
import 'dart:html';
import 'dart:html' as html;
import 'package:flutter/material.dart';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/objects/barcode.dart';
import 'package:mobile_scanner/src/web/media.dart';
... ... @@ -8,7 +10,7 @@ import 'package:mobile_scanner/src/web/media.dart';
abstract class WebBarcodeReaderBase {
/// Timer used to capture frames to be analyzed
final Duration frameInterval;
final DivElement videoContainer;
final html.DivElement videoContainer;
const WebBarcodeReaderBase({
required this.videoContainer,
... ... @@ -35,24 +37,24 @@ abstract class WebBarcodeReaderBase {
Future<void> toggleTorch({required bool enabled});
/// Determine whether device has flash
bool get hasTorch;
Future<bool> hasTorch();
}
mixin InternalStreamCreation on WebBarcodeReaderBase {
/// The video stream.
/// Will be initialized later to see which camera needs to be used.
MediaStream? localMediaStream;
final VideoElement video = VideoElement();
html.MediaStream? localMediaStream;
final html.VideoElement video = html.VideoElement();
@override
int get videoWidth => video.videoWidth;
@override
int get videoHeight => video.videoHeight;
Future<MediaStream?> initMediaStream(CameraFacing cameraFacing) async {
Future<html.MediaStream?> initMediaStream(CameraFacing cameraFacing) async {
// Check if browser supports multiple camera's and set if supported
final Map? capabilities =
window.navigator.mediaDevices?.getSupportedConstraints();
html.window.navigator.mediaDevices?.getSupportedConstraints();
final Map<String, dynamic> constraints;
if (capabilities != null && capabilities['facingMode'] as bool) {
constraints = {
... ... @@ -65,15 +67,15 @@ mixin InternalStreamCreation on WebBarcodeReaderBase {
constraints = {'video': true};
}
final stream =
await window.navigator.mediaDevices?.getUserMedia(constraints);
await html.window.navigator.mediaDevices?.getUserMedia(constraints);
return stream;
}
void prepareVideoElement(VideoElement videoSource);
void prepareVideoElement(html.VideoElement videoSource);
Future<void> attachStreamToVideo(
MediaStream stream,
VideoElement videoSource,
html.MediaStream stream,
html.VideoElement videoSource,
);
@override
... ... @@ -96,19 +98,34 @@ mixin InternalStreamCreation on WebBarcodeReaderBase {
/// Mixin for libraries that don't have built-in torch support
mixin InternalTorchDetection on InternalStreamCreation {
Future<List<String>> getSupportedTorchStates() async {
try {
final track = localMediaStream?.getVideoTracks();
if (track != null) {
final imageCapture = ImageCapture(track.first);
final photoCapabilities = await promiseToFuture<PhotoCapabilities>(
imageCapture.getPhotoCapabilities(),
);
final fillLightMode = photoCapabilities.fillLightMode;
if (fillLightMode != null) {
return fillLightMode;
}
}
} catch (e) {
// ImageCapture is not supported by some browsers:
// https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture#browser_compatibility
}
return [];
}
@override
bool get hasTorch {
// TODO: fix flash light. See https://github.com/dart-lang/sdk/issues/48533
// final track = _localStream?.getVideoTracks();
// if (track != null) {
// final imageCapture = html.ImageCapture(track.first);
// final photoCapabilities = await imageCapture.getPhotoCapabilities();
// }
return false;
Future<bool> hasTorch() async {
return (await getSupportedTorchStates()).isNotEmpty;
}
@override
Future<void> toggleTorch({required bool enabled}) async {
final hasTorch = await this.hasTorch();
if (hasTorch) {
final track = localMediaStream?.getVideoTracks();
await track?.first.applyConstraints({
... ... @@ -119,3 +136,25 @@ mixin InternalTorchDetection on InternalStreamCreation {
}
}
}
@JS('Promise')
@staticInterop
class Promise<T> {}
@JS()
@anonymous
class PhotoCapabilities {
/// Returns an array of available fill light options. Options include auto, off, or flash.
external List<String>? get fillLightMode;
}
@JS('ImageCapture')
@staticInterop
class ImageCapture {
/// MediaStreamTrack
external factory ImageCapture(dynamic track);
}
extension ImageCaptureExt on ImageCapture {
external Promise<PhotoCapabilities> getPhotoCapabilities();
}
... ...
... ... @@ -7,10 +7,6 @@ import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/objects/barcode.dart';
import 'package:mobile_scanner/src/web/base.dart';
@JS('Promise')
@staticInterop
class Promise<T> {}
@JS('ZXing.BrowserMultiFormatReader')
@staticInterop
class JsZXingBrowserMultiFormatReader {
... ...