camera_service.dart 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import 'dart:io';
  2. import 'package:camera/camera.dart' as camera_pkg;
  3. import 'package:sino_med_cloud/core/utils/logger.dart';
  4. import 'package:sino_med_cloud/core/utils/toast_utils.dart';
  5. import 'package:sino_med_cloud/core/constants/app_enum.dart';
  6. import 'package:sino_med_cloud/permission/permission_service.dart';
  7. import 'media_exception.dart';
  8. // 使用别名避免命名冲突
  9. typedef CameraController = camera_pkg.CameraController;
  10. typedef ResolutionPreset = camera_pkg.ResolutionPreset;
  11. typedef CameraLensDirection = camera_pkg.CameraLensDirection;
  12. typedef XFile = camera_pkg.XFile;
  13. /// 专业相机服务
  14. ///
  15. /// 适合医疗影像、证件采集、高质量拍照、连拍/自定义UI
  16. ///
  17. /// 使用示例:
  18. /// ```dart
  19. /// final cameraService = CameraService();
  20. /// await cameraService.init();
  21. /// final photo = await cameraService.takePicture();
  22. /// cameraService.dispose();
  23. /// ```
  24. class CameraService {
  25. CameraController? _controller;
  26. bool _isInitialized = false;
  27. bool _isDisposed = false;
  28. /// 初始化相机
  29. ///
  30. /// - [resolutionPreset] 分辨率预设,默认 veryHigh
  31. /// - [enableAudio] 是否启用音频,默认 false
  32. /// - [lensDirection] 镜头方向,默认后置摄像头
  33. ///
  34. /// 抛出 [MediaCameraException] 如果初始化失败
  35. Future<void> init({
  36. ResolutionPreset resolutionPreset = camera_pkg.ResolutionPreset.veryHigh,
  37. bool enableAudio = false,
  38. CameraLensDirection lensDirection = camera_pkg.CameraLensDirection.back,
  39. }) async {
  40. try {
  41. if (_isDisposed) {
  42. throw MediaCameraException('相机服务已释放,无法初始化');
  43. }
  44. if (_isInitialized) {
  45. AppLogger.d('相机已初始化,跳过重复初始化');
  46. return;
  47. }
  48. // 检查相机权限
  49. final permissionResult = await PermissionService.request(PermissionType.camera);
  50. if (permissionResult != PermissionResult.granted) {
  51. if (permissionResult == PermissionResult.permanentlyDenied) {
  52. ToastUtils.showError('相机权限被永久拒绝,请在设置中开启相机权限');
  53. // 自动打开设置页面
  54. await PermissionService.openSetting();
  55. throw MediaCameraException('相机权限被永久拒绝,已引导用户前往设置');
  56. } else {
  57. ToastUtils.showError('需要相机权限才能使用相机');
  58. throw MediaCameraException('相机权限被拒绝');
  59. }
  60. }
  61. AppLogger.d('开始初始化相机...');
  62. final cameras = await camera_pkg.availableCameras();
  63. if (cameras.isEmpty) {
  64. throw MediaCameraException('未找到可用相机');
  65. }
  66. final camera = cameras.firstWhere(
  67. (c) => c.lensDirection == lensDirection,
  68. orElse: () => cameras.first,
  69. );
  70. _controller = camera_pkg.CameraController(
  71. camera,
  72. resolutionPreset,
  73. enableAudio: enableAudio,
  74. );
  75. await _controller!.initialize();
  76. _isInitialized = true;
  77. AppLogger.d('相机初始化成功: ${camera.name}');
  78. } catch (e) {
  79. _isInitialized = false;
  80. if (e is camera_pkg.CameraException) {
  81. AppLogger.e('相机初始化失败(原生异常)', e);
  82. throw MediaCameraException(
  83. '相机初始化失败: ${e.toString()}',
  84. originalError: e,
  85. );
  86. }
  87. if (e is MediaCameraException) {
  88. rethrow;
  89. }
  90. AppLogger.e('相机初始化失败', e);
  91. throw MediaCameraException(
  92. '相机初始化失败: ${e.toString()}',
  93. originalError: e,
  94. );
  95. }
  96. }
  97. /// 获取相机控制器
  98. ///
  99. /// 抛出 [MediaCameraException] 如果相机未初始化
  100. CameraController get controller {
  101. if (!_isInitialized || _controller == null) {
  102. throw MediaCameraException('相机未初始化,请先调用 init()');
  103. }
  104. return _controller!;
  105. }
  106. /// 检查相机是否已初始化
  107. bool get isInitialized => _isInitialized && _controller?.value.isInitialized == true;
  108. /// 拍照
  109. ///
  110. /// 返回拍摄的照片文件,如果失败返回 null
  111. ///
  112. /// 抛出 [MediaCameraException] 如果拍照失败
  113. Future<File?> takePicture() async {
  114. try {
  115. if (!isInitialized) {
  116. throw MediaCameraException('相机未初始化,无法拍照');
  117. }
  118. AppLogger.d('开始拍照...');
  119. final camera_pkg.XFile file = await _controller!.takePicture();
  120. final photoFile = File(file.path);
  121. if (!await photoFile.exists()) {
  122. throw MediaCameraException('拍照失败:文件不存在');
  123. }
  124. AppLogger.d('拍照成功: ${photoFile.path}');
  125. return photoFile;
  126. } catch (e) {
  127. if (e is camera_pkg.CameraException) {
  128. AppLogger.e('拍照失败(原生异常)', e);
  129. throw MediaCameraException(
  130. '拍照失败: ${e.toString()}',
  131. originalError: e,
  132. );
  133. }
  134. if (e is MediaCameraException) {
  135. rethrow;
  136. }
  137. AppLogger.e('拍照失败', e);
  138. throw MediaCameraException(
  139. '拍照失败: ${e.toString()}',
  140. originalError: e,
  141. );
  142. }
  143. }
  144. /// 释放相机资源
  145. ///
  146. /// 必须在不再使用相机时调用,否则可能导致资源泄漏
  147. Future<void> dispose() async {
  148. if (_isDisposed) {
  149. return;
  150. }
  151. try {
  152. if (_controller != null) {
  153. await _controller!.dispose();
  154. _controller = null;
  155. }
  156. _isInitialized = false;
  157. _isDisposed = true;
  158. AppLogger.d('相机资源已释放');
  159. } catch (e) {
  160. AppLogger.e('释放相机资源失败', e);
  161. }
  162. }
  163. }