Files
voicediary/VoiceDiary/Services/TranscriptionService.swift
Felix Förtsch 3d42c83f75 fix speech authorization crash: decouple from SwiftData context
- Make TranscriptionService a plain Sendable class (not @Observable/@MainActor)
- Request speech authorization in ContentView.onAppear via callback (no async)
- Use @State pendingMemo + Task in View for transcription (Swift 6 safe)
- Separate saveRecording() and startTranscription() to avoid data races

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:31:59 +01:00

50 lines
1.3 KiB
Swift

import Foundation
import Speech
final class TranscriptionService: Sendable {
func requestAuthorization() async -> SFSpeechRecognizerAuthorizationStatus {
await withCheckedContinuation { continuation in
SFSpeechRecognizer.requestAuthorization { status in
continuation.resume(returning: status)
}
}
}
func transcribe(audioURL: URL) async throws -> String {
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "de-DE"))
guard let recognizer, recognizer.isAvailable else {
throw TranscriptionError.recognizerUnavailable
}
let request = SFSpeechURLRecognitionRequest(url: audioURL)
request.requiresOnDeviceRecognition = true
request.shouldReportPartialResults = false
request.addsPunctuation = true
return try await withCheckedThrowingContinuation { continuation in
recognizer.recognitionTask(with: request) { result, error in
if let error {
continuation.resume(throwing: error)
} else if let result, result.isFinal {
continuation.resume(returning: result.bestTranscription.formattedString)
}
}
}
}
}
enum TranscriptionError: LocalizedError {
case recognizerUnavailable
case noResult
var errorDescription: String? {
switch self {
case .recognizerUnavailable:
String(localized: "transcription.error.unavailable")
case .noResult:
String(localized: "transcription.error.noResult")
}
}
}