This commit is contained in:
dzc committed 2026-08-09 14:45:41 +08:00
1 parent 2ee78a1561
commit 3f9a7df721
1 file changed
+194
@@ -0,0 +1,194 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:pj_supermarket_cloud/common/models/plugs/face_recognition_session_models.dart';
import 'package:pj_supermarket_cloud/common/utils/plugs/face_discern_plugs.dart';
import 'package:pj_supermarket_cloud/common/widgets/face/face_camera_preview.dart';
/// 主副屏共用的常驻人脸摄像头预览。
///
/// 组件只持有当前 Flutter Engine 的 Texture 和原生状态订阅,不创建、结束或重试
/// 业务识别 Session,因此查询结果展示的十秒内仍能保持实时画面。
class FaceLiveCameraPreview extends StatefulWidget {
/// 创建跟随宿主页面生命周期的实时预览区域。
const FaceLiveCameraPreview({
super.key,
required this.statusText,
required this.statusColor,
this.scale = 1,
});
/// 查询状态对应的可见文案,避免原生空闲态被误解为仍在识别下一位。
final String statusText;
/// 查询状态对应的语义颜色,必须与文字同时表达状态。
final Color statusColor;
/// 宿主 Engine 自己计算的尺寸比例,默认按 1920 x 1080 设计尺寸一比一展示。
final double scale;
/// 创建当前 Flutter Engine 的 Texture 生命周期状态。
@override
State<FaceLiveCameraPreview> createState() => _FaceLiveCameraPreviewState();
}
/// [FaceLiveCameraPreview] 的 Texture、原生事件和异常状态所有者。
class _FaceLiveCameraPreviewState extends State<FaceLiveCameraPreview> {
/// 当前 Flutter Engine 持有的原生预览纹理。
FaceRecognitionPreviewTexture? _texture;
/// 最近一次原生人脸会话事件,用于方向、人脸框和设备状态渲染。
FaceRecognitionSessionEvent? _event;
/// 常驻原生会话事件订阅,页面销毁时必须取消。
StreamSubscription<FaceRecognitionSessionEvent>? _eventSubscription;
/// Texture 异步创建序号,用于释放页面销毁或重建后迟到的旧资源。
int _textureRequestId = 0;
/// 当前预览初始化失败信息;为空表示按 Texture 和原生状态正常展示。
String? _previewError;
/// 订阅原生状态并创建当前 Engine 独立的输出 Texture。
@override
void initState() {
super.initState();
_eventSubscription = FaceDiscernPlugs.sessionStream.listen(
_handleEvent,
onError: _handleEventError,
);
unawaited(_loadInitialState());
unawaited(_initializeTexture());
}
/// 读取挂载前已存在的热摄像头状态,避免页面刚进入时长期停留在初始化提示。
Future<void> _loadInitialState() async {
try {
final event = await FaceDiscernPlugs.getRecognitionState();
// EventChannel 可能先送达真实检测帧;迟到的 MethodChannel 快照不含 faceBoxes,
// 只能用于尚未收到任何事件的首次占位,不能把已经显示的人脸框清空。
if (mounted && _event == null) setState(() => _event = event);
} catch (_) {
// 后续 EventChannel 仍可恢复状态,首次快照失败不覆盖真实 Texture 画面。
}
}
/// 更新最近原生状态;致命失败后的冷重试需要替换已经失效的 Renderer 输出。
void _handleEvent(FaceRecognitionSessionEvent event) {
if (!mounted || !_shouldAcceptEvent(event)) return;
final previousStatus = _event?.status;
final shouldReplace =
(previousStatus == FaceRecognitionSessionStatus.cold ||
previousStatus == FaceRecognitionSessionStatus.failed) &&
(event.status == FaceRecognitionSessionStatus.initializing ||
event.status == FaceRecognitionSessionStatus.detecting);
setState(() {
_event = event;
_previewError = null;
});
if (shouldReplace) unawaited(_initializeTexture(replace: true));
}
/// 按原生单调序号拒绝乱序快照,避免跨会话的旧空框覆盖较新的多人检测结果。
bool _shouldAcceptEvent(FaceRecognitionSessionEvent event) {
final current = _event;
if (current == null) return true;
if (current.eventSequence > 0 && event.eventSequence > 0) {
if (event.eventSequence != current.eventSequence) {
return event.eventSequence > current.eventSequence;
}
// MethodChannel 快照不含框;允许相同序号的真实 EventChannel 事件补齐它。
return current.faceBoxes.isEmpty && event.faceBoxes.isNotEmpty;
}
if (event.sessionId != current.sessionId) {
// 兼容旧版原生事件:活动识别期间不得让迟到的空闲资源快照清掉当前人脸框。
return !(current.isActive && event.sessionId.isEmpty);
}
return event.revision >= current.revision;
}
/// 将原生事件通道异常收敛为预览内提示,不中断主屏查询状态机。
void _handleEventError(Object error) {
if (mounted) setState(() => _previewError = '摄像头状态连接异常,请检查设备');
}
/// 创建或替换预览 Texture,并释放异步迟到或已被替换的输出。
Future<void> _initializeTexture({bool replace = false}) async {
final requestId = ++_textureRequestId;
if (replace) {
final previous = _texture;
_texture = null;
if (previous != null) {
try {
await FaceDiscernPlugs.releasePreviewTexture(previous.textureId);
} catch (_) {
// 原生 Renderer 已冷释放时旧端点可能失效,仍要继续创建新 Texture 恢复画面。
}
}
if (mounted) setState(() {});
}
try {
final texture = await FaceDiscernPlugs.createPreviewTexture();
if (!mounted || requestId != _textureRequestId) {
await FaceDiscernPlugs.releasePreviewTexture(texture.textureId);
return;
}
setState(() {
_texture = texture;
_previewError = null;
});
} catch (_) {
// 已被新一轮 Texture 请求替代的旧异常不得覆盖新画面。
if (mounted && requestId == _textureRequestId) {
setState(() => _previewError = '摄像头画面暂不可用,请检查设备');
}
}
}
/// 取消事件订阅并释放当前 Engine 的 Texture,不触碰业务识别 Session。
@override
void dispose() {
_textureRequestId++;
unawaited(_eventSubscription?.cancel());
final texture = _texture;
_texture = null;
if (texture != null) {
unawaited(FaceDiscernPlugs.releasePreviewTexture(texture.textureId));
}
super.dispose();
}
/// 构建不会因 Session 暂停或结果展示而移除的实时画面。
@override
Widget build(BuildContext context) {
final status = _previewError == null
? _event?.status ?? FaceRecognitionSessionStatus.initializing
: FaceRecognitionSessionStatus.failed;
return Stack(
fit: StackFit.expand,
children: [
FaceCameraPreview(
texture: _texture,
event: _event,
status: status,
statusText: widget.statusText,
statusColor: widget.statusColor,
scale: widget.scale,
),
if (_previewError != null)
Center(
child: Text(
_previewError!,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 16 * widget.scale,
fontWeight: FontWeight.w700,
),
),
),
],
);
}
}