Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Test] 음성 녹음 테스트 코드 작성 #183

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Heim/Application/Application/Source/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
35 changes: 33 additions & 2 deletions Heim/Presentation/Presentation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
4093ABB42CDB9CCA00F7E060 /* Domain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B307B4C02CD9B76A001C5040 /* Domain.framework */; };
4093ABB52CDB9CCA00F7E060 /* Domain.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B307B4C02CD9B76A001C5040 /* Domain.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
807206302CDB50F100123BFB /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8072062F2CDB50F100123BFB /* SnapKit */; };
80920C562D0192050068C81A /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 80920C552D0192050068C81A /* Lottie */; };
B307B3FE2CD9B4B7001C5040 /* Presentation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B307B3F32CD9B4B7001C5040 /* Presentation.framework */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -71,6 +72,7 @@
buildActionMask = 2147483647;
files = (
B307B3FE2CD9B4B7001C5040 /* Presentation.framework in Frameworks */,
80920C562D0192050068C81A /* Lottie in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -161,6 +163,7 @@
);
name = PresentationTests;
packageProductDependencies = (
80920C552D0192050068C81A /* Lottie */,
);
productName = PresentationTests;
productReference = B307B3FD2CD9B4B7001C5040 /* PresentationTests.xctest */;
Expand All @@ -182,6 +185,7 @@
};
B307B3FC2CD9B4B7001C5040 = {
CreatedOnToolsVersion = 16.0;
LastSwiftMigration = 1600;
};
};
};
Expand All @@ -197,6 +201,7 @@
packageReferences = (
8072062E2CDB50F100123BFB /* XCRemoteSwiftPackageReference "SnapKit" */,
B307B6BC2CDC7D48001C5040 /* XCRemoteSwiftPackageReference "SwiftLint" */,
80920C542D0192050068C81A /* XCRemoteSwiftPackageReference "lottie-spm" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = B307B3F42CD9B4B7001C5040 /* Products */;
Expand Down Expand Up @@ -481,32 +486,45 @@
B307B40C2CD9B4B7001C5040 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = B3PWYBKFUK;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.boostcamp9.Heim.PresentationTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
B307B40D2CD9B4B7001C5040 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = B3PWYBKFUK;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.boostcamp9.Heim.PresentationTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
Expand Down Expand Up @@ -551,6 +569,14 @@
minimumVersion = 5.7.1;
};
};
80920C542D0192050068C81A /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/airbnb/lottie-spm.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.5.0;
};
};
B307B6BC2CDC7D48001C5040 /* XCRemoteSwiftPackageReference "SwiftLint" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/SwiftLint";
Expand All @@ -567,6 +593,11 @@
package = 8072062E2CDB50F100123BFB /* XCRemoteSwiftPackageReference "SnapKit" */;
productName = SnapKit;
};
80920C552D0192050068C81A /* Lottie */ = {
isa = XCSwiftPackageProductDependency;
package = 80920C542D0192050068C81A /* XCRemoteSwiftPackageReference "lottie-spm" */;
productName = Lottie;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = B307B3EA2CD9B4B7001C5040 /* Project object */;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B307B3F22CD9B4B7001C5040"
BuildableName = "Presentation.framework"
BlueprintName = "Presentation"
ReferencedContainer = "container:Presentation.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B307B3FC2CD9B4B7001C5040"
BuildableName = "PresentationTests.xctest"
BlueprintName = "PresentationTests"
ReferencedContainer = "container:Presentation.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B307B3F22CD9B4B7001C5040"
BuildableName = "Presentation.framework"
BlueprintName = "Presentation"
ReferencedContainer = "container:Presentation.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,44 @@ import Domain
import UIKit
import Speech

final class RecordManager {
public protocol RecordManagerProtocol {
var recognizedText: String { get }
Comment on lines -13 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 저희 네이밍 컨벤션과 다른 것 같습니다!
  2. minuteAndSeconds가 추상화가 필요한 변수일까요?

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
Comment on lines +24 to 26
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RecordManager는 코드 품질을 개선할 여지가 많은 것 같습니다.
추후에 여유가 되신다면 도전해보셔도 좋을 것 같네요!

private let speechRecognizer: SFSpeechRecognizer
private let audioEngine: AVAudioEngine
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
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?
private var recordingURL: URL
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 = ""
Expand All @@ -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)
Expand All @@ -68,7 +80,7 @@ final class RecordManager {
}
}

func startRecording() throws {
public func startRecording() throws {
if recognitionRequest == nil {
// MARK: - 새로운 녹음 시작
try setupNewRecording()
Expand All @@ -79,7 +91,7 @@ final class RecordManager {
}

// MARK: - 일시중지
func stopRecording() {
public func stopRecording() {
audioEngine.pause()
audioRecorder?.stop()

Expand All @@ -93,7 +105,7 @@ final class RecordManager {
timer = nil
}

func resetAll() {
public func resetAll() {
audioEngine.stop()

audioEngine.inputNode.removeTap(onBus: 0)
Expand All @@ -116,7 +128,7 @@ final class RecordManager {
}
}

private extension RecordManager {
private extension DefaultRecordManager {
// MARK: - 녹음 재개
func resumeRecording() throws {
do {
Expand All @@ -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
]

// 기존 파일이 있다면 제거
Expand Down Expand Up @@ -219,3 +231,4 @@ private extension RecordManager {
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ 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
private var voice: Voice?
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()
Expand Down Expand Up @@ -133,3 +133,4 @@ private extension RecordViewModel {
timer = nil
}
}

Loading