AudioRecorderProtocol.swift 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import Foundation
  2. import Combine
  3. /// The physical source selected for one recording segment.
  4. enum RecordingSourceChoice: Identifiable, Equatable {
  5. case iPhone
  6. case spark(deviceID: String, displayName: String)
  7. var id: String {
  8. switch self {
  9. case .iPhone:
  10. return "iphone"
  11. case .spark(let deviceID, _):
  12. return "spark-\(deviceID)"
  13. }
  14. }
  15. var displayName: String {
  16. switch self {
  17. case .iPhone:
  18. return "iPhone 麦克风"
  19. case .spark(_, let displayName):
  20. return displayName
  21. }
  22. }
  23. var systemImage: String {
  24. switch self {
  25. case .iPhone: return "iphone"
  26. case .spark: return "wave.3.right.circle.fill"
  27. }
  28. }
  29. }
  30. enum AudioRecorderError: LocalizedError {
  31. case failedToStart(String)
  32. case failedToStop(String)
  33. var errorDescription: String? {
  34. switch self {
  35. case .failedToStart(let message), .failedToStop(let message):
  36. return message
  37. }
  38. }
  39. }
  40. /// Protocol abstracting audio recording capabilities.
  41. /// Allows swapping between Mock and Real (AVAudioEngine) implementations.
  42. protocol AudioRecorderProtocol: ObservableObject {
  43. /// Current recording state
  44. var isRecording: Bool { get }
  45. /// Elapsed recording time in seconds
  46. var elapsedTime: TimeInterval { get }
  47. /// Current amplitude level (0.0 - 1.0) for waveform visualization
  48. var currentAmplitude: Float { get }
  49. /// Rolling buffer of recent amplitude samples for waveform drawing
  50. var waveformSamples: [Float] { get }
  51. /// The local file URL of the current/last recording. nil for mock.
  52. var outputFileURL: URL? { get }
  53. /// Whether the active recording is paused
  54. var isPaused: Bool { get }
  55. /// User-facing name of the active recording source.
  56. var sourceDisplayName: String { get }
  57. /// Current source status, such as connecting or recording.
  58. var statusMessage: String { get }
  59. /// A recoverable error that should be shown in the recording UI.
  60. var errorMessage: String? { get }
  61. func startRecording()
  62. func stopRecording()
  63. func stopRecording(completion: @escaping (Result<URL?, Error>) -> Void)
  64. func pauseRecording()
  65. func resumeRecording()
  66. }
  67. extension AudioRecorderProtocol {
  68. var sourceDisplayName: String { "iPhone 麦克风" }
  69. var statusMessage: String { isRecording ? "正在录音" : "正在准备" }
  70. var errorMessage: String? { nil }
  71. func stopRecording(completion: @escaping (Result<URL?, Error>) -> Void) {
  72. stopRecording()
  73. completion(.success(outputFileURL))
  74. }
  75. }