Skip to content

[iOS] Calling speak() after stop() does not work – AVSpeechSynthesizer never speaks again until app restart #627

@thanhit93

Description

@thanhit93

Environment

  • flutter_tts version: 4.2.5 (or your version)
  • Platform: iOS (device/simulator, e.g. iOS 17)
  • Flutter: (e.g. 3.24.x)

Summary

On iOS, after calling stop() (or pause() then stop()), any subsequent call to speak() has no effect: no sound, no error. TTS only works again after killing and reopening the app.

Root cause (from debugging)

The plugin uses a single AVSpeechSynthesizer instance (in SwiftFlutterTtsPlugin.swift: let synthesizer = AVSpeechSynthesizer()). On iOS, after stopSpeaking(at: .immediate), this synthesizer can end up in a state where it no longer accepts speak(utterance) – a known limitation / bug with AVSpeechSynthesizer in some iOS versions. Creating a new FlutterTts() in Dart does not help because all Dart instances share the same MethodChannel('flutter_tts') and thus the same native synthesizer.

Steps to reproduce

  1. Create a screen with TTS: play, then stop, then play again.
  2. Call await flutterTts.speak("First sentence");, wait or let it play.
  3. Call await flutterTts.stop(); (or pause then stop).
  4. Call await flutterTts.speak("Second sentence");.
  5. Expected: Second sentence is spoken.
    Actual: No sound. TTS only works again after restarting the app.

Suggested fix

On the iOS side, allow recreating the synthesizer so the next speak() uses a fresh instance:

  1. Change synthesizer from let to var in SwiftFlutterTtsPlugin.swift.
  2. Add a new method (e.g. resetSynthesizer) that does:
    • self.synthesizer = AVSpeechSynthesizer()
    • self.synthesizer.delegate = self
  3. Expose it via the method channel (e.g. a new method resetSynthesizer) so that apps can call it before the next speak() when they have just called stop().

Alternatively, the plugin could call this reset internally after each stop() on iOS, so that the next speak() always uses a new synthesizer.

Workaround (for maintainers / other users)

Until the plugin supports this, apps can patch the plugin’s iOS Swift file (e.g. with a post–pub get script) to add the above logic and call the new method from Dart via MethodChannel('flutter_tts').invokeMethod('resetSynthesizer') before each speak() on iOS. This workaround has been tested and fixes the issue.

References


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions