|
|
@@ -0,0 +1,164 @@
|
|
|
+import 'dart:io';
|
|
|
+import 'package:camera/camera.dart' as camera_pkg;
|
|
|
+import 'package:sino_med_cloud/core/utils/logger.dart';
|
|
|
+import 'media_exception.dart';
|
|
|
+
|
|
|
+// 使用别名避免命名冲突
|
|
|
+typedef CameraController = camera_pkg.CameraController;
|
|
|
+typedef ResolutionPreset = camera_pkg.ResolutionPreset;
|
|
|
+typedef CameraLensDirection = camera_pkg.CameraLensDirection;
|
|
|
+typedef XFile = camera_pkg.XFile;
|
|
|
+
|
|
|
+/// 专业相机服务
|
|
|
+///
|
|
|
+/// 适合医疗影像、证件采集、高质量拍照、连拍/自定义UI
|
|
|
+///
|
|
|
+/// 使用示例:
|
|
|
+/// ```dart
|
|
|
+/// final cameraService = CameraService();
|
|
|
+/// await cameraService.init();
|
|
|
+/// final photo = await cameraService.takePicture();
|
|
|
+/// cameraService.dispose();
|
|
|
+/// ```
|
|
|
+class CameraService {
|
|
|
+ CameraController? _controller;
|
|
|
+ bool _isInitialized = false;
|
|
|
+ bool _isDisposed = false;
|
|
|
+
|
|
|
+ /// 初始化相机
|
|
|
+ ///
|
|
|
+ /// - [resolutionPreset] 分辨率预设,默认 veryHigh
|
|
|
+ /// - [enableAudio] 是否启用音频,默认 false
|
|
|
+ /// - [lensDirection] 镜头方向,默认后置摄像头
|
|
|
+ ///
|
|
|
+ /// 抛出 [MediaCameraException] 如果初始化失败
|
|
|
+ Future<void> init({
|
|
|
+ ResolutionPreset resolutionPreset = camera_pkg.ResolutionPreset.veryHigh,
|
|
|
+ bool enableAudio = false,
|
|
|
+ CameraLensDirection lensDirection = camera_pkg.CameraLensDirection.back,
|
|
|
+ }) async {
|
|
|
+ try {
|
|
|
+ if (_isDisposed) {
|
|
|
+ throw MediaCameraException('相机服务已释放,无法初始化');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_isInitialized) {
|
|
|
+ AppLogger.d('相机已初始化,跳过重复初始化');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ AppLogger.d('开始初始化相机...');
|
|
|
+ final cameras = await camera_pkg.availableCameras();
|
|
|
+
|
|
|
+ if (cameras.isEmpty) {
|
|
|
+ throw MediaCameraException('未找到可用相机');
|
|
|
+ }
|
|
|
+
|
|
|
+ final camera = cameras.firstWhere(
|
|
|
+ (c) => c.lensDirection == lensDirection,
|
|
|
+ orElse: () => cameras.first,
|
|
|
+ );
|
|
|
+
|
|
|
+ _controller = camera_pkg.CameraController(
|
|
|
+ camera,
|
|
|
+ resolutionPreset,
|
|
|
+ enableAudio: enableAudio,
|
|
|
+ );
|
|
|
+
|
|
|
+ await _controller!.initialize();
|
|
|
+ _isInitialized = true;
|
|
|
+ AppLogger.d('相机初始化成功: ${camera.name}');
|
|
|
+ } catch (e) {
|
|
|
+ _isInitialized = false;
|
|
|
+ if (e is camera_pkg.CameraException) {
|
|
|
+ AppLogger.e('相机初始化失败(原生异常)', e);
|
|
|
+ throw MediaCameraException(
|
|
|
+ '相机初始化失败: ${e.toString()}',
|
|
|
+ originalError: e,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (e is MediaCameraException) {
|
|
|
+ rethrow;
|
|
|
+ }
|
|
|
+ AppLogger.e('相机初始化失败', e);
|
|
|
+ throw MediaCameraException(
|
|
|
+ '相机初始化失败: ${e.toString()}',
|
|
|
+ originalError: e,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 获取相机控制器
|
|
|
+ ///
|
|
|
+ /// 抛出 [MediaCameraException] 如果相机未初始化
|
|
|
+ CameraController get controller {
|
|
|
+ if (!_isInitialized || _controller == null) {
|
|
|
+ throw MediaCameraException('相机未初始化,请先调用 init()');
|
|
|
+ }
|
|
|
+ return _controller!;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 检查相机是否已初始化
|
|
|
+ bool get isInitialized => _isInitialized && _controller?.value.isInitialized == true;
|
|
|
+
|
|
|
+ /// 拍照
|
|
|
+ ///
|
|
|
+ /// 返回拍摄的照片文件,如果失败返回 null
|
|
|
+ ///
|
|
|
+ /// 抛出 [MediaCameraException] 如果拍照失败
|
|
|
+ Future<File?> takePicture() async {
|
|
|
+ try {
|
|
|
+ if (!isInitialized) {
|
|
|
+ throw MediaCameraException('相机未初始化,无法拍照');
|
|
|
+ }
|
|
|
+
|
|
|
+ AppLogger.d('开始拍照...');
|
|
|
+ final camera_pkg.XFile file = await _controller!.takePicture();
|
|
|
+ final photoFile = File(file.path);
|
|
|
+
|
|
|
+ if (!await photoFile.exists()) {
|
|
|
+ throw MediaCameraException('拍照失败:文件不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ AppLogger.d('拍照成功: ${photoFile.path}');
|
|
|
+ return photoFile;
|
|
|
+ } catch (e) {
|
|
|
+ if (e is camera_pkg.CameraException) {
|
|
|
+ AppLogger.e('拍照失败(原生异常)', e);
|
|
|
+ throw MediaCameraException(
|
|
|
+ '拍照失败: ${e.toString()}',
|
|
|
+ originalError: e,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (e is MediaCameraException) {
|
|
|
+ rethrow;
|
|
|
+ }
|
|
|
+ AppLogger.e('拍照失败', e);
|
|
|
+ throw MediaCameraException(
|
|
|
+ '拍照失败: ${e.toString()}',
|
|
|
+ originalError: e,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 释放相机资源
|
|
|
+ ///
|
|
|
+ /// 必须在不再使用相机时调用,否则可能导致资源泄漏
|
|
|
+ Future<void> dispose() async {
|
|
|
+ if (_isDisposed) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (_controller != null) {
|
|
|
+ await _controller!.dispose();
|
|
|
+ _controller = null;
|
|
|
+ }
|
|
|
+ _isInitialized = false;
|
|
|
+ _isDisposed = true;
|
|
|
+ AppLogger.d('相机资源已释放');
|
|
|
+ } catch (e) {
|
|
|
+ AppLogger.e('释放相机资源失败', e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|