I am trying to use Apple's Speech framework to do speech recognition on macOS 10.15.1. Before macOS 10.15, speech recognition was only available on iOS, but according to the documentation and this talk, should now be available on macOS as well.
However, all my my attempts to use it have resulted in the SFSpeechRecognizer
's isAvailable
property being set to false. Per that talk and the documentation I've enabled Siri and made sure that my app has the "Privacy - Speech Recognition Usage Description"
key set to a string value in Info.plist.
I've also tried enabling code signing (which this question suggests might be necessary), enabling Dictation under Keyboard > Dictation in the System preferences.
Here's some example code, although the specifics probably aren't important; I've tried it using a Storyboard instead of SwiftUI, putting the instantiation of the SFSpeechRecognizer
inside and outside the requestAuthorization
callback, leaving the locale unspecified, etc. Nothing seems to have any effect:
import SwiftUI
import Speech
struct ContentView: View {
func tryAuth() {
SFSpeechRecognizer.requestAuthorization { authStatus in
switch authStatus {
case .authorized:
print("authorized")
case .denied:
print("denied")
case .restricted:
print("restricted")
case .notDetermined:
print("notDetermined")
@unknown default:
print("unanticipated auth status encountered")
}
}
}
func speechTest() {
guard let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US")) else {
// Not supported for device's locale
print("couldnt get recognizer")
return
}
if !recognizer.isAvailable {
print("not available")
return
}
print("Success")
}
var body: some View {
VStack {
Button("Try auth") {
self.tryAuth()
}
Button("Test") {
self.speechTest()
}
}
}
}
What's especially odd is that if I run the app and then click the "Try auth" button, the authStatus
returned from the callback is always .authorized
. However, I've never been presented with a dialog asking me to authorize the app, and the app doesn't show up in the list of authorized apps under System Preferences > Security and Privacy > Privacy > Speech Recogniztion.
Nonetheless, clicking the "Test" button afterwards results in printing not available
.
It seems like there's some hole in my understanding of the macOS privacy/permissions system, but I'm not sure how to debug further. I also think it should be possible to get this working, because I've seen other questions on StackOverflow suggesting that people have done so, for example here, here.
EDIT: At the suggestion of a comment, I tried simply ignoring the fact that isAvailable
is false by replacing my check for it with code to actually try to transcribe a file, e.g.:
let request = SFSpeechURLRecognitionRequest(url: URL(fileURLWithPath: "/Users/james/Downloads/test.wav"))
recognizer.recognitionTask(with: request) { (result, error) in
guard let result = result else {
print("There was an error transcribing that file")
print("print \(error!.localizedDescription)")
return
}
if result.isFinal {
print(result.bestTranscription.formattedString)
}
}
Then it fails, printing: The operation couldn’t be completed. (kAFAssistantErrorDomain error 1700.)
. So it seems like it really is necessary to check for isAvailable
, and my question remains: how to get it to be true
?
Had similar problems with SFSpeechRecognizer... Perhaps you can set the delegate of SFSpeechRecognizer
before requesting for authorization, as shown here.
For example:
class ViewController: NSViewController {
var speechRecognizer: SFSpeechRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))
speechRecognizer.delegate = self
}
override func viewWillAppear() {
SFSpeechRecognizer.requestAuthorization { authStatus in
...
}
if !speechRecognizer.isAvailable {
print("Not available!")
}
let url = Bundle.main.url(forResource: "sample", withExtension: "mp3")!
let request = SFSpeechURLRecognitionRequest(url: url)
// will now ask for authorisation
speechRecognizer.recognitionTask(with: request) { (result, error) in
...
}
}
}
extension ViewController: SFSpeechRecognizerDelegate {
}
Then the authorisation dialog will be properly shown.
In addition, it seems that only when there is a call to recognitionTask
, that the user will be asked to give permission. Instead, calling requestAuthorization
alone will not have any effect.