99 lines
3.0 KiB
Swift
99 lines
3.0 KiB
Swift
import AVFoundation
|
|
import Observation
|
|
|
|
@Observable
|
|
final class AudioRecorder: NSObject, AVAudioRecorderDelegate {
|
|
var isRecording = false
|
|
var recordingURL: URL?
|
|
var errorMessage: String?
|
|
var audioLevel: Float = 0
|
|
var lastRecordingDuration: TimeInterval = 0
|
|
|
|
private var audioRecorder: AVAudioRecorder?
|
|
private var levelTimer: Timer?
|
|
private var currentFileURL: URL?
|
|
|
|
private func makeRecordingURL() -> URL {
|
|
FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("blitztext-\(UUID().uuidString).m4a")
|
|
}
|
|
|
|
func startRecording() {
|
|
errorMessage = nil
|
|
lastRecordingDuration = 0
|
|
recordingURL = nil
|
|
if let currentFileURL {
|
|
try? FileManager.default.removeItem(at: currentFileURL)
|
|
}
|
|
|
|
let settings: [String: Any] = [
|
|
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
|
|
AVSampleRateKey: 16000,
|
|
AVNumberOfChannelsKey: 1,
|
|
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
|
|
]
|
|
|
|
do {
|
|
let fileURL = makeRecordingURL()
|
|
currentFileURL = fileURL
|
|
audioRecorder = try AVAudioRecorder(url: fileURL, settings: settings)
|
|
audioRecorder?.delegate = self
|
|
audioRecorder?.isMeteringEnabled = true
|
|
audioRecorder?.record()
|
|
isRecording = true
|
|
startMetering()
|
|
} catch {
|
|
currentFileURL = nil
|
|
errorMessage = "Aufnahme konnte nicht gestartet werden: \(error.localizedDescription)"
|
|
}
|
|
}
|
|
|
|
func stopRecording() {
|
|
stopMetering()
|
|
lastRecordingDuration = audioRecorder?.currentTime ?? 0
|
|
audioRecorder?.stop()
|
|
isRecording = false
|
|
recordingURL = currentFileURL
|
|
currentFileURL = nil
|
|
audioRecorder = nil
|
|
audioLevel = 0
|
|
}
|
|
|
|
func discardRecording() {
|
|
if let recordingURL {
|
|
try? FileManager.default.removeItem(at: recordingURL)
|
|
self.recordingURL = nil
|
|
}
|
|
|
|
if let currentFileURL {
|
|
try? FileManager.default.removeItem(at: currentFileURL)
|
|
self.currentFileURL = nil
|
|
}
|
|
}
|
|
|
|
private func startMetering() {
|
|
levelTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in
|
|
guard let self else { return }
|
|
self.audioRecorder?.updateMeters()
|
|
let power = self.audioRecorder?.averagePower(forChannel: 0) ?? -160
|
|
let normalized = max(0, min(1, (power + 50) / 50))
|
|
self.audioLevel = normalized
|
|
}
|
|
}
|
|
|
|
private func stopMetering() {
|
|
levelTimer?.invalidate()
|
|
levelTimer = nil
|
|
}
|
|
|
|
// MARK: - AVAudioRecorderDelegate
|
|
|
|
nonisolated func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
|
|
if !flag {
|
|
Task { @MainActor in
|
|
self.errorMessage = "Aufnahme fehlgeschlagen"
|
|
}
|
|
}
|
|
}
|
|
}
|