控制循环执行

Controlling the loop execution

我正在尝试用时间间隔发音句子,但问题是在合成器第一次发音后,循环会直接运行到最后。台词很好,但中间没有停顿。

如何在语音合成任务完成后才循环切换到下一个项目?

编辑:也许,循环每次都等待 didFinish,然后 didFinish 告诉循环什么时候可以继续?

let speaker = Speaker()
let capitals = ["Canberra is the capital of Australia", "Seoul is the capital of South Korea", "Tokyo is the capital of Japan", "Berlin is the capital of Germany"]

var body: some View {

    Button("Play Sound") {
        playSound()
    }    
}

func playSound() {
        for item in 0..<capitals.count {
            let timer = Timer.scheduledTimer(withTimeInterval: 20, repeats: false) { timer in
                
                speaker.speak("\(capitals[item])")
                print("I am out")
            }
        }
 }

...

import AVFoundation

class Speaker: NSObject {
    let synth = AVSpeechSynthesizer()

    override init() {
        super.init()
        synth.delegate = self
    }

    func speak(_ string: String) {
        let utterance = AVSpeechUtterance(string: string)
        utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
        utterance.rate = 0.5
        synth.speak(utterance)
    }
}

extension Speaker: AVSpeechSynthesizerDelegate {
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        print("all done")
    }
}

您始终可以为此使用 Combine

import Combine

let speaker = Speaker()
let capitals = ["Canberra is the capital of Australia", "Seoul is the capital of South Korea", "Tokyo is the capital of Japan", "Berlin is the capital of Germany"]
var playerCancellable: AnyCancellable? = nil

 Button("Play Sound") {
     playSound()
 }    

func playSound() {
     // Fairly standard timer publisher. The call to .autoconnect() tells the timer to start publishing when subscribed to.
     let timer = Timer.publish(every: 20, on: .main, in: .default)
         .autoconnect()
    
    // Publishers.Zip takes two publishers. 
    // It will only publish when there is a "symmetrical" output. It behaves in a similar manner as `zip` on sequences.
    // So, in this scenario, you will not get the next element of your array until the timer emits another event.
    // In the call to sink, we ignore the first element of the tuple relating to the timer
    playerCancellable = Publishers.Zip(timer, capitals.publisher)
         .sink { _, item in
             speaker.speak(item)
         }
 }

编辑

您在评论中提到您希望能够可变地控制话语之间的延迟。这不是真正可以使用 Timer 的东西。我进行了一些修改,因为我发现这是一个有趣的问题,并且能够按照您在评论中描述的那样进行这项工作:

class Speaker: NSObject {
  let synth = AVSpeechSynthesizer()

  private var timedPhrases: [(phrase: String, delay: TimeInterval)]
  // This is so you don't potentially block the main queue
  private let queue = DispatchQueue(label: "Phrase Queue")
  
  override init() {
    timed = []
    super.init()
    synth.delegate = self
  }
  
  init(_ timedPhrases: [(phrase: String, delay: TimeInterval)]) {
    self.timedPhrases = timedPhrases
    super.init()
    synth.delegate = self
  }
  
  private func speak(_ string: String) {
    let utterance = AVSpeechUtterance(string: string)
    utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
    utterance.rate = 0.5
    synth.speak(utterance)
  }
  
  func speak() {
    guard let first = timed.first else { return }
    speak(first.value)
    timed = Array(timed.dropFirst())
  }
}

extension Speaker: AVSpeechSynthesizerDelegate {
  func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
    if !timed.isEmpty {
      queue.sync {
        Thread.sleep(forTimeInterval: TimeInterval(timed.first!.delay))
        self.speak()
      }
    } else {
      print("all done")
    }
  }
}

let speaker = let speaker = Speaker([
    (phrase: "1", delay: 0),
    (phrase: "2", delay: 3),
    (phrase: "3", delay: 1),
    (phrase: "4", delay: 5),
    (phrase: "5", delay: 10)
])

speaker.speak()

对此持怀疑态度。我真的不认为使用 Thread.sleep 是一个很好的做法,但也许这会给你一些关于如何处理它的想法。如果你想要可变时间,Timer 实例不会给你。