I'm currently trying to use AVSpeechSynthesizer
to speak text from within an iOS Safari extension:
let synthesizer = AVSpeechSynthesizer()
...
let utterance = AVSpeechUtterance(string: self.text)
utterance.rate = 0.55;
self.synthesizer.speak(utterance)
On a simulator this works fine. However, on a physical device, I get the following error (even when the device is unmuted/volume-up):
NSURLConnection finished with error - code -1002
NSURLConnection finished with error - code -1002
NSURLConnection finished with error - code -1002
[AXTTSCommon] Failure starting audio queue alp!
[AXTTSCommon] Run loop timed out waiting for free audio buffer
[AXTTSCommon] _BeginSpeaking: speech cancelled error: Error Domain=TTSErrorDomain Code=-4001 "(null)"
[AXTTSCommon] _BeginSpeaking: couldn't begin playback
I have looked through quite a few SO and Apple Dev Forums threads and have tried many of the proposed solutions with no luck. Here are the things I've tried:
Linking AVFAudio.framework
and AVFoundation.framework
to the extension.
Starting an AVAudioSession
prior to playing the utterance:
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch let error {
print("Error starting audio: \(error.localizedDescription)")
}
This actually results in another error being thrown right before the same errors above:
Error starting audio: The operation couldn’t be completed. (OSStatus error 2003329396.)
mp3
audio file:guard let url = Bundle.main.url(forResource: "sample", withExtension: "mp3") else {
print("Couldn't find file")
return
}
do {
self.player = try AVAudioPlayer(contentsOf: url)
self.player.play()
print("**playing sound")
} catch let error as NSError {
print("Error playing sound: \(error.localizedDescription)")
}
This prints the following:
**playing sound
[aqsrv] AQServer.cpp:72 Exception caught in AudioQueueInternalNotifyRunning - error -66671
Audio, AirPlay, and Picture in Picture
in Background Modes for the main target app (not available for the extension).Any help would be appreciated.
EDIT:
The solution below gets rejected due to a validation error when submitting to App Store Connect.
I filed a Technical Support Incident with Apple, and this was their response:
Safari extensions are very short-lived, hence not fit for audio playback or speech synthesis. Not being able to validate an app extension in Xcode with a manually-added plist entry for background audio is the designed behavior. The general recommendation is to synthesize speech using JavaScript in conjunction with the Web Speech API.
TLDR: Use the Web Speech API for text-to-speech in Safari extensions, not AVSpeechSynthesizer
.
Original answer:
Adding the following to the extension's Info.plist
allowed the audio to play as expected:
<dict>
...
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
...
</dict>
Interestingly, it actually shows the same errors in the console as before, but it does play the audio.