AVSpeechSynthesizer Broken on iOS 17

I just upgraded to iOS 17 and it looks like AVSpeechSynthesizer is now broken.

I noticed when feeding certain strings to AVSpeechUtterance it just dies flat out stops after only speaking a portion of the string.

For example I fed it a string of approx. 1200 words and it speaks up to around 300 words or so and then just stops. The synthesizer delegate method -speechSynthesizer:didFinishSpeechUtterance: is called when this happens, as if this is supposed to be the end even though it is not even close to being finishes.

Was working fine on iOS 16.

FWIW I create the AVSpeechUtterance with -initWithString:

  • how to fix this bug ? just to wait ?

  • Yeah pretty much all you can do is wait. I filed a TSI and it got credited back and they said there is no workaround to the issue.

    I advise everyone impacted by this to file a Feedback with Apple which (hopefully) will cause them to treat the issue with a higher priority if they see it is affecting a lot of apps. I'm pretty sure every app using AVSpeechSynthesizer is broken on iOS 17.

Add a Comment

Replies

I filed FB13188396

Part of my troubleshooting steps had me write the utterance to an AVAudioFile instead of just speaking it. This log seems like it could be related:

Input data proc returned inconsistent 512 packets for 2,048 bytes; at 2 bytes per packet, that is actually 1,024 packets

--

This really is a big setback for my development (I'm sure lots of other apps are affected by this too). Workaround I can think of are:

  1. Splitting the string into separate utterances

or 2) Write the utterance to a file and play the audio file.

Both options require me to restructure quite a bit of code and makes things much harder to maintain.

No symbols for system methods...making it hard for devs to figure out workarounds. Anyone know if there is a method I can swizzle to patch this?

Looks like this bug is triggered when using the "Daniel Enhanced" voice in combination with this particular string. If I change the voice suddenly AVSpeechSynthesizer plays the entire string fine...

Well oddly after changing the voice it works but for some reason when switching back to the Daniel enhanced voice it causes -speechSynthesizer:didFinishSpeechUtterance: to be called prematurely at the exact same location of the speech string every time in my app.

In the event of some sort of error where speech synthesis is supposed to stop prematurely I'd expect -speechSynthesizer:didCancelSpeechUtterance: to be called not -speechSynthesizer:didFinishSpeechUtterance: (though I think a new -speechSynthesizer:didFailWithError: would be a useful addition since -speechSynthesizer:didCancelSpeechUtterance: I believe is called when I programmatically stop an utterance).

Not sure why the synthesizer is prematurely calling -speechSynthesizer:didFinishSpeechUtterance:.

I set a breakpoint in -speechSynthesizer:didFinishSpeechUtterance: but the call stack doesn't include debug symbols for the system methods that are called before. A bunch of methods _lldb_nanamed_symbol_1111 are listed before -speechSynthesizer:didFinishSpeechUtterance so there's not a whole lot for me to go on. Anyone else run into this?

Tested after upgrading to Version 15.0.1 (15A507). No change unfortunately. Did notice AVSpeechSynthesizer no longer works in the simulator though but same issues on device has all the same problems.

Add a Comment

Got Xcode 15.3 Beta with iOS 17.2 fixes the problem

Just downloaded Xcode 15.3 Beta then updated the phone to iOS 17.2 When running the simple example based code with all relevant suggestions eg. used Fred, use an @State to keep AVSpeechSynthesiser in scope. the app still throws:


#FactoryInstall Unable to query results, error: 5 Unable to list voice folder

When the AVSpeechSynthesizer() instance is created in the @main:

I've appended the relevant code snippet of the test class for reference. Although the detail is irrelevant as the class won't create without the errors.

frankusu: what did you do that's different?

If this is still broken Apple really need to fix this. I tried to use the CreatingACustomSpeechSynthesizer example app to make a work around but that has install issues.

Any new suggestions gladly received.

Thanks all, Maz

class VoiceControl: NSObject, ObservableObject { let synthesizer = AVSpeechSynthesizer() // tried the @State var synthesizer = AVSpeechSynthesizer() approach and that failed too override init() { super.init() //AVSpeechSynthesisVoice.speechVoices() // <-- fetch voice dependencies I tried this and it throws: "Unable to list voice folder" at init }

func askAQuestion (_ theQuestion: String){
    // Create an utterance.
    let utterance = AVSpeechUtterance(string: "The quick brown fox jumped over the lazy dog.")
    // Configure the utterance.
    utterance.rate = 0.57
    utterance.pitchMultiplier = 0.8
    utterance.postUtteranceDelay = 0.2
    utterance.volume = 0.8
    
    // Retrieve the Fred voice as it's the one that works for some.
    if let voice = AVSpeechSynthesisVoice(identifier: "com.apple.speech.synthesis.voice.Fred"){
        utterance.voice = voice
        self.synthesizer.speak(utterance)
    }
    else {
        print("Fred's not home man.")
    }
    

} ...

As a sanity check I installed this framework:

SwiftTTS from https://github.com/renaudjenny/swift-tts

wrapping it in this class:


import Combine import SwiftTTSCombine

class VoiceSynth: NSObject{

let engine: TTSEngine = SwiftTTSCombine.Engine()
var cancellables = Set<AnyCancellable>()

func sayThis(_ phrase: String){

    engine.speak(string: phrase)

}

func isSpeaking(){

    engine.isSpeakingPublisher
        .sink { isSpeaking in
            print("TTS is currently \(isSpeaking ? "speaking" : "not speaking")")
        }
        .store(in: &cancellables)
}

}

Then initialised the class in the @main with: var voiceSynth = VoiceSynth() And the app throws:

FactoryInstall Unable to query results, error: 5 Unable to list voice folder

As before.

Thanks Maz

I'm also experiencing this issue. It's very frustrating.

Can someone from Apple please provide an update on this?

I've had some success by moving the synthesizer out as a global property.

var synthesizer = AVSpeechSynthesizer()

class TextToSpeechPlayer: NSObject {
    //private var synthesizer = AVSpeechSynthesizer()

    override init() {
        super.init()
        prepareAudioSession()
        synthesizer.delegate = self
    }

    deinit {
        disableAudioSession()
    }