2016-05-17 74 views
3

我試圖讓iOS文本到語音合成器「說出」一個短語列表,並在短語之間添加一個可變延遲。例如,我可能希望它說「你好」,然後等待5秒鐘,然後「有人在嗎?」,然後等待10秒,然後說出「你好?」等等。Swift iOS Text To Speech不能在循環中使用「delay」

我在下面做了一個簡單的例子,說明了我正在嘗試做什麼。我知道語音合成器正在講話,額外的話語被添加到隊列中,並按照他們接收的順序講話。

我試過很多方法來實現循環中的延遲。使用打印語句測試延遲證實它們正在工作,但它們似乎干擾了文本說明功能,它說明第一個短語,但等到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. 設置的類作爲一個代表語音合成器並運行一段時間循環,直到合成器完成。基於

  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. 睡眠()

+0

選項3可能是最好的。你需要確保你的閉包檢索下一個文本並說出它。如果你只是一次提交所有的話語,就像現在一樣,它將不起作用 – Paulw11

回答

4

這樣的事情如何:

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.")]) 
+0

謝謝!我只是測試了這一點,它的工作方式如上所述。你能向我解釋爲什麼阻塞循環不起作用嗎? – benglish

+1

你永遠不想在主線程上運行很長時間,所以用'sleep'阻塞是一個非常糟糕的主意。如果你這樣做了,你的應用用戶界面會凍結整個你說句子的時間。 'delay'通過調度'{}'中的代碼在稍後的時間在主線程上運行。這允許'speak'返回並運行其他線程並讓您的UI保持響應。然後在等待延遲之後,閉包('{}'中的代碼)在主線程上運行,並在調度下一個之前執行另一小塊語音。 – vacawama

+0

歡迎來到Stack Overflow。如果我已經回答了您的問題,請點擊左側的複選標記將其轉爲綠色,以接受此問題。這讓其他人知道你的問題已經得到解答。 – vacawama