按顺序执行文本到语音

问题描述

我想合成文本。我有一系列句子和一系列停顿,我希望在这些句子之间。

这是什么想法 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-1index+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)
            
        })