问题描述
我想合成文本。我有一系列句子和一系列停顿,我希望在这些句子之间。
这是什么想法
Synthesize -> start the timer,timer fires after provided time -> Synthesize -> start the timer -> Synt...
偶然地,我注意到计时器首先触发较短的时间,而不是按顺序执行和设置计时器。循环不会等到合成器完成发音,它会继续运行。
如何确定合成器用提供的停顿和顺序发音句子?
import SwiftUI
struct KingsspeechView: View {
@Observedobject var speaker = Speaker()
@State private var subtitles = ""
@State private var currentStepIndex = 0
let kingsspeech = [
"Hello. Let's start the Game! Let the hunger Games Begin...Whoa-Whoa. Here're are the rules on the screen.","Okey,Now that you kNow the rules,chill out. Let's play another game.","You say Hi,I say Ho.","Hooo","Hooo"
]
var pauses = [0.0,20.0,90.0,40.0,40.0]
// try to change into this
// var pauses = [0.0,10.0,5.0,5.0]
// the sequence of execution is completely different
// the ones that has less value,will execute first
// While I expected it to execute in order it is in array,instead it runs as it runs (wants)
// (or maybe it's the case it's just one timer for all)
// How to prevent loop from continuing to new iteration until the speech is not pronounced?
var body: some View {
vstack {
Text(subtitles)
.padding(.bottom,50)
.padding(.horizontal,20)
Button("Play") {
playSound()
}
}
}
func playSound() {
for step in 0..<kingsspeech.count {
let timer = Timer.scheduledTimer(withTimeInterval: pauses[step],repeats: false) { timer in
subtitles = kingsspeech[step]
speaker.speak("\(kingsspeech[step])")
print("I am out")
currentStepIndex += 1
// I've tried to stop a loop from moving on,before the speech had finished to pronounce
// with some sort of a condition maybe; by index or by identifying if the synthesizer is speaking
// but it even turned out that timer executes completely different,look in time arrays above
// while speaker.semaphoreIndex == step {
// print("still waiting")
// }
// while speaker.synth.isspeaking {
//
// }
}
}
}
}
...
import AVFoundation
import Combine
class Speaker: NSObject,ObservableObject,AVSpeechSynthesizerDelegate {
let synth = AVSpeechSynthesizer()
// started to try something with simophore,but didn't understand how to implement it
var semaphore = dispatchSemaphore(value: 0)
var semaphoreIndex = 0
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.4
synth.speak(utterance)
}
}
extension Speaker {
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,didFinish utterance: AVSpeechUtterance) {
print("all done")
semaphore.signal()
semaphoreIndex += 1
}
}
解决方法
只需说出一个话语,接收委托方法,然后在该方法中等待所需的时间间隔,然后继续下一个话语和时间间隔。
这是一个完整的例子。它使用 Cocoa 项目,而不是 SwiftUI,但您可以轻松地对其进行调整。
import UIKit
import AVFoundation
func delay(_ delay:Double,closure:@escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when,execute: closure)
}
class Speaker : NSObject,AVSpeechSynthesizerDelegate {
var synth : AVSpeechSynthesizer!
var sentences = [String]()
var intervals = [Double]()
func start(_ sentences: [String],_ intervals: [Double]) {
self.sentences = sentences
self.intervals = intervals
self.synth = AVSpeechSynthesizer()
synth.delegate = self
self.sayOne()
}
func sayOne() {
if let sentence = sentences.first {
sentences.removeFirst()
let utter = AVSpeechUtterance(string: sentence)
self.synth.speak(utter)
}
}
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,didFinish utterance: AVSpeechUtterance) {
if let interval = intervals.first {
intervals.removeFirst()
delay(interval) {
self.sayOne()
}
}
}
}
class ViewController: UIViewController {
let speaker = Speaker()
override func viewDidLoad() {
super.viewDidLoad()
let sentences = [
"I will speak again in one second","I will speak again in five seconds","I will speak again in one second","Done"]
let intervals = [1.0,5.0,1.0]
self.speaker.start(sentences,intervals)
}
}
,
尝试回答我在评论中提出的解决方案问题: 目前,它可以播放/暂停
TODO:现在我必须发现如何在句子之间向后/向前跳转。所以,为此我首先需要停止当前的语音任务。 speaker.synth.stopSpeaking(at: .word)
然后,我也许应该有一些索引来跟踪当前阶段是什么。然后,当我停止任务时,我记住了索引。我可以倒退/前进。现在从 index-1
或 index+1
处开始,而不是从头开始。
@State private var isPlaying = false
...
// play button
Button(action: {
if isPlaying {
isPlaying.toggle()
speaker.synth.pauseSpeaking(at: .word)
} else {
isPlaying.toggle()
// continue playing here if it was paused before,else ignite speech utterance
if speaker.synth.isPaused {
speaker.synth.continueSpeaking()
} else {
speaker.speak()
}
}
},label: {
Image(systemName: (isPlaying ? "pause.fill" : "play.fill"))
.resizable()
.scaledToFit()
.frame(width: 50,height: 50)
})