camera_service.dart 4.6 KB

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