406 lines
12 KiB
Dart
406 lines
12 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:pj_supermarket_cloud/common/index.dart';
|
|
|
|
abstract class FaceDiscernPlugs {
|
|
static const String _tag = "人脸识别plug";
|
|
|
|
static const MethodChannel _channel = MethodChannel(
|
|
Constants.facePlugsMethodChannel,
|
|
);
|
|
|
|
static const EventChannel _eventChannel = EventChannel(
|
|
Constants.facePlugsEventChannel,
|
|
);
|
|
|
|
static const EventChannel _sessionEventChannel = EventChannel(
|
|
'local/face_discern_session_event',
|
|
);
|
|
|
|
static const EventChannel _registrationEventChannel = EventChannel(
|
|
Constants.facePlugsRegistrationEventChannel,
|
|
);
|
|
|
|
/// 原生在库图片质量批量检测进度通道。
|
|
static const EventChannel _qualityAuditEventChannel = EventChannel(
|
|
Constants.facePlugsQualityAuditEventChannel,
|
|
);
|
|
|
|
static MethodChannel get getChannel => _channel;
|
|
|
|
static Stream<FaceStream?>? _stream;
|
|
static Stream<FaceRecognitionSessionEvent>? _sessionStream;
|
|
static Stream<FaceRegistrationProgress>? _registrationStream;
|
|
|
|
/// 延迟创建并复用的在库图片质量检测进度流。
|
|
static Stream<FaceQualityAuditProgress>? _qualityAuditStream;
|
|
|
|
static Future<bool> jumpFaceDiscernPage() async {
|
|
try {
|
|
await _channel.invokeMethod('jumpFaceDiscern');
|
|
return true;
|
|
} catch (e) {
|
|
logger.w("|$_tag| jumpFaceDiscernPage 错误: $e");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static Future<FaceRecognitionPreviewTexture> createPreviewTexture() async {
|
|
final map = await _channel.invokeMethod<dynamic>('createPreviewTexture');
|
|
if (map is! Map) throw StateError('原生未返回有效的人脸预览纹理');
|
|
return FaceRecognitionPreviewTexture.fromMap(map);
|
|
}
|
|
|
|
static Future<void> releasePreviewTexture(int textureId) async {
|
|
await _channel.invokeMethod<void>('releasePreviewTexture', {
|
|
'textureId': textureId,
|
|
});
|
|
}
|
|
|
|
static Future<FaceRecognitionSessionEvent> startRecognition({
|
|
required String sessionId,
|
|
required FaceRecognitionScene scene,
|
|
}) {
|
|
return _invokeSession('startRecognition', {
|
|
'sessionId': sessionId,
|
|
'scene': scene.name,
|
|
});
|
|
}
|
|
|
|
static Future<FaceRecognitionSessionEvent> retryRecognition(
|
|
String sessionId,
|
|
) {
|
|
return _invokeSession('retryRecognition', {'sessionId': sessionId});
|
|
}
|
|
|
|
static Future<FaceRecognitionSessionEvent> cancelRecognition(
|
|
String sessionId, {
|
|
String? message,
|
|
}) {
|
|
return _invokeSession('cancelRecognition', {
|
|
'sessionId': sessionId,
|
|
'message': message,
|
|
});
|
|
}
|
|
|
|
static Future<FaceRecognitionSessionEvent> finishRecognition(
|
|
String sessionId,
|
|
) {
|
|
return _invokeSession('finishRecognition', {'sessionId': sessionId});
|
|
}
|
|
|
|
static Future<FaceRecognitionSessionEvent> suspendRecognition({
|
|
String? reason,
|
|
}) {
|
|
return _invokeSession('suspendRecognition', {'reason': reason});
|
|
}
|
|
|
|
static Future<FaceRecognitionSessionEvent> getRecognitionState() {
|
|
return _invokeSession('getRecognitionState');
|
|
}
|
|
|
|
/// 通过 Android Camera 枚举检查终端是否存在摄像头,不打开设备也不占用识别资源。
|
|
static Future<bool> hasCamera() async {
|
|
try {
|
|
return await _channel.invokeMethod<bool>('hasCamera') ?? false;
|
|
} catch (e) {
|
|
logger.w('|$_tag| hasCamera 错误: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 清理 Android 私有目录中超过截止时间且未被 Floor 任务引用的证据图片。
|
|
///
|
|
/// [referencedPaths] 仅在进程内传给原生层做路径保护,不会写入日志或发送到服务端。
|
|
static Future<int> cleanupOrderEvidenceFiles({
|
|
required Set<String> referencedPaths,
|
|
required DateTime olderThan,
|
|
}) async {
|
|
return await _channel.invokeMethod<int>('cleanupOrderEvidenceFiles', {
|
|
'referencedPaths': referencedPaths.toList(growable: false),
|
|
'olderThan': olderThan.millisecondsSinceEpoch,
|
|
}) ??
|
|
0;
|
|
}
|
|
|
|
static Future<FaceRecognitionConfiguration> getRecognizeConfiguration() {
|
|
return _invokeRecognizeConfiguration('getRecognizeConfiguration');
|
|
}
|
|
|
|
static Future<FaceRecognitionConfiguration> setRecognizeConfiguration(
|
|
FaceRecognitionConfiguration configuration,
|
|
) {
|
|
return _invokeRecognizeConfiguration(
|
|
'setRecognizeConfiguration',
|
|
configuration.toMap(),
|
|
);
|
|
}
|
|
|
|
static Future<FaceRecognitionConfiguration> resetRecognizeConfiguration() {
|
|
return _invokeRecognizeConfiguration('resetRecognizeConfiguration');
|
|
}
|
|
|
|
static Stream<FaceRecognitionSessionEvent> get sessionStream {
|
|
_sessionStream ??= _sessionEventChannel
|
|
.receiveBroadcastStream()
|
|
.where((event) => event is Map)
|
|
.map((event) => FaceRecognitionSessionEvent.fromMap(event as Map))
|
|
.asBroadcastStream();
|
|
return _sessionStream!;
|
|
}
|
|
|
|
static Future<FaceRecognitionSessionEvent> _invokeSession(
|
|
String method, [
|
|
Map<String, dynamic>? arguments,
|
|
]) async {
|
|
final map = await _channel.invokeMethod<dynamic>(method, arguments);
|
|
if (map is! Map) throw StateError('原生未返回有效的人脸会话状态');
|
|
return FaceRecognitionSessionEvent.fromMap(map);
|
|
}
|
|
|
|
static Future<FaceRecognitionConfiguration> _invokeRecognizeConfiguration(
|
|
String method, [
|
|
Map<String, dynamic>? arguments,
|
|
]) async {
|
|
final map = await _channel.invokeMethod<dynamic>(method, arguments);
|
|
if (map is! Map) throw StateError('原生未返回有效的人脸识别配置');
|
|
return FaceRecognitionConfiguration.fromMap(map);
|
|
}
|
|
|
|
static Future<FaceConfig?> getConfig() async {
|
|
try {
|
|
final Map<Object?, Object?>? map =
|
|
await _channel.invokeMethod('getConfig');
|
|
if (map == null) return null;
|
|
final encode = json.encode(map);
|
|
return FaceConfig.fromJson(json.decode(encode));
|
|
} catch (e) {
|
|
logger.w("|$_tag| getConfig 错误: $e");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Future<bool> active({
|
|
required String appId,
|
|
required String sdkKey,
|
|
required String activeKey,
|
|
}) async {
|
|
try {
|
|
final pa = {
|
|
"appId": appId,
|
|
"sdkKey": sdkKey,
|
|
"activeKey": activeKey,
|
|
};
|
|
final isActive = await _channel.invokeMethod('active', pa);
|
|
return isActive;
|
|
} catch (e) {
|
|
logger.w("|$_tag| getConfig 错误: $e");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// 查询真实授权文件、引擎和内存人脸库状态。
|
|
static Future<FacePluginStatus?> getFaceStatus({
|
|
required String appId,
|
|
required String sdkKey,
|
|
required String activeKey,
|
|
}) async {
|
|
try {
|
|
final map = await _channel.invokeMethod<dynamic>('getFaceStatus', {
|
|
'appId': appId,
|
|
'sdkKey': sdkKey,
|
|
'activeKey': activeKey,
|
|
});
|
|
return map is Map ? FacePluginStatus.fromMap(map) : null;
|
|
} catch (e) {
|
|
logger.w('|$_tag| getFaceStatus 错误: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static Future<FaceActivationResult> activateFaceSdk({
|
|
required String appId,
|
|
required String sdkKey,
|
|
required String activeKey,
|
|
}) async {
|
|
try {
|
|
final map = await _channel.invokeMethod<dynamic>('activateFaceSdk', {
|
|
'appId': appId,
|
|
'sdkKey': sdkKey,
|
|
'activeKey': activeKey,
|
|
});
|
|
if (map is Map) return FaceActivationResult.fromMap(map);
|
|
} catch (e) {
|
|
logger.w('|$_tag| activateFaceSdk 错误: $e');
|
|
}
|
|
return const FaceActivationResult(
|
|
success: false,
|
|
code: -1,
|
|
message: '人脸插件激活失败,请稍后重试',
|
|
requiresPermission: false,
|
|
canOpenSettings: false,
|
|
);
|
|
}
|
|
|
|
static Future<FaceRegistrationResult> rebuildFaceLibrary({
|
|
String? userId,
|
|
}) async {
|
|
final map = await _channel.invokeMethod<dynamic>('rebuildFaceLibrary', {
|
|
'userId': userId,
|
|
});
|
|
if (map is Map) return FaceRegistrationResult.fromMap(map);
|
|
return const FaceRegistrationResult(
|
|
success: false,
|
|
message: '人脸库注册失败,请稍后重试',
|
|
total: 0,
|
|
successCount: 0,
|
|
failedCount: 0,
|
|
failedSampleIds: [],
|
|
durationMs: 0,
|
|
);
|
|
}
|
|
|
|
/// 从临时图片提取注册特征,不修改虹软内存库和本地数据库。
|
|
static Future<FaceFeatureExtractionResult> extractFaceFeature({
|
|
required String filePath,
|
|
}) async {
|
|
final map = await _channel.invokeMethod<dynamic>('extractFaceFeature', {
|
|
'filePath': filePath,
|
|
});
|
|
if (map is Map) return FaceFeatureExtractionResult.fromMap(map);
|
|
return const FaceFeatureExtractionResult(
|
|
success: false,
|
|
message: '原生未返回有效的人脸特征结果',
|
|
featureData: [],
|
|
);
|
|
}
|
|
|
|
/// 在原生人脸操作互斥边界内批量检测本地在库照片质量。
|
|
///
|
|
/// 文件路径仅传入本机 Android 插件;原生逐图解码并通过 [qualityAuditStream]
|
|
/// 报告进度,返回结果不会自动修改 Floor 数据库。
|
|
static Future<FaceQualityAuditResult> auditFaceQuality({
|
|
required List<FaceQualityAuditItem> items,
|
|
}) async {
|
|
final map = await _channel.invokeMethod<dynamic>('auditFaceQuality', {
|
|
'items': items.map((item) => item.toMap()).toList(growable: false),
|
|
});
|
|
if (map is Map) return FaceQualityAuditResult.fromMap(map);
|
|
return const FaceQualityAuditResult(
|
|
results: [],
|
|
message: '原生未返回有效的质量检测结果',
|
|
);
|
|
}
|
|
|
|
/// 在虹软内存库中原子应用单样本新增、替换或删除。
|
|
///
|
|
/// 替换失败时原生层恢复旧特征;[operation] 仅允许 upsert 或 delete。
|
|
static Future<FaceDeltaApplyResult> applyFaceDelta({
|
|
required String operation,
|
|
required String sampleId,
|
|
String? userId,
|
|
String? username,
|
|
List<int>? featureData,
|
|
}) async {
|
|
final map = await _channel.invokeMethod<dynamic>('applyFaceDelta', {
|
|
'operation': operation,
|
|
'sampleId': int.tryParse(sampleId),
|
|
'userId': userId,
|
|
'username': username,
|
|
'featureData': featureData,
|
|
});
|
|
if (map is Map) return FaceDeltaApplyResult.fromMap(map);
|
|
return const FaceDeltaApplyResult(
|
|
success: false,
|
|
message: '原生未返回有效的人脸增量结果',
|
|
code: -99,
|
|
);
|
|
}
|
|
|
|
static Future<RegisterAllStorageFaceRes?> registerAllStorageFace({
|
|
required String filePath,
|
|
String? fileName,
|
|
}) async {
|
|
final pa = {
|
|
"filePath": filePath,
|
|
"fileName": fileName,
|
|
};
|
|
final map =
|
|
await _channel.invokeMethod<dynamic>('registerAllStorageFace', pa);
|
|
if (map == null) return null;
|
|
final encode = json.encode(map);
|
|
return RegisterAllStorageFaceRes.fromJson(json.decode(encode));
|
|
}
|
|
|
|
static Future<FaceRes?> getFace({
|
|
required int sampleId,
|
|
}) async {
|
|
final pa = {
|
|
"sampleId": sampleId,
|
|
};
|
|
final map = await _channel.invokeMethod<dynamic>('getFace', pa);
|
|
if (map == null) return null;
|
|
final encode = json.encode(map);
|
|
return FaceRes.fromJson(json.decode(encode));
|
|
}
|
|
|
|
static Future<int> getFaceCount() async {
|
|
final pa = {};
|
|
final res = await _channel.invokeMethod<int>('getFaceCount', pa);
|
|
if (res == null) return 0;
|
|
return res;
|
|
}
|
|
|
|
static Future<bool> removeFace({
|
|
required String sampleId,
|
|
}) async {
|
|
final sampleIdInt = int.tryParse(sampleId);
|
|
final pa = {
|
|
"sampleId": sampleIdInt,
|
|
};
|
|
final res = await _channel.invokeMethod<bool?>('removeFace', pa);
|
|
if (res == null) return false;
|
|
return res;
|
|
}
|
|
|
|
static Future<List<int>> registerFaceByDatabase({
|
|
String? userId,
|
|
}) async {
|
|
final pa = {
|
|
"userId": userId,
|
|
};
|
|
final res = await _channel.invokeMethod('registerFaceByDatabase', pa);
|
|
final errorSampleIdList = (res as List).cast<int>();
|
|
return errorSampleIdList;
|
|
}
|
|
|
|
static Stream<FaceStream?> get faceStream {
|
|
_stream ??= _eventChannel.receiveBroadcastStream().map((event) {
|
|
if (event == null) return null;
|
|
if (event == 'null') return null;
|
|
return FaceStream.fromJson(jsonDecode(event));
|
|
});
|
|
return _stream!;
|
|
}
|
|
|
|
static Stream<FaceRegistrationProgress> get registrationStream {
|
|
_registrationStream ??= _registrationEventChannel
|
|
.receiveBroadcastStream()
|
|
.where((event) => event is Map)
|
|
.map((event) => FaceRegistrationProgress.fromMap(event as Map));
|
|
return _registrationStream!;
|
|
}
|
|
|
|
/// 在库人脸质量检测进度广播流;订阅时会先收到原生最新状态。
|
|
static Stream<FaceQualityAuditProgress> get qualityAuditStream {
|
|
_qualityAuditStream ??= _qualityAuditEventChannel
|
|
.receiveBroadcastStream()
|
|
.where((event) => event is Map)
|
|
.map((event) => FaceQualityAuditProgress.fromMap(event as Map))
|
|
.asBroadcastStream();
|
|
return _qualityAuditStream!;
|
|
}
|
|
}
|