I'm trying to have the iOS text-to-speech synthesizer "say" a list of phrases with a variable delay between the phrases. For example, I may want to it say "Hello", then wait 5 seconds, then "Is anyone there?", then wait 10 seconds, then say "Hello?"...etc.
I've made a simple example below that illustrates what I am trying to do. I know that the speech synthesizer is speaking, additional utterances are added to a queue and spoken in the order they are received.
I've tried many ways to achieve this delay in the loop. Testing delays with a print statement confirms they are working, but they seem to be interfering with the text-speach-functionality which says the first phrase but waits until the for-loop is done before saying the rest. I thought that any of these types of delays would work as I assume the speech synthesizer is event driven.
I'd appreciate some help, or at least an insight it to why it isn't working. Thanks!
Here is the example code: iPhone 6 simulator, Xcode 7.3
import UIKit
import AVFoundation
class ViewController: UIViewController {
let speechSynthesizer = AVSpeechSynthesizer()
var phraseArray: [String] = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"]
override func viewDidLoad() {
super.viewDidLoad()
for phrase in phraseArray{
let speechUtterance = AVSpeechUtterance(string: phrase)
speechSynthesizer.speakUtterance(speechUtterance)
//"delay()" goes here. It needs to be a variable length delay.
}
}
}
Here are some of the delay methods that I have tried:
Setup the class as a delegate for the speech synthesizer and run a while loop until the synthesizer is finished.
Time based delay: referenceDate = NSDate() while(NSDate().timeIntervalSinceDate(referenceDate) < 0.5) {}
I've tried "delay" solutions from stack, like this one: Swift delay in loop
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
Sleep()
How about something like this:
import UIKit
import AVFoundation
func delay(_ delay:Double, closure:@escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
class ViewController: UIViewController {
let speechSynthesizer = AVSpeechSynthesizer()
override func viewDidLoad() {
super.viewDidLoad()
speak([("Hello", 5.0), ("Is there anyone there?", 10.0), ("Hello?", 0.0)])
}
func speak(_ phrases: [(phrase: String, wait: Double)]) {
if let (phrase, wait) = phrases.first {
let speechUtterance = AVSpeechUtterance(string: phrase)
speechSynthesizer.speak(speechUtterance)
let rest = Array(phrases.dropFirst())
if !rest.isEmpty {
delay(wait) {
self.speak(rest)
}
}
}
}
}
Notes:
speak
. A tuple pair contains a phrase to speak and a delay to wait before the next phrase is spoken.speak
takes the first item from the array, speaks the phrase and passes the rest of the array (if not empty) to speak
again after waiting for the delay.delay
was written by @matt and comes from here.Since the last delay does nothing useful, you can turn it around and have the first phrase spoken after a delay.
func speak(_ phrases: [(wait: Double, phrase: String)]) {
if let (wait, phrase) = phrases.first {
delay(wait) {
let speechUtterance = AVSpeechUtterance(string: phrase)
self.speechSynthesizer.speak(speechUtterance)
let rest = Array(phrases.dropFirst())
if !rest.isEmpty {
self.speak(rest)
}
}
}
}
You would use this one like this:
// Wait 5 seconds before starting...
speak([(5.0, "I'm sorry Dave."), (2.0, "I can't do that.")])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With