【问题标题】:Controlling the loop execution控制循环执行
【发布时间】:2021-02-10 22:52:31
【问题描述】:

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

只有在语音合成任务完成后,才能让循环切换到下一个项目?

编辑:也许,循环可能每次都等待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")
    }
}

【问题讨论】:

  • 你研究过信号量吗?
  • 不知道信号量。看起来非常非常有趣!!!这对我来说是一个发现。我去找找。
  • 另一种方法是使用带有完成处理程序的递归函数,从您的didFinish 函数调用。
  • 我希望以递归方式更好地控制暂停,因为我理解它总是相同的暂停时间。而且我不能每次都做不同的停顿(可以吗?)

标签: swift avfoundation


【解决方案1】:

你总是可以为此使用 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)
         }
 }

编辑

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

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 实例不会给你。

【讨论】:

  • 你能添加更多关于那里发生的事情的背景
  • 感谢您的回复!我在timer 的行中有一个错误Expected expression in list of expressions
  • @MikeMaus,我错过了领先的 .对于in: default 部分,它应该是in: .default。我会更新
  • 您可以将var playerCancellable: AnyCancellable? = nil 设为私有@State var 或其他东西。我在操场上运行代码,我没有将它包装在 SwiftUI 视图中
  • 你的意思是想让定时器根据要阅读的内容以不同的时间间隔触发吗?
猜你喜欢
  • 2017-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-31
  • 1970-01-01
相关资源
最近更新 更多