Search code examples
iosswiftavfoundationsafari-extensionavspeechsynthesizer

Unable to play audio / speech from iOS Swift Safari Extension on device (OSStatus error 2003329396)


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:

  1. Linking AVFAudio.framework and AVFoundation.framework to the extension.

  2. 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.)
  1. Playing a plain 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
  1. Enabling Audio, AirPlay, and Picture in Picture in Background Modes for the main target app (not available for the extension).

Any help would be appreciated.


Solution

  • 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.