【问题标题】:Swift iOS Text To Speech not working with "delay" in loopSwift iOS Text To Speech 不能在循环中使用“延迟”
【发布时间】:2016-09-14 05:03:59
【问题描述】:

我正在尝试让 iOS 文本到语音合成器“说出”一个短语列表,这些短语之间的延迟可变。例如,我可能希望它说“你好”,然后等待 5 秒,然后“有人在吗?”,然后等待 10 秒,然后说“你好?”......等等。

我在下面做了一个简单的例子来说明我想要做什么。我知道语音合成器正在说话,额外的话语被添加到队列中,并按照接收到的顺序说出。

我尝试了很多方法来实现循环中的这种延迟。使用打印语句测试延迟确认它们正在工作,但它们似乎干扰了文本语音功能,该功能说第一个短语,但要等到 for 循环完成后再说出其余部分。我认为任何这些类型的延迟都会起作用,因为我假设语音合成器是事件驱动的。

我会很感激一些帮助,或者至少了解它为什么不起作用。谢谢!

这里是示例代码:iPhone 6 模拟器,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.

        }
    }
}

以下是我尝试过的一些延迟方法:

  1. 将类设置为语音合成器的委托,并运行一个 while 循环,直到合成器完成。

  2. 基于时间的延迟:referenceDate = NSDate() while(NSDate().timeIntervalSinceDate(referenceDate) < 0.5) {}

  3. 我已经尝试过堆栈中的“延迟”解决方案,例如: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) }

  4. 睡眠()

【问题讨论】:

  • 选项 3 可能是最好的。您需要确保您的闭包检索下一段文本并说出它。如果你只是像现在一样一次提交所有的话语,那是行不通的

标签: ios swift delay text-to-speech


【解决方案1】:

这样的事情怎么样:

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)
                }
            }
        }
    }    
}

注意事项:

  • 一个元组数组被传递给speak。一个元组对包含一个要说出的短语和在说出下一个短语之前要等待的延迟。
  • speak 从数组中取出第一项,说出短语并将数组的其余部分(如果不为空)在等待延迟后再次传递给 speak
  • delay 由@matt 编写,来自here

由于最后一次延迟没有任何用处,您可以将其转过来,在延迟后说出第一句话。

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)
            }
        }
    }
}

你会像这样使用这个:

// Wait 5 seconds before starting...
speak([(5.0, "I'm sorry Dave."), (2.0, "I can't do that.")])

【讨论】:

  • 谢谢!我刚刚对此进行了测试,它按描述工作。你能解释一下为什么阻塞循环不起作用吗?
  • 你永远不想在主线程上运行很长时间,所以用sleep 阻塞是一个非常糟糕的主意。如果你这样做了,你的应用程序 UI 会在你说一个句子的整个过程中冻结。 delay 通过调度 {} 中的代码稍后在主线程上运行来工作。这允许speak 返回并允许其他线程运行,并使您的 UI 保持响应。然后在等待延迟之后,闭包({} 中的代码)在主线程上运行,并在安排下一个之前再做一小段语音。
  • 你明白了!感谢您的及时回复和解释!去红衣主教!
猜你喜欢
  • 1970-01-01
  • 2016-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多