如何修复我的应用程序中使用 AVSpeechSynthesizer 播放播放列表的这个相当复杂的错误?

How can I make fix this quite complex bug in my app that uses AVSpeechSynthesizer to play a playlist?

(如果有人能想到更好的标题,请编辑!)

我注意到通常,当我调用 AVSpeechSynthesizer.stopSpeaking(at: .immediate) 时,会调用 didCancel 委托方法。但是,如果我在最后一个音节停止合成器,它会调用 didFinish.

重现步骤:

使用一个按钮创建一个应用程序。将按钮的 IBAction 连接到此方法:

let synthesiser = AVSpeechSynthesizer()
@IBAction func click() {
    if synthesiser.isSpeaking {
        print("Manually stopping")
        synthesiser.stopSpeaking(at: .immediate)
    } else {
        let utterance = AVSpeechUtterance(string: "One Two Three Four Internationalization")
        utterance.rate = AVSpeechUtteranceMinimumSpeechRate
        utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
        synthesiser.delegate = self
        synthesiser.speak(utterance)
    }
}

实现这两个委托方法:

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
    print("Stopped!")
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
    print("Cancelled!")
}

按下按钮开始合成,再次按下停止。如果你停在中间,它会打印:

Manually stopping
Cancelled!

这是预期的。如果你让它说整行,最终它会打印:

Stopped!

这也是意料之中的。但是,如果您在最后一个音节处停止它,那么它会打印:

Manually stopping
Stopped!

我预计第二行是"Cancelled!",就像停在中间一样。


为什么这对我很重要?

在我的应用程序中,我有一个用户可以播放的话语列表(即播放列表)。用户可以按按钮转到上一个或下一个话语。如果一段话播放完,则自动播放下一段话。

// these are properties of the VC
var playlist: Playlist!
var currentIndex = 0 {
    didSet {
        updateTextView()
    }
}

var currentUtterance: Utterance {
    return playlist.items[currentIndex]
}

var isPlaying = false

// plays the previous utterance in the playlist
@objc func previousPressed() {
    guard currentIndex > 0 else { return }
    previous()
}

// plays the next utterance in the playlist
@objc func nextPressed() {
    guard currentIndex < playlist.items.count - 1 else { return }
    next()
}

func next() {
    currentIndex += 1
    playCurrentUtterance()
}

func previous() {
    currentIndex -= 1
    playCurrentUtterance()
}

func playCurrentUtterance() {
    speechSynthesiser.stopSpeaking(at: .immediate)
    speechSynthesiser.speak(currentUtterance.avUtterance)
    isPlaying = true
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
    if currentIndex >= playlist.items.count - 1 { // this checks whether this is the last utterance
        isPlaying = false
    } else {
        // when one utterance finishes, play the next one
        next()
    }
}

当用户在合成器说出最后一个音节时转到下一个或上一个话语时,就会出现该错误。假设用户转到下一个话语。 speechSynthesiser.stopSpeaking(at: .immediate) 导致 didFinish 被调用,所以 next 实际上被调用了两次(一次被 nextPressed 一次被 didFinish 调用)!结果,currentIndex 增加了两次! playCurrentUtterance 也被调用了两次,这意味着 speechSynthesiser.speak 被调用了两次。但是,合成器一次只能说一个话语,并且仍在说 next 话语(与 next next 话语相反)。

我该如何修复这个错误?

将此 属性 添加到您的 viewController:

var manuallyStopping = false

调用时设置stopSpeaking():

manuallyStopping = synthesizer.stopSpeaking(at: .immediate)

didFinish()中勾选,如果设置了就按didCancel()处理,然后再设置false.

同时在didCancel()

中将manuallyStopping设置为false