iosxcodeavfoundationavspeechsynthesizeravspeechutterance

Change AVSpeechUtterance rate in real time


I am currently developing an iOS app that converts text to speech using AVSynthesizer.

What I want to do is that while the synthesizer is speaking, utterance rate can be changed and with a slider and the speed of the speaking changes.

I am doing this in the IBAction of the slider: self.utterance = sender.value

but the synthesizer doesn't change the speed. I've been looking for information but I haven't found something yet. What cand I do? Thanks in advance.


Solution

  • Ok, so after playing a bit with this cool feature I wasn't aware of, I found out a way to change the rate of utterance. The main problem is that the utterance is currently enqueued by synthetizer, the rate can't be changed. Corresponds to the documentation:

    /* Setting these values after a speech utterance has been enqueued will have no effect. */
    
    open var rate: Float // Values are pinned between AVSpeechUtteranceMinimumSpeechRate and AVSpeechUtteranceMaximumSpeechRate.
    
    open var pitchMultiplier: Float // [0.5 - 2] Default = 1
    
    open var volume: Float // [0-1] Default = 1
    

    So the workaround would be to stop the synthesizer and feed him new utterance with trimmed string.

    import UIKit
    import AVFoundation
    
    class ViewController: UIViewController {
        var synthesizer: AVSpeechSynthesizer!
        var string: String!
        var currentRange: NSRange = NSRange(location: 0, length: 0)
    
        @IBAction func sl(_ sender: UISlider) {
            synthesizer.stopSpeaking(at: .immediate)
    
            if currentRange.length > 0 {
                let startIndex = string.index(string.startIndex, offsetBy: NSMaxRange(currentRange))
                let newString = string.substring(from: startIndex)
                string = newString
                synthesizer.speak(buildUtterance(for: sender.value, with: string))
            }
        }
    
        func buildUtterance(for rate: Float, with str: String) -> AVSpeechUtterance {
            let utterance = AVSpeechUtterance(string: str)
            utterance.rate = rate
            utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
            return utterance
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            string = "I am currently developing an iOS app that converts text to speech using AVSynthesizer.What I want to do is that while the synthesizer is speaking, utterance rate can be changed and with a slider and the speed of the speaking changes. I am doing this in the IBAction of the slider: self.utterance = sender.value but the synthesizer doesn't change the speed. Ive been looking for information but I haven't found something yet. What can I do? Thanks in advance."
    
            synthesizer = AVSpeechSynthesizer()
            synthesizer.delegate = self
            synthesizer.speak(buildUtterance(for: AVSpeechUtteranceDefaultSpeechRate, with: string))
        }
    }
    
    extension ViewController: AVSpeechSynthesizerDelegate {
        func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
            debugPrint(characterRange.toRange())
            currentRange = characterRange
        }
    }
    
    1. implement the delegate method willSpeakRangeOfSpeechString of AVSpeechSynthesizerDelegate and define the delegate of synthesizer as self: synthesizer.delegate = self

    2. In that delegate method, save the characterRange which would be spoken next.

    3. Bind IBAction func sl(_ sender: UISlider) to your slider's event touchUpInside.

    4. in that IBAction, stop speaking, and get the substring of your current text being spoken from index it would've continue.

    5. Build new utterance and start speaking it

    6. Profit.