diff --git a/Heim/Application/Application/Source/SceneDelegate.swift b/Heim/Application/Application/Source/SceneDelegate.swift index c4047606..07f0da39 100644 --- a/Heim/Application/Application/Source/SceneDelegate.swift +++ b/Heim/Application/Application/Source/SceneDelegate.swift @@ -241,6 +241,10 @@ private extension SceneDelegate { return DefaultSettingCoordinator(navigationController: navigationController) } + DIContainer.shared.register(type: RecordManagerProtocol.self) { _ in + return DefaultRecordManager() + } + DIContainer.shared.register(type: RecordCoordinator.self) { _ in return DefaultRecordCoordinator(navigationController: recordNavigationController) } diff --git a/Heim/Presentation/Presentation/Record/RecordFeature/Coordinator/RecordCoordinator.swift b/Heim/Presentation/Presentation/Record/RecordFeature/Coordinator/RecordCoordinator.swift index d5ea98f4..2425f42e 100644 --- a/Heim/Presentation/Presentation/Record/RecordFeature/Coordinator/RecordCoordinator.swift +++ b/Heim/Presentation/Presentation/Record/RecordFeature/Coordinator/RecordCoordinator.swift @@ -58,7 +58,11 @@ public final class DefaultRecordCoordinator: RecordCoordinator { // MARK: - Private private extension DefaultRecordCoordinator { func createRecordViewController() -> RecordViewController? { - let viewModel = RecordViewModel() + guard let recordManager = DIContainer.shared.resolve(type: RecordManagerProtocol.self) else { + return nil + } + + let viewModel = RecordViewModel(recordManager: recordManager) let viewController = RecordViewController(viewModel: viewModel) viewController.coordinator = self return viewController diff --git a/Heim/Presentation/Presentation/Record/RecordFeature/Manager/RecordManager.swift b/Heim/Presentation/Presentation/Record/RecordFeature/Manager/RecordManager.swift index 29e61fde..335b10d9 100644 --- a/Heim/Presentation/Presentation/Record/RecordFeature/Manager/RecordManager.swift +++ b/Heim/Presentation/Presentation/Record/RecordFeature/Manager/RecordManager.swift @@ -10,7 +10,19 @@ import Domain import UIKit import Speech -final class RecordManager { +public protocol RecordManagerProtocol { + var recognizedText: String { get } + var minuteAndSeconds: Int { get } + var voice: Voice? { get } + var formattedTime: String { get } + + func setupSpeech() async throws + func startRecording() throws + func stopRecording() + func resetAll() +} + +public final class DefaultRecordManager: RecordManagerProtocol { // MARK: - 음성 인식을 위한 Properties private let speechRecognizer: SFSpeechRecognizer private let audioEngine: AVAudioEngine @@ -18,8 +30,8 @@ final class RecordManager { private var recognitionTask: SFSpeechRecognitionTask? // MARK: - 인식된 텍스트, 경과한 시간 - private(set) var recognizedText: String - private(set) var minuteAndSeconds: Int + private(set) public var recognizedText: String + private(set) public var minuteAndSeconds: Int // MARK: - 음성 녹음을 위한 Properties private var audioRecorder: AVAudioRecorder? @@ -27,15 +39,15 @@ final class RecordManager { private var timer: Timer? // MARK: - 음성 데이터 - private(set) var voice: Voice? + private(set) public var voice: Voice? - var formattedTime: String { + public var formattedTime: String { let minutes = minuteAndSeconds / 60 let seconds = minuteAndSeconds % 60 return String(format: "%02d:%02d", minutes, seconds) } - init(locale: Locale = Locale(identifier: "ko-KR")) { + public init(locale: Locale = Locale(identifier: "ko-KR")) { self.speechRecognizer = SFSpeechRecognizer(locale: locale)! self.audioEngine = AVAudioEngine() self.recognizedText = "" @@ -47,7 +59,7 @@ final class RecordManager { } // MARK: - 녹음과정 준비 - func setupSpeech() async throws { + public func setupSpeech() async throws { let speechStatus = await withCheckedContinuation { continuation in SFSpeechRecognizer.requestAuthorization { status in continuation.resume(returning: status) @@ -68,7 +80,7 @@ final class RecordManager { } } - func startRecording() throws { + public func startRecording() throws { if recognitionRequest == nil { // MARK: - 새로운 녹음 시작 try setupNewRecording() @@ -79,7 +91,7 @@ final class RecordManager { } // MARK: - 일시중지 - func stopRecording() { + public func stopRecording() { audioEngine.pause() audioRecorder?.stop() @@ -93,7 +105,7 @@ final class RecordManager { timer = nil } - func resetAll() { + public func resetAll() { audioEngine.stop() audioEngine.inputNode.removeTap(onBus: 0) @@ -116,7 +128,7 @@ final class RecordManager { } } -private extension RecordManager { +private extension DefaultRecordManager { // MARK: - 녹음 재개 func resumeRecording() throws { do { @@ -139,19 +151,19 @@ private extension RecordManager { // 오디오 세션 설정 let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.playAndRecord, - mode: .default, - options: [.defaultToSpeaker, .allowBluetooth]) + mode: .default, + options: [.defaultToSpeaker, .allowBluetooth]) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) - + // PCM 설정 let settings: [String: Any] = [ - AVFormatIDKey: Int(kAudioFormatLinearPCM), - AVSampleRateKey: 44100.0, - AVNumberOfChannelsKey: 2, // 스테레오로 변경 - AVLinearPCMBitDepthKey: 16, - AVLinearPCMIsFloatKey: false, - AVLinearPCMIsBigEndianKey: false, - AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue + AVFormatIDKey: Int(kAudioFormatLinearPCM), + AVSampleRateKey: 44100.0, + AVNumberOfChannelsKey: 2, // 스테레오로 변경 + AVLinearPCMBitDepthKey: 16, + AVLinearPCMIsFloatKey: false, + AVLinearPCMIsBigEndianKey: false, + AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue ] // 기존 파일이 있다면 제거 @@ -219,3 +231,4 @@ private extension RecordManager { } } } + diff --git a/Heim/Presentation/Presentation/Record/RecordFeature/ViewModel/RecordViewModel.swift b/Heim/Presentation/Presentation/Record/RecordFeature/ViewModel/RecordViewModel.swift index 8a07a01f..f085dc81 100644 --- a/Heim/Presentation/Presentation/Record/RecordFeature/ViewModel/RecordViewModel.swift +++ b/Heim/Presentation/Presentation/Record/RecordFeature/ViewModel/RecordViewModel.swift @@ -28,7 +28,7 @@ public final class RecordViewModel: ViewModel { } @Published public var state: State - private var recordManager: RecordManager + private var recordManager: RecordManagerProtocol private var timer: Timer? private var isPaused: Bool = false @@ -36,9 +36,9 @@ public final class RecordViewModel: ViewModel { private var recognizedText: String? // MARK: - Initializer - public init() { + public init(recordManager: RecordManagerProtocol) { self.state = State() - self.recordManager = RecordManager() + self.recordManager = recordManager Task { try await recordManager.setupSpeech() @@ -133,3 +133,4 @@ private extension RecordViewModel { timer = nil } } +