import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'package:camera/camera.dart'; import 'package:get/get.dart'; import 'package:myhr_facescan/active_liveness/enum.dart'; import 'package:myhr_facescan/active_liveness/face_gesture_verifier.dart'; import 'package:myhr_facescan/models/template.dart'; import 'package:myhr_facescan/luxand_controller.dart'; import 'package:myhr_facescan/utils/camera.dart'; import 'package:myhr_facescan/utils/throttler.dart'; class ActiveLivenessController extends GetxController { ActiveLivenessController({ required String licenseKey, required void Function(bool, TemplateModel?) onFinish, required int imageStreamMilliSecond, required int failAcceptableInSecond, }) : _imageStreamMilliSecond = imageStreamMilliSecond, _onFinish = onFinish, _licenseKey = licenseKey { _throttler = Throttler(milliSeconds: _imageStreamMilliSecond); _luxand = Get.put(LuxandController(licenseKey: _licenseKey)); _commands = getCommands(); _failRatio = 1 ~/ (_imageStreamMilliSecond / 1000) * failAcceptableInSecond; } CameraController? camController; double previewScale = 0; late Throttler _throttler; int failFrame = 0; // StreamSubscription<int> _timer = null; final String _licenseKey; final void Function(bool, TemplateModel?) _onFinish; final int _imageStreamMilliSecond; late List<EFaceGesture> _commands; late int _failRatio; var currentCommand = 0.obs; var isCameraReady = false.obs; var isWaitNextCommand = false.obs; late LuxandController _luxand; final verifier = FaceGestureVerifier(); @override void onInit() async { super.onInit(); await _luxand.initialize(); await initCamera(); camController!.initialize().then((_) { isCameraReady.value = true; previewScale = getCameraPreviewScale(camController!.value.aspectRatio); // Only open and close camera in iOS for low-tier device if (Platform.isIOS) { // _timer = Stream.periodic(const Duration(milliseconds: 500), (v) => v) // .listen((count) async { // _throttler.run(() async { // controller.startImageStream((image) async {}); // Future.delayed(const Duration(milliseconds: 50), () async { // await controller.stopImageStream(); // }); // }); // }); } else { camController!.startImageStream(_handleCameraAndroid); } }); } initCamera() async { camController = await getCameraController(); } @override void onClose() async { super.onClose(); await _clearResource(); } get commandName { switch (_commands[currentCommand.value]) { case EFaceGesture.lookStraight: return 'มองตรง'; case EFaceGesture.turnLeft: return 'หันไปทางซ้าย'; case EFaceGesture.turnRight: return 'หันไปทางขวา'; case EFaceGesture.lookUp: return 'เงยหน้า'; case EFaceGesture.lookDown: return 'ก้มหน้า'; case EFaceGesture.smile: return 'ยิ้ม'; default: return 'N/A'; } } _clearResource() async { isCameraReady.value = false; await camController?.stopImageStream(); await camController?.dispose(); await _luxand.freeResource(); } _handleCameraAndroid(CameraImage image) async { _throttler.run(() async { if (isWaitNextCommand.value) { return; } var command = _commands[currentCommand.value]; var isExpression = command == EFaceGesture.smile; var attr = await _luxand.getFaceAttribute(image, isExpression); if (attr.isEmpty || (attr.isNotEmpty && attr[0] == 0 && attr[1] == 0)) { await _handleFail(); return; } var isMatch = verifier.isMatch(attr[0], attr[1], command); if (isMatch) { await approveCommand(); } }); } _handleFail() async { failFrame++; if (failFrame >= _failRatio) { _onFinish(false, null); await _clearResource(); } } approveCommand() async { if (currentCommand.value + 1 >= _commands.length) { _onFinish(true, null); await _clearResource(); } else { currentCommand.value += 1; } isWaitNextCommand.value = true; Timer(const Duration(seconds: 2), () => isWaitNextCommand.value = false); } List<EFaceGesture> getCommands() { var result = <EFaceGesture>[]; while (true) { if (result.contains(EFaceGesture.lookUp) && result.contains(EFaceGesture.lookDown) && result.contains(EFaceGesture.turnLeft) && result.contains(EFaceGesture.turnRight) && result.contains(EFaceGesture.smile)) { break; } do { var cm = EFaceGesture.fromInt(Random().nextInt(6)); if (!result.contains(cm) && cm != EFaceGesture.lookStraight) { result.add(cm); break; } } while (true); } result.insert(2, EFaceGesture.lookStraight); return result; } }