Search code examples
swiftxcodetext-to-speechspeechavspeechsynthesizer

AVSpeechSynthesizer not working when new strings are added to be spoken [xcode - swift 4]


Im using the code below to read random sentences at a random time. However I run into the problem when a random sentence is called to be read while the previous sentence is still being spoken by the AVSpeechSynthesizer, making the second sentence not spoken. What I am asking is, how can I get the second sentence to be spoken after the first sentence is finished being spoken??

Any import would be appreciated. Cheers

Heres my code:

import UIKit
import AVFoundation


class ViewController: UIViewController {

var myTimer = Timer()
   let string = ["what kind of car do you have?", "do you like the beach?","did you bring a towel?","There are big waves today"]
var randomTimer = Int()


@objc func speakToMe(){

    let random = Int.random(in: 0...3)
    randomTimer = Int.random(in: 0...2)
    print(randomTimer)
    print(string[random])


let utterance = AVSpeechUtterance(string: string[random])
utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
utterance.rate = 0.1
let synthesizer = AVSpeechSynthesizer()
synthesizer.speak(utterance)

}

override func viewDidLoad() {
    super.viewDidLoad()
    speakToMe()
    myTimer = Timer.scheduledTimer(timeInterval: TimeInterval(randomTimer), target: self, selector: #selector(ViewController.speakToMe), userInfo: nil, repeats: true)
}


}   

Solution

  • You can simply use AVSpeechSynthesizerDelegate for that and you can remove timer from your code.

    And to use AVSpeechSynthesizerDelegate first you need to confirm your view controller with AVSpeechSynthesizerDelegate like shown below:

    class ViewController: UIViewController, AVSpeechSynthesizerDelegate {
    

    next thing is you need to add

    synthesizer.delegate = self
    

    in your viewDidLoad method. and you need to declare

    let synthesizer = AVSpeechSynthesizer()
    

    outside the methods and inside the class.

    And you can use randomElement property to find random element from string array.

    And your final code will look like:

    import UIKit
    import AVFoundation
    
    class ViewController: UIViewController, AVSpeechSynthesizerDelegate {
    
        let string = ["what kind of car do you have?", "do you like the beach?","did you bring a towel?","There are big waves today"]
        let synthesizer = AVSpeechSynthesizer()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            synthesizer.delegate = self
            speakToMe()
        }
    
        @objc func speakToMe(){
    
            let utterance = AVSpeechUtterance(string: string.randomElement()!)
            utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
            utterance.rate = 0.1
            synthesizer.speak(utterance)
    
        }
    
        func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
            speakToMe()
        }
    }
    

    EDIT:

    Since you are using only 4 elements in your array there will be possibilities to repeat same sentence many time when you take random string for it so you can add one more logic here which will prevent it.

    Update your speakToMe function like below:

    @objc func speakToMe(){
    
        var randomStr = string.randomElement()!
        while previousStr == randomStr {
            randomStr = string.randomElement()!
        }
        previousStr = randomStr
        let utterance = AVSpeechUtterance(string: randomStr)
        utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
        utterance.rate = 0.1
        synthesizer.speak(utterance)
    }
    

    And declare var previousStr = "" outside the function.