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

[Refactor] 에러 핸들링 개선 #35

Merged
merged 9 commits into from
Feb 2, 2025
80 changes: 42 additions & 38 deletions alsongDalsong/ASAudioKit/ASAudioAnalyzer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,49 @@ import Foundation

public enum ASAudioAnalyzer {
public static func analyze(data: Data, samplesCount: Int) async throws -> [CGFloat] {
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".m4a")
try data.write(to: tempURL)
let file = try AVAudioFile(forReading: tempURL)

guard
let format = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: file.fileFormat.sampleRate,
channels: file.fileFormat.channelCount,
interleaved: false
),
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(file.length))
else {
return []
do {
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".m4a")
try data.write(to: tempURL)
let file = try AVAudioFile(forReading: tempURL)

guard
let format = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: file.fileFormat.sampleRate,
channels: file.fileFormat.channelCount,
interleaved: false
),
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(file.length))
else {
return []
}

try file.read(into: buffer)
guard let floatChannelData = buffer.floatChannelData else {
return []
}

let frameLength = Int(buffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: floatChannelData[0], count: frameLength))
var result = [CGFloat]()
let chunkedSamples = samples.chunked(into: samples.count / samplesCount)

for chunk in chunkedSamples {
let squaredSum = chunk.reduce(0) { $0 + $1 * $1 }
let averagePower = squaredSum / Float(chunk.count)
let decibels = 10 * log10(max(averagePower, Float.ulpOfOne))

let newAmplitude = 1.8 * pow(10.0, decibels / 20.0)
let clampedAmplitude = min(max(CGFloat(newAmplitude), 0), 1)
result.append(clampedAmplitude)
}

try? FileManager.default.removeItem(at: tempURL)

return result
} catch {
throw ASAudioErrors(type: .analyze, reason: error.localizedDescription, file: #file, line: #line)
}

try file.read(into: buffer)
guard let floatChannelData = buffer.floatChannelData else {
return []
}

let frameLength = Int(buffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: floatChannelData[0], count: frameLength))
var result = [CGFloat]()
let chunkedSamples = samples.chunked(into: samples.count / samplesCount)

for chunk in chunkedSamples {
let squaredSum = chunk.reduce(0) { $0 + $1 * $1 }
let averagePower = squaredSum / Float(chunk.count)
let decibels = 10 * log10(max(averagePower, Float.ulpOfOne))

let newAmplitude = 1.8 * pow(10.0, decibels / 20.0)
let clampedAmplitude = min(max(CGFloat(newAmplitude), 0), 1)
result.append(clampedAmplitude)
}

try? FileManager.default.removeItem(at: tempURL)

return result
}
}

Expand Down
19 changes: 19 additions & 0 deletions alsongDalsong/ASAudioKit/ASAudioErrors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

struct ASAudioErrors: LocalizedError {
let type: ErrorType
let reason: String
let file: String
let line: Int

enum ErrorType {
case analyze
case startPlaying, getDuration
case configureAudioSession
case startRecording
}

var errorDescription: String? {
return "[\(URL(fileURLWithPath: file).lastPathComponent):\(line)] \(type) 에러: \n\(reason)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
4E8907C22CE2489A00D5B547 /* ASAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E89071E2CE23D1B00D5B547 /* ASAudioKit.framework */; platformFilter = ios; };
4EB1ED372CE88E500012FFBA /* ASAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E89071E2CE23D1B00D5B547 /* ASAudioKit.framework */; };
4EB1ED382CE88E500012FFBA /* ASAudioKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4E89071E2CE23D1B00D5B547 /* ASAudioKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8593659D2D3F77FE0086C8C4 /* ASAudioErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8593659C2D3F77F60086C8C4 /* ASAudioErrors.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -50,6 +51,7 @@
4E89071E2CE23D1B00D5B547 /* ASAudioKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ASAudioKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4E8907BE2CE2489A00D5B547 /* ASAudioKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ASAudioKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4EB1EC802CE7AA160012FFBA /* ASAudioDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ASAudioDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
8593659C2D3F77F60086C8C4 /* ASAudioErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASAudioErrors.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
Expand Down Expand Up @@ -125,6 +127,7 @@
isa = PBXGroup;
children = (
1B7EB01A2CFDA83B00B2BE2A /* ASAudioAnalyzer.swift */,
8593659C2D3F77F60086C8C4 /* ASAudioErrors.swift */,
4E8907AD2CE240D400D5B547 /* ASAudioKit */,
4E8907BF2CE2489A00D5B547 /* ASAudioKitTests */,
4EB1EC812CE7AA160012FFBA /* ASAudioDemo */,
Expand Down Expand Up @@ -305,6 +308,7 @@
buildActionMask = 2147483647;
files = (
1B7EB01C2CFDA83B00B2BE2A /* ASAudioAnalyzer.swift in Sources */,
8593659D2D3F77FE0086C8C4 /* ASAudioErrors.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,29 @@ public actor ASAudioPlayer: NSObject {
public var onPlaybackFinished: (@Sendable () async -> Void)?

/// 녹음파일을 재생하고 옵션에 따라 재생시간을 설정합니다.
public func startPlaying(data: Data, option: PlayType = .full) {
configureAudioSession()
public func startPlaying(data: Data, option: PlayType = .full) throws {
do {
try configureAudioSession()
audioPlayer = try AVAudioPlayer(data: data)
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
audioPlayer?.isMeteringEnabled = true
} catch {
// TODO: 오디오 객체생성 실패 시 처리
throw ASAudioErrors(type: .startPlaying, reason: error.localizedDescription, file: #file, line: #line)
}

switch option {
case .full:
audioPlayer?.play()
case let .partial(time):
audioPlayer?.currentTime = 0
audioPlayer?.play()
DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(time)) {
Task {
await self.stopPlaying()
}
case .full:
audioPlayer?.play()
case let .partial(time):
audioPlayer?.currentTime = 0
audioPlayer?.play()
DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(time)) {
Task {
await self.stopPlaying()
}
}
}
}

Expand All @@ -51,12 +52,13 @@ public actor ASAudioPlayer: NSObject {
}

/// 녹음파일의 총 녹음시간을 리턴합니다.
public func getDuration(data: Data) -> TimeInterval {
public func getDuration(data: Data) throws -> TimeInterval {
if audioPlayer == nil {
do {
audioPlayer = try AVAudioPlayer(data: data)
} catch {
// TODO: 오디오 객체생성 실패 시 처리
throw ASAudioErrors(type: .getDuration, reason: error.localizedDescription, file: #file, line: #line)
}
}
return audioPlayer?.duration ?? 0
Expand All @@ -66,13 +68,14 @@ public actor ASAudioPlayer: NSObject {
onPlaybackFinished = handler
}

private func configureAudioSession() {
private func configureAudioSession() throws {
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
// TODO: 세션 설정 실패에 따른 처리
throw ASAudioErrors(type: .configureAudioSession, reason: error.localizedDescription, file: #file, line: #line)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@ public actor ASAudioRecorder {

public init() {}
/// 녹음 후 저장될 파일의 위치를 지정하여 녹음합니다.
public func startRecording(url: URL) {
configureAudioSession()
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
]

public func startRecording(url: URL) throws {
do {
try configureAudioSession()
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
]
audioRecorder = try AVAudioRecorder(url: url, settings: settings)
audioRecorder?.prepareToRecord()
audioRecorder?.isMeteringEnabled = true
audioRecorder?.record()
} catch {
// TODO: AVAudioRecorder 객체 생성 실패 시에 대한 처리
throw ASAudioErrors(type: .startRecording, reason: error.localizedDescription, file: #file, line: #line)
}
}

/// 오디오 세션을 설정합니다.
private func configureAudioSession() {
private func configureAudioSession() throws {
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
// TODO: 세션 설정 실패에 따른 처리
throw ASAudioErrors(type: .configureAudioSession, reason: error.localizedDescription, file: #file, line: #line)
}
}

Expand Down
4 changes: 4 additions & 0 deletions alsongDalsong/ASDecoder/ASDecoder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
1B06307A2CE64E7C005300BF /* ASDecoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E612CE5193F00124F73 /* ASDecoder.framework */; };
1B06307B2CE64E7C005300BF /* ASDecoder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E612CE5193F00124F73 /* ASDecoder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1B702E6A2CE5193F00124F73 /* ASDecoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E612CE5193F00124F73 /* ASDecoder.framework */; };
8593659A2D3F71E80086C8C4 /* ASDecoderErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859365982D3F71E40086C8C4 /* ASDecoderErrors.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -47,6 +48,7 @@
1B06306B2CE64E74005300BF /* ASDecoderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ASDecoderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
1B702E612CE5193F00124F73 /* ASDecoder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ASDecoder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1B702E692CE5193F00124F73 /* ASDecoderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ASDecoderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
859365982D3F71E40086C8C4 /* ASDecoderErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASDecoderErrors.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand Down Expand Up @@ -104,6 +106,7 @@
1B702E572CE5193F00124F73 = {
isa = PBXGroup;
children = (
859365982D3F71E40086C8C4 /* ASDecoderErrors.swift */,
1B702E632CE5193F00124F73 /* ASDecoder */,
1B702E6D2CE5193F00124F73 /* ASDecoderTests */,
1B06306C2CE64E74005300BF /* ASDecoderDemo */,
Expand Down Expand Up @@ -285,6 +288,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8593659A2D3F71E80086C8C4 /* ASDecoderErrors.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
12 changes: 8 additions & 4 deletions alsongDalsong/ASDecoder/ASDecoder/ASDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import Foundation

public enum ASDecoder {
public static func decode<T: Decodable>(_: T.Type, from data: Data) throws -> T {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601

return try decoder.decode(T.self, from: data)
return try decoder.decode(T.self, from: data)
} catch {
throw ASDecoderErrors(type: .decode, reason: error.localizedDescription, file: #file, line: #line)
}
}

public static func handleResponse<T: Decodable>(result: Result<Data, Error>) async throws -> T {
Expand Down
16 changes: 16 additions & 0 deletions alsongDalsong/ASDecoder/ASDecoderErrors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

struct ASDecoderErrors: LocalizedError {
let type: ErrorType
let reason: String
let file: String
let line: Int

enum ErrorType {
case decode
}

var errorDescription: String? {
return "[\(URL(fileURLWithPath: file).lastPathComponent):\(line)] \(type) 에러: \n\(reason)"
}
}
4 changes: 4 additions & 0 deletions alsongDalsong/ASEncoder/ASEncoder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
1B1327F02CE5F26400EF706D /* ASEncoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E2F2CE5152600124F73 /* ASEncoder.framework */; };
1B1327F12CE5F26400EF706D /* ASEncoder.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E2F2CE5152600124F73 /* ASEncoder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1B702E382CE5152600124F73 /* ASEncoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B702E2F2CE5152600124F73 /* ASEncoder.framework */; };
859365972D3F71390086C8C4 /* ASEncoderErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859365952D3F71310086C8C4 /* ASEncoderErrors.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -47,6 +48,7 @@
1B702E2F2CE5152600124F73 /* ASEncoder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ASEncoder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1B702E372CE5152600124F73 /* ASEncoderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ASEncoderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1BDDABBD2CE5E2E000147881 /* ASEncoderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ASEncoderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
859365952D3F71310086C8C4 /* ASEncoderErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASEncoderErrors.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand Down Expand Up @@ -104,6 +106,7 @@
1B702E252CE5152600124F73 = {
isa = PBXGroup;
children = (
859365952D3F71310086C8C4 /* ASEncoderErrors.swift */,
1B702E312CE5152600124F73 /* ASEncoder */,
1B702E3B2CE5152600124F73 /* ASEncoderTests */,
1BDDABBE2CE5E2E000147881 /* ASEncoderDemo */,
Expand Down Expand Up @@ -278,6 +281,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
859365972D3F71390086C8C4 /* ASEncoderErrors.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
14 changes: 9 additions & 5 deletions alsongDalsong/ASEncoder/ASEncoder/ASEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import Foundation

public enum ASEncoder {
public static func encode<T: Encodable>(_ value: T) throws -> Data {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.keyEncodingStrategy = .useDefaultKeys
encoder.outputFormatting = [.prettyPrinted]
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.keyEncodingStrategy = .useDefaultKeys
encoder.outputFormatting = [.prettyPrinted]

return try encoder.encode(value)
return try encoder.encode(value)
} catch {
throw ASEncoderErrors(type: .encode, reason: error.localizedDescription, file: #file, line: #line)
}
}
}
16 changes: 16 additions & 0 deletions alsongDalsong/ASEncoder/ASEncoderErrors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

struct ASEncoderErrors: LocalizedError {
let type: ErrorType
let reason: String
let file: String
let line: Int

enum ErrorType {
case encode
}

var errorDescription: String? {
return "[\(URL(fileURLWithPath: file).lastPathComponent):\(line)] \(type) 에러: \n\(reason)"
}
}
Loading